PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Codeception/Module/WebDriver.php

https://github.com/pmcjury/Codeception
PHP | 1730 lines | 1167 code | 111 blank | 452 comment | 105 complexity | 679e858353455306f12da8100a8eb481 MD5 | raw file

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

  1. <?php
  2. namespace Codeception\Module;
  3. use Codeception\Exception\ElementNotFound;
  4. use Codeception\Exception\TestRuntime;
  5. use Codeception\Util\Debug;
  6. use Codeception\Util\Locator;
  7. use Codeception\Lib\Interfaces\MultiSession as MultiSessionInterface;
  8. use Codeception\Lib\Interfaces\Web as WebInterface;
  9. use Codeception\Lib\Interfaces\Remote as RemoteInterface;
  10. use Symfony\Component\DomCrawler\Crawler;
  11. use Codeception\PHPUnit\Constraint\WebDriver as WebDriverConstraint;
  12. use Codeception\PHPUnit\Constraint\WebDriverNot as WebDriverConstraintNot;
  13. use Codeception\PHPUnit\Constraint\Page as PageConstraint;
  14. /**
  15. * New generation Selenium2 module.
  16. * *Included in Codeception 1.7.0*
  17. *
  18. * ## Installation
  19. *
  20. * Download [Selenium2 WebDriver](http://code.google.com/p/selenium/downloads/list?q=selenium-server-standalone-2)
  21. * Launch the daemon: `java -jar selenium-server-standalone-2.xx.xxx.jar`
  22. *
  23. * ## Migration Guide (Selenium2 -> WebDriver)
  24. *
  25. * * `wait` method accepts seconds instead of milliseconds. All waits use second as parameter.
  26. *
  27. *
  28. *
  29. * ## Status
  30. *
  31. * * Maintainer: **davert**
  32. * * Stability: **beta**
  33. * * Contact: davert.codecept@mailican.com
  34. * * Based on [facebook php-webdriver](https://github.com/facebook/php-webdriver)
  35. *
  36. * ## Configuration
  37. *
  38. * * url *required* - start url for your app
  39. * * browser *required* - browser that would be launched
  40. * * host - Selenium server host (localhost by default)
  41. * * port - Selenium server port (4444 by default)
  42. * * restart - set to false to share browser sesssion between tests, or set to true (by default) to create a session per test
  43. * * window_size - initial window size. Values `maximize` or dimensions in format `640x480` are accepted.
  44. * * clear_cookies - set to false to keep cookies (not default), or set to true to delete all cookies between cases.
  45. * * wait - set the implicit wait (5 secs) by default.
  46. * * capabilities - sets Selenium2 [desired capabilities](http://code.google.com/p/selenium/wiki/DesiredCapabilities). Should be a key-value array.
  47. *
  48. * ### Example (`acceptance.suite.yml`)
  49. *
  50. * modules:
  51. * enabled: [WebDriver]
  52. * config:
  53. * WebDriver:
  54. * url: 'http://localhost/'
  55. * browser: firefox
  56. * window_size: 1024x768
  57. * wait: 10
  58. * capabilities:
  59. * unexpectedAlertBehaviour: 'accept'
  60. *
  61. *
  62. * Class WebDriver
  63. * @package Codeception\Module
  64. */
  65. class WebDriver extends \Codeception\Module implements WebInterface, RemoteInterface, MultiSessionInterface {
  66. protected $requiredFields = array('browser', 'url');
  67. protected $config = array(
  68. 'host' => '127.0.0.1',
  69. 'port' => '4444',
  70. 'restart' => false,
  71. 'wait' => 0,
  72. 'clear_cookies' => true,
  73. 'window_size' => false,
  74. 'capabilities' => array()
  75. );
  76. protected $wd_host;
  77. protected $capabilities;
  78. protected $test;
  79. /**
  80. * @var \RemoteWebDriver
  81. */
  82. public $webDriver;
  83. public function _initialize()
  84. {
  85. $this->wd_host = sprintf('http://%s:%s/wd/hub', $this->config['host'], $this->config['port']);
  86. $this->capabilities = $this->config['capabilities'];
  87. $this->capabilities[\WebDriverCapabilityType::BROWSER_NAME] = $this->config['browser'];
  88. $this->webDriver = \RemoteWebDriver::create($this->wd_host, $this->capabilities);
  89. $this->webDriver->manage()->timeouts()->implicitlyWait($this->config['wait']);
  90. $this->initialWindowSize();
  91. }
  92. public function _before(\Codeception\TestCase $test)
  93. {
  94. if (!isset($this->webDriver)) {
  95. $this->_initialize();
  96. }
  97. }
  98. protected function initialWindowSize()
  99. {
  100. if ($this->config['window_size'] == 'maximize') {
  101. $this->maximizeWindow();
  102. return;
  103. }
  104. $size = explode('x', $this->config['window_size']);
  105. if (count($size) == 2) {
  106. $this->resizeWindow(intval($size[0]), intval($size[1]));
  107. }
  108. }
  109. public function _after(\Codeception\TestCase $test)
  110. {
  111. if ($this->config['restart'] && isset($this->webDriver)) {
  112. $this->webDriver->quit();
  113. // \RemoteWebDriver consists of four parts, executor, mouse, keyboard and touch, quit only set executor to null,
  114. // but \RemoteWebDriver doesn't provide public access to check on executor
  115. // so we need to unset $this->webDriver here to shut it down completely
  116. $this->webDriver = null;
  117. }
  118. if ($this->config['clear_cookies'] && isset($this->webDriver)) {
  119. $this->webDriver->manage()->deleteAllCookies();
  120. }
  121. }
  122. public function _failed(\Codeception\TestCase $test, $fail)
  123. {
  124. $filename = str_replace(['::','\\','/'], ['.','',''], \Codeception\TestCase::getTestSignature($test)).'.fail.png';
  125. $this->_saveScreenshot(codecept_output_dir($filename));
  126. $this->debug("Screenshot was saved into '_output' dir");
  127. }
  128. public function _afterSuite()
  129. {
  130. // this is just to make sure webDriver is cleared after suite
  131. if (isset($this->webDriver)) {
  132. $this->webDriver->quit();
  133. unset($this->webDriver);
  134. }
  135. }
  136. public function _getResponseCode()
  137. {
  138. }
  139. public function _sendRequest($url)
  140. {
  141. $this->webDriver->get($this->_getUrl() . '');
  142. }
  143. public function amOnSubdomain($subdomain)
  144. {
  145. $url = $this->config['url'];
  146. $url = preg_replace('~(https?:\/\/)(.*\.)(.*\.)~', "$1$3", $url); // removing current subdomain
  147. $url = preg_replace('~(https?:\/\/)(.*)~', "$1$subdomain.$2", $url); // inserting new
  148. $this->_reconfigure(array('url' => $url));
  149. }
  150. public function _getUrl()
  151. {
  152. if (!isset($this->config['url'])) {
  153. throw new \Codeception\Exception\ModuleConfig(__CLASS__, "Module connection failure. The URL for client can't bre retrieved");
  154. }
  155. return $this->config['url'];
  156. }
  157. public function _getCurrentUri()
  158. {
  159. $url = $this->webDriver->getCurrentURL();
  160. $parts = parse_url($url);
  161. if (!$parts) {
  162. $this->fail("URL couldn't be parsed");
  163. }
  164. $uri = "";
  165. if (isset($parts['path'])) {
  166. $uri .= $parts['path'];
  167. }
  168. if (isset($parts['query'])) {
  169. $uri .= "?" . $parts['query'];
  170. }
  171. if (isset($parts['fragment'])) {
  172. $uri .= "#" . $parts['fragment'];
  173. }
  174. return $uri;
  175. }
  176. public function _saveScreenshot($filename)
  177. {
  178. $this->webDriver->takeScreenshot($filename);
  179. }
  180. /**
  181. * Makes a screenshot of current window and saves it to `tests/_log/debug`.
  182. *
  183. * ``` php
  184. * <?php
  185. * $I->amOnPage('/user/edit');
  186. * $I->makeScreenshot('edit_page');
  187. * // saved to: tests/_log/debug/edit_page.png
  188. * ?>
  189. * ```
  190. *
  191. * @param $name
  192. */
  193. public function makeScreenshot($name)
  194. {
  195. $debugDir = codecept_log_dir().'debug';
  196. if (!is_dir($debugDir)) {
  197. mkdir($debugDir, 0777);
  198. }
  199. $screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png';
  200. $this->_saveScreenshot($screenName);
  201. $this->debug("Screenshot saved to $screenName");
  202. }
  203. /**
  204. * Resize current window
  205. *
  206. * Example:
  207. * ``` php
  208. * <?php
  209. * $I->resizeWindow(800, 600);
  210. *
  211. * ```
  212. *
  213. * @param int $width
  214. * @param int $height
  215. */
  216. public function resizeWindow($width, $height)
  217. {
  218. $this->webDriver->manage()->window()->setSize(new \WebDriverDimension($width, $height));
  219. }
  220. public function seeCookie($cookie)
  221. {
  222. $cookies = $this->webDriver->manage()->getCookies();
  223. $cookies = array_map(
  224. function ($c) {
  225. return $c['name'];
  226. },
  227. $cookies
  228. );
  229. $this->debugSection('Cookies', json_encode($this->webDriver->manage()->getCookies()));
  230. $this->assertContains($cookie, $cookies);
  231. }
  232. public function dontSeeCookie($cookie)
  233. {
  234. $this->debugSection('Cookies', json_encode($this->webDriver->manage()->getCookies()));
  235. $this->assertNull($this->webDriver->manage()->getCookieNamed($cookie));
  236. }
  237. public function setCookie($cookie, $value)
  238. {
  239. $this->webDriver->manage()->addCookie(array('name' => $cookie, 'value' => $value));
  240. $this->debugSection('Cookies', json_encode($this->webDriver->manage()->getCookies()));
  241. }
  242. public function resetCookie($cookie)
  243. {
  244. $this->webDriver->manage()->deleteCookieNamed($cookie);
  245. $this->debugSection('Cookies', json_encode($this->webDriver->manage()->getCookies()));
  246. }
  247. public function grabCookie($cookie)
  248. {
  249. $value = $this->webDriver->manage()->getCookieNamed($cookie);
  250. if (is_array($value)) {
  251. return $value['value'];
  252. }
  253. }
  254. public function amOnPage($page)
  255. {
  256. $host = rtrim($this->config['url'], '/');
  257. $page = ltrim($page, '/');
  258. $this->webDriver->get($host . '/' . $page);
  259. }
  260. public function see($text, $selector = null)
  261. {
  262. if (!$selector) {
  263. return $this->assertPageContains($text);
  264. }
  265. $nodes = $this->match($this->webDriver, $selector);
  266. $this->assertNodesContain($text, $nodes, $selector);
  267. }
  268. public function dontSee($text, $selector = null)
  269. {
  270. if (!$selector) {
  271. return $this->assertPageNotContains($text);
  272. }
  273. $nodes = $this->match($this->webDriver, $selector);
  274. $this->assertNodesNotContain($text, $nodes, $selector);
  275. }
  276. public function click($link, $context = null)
  277. {
  278. $page = $this->webDriver;
  279. if ($context) {
  280. $nodes = $this->match($this->webDriver, $context);
  281. if (empty($nodes)) {
  282. throw new ElementNotFound($context, 'CSS or XPath');
  283. }
  284. $page = reset($nodes);
  285. }
  286. $el = $this->findClickable($page, $link);
  287. if (!$el) {
  288. $els = $this->match($page, $link);
  289. $el = reset($els);
  290. }
  291. if (!$el) {
  292. throw new ElementNotFound($link, 'Link or Button or CSS or XPath');
  293. }
  294. $el->click();
  295. }
  296. /**
  297. * @param $page
  298. * @param $link
  299. * @return \WebDriverElement
  300. */
  301. protected function findClickable($page, $link)
  302. {
  303. if (is_array($link) or ($link instanceof \WebDriverBy)) {
  304. return $this->matchFirstOrFail($page, $link);
  305. }
  306. $locator = Crawler::xpathLiteral(trim($link));
  307. // narrow
  308. $xpath = Locator::combine(
  309. ".//a[normalize-space(.)=$locator]",
  310. ".//button[normalize-space(.)=$locator]",
  311. ".//a/img[normalize-space(@alt)=$locator]/ancestor::a",
  312. ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][normalize-space(@value)=$locator]"
  313. );
  314. $els = $page->findElements(\WebDriverBy::xpath($xpath));
  315. if (count($els)) {
  316. return reset($els);
  317. }
  318. // wide
  319. $xpath = Locator::combine(
  320. ".//a[./@href][((contains(normalize-space(string(.)), $locator)) or .//img[contains(./@alt, $locator)])]",
  321. ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][contains(./@value, $locator)]",
  322. ".//input[./@type = 'image'][contains(./@alt, $locator)]",
  323. ".//button[contains(normalize-space(string(.)), $locator)]",
  324. ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][./@name = $locator]",
  325. ".//button[./@name = $locator]"
  326. );
  327. $els = $page->findElements(\WebDriverBy::xpath($xpath));
  328. if (count($els)) {
  329. return reset($els);
  330. }
  331. $els = $page->findElements(\WebDriverBy::xpath($xpath));
  332. if (count($els)) return reset($els);
  333. return null;
  334. }
  335. /**
  336. * @param $selector
  337. * @return \WebDriverElement
  338. * @throws \Codeception\Exception\ElementNotFound
  339. */
  340. protected function findField($selector)
  341. {
  342. if ($selector instanceof \WebDriverElement) {
  343. return $selector;
  344. }
  345. if (is_array($selector) or ($selector instanceof \WebDriverBy)) {
  346. return $this->matchFirstOrFail($this->webDriver, $selector);
  347. }
  348. $locator = Crawler::xpathLiteral(trim($selector));
  349. // by text or label
  350. $xpath = Locator::combine(
  351. ".//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = $locator) or ./@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or ./@placeholder = $locator)]",
  352. ".//label[contains(normalize-space(string(.)), $locator)]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]"
  353. );
  354. $els = $this->webDriver->findElements(\WebDriverBy::xpath($xpath));
  355. if (count($els)) return reset($els);
  356. // by name
  357. $xpath = ".//*[self::input | self::textarea | self::select][@name = $locator]";
  358. $els = $this->webDriver->findElements(\WebDriverBy::xpath($xpath));
  359. if (count($els)) {
  360. return reset($els);
  361. }
  362. $els = $this->match($this->webDriver, $selector);
  363. if (count($els)) {
  364. return reset($els);
  365. }
  366. throw new ElementNotFound($selector, "Field by name, label, CSS or XPath");
  367. }
  368. public function seeLink($text, $url = null)
  369. {
  370. $nodes = $this->webDriver->findElements(\WebDriverBy::partialLinkText($text));
  371. if (!$url) {
  372. return $this->assertNodesContain($text, $nodes, 'a');
  373. }
  374. $nodes = array_filter(
  375. $nodes,
  376. function (\WebDriverElement $e) use ($url) {
  377. $parts = parse_url($url);
  378. if (!$parts) {
  379. $this->fail("Link URL of '$url' couldn't be parsed");
  380. }
  381. $uri = "";
  382. if (isset($parts['path'])) {
  383. $uri .= $parts['path'];
  384. }
  385. if (isset($parts['query'])) {
  386. $uri .= "?" . $parts['query'];
  387. }
  388. if (isset($parts['fragment'])) {
  389. $uri .= "#" . $parts['fragment'];
  390. }
  391. return $uri == trim($url);
  392. }
  393. );
  394. $this->assertNodesContain($text, $nodes, "a[href=$url]");
  395. }
  396. public function dontSeeLink($text, $url = null)
  397. {
  398. $nodes = $this->webDriver->findElements(\WebDriverBy::partialLinkText($text));
  399. if (!$url) {
  400. $this->assertNodesNotContain($text, $nodes, 'a');
  401. return;
  402. }
  403. $nodes = array_filter(
  404. $nodes,
  405. function (\WebDriverElement $e) use ($url) {
  406. return trim($e->getAttribute('href')) == trim($url);
  407. }
  408. );
  409. $this->assertNodesNotContain($text, $nodes, "a[href=$url]");
  410. }
  411. public function seeInCurrentUrl($uri)
  412. {
  413. $this->assertContains($uri, $this->_getCurrentUri());
  414. }
  415. public function seeCurrentUrlEquals($uri)
  416. {
  417. $this->assertEquals($uri, $this->_getCurrentUri());
  418. }
  419. public function seeCurrentUrlMatches($uri)
  420. {
  421. \PHPUnit_Framework_Assert::assertRegExp($uri, $this->_getCurrentUri());
  422. }
  423. public function dontSeeInCurrentUrl($uri)
  424. {
  425. $this->assertNotContains($uri, $this->_getCurrentUri());
  426. }
  427. public function dontSeeCurrentUrlEquals($uri)
  428. {
  429. $this->assertNotEquals($uri, $this->_getCurrentUri());
  430. }
  431. public function dontSeeCurrentUrlMatches($uri)
  432. {
  433. \PHPUnit_Framework_Assert::assertNotRegExp($uri, $this->_getCurrentUri());
  434. }
  435. public function grabFromCurrentUrl($uri = null)
  436. {
  437. if (!$uri) {
  438. return $this->_getCurrentUri();
  439. }
  440. $matches = array();
  441. $res = preg_match($uri, $this->_getCurrentUri(), $matches);
  442. if (!$res) {
  443. $this->fail("Couldn't match $uri in " . $this->_getCurrentUri());
  444. }
  445. if (!isset($matches[1])) {
  446. $this->fail("Nothing to grab. A regex parameter required. Ex: '/user/(\\d+)'");
  447. }
  448. return $matches[1];
  449. }
  450. public function seeCheckboxIsChecked($checkbox)
  451. {
  452. $this->assertTrue($this->findField($checkbox)->isSelected());
  453. }
  454. public function dontSeeCheckboxIsChecked($checkbox)
  455. {
  456. $this->assertFalse($this->findField($checkbox)->isSelected());
  457. }
  458. public function seeInField($field, $value)
  459. {
  460. $el = $this->findField($field);
  461. if (!$el) {
  462. throw new ElementNotFound($field, "Field by name, label, CSS or XPath");
  463. }
  464. $el_value = $el->getTagName() == 'textarea'
  465. ? $el->getText()
  466. : $el->getAttribute('value');
  467. $this->assertEquals($value, $el_value);
  468. }
  469. public function dontSeeInField($field, $value)
  470. {
  471. $el = $this->findField($field);
  472. $el_value = $el->getTagName() == 'textarea'
  473. ? $el->getText()
  474. : $el->getAttribute('value');
  475. $this->assertNotEquals($value, $el_value);
  476. }
  477. public function selectOption($select, $option)
  478. {
  479. $el = $this->findField($select);
  480. if ($el->getTagName() != 'select') {
  481. $els = $this->matchCheckables($select);
  482. $radio = null;
  483. foreach ($els as $el) {
  484. $radio = $this->findCheckable($el, $option, true);
  485. if ($radio) {
  486. break;
  487. }
  488. }
  489. if (!$radio) {
  490. throw new ElementNotFound($select, "Radiobutton with value or name '$option in");
  491. }
  492. $radio->click();
  493. return;
  494. }
  495. $wdSelect = new \WebDriverSelect($el);
  496. if ($wdSelect->isMultiple()) {
  497. $wdSelect->deselectAll();
  498. }
  499. if (!is_array($option)) {
  500. $option = array($option);
  501. }
  502. $matched = false;
  503. foreach ($option as $opt) {
  504. try {
  505. $wdSelect->selectByVisibleText($opt);
  506. $matched = true;
  507. } catch (\NoSuchElementException $e) {
  508. }
  509. }
  510. if ($matched) {
  511. return;
  512. }
  513. foreach ($option as $opt) {
  514. try {
  515. $wdSelect->selectByValue($opt);
  516. $matched = true;
  517. } catch (\NoSuchElementException $e) {
  518. }
  519. }
  520. if ($matched) {
  521. return;
  522. }
  523. if ($matched) return;
  524. // partially matching
  525. foreach ($option as $opt) {
  526. try {
  527. $optElement = $el->findElement(\WebDriverBy::xpath('//option [contains (., "'.$opt.'")]'));
  528. $matched = true;
  529. if (!$optElement->isSelected()) {
  530. $optElement->click();
  531. }
  532. } catch (\NoSuchElementException $e) {}
  533. }
  534. if ($matched) return;
  535. throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value");
  536. }
  537. public function _initializeSession()
  538. {
  539. $this->webDriver = \RemoteWebDriver::create($this->wd_host, $this->capabilities);
  540. $this->webDriver->manage()->timeouts()->implicitlyWait($this->config['wait']);
  541. }
  542. public function _loadSessionData($data)
  543. {
  544. $this->webDriver = $data;
  545. }
  546. public function _backupSessionData()
  547. {
  548. return $this->webDriver;
  549. }
  550. public function _closeSession($webdriver)
  551. {
  552. $webdriver->close();
  553. }
  554. /*
  555. * Unselect an option in a select box
  556. *
  557. */
  558. public function unselectOption($select, $option)
  559. {
  560. $el = $this->findField($select);
  561. $wdSelect = new \WebDriverSelect($el);
  562. if (!is_array($option)) {
  563. $option = array($option);
  564. }
  565. $matched = false;
  566. foreach ($option as $opt) {
  567. try {
  568. $wdSelect->deselectByVisibleText($opt);
  569. $matched = true;
  570. } catch (\NoSuchElementException $e) {
  571. }
  572. try {
  573. $wdSelect->deselectByValue($opt);
  574. $matched = true;
  575. } catch (\NoSuchElementException $e) {
  576. }
  577. }
  578. if ($matched) {
  579. return;
  580. }
  581. throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value");
  582. }
  583. /**
  584. * @param $context
  585. * @param $radio_or_checkbox
  586. * @param bool $byValue
  587. * @return mixed|null
  588. */
  589. protected function findCheckable($context, $radio_or_checkbox, $byValue = false)
  590. {
  591. if ($radio_or_checkbox instanceof \WebDriverElement) {
  592. return $radio_or_checkbox;
  593. }
  594. if (is_array($radio_or_checkbox) or ($radio_or_checkbox instanceof \WebDriverBy)) {
  595. return $this->matchFirstOrFail($this->webDriver, $radio_or_checkbox);
  596. }
  597. $locator = Crawler::xpathLiteral($radio_or_checkbox);
  598. $xpath = Locator::combine(
  599. "//input[./@type = 'checkbox'][(./@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or ./@placeholder = $locator]",
  600. "//label[contains(normalize-space(string(.)), $locator)]//.//input[./@type = 'checkbox']",
  601. "//input[./@type = 'radio'][(./@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or ./@placeholder = $locator]",
  602. "//label[contains(normalize-space(string(.)), $locator)]//.//input[./@type = 'radio']"
  603. );
  604. if ($byValue) {
  605. $xpath = Locator::combine(
  606. $xpath,
  607. "//input[./@type = 'checkbox'][./@value = $locator]",
  608. "//input[./@type = 'radio'][./@value = $locator]"
  609. );
  610. }
  611. /** @var $context \WebDriverElement * */
  612. $els = $context->findElements(\WebDriverBy::xpath($xpath));
  613. if (count($els)) {
  614. return reset($els);
  615. }
  616. $els = $this->match($context, $radio_or_checkbox);
  617. if (count($els)) {
  618. return reset($els);
  619. }
  620. return null;
  621. }
  622. protected function matchCheckables($selector)
  623. {
  624. $els = $this->match($this->webDriver, $selector);
  625. if (!count($els)) {
  626. throw new ElementNotFound($selector, "Element containing radio by CSS or XPath");
  627. }
  628. return $els;
  629. }
  630. public function checkOption($option)
  631. {
  632. $field = $this->findCheckable($this->webDriver, $option);
  633. if (!$field) {
  634. throw new ElementNotFound($option, "Checkbox or Radio by Label or CSS or XPath");
  635. }
  636. if ($field->isSelected()) {
  637. return;
  638. }
  639. $field->click();
  640. }
  641. public function uncheckOption($option)
  642. {
  643. $field = $this->findCheckable($this->webDriver, $option);
  644. if (!$field) {
  645. throw new ElementNotFound($option, "Checkbox by Label or CSS or XPath");
  646. }
  647. if (!$field->isSelected()) {
  648. return;
  649. }
  650. $field->click();
  651. }
  652. public function fillField($field, $value)
  653. {
  654. $el = $this->findField($field);
  655. $el->clear();
  656. $el->sendKeys($value);
  657. }
  658. public function attachFile($field, $filename)
  659. {
  660. $el = $this->findField($field);
  661. // in order to be compatible on different OS
  662. $filePath = realpath(\Codeception\Configuration::dataDir().$filename);
  663. $el->sendKeys($filePath);
  664. }
  665. /**
  666. * @return string
  667. */
  668. public function getVisibleText() {
  669. $els = $this->webDriver->findElements(\WebDriverBy::cssSelector('body'));
  670. if (count($els)) {
  671. return $els[0]->getText();
  672. }
  673. return "";
  674. }
  675. public function grabTextFrom($cssOrXPathOrRegex)
  676. {
  677. $els = $this->match($this->webDriver, $cssOrXPathOrRegex);
  678. if (count($els)) {
  679. return $els[0]->getText();
  680. }
  681. if (@preg_match($cssOrXPathOrRegex, $this->webDriver->getPageSource(), $matches)) {
  682. return $matches[1];
  683. }
  684. throw new ElementNotFound($cssOrXPathOrRegex, 'CSS or XPath or Regex');
  685. }
  686. public function grabAttributeFrom($cssOrXpath, $attribute)
  687. {
  688. $els = $this->match($this->webDriver, $cssOrXpath);
  689. if (count($els)) {
  690. return $els[0]->getAttribute($attribute);
  691. }
  692. throw new ElementNotFound($cssOrXpath, 'CSS or XPath');
  693. }
  694. public function grabValueFrom($field)
  695. {
  696. $el = $this->findField($field);
  697. // value of multiple select is the value of the first selected option
  698. if ($el->getTagName() == 'select') {
  699. $select = new \WebDriverSelect($el);
  700. return $select->getFirstSelectedOption()->getAttribute('value');
  701. }
  702. return $el->getAttribute('value');
  703. }
  704. protected function filterByAttributes($els, array $attributes)
  705. {
  706. foreach ($attributes as $attr => $value) {
  707. $els = array_filter($els, function (\WebDriverElement $el) use ($attr, $value) {
  708. return $el->getAttribute($attr) == $value;
  709. });
  710. }
  711. return $els;
  712. }
  713. /**
  714. * Checks for a visible element on a page, matching it by CSS or XPath
  715. *
  716. * ``` php
  717. * <?php
  718. * $I->seeElement('.error');
  719. * $I->seeElement('//form/input[1]');
  720. * ?>
  721. * ```
  722. * @param $selector
  723. * @param array $attributes
  724. */
  725. public function seeElement($selector, $attributes = array())
  726. {
  727. $els = array_filter($this->match($this->webDriver, $selector),
  728. function (\WebDriverElement $el) use ($attributes) {
  729. return $el->isDisplayed();
  730. }
  731. );
  732. $els = $this->filterByAttributes($els, $attributes);
  733. $this->assertNotEmpty($els);
  734. }
  735. /**
  736. * Checks that element is invisible or not present on page.
  737. *
  738. * ``` php
  739. * <?php
  740. * $I->dontSeeElement('.error');
  741. * $I->dontSeeElement('//form/input[1]');
  742. * ?>
  743. * ```
  744. *
  745. * @param $selector
  746. * @param array $attributes
  747. */
  748. public function dontSeeElement($selector, $attributes = array())
  749. {
  750. $els = array_filter(
  751. $this->match($this->webDriver, $selector),
  752. function (\WebDriverElement $el) {
  753. return $el->isDisplayed();
  754. }
  755. );
  756. $els = $this->filterByAttributes($els, $attributes);
  757. $this->assertEmpty($els);
  758. }
  759. /**
  760. * Checks if element exists on a page even it is invisible.
  761. *
  762. * ``` php
  763. * <?php
  764. * $I->seeElementInDOM('//form/input[type=hidden]');
  765. * ?>
  766. * ```
  767. *
  768. * @param $selector
  769. */
  770. public function seeElementInDOM($selector, $attributes = array())
  771. {
  772. $els = $this->match($this->webDriver, $selector);
  773. $els = $this->filterByAttributes($els, $attributes);
  774. $this->assertNotEmpty($els);
  775. }
  776. /**
  777. * Opposite to `seeElementInDOM`.
  778. *
  779. * @param $selector
  780. */
  781. public function dontSeeElementInDOM($selector, $attributes = array())
  782. {
  783. $els = $this->match($this->webDriver, $selector);
  784. $els = $this->filterByAttributes($els, $attributes);
  785. $this->assertEmpty($els);
  786. }
  787. public function seeOptionIsSelected($selector, $optionText)
  788. {
  789. $el = $this->findField($selector);
  790. if ($el->getTagName() !== 'select') {
  791. $els = $this->matchCheckables($selector);
  792. foreach ($els as $k => $el) {
  793. $els[$k] = $this->findCheckable($el, $optionText, true);
  794. }
  795. $this->assertNotEmpty(
  796. array_filter(
  797. $els,
  798. function ($e) {
  799. return $e->isSelected();
  800. }
  801. )
  802. );
  803. return;
  804. }
  805. $select = new \WebDriverSelect($el);
  806. $this->assertNodesContain($optionText, $select->getAllSelectedOptions(), 'option');
  807. }
  808. public function dontSeeOptionIsSelected($selector, $optionText)
  809. {
  810. $el = $this->findField($selector);
  811. if ($el->getTagName() !== 'select') {
  812. $els = $this->matchCheckables($selector);
  813. foreach ($els as $k => $el) {
  814. $els[$k] = $this->findCheckable($el, $optionText, true);
  815. }
  816. $this->assertEmpty(array_filter($els, function ($e) {
  817. return $e->isSelected();
  818. }));
  819. return;
  820. }
  821. $select = new \WebDriverSelect($el);
  822. $this->assertNodesNotContain($optionText, $select->getAllSelectedOptions(), 'option');
  823. }
  824. public function seeInTitle($title)
  825. {
  826. $this->assertContains($title, $this->webDriver->getTitle());
  827. }
  828. public function dontSeeInTitle($title)
  829. {
  830. $this->assertNotContains($title, $this->webDriver->getTitle());
  831. }
  832. /**
  833. * Accepts JavaScript native popup window created by `window.alert`|`window.confirm`|`window.prompt`.
  834. * Don't confuse it with modal windows, created by [various libraries](http://jster.net/category/windows-modals-popups).
  835. *
  836. */
  837. public function acceptPopup()
  838. {
  839. $this->webDriver->switchTo()->alert()->accept();
  840. }
  841. /**
  842. * Dismisses active JavaScript popup created by `window.alert`|`window.confirm`|`window.prompt`.
  843. */
  844. public function cancelPopup()
  845. {
  846. $this->webDriver->switchTo()->alert()->dismiss();
  847. }
  848. /**
  849. * Checks that active JavaScript popup created by `window.alert`|`window.confirm`|`window.prompt` contain text.
  850. *
  851. * @param $text
  852. */
  853. public function seeInPopup($text)
  854. {
  855. $this->assertContains($text, $this->webDriver->switchTo()->alert()->getText());
  856. }
  857. /**
  858. * Enters text into native JavaScript prompt popup created by `window.prompt`.
  859. *
  860. * @param $keys
  861. */
  862. public function typeInPopup($keys)
  863. {
  864. $this->webDriver->switchTo()->alert()->sendKeys($keys);
  865. }
  866. /**
  867. * Reloads current page
  868. */
  869. public function reloadPage()
  870. {
  871. $this->webDriver->navigate()->refresh();
  872. }
  873. /**
  874. * Moves back in history
  875. */
  876. public function moveBack()
  877. {
  878. $this->webDriver->navigate()->back();
  879. $this->debug($this->_getCurrentUri());
  880. }
  881. /**
  882. * Moves forward in history
  883. */
  884. public function moveForward()
  885. {
  886. $this->webDriver->navigate()->forward();
  887. $this->debug($this->_getCurrentUri());
  888. }
  889. /**
  890. * Submits a form located on page.
  891. * Specify the form by it's css or xpath selector.
  892. * Fill the form fields values as array. Hidden fields can't be accessed.
  893. *
  894. * This command itself triggers the request to form's action.
  895. *
  896. * Examples:
  897. *
  898. * ``` php
  899. * <?php
  900. * $I->submitForm('#login', array('login' => 'davert', 'password' => '123456'));
  901. *
  902. * ```
  903. *
  904. * For sample Sign Up form:
  905. *
  906. * ``` html
  907. * <form action="/sign_up">
  908. * Login: <input type="text" name="user[login]" /><br/>
  909. * Password: <input type="password" name="user[password]" /><br/>
  910. * Do you agree to out terms? <input type="checkbox" name="user[agree]" /><br/>
  911. * Select pricing plan <select name="plan"><option value="1">Free</option><option value="2" selected="selected">Paid</option></select>
  912. * <input type="submit" value="Submit" />
  913. * </form>
  914. * ```
  915. * You can write this:
  916. *
  917. * ``` php
  918. * <?php
  919. * $I->submitForm('#userForm', array('user' => array('login' => 'Davert', 'password' => '123456', 'agree' => true)));
  920. *
  921. * ```
  922. *
  923. * @param $selector
  924. * @param $params
  925. * @throws \Codeception\Exception\ElementNotFound
  926. */
  927. public function submitForm($selector, $params)
  928. {
  929. $form = $this->match($this->webDriver, $selector);
  930. if (empty($form)) {
  931. throw new ElementNotFound($selector, "Form via CSS or XPath");
  932. }
  933. $form = reset($form);
  934. /** @var $form \WebDriverElement * */
  935. foreach ($params as $param => $value) {
  936. $els = $form->findElements(\WebDriverBy::name($param));
  937. if (empty($els)) {
  938. throw new ElementNotFound($param);
  939. }
  940. $el = reset($els);
  941. if ($el->getTagName() == 'textarea') {
  942. $this->fillField($el, $value);
  943. }
  944. if ($el->getTagName() == 'select') {
  945. $this->selectOption($el, $value);
  946. }
  947. if ($el->getTagName() == 'input') {
  948. $type = $el->getAttribute('type');
  949. if ($type == 'text' or $type == 'password') {
  950. $this->fillField($el, $value);
  951. }
  952. if ($type == 'radio' or $type == 'checkbox') {
  953. foreach ($els as $radio) {
  954. if ($radio->getAttribute('value') == $value) {
  955. $this->checkOption($radio);
  956. }
  957. }
  958. }
  959. }
  960. }
  961. $this->debugSection(
  962. 'Uri',
  963. $form->getAttribute('action') ? $form->getAttribute('action') : $this->_getCurrentUri()
  964. );
  965. $this->debugSection('Method', $form->getAttribute('method') ? $form->getAttribute('method') : 'GET');
  966. $this->debugSection('Parameters', json_encode($params));
  967. $form->submit();
  968. $this->debugSection('Page', $this->_getCurrentUri());
  969. }
  970. /**
  971. * Waits for element to change or for $timeout seconds to pass. Element "change" is determined
  972. * by a callback function which is called repeatedly until the return value evaluates to true.
  973. *
  974. * ``` php
  975. * <?php
  976. * $I->waitForElementChange('#menu', function(\WebDriverElement $el) {
  977. * return $el->isDisplayed();
  978. * }, 100);
  979. * ?>
  980. * ```
  981. *
  982. * @param $element
  983. * @param \Closure $callback
  984. * @param int $timeout seconds
  985. * @throws \Codeception\Exception\ElementNotFound
  986. */
  987. public function waitForElementChange($element, \Closure $callback, $timeout = 30)
  988. {
  989. $els = $this->match($this->webDriver, $element);
  990. if (!count($els)) {
  991. throw new ElementNotFound($element, "CSS or XPath");
  992. }
  993. $el = reset($els);
  994. $checker = function () use ($el, $callback) {
  995. return $callback($el);
  996. };
  997. $this->webDriver->wait($timeout)->until($checker);
  998. }
  999. /**
  1000. * Waits for element to appear on page for $timeout seconds to pass.
  1001. * If element not appears, timeout exception is thrown.
  1002. *
  1003. * ``` php
  1004. * <?php
  1005. * $I->waitForElement('#agree_button', 30); // secs
  1006. * $I->click('#agree_button');
  1007. * ?>
  1008. * ```
  1009. *
  1010. * @param $element
  1011. * @param int $timeout seconds
  1012. * @throws \Exception
  1013. */
  1014. public function waitForElement($element, $timeout = 10)
  1015. {
  1016. $condition = null;
  1017. if (Locator::isID($element)) {
  1018. $condition = \WebDriverExpectedCondition::presenceOfElementLocated(
  1019. \WebDriverBy::id(substr($element, 1))
  1020. );
  1021. }
  1022. if (!$condition and Locator::isCSS(
  1023. $element
  1024. )
  1025. ) {
  1026. $condition = \WebDriverExpectedCondition::presenceOfElementLocated(\WebDriverBy::cssSelector($element));
  1027. }
  1028. if (Locator::isXPath($element)) {
  1029. $condition = \WebDriverExpectedCondition::presenceOfElementLocated(
  1030. \WebDriverBy::xpath($element)
  1031. );
  1032. }
  1033. if (!$condition) {
  1034. throw new \Exception("Only CSS or XPath allowed");
  1035. }
  1036. $this->webDriver->wait($timeout)->until($condition);
  1037. }
  1038. /**
  1039. * Waits for element to be visible on the page for $timeout seconds to pass.
  1040. * If element doesn't appear, timeout exception is thrown.
  1041. *
  1042. * ``` php
  1043. * <?php
  1044. * $I->waitForElementVisible('#agree_button', 30); // secs
  1045. * $I->click('#agree_button');
  1046. * ?>
  1047. * ```
  1048. *
  1049. * @param $element
  1050. * @param int $timeout seconds
  1051. * @throws \Exception
  1052. */
  1053. public function waitForElementVisible($element, $timeout = 10)
  1054. {
  1055. $condition = null;
  1056. if (Locator::isID($element)) {
  1057. $condition = \WebDriverExpectedCondition::visibilityOfElementLocated(
  1058. \WebDriverBy::id(substr($element, 1))
  1059. );
  1060. }
  1061. if (!$condition and Locator::isCSS(
  1062. $element
  1063. )
  1064. ) {
  1065. $condition = \WebDriverExpectedCondition::visibilityOfElementLocated(\WebDriverBy::cssSelector($element));
  1066. }
  1067. if (Locator::isXPath($element)) {
  1068. $condition = \WebDriverExpectedCondition::visibilityOfElementLocated(
  1069. \WebDriverBy::xpath($element)
  1070. );
  1071. }
  1072. if (!$condition) {
  1073. throw new \Exception("Only CSS or XPath allowed");
  1074. }
  1075. $this->webDriver->wait($timeout)->until($condition);
  1076. }
  1077. /**
  1078. * Waits for element to not be visible on the page for $timeout seconds to pass.
  1079. * If element stays visible, timeout exception is thrown.
  1080. *
  1081. * ``` php
  1082. * <?php
  1083. * $I->waitForElementNotVisible('#agree_button', 30); // secs
  1084. * ?>
  1085. * ```
  1086. *
  1087. * @param $element
  1088. * @param int $timeout seconds
  1089. * @throws \Exception
  1090. */
  1091. public function waitForElementNotVisible($element, $timeout = 10)
  1092. {
  1093. $condition = null;
  1094. if (Locator::isID($element)) {
  1095. $condition = \WebDriverExpectedCondition::invisibilityOfElementLocated(
  1096. \WebDriverBy::id(substr($element, 1))
  1097. );
  1098. }
  1099. if (!$condition and Locator::isCSS(
  1100. $element
  1101. )
  1102. ) {
  1103. $condition = \WebDriverExpectedCondition::invisibilityOfElementLocated(\WebDriverBy::cssSelector($element));
  1104. }
  1105. if (Locator::isXPath($element)) {
  1106. $condition = \WebDriverExpectedCondition::invisibilityOfElementLocated(
  1107. \WebDriverBy::xpath($element)
  1108. );
  1109. }
  1110. if (!$condition) {
  1111. throw new \Exception("Only CSS or XPath allowed");
  1112. }
  1113. $this->webDriver->wait($timeout)->until($condition);
  1114. }
  1115. /**
  1116. * Waits for text to appear on the page for a specific amount of time.
  1117. * Can also be passed a selector to search in.
  1118. * If text does not appear, timeout exception is thrown.
  1119. *
  1120. * ``` php
  1121. * <?php
  1122. * $I->waitForText('foo', 30); // secs
  1123. * $I->waitForText('foo', 30, '.title'); // secs
  1124. * ?>
  1125. * ```
  1126. *
  1127. * @param string $text
  1128. * @param int $timeout seconds
  1129. * @param null $selector
  1130. * @throws \Exception
  1131. * @internal param string $element
  1132. */
  1133. public function waitForText($text, $timeout = 10, $selector = null)
  1134. {
  1135. $condition = null;
  1136. if (!$selector) {
  1137. $condition = \WebDriverExpectedCondition::textToBePresentInElement(\WebDriverBy::xpath('//body'), $text);
  1138. } else {
  1139. if (Locator::isID($selector)) {
  1140. $condition = \WebDriverExpectedCondition::textToBePresentInElement(
  1141. \WebDriverBy::id(substr($selector, 1)),
  1142. $text
  1143. );
  1144. }
  1145. if (!$condition and Locator::isCSS($selector)) {
  1146. $condition = \WebDriverExpectedCondition::textToBePresentInElement(
  1147. \WebDriverBy::cssSelector($selector),
  1148. $text
  1149. );
  1150. }
  1151. if (Locator::isXPath($selector)) {
  1152. $condition = \WebDriverExpectedCondition::textToBePresentInElement(
  1153. \WebDriverBy::xpath($selector),
  1154. $text
  1155. );
  1156. }
  1157. if (!$condition) {
  1158. throw new \Exception("Only CSS or XPath allowed");
  1159. }
  1160. }
  1161. $this->webDriver->wait($timeout)->until($condition);
  1162. }
  1163. /**
  1164. * Explicit wait.
  1165. *
  1166. * @param int $timeout secs
  1167. * @throws \Codeception\Exception\TestRuntime
  1168. */
  1169. public function wait($timeout)
  1170. {
  1171. if ($timeout >= 1000) {
  1172. throw new TestRuntime("
  1173. Waiting for more then 1000 seconds: 16.6667 mins\n
  1174. Please note that wait method accepts number of seconds as parameter.");
  1175. }
  1176. sleep($timeout);
  1177. }
  1178. /**
  1179. * Low-level API method.
  1180. * If Codeception commands are not enough, use Selenium WebDriver methods directly
  1181. *
  1182. * ``` php
  1183. * $I->executeInSelenium(function(\WebDriver $webdriver) {
  1184. * $webdriver->get('http://google.com');
  1185. * });
  1186. * ```
  1187. *
  1188. * Use [WebDriver Session API](https://github.com/facebook/php-webdriver)
  1189. * Not recommended this command too be used on regular basis.
  1190. * If Codeception lacks important Selenium methods implement then and submit patches.
  1191. *
  1192. * @param callable $function
  1193. */
  1194. public function executeInSelenium(\Closure $function)
  1195. {
  1196. return $function($this->webDriver);
  1197. }
  1198. /**
  1199. * Switch to another window identified by its name.
  1200. *
  1201. * The window can only be identified by its name. If the $name parameter is blank it will switch to the parent window.
  1202. *
  1203. * Example:
  1204. * ``` html
  1205. * <input type="button" value="Open window" onclick="window.open('http://example.com', 'another_window')">
  1206. * ```
  1207. *
  1208. * ``` php
  1209. * <?php
  1210. * $I->click("Open window");
  1211. * # switch to another window
  1212. * $I->switchToWindow("another_window");
  1213. * # switch to parent window
  1214. * $I->switchToWindow();
  1215. * ?>
  1216. * ```
  1217. *
  1218. * If the window has no name, the only way to access it is via the `executeInSelenium()` method like so:
  1219. *
  1220. * ``` php
  1221. * <?php
  1222. * $I->executeInSelenium(function (\Webdriver $webdriver) {
  1223. * $handles=$webdriver->getWindowHandles();
  1224. * $last_window = end($handles);
  1225. * $webdriver->switchTo()->window($last_window);
  1226. * });
  1227. * ?>
  1228. * ```
  1229. *
  1230. * @param string|null $name
  1231. */
  1232. public function switchToWindow($name = null)
  1233. {
  1234. $this->webDriver->switchTo()->window($name);
  1235. }
  1236. /**
  1237. * Switch to another frame
  1238. *
  1239. * Example:
  1240. * ``` html
  1241. * <iframe name="another_frame" src="http://example.com">
  1242. *
  1243. * ```
  1244. *
  1245. * ``` php
  1246. * <?php
  1247. * # switch to iframe
  1248. * $I->switchToIFrame("another_frame");
  1249. * # switch to parent page
  1250. * $I->switchToIFrame();
  1251. *
  1252. * ```
  1253. *
  1254. * @param string|null $name
  1255. */
  1256. public function switchToIFrame($name = null)
  1257. {
  1258. if (is_null($name)) {
  1259. $this->webDriver->switchTo()->defaultContent();
  1260. } else {
  1261. $this->webDriver->switchTo()->frame($name);
  1262. }
  1263. }
  1264. /**
  1265. * Executes JavaScript and waits for it to return true or for the timeout.
  1266. *
  1267. * In this example we will wait for all jQuery ajax requests are finished or 60 secs otherwise.
  1268. *
  1269. * ``` php
  1270. * <?php
  1271. * $I->waitForJS("return $.active == 0;", 60);
  1272. * ?>
  1273. * ```
  1274. *
  1275. * @param string $script
  1276. * @param int $timeout seconds
  1277. */
  1278. public function waitForJS($script, $timeout = 5)
  1279. {
  1280. $condition = function ($wd) use ($script) {
  1281. return $wd->executeScript($script);
  1282. };
  1283. $this->webDriver->wait($timeout)->until($condition);
  1284. }
  1285. /**
  1286. * Executes custom JavaScript
  1287. *
  1288. * @param $script
  1289. * @return mixed
  1290. */
  1291. public function executeJS($script)
  1292. {
  1293. return $this->webDriver->executeScript($script);
  1294. }
  1295. /**
  1296. * Maximizes current window
  1297. */
  1298. public function maximizeWindow()
  1299. {
  1300. $this->webDriver->manage()->window()->maximize();
  1301. }
  1302. /**
  1303. * Performs a simple mouse drag and drop operation.
  1304. *
  1305. * ``` php
  1306. * <?php
  1307. * $I->dragAndDrop('#drag', '#drop');
  1308. * ?>
  1309. * ```
  1310. *
  1311. * @param string $source (CSS ID or XPath)
  1312. * @param string $target (CSS ID or XPath)
  1313. */
  1314. public function dragAndDrop($source, $target)
  1315. {
  1316. $snodes = $this->matchFirstOrFail($this->webDriver, $source);
  1317. $tnodes = $this->matchFirstOrFail($this->webDriver, $target);
  1318. $action = new \WebDriverActions($this->webDriver);
  1319. $action->dragAndDrop($snodes, $tnodes)->perform();
  1320. }
  1321. /**
  1322. * Move mouse over the first element matched by css or xPath on page
  1323. *
  1324. * https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/moveto
  1325. *
  1326. * @param string $cssOrXPath css or xpath of the web element
  1327. * @param int $offsetX
  1328. * @param int $offsetY
  1329. *
  1330. * @throws \Codeception\Exception\ElementNotFound
  1331. * @return null
  1332. */
  1333. public function moveMouseOver($cssOrXPath, $offsetX = null, $offsetY = null)
  1334. {
  1335. $el = $this->matchFirstOrFail($this->webDriver, $cssOrXPath);
  1336. $this->webDriver->getMouse()->mouseMove($el->getCoordinates(), $offsetX, $offsetY);
  1337. }
  1338. /**
  1339. * Performs contextual click with right mouse button on element matched by CSS or XPath.
  1340. *
  1341. * @param $cssOrXPath
  1342. * @throws \Codeception\Exception\ElementNotFound
  1343. */
  1344. public function clickWithRightButton($cssOrXPath)
  1345. {
  1346. $el = $this->matchFirstOrFail($this->webDriver, $cssOrXPath);
  1347. $this->webDriver->getMouse()->contextClick($el->getCoordinates());
  1348. }
  1349. /**
  1350. * Pauses test execution in debug mode.
  1351. * To proceed test press "ENTER" in console.
  1352. *
  1353. * This method is recommended to use in test development, for additional page analysis, locator searing, etc.
  1354. */
  1355. public function pauseExecution()
  1356. {
  1357. Debug::pause();
  1358. }
  1359. /**
  1360. * Performs a double click on element matched by CSS or XPath.
  1361. *
  1362. * @param $cssOrXPath
  1363. * @throws \Codeception\Exception\ElementNotFound
  1364. */
  1365. public function doubleClick($cssOrXPath)
  1366. {
  1367. $el = $this->matchFirstOrFail($this->webDriver, $cssOrXPath);
  1368. $this->webDriver->getMouse()->doubleClick($el->getCoordinates());
  1369. }
  1370. /**
  1371. * @param $page
  1372. * @param $selector
  1373. * @return array
  1374. */
  1375. protected function match($page, $selector)
  1376. {
  1377. $nodes = array();
  1378. if (is_array($selector)) {
  1379. return $page->findElements($this->getWebDriverLocator($selector));
  1380. }
  1381. if ($selector instanceof \WebDriverBy) {
  1382. return $page->findElements($selector);
  1383. }
  1384. if (Locator::isID($selector)) {
  1385. $nodes = $page->findElements(\WebDriverBy::id(substr($selector, 1)));
  1386. }
  1387. if (!empty($nodes)) {
  1388. return $nodes;
  1389. }
  1390. if (Locator::isCSS($selector)) {
  1391. $nodes = $page->findElements(\WebDriverBy::cssSelector($selector));
  1392. }
  1393. if (!empty($nodes)) {
  1394. return $nodes;
  1395. }
  1396. if (Locator::isXPath($selector)) {
  1397. $nodes = $page->findElements(\WebDriverBy::xpath($selector));
  1398. }
  1399. return $nodes;
  1400. }
  1401. protected function getWebDriverLocator(array $by)
  1402. {
  1403. $type = key($by);
  1404. $locator = $by[$type];
  1405. switch ($type) {
  1406. case 'id':
  1407. return \WebDriverBy::id($locator);
  1408. case 'name':
  1409. return \WebDriverBy::name($locator);
  1410. case 'css':
  1411. return \WebDriverBy::cssSelector($locator);
  1412. case 'xpath':
  1413. return \WebDriverBy::xpath($locator);
  1414. case 'link':
  1415. return \WebDriverBy::linkText($locator);
  1416. case 'class':
  1417. return \WebDriverBy::className($locator);
  1418. default:
  1419. throw new TestRuntime("Locator type '$by' is not defined. Use either: xpath, css, id, link, class, name");
  1420. }
  1421. }
  1422. /**
  1423. * @param $page
  1424. * @param $selector
  1425. * @return \RemoteWebElement
  1426. * @throws \Codeception\Exception\ElementNotFound
  1427. */
  1428. protected function matchFirstOrFail($page, $selector)
  1429. {
  1430. $els = $this->match($page, $selector);
  1431. if (count($els)) {
  1432. return reset($els);
  1433. }
  1434. throw new ElementNotFound($selector, 'CSS or XPath');
  1435. }
  1436. /**
  1437. * Presses key on element found by css, xpath is focused
  1438. * A char and modifier (ctrl, alt, shift, meta) can be provided.
  1439. * For special keys use key constants from \WebDriverKeys class.
  1440. *
  1441. * Example:
  1442. *
  1443. * ``` php
  1444. * <?php
  1445. * // <input id="page" value="old" />
  1446. * $I->pressKey('#page','a'); // => olda
  1447. * $I->pressKey('#page',array('ctrl','a'),'new'); //=> new
  1448. * $I->pressKey('#page',array('shift','111'),'1','x'); //=> old!!!1x
  1449. * $I->pressKey('descendant-or-self::*[@id='page']','u'); //=> oldu
  1450. * $I->pressKey('#name', array('ctrl', 'a'), WebDriverKeys::DELETE); //=>''
  1451. * ?>
  1452. * ```
  1453. *
  1454. * @param $element
  1455. * @param $char can be char or array with modifier. You can provide several chars.
  1456. * @throws \Codeception\Exception\ElementNotFound
  1457. */
  1458. public function pressKey($element, $char)
  1459. {
  1460. $el = $this->matchFirstOrFail($this->webDriver, $element);
  1461. $args = func_get_args();
  1462. array_shift($args);
  1463. $keys = array();
  1464. foreach ($args as $key) {
  1465. $keys[] = $this->convertKeyMod

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