PageRenderTime 57ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/framework/Mage/Selenium/TestCase.php

https://github.com/KNXSebastian/taf
PHP | 3668 lines | 2361 code | 215 blank | 1092 comment | 282 complexity | 9a032bdf8a5c7e4b071e2f310c654569 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category tests
  22. * @package selenium
  23. * @subpackage Mage_Selenium
  24. * @author Magento Core Team <core@magentocommerce.com>
  25. * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
  26. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  27. */
  28. /**
  29. * An extended test case implementation that adds useful helper methods
  30. *
  31. * @package selenium
  32. * @subpackage Mage_Selenium
  33. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  34. * @method Core_Mage_AdminUser_Helper adminUserHelper()
  35. * @method Core_Mage_AttributeSet_Helper attributeSetHelper()
  36. * @method Core_Mage_Category_Helper categoryHelper()
  37. * @method Core_Mage_CheckoutMultipleAddresses_Helper checkoutMultipleAddressesHelper()
  38. * @method Core_Mage_CheckoutOnePage_Helper checkoutOnePageHelper()
  39. * @method Core_Mage_CmsPages_Helper cmsPagesHelper()
  40. * @method Core_Mage_CmsPolls_Helper cmsPollsHelper()
  41. * @method Core_Mage_CmsStaticBlocks_Helper cmsStaticBlocksHelper()
  42. * @method Core_Mage_CmsWidgets_Helper cmsWidgetsHelper()
  43. * @method Core_Mage_CompareProducts_Helper compareProductsHelper()
  44. * @method Core_Mage_CustomerGroups_Helper customerGroupsHelper()
  45. * @method Core_Mage_Customer_Helper customerHelper()
  46. * @method Core_Mage_Installation_Helper installationHelper()
  47. * @method Core_Mage_Newsletter_Helper newsletterHelper()
  48. * @method Core_Mage_OrderCreditMemo_Helper orderCreditMemoHelper()
  49. * @method Core_Mage_OrderInvoice_Helper orderInvoiceHelper()
  50. * @method Core_Mage_OrderShipment_Helper orderShipmentHelper()
  51. * @method Core_Mage_Order_Helper orderHelper()
  52. * @method Core_Mage_Paypal_Helper paypalHelper()
  53. * @method Core_Mage_PriceRules_Helper priceRulesHelper()
  54. * @method Core_Mage_ProductAttribute_Helper productAttributeHelper()
  55. * @method Core_Mage_Product_Helper productHelper()
  56. * @method Core_Mage_Rating_Helper ratingHelper()
  57. * @method Core_Mage_Review_Helper reviewHelper()
  58. * @method Core_Mage_ShoppingCart_Helper shoppingCartHelper()
  59. * @method Core_Mage_Store_Helper storeHelper()
  60. * @method Core_Mage_SystemConfiguration_Helper systemConfigurationHelper()
  61. * @method Core_Mage_Tags_Helper tagsHelper()
  62. * @method Core_Mage_Tax_Helper taxHelper()
  63. * @method Core_Mage_Wishlist_Helper wishlistHelper()
  64. */
  65. class Mage_Selenium_TestCase extends PHPUnit_Extensions_SeleniumTestCase
  66. {
  67. ################################################################################
  68. # Framework variables and constant #
  69. ################################################################################
  70. /**
  71. * Configuration object instance
  72. * @var Mage_Selenium_TestConfiguration
  73. */
  74. protected $_testConfig;
  75. /**
  76. * Config helper instance
  77. * @var Mage_Selenium_Helper_Config
  78. */
  79. protected $_configHelper;
  80. /**
  81. * UIMap helper instance
  82. * @var Mage_Selenium_Helper_Uimap
  83. */
  84. protected $_uimapHelper;
  85. /**
  86. * Data helper instance
  87. * @var Mage_Selenium_Helper_Data
  88. */
  89. protected $_dataHelper;
  90. /**
  91. * Params helper instance
  92. * @var Mage_Selenium_Helper_Params
  93. */
  94. protected $_paramsHelper;
  95. /**
  96. * Data Generator helper instance
  97. * @var Mage_Selenium_Helper_DataGenerator
  98. */
  99. protected $_dataGeneratorHelper;
  100. /**
  101. * Array of Test Helper instances
  102. * @var array
  103. */
  104. protected static $_testHelpers = array();
  105. /**
  106. * Framework setting
  107. * @var array
  108. */
  109. public $frameworkConfig;
  110. /**
  111. * Saves HTML content of the current page if the test failed
  112. * @var bool
  113. */
  114. protected $_saveHtmlPageOnFailure = false;
  115. /**
  116. * Timeout in ms
  117. * @var int
  118. */
  119. protected $_browserTimeoutPeriod = 40000;
  120. /**
  121. * Name of the first page after logging into the back-end
  122. * @var string
  123. */
  124. protected $_firstPageAfterAdminLogin = 'dashboard';
  125. /**
  126. * Array of messages on page
  127. * @var array
  128. */
  129. protected static $_messages = array();
  130. /**
  131. * Name of run Test Class
  132. * @var null
  133. */
  134. public static $_testClass = null;
  135. /**
  136. * Name of last testcase in test class
  137. * @var array
  138. */
  139. protected static $_lastTestNameInClass = null;
  140. /**
  141. * Additional params for navigation URL
  142. * @var string
  143. */
  144. private $_urlPostfix;
  145. /**
  146. * Testcase error
  147. * @var boolean
  148. * @deprecated
  149. */
  150. protected $_error = false;
  151. /**
  152. * Type of uimap elements
  153. * @var string
  154. */
  155. const FIELD_TYPE_MULTISELECT = 'multiselect';
  156. /**
  157. * Type of uimap elements
  158. * @var string
  159. */
  160. const FIELD_TYPE_DROPDOWN = 'dropdown';
  161. /**
  162. * Type of uimap elements
  163. * @var string
  164. */
  165. const FIELD_TYPE_CHECKBOX = 'checkbox';
  166. /**
  167. * Type of uimap elements
  168. * @var string
  169. */
  170. const FIELD_TYPE_RADIOBUTTON = 'radiobutton';
  171. /**
  172. * Type of uimap elements
  173. * @var string
  174. */
  175. const FIELD_TYPE_INPUT = 'field';
  176. ################################################################################
  177. # Selenium variables(do not rename) #
  178. ################################################################################
  179. /**
  180. * @var PHPUnit_Extensions_SeleniumTestCase_Driver[]
  181. */
  182. protected $drivers = array();
  183. /**
  184. * @var string
  185. */
  186. protected $coverageScriptUrl = '';
  187. /**
  188. * @var bool
  189. */
  190. protected $captureScreenshotOnFailure = false;
  191. ################################################################################
  192. # Else variables #
  193. ################################################################################
  194. /**
  195. * Loading holder XPath
  196. * @staticvar string
  197. */
  198. protected static $xpathLoadingHolder = "//div[@id='loading-mask'][not(contains(@style,'display:') and contains(@style,'none'))]";
  199. /**
  200. * Constructs a test case with the given name and browser to test execution
  201. *
  202. * @param string $name Test case name(by default = null)
  203. * @param array $data Test case data array(by default = array())
  204. * @param string $dataName Name of Data set(by default = '')
  205. */
  206. public function __construct($name = null, array $data = array(), $dataName = '')
  207. {
  208. $this->_testConfig = Mage_Selenium_TestConfiguration::getInstance();
  209. $this->_configHelper = $this->_testConfig->getHelper('config');
  210. $this->_uimapHelper = $this->_testConfig->getHelper('uimap');
  211. $this->_dataHelper = $this->_testConfig->getHelper('data');
  212. $this->_paramsHelper = $this->_testConfig->getHelper('params');
  213. $this->_dataGeneratorHelper = $this->_testConfig->getHelper('dataGenerator');
  214. $this->frameworkConfig = $this->_configHelper->getConfigFramework();
  215. parent::__construct($name, $data, $dataName);
  216. $this->captureScreenshotOnFailure = $this->frameworkConfig['captureScreenshotOnFailure'];
  217. $this->_saveHtmlPageOnFailure = $this->frameworkConfig['saveHtmlPageOnFailure'];
  218. $this->coverageScriptUrl = $this->frameworkConfig['coverageScriptUrl'];
  219. $this->screenshotPath = $this->screenshotUrl = $this->getDefaultScreenshotPath();
  220. }
  221. /**
  222. * Delegate method calls to the driver. Overridden to load test helpers
  223. *
  224. * @param string $command Command (method) name to call
  225. * @param array $arguments Arguments to be sent to the called command (method)
  226. *
  227. * @return mixed
  228. */
  229. public function __call($command, $arguments)
  230. {
  231. $helper = substr($command, 0, strpos($command, 'Helper'));
  232. if ($helper) {
  233. $helper = $this->_loadHelper($helper);
  234. if ($helper) {
  235. return $helper;
  236. }
  237. }
  238. return parent::__call($command, $arguments);
  239. }
  240. /**
  241. * Loads a specific driver for the specified browser
  242. *
  243. * @param array $browser Defines what kind of driver, for a what browser will be loaded
  244. *
  245. * @return Mage_Selenium_Driver
  246. */
  247. protected function getDriver(array $browser)
  248. {
  249. if (!isset($browser['name'])) {
  250. $browser['name'] = '';
  251. }
  252. if (!isset($browser['browser'])) {
  253. $browser['browser'] = '';
  254. }
  255. if (!isset($browser['host'])) {
  256. $browser['host'] = 'localhost';
  257. }
  258. if (!isset($browser['port'])) {
  259. $browser['port'] = 4444;
  260. }
  261. if (!isset($browser['timeout'])) {
  262. $browser['timeout'] = 30;
  263. }
  264. if (!isset($browser['httpTimeout'])) {
  265. $browser['httpTimeout'] = 45;
  266. }
  267. $driver = new Mage_Selenium_Driver();
  268. $driver->setName($browser['name']);
  269. $driver->setBrowser($browser['browser']);
  270. $driver->setHost($browser['host']);
  271. $driver->setPort($browser['port']);
  272. $driver->setTimeout($browser['timeout']);
  273. $driver->setHttpTimeout($browser['httpTimeout']);
  274. $driver->setTestCase($this);
  275. $driver->setTestId($this->testId);
  276. $driver->setLogHandle($this->_testConfig->getLogFile());
  277. $driver->setBrowserUrl($this->_configHelper->getBaseUrl());
  278. $this->_browserTimeoutPeriod = $browser['timeout'] * 1000;
  279. $this->drivers[0] = $driver;
  280. return $driver;
  281. }
  282. /**
  283. * Implementation of setUpBeforeClass() method in the object context, called as setUpBeforeTests()<br>
  284. * Used ONLY one time before execution of each class (tests in test class)
  285. * @throws Exception
  286. */
  287. private function setUpBeforeTestClass()
  288. {
  289. $currentTestClass = get_class($this);
  290. static $setUpBeforeTestsError = null;
  291. if (self::$_testClass != $currentTestClass) {
  292. self::$_testClass = $currentTestClass;
  293. //work with xpath for IE
  294. $browser = $this->getBrowserSettings();
  295. if (strstr($browser['browser'], '*ie') !== false) {
  296. $this->useXpathLibrary('javascript-xpath');
  297. $this->allowNativeXpath(true);
  298. }
  299. $this->setLastTestNameInClass();
  300. try {
  301. $setUpBeforeTestsError = null;
  302. $this->setUpBeforeTests();
  303. } catch (Exception $e) {
  304. $setUpBeforeTestsError =
  305. "\nError in setUpBeforeTests method for '" . $currentTestClass . "' class:\n" . $e->getMessage();
  306. }
  307. if (isset($e)) {
  308. throw $e;
  309. }
  310. }
  311. if ($setUpBeforeTestsError !== null) {
  312. $this->markTestSkipped($setUpBeforeTestsError);
  313. }
  314. }
  315. /**
  316. * Prepare browser session
  317. */
  318. public function prepareBrowserSession()
  319. {
  320. $browsers = $this->_configHelper->getConfigBrowsers();
  321. if ($this->frameworkConfig['shareSession'] && empty(self::$browsers)) {
  322. $this->setupSpecificBrowser($browsers['default']);
  323. $this->shareSession($this->prepareTestSession());
  324. } elseif (empty(self::$browsers)) {
  325. $this->setupSpecificBrowser($browsers['default']);
  326. $this->prepareTestSession();
  327. } else {
  328. $this->frameworkConfig['shareSession'] = false;
  329. $this->prepareTestSession();
  330. }
  331. }
  332. final function setUp()
  333. {
  334. $this->clearMessages();
  335. $this->prepareBrowserSession();
  336. $this->setUpBeforeTestClass();
  337. }
  338. /**
  339. * Function is called before all tests in a test class
  340. * and can be used for some precondition(s) for all tests
  341. */
  342. public function setUpBeforeTests()
  343. {
  344. }
  345. /**
  346. * Define name of last testcase in test class
  347. */
  348. private function setLastTestNameInClass()
  349. {
  350. $testMethods = array();
  351. $class = new ReflectionClass(self::$_testClass);
  352. foreach ($class->getMethods() as $method) {
  353. if (PHPUnit_Framework_TestSuite::isPublicTestMethod($method)) {
  354. $testMethods[] = $method->getName();
  355. }
  356. }
  357. $testName = end($testMethods);
  358. $data = PHPUnit_Util_Test::getProvidedData(self::$_testClass, $testName);
  359. if ($data) {
  360. $testName .= sprintf(' with data set #%d', count($data) - 1);
  361. }
  362. self::$_lastTestNameInClass = $testName;
  363. }
  364. /**
  365. * Implementation of tearDownAfterAllTests() method in the object context, called as tearDownAfterTestClass()<br>
  366. * Used ONLY one time after execution of last test in test class
  367. * Implementation of tearDownAfterEachTest() method in the object context, called as tearDownAfterTest()<br>
  368. * Used after execution of each test in test class
  369. * @throws Exception
  370. */
  371. final function tearDown()
  372. {
  373. if ($this->hasFailed()) {
  374. if ($this->_saveHtmlPageOnFailure) {
  375. $this->saveHtmlPage();
  376. }
  377. if ($this->captureScreenshotOnFailure) {
  378. $this->takeScreenshot();
  379. }
  380. } else {
  381. $this->assertEmptyVerificationErrors();
  382. }
  383. $annotations = $this->getAnnotations();
  384. if (!isset($annotations['method']['skipTearDown'])) {
  385. try {
  386. $this->tearDownAfterTest();
  387. } catch (Exception $e) {
  388. }
  389. }
  390. try {
  391. if ($this->getName() == self::$_lastTestNameInClass) {
  392. $this->tearDownAfterTestClass();
  393. }
  394. } catch (Exception $_e) {
  395. if (!isset($e)) {
  396. $e = $_e;
  397. }
  398. }
  399. if (isset($e) && !$this->hasFailed()) {
  400. if ($this->_saveHtmlPageOnFailure) {
  401. $this->saveHtmlPage();
  402. }
  403. if ($this->captureScreenshotOnFailure) {
  404. $this->takeScreenshot();
  405. }
  406. }
  407. if (!$this->frameworkConfig['shareSession']) {
  408. $this->stop();
  409. }
  410. if (isset($e)) {
  411. throw $e;
  412. }
  413. }
  414. protected function tearDownAfterTestClass()
  415. {
  416. }
  417. protected function tearDownAfterTest()
  418. {
  419. }
  420. /**
  421. * Access/load helpers from the tests. Helper class name should be like "TestScope_HelperName"
  422. *
  423. * @param string $testScope Part of the helper class name which refers to the file with the needed helper
  424. *
  425. * @return object
  426. * @throws UnexpectedValueException
  427. */
  428. protected function _loadHelper($testScope)
  429. {
  430. if (empty($testScope)) {
  431. throw new UnexpectedValueException('Helper name can\'t be empty');
  432. }
  433. $helpers = $this->_testConfig->getTestHelperClassNames();
  434. if (!isset($helpers[ucwords($testScope)])) {
  435. throw new UnexpectedValueException('Cannot load helper "' . $testScope . '"');
  436. }
  437. $helperClassName = $helpers[ucwords($testScope)];
  438. if (!isset(self::$_testHelpers[$helperClassName])) {
  439. if (class_exists($helperClassName)) {
  440. self::$_testHelpers[$helperClassName] = new $helperClassName();
  441. } else {
  442. return false;
  443. }
  444. }
  445. if (self::$_testHelpers[$helperClassName] instanceof Mage_Selenium_TestCase) {
  446. foreach (get_object_vars($this) as $name => $value) {
  447. self::$_testHelpers[$helperClassName]->$name = $value;
  448. }
  449. }
  450. return self::$_testHelpers[$helperClassName];
  451. }
  452. /**
  453. * Retrieve instance of helper
  454. * @deprecated
  455. * @see _loadHelper()
  456. *
  457. * @param string $className
  458. *
  459. * @return Mage_Selenium_TestCase
  460. */
  461. public function helper($className)
  462. {
  463. $className = str_replace('/', '_', $className);
  464. if (strpos($className, '_Helper') === false) {
  465. $className .= '_Helper';
  466. }
  467. if (!isset(self::$_testHelpers[$className])) {
  468. if (class_exists($className)) {
  469. self::$_testHelpers[$className] = new $className;
  470. } else {
  471. return false;
  472. }
  473. }
  474. if (self::$_testHelpers[$className] instanceof Mage_Selenium_TestCase) {
  475. foreach (get_object_vars($this) as $name => $value) {
  476. self::$_testHelpers[$className]->$name = $value;
  477. }
  478. }
  479. return self::$_testHelpers[$className];
  480. }
  481. /**
  482. * Checks if there was error during last operations
  483. * @return boolean
  484. * @deprecated
  485. */
  486. public function hasError()
  487. {
  488. return $this->_error;
  489. }
  490. ################################################################################
  491. # #
  492. # Assertions Methods #
  493. # #
  494. ################################################################################
  495. /**
  496. * Asserts that $condition is true. Reports an error $message if $condition is false.
  497. * @static
  498. *
  499. * @param bool $condition Condition to assert
  500. * @param string|array $message Message to report if the condition is false (by default = '')
  501. */
  502. public static function assertTrue($condition, $message = '')
  503. {
  504. $message = self::messagesToString($message);
  505. if (is_object($condition)) {
  506. $condition = (false === $condition->hasError());
  507. }
  508. self::assertThat($condition, self::isTrue(), $message);
  509. }
  510. /**
  511. * Asserts that $condition is false. Reports an error $message if $condition is true.
  512. * @static
  513. *
  514. * @param bool $condition Condition to assert
  515. * @param string $message Message to report if the condition is true (by default = '')
  516. */
  517. public static function assertFalse($condition, $message = '')
  518. {
  519. $message = self::messagesToString($message);
  520. if (is_object($condition)) {
  521. $condition = (false === $condition->hasError());
  522. }
  523. self::assertThat($condition, self::isFalse(), $message);
  524. }
  525. ################################################################################
  526. # #
  527. # Parameter helper methods #
  528. # #
  529. ################################################################################
  530. /**
  531. * Append parameters decorator object
  532. *
  533. * @param Mage_Selenium_Helper_Params $paramsHelperObject Parameters decorator object
  534. *
  535. * @return Mage_Selenium_TestCase
  536. */
  537. public function appendParamsDecorator($paramsHelperObject)
  538. {
  539. $this->_paramsHelper = $paramsHelperObject;
  540. return $this;
  541. }
  542. /**
  543. * Add parameter to params object instance
  544. *
  545. * @param string $name
  546. * @param string $value
  547. *
  548. * @return Mage_Selenium_Helper_Params
  549. */
  550. public function addParameter($name, $value)
  551. {
  552. $this->_paramsHelper->setParameter($name, $value);
  553. return $this;
  554. }
  555. /**
  556. * Get parameter from params object instance
  557. *
  558. * @param string $name
  559. *
  560. * @return string
  561. */
  562. public function getParameter($name)
  563. {
  564. return $this->_paramsHelper->getParameter($name);
  565. }
  566. /**
  567. * Define parameter %$paramName% from URL
  568. *
  569. * @param string $paramName
  570. * @param null|string $url
  571. *
  572. * @return null|string
  573. */
  574. public function defineParameterFromUrl($paramName, $url = null)
  575. {
  576. if (is_null($url)) {
  577. $url = self::_getMcaFromCurrentUrl($this->_configHelper->getConfigAreas(), $this->getLocation());
  578. }
  579. $title_arr = explode('/', $url);
  580. if (in_array($paramName, $title_arr) && isset($title_arr[array_search($paramName, $title_arr) + 1])) {
  581. return $title_arr[array_search($paramName, $title_arr) + 1];
  582. }
  583. foreach ($title_arr as $key => $value) {
  584. if (preg_match("#$paramName$#i", $value) && isset($title_arr[$key + 1])) {
  585. return $title_arr[$key + 1];
  586. }
  587. }
  588. return null;
  589. }
  590. /**
  591. * Define parameter %id% from attribute @title by XPath
  592. *
  593. * @param string $xpath
  594. *
  595. * @return null|string
  596. */
  597. public function defineIdFromTitle($xpath)
  598. {
  599. $urlFromTitleAttribute = $this->getValue($xpath . '/@title');
  600. if (is_numeric($urlFromTitleAttribute)) {
  601. return $urlFromTitleAttribute;
  602. }
  603. return $this->defineIdFromUrl($urlFromTitleAttribute);
  604. }
  605. /**
  606. * Define parameter %id% from URL
  607. *
  608. * @param null|string $url
  609. *
  610. * @return null|string
  611. */
  612. public function defineIdFromUrl($url = null)
  613. {
  614. return $this->defineParameterFromUrl('id', $url);
  615. }
  616. /**
  617. * Adds field ID to Message Xpath (sets %fieldId% parameter)
  618. *
  619. * @param string $fieldType Field type
  620. * @param string $fieldName Field name from UIMap
  621. */
  622. public function addFieldIdToMessage($fieldType, $fieldName)
  623. {
  624. $fieldXpath = $this->_getControlXpath($fieldType, $fieldName);
  625. if ($this->isElementPresent($fieldXpath . '/@id')) {
  626. $fieldId = $this->getAttribute($fieldXpath . '/@id');
  627. $fieldId = empty($fieldId)
  628. ? $this->getAttribute($fieldXpath . '/@name')
  629. : $fieldId;
  630. } else {
  631. $fieldId = $this->getAttribute($fieldXpath . '/@name');
  632. }
  633. $this->addParameter('fieldId', $fieldId);
  634. }
  635. ################################################################################
  636. # #
  637. # Data helper methods #
  638. # #
  639. ################################################################################
  640. /**
  641. * Generates random value as a string|text|email $type, with specified $length.<br>
  642. * Available $modifier:
  643. * <li>if $type = string - alnum|alpha|digit|lower|upper|punct
  644. * <li>if $type = text - alnum|alpha|digit|lower|upper|punct
  645. * <li>if $type = email - valid|invalid
  646. *
  647. * @param string $type Available types are 'string', 'text', 'email' (by default = 'string')
  648. * @param int $length Generated value length (by default = 100)
  649. * @param null|string $modifier Value modifier, e.g. PCRE class (by default = null)
  650. * @param null|string $prefix Prefix to prepend the generated value (by default = null)
  651. *
  652. * @return string
  653. */
  654. public function generate($type = 'string', $length = 100, $modifier = null, $prefix = null)
  655. {
  656. $result = $this->_dataGeneratorHelper->generate($type, $length, $modifier, $prefix);
  657. return $result;
  658. }
  659. /**
  660. * Loads test data.
  661. *
  662. * @param string $dataFile - File name or full path to file in fixture folder
  663. * (for example: 'default\core\Mage\AdminUser\data\AdminUsers') in which DataSet is specified
  664. * @param string $dataSource - DataSet name(for example: 'test_data')
  665. * or part of DataSet (for example: 'test_data/product')
  666. * @param array|null $overrideByKey
  667. * @param array|null $overrideByValueParam
  668. *
  669. * @throws PHPUnit_Framework_Exception
  670. * @return array
  671. */
  672. public function loadDataSet($dataFile, $dataSource, $overrideByKey = null, $overrideByValueParam = null)
  673. {
  674. $data = $this->_dataHelper->getDataValue($dataSource);
  675. if ($data === false) {
  676. $dataSetName = array_shift(explode('/', $dataSource));
  677. $this->_dataHelper->loadTestDataSet($dataFile, $dataSetName);
  678. $data = $this->_dataHelper->getDataValue($dataSource);
  679. }
  680. if (!is_array($data)) {
  681. throw new PHPUnit_Framework_Exception('Data "' . $dataSource . '" is not specified.');
  682. }
  683. if ($overrideByKey) {
  684. $data = $this->overrideArrayData($overrideByKey, $data, 'byFieldKey');
  685. }
  686. if ($overrideByValueParam) {
  687. $data = $this->overrideArrayData($overrideByValueParam, $data, 'byValueParam');
  688. }
  689. array_walk_recursive($data, array($this, 'setDataParams'));
  690. return $this->clearDataArray($data);
  691. }
  692. /**
  693. * Override data in array.
  694. *
  695. * @param array $dataForOverride
  696. * @param array $overrideArray
  697. * @param string $overrideType
  698. *
  699. * @return array
  700. * @throws RuntimeException
  701. */
  702. public function overrideArrayData(array $dataForOverride, array $overrideArray, $overrideType)
  703. {
  704. $errorMessages = array();
  705. $messageParam = strtolower(substr_replace(str_replace('by', '', $overrideType), ' ', 5, 0));
  706. foreach ($dataForOverride as $fieldKey => $fieldValue) {
  707. if (!$this->overrideDataByCondition($fieldKey, $fieldValue, $overrideArray, $overrideType)) {
  708. $errorMessages[] =
  709. "Value for '" . $fieldKey . "' " . $messageParam . " is not changed: [There is no this "
  710. . $messageParam . " in dataset]";
  711. }
  712. }
  713. if ($errorMessages) {
  714. throw new RuntimeException(implode("\n", $errorMessages));
  715. }
  716. return $overrideArray;
  717. }
  718. /**
  719. * Change in array value by condition.
  720. *
  721. * @param string $overrideKey
  722. * @param string $overrideValue
  723. * @param array $overrideArray
  724. * @param string $condition byFieldKey|byValueParam
  725. *
  726. * @return bool
  727. * @throws OutOfRangeException
  728. */
  729. public function overrideDataByCondition($overrideKey, $overrideValue, &$overrideArray, $condition)
  730. {
  731. $isOverridden = false;
  732. foreach ($overrideArray as $currentKey => &$currentValue) {
  733. switch ($condition) {
  734. case 'byFieldKey':
  735. $isFound = ($currentKey === $overrideKey);
  736. break;
  737. case 'byValueParam':
  738. $isFound = ($currentValue === '%' . $overrideKey . '%');
  739. break;
  740. default:
  741. throw new OutOfRangeException('Wrong condition');
  742. break;
  743. }
  744. if ($isFound) {
  745. $currentValue = $overrideValue;
  746. $isOverridden = true;
  747. } elseif (is_array($currentValue)) {
  748. $isOverridden = $this->overrideDataByCondition($overrideKey, $overrideValue, $currentValue, $condition)
  749. || $isOverridden;
  750. }
  751. }
  752. return $isOverridden;
  753. }
  754. /**
  755. * Set data params
  756. *
  757. * @param string $value
  758. * @param string $key Index of the target to randomize
  759. */
  760. public function setDataParams(&$value, $key)
  761. {
  762. if (preg_match('/%randomize%/', $value)) {
  763. $value = preg_replace('/%randomize%/', $this->generate('string', 5, ':lower:'), $value);
  764. }
  765. if (preg_match('/^%longValue[0-9]+%$/', $value)) {
  766. $length = preg_replace('/[^0-9]/', '', $value);
  767. $value = preg_replace('/%longValue[0-9]+%/', $this->generate('string', $length, ':alpha:'), $value);
  768. }
  769. if (preg_match('/^%specialValue[0-9]+%$/', $value)) {
  770. $length = preg_replace('/[^0-9]/', '', $value);
  771. $value = preg_replace('/%specialValue[0-9]+%/', $this->generate('string', $length, ':punct:'), $value);
  772. }
  773. if (preg_match('/%currentDate%/', $value)) {
  774. $fallbackOrderHelper = $this->_configHelper->getFixturesFallbackOrder();
  775. switch (end($fallbackOrderHelper)) {
  776. case 'enterprise':
  777. $value = preg_replace('/%currentDate%/', date("n/j/Y"), $value);
  778. break;
  779. default:
  780. $value = preg_replace('/%currentDate%/', date("n/j/y"), $value);
  781. break;
  782. }
  783. }
  784. }
  785. /**
  786. * Delete field in array with special values(for example: %noValue%)
  787. *
  788. * @param array $dataArray
  789. *
  790. * @return array|bool
  791. */
  792. public function clearDataArray($dataArray)
  793. {
  794. if (!is_array($dataArray)) {
  795. return false;
  796. }
  797. foreach ($dataArray as $key => $value) {
  798. if (is_array($value)) {
  799. $dataArray[$key] = $this->clearDataArray($value);
  800. if (count($dataArray[$key]) == false) {
  801. unset($dataArray[$key]);
  802. }
  803. } elseif (preg_match('/^\%(\w)+\%$/', $value)) {
  804. unset($dataArray[$key]);
  805. }
  806. }
  807. return $dataArray;
  808. }
  809. ################################################################################
  810. # Deprecated data helper methods #
  811. ################################################################################
  812. /**
  813. * Loads test data from DataSet, specified in the $dataSource
  814. *
  815. * @deprecated
  816. * @see loadDataSet()
  817. *
  818. * @param string $dataSource Data source (e.g. filename in ../data without .yml extension)
  819. * @param null|array $override value to override in original data from data source
  820. * @param null|array|string $randomize Value to randomize
  821. *
  822. * @return array
  823. */
  824. public function loadData($dataSource, $override = null, $randomize = null)
  825. {
  826. $data = $this->_dataHelper->getDataValue($dataSource);
  827. if (!is_array($data)) {
  828. $this->fail('Data \'' . $dataSource . '\' is not loaded');
  829. }
  830. array_walk_recursive($data, array($this, 'setDataParams'));
  831. if (!empty($randomize)) {
  832. $randomize = (!is_array($randomize))
  833. ? array($randomize)
  834. : $randomize;
  835. array_walk_recursive($data, array($this, 'randomizeData'), $randomize);
  836. }
  837. if (!empty($override) && is_array($override)) {
  838. $withSubArray = array();
  839. $withOutSubArray = array();
  840. foreach ($override as $key => $value) {
  841. if (preg_match('|/|', $key)) {
  842. $withSubArray[$key]['subArray'] = preg_replace('#/[a-z0-9_]+$#i', '', $key);
  843. $withSubArray[$key]['name'] = preg_replace('#^[a-z0-9_]+/#i', '', $key);
  844. $withSubArray[$key]['value'] = $value;
  845. } else {
  846. $withOutSubArray[$key] = $value;
  847. }
  848. }
  849. foreach ($withOutSubArray as $key => $value) {
  850. if (!$this->overrideData($key, $value, $data)) {
  851. $data[$key] = $value;
  852. }
  853. }
  854. foreach ($withSubArray as $value) {
  855. if (!$this->overrideDataInSubArray($value['subArray'], $value['name'], $value['value'], $data)) {
  856. $data[$value['subArray']][$value['name']] = $value['value'];
  857. }
  858. }
  859. }
  860. return $data;
  861. }
  862. /**
  863. * Remove array elements that have '%noValue%' value
  864. *
  865. * @deprecated
  866. * @see clearDataArray()
  867. *
  868. * @param array $array
  869. *
  870. * @return array
  871. */
  872. public function arrayEmptyClear(array $array)
  873. {
  874. foreach ($array as $k => $v) {
  875. if (is_array($v)) {
  876. $array[$k] = $this->arrayEmptyClear($v);
  877. if (count($array[$k]) == false) {
  878. unset($array[$k]);
  879. }
  880. } else {
  881. if ($v === '%noValue%') {
  882. unset($array[$k]);
  883. }
  884. }
  885. }
  886. return $array;
  887. }
  888. /**
  889. * Override data with index $key on-fly in the $overrideArray by new value (&$value)
  890. * @deprecated
  891. * @see overrideDataByCondition()
  892. *
  893. * @param string $overrideKey Index of the target to override
  894. * @param string $overrideValue Value for override
  895. * @param array $overrideArray Target array, which contains some index(es) to override
  896. *
  897. * @return bool
  898. */
  899. public function overrideData($overrideKey, $overrideValue, &$overrideArray)
  900. {
  901. $overrideResult = false;
  902. foreach ($overrideArray as $key => &$value) {
  903. if ($key === $overrideKey) {
  904. $overrideArray[$key] = $overrideValue;
  905. $overrideResult = true;
  906. } elseif (is_array($value)) {
  907. $result = $this->overrideData($overrideKey, $overrideValue, $value);
  908. if ($result || $overrideResult) {
  909. $overrideResult = true;
  910. }
  911. }
  912. }
  913. return $overrideResult;
  914. }
  915. /**
  916. * @deprecated
  917. * @see overrideDataByCondition()
  918. *
  919. * @param string $subArray
  920. * @param string $overrideKey
  921. * @param string $overrideValue
  922. * @param array $overrideArray
  923. *
  924. * @return bool
  925. */
  926. public function overrideDataInSubArray($subArray, $overrideKey, $overrideValue, &$overrideArray)
  927. {
  928. $overrideResult = false;
  929. foreach ($overrideArray as $key => &$value) {
  930. if (is_array($value)) {
  931. if ($key === $subArray) {
  932. foreach ($value as $k => $v) {
  933. if ($k === $overrideKey) {
  934. $value[$k] = $overrideValue;
  935. $overrideResult = true;
  936. }
  937. if (is_array($v)) {
  938. $result = $this->overrideDataInSubArray($subArray, $overrideKey, $overrideValue, $value);
  939. if ($result || $overrideResult) {
  940. $overrideResult = true;
  941. }
  942. }
  943. }
  944. } else {
  945. $result = $this->overrideDataInSubArray($subArray, $overrideKey, $overrideValue, $value);
  946. if ($result || $overrideResult) {
  947. $overrideResult = true;
  948. }
  949. }
  950. }
  951. }
  952. return $overrideResult;
  953. }
  954. /**
  955. * Randomize data with index $key on-fly in the $randomizeArray by new value (&$value)
  956. *
  957. * @deprecated
  958. * @see setDataParams()
  959. *
  960. * @param string $value Value for randomization (in this case - value will be as a suffix)
  961. * @param string $key Index of the target to randomize
  962. * @param array $randomizeArray Target array, which contains some index(es) to randomize
  963. */
  964. public function randomizeData(&$value, $key, $randomizeArray)
  965. {
  966. foreach ($randomizeArray as $randomizeField) {
  967. if ($randomizeField === $key) {
  968. $value = $this->generate('string', 5, ':lower:') . '_' . $value;
  969. }
  970. }
  971. }
  972. ################################################################################
  973. # #
  974. # Messages helper methods #
  975. # #
  976. ################################################################################
  977. /**
  978. * Removes all added messages
  979. *
  980. * @param null|string $type
  981. */
  982. public function clearMessages($type = null)
  983. {
  984. if ($type && array_key_exists($type, self::$_messages)) {
  985. unset(self::$_messages[$type]);
  986. } elseif ($type == null) {
  987. self::$_messages = null;
  988. }
  989. }
  990. /**
  991. * Gets all messages on the pages
  992. */
  993. protected function _parseMessages()
  994. {
  995. $area = $this->getArea();
  996. $page = $this->getCurrentUimapPage();
  997. if ($area == 'admin' || $area == 'frontend') {
  998. $fieldNameWithMessage = $page->findPageelement('fieldNameWithValidationMessage');
  999. self::$_messages['notice'] = $this->getElementsByXpath($page->findMessage('general_notice'));
  1000. self::$_messages['validation'] =
  1001. $this->getElementsByXpath($page->findMessage('general_validation'), 'text', $fieldNameWithMessage);
  1002. } else {
  1003. self::$_messages['validation'] = $this->getElementsByXpath($page->findMessage('general_validation'));
  1004. }
  1005. self::$_messages['success'] = $this->getElementsByXpath($page->findMessage('general_success'));
  1006. self::$_messages['error'] = $this->getElementsByXpath($page->findMessage('general_error'));
  1007. }
  1008. /**
  1009. * Returns all messages (or messages of the specified type) on the page
  1010. *
  1011. * @param null|string $type Message type: validation|error|success
  1012. *
  1013. * @return array
  1014. */
  1015. public function getMessagesOnPage($type = null)
  1016. {
  1017. $this->_parseMessages();
  1018. if ($type) {
  1019. return self::$_messages[$type];
  1020. }
  1021. return self::$_messages;
  1022. }
  1023. /**
  1024. * Returns all parsed messages (or messages of the specified type)
  1025. *
  1026. * @param null|string $type Message type: validation|error|success (default = null, for all messages)
  1027. *
  1028. * @return array|null
  1029. */
  1030. public function getParsedMessages($type = null)
  1031. {
  1032. if ($type) {
  1033. return (isset(self::$_messages[$type]))
  1034. ? self::$_messages[$type]
  1035. : null;
  1036. }
  1037. return self::$_messages;
  1038. }
  1039. /**
  1040. * Adds validation|error|success message(s)
  1041. *
  1042. * @param string $type Message type: validation|error|success
  1043. * @param string|array $message Message text
  1044. */
  1045. public function addMessage($type, $message)
  1046. {
  1047. if (is_array($message)) {
  1048. foreach ($message as $value) {
  1049. self::$_messages[$type][] = $value;
  1050. }
  1051. } else {
  1052. self::$_messages[$type][] = $message;
  1053. }
  1054. }
  1055. /**
  1056. * Adds a verification message
  1057. *
  1058. * @param string|array $message Message text
  1059. */
  1060. public function addVerificationMessage($message)
  1061. {
  1062. $this->addMessage('verification', $message);
  1063. }
  1064. /**
  1065. * Verifies messages count
  1066. *
  1067. * @param int $count Expected number of message(s) on the page
  1068. * @param null|string $xpath XPath of a message(s) that should be evaluated (default = null)
  1069. *
  1070. * @return int Number of nodes that match the specified $xpath
  1071. */
  1072. public function verifyMessagesCount($count = 1, $xpath = null)
  1073. {
  1074. if ($xpath === null) {
  1075. $xpath = $this->_getMessageXpath('general_validation');
  1076. }
  1077. $this->_parseMessages();
  1078. return $this->getXpathCount($xpath) == $count;
  1079. }
  1080. /**
  1081. * Check if the specified message exists on the page
  1082. *
  1083. * @param string $message Message ID from UIMap
  1084. *
  1085. * @return array
  1086. */
  1087. public function checkMessage($message)
  1088. {
  1089. $messageLocator = $this->_getMessageXpath($message);
  1090. return $this->checkMessageByXpath($messageLocator);
  1091. }
  1092. /**
  1093. * Checks if message with the specified XPath exists on the page
  1094. *
  1095. * @param string $xpath XPath of message to checking
  1096. *
  1097. * @return array
  1098. */
  1099. public function checkMessageByXpath($xpath)
  1100. {
  1101. $this->_parseMessages();
  1102. if ($xpath && $this->isElementPresent($xpath)) {
  1103. return array("success" => true);
  1104. }
  1105. return array("success" => false,
  1106. "xpath" => $xpath,
  1107. "found" => self::messagesToString($this->getMessagesOnPage()));
  1108. }
  1109. /**
  1110. * Checks if any 'error' message exists on the page
  1111. *
  1112. * @param null|string $message Error message ID from UIMap OR XPath of the error message (by default = null)
  1113. *
  1114. * @return array
  1115. */
  1116. public function errorMessage($message = null)
  1117. {
  1118. return (!empty($message))
  1119. ? $this->checkMessage($message)
  1120. : $this->checkMessageByXpath($this->_getMessageXpath('general_error'));
  1121. }
  1122. /**
  1123. * Checks if any 'success' message exists on the page
  1124. *
  1125. * @param null|string $message Success message ID from UIMap OR XPath of the success message (by default = null)
  1126. *
  1127. * @return array
  1128. */
  1129. public function successMessage($message = null)
  1130. {
  1131. return (!empty($message))
  1132. ? $this->checkMessage($message)
  1133. : $this->checkMessageByXpath($this->_getMessageXpath('general_success'));
  1134. }
  1135. /**
  1136. * Checks if any 'validation' message exists on the page
  1137. *
  1138. * @param null|string $message Validation message ID from UIMap OR XPath of the validation message (by default = null)
  1139. *
  1140. * @return array
  1141. */
  1142. public function validationMessage($message = null)
  1143. {
  1144. return (!empty($message))
  1145. ? $this->checkMessage($message)
  1146. : $this->checkMessageByXpath($this->_getMessageXpath('general_validation'));
  1147. }
  1148. /**
  1149. * Asserts that the specified message of the specified type is present on the current page
  1150. *
  1151. * @param string $type success|validation|error
  1152. * @param null|string $message Message ID from UIMap
  1153. */
  1154. public function assertMessagePresent($type, $message = null)
  1155. {
  1156. $method = strtolower($type) . 'Message';
  1157. $result = $this->$method($message);
  1158. if (!$result['success']) {
  1159. if (is_null($message)) {
  1160. $error = "Failed looking for '" . $type . "' message.\n";
  1161. } else {
  1162. $error = "Failed looking for '" . $message . "' message.\n[xpath: " . $result['xpath'] . "]\n";
  1163. }
  1164. if ($result['found']) {
  1165. $error .= "Found messages instead:\n" . $result['found'];
  1166. }
  1167. $this->fail($error);
  1168. }
  1169. }
  1170. /**
  1171. * Asserts that the specified message of the specified type is not present on the current page
  1172. *
  1173. * @param string $type success|validation|error
  1174. * @param null|string $message Message ID from UIMap
  1175. */
  1176. public function assertMessageNotPresent($type, $message = null)
  1177. {
  1178. $method = strtolower($type) . 'Message';
  1179. $result = $this->$method($message);
  1180. if ($result['success']) {
  1181. if (is_null($message)) {
  1182. $error = "'" . $type . "' message is on the page.";
  1183. } else {
  1184. $error = "'" . $message . "' message is on the page.";
  1185. }
  1186. $messagesOnPage = self::messagesToString($this->getMessagesOnPage());
  1187. if ($messagesOnPage) {
  1188. $error .= "\n" . $messagesOnPage;
  1189. }
  1190. $this->fail($error);
  1191. }
  1192. }
  1193. /**
  1194. * Assert there are no verification errors
  1195. */
  1196. public function assertEmptyVerificationErrors()
  1197. {
  1198. $verificationErrors = $this->getParsedMessages('verification');
  1199. if ($verificationErrors) {
  1200. $this->clearMessages('verification');
  1201. $this->fail(implode("\n", $verificationErrors));
  1202. }
  1203. }
  1204. /**
  1205. * @param string $type
  1206. *
  1207. * @return array|string
  1208. * @throws RuntimeException
  1209. */
  1210. public function getBasicXpathMessage($type = 'all')
  1211. {
  1212. $xpath = null;
  1213. $types = array('success', 'error', 'validation', 'notice');
  1214. $currentPage = $this->getCurrentPage();
  1215. $currentArea = $this->getArea();
  1216. $this->setArea('admin');
  1217. $this->setCurrentPage('dashboard');
  1218. if ($type != 'all') {
  1219. if (in_array($type, $types)) {
  1220. $xpath = $this->_getMessageXpath('general_' . $type);
  1221. } else {
  1222. throw new RuntimeException('Incorrect message type');
  1223. }
  1224. } else {
  1225. foreach ($types as $value) {
  1226. $xpath[$value] = $this->_getMessageXpath('general_' . $value);
  1227. }
  1228. }
  1229. $this->setArea($currentArea);
  1230. $this->setCurrentPage($currentPage);
  1231. return $xpath;
  1232. }
  1233. /**
  1234. * Returns a string representation of the messages.
  1235. *
  1236. * @static
  1237. *
  1238. * @param array|string $message
  1239. *
  1240. * @return string
  1241. */
  1242. protected static function messagesToString($message)
  1243. {
  1244. if (is_array($message) && $message) {
  1245. $message = implode("\n", call_user_func_array('array_merge', $message));
  1246. }
  1247. return $message;
  1248. }
  1249. ################################################################################
  1250. # #
  1251. # Navigation helper methods #
  1252. # #
  1253. ################################################################################
  1254. /**
  1255. * Set additional params for navigation
  1256. *
  1257. * @param string $params your params to add to URL (?paramName1=paramValue1&paramName2=paramValue2)
  1258. */
  1259. public function setUrlPostfix($params)
  1260. {
  1261. $this->_urlPostfix = $params;
  1262. }
  1263. /**
  1264. * Navigates to the specified page in specified area.<br>
  1265. * Page identifier must be described in the UIMap.
  1266. *
  1267. * @param string $area Area identifier (by default = 'frontend')
  1268. * @param string $page Page identifier
  1269. * @param bool $validatePage
  1270. *
  1271. * @return Mage_Selenium_TestCase
  1272. */
  1273. public function goToArea($area = 'frontend', $page = '', $validatePage = true)
  1274. {
  1275. $this->_configHelper->setArea($area);
  1276. if ($page == '') {
  1277. $areaConfig = $this->_configHelper->getAreaConfig();
  1278. $page = $areaConfig['base_page_uimap'];
  1279. }
  1280. $this->navigate($page, $validatePage);
  1281. return $this;
  1282. }
  1283. /**
  1284. * Navigates to the specified page in the current area.<br>
  1285. * Page identifier must be described in the UIMap.
  1286. *
  1287. * @param string $page Page identifier
  1288. * @param bool $validatePage
  1289. *
  1290. * @return Mage_Selenium_TestCase
  1291. */
  1292. public function navigate($page, $validatePage = true)
  1293. {
  1294. $area = $this->_configHelper->getArea();
  1295. $clickXpath = $this->_uimapHelper->getPageClickXpath($area, $page, $this->_paramsHelper);
  1296. if ($clickXpath && $this->isElementPresent($clickXpath)) {
  1297. $this->click($clickXpath);
  1298. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  1299. } elseif (isset($this->_urlPostfix)) {
  1300. $this->open($this->_uimapHelper->getPageUrl($area, $page, $this->_paramsHelper) . $this->_urlPostfix);
  1301. } else {
  1302. $this->open($this->_uimapHelper->getPageUrl($area, $page, $this->_paramsHelper));
  1303. }
  1304. if ($validatePage) {
  1305. $this->validatePage($page);
  1306. }
  1307. return $this;
  1308. }
  1309. /**
  1310. * Navigate to the specified admin page.<br>
  1311. * Page identifier must be described in the UIMap. Opens "Dashboard" page by default.
  1312. *
  1313. * @param string $page Page identifier (by default = 'dashboard')
  1314. * @param bool $validatePage
  1315. *
  1316. * @return Mage_Selenium_TestCase
  1317. */
  1318. public function admin($page = 'dashboard', $validatePage = true)
  1319. {
  1320. $this->goToArea('admin', $page, $validatePage);
  1321. return $this;
  1322. }
  1323. /**
  1324. * Navigate to the specified frontend page<br>
  1325. * Page identifier must be described in the UIMap. Opens "Home page" by default.
  1326. *
  1327. * @param string $page Page identifier (by default = 'home_page')
  1328. * @param bool $validatePage
  1329. *
  1330. * @return Mage_Selenium_TestCase
  1331. */
  1332. public function frontend($page = 'home_page', $validatePage = true)
  1333. {
  1334. $this->goToArea('frontend', $page, $validatePage);
  1335. return $this;
  1336. }
  1337. ################################################################################
  1338. # #
  1339. # Area helper methods #
  1340. # #
  1341. ################################################################################
  1342. /**
  1343. * Gets current location area<br>
  1344. * Usage: define area currently operating.
  1345. * <li>Possible areas: frontend | admin
  1346. * @return string
  1347. */
  1348. public function getCurrentLocationArea()
  1349. {
  1350. $currentArea = self::_getAreaFromCurrentUrl($this->_configHelper->getConfigAreas(), $this->getLocation());
  1351. $this->_configHelper->setArea($currentArea);
  1352. return $currentArea;
  1353. }
  1354. /**
  1355. * Find area in areasConfig using full page URL
  1356. * @static
  1357. *
  1358. * @param array $areasConfig Full area config
  1359. * @param string $cu…

Large files files are truncated, but you can click here to view the full file