PageRenderTime 74ms CodeModel.GetById 11ms 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
  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 $currentUrl Full URL to page
  1360. *
  1361. * @return string
  1362. * @throws RuntimeException
  1363. */
  1364. protected static function _getAreaFromCurrentUrl($areasConfig, $currentUrl)
  1365. {
  1366. $currentArea = '';
  1367. $currentUrl = preg_replace('|^http([s]{0,1})://|', '', preg_replace('|/index.php/?|', '/', $currentUrl));
  1368. foreach ($areasConfig as $area => $areaConfig) {
  1369. $areaUrl =
  1370. preg_replace('|^http([s]{0,1})://|', '', preg_replace('|/index.php/?|', '/', $areaConfig['url']));
  1371. if (strpos($currentUrl, $areaUrl) === 0) {
  1372. $currentArea = $area;
  1373. break;
  1374. }
  1375. }
  1376. if ($currentArea == '') {
  1377. throw new RuntimeException('Area is not defined for ulr: ' . $currentUrl);
  1378. }
  1379. return $currentArea;
  1380. }
  1381. /**
  1382. * Set current area
  1383. *
  1384. * @param string $name
  1385. *
  1386. * @return Mage_Selenium_TestCase
  1387. */
  1388. public function setArea($name)
  1389. {
  1390. $this->_configHelper->setArea($name);
  1391. return $this;
  1392. }
  1393. /**
  1394. * Return current area name
  1395. * @return string
  1396. * @throws OutOfRangeException
  1397. */
  1398. public function getArea()
  1399. {
  1400. return $this->_configHelper->getArea();
  1401. }
  1402. /**
  1403. * Return current application config
  1404. * @return array
  1405. * @throws OutOfRangeException
  1406. */
  1407. public function getApplicationConfig()
  1408. {
  1409. return $this->_configHelper->getApplicationConfig();
  1410. }
  1411. ################################################################################
  1412. # #
  1413. # UIMap of Page helper methods #
  1414. # #
  1415. ################################################################################
  1416. /**
  1417. * Retrieves Page data from UIMap by $pageKey
  1418. *
  1419. * @param string $area Area identifier
  1420. * @param string $pageKey UIMap page key
  1421. *
  1422. * @return Mage_Selenium_Uimap_Page
  1423. */
  1424. public function getUimapPage($area, $pageKey)
  1425. {
  1426. return $this->_uimapHelper->getUimapPage($area, $pageKey, $this->_paramsHelper);
  1427. }
  1428. /**
  1429. * Retrieves current Page data from UIMap.
  1430. * Gets current page name from an internal variable.
  1431. * @return Mage_Selenium_Uimap_Page
  1432. */
  1433. public function getCurrentUimapPage()
  1434. {
  1435. return $this->getUimapPage($this->_configHelper->getArea(), $this->getCurrentPage());
  1436. }
  1437. /**
  1438. * Retrieves current Page data from UIMap.
  1439. * Gets current page name from the current URL.
  1440. * @return Mage_Selenium_Uimap_Page
  1441. */
  1442. public function getCurrentLocationUimapPage()
  1443. {
  1444. $areasConfig = $this->_configHelper->getConfigAreas();
  1445. $currentUrl = $this->getLocation();
  1446. $mca = self::_getMcaFromCurrentUrl($areasConfig, $currentUrl);
  1447. $area = self::_getAreaFromCurrentUrl($areasConfig, $currentUrl);
  1448. return $this->_uimapHelper->getUimapPageByMca($area, $mca, $this->_paramsHelper);
  1449. }
  1450. ################################################################################
  1451. # #
  1452. # Page ID helper methods #
  1453. # #
  1454. ################################################################################
  1455. /**
  1456. * Change current page
  1457. *
  1458. * @param string $page
  1459. *
  1460. * @return Mage_Selenium_TestCase
  1461. */
  1462. public function setCurrentPage($page)
  1463. {
  1464. $this->_configHelper->setCurrentPageId($page);
  1465. return $this;
  1466. }
  1467. /**
  1468. * Get current page
  1469. * @return string
  1470. */
  1471. public function getCurrentPage()
  1472. {
  1473. return $this->_configHelper->getCurrentPageId();
  1474. }
  1475. /**
  1476. * Find PageID in UIMap in the current area using full page URL
  1477. *
  1478. * @param string|null $url Full URL
  1479. *
  1480. * @return string
  1481. */
  1482. protected function _findCurrentPageFromUrl($url = null)
  1483. {
  1484. if (is_null($url)) {
  1485. $url = str_replace($this->_urlPostfix, '', $this->getLocation());
  1486. }
  1487. $areasConfig = $this->_configHelper->getConfigAreas();
  1488. $mca = self::_getMcaFromCurrentUrl($areasConfig, $url);
  1489. $area = self::_getAreaFromCurrentUrl($areasConfig, $url);
  1490. $page = $this->_uimapHelper->getUimapPageByMca($area, $mca, $this->_paramsHelper);
  1491. return $page->getPageId();
  1492. }
  1493. /**
  1494. * Checks if the currently opened page is $page.<br>
  1495. * Returns true if the specified page is the current page, otherwise returns false and sets the error message:
  1496. * "Opened the wrong page: $currentPage (should be:$page)".<br>
  1497. * Page identifier must be described in the UIMap.
  1498. *
  1499. * @param string $page Page identifier
  1500. *
  1501. * @return bool
  1502. */
  1503. public function checkCurrentPage($page)
  1504. {
  1505. $currentPage = $this->_findCurrentPageFromUrl();
  1506. if ($currentPage != $page) {
  1507. $this->addVerificationMessage("Opened the wrong page '" . $currentPage . "'(should be: '" . $page . "')");
  1508. return false;
  1509. }
  1510. return true;
  1511. }
  1512. /**
  1513. * Validates properties of the current page.
  1514. *
  1515. * @param string $page Page identifier
  1516. */
  1517. public function validatePage($page = '')
  1518. {
  1519. $this->getCurrentLocationArea();
  1520. if ($page) {
  1521. $this->assertTrue($this->checkCurrentPage($page), $this->getMessagesOnPage());
  1522. } else {
  1523. $page = $this->_findCurrentPageFromUrl();
  1524. }
  1525. $this->assertTextNotPresent('Fatal error', 'Fatal error on page');
  1526. $this->assertTextNotPresent('There has been an error processing your request',
  1527. 'Fatal error on page: \'There has been an error processing your request\'');
  1528. $this->assertTextNotPresent('Notice:', 'Notice error on page');
  1529. $this->assertTextNotPresent('Parse error', 'Parse error on page');
  1530. if (!$this->isElementPresent($this->_getMessageXpath('general_notice'))) {
  1531. $this->assertTextNotPresent('Warning:', 'Warning on page');
  1532. }
  1533. $this->assertTextNotPresent('If you typed the URL directly', 'The requested page was not found.');
  1534. $this->assertTextNotPresent('was not found', 'Something was not found:)');
  1535. $this->assertTextNotPresent('Service Temporarily Unavailable', 'Service Temporarily Unavailable');
  1536. $this->assertTextNotPresent('The page isn\'t redirecting properly', 'The page isn\'t redirecting properly');
  1537. $fallbackOrderHelper = $this->_configHelper->getFixturesFallbackOrder();
  1538. if (end($fallbackOrderHelper) == 'enterprise') {
  1539. $expectedTitle =
  1540. $this->getUimapPage($this->_configHelper->getArea(), $page)->getTitle($this->_paramsHelper);
  1541. $this->assertSame($expectedTitle, $this->getTitle(),
  1542. 'Title for page "' . $this->getCurrentPage() . '" is unexpected.');
  1543. }
  1544. $this->setCurrentPage($page);
  1545. }
  1546. ################################################################################
  1547. # #
  1548. # Page Elements helper methods #
  1549. # #
  1550. ################################################################################
  1551. /**
  1552. * Get Module-Controller-Action-part of page URL
  1553. * @static
  1554. *
  1555. * @param array $areasConfig Full area config
  1556. * @param string $currentUrl Current URL
  1557. *
  1558. * @return string
  1559. */
  1560. protected static function _getMcaFromCurrentUrl($areasConfig, $currentUrl)
  1561. {
  1562. $mca = '';
  1563. $currentArea = '';
  1564. $baseUrl = '';
  1565. $currentUrl = preg_replace('|^http([s]{0,1})://|', '', preg_replace('|/index.php/?|', '/', $currentUrl));
  1566. foreach ($areasConfig as $area => $areaConfig) {
  1567. $areaUrl =
  1568. preg_replace('|^http([s]{0,1})://|', '', preg_replace('|/index.php/?|', '/', $areaConfig['url']));
  1569. if (strpos($currentUrl, $areaUrl) === 0) {
  1570. $baseUrl = $areaUrl;
  1571. $currentArea = $area;
  1572. break;
  1573. }
  1574. }
  1575. if (strpos($currentUrl, $baseUrl) !== false) {
  1576. $mca = trim(substr($currentUrl, strlen($baseUrl)), " /\\");
  1577. }
  1578. if ($mca && $mca[0] != '/') {
  1579. $mca = '/' . $mca;
  1580. }
  1581. if ($currentArea == 'admin') {
  1582. //Removes part of url that appears after pressing "Reset Filter" or "Search" button in grid
  1583. //(when not using ajax to reload the page)
  1584. $mca = preg_replace('|/filter/((\S)+)?/form_key/[A-Za-z0-9]+/?|', '/', $mca);
  1585. //Delete secret key from url
  1586. $mca = preg_replace('|/(index/)?key/[A-Za-z0-9]+/?|', '/', $mca);
  1587. //Delete action part of mca if it's index
  1588. $mca = preg_replace('|/index/?$|', '/', $mca);
  1589. } elseif ($currentArea == 'frontend') {
  1590. //Delete action part of mca if it's index
  1591. $mca = preg_replace('|/index/?$|', '/', $mca);
  1592. }
  1593. return preg_replace('|^/|', '', $mca);
  1594. }
  1595. /**
  1596. * Get URL of the specified page
  1597. *
  1598. * @param string $area Application area
  1599. * @param string $page UIMap page key
  1600. *
  1601. * @return string
  1602. */
  1603. public function getPageUrl($area, $page)
  1604. {
  1605. return $this->_uimapHelper->getPageUrl($area, $page, $this->_paramsHelper);
  1606. }
  1607. /**
  1608. * Get part of UIMap for specified uimap element(does not use for 'message' element)
  1609. *
  1610. * @param string $elementType
  1611. * @param string $elementName
  1612. * @param Mage_Selenium_Uimap_Page|null $uimap
  1613. *
  1614. * @return mixed
  1615. * @throws PHPUnit_Framework_AssertionFailedError
  1616. */
  1617. protected function _findUimapElement($elementType, $elementName, $uimap = null)
  1618. {
  1619. $fieldSetsNotInTab = null;
  1620. $errorMessage = null;
  1621. $returnValue = null;
  1622. if (is_null($uimap)) {
  1623. if ($elementType == 'button') {
  1624. $generalButtons = $this->getCurrentUimapPage()->getMainButtons();
  1625. if (isset($generalButtons[$elementName])) {
  1626. return $generalButtons[$elementName];
  1627. }
  1628. }
  1629. if ($elementType != 'fieldset' && $elementType != 'tab') {
  1630. $uimap = $this->_getActiveTabUimap();
  1631. if (is_null($uimap)) {
  1632. $uimap = $this->getCurrentUimapPage();
  1633. } else {
  1634. $mainForm = $this->getCurrentUimapPage()->getMainForm();
  1635. $fieldSetsNotInTab = $mainForm->getMainFormFieldsets();
  1636. }
  1637. } else {
  1638. $uimap = $this->getCurrentUimapPage();
  1639. }
  1640. }
  1641. $method = 'find' . ucfirst(strtolower($elementType));
  1642. try {
  1643. $returnValue = $uimap->$method($elementName, $this->_paramsHelper);
  1644. } catch (Exception $e) {
  1645. $messagesOnPage = self::messagesToString($this->getMessagesOnPage());
  1646. $errorMessage =
  1647. 'Current location url: ' . $this->getLocation() . "\n" . 'Current page "' . $this->getCurrentPage()
  1648. . '": ' . $e->getMessage() . ' - "' . $elementName . '"';
  1649. if (strlen($messagesOnPage) > 0) {
  1650. $errorMessage .= "\nMessages on current page:\n" . $messagesOnPage;
  1651. }
  1652. }
  1653. if (isset($e) && $fieldSetsNotInTab != null) {
  1654. foreach ($fieldSetsNotInTab as $fieldset) {
  1655. try {
  1656. $returnValue = $fieldset->$method($elementName, $this->_paramsHelper);
  1657. } catch (Exception $_e) {
  1658. }
  1659. }
  1660. }
  1661. if ($errorMessage != null && $returnValue === null) {
  1662. throw new PHPUnit_Framework_AssertionFailedError($errorMessage);
  1663. }
  1664. return $returnValue;
  1665. }
  1666. /**
  1667. * Get part of UIMap for opened tab
  1668. * @return mixed
  1669. */
  1670. protected function _getActiveTabUimap()
  1671. {
  1672. $tabData = $this->getCurrentUimapPage()->getAllTabs($this->_paramsHelper);
  1673. foreach ($tabData as $tabUimap) {
  1674. $isTabOpened = '';
  1675. $tabXpath = $tabUimap->getXpath();
  1676. if (preg_match('/^css=/', $tabXpath)) {
  1677. if ($this->isElementPresent($tabXpath . '[class]')) {
  1678. $isTabOpened = $this->getAttribute($tabXpath . '@class');
  1679. }
  1680. } elseif ($this->isElementPresent($tabXpath . '[@class]')) {
  1681. $isTabOpened = $this->getAttribute($tabXpath . '@class');
  1682. } elseif ($this->isElementPresent($tabXpath . '/parent::*[@class]')) {
  1683. $isTabOpened = $this->getAttribute($tabXpath . '/parent::*@class');
  1684. }
  1685. if (preg_match('/active/', $isTabOpened)) {
  1686. return $tabUimap;
  1687. }
  1688. }
  1689. return null;
  1690. }
  1691. /**
  1692. * Gets XPath of a control with the specified name and type.
  1693. *
  1694. * @param string $controlType Type of control (e.g. button | link | radiobutton | checkbox)
  1695. * @param string $controlName Name of a control from UIMap
  1696. * @param mixed $uimap
  1697. *
  1698. * @return string
  1699. */
  1700. protected function _getControlXpath($controlType, $controlName, $uimap = null)
  1701. {
  1702. if ($controlType === 'message') {
  1703. return $this->_getMessageXpath($controlName);
  1704. }
  1705. $xpath = $this->_findUimapElement($controlType, $controlName, $uimap);
  1706. if (is_object($xpath) && method_exists($xpath, 'getXPath')) {
  1707. $xpath = $xpath->getXPath($this->_paramsHelper);
  1708. }
  1709. return $xpath;
  1710. }
  1711. /**
  1712. * Gets XPath of a message with the specified name.
  1713. *
  1714. * @param string $message Name of a message from UIMap
  1715. *
  1716. * @return string
  1717. * @throws RuntimeException
  1718. */
  1719. protected function _getMessageXpath($message)
  1720. {
  1721. $messages = $this->getCurrentUimapPage()->getAllElements('messages');
  1722. $messageLocator = $messages->get($message, $this->_paramsHelper);
  1723. if ($messageLocator === null) {
  1724. $messagesOnPage = self::messagesToString($this->getMessagesOnPage());
  1725. $errorMessage =
  1726. 'Current location url: ' . $this->getLocation() . "\n" . 'Current page "' . $this->getCurrentPage()
  1727. . '": ' . 'Message "' . $message . '" is not found';
  1728. if (strlen($messagesOnPage) > 0) {
  1729. $errorMessage .= "\nMessages on current page:\n" . $messagesOnPage;
  1730. }
  1731. throw new RuntimeException($errorMessage);
  1732. }
  1733. return $messageLocator;
  1734. }
  1735. /**
  1736. * Gets map data values to UIPage form
  1737. *
  1738. * @param mixed $fieldsets Array of fieldsets to fill
  1739. * @param array $data Array of data to fill
  1740. *
  1741. * @return array
  1742. */
  1743. protected function _getFormDataMap($fieldsets, $data)
  1744. {
  1745. $dataMap = array();
  1746. $uimapFields = array();
  1747. foreach ($data as $dataFieldName => $dataFieldValue) {
  1748. if ($dataFieldValue == '%noValue%') {
  1749. continue;
  1750. }
  1751. foreach ($fieldsets as $fieldset) {
  1752. $uimapFields[self::FIELD_TYPE_MULTISELECT] = $fieldset->getAllMultiselects();
  1753. $uimapFields[self::FIELD_TYPE_DROPDOWN] = $fieldset->getAllDropdowns();
  1754. $uimapFields[self::FIELD_TYPE_RADIOBUTTON] = $fieldset->getAllRadiobuttons();
  1755. $uimapFields[self::FIELD_TYPE_CHECKBOX] = $fieldset->getAllCheckboxes();
  1756. $uimapFields[self::FIELD_TYPE_INPUT] = $fieldset->getAllFields();
  1757. foreach ($uimapFields as $fieldsType => $fieldsData) {
  1758. foreach ($fieldsData as $uimapFieldName => $uimapFieldValue) {
  1759. if ($dataFieldName == $uimapFieldName) {
  1760. $dataMap[$dataFieldName] = array('type' => $fieldsType,
  1761. 'path' => $uimapFieldValue,
  1762. 'value' => $dataFieldValue);
  1763. break 3;
  1764. }
  1765. }
  1766. }
  1767. }
  1768. }
  1769. return $dataMap;
  1770. }
  1771. /**
  1772. * Gets map data values to UIPage fieldset
  1773. *
  1774. * @param array $data
  1775. * @param string $fieldsetId
  1776. *
  1777. * @return array
  1778. */
  1779. protected function formFieldsetDataMap(array $data, $fieldsetId)
  1780. {
  1781. $fieldsetUimap = $this->_findUimapElement('fieldset', $fieldsetId);
  1782. $fieldsetElements = $fieldsetUimap->getFieldsetElements();
  1783. $fillData = array();
  1784. foreach ($data as $fieldName => $fieldValue) {
  1785. if ($fieldValue == '%noValue%' || is_array($fieldValue)) {
  1786. $fillData['skipped'][$fieldName] = $fieldValue;
  1787. continue;
  1788. }
  1789. foreach ($fieldsetElements as $elementType => $elementsData) {
  1790. if (isset($elementsData[$fieldName])) {
  1791. $fillData['inFieldset'][] = array('type' => $elementType,
  1792. 'name' => $fieldName,
  1793. 'value' => $fieldValue,
  1794. 'xpath' => $elementsData[$fieldName]);
  1795. continue 2;
  1796. }
  1797. }
  1798. $fillData['outFieldset'][$fieldName] = $fieldValue;
  1799. }
  1800. return $fillData;
  1801. }
  1802. ################################################################################
  1803. # #
  1804. # Framework helper methods #
  1805. # #
  1806. ################################################################################
  1807. /**
  1808. * Returns HTTP response for the specified URL.
  1809. *
  1810. * @param string $url
  1811. *
  1812. * @return array
  1813. *
  1814. * @throws RuntimeException when an internal CURL error happens
  1815. */
  1816. public function getHttpResponse($url)
  1817. {
  1818. $curl = curl_init();
  1819. curl_setopt($curl, CURLOPT_URL, $url);
  1820. curl_setopt($curl, CURLOPT_HEADER, true);
  1821. curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 60);
  1822. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  1823. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  1824. $response = curl_exec($curl);
  1825. $info = curl_getinfo($curl);
  1826. if (!$info) {
  1827. throw new RuntimeException("CURL error when accessing '$url': " . curl_error($curl));
  1828. }
  1829. curl_close($curl);
  1830. return $info;
  1831. }
  1832. /**
  1833. * Verifies if an external service is available.
  1834. *
  1835. * @param string $url
  1836. *
  1837. * @return bool True if the response is 200 or redirects to a such page. False otherwise.
  1838. */
  1839. public function httpResponseIsOK($url)
  1840. {
  1841. $maxRedirects = 100;
  1842. $response = null;
  1843. do {
  1844. $response = $this->getHttpResponse($url);
  1845. $url = ($response['http_code'] == 301)
  1846. ? $response['redirect_url']
  1847. : null;
  1848. $maxRedirects--;
  1849. } while ($url && $maxRedirects > 0);
  1850. return $response['http_code'] == 200;
  1851. }
  1852. /**
  1853. * SavesHTML content of the current page and return information about it.
  1854. * Return an empty string if the screenshotPath property is empty.
  1855. *
  1856. * @param null|string $fileName
  1857. *
  1858. * @return string
  1859. */
  1860. public function saveHtmlPage($fileName = null)
  1861. {
  1862. if (empty($this->screenshotPath)) {
  1863. return '';
  1864. }
  1865. if ($fileName == null) {
  1866. $fileName = date('d-m-Y-H-i-s') . '_' . $this->getName();
  1867. }
  1868. $filePath = $this->getScreenshotPath() . $fileName;
  1869. $file = fopen($filePath . '.html', 'a+');
  1870. fputs($file, $this->drivers[0]->getHtmlSource());
  1871. fflush($file);
  1872. fclose($file);
  1873. return 'HTML Page: ' . $filePath . ".html\n";
  1874. }
  1875. /**
  1876. * Take a screenshot and return information about it.
  1877. * Return an empty string if the screenshotPath property is empty.
  1878. *
  1879. * @param null|string $fileName
  1880. *
  1881. * @return string
  1882. */
  1883. public function takeScreenshot($fileName = null)
  1884. {
  1885. if (empty($this->screenshotPath)) {
  1886. return '';
  1887. }
  1888. try {
  1889. $screenshotContent = base64_decode($this->drivers[0]->captureEntirePageScreenshotToString());
  1890. } catch (Exception $e) {
  1891. return '';
  1892. }
  1893. if (empty($screenshotContent)) {
  1894. return '';
  1895. }
  1896. if ($fileName == null) {
  1897. $fileName = time() . '-' . get_class($this) . '-' . $this->getName();
  1898. $fileName = preg_replace('/ /', '_', preg_replace('/"/', '\'', $fileName));
  1899. $fileName = preg_replace('/_with_data_set/', '-set', $fileName);
  1900. }
  1901. $filePath = $this->getScreenshotPath() . $fileName;
  1902. $file = fopen($filePath . '.png', 'a+');
  1903. fputs($file, $screenshotContent);
  1904. fflush($file);
  1905. fclose($file);
  1906. return 'Screenshot: ' . $filePath . ".png\n";
  1907. }
  1908. /**
  1909. * Operation System definition
  1910. *
  1911. * @return string Windows|Linux|MacOS|Unknown OS
  1912. */
  1913. public function detectOS()
  1914. {
  1915. $osName = $this->getEval('navigator.userAgent');
  1916. if (preg_match('/Windows/i', $osName)) {
  1917. return 'Windows';
  1918. } elseif (preg_match('/Linux/i', $osName)) {
  1919. return 'Linux';
  1920. } elseif (preg_match('/Macintosh/i', $osName)) {
  1921. return 'MacOS';
  1922. }
  1923. return 'Unknown OS';
  1924. }
  1925. /**
  1926. * Get TestCase Id
  1927. *
  1928. * @return string
  1929. */
  1930. public function getTestId()
  1931. {
  1932. return $this->testId;
  1933. }
  1934. /**
  1935. * Set test case Id
  1936. *
  1937. * @param $testId
  1938. *
  1939. * @return Mage_Selenium_TestCase
  1940. */
  1941. public function setTestId($testId)
  1942. {
  1943. $this->drivers[0]->setTestId($testId);
  1944. $this->testId = $testId;
  1945. return $this;
  1946. }
  1947. /**
  1948. * Returns correct path to screenshot save path.
  1949. *
  1950. * @return string
  1951. */
  1952. public function getScreenshotPath()
  1953. {
  1954. return parent::getScreenshotPath();
  1955. }
  1956. /**
  1957. * Set screenshot path (current test)
  1958. *
  1959. * @param $path
  1960. *
  1961. * @return Mage_Selenium_TestCase
  1962. */
  1963. public function setScreenshotPath($path)
  1964. {
  1965. $this->screenshotPath = $path;
  1966. return $this;
  1967. }
  1968. /**
  1969. * Set default screenshot path (config)
  1970. *
  1971. * @param string $path
  1972. *
  1973. * @return Mage_Selenium_TestCase
  1974. */
  1975. public function setDefaultScreenshotPath($path)
  1976. {
  1977. $this->_configHelper->setScreenshotDir($path);
  1978. $this->setScreenshotPath($path);
  1979. return $this;
  1980. }
  1981. /**
  1982. * Get default screenshot path (config)
  1983. *
  1984. * @return string
  1985. */
  1986. public function getDefaultScreenshotPath()
  1987. {
  1988. return $this->_configHelper->getScreenshotDir();
  1989. }
  1990. /**
  1991. * Clicks a control with the specified name and type.
  1992. *
  1993. * @param string $controlType Type of control (e.g. button|link|radiobutton|checkbox)
  1994. * @param string $controlName Name of a control from UIMap
  1995. * @param bool $willChangePage Triggers page reloading. If clicking the control doesn't result<br>
  1996. * in page reloading, should be false (by default = true).
  1997. *
  1998. * @return Mage_Selenium_TestCase
  1999. */
  2000. public function clickControl($controlType, $controlName, $willChangePage = true)
  2001. {
  2002. $xpath = $this->_getControlXpath($controlType, $controlName);
  2003. if (!$this->isElementPresent($xpath) || !$this->isVisible($xpath)) {
  2004. $this->fail(
  2005. "Current location url: '" . $this->getLocation() . "'\nCurrent page: '" . $this->getCurrentPage()
  2006. . "'\nProblem with $controlType '$controlName', xpath '$xpath':\n"
  2007. . 'Control is not present on the page');
  2008. }
  2009. $this->click($xpath);
  2010. if ($willChangePage) {
  2011. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  2012. $this->addParameter('id', $this->defineIdFromUrl());
  2013. $this->validatePage();
  2014. }
  2015. return $this;
  2016. }
  2017. /**
  2018. * Click a button with the specified name
  2019. *
  2020. * @param string $button Name of a control from UIMap
  2021. * @param bool $willChangePage Triggers page reloading. If clicking the control doesn't result<br>
  2022. * in page reloading, should be false (by default = true).
  2023. *
  2024. * @return Mage_Selenium_TestCase
  2025. */
  2026. public function clickButton($button, $willChangePage = true)
  2027. {
  2028. return $this->clickControl('button', $button, $willChangePage);
  2029. }
  2030. /**
  2031. * Clicks a control with the specified name and type
  2032. * and confirms the confirmation popup with the specified message.
  2033. *
  2034. * @param string $controlType Type of control (e.g. button|link)
  2035. * @param string $controlName Name of a control from UIMap
  2036. * @param string $message Confirmation message
  2037. * @param bool $willChangePage Triggers page reloading. If clicking the control doesn't result<br>
  2038. * in page reloading, should be false (by default = true).
  2039. *
  2040. * @return bool
  2041. */
  2042. public function clickControlAndConfirm($controlType, $controlName, $message, $willChangePage = true)
  2043. {
  2044. $buttonXpath = $this->_getControlXpath($controlType, $controlName);
  2045. if ($this->isElementPresent($buttonXpath)) {
  2046. $confirmation = $this->_getMessageXpath($message);
  2047. $this->chooseCancelOnNextConfirmation();
  2048. $this->click($buttonXpath);
  2049. if ($this->isConfirmationPresent()) {
  2050. $text = $this->getConfirmation();
  2051. if ($text == $confirmation) {
  2052. $this->chooseOkOnNextConfirmation();
  2053. $this->click($buttonXpath);
  2054. $this->getConfirmation();
  2055. if ($willChangePage) {
  2056. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  2057. $this->validatePage();
  2058. }
  2059. return true;
  2060. } else {
  2061. $this->addVerificationMessage("The confirmation text incorrect: {$text}");
  2062. }
  2063. } else {
  2064. $this->addVerificationMessage('The confirmation does not appear');
  2065. if ($willChangePage) {
  2066. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  2067. $this->validatePage();
  2068. }
  2069. return true;
  2070. }
  2071. } else {
  2072. $this->addVerificationMessage("There is no way to click on control(There is no '$controlName' control)");
  2073. }
  2074. return false;
  2075. }
  2076. /**
  2077. * Submit form and confirm the confirmation popup with the specified message.
  2078. *
  2079. * @param string $buttonName Name of a button from UIMap
  2080. * @param string $message Confirmation message id from UIMap
  2081. * @param bool $willChangePage Triggers page reloading. If clicking the control doesn't result<br>
  2082. * in page reloading, should be false (by default = true).
  2083. *
  2084. * @return bool
  2085. */
  2086. public function clickButtonAndConfirm($buttonName, $message, $willChangePage = true)
  2087. {
  2088. return $this->clickControlAndConfirm('button', $buttonName, $message, $willChangePage);
  2089. }
  2090. /**
  2091. * Searches a control with the specified name and type on the page.
  2092. * If the control is present, returns true; otherwise false.
  2093. *
  2094. * @param string $controlType Type of control (e.g. button | link | radiobutton | checkbox)
  2095. * @param string $controlName Name of a control from UIMap
  2096. *
  2097. * @return bool
  2098. */
  2099. public function controlIsPresent($controlType, $controlName)
  2100. {
  2101. $xpath = $this->_getControlXpath($controlType, $controlName);
  2102. if ($this->isElementPresent($xpath)) {
  2103. return true;
  2104. }
  2105. return false;
  2106. }
  2107. /**
  2108. * Searches a control with the specified name and type on the page.
  2109. * If the control is visible, returns true; otherwise false.
  2110. *
  2111. * @param string $controlType Type of control (e.g. button | link | radiobutton | checkbox)
  2112. * @param string $controlName Name of a control from UIMap
  2113. *
  2114. * @return bool
  2115. */
  2116. public function controlIsVisible($controlType, $controlName)
  2117. {
  2118. $xpath = $this->_getControlXpath($controlType, $controlName);
  2119. if ($this->isElementPresent($xpath) && $this->isVisible($xpath)) {
  2120. return true;
  2121. }
  2122. return false;
  2123. }
  2124. /**
  2125. * Searches a button with the specified name on the page.
  2126. * If the button is present, returns true; otherwise false.
  2127. *
  2128. * @param string $button Name of a button from UIMap
  2129. *
  2130. * @return bool
  2131. */
  2132. public function buttonIsPresent($button)
  2133. {
  2134. return $this->controlIsPresent('button', $button);
  2135. }
  2136. /**
  2137. * Open tab
  2138. *
  2139. * @param string $tabName tab id from uimap
  2140. *
  2141. * @throws OutOfRangeException
  2142. */
  2143. public function openTab($tabName)
  2144. {
  2145. $waitAjax = false;
  2146. $isTabOpened = $this->getTabAttribute($tabName, 'class');
  2147. if (!preg_match('/active/', $isTabOpened)) {
  2148. if (preg_match('/ajax/', $isTabOpened)) {
  2149. $waitAjax = true;
  2150. }
  2151. $this->clickControl('tab', $tabName, false);
  2152. if ($waitAjax) {
  2153. $this->pleaseWait();
  2154. }
  2155. }
  2156. }
  2157. /**
  2158. * Get attribute value(like @id, @class) in tab xpath
  2159. *
  2160. * @param string $tabName
  2161. * @param string $attribute
  2162. *
  2163. * @return string
  2164. * @throws OutOfRangeException
  2165. */
  2166. public function getTabAttribute($tabName, $attribute)
  2167. {
  2168. $tabXpath = $this->_getControlXpath('tab', $tabName);
  2169. if (preg_match('/^css=/', $tabXpath)) {
  2170. if ($this->isElementPresent($tabXpath . '[' . $attribute . ']')) {
  2171. return $this->getAttribute($tabXpath . '@' . $attribute);
  2172. }
  2173. throw new OutOfRangeException("Wrong css for tab: [$tabName : $tabXpath]");
  2174. }
  2175. if ($this->isElementPresent($tabXpath . '[@' . $attribute . ']')) {
  2176. return $this->getAttribute($tabXpath . '@' . $attribute);
  2177. }
  2178. if ($this->isElementPresent($tabXpath . '/parent::*[@' . $attribute . ']')) {
  2179. return $this->getAttribute($tabXpath . '/parent::*@' . $attribute);
  2180. }
  2181. throw new OutOfRangeException("Wrong xpath for tab: [$tabName : $tabXpath]");
  2182. }
  2183. /**
  2184. * Gets all element(s) by XPath
  2185. *
  2186. * @param string $xpath General XPath of looking up element(s)
  2187. * @param string $get What to get. Allowed params: 'text' or 'value' (by default = 'text')
  2188. * @param string $additionalXPath Additional XPath (by default= '')
  2189. *
  2190. * @return array
  2191. * @throws OutOfRangeException
  2192. */
  2193. public function getElementsByXpath($xpath, $get = 'text', $additionalXPath = '')
  2194. {
  2195. $elements = array();
  2196. if (!empty($xpath)) {
  2197. $totalElements = $this->getXpathCount($xpath);
  2198. $pos = stripos(trim($xpath), 'css=');
  2199. for ($i = 1; $i < $totalElements + 1; $i++) {
  2200. if ($pos !== false && $pos == 0) {
  2201. $x = $xpath . ':nth(' . ($i - 1) . ')';
  2202. } else {
  2203. $x = $xpath . '[' . $i . ']';
  2204. }
  2205. switch ($get) {
  2206. case 'value' :
  2207. $element = $this->getValue($x);
  2208. break;
  2209. case 'text' :
  2210. $element = $this->getText($x);
  2211. break;
  2212. default :
  2213. throw new OutOfRangeException('Possible values of the variable $get only "text" and "value"');
  2214. break;
  2215. }
  2216. if (!empty($element)) {
  2217. if ($additionalXPath) {
  2218. if ($this->isElementPresent($x . $additionalXPath)) {
  2219. $label = trim($this->getText($x . $additionalXPath), " *\t\n\r");
  2220. } else {
  2221. $label = $this->getAttribute($x . "@id");
  2222. $label = strrev($label);
  2223. $label = strrev(substr($label, 0, strpos($label, "-")));
  2224. }
  2225. if ($label) {
  2226. $element = '"' . $label . '": ' . $element;
  2227. }
  2228. }
  2229. $elements[] = $element;
  2230. }
  2231. }
  2232. }
  2233. return $elements;
  2234. }
  2235. /**
  2236. * Gets an element by XPath
  2237. *
  2238. * @param string $xpath XPath of an element to look up
  2239. * @param string $get What to get. Allowed params: 'text' or 'value' (by default = 'text')
  2240. *
  2241. * @return mixed
  2242. */
  2243. public function getElementByXpath($xpath, $get = 'text')
  2244. {
  2245. $elements = $this->getElementsByXpath($xpath, $get);
  2246. return array_shift($elements);
  2247. }
  2248. /**
  2249. * Returns number of nodes that match the specified CSS selector,
  2250. * eg. "table" would give number of tables.
  2251. *
  2252. * @param string $locator CSS selector
  2253. *
  2254. * @return int
  2255. */
  2256. public function getCssCount($locator)
  2257. {
  2258. $script = "this.browserbot.evaluateCssCount('" . addslashes($locator) . "', this.browserbot.getDocument())";
  2259. return $this->getEval($script);
  2260. }
  2261. /**
  2262. * Returns number of nodes that match the specified xPath selector,
  2263. * eg. "table" would give number of tables.
  2264. *
  2265. * @param string $locator xPath selector
  2266. *
  2267. * @return int
  2268. */
  2269. public function getXpathCount($locator)
  2270. {
  2271. $pos = stripos(trim($locator), 'css=');
  2272. if ($pos !== false && $pos == 0) {
  2273. return $this->getCssCount($locator);
  2274. }
  2275. return parent::getXpathCount($locator);
  2276. }
  2277. /**
  2278. * Returns table column names
  2279. *
  2280. * @param string $tableXpath
  2281. *
  2282. * @return array
  2283. */
  2284. public function getTableHeadRowNames($tableXpath = '//table[@id]')
  2285. {
  2286. $xpath = $tableXpath . "//tr[normalize-space(@class)='headings']";
  2287. if (!$this->isElementPresent($xpath)) {
  2288. $this->fail('Incorrect table head xpath: ' . $xpath);
  2289. }
  2290. $cellNum = $this->getXpathCount($xpath . '/th');
  2291. $headNames = array();
  2292. for ($cell = 0; $cell < $cellNum; $cell++) {
  2293. $cellLocator = $tableXpath . '.0.' . $cell;
  2294. $headNames[$cell] = $this->getTable($cellLocator);
  2295. }
  2296. return array_diff($headNames, array(''));
  2297. }
  2298. /**
  2299. * Returns table column ID based on the column name.
  2300. *
  2301. * @param string $columnName
  2302. * @param string $tableXpath
  2303. *
  2304. * @return int
  2305. */
  2306. public function getColumnIdByName($columnName, $tableXpath = '//table[@id]')
  2307. {
  2308. return array_search($columnName, $this->getTableHeadRowNames($tableXpath)) + 1;
  2309. }
  2310. /**
  2311. * Waits for the element to appear
  2312. *
  2313. * @param string|array $locator XPath locator or array of locators
  2314. * @param int $timeout Timeout period in seconds (by default = 40)
  2315. *
  2316. * @return bool
  2317. */
  2318. public function waitForElement($locator, $timeout = 40)
  2319. {
  2320. $iStartTime = time();
  2321. while ($timeout > time() - $iStartTime) {
  2322. if (is_array($locator)) {
  2323. foreach ($locator as $loc) {
  2324. if ($this->isElementPresent($loc)) {
  2325. sleep(1);
  2326. return true;
  2327. }
  2328. }
  2329. } else {
  2330. if ($this->isElementPresent($locator)) {
  2331. sleep(1);
  2332. return true;
  2333. }
  2334. }
  2335. sleep(1);
  2336. }
  2337. return false;
  2338. }
  2339. /**
  2340. * Waits for the element(s) to be visible
  2341. *
  2342. * @param string|array $locator XPath locator or array of locators
  2343. * @param int $timeout Timeout period in seconds (by default = 40)
  2344. *
  2345. * @return bool
  2346. */
  2347. public function waitForElementVisible($locator, $timeout = 40)
  2348. {
  2349. $iStartTime = time();
  2350. while ($timeout > time() - $iStartTime) {
  2351. if (is_array($locator)) {
  2352. foreach ($locator as $loc) {
  2353. if ($this->isElementPresent($loc) && $this->isVisible($loc)) {
  2354. return true;
  2355. }
  2356. }
  2357. } else {
  2358. if ($this->isElementPresent($locator) && $this->isVisible($locator)) {
  2359. return true;
  2360. }
  2361. }
  2362. sleep(1);
  2363. }
  2364. return false;
  2365. }
  2366. /**
  2367. * Waits for AJAX request to continue.<br>
  2368. * Method works only if AJAX request was sent by Prototype or JQuery framework.
  2369. *
  2370. * @param int $timeout Timeout period in milliseconds. If not set, uses a default period.
  2371. */
  2372. public function waitForAjax($timeout = null)
  2373. {
  2374. if (is_null($timeout)) {
  2375. $timeout = $this->_browserTimeoutPeriod;
  2376. }
  2377. $jsCondition = 'var c = function(){if(typeof selenium.browserbot.getCurrentWindow().Ajax != "undefined"){'
  2378. . 'if(selenium.browserbot.getCurrentWindow().Ajax.activeRequestCount){return false;};};'
  2379. . 'if(typeof selenium.browserbot.getCurrentWindow().jQuery != "undefined"){'
  2380. . 'if(selenium.browserbot.getCurrentWindow().jQuery.active){return false;};};return true;};c();';
  2381. $this->waitForCondition($jsCondition, $timeout);
  2382. }
  2383. /**
  2384. * Click 'Save and continue edit' control on page with tabs
  2385. *
  2386. * @param string $controlType
  2387. * @param string $controlName
  2388. */
  2389. public function saveAndContinueEdit($controlType, $controlName)
  2390. {
  2391. $tabUimap = $this->_getActiveTabUimap();
  2392. $name = $tabUimap->getTabId();
  2393. $this->addParameter('tab', $this->getTabAttribute($name, 'id'));
  2394. $this->clickControlAndWaitMessage($controlType, $controlName);
  2395. }
  2396. /**
  2397. * Submits the opened form.
  2398. *
  2399. * @param string $buttonName Name of the button, what intended to save (submit) form (from UIMap)
  2400. * @param bool $validate
  2401. *
  2402. * @return Mage_Selenium_TestCase
  2403. */
  2404. public function saveForm($buttonName, $validate = true)
  2405. {
  2406. return $this->clickControlAndWaitMessage('button', $buttonName, $validate);
  2407. }
  2408. /**
  2409. * Click control and wait message
  2410. *
  2411. * @param string $controlType Type of control (e.g. button|link)
  2412. * @param string $controlName Name of a control from UIMap
  2413. * @param bool $validate
  2414. *
  2415. * @return Mage_Selenium_TestCase
  2416. */
  2417. public function clickControlAndWaitMessage($controlType, $controlName, $validate = true)
  2418. {
  2419. $this->_parseMessages();
  2420. foreach (self::$_messages as $key => $value) {
  2421. self::$_messages[$key] = array_unique($value);
  2422. }
  2423. $success = $this->_getMessageXpath('general_success');
  2424. $error = $this->_getMessageXpath('general_error');
  2425. $validation = $this->_getMessageXpath('general_validation');
  2426. $types = array('success', 'error', 'validation');
  2427. foreach ($types as $message) {
  2428. if (array_key_exists($message, self::$_messages)) {
  2429. $exclude = '';
  2430. foreach (self::$_messages[$message] as $messageText) {
  2431. $exclude .= "[not(..//.='$messageText')]";
  2432. }
  2433. ${$message} .= $exclude;
  2434. }
  2435. }
  2436. $this->clickControl($controlType, $controlName, false);
  2437. $this->waitForElement(array($success, $error, $validation));
  2438. $this->addParameter('id', $this->defineIdFromUrl());
  2439. if ($validate) {
  2440. $this->validatePage();
  2441. }
  2442. return $this;
  2443. }
  2444. /**
  2445. * Performs scrolling to the specified element in the specified list(block) with the specified name.
  2446. *
  2447. * @param string $elementType Type of the element that should be visible after scrolling
  2448. * @param string $elementName Name of the element that should be visible after scrolling
  2449. * @param string $blockType Type of the block where to use scroll
  2450. * @param string $blockName Name of the block where to use scroll
  2451. */
  2452. public function moveScrollToElement($elementType, $elementName, $blockType, $blockName)
  2453. {
  2454. // Getting XPath of the element what should be visible after scrolling
  2455. $specElementXpath = $this->_getControlXpath($elementType, $elementName);
  2456. // Getting @ID of the element what should be visible after scrolling
  2457. $specElementId = $this->getAttribute($specElementXpath . "/@id");
  2458. // Getting XPath of the block where scroll is using
  2459. $specFieldsetXpath = $this->_getControlXpath($blockType, $blockName);
  2460. // Getting @ID of the block where scroll is using
  2461. $specFieldsetId = $this->getAttribute($specFieldsetXpath . "/@id");
  2462. // Getting offset position of the element what should be visible after scrolling
  2463. $destinationOffsetTop = $this->getEval("this.browserbot.findElement('id=" . $specElementId . "').offsetTop");
  2464. // Moving scroll bar to previously defined offset
  2465. // Position (to the element what should be visible after scrolling)
  2466. $this->getEval(
  2467. "this.browserbot.findElement('id=" . $specFieldsetId . "').scrollTop = " . $destinationOffsetTop);
  2468. }
  2469. /**
  2470. * Moves the specified element (with type = $elementType and name = $elementName)<br>
  2471. * over the specified JS tree (with type = $blockType and name = $blockName)<br>
  2472. * to position = $moveToPosition
  2473. *
  2474. * @param string $elementType Type of the element to move
  2475. * @param string $elementName Name of the element to move
  2476. * @param string $blockType Type of the block that contains JS tree
  2477. * @param string $blockName Name of the block that contains JS tree
  2478. * @param integer $moveToPosition Index of the position where element should be after moving (default = 1)
  2479. */
  2480. public function moveElementOverTree($elementType, $elementName, $blockType, $blockName, $moveToPosition = 1)
  2481. {
  2482. // Getting XPath of the element to move
  2483. $specElementXpath = $this->_getControlXpath($elementType, $elementName);
  2484. // Getting @ID of the element to move
  2485. $specElementId = $this->getAttribute($specElementXpath . "/@id");
  2486. // Getting XPath of the block what is a JS tree
  2487. $specFieldsetXpath = $this->_getControlXpath($blockType, $blockName);
  2488. // Getting @ID of the block what is a JS tree
  2489. $specFieldsetId = $this->getAttribute($specFieldsetXpath . "/@id");
  2490. // Getting offset position of the element to move
  2491. $destinationOffsetTop = $this->getEval("this.browserbot.findElement('id=" . $specElementId . "').offsetTop");
  2492. // Storing of current height of the block with JS tree
  2493. $tmpBlockHeight =
  2494. (integer)$this->getEval("this.browserbot.findElement('id=" . $specFieldsetId . "').style.height");
  2495. // If element to move situated abroad of the current height, it will be increased
  2496. if ($destinationOffsetTop >= $tmpBlockHeight) {
  2497. $destinationOffsetTop = $destinationOffsetTop + 50;
  2498. $this->getEval(
  2499. "this.browserbot.findElement('id=" . $specFieldsetId . "').style.height='" . $destinationOffsetTop
  2500. . "px'");
  2501. }
  2502. $this->clickAt($specElementXpath, '1,1');
  2503. $blockTo = $specFieldsetXpath . '//li[' . $moveToPosition . ']//a//span';
  2504. $this->mouseDownAt($specElementXpath, '1,1');
  2505. $this->mouseMoveAt($blockTo, '1,1');
  2506. $this->mouseUpAt($blockTo, '1,1');
  2507. $this->clickAt($specElementXpath, '1,1');
  2508. }
  2509. /**
  2510. * Searches for the specified data in specific the grid and opens the found item.
  2511. *
  2512. * @param array $data Array of data to look up
  2513. * @param bool $willChangePage Triggers page reloading. If clicking the control doesn't result<br>
  2514. * in page reloading, should be false (by default = true).
  2515. * @param string|null $fieldSetName Fieldset name that contains the grid (by default = null)
  2516. */
  2517. public function searchAndOpen(array $data, $willChangePage = true, $fieldSetName = null)
  2518. {
  2519. $this->_prepareDataForSearch($data);
  2520. $xpathTR = $this->search($data, $fieldSetName);
  2521. if ($xpathTR) {
  2522. if ($willChangePage) {
  2523. $itemId = $this->defineIdFromTitle($xpathTR);
  2524. $this->addParameter('id', $itemId);
  2525. $this->click($xpathTR . "/td[contains(text(),'" . $data[array_rand($data)] . "')]");
  2526. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  2527. $this->validatePage();
  2528. } else {
  2529. $this->click($xpathTR . "/td[contains(text(),'" . $data[array_rand($data)] . "')]");
  2530. $this->waitForAjax($this->_browserTimeoutPeriod);
  2531. }
  2532. } else {
  2533. $this->fail('Can\'t find item in grid for data: ' . print_r($data, true));
  2534. }
  2535. }
  2536. /**
  2537. * Searches for the specified data in specific the grid and selects the found item.
  2538. *
  2539. * @param array $data Array of data to look up
  2540. * @param string|null $fieldSetName Fieldset name that contains the grid (by default = null)
  2541. */
  2542. public function searchAndChoose(array $data, $fieldSetName = null)
  2543. {
  2544. $this->_prepareDataForSearch($data);
  2545. $xpathTR = $this->search($data, $fieldSetName);
  2546. if ($xpathTR) {
  2547. $xpathTR .= "//input[contains(@class,'checkbox') or contains(@class,'radio')][not(@disabled)]";
  2548. if ($this->getValue($xpathTR) == 'off') {
  2549. $this->click($xpathTR);
  2550. }
  2551. } else {
  2552. $this->fail('Cant\'t find item in grid for data: ' . print_r($data, true));
  2553. }
  2554. }
  2555. /**
  2556. * Prepare data array to search in grid
  2557. *
  2558. * @param array $data Array of data to look up
  2559. * @param array $checkFields
  2560. *
  2561. * @return array
  2562. */
  2563. protected function _prepareDataForSearch(array &$data, array $checkFields = array('dropdown' => 'website'))
  2564. {
  2565. $data = $this->arrayEmptyClear($data);
  2566. foreach ($checkFields as $fieldType => $fieldName) {
  2567. if (array_key_exists($fieldName, $data) && !$this->controlIsPresent($fieldType, $fieldName)) {
  2568. unset($data[$fieldName]);
  2569. }
  2570. }
  2571. return $data;
  2572. }
  2573. /**
  2574. * Searches the specified data in the specific grid. Returns null or XPath of the found data.
  2575. *
  2576. * @param array $data Array of data to look up.
  2577. * @param string|null $fieldSetName Fieldset name that contains the grid (by default = null)
  2578. *
  2579. * @return string|null
  2580. */
  2581. public function search(array $data, $fieldSetName = null)
  2582. {
  2583. $waitAjax = true;
  2584. $xpath = '';
  2585. $xpathContainer = null;
  2586. if ($fieldSetName) {
  2587. $xpathContainer = $this->_findUimapElement('fieldset', $fieldSetName);
  2588. $xpath = $xpathContainer->getXpath($this->_paramsHelper);
  2589. }
  2590. $resetXpath = $this->_getControlXpath('button', 'reset_filter', $xpathContainer);
  2591. $jsName = $this->getAttribute($resetXpath . '@onclick');
  2592. $jsName = preg_replace('/\.[\D]+\(\)/', '', $jsName);
  2593. $scriptXpath = "//script[contains(text(),\"$jsName.useAjax = ''\")]";
  2594. if ($this->isElementPresent($scriptXpath)) {
  2595. $waitAjax = false;
  2596. }
  2597. $this->click($resetXpath);
  2598. if ($waitAjax) {
  2599. $this->waitForAjax();
  2600. } else {
  2601. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  2602. $this->validatePage();
  2603. }
  2604. $qtyElementsInTable = $this->_getControlXpath('pageelement', 'qtyElementsInTable');
  2605. //Forming xpath that contains string 'Total $number records found' where $number - number of items in table
  2606. $totalCount = intval($this->getText($xpath . $qtyElementsInTable));
  2607. $xpathPager = $xpath . $qtyElementsInTable . "[not(text()='" . $totalCount . "')]";
  2608. $xpathTR = $this->formSearchXpath($data);
  2609. if (!$this->isElementPresent($xpath . $xpathTR) && $totalCount > 20) {
  2610. // Fill in search form and click 'Search' button
  2611. $this->fillForm($data);
  2612. $this->clickButton('search', false);
  2613. $this->waitForElement($xpathPager);
  2614. }
  2615. if ($this->isElementPresent($xpath . $xpathTR)) {
  2616. return $xpath . $xpathTR;
  2617. }
  2618. return null;
  2619. }
  2620. /**
  2621. * Forming xpath that contains the data to look up
  2622. *
  2623. * @param array $data Array of data to look up
  2624. *
  2625. * @return string
  2626. */
  2627. public function formSearchXpath(array $data)
  2628. {
  2629. $xpathTR = "//table[@class='data']//tr";
  2630. foreach ($data as $key => $value) {
  2631. if (!preg_match('/_from/', $key) && !preg_match('/_to/', $key) && !is_array($value)) {
  2632. if (strpos($value, "'")) {
  2633. $value = "concat('" . str_replace('\'', "',\"'\",'", $value) . "')";
  2634. } else {
  2635. $value = "'" . $value . "'";
  2636. }
  2637. $xpathTR .= "[td[contains(text(),$value)]]";
  2638. }
  2639. }
  2640. return $xpathTR;
  2641. }
  2642. /**
  2643. * Fill fieldset
  2644. *
  2645. * @param array $data
  2646. * @param string $fieldsetId
  2647. * @param bool $failIfFieldsWithoutXpath
  2648. *
  2649. * @return bool
  2650. * @throws OutOfRangeException
  2651. */
  2652. public function fillFieldset(array $data, $fieldsetId, $failIfFieldsWithoutXpath = true)
  2653. {
  2654. $fillData = $this->formFieldsetDataMap($data, $fieldsetId);
  2655. if (!isset($fillData['inFieldset']) && !$failIfFieldsWithoutXpath) {
  2656. return false;
  2657. }
  2658. if (isset($fillData['outFieldset']) && $failIfFieldsWithoutXpath) {
  2659. $message =
  2660. "\n" . 'Current page "' . $this->getCurrentPage() . '": ' . 'There are no fields in "' . $fieldsetId
  2661. . '" fieldset:' . "\n" . implode("\n", array_keys($fillData['outFieldset']));
  2662. $this->fail($message);
  2663. }
  2664. foreach ($fillData['inFieldset'] as $fieldData) {
  2665. $this->_fill($fieldData);
  2666. }
  2667. return true;
  2668. }
  2669. /**
  2670. * Fill tab
  2671. *
  2672. * @param array $data
  2673. * @param string $tabId
  2674. * @param bool $failIfFieldsWithoutXpath
  2675. *
  2676. * @throws RuntimeException
  2677. */
  2678. public function fillTab(array $data, $tabId, $failIfFieldsWithoutXpath = true)
  2679. {
  2680. $tabUimap = $this->_findUimapElement('tab', $tabId);
  2681. $fieldsets = $tabUimap->getFieldsetNames();
  2682. if (empty($fieldsets)) {
  2683. throw new RuntimeException(
  2684. 'There is no fieldsets in "' . $tabId . '" tab on "' . $this->getCurrentPage() . '" page');
  2685. }
  2686. $fillTabData = array();
  2687. $errorFields = array();
  2688. foreach ($fieldsets as $fieldsetName) {
  2689. $fillFieldsetData = $this->formFieldsetDataMap($data, $fieldsetName);
  2690. if (isset($fillFieldsetData['inFieldset'])) {
  2691. $fillTabData = array_merge($fillTabData, $fillFieldsetData['inFieldset']);
  2692. }
  2693. if (isset($fillFieldsetData['outFieldset'])) {
  2694. $errorFields = $fillFieldsetData['outFieldset'];
  2695. $data = $fillFieldsetData['outFieldset'];
  2696. } else {
  2697. $errorFields = array();
  2698. break;
  2699. }
  2700. }
  2701. if (!empty($errorFields) && $failIfFieldsWithoutXpath) {
  2702. $message = "\n" . 'Current page "' . $this->getCurrentPage() . '": ' . 'There are no fields in "' . $tabId
  2703. . '" fieldset:' . "\n" . implode("\n", array_keys($errorFields));
  2704. $this->fail($message);
  2705. }
  2706. $this->openTab($tabId);
  2707. foreach ($fillTabData as $fieldData) {
  2708. $this->_fill($fieldData);
  2709. }
  2710. }
  2711. /**
  2712. * Fills any form with the provided data. Specific Tab can be filled only if $tabId is provided.
  2713. *
  2714. * @param array|string $data Array of data to fill or datasource name
  2715. * @param string $tabId Tab ID from UIMap (by default = '')
  2716. *
  2717. * @throws OutOfRangeException|PHPUnit_Framework_Exception
  2718. * @deprecated
  2719. * @see fillTab() or fillFieldset()
  2720. */
  2721. public function fillForm($data, $tabId = '')
  2722. {
  2723. if (is_string($data)) {
  2724. $data = $this->loadData($data);
  2725. }
  2726. $formData = $this->getCurrentUimapPage()->getMainForm();
  2727. if ($tabId && $formData->getTab($tabId)) {
  2728. $fieldsets = $formData->getTab($tabId)->getAllFieldsets($this->_paramsHelper);
  2729. } else {
  2730. $fieldsets = $formData->getAllFieldsets($this->_paramsHelper);
  2731. }
  2732. // if we have got empty UIMap but not empty dataset
  2733. if (empty($fieldsets)) {
  2734. throw new OutOfRangeException(
  2735. "Can't find main form in UIMap array for page '" . $this->getCurrentPage() . "', area['"
  2736. . $this->_configHelper->getArea() . "']");
  2737. }
  2738. $formDataMap = $this->_getFormDataMap($fieldsets, $data);
  2739. if ($tabId) {
  2740. $this->openTab($tabId);
  2741. }
  2742. try {
  2743. foreach ($formDataMap as $formFieldName => $formField) {
  2744. switch ($formField['type']) {
  2745. case self::FIELD_TYPE_INPUT:
  2746. $this->_fillFormField($formField);
  2747. break;
  2748. case self::FIELD_TYPE_CHECKBOX:
  2749. $this->_fillFormCheckbox($formField);
  2750. break;
  2751. case self::FIELD_TYPE_DROPDOWN:
  2752. $this->_fillFormDropdown($formField);
  2753. break;
  2754. case self::FIELD_TYPE_RADIOBUTTON:
  2755. $this->_fillFormRadiobutton($formField);
  2756. break;
  2757. case self::FIELD_TYPE_MULTISELECT:
  2758. $this->_fillFormMultiselect($formField);
  2759. break;
  2760. default:
  2761. throw new PHPUnit_Framework_Exception('Unsupported field type');
  2762. }
  2763. }
  2764. } catch (PHPUnit_Framework_Exception $e) {
  2765. $errorMessage = isset($formFieldName)
  2766. ? 'Problem with field \'' . $formFieldName . '\': ' . $e->getMessage()
  2767. : $e->getMessage();
  2768. $this->fail($errorMessage);
  2769. }
  2770. }
  2771. /**
  2772. * Verifies values on the opened form
  2773. *
  2774. * @param array|string $data Array of data to verify or datasource name
  2775. * @param string $tabId Defines a specific Tab on the page that contains the form to verify (by default = '')
  2776. * @param array $skipElements Array of elements that will be skipped during verification <br>
  2777. * (default = array('password'))
  2778. *
  2779. * @return bool
  2780. * @throws InvalidArgumentException|OutOfRangeException
  2781. */
  2782. public function verifyForm($data, $tabId = '', $skipElements = array('password', 'password_confirmation'))
  2783. {
  2784. if (is_string($data)) {
  2785. $data = $this->loadData($data);
  2786. }
  2787. $formData = $this->getCurrentUimapPage()->getMainForm();
  2788. if ($tabId && $formData->getTab($tabId)) {
  2789. $fieldsets = $formData->getTab($tabId)->getAllFieldsets($this->_paramsHelper);
  2790. } else {
  2791. $fieldsets = $formData->getAllFieldsets($this->_paramsHelper);
  2792. }
  2793. // if we have got empty UIMap but not empty dataset
  2794. if (empty($fieldsets)) {
  2795. throw new OutOfRangeException(
  2796. "Can't find main form in UIMap array for page '" . $this->getCurrentPage() . "', area['"
  2797. . $this->_configHelper->getArea() . "']");
  2798. }
  2799. if ($tabId) {
  2800. $this->openTab($tabId);
  2801. }
  2802. foreach ($data as $key => $value) {
  2803. if (in_array($key, $skipElements) || $value === '%noValue%') {
  2804. unset($data[$key]);
  2805. }
  2806. }
  2807. $formDataMap = $this->_getFormDataMap($fieldsets, $data);
  2808. $resultFlag = true;
  2809. foreach ($formDataMap as $formFieldName => $formField) {
  2810. switch ($formField['type']) {
  2811. case self::FIELD_TYPE_INPUT:
  2812. if ($this->isElementPresent($formField['path'])) {
  2813. $val = $this->getValue($formField['path']);
  2814. if ($val != $formField['value']) {
  2815. $this->addVerificationMessage(
  2816. $formFieldName . ": The stored value is not equal to specified: (" . $formField['value']
  2817. . "' != '" . $val . "')");
  2818. $resultFlag = false;
  2819. }
  2820. } else {
  2821. $this->addVerificationMessage('Can not find field (xpath:' . $formField['path'] . ')');
  2822. $resultFlag = false;
  2823. }
  2824. break;
  2825. case self::FIELD_TYPE_CHECKBOX:
  2826. case self::FIELD_TYPE_RADIOBUTTON:
  2827. if ($this->isElementPresent($formField['path'])) {
  2828. $isChecked = $this->isChecked($formField['path']);
  2829. $expectedVal = strtolower($formField['value']);
  2830. if (($isChecked && $expectedVal != 'yes')
  2831. || (!$isChecked && !($expectedVal == 'no' || $expectedVal == ''))
  2832. ) {
  2833. $printVal = ($isChecked)
  2834. ? 'yes'
  2835. : 'no';
  2836. $this->addVerificationMessage(
  2837. $formFieldName . ": The stored value is not equal to specified: (" . $expectedVal
  2838. . "' != '" . $printVal . "')");
  2839. $resultFlag = false;
  2840. }
  2841. } else {
  2842. $this->addVerificationMessage('Can not find field (xpath:' . $formField['path'] . ')');
  2843. $resultFlag = false;
  2844. }
  2845. break;
  2846. case self::FIELD_TYPE_DROPDOWN:
  2847. if ($this->isElementPresent($formField['path'])) {
  2848. $label = $this->getSelectedLabel($formField['path']);
  2849. if ($formField['value'] != $label) {
  2850. $this->addVerificationMessage(
  2851. $formFieldName . ": The stored value is not equal to specified: (" . $formField['value']
  2852. . "' != '" . $label . "')");
  2853. $resultFlag = false;
  2854. }
  2855. } else {
  2856. $this->addVerificationMessage('Can not find field (xpath:' . $formField['path'] . ')');
  2857. $resultFlag = false;
  2858. }
  2859. break;
  2860. case self::FIELD_TYPE_MULTISELECT:
  2861. if ($this->isElementPresent($formField['path'])) {
  2862. $selectedLabels = $this->getSelectedLabels($formField['path']);
  2863. $selectedLabels = array_map('trim', $selectedLabels, array(chr(0xC2) . chr(0xA0)));
  2864. $expectedLabels = explode(',', $formField['value']);
  2865. $expectedLabels = array_map('trim', $expectedLabels);
  2866. foreach ($expectedLabels as $value) {
  2867. if (!in_array($value, $selectedLabels)) {
  2868. $this->addVerificationMessage($formFieldName . ": The value '" . $value
  2869. . "' is not selected. (Selected values are: '"
  2870. . implode(', ', $selectedLabels) . "')");
  2871. $resultFlag = false;
  2872. }
  2873. }
  2874. if (count($selectedLabels) != count($expectedLabels)) {
  2875. $this->addVerificationMessage(
  2876. "Amounts of the expected options are not equal to selected: ('" . $formField['value']
  2877. . "' != '" . implode(', ', $selectedLabels) . "')");
  2878. $resultFlag = false;
  2879. }
  2880. } else {
  2881. $this->addVerificationMessage('Can not find field (xpath:' . $formField['path'] . ')');
  2882. $resultFlag = false;
  2883. }
  2884. break;
  2885. default:
  2886. $this->addVerificationMessage('Unsupported field type');
  2887. $resultFlag = false;
  2888. }
  2889. }
  2890. return $resultFlag;
  2891. }
  2892. /**
  2893. * Fill any type of field(dropdown|field|checkbox|multiselect|radiobutton)
  2894. *
  2895. * @param $fieldData
  2896. *
  2897. * @throws OutOfRangeException
  2898. */
  2899. protected function _fill($fieldData)
  2900. {
  2901. switch ($fieldData['type']) {
  2902. case self::FIELD_TYPE_INPUT:
  2903. $this->fillField($fieldData['name'], $fieldData['value'], $fieldData['xpath']);
  2904. break;
  2905. case self::FIELD_TYPE_CHECKBOX:
  2906. $this->fillCheckbox($fieldData['name'], $fieldData['value'], $fieldData['xpath']);
  2907. break;
  2908. case self::FIELD_TYPE_RADIOBUTTON:
  2909. $this->fillRadiobutton($fieldData['name'], $fieldData['value'], $fieldData['xpath']);
  2910. break;
  2911. case self::FIELD_TYPE_MULTISELECT:
  2912. $this->fillMultiselect($fieldData['name'], $fieldData['value'], $fieldData['xpath']);
  2913. break;
  2914. case self::FIELD_TYPE_DROPDOWN:
  2915. $this->fillDropdown($fieldData['name'], $fieldData['value'], $fieldData['xpath']);
  2916. break;
  2917. default:
  2918. throw new OutOfRangeException(
  2919. 'Unsupported field type: "' . $fieldData['type'] . '" for fillFieldset() function');
  2920. }
  2921. }
  2922. /**
  2923. * Fills a text field of ('field' | 'input') control type by typing a value.
  2924. *
  2925. * @param array $fieldData Array of a 'path' to control and 'value' to type
  2926. *
  2927. * @throws PHPUnit_Framework_Exception
  2928. * @deprecated
  2929. * @see fillField()
  2930. */
  2931. protected function _fillFormField($fieldData)
  2932. {
  2933. $this->fillField('', $fieldData['value'], $fieldData['path']);
  2934. }
  2935. /**
  2936. * Fills a text field of control type by typing a value.
  2937. *
  2938. * @param string $name
  2939. * @param string $value
  2940. * @param string|null $xpath
  2941. *
  2942. * @throws RuntimeException
  2943. */
  2944. protected function fillField($name, $value, $xpath = null)
  2945. {
  2946. if (is_null($xpath)) {
  2947. $xpath = $this->_getControlXpath('field', $name);
  2948. }
  2949. $errorMessage =
  2950. 'Current location url: \'' . $this->getLocation() . "'\nCurrent page: '" . $this->getCurrentPage() . "'\n"
  2951. . "Problem with field '$name' and xpath '$xpath':\n";
  2952. if ($this->isElementPresent($xpath)) {
  2953. $this->waitForEditable($xpath);
  2954. $this->type($xpath, $value);
  2955. $this->waitForAjax();
  2956. } else {
  2957. throw new RuntimeException($errorMessage . 'Element is not present on the page');
  2958. }
  2959. }
  2960. /**
  2961. * Fills 'multiselect' control by selecting the specified values.
  2962. *
  2963. * @param array $fieldData Array of a 'path' to control and 'value' to select
  2964. *
  2965. * @throws PHPUnit_Framework_Exception
  2966. * @deprecated
  2967. * @see fillMultiselect()
  2968. */
  2969. protected function _fillFormMultiselect($fieldData)
  2970. {
  2971. $this->fillMultiselect('', $fieldData['value'], $fieldData['path']);
  2972. }
  2973. /**
  2974. * Fills 'multiselect' control by selecting the specified values.
  2975. *
  2976. * @param string $name
  2977. * @param string $value
  2978. * @param string|null $xpath
  2979. *
  2980. * @throws RuntimeException
  2981. */
  2982. protected function fillMultiselect($name, $value, $xpath = null)
  2983. {
  2984. if (is_null($xpath)) {
  2985. $xpath = $this->_getControlXpath('multiselect', $name);
  2986. }
  2987. $errorMessage =
  2988. 'Current location url: \'' . $this->getLocation() . "'\nCurrent page: '" . $this->getCurrentPage() . "'\n"
  2989. . "Problem with multiselect field '$name' and xpath '$xpath':\n";
  2990. if ($this->isElementPresent($xpath)) {
  2991. if ($this->isEditable($xpath)) {
  2992. $this->removeAllSelections($xpath);
  2993. //@TODO
  2994. //$options = $this->getSelectOptions($xpath);
  2995. $valuesArray = array();
  2996. if (strtolower($value) == 'all') {
  2997. $count = $this->getXpathCount($xpath . '//option');
  2998. for ($i = 1; $i <= $count; $i++) {
  2999. $valuesArray[] = $this->getText($xpath . "//option[$i]");
  3000. }
  3001. } else {
  3002. $valuesArray = explode(',', $value);
  3003. $valuesArray = array_map('trim', $valuesArray);
  3004. }
  3005. foreach ($valuesArray as $v) {
  3006. if ($value != null) {
  3007. if ($this->isElementPresent($xpath . "//option[text()='" . $v . "']")) {
  3008. $this->addSelection($xpath, 'label=' . $v);
  3009. } else {
  3010. $this->addSelection($xpath, 'regexp:' . preg_quote($v));
  3011. }
  3012. }
  3013. }
  3014. } else {
  3015. throw new RuntimeException($errorMessage . 'Element is not editable');
  3016. }
  3017. } else {
  3018. throw new RuntimeException($errorMessage . 'Element is not present on the page');
  3019. }
  3020. }
  3021. /**
  3022. * Fills the 'dropdown' control by selecting the specified value.
  3023. *
  3024. * @param array $fieldData Array of a 'path' to control and 'value' to select
  3025. *
  3026. * @throws PHPUnit_Framework_Exception
  3027. * @deprecated
  3028. * @see fillDropdown()
  3029. */
  3030. protected function _fillFormDropdown($fieldData)
  3031. {
  3032. $this->fillDropdown('', $fieldData['value'], $fieldData['path']);
  3033. }
  3034. /**
  3035. * Fills the 'dropdown' control by selecting the specified value.
  3036. *
  3037. * @param string $name
  3038. * @param string $value
  3039. * @param string|null $xpath
  3040. *
  3041. * @throws RuntimeException
  3042. */
  3043. protected function fillDropdown($name, $value, $xpath = null)
  3044. {
  3045. if (is_null($xpath)) {
  3046. $xpath = $this->_getControlXpath('dropdown', $name);
  3047. }
  3048. $errorMessage =
  3049. 'Current location url: \'' . $this->getLocation() . "'\nCurrent page: '" . $this->getCurrentPage() . "'\n"
  3050. . "Problem with dropdown field '$name' and xpath '$xpath':\n";
  3051. if ($this->isElementPresent($xpath)) {
  3052. if ($this->isEditable($xpath)) {
  3053. if ($this->getSelectedValue($xpath) != $value) {
  3054. if ($this->isElementPresent($xpath . "//option[text()='" . $value . "']")) {
  3055. $this->select($xpath, 'label=' . $value);
  3056. } else {
  3057. $this->select($xpath, 'regexp:' . preg_quote($value));
  3058. }
  3059. $this->waitForAjax();
  3060. }
  3061. } else {
  3062. throw new RuntimeException($errorMessage . 'Element is not editable');
  3063. }
  3064. } else {
  3065. throw new RuntimeException($errorMessage . 'Element is not present on the page');
  3066. }
  3067. }
  3068. /**
  3069. * Fills 'checkbox' control by selecting/unselecting it based on the specified value.
  3070. *
  3071. * @param array $fieldData Array of a 'path' to control and 'value' to select. Value can be 'Yes' or 'No'.
  3072. *
  3073. * @throws PHPUnit_Framework_Exception
  3074. * @deprecated
  3075. * @see fillCheckbox()
  3076. */
  3077. protected function _fillFormCheckbox($fieldData)
  3078. {
  3079. $this->fillCheckbox('', $fieldData['value'], $fieldData['path']);
  3080. }
  3081. /**
  3082. * @param string $name
  3083. * @param string $value
  3084. * @param string|null $xpath
  3085. *
  3086. * @throws RuntimeException
  3087. */
  3088. protected function fillCheckbox($name, $value, $xpath = null)
  3089. {
  3090. if (is_null($xpath)) {
  3091. $xpath = $this->_getControlXpath('checkbox', $name);
  3092. }
  3093. $errorMessage =
  3094. 'Current location url: \'' . $this->getLocation() . "'\nCurrent page: '" . $this->getCurrentPage() . "'\n"
  3095. . "Problem with checkbox '$name' and xpath '$xpath':\n";
  3096. if ($this->isElementPresent($xpath)) {
  3097. if ($this->isEditable($xpath)) {
  3098. $currentValue = $this->getValue($xpath);
  3099. if (strtolower($value) == 'yes') {
  3100. if ($currentValue == 'off' || $currentValue == '0') {
  3101. $this->click($xpath);
  3102. $this->waitForAjax();
  3103. }
  3104. } elseif (strtolower($value) == 'no') {
  3105. if ($currentValue == 'on' || $currentValue == '1') {
  3106. $this->click($xpath);
  3107. $this->waitForAjax();
  3108. }
  3109. }
  3110. } else {
  3111. throw new RuntimeException($errorMessage . 'Element is not editable');
  3112. }
  3113. } else {
  3114. throw new RuntimeException($errorMessage . 'Element is not present on the page');
  3115. }
  3116. }
  3117. /**
  3118. * Fills the 'radiobutton' control by selecting the specified value.
  3119. *
  3120. * @param array $fieldData Array of a 'path' to control and 'value' to select.<br>
  3121. * Value should be 'Yes' to select the radiobutton.
  3122. *
  3123. * @throws PHPUnit_Framework_Exception
  3124. * @deprecated
  3125. * @see fillRadiobutton()
  3126. */
  3127. protected function _fillFormRadiobutton($fieldData)
  3128. {
  3129. $this->fillRadiobutton('', $fieldData['value'], $fieldData['path']);
  3130. }
  3131. /**
  3132. * @param string $name
  3133. * @param string $value
  3134. * @param string|null $xpath
  3135. *
  3136. * @throws RuntimeException
  3137. */
  3138. protected function fillRadiobutton($name, $value, $xpath = null)
  3139. {
  3140. if (is_null($xpath)) {
  3141. $xpath = $this->_getControlXpath('radiobutton', $name);
  3142. }
  3143. $errorMessage =
  3144. 'Current location url: \'' . $this->getLocation() . "'\nCurrent page: '" . $this->getCurrentPage() . "'\n"
  3145. . "Problem with radiobutton '$name' and xpath '$xpath':\n";
  3146. if ($this->isElementPresent($xpath)) {
  3147. if ($this->isEditable($xpath)) {
  3148. if (strtolower($value) == 'yes') {
  3149. $this->click($xpath);
  3150. $this->waitForAjax();
  3151. } elseif (strtolower($value) == 'no') {
  3152. $this->uncheck($xpath);
  3153. $this->waitForAjax();
  3154. }
  3155. } else {
  3156. throw new RuntimeException($errorMessage . 'Element is not editable');
  3157. }
  3158. } else {
  3159. throw new RuntimeException($errorMessage . 'Element is not present on the page');
  3160. }
  3161. }
  3162. ################################################################################
  3163. # #
  3164. # Magento helper methods #
  3165. # #
  3166. ################################################################################
  3167. /**
  3168. * Waits for "Please wait" animated gif to appear and disappear.
  3169. *
  3170. * @param integer $waitAppear Timeout in seconds to wait for the loader to appear (by default = 10)
  3171. * @param integer $waitDisappear Timeout in seconds to wait for the loader to disappear (by default = 30)
  3172. *
  3173. * @return Mage_Selenium_TestCase
  3174. */
  3175. public function pleaseWait($waitAppear = 10, $waitDisappear = 30)
  3176. {
  3177. for ($second = 0; $second < $waitAppear; $second++) {
  3178. if ($this->isElementPresent(self::$xpathLoadingHolder)) {
  3179. break;
  3180. }
  3181. sleep(1);
  3182. }
  3183. for ($second = 0; $second < $waitDisappear; $second++) {
  3184. if (!$this->isElementPresent(self::$xpathLoadingHolder)) {
  3185. break;
  3186. }
  3187. sleep(1);
  3188. }
  3189. return $this;
  3190. }
  3191. /**
  3192. * Logs in as a default admin user on back-end
  3193. * @return Mage_Selenium_TestCase
  3194. */
  3195. public function loginAdminUser()
  3196. {
  3197. $this->admin('log_in_to_admin', false);
  3198. $loginData = array('user_name' => $this->_configHelper->getDefaultLogin(),
  3199. 'password' => $this->_configHelper->getDefaultPassword());
  3200. if ($this->_findCurrentPageFromUrl() != $this->_firstPageAfterAdminLogin) {
  3201. $this->validatePage('log_in_to_admin');
  3202. $dashboardLogo = $this->_getControlXpath('pageelement', 'admin_logo');
  3203. $closeButton = $this->_getControlXpath('button', 'close');
  3204. $this->fillFieldset($loginData, 'log_in');
  3205. $this->clickButton('login', false);
  3206. $this->waitForElement(array($dashboardLogo, $this->_getMessageXpath('general_error'),
  3207. $this->_getMessageXpath('general_validation')));
  3208. if ($this->controlIsPresent('link', 'go_to_notifications') && $this->waitForElement($closeButton, 5)) {
  3209. $this->click($closeButton);
  3210. }
  3211. }
  3212. $this->validatePage($this->_firstPageAfterAdminLogin);
  3213. return $this;
  3214. }
  3215. /**
  3216. * Logs out from back-end
  3217. * @return Mage_Selenium_TestCase
  3218. */
  3219. public function logoutAdminUser()
  3220. {
  3221. $logOutXpath = $this->_getControlXpath('link', 'log_out');
  3222. if ($this->isElementPresent($logOutXpath)) {
  3223. $this->click($logOutXpath);
  3224. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  3225. }
  3226. $this->validatePage('log_in_to_admin');
  3227. return $this;
  3228. }
  3229. /**
  3230. * Clears invalided cache in Admin
  3231. */
  3232. public function clearInvalidedCache()
  3233. {
  3234. $xpath = $this->_getControlXpath('link', 'invalided_cache');
  3235. if ($this->isElementPresent($xpath)) {
  3236. $this->clickAndWait($xpath);
  3237. $this->validatePage('cache_storage_management');
  3238. $invalided = array('cache_disabled', 'cache_invalided');
  3239. foreach ($invalided as $value) {
  3240. $xpath = $this->_getControlXpath('pageelement', $value);
  3241. $qty = $this->getXpathCount($xpath);
  3242. for ($i = 1; $i < $qty + 1; $i++) {
  3243. $fillData = array('path' => $xpath . '[' . $i . ']//input',
  3244. 'value' => 'Yes');
  3245. $this->_fillFormCheckbox($fillData);
  3246. }
  3247. }
  3248. $this->fillForm(array('cache_action' => 'Refresh'));
  3249. $selectedItems = $this->getText($this->_getControlXpath('pageelement', 'selected_items'));
  3250. $this->addParameter('qtySelected', $selectedItems);
  3251. $this->clickButton('submit', false);
  3252. $alert = $this->isAlertPresent();
  3253. if ($alert) {
  3254. $text = $this->getAlert();
  3255. $this->fail($text);
  3256. }
  3257. $this->waitForNewPage();
  3258. $this->validatePage('cache_storage_management');
  3259. }
  3260. }
  3261. /**
  3262. * Reindex indexes that are marked as 'reindex required' or 'update required'.
  3263. */
  3264. public function reindexInvalidedData()
  3265. {
  3266. $xpath = $this->_getControlXpath('link', 'invalided_index');
  3267. if ($this->isElementPresent($xpath)) {
  3268. $this->clickAndWait($xpath);
  3269. $this->validatePage('index_management');
  3270. $invalided = array('reindex_required', 'update_required');
  3271. foreach ($invalided as $value) {
  3272. $xpath = $this->_getControlXpath('pageelement', $value);
  3273. while ($this->isElementPresent($xpath)) {
  3274. $this->click($xpath . "//a[text()='Reindex Data']");
  3275. $this->waitForNewPage();
  3276. $this->validatePage('index_management');
  3277. }
  3278. }
  3279. }
  3280. }
  3281. /**
  3282. *
  3283. * @throws RuntimeException
  3284. */
  3285. public function waitForNewPage()
  3286. {
  3287. $notLoaded = true;
  3288. $retries = 0;
  3289. while ($notLoaded) {
  3290. try {
  3291. $retries++;
  3292. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  3293. $notLoaded = false;
  3294. } catch (RuntimeException $e) {
  3295. if ($retries == 10) {
  3296. throw $e;
  3297. }
  3298. }
  3299. }
  3300. }
  3301. /**
  3302. * Performs LogOut customer on front-end
  3303. * @return Mage_Selenium_TestCase
  3304. */
  3305. public function logoutCustomer()
  3306. {
  3307. $this->frontend();
  3308. if ($this->controlIsPresent('link', 'log_out')) {
  3309. $this->clickControl('link', 'log_out', false);
  3310. $this->waitForTextPresent('You are now logged out');
  3311. $this->waitForTextNotPresent('You are now logged out');
  3312. //$this->deleteAllVisibleCookies();
  3313. $this->validatePage('home_page');
  3314. }
  3315. return $this;
  3316. }
  3317. /**
  3318. * Selects StoreView on Frontend
  3319. *
  3320. * @param string $storeViewName
  3321. */
  3322. public function selectFrontStoreView($storeViewName = 'Default Store View')
  3323. {
  3324. $dropdown = ($this->controlIsPresent('dropdown', 'your_language'))
  3325. ? $this->_getControlXpath('dropdown', 'your_language')
  3326. : false;
  3327. if ($dropdown != false) {
  3328. $toSelect = $dropdown . '//option[normalize-space(text())="' . $storeViewName . '"]';
  3329. $isSelected = $toSelect . '[@selected]';
  3330. if (!$this->isElementPresent($isSelected)) {
  3331. $this->select($dropdown, $storeViewName);
  3332. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  3333. }
  3334. $this->assertElementPresent($isSelected, '\'' . $storeViewName . '\' store view not selected');
  3335. } else {
  3336. $this->addParameter('storeView', $storeViewName);
  3337. $isSelected = $this->_getControlXpath('pageelement', 'selected_store_view');
  3338. $storeViewXpath = $this->_getControlXpath('link', 'your_language');
  3339. if (!$this->controlIsPresent('pageelement', 'selected_store_view')) {
  3340. $this->clickControl('pageelement', 'change_store_view', false);
  3341. if ($this->waitForElementVisible($storeViewXpath, $this->_browserTimeoutPeriod)) {
  3342. $this->clickControl('link', 'your_language', false);
  3343. $this->waitForPageToLoad($this->_browserTimeoutPeriod);
  3344. } else {
  3345. $this->fail('Store view cannot be changed to ' . $storeViewName);
  3346. }
  3347. }
  3348. $this->assertElementPresent($isSelected, '\'' . $storeViewName . '\' store view not selected');
  3349. }
  3350. }
  3351. ################################################################################
  3352. # #
  3353. # Should be removed when CodeCoverage work for PHPUnit3.6 #
  3354. # #
  3355. ################################################################################
  3356. /**
  3357. * @return array
  3358. * @throws Exception
  3359. */
  3360. protected function getCodeCoverage()
  3361. {
  3362. if (!empty($this->coverageScriptUrl)) {
  3363. $url = sprintf('%s?PHPUNIT_SELENIUM_TEST_ID=%s', $this->coverageScriptUrl, //$this->testId
  3364. $_COOKIE['PHPUNIT_SELENIUM_TEST_ID']);
  3365. $buffer = @file_get_contents($url);
  3366. if ($buffer !== FALSE) {
  3367. $coverageData = unserialize($buffer);
  3368. if (is_array($coverageData)) {
  3369. return $this->matchLocalAndRemotePaths($coverageData);
  3370. } else {
  3371. throw new Exception('Empty or invalid code coverage data received from url "' . $url . '"');
  3372. }
  3373. }
  3374. }
  3375. return array();
  3376. }
  3377. ################################################################################
  3378. # #
  3379. # Should be removed when onNotSuccessfulTest is fixed #
  3380. # #
  3381. ################################################################################
  3382. /**
  3383. * @param Exception $e
  3384. *
  3385. * @throws Exception|RuntimeException
  3386. */
  3387. protected function onNotSuccessfulTest(Exception $e)
  3388. {
  3389. if ($this->frameworkConfig['shareSession']) {
  3390. //Set 'shareSession' to false for stopping session
  3391. $this->frameworkConfig['shareSession'] = false;
  3392. //Remove sessionId used for sharing session.
  3393. $this->shareSession(null);
  3394. try {
  3395. $this->stop();
  3396. } catch (RuntimeException $_e) {
  3397. }
  3398. $this->frameworkConfig['shareSession'] = true;
  3399. }
  3400. throw $e;
  3401. }
  3402. }