PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/codeception/codeception/src/Codeception/Lib/InnerBrowser.php

https://gitlab.com/jhonn/rest
PHP | 946 lines | 743 code | 89 blank | 114 comment | 80 complexity | 1eee585cca44e68b6bdae248ab88bc85 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. namespace Codeception\Lib;
  3. use Codeception\Configuration;
  4. use Codeception\Exception\ElementNotFound;
  5. use Codeception\Exception\TestRuntime;
  6. use Codeception\PHPUnit\Constraint\Page as PageConstraint;
  7. use Codeception\PHPUnit\Constraint\Crawler as CrawlerConstraint;
  8. use Codeception\PHPUnit\Constraint\CrawlerNot as CrawlerNotConstraint;
  9. use Codeception\Module;
  10. use Codeception\TestCase;
  11. use Codeception\Util\Locator;
  12. use Codeception\Lib\Interfaces\Web;
  13. use Symfony\Component\BrowserKit\Cookie;
  14. use Symfony\Component\CssSelector\CssSelector;
  15. use Symfony\Component\CssSelector\Exception\ParseException;
  16. use Symfony\Component\DomCrawler\Crawler;
  17. use Symfony\Component\DomCrawler\Field\ChoiceFormField;
  18. use Symfony\Component\DomCrawler\Field\FileFormField;
  19. use Symfony\Component\DomCrawler\Field\FormField;
  20. use Symfony\Component\DomCrawler\Field\InputFormField;
  21. use Symfony\Component\DomCrawler\Field\TextareaFormField;
  22. class InnerBrowser extends Module implements Web
  23. {
  24. /**
  25. * @var \Symfony\Component\DomCrawler\Crawler
  26. */
  27. protected $crawler;
  28. /**
  29. * @api
  30. * @var \Symfony\Component\BrowserKit\Client
  31. */
  32. public $client;
  33. /**
  34. * @var array|\Symfony\Component\DomCrawler\Form[]
  35. */
  36. protected $forms = array();
  37. public function _failed(TestCase $test, $fail)
  38. {
  39. if (!$this->client || !$this->client->getInternalResponse()) {
  40. return;
  41. }
  42. $filename = str_replace(['::','\\','/'], ['.','',''], TestCase::getTestSignature($test)).'.fail.html';
  43. file_put_contents(codecept_output_dir($filename), $this->client->getInternalResponse()->getContent());
  44. }
  45. public function _after(TestCase $test)
  46. {
  47. $this->client = null;
  48. $this->crawler = null;
  49. $this->forms = array();
  50. }
  51. /**
  52. * Authenticates user for HTTP_AUTH
  53. *
  54. * @param $username
  55. * @param $password
  56. */
  57. public function amHttpAuthenticated($username, $password)
  58. {
  59. $this->client->setServerParameter('PHP_AUTH_USER', $username);
  60. $this->client->setServerParameter('PHP_AUTH_PW', $password);
  61. }
  62. public function amOnPage($page)
  63. {
  64. $this->crawler = $this->client->request('GET', $page);
  65. $this->forms = [];
  66. $this->debugResponse();
  67. }
  68. public function click($link, $context = null)
  69. {
  70. if ($context) {
  71. $this->crawler = $this->match($context);
  72. }
  73. if (is_array($link)) {
  74. $this->clickByLocator($link);
  75. return;
  76. }
  77. $anchor = $this->strictMatch(['link' => $link]);
  78. if (!count($anchor)) {
  79. $anchor = $this->crawler->selectLink($link);
  80. }
  81. if (count($anchor)) {
  82. $this->crawler = $this->client->click($anchor->first()->link());
  83. $this->forms = [];
  84. $this->debugResponse();
  85. return;
  86. }
  87. $buttonText = str_replace('"',"'", $link);
  88. $button = $this->crawler->selectButton($buttonText);
  89. if (count($button)) {
  90. $this->submitFormWithButton($button);
  91. $this->debugResponse();
  92. return;
  93. }
  94. $this->clickByLocator($link);
  95. }
  96. protected function clickByLocator($link)
  97. {
  98. $nodes = $this->match($link);
  99. if (!$nodes->count()) {
  100. throw new ElementNotFound($link, 'Link or Button by name or CSS or XPath');
  101. }
  102. foreach ($nodes as $node) {
  103. $tag = $node->nodeName;
  104. $type = $node->getAttribute('type');
  105. if ($tag == 'a') {
  106. $this->crawler = $this->client->click($nodes->first()->link());
  107. $this->forms = [];
  108. $this->debugResponse();
  109. return;
  110. } elseif(
  111. ($tag == 'input' && in_array($type, array('submit', 'image'))) ||
  112. ($tag == 'button' && $type == 'submit'))
  113. {
  114. $this->submitFormWithButton($nodes->first());
  115. $this->debugResponse();
  116. return;
  117. }
  118. }
  119. }
  120. protected function submitFormWithButton($button)
  121. {
  122. $form = $this->getFormFor($button);
  123. // Only now do we know which submit button was pressed.
  124. // Add it to the form object.
  125. $buttonNode = $button->getNode(0);
  126. if ($buttonNode->getAttribute("name")) {
  127. $f = new InputFormField($buttonNode);
  128. $form->set($f);
  129. }
  130. $this->debugSection('Uri', $form->getUri());
  131. $this->debugSection($form->getMethod(), $form->getValues());
  132. $this->crawler = $this->client->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles());
  133. $this->forms = [];
  134. }
  135. public function see($text, $selector = null)
  136. {
  137. if (!$selector) {
  138. $this->assertPageContains($text);
  139. } else {
  140. $nodes = $this->match($selector);
  141. $this->assertDomContains($nodes, $selector, $text);
  142. }
  143. }
  144. public function dontSee($text, $selector = null)
  145. {
  146. if (!$selector) {
  147. $this->assertPageNotContains($text);
  148. } else {
  149. $nodes = $this->match($selector);
  150. $this->assertDomNotContains($nodes, $selector, $text);
  151. }
  152. }
  153. public function seeLink($text, $url = null)
  154. {
  155. $links = $this->crawler->selectLink($text);
  156. if ($url) {
  157. $links = $links->filterXPath(sprintf('.//a[contains(@href, %s)]', Crawler::xpathLiteral($url)));
  158. }
  159. $this->assertDomContains($links, 'a');
  160. }
  161. public function dontSeeLink($text, $url = null)
  162. {
  163. $links = $this->crawler->selectLink($text);
  164. if ($url) {
  165. $links = $links->filterXPath(sprintf('.//a[contains(@href, %s)]', Crawler::xpathLiteral($url)));
  166. }
  167. $this->assertDomNotContains($links, 'a');
  168. }
  169. public function _getCurrentUri()
  170. {
  171. $url = $this->client->getHistory()->current()->getUri();
  172. $parts = parse_url($url);
  173. if (!$parts) {
  174. $this->fail("URL couldn't be parsed");
  175. }
  176. $uri = "";
  177. if (isset($parts['path'])) {
  178. $uri .= $parts['path'];
  179. }
  180. if (isset($parts['query'])) {
  181. $uri .= "?" . $parts['query'];
  182. }
  183. return $uri;
  184. }
  185. public function seeInCurrentUrl($uri)
  186. {
  187. $this->assertContains($uri, $this->_getCurrentUri());
  188. }
  189. public function dontSeeInCurrentUrl($uri)
  190. {
  191. $this->assertNotContains($uri, $this->_getCurrentUri());
  192. }
  193. public function seeCurrentUrlEquals($uri)
  194. {
  195. $this->assertEquals($uri, $this->_getCurrentUri());
  196. }
  197. public function dontSeeCurrentUrlEquals($uri)
  198. {
  199. $this->assertNotEquals($uri, $this->_getCurrentUri());
  200. }
  201. public function seeCurrentUrlMatches($uri)
  202. {
  203. \PHPUnit_Framework_Assert::assertRegExp($uri, $this->_getCurrentUri());
  204. }
  205. public function dontSeeCurrentUrlMatches($uri)
  206. {
  207. \PHPUnit_Framework_Assert::assertNotRegExp($uri, $this->_getCurrentUri());
  208. }
  209. public function grabFromCurrentUrl($uri = null)
  210. {
  211. if (!$uri) {
  212. return $this->_getCurrentUri();
  213. }
  214. $matches = array();
  215. $res = preg_match($uri, $this->_getCurrentUri(), $matches);
  216. if (!$res) {
  217. $this->fail("Couldn't match $uri in " . $this->_getCurrentUri());
  218. }
  219. if (!isset($matches[1])) {
  220. $this->fail("Nothing to grab. A regex parameter required. Ex: '/user/(\\d+)'");
  221. }
  222. return $matches[1];
  223. }
  224. public function seeCheckboxIsChecked($checkbox)
  225. {
  226. $checkboxes = $this->crawler->filter($checkbox);
  227. $this->assertDomContains($checkboxes->filter('input[checked=checked]'), 'checkbox');
  228. }
  229. public function dontSeeCheckboxIsChecked($checkbox)
  230. {
  231. $checkboxes = $this->crawler->filter($checkbox);
  232. \PHPUnit_Framework_Assert::assertEquals(0, $checkboxes->filter('input[checked=checked]')->count());
  233. }
  234. public function seeInField($field, $value)
  235. {
  236. $this->assert($this->proceedSeeInField($field, $value));
  237. }
  238. public function dontSeeInField($field, $value)
  239. {
  240. $this->assertNot($this->proceedSeeInField($field, $value));
  241. }
  242. protected function proceedSeeInField($field, $value)
  243. {
  244. $fields = $this->getFieldsByLabelOrCss($field);
  245. $currentValues = [];
  246. if ($fields->filter('textarea')->count() !== 0) {
  247. $currentValues = $fields->filter('textarea')->extract(array('_text'));
  248. } elseif ($fields->filter('select')->count() !== 0) {
  249. $currentValues = $fields->filter('select option:selected')->extract(array('value'));
  250. } elseif ($fields->filter('input[type=radio],input[type=checkbox]')->count() !== 0) {
  251. if (is_bool($value)) {
  252. $currentValues = [$fields->filter('input:checked')->count() > 0];
  253. } else {
  254. $currentValues = $fields->filter('input:checked')->extract(array('value'));
  255. }
  256. } else {
  257. $currentValues = $fields->extract(array('value'));
  258. }
  259. $strField = $field;
  260. if (is_array($field)) {
  261. $ident = reset($field);
  262. $strField = key($field) . '=>' . $ident;
  263. }
  264. return [
  265. 'Contains',
  266. $value,
  267. $currentValues,
  268. "Failed testing for '$value' in $strField's value: " . implode(', ', $currentValues)
  269. ];
  270. }
  271. public function submitForm($selector, $params, $button = null)
  272. {
  273. $form = $this->match($selector)->first();
  274. if (!count($form)) {
  275. throw new ElementNotFound($selector, 'Form');
  276. }
  277. $url = '';
  278. /** @var \Symfony\Component\DomCrawler\Crawler|\DOMElement[] $fields */
  279. $fields = $form->filter('input,button');
  280. foreach ($fields as $field) {
  281. if (($field->getAttribute('type') === 'checkbox' || $field->getAttribute('type') === 'radio') && !$field->hasAttribute('checked')) {
  282. continue;
  283. } elseif ($field->getAttribute('type') === 'button') {
  284. continue;
  285. } elseif (($field->getAttribute('type') === 'submit' || $field->tagName === 'button') && $field->getAttribute('name') !== $button) {
  286. continue;
  287. }
  288. $url .= sprintf('%s=%s', $field->getAttribute('name'), $field->getAttribute('value')) . '&';
  289. }
  290. /** @var \Symfony\Component\DomCrawler\Crawler|\DOMElement[] $fields */
  291. $fields = $form->filter('textarea');
  292. foreach ($fields as $field) {
  293. $url .= sprintf('%s=%s', $field->getAttribute('name'), $field->nodeValue) . '&';
  294. }
  295. /** @var \Symfony\Component\DomCrawler\Crawler|\DOMElement[] $fields */
  296. $fields = $form->filter('select');
  297. foreach ($fields as $field) {
  298. /** @var \DOMElement $option */
  299. foreach ($field->childNodes as $option) {
  300. if ($option->getAttribute('selected') == 'selected') {
  301. $url .= sprintf('%s=%s', $field->getAttribute('name'), $option->getAttribute('value')) . '&';
  302. }
  303. }
  304. }
  305. $url .= http_build_query($params);
  306. parse_str($url, $params);
  307. $method = $form->attr('method') ? $form->attr('method') : 'GET';
  308. $query = '';
  309. if (strtoupper($method) == 'GET') {
  310. $query = '?' . http_build_query($params);
  311. }
  312. $this->debugSection('Uri', $this->getFormUrl($form));
  313. $this->debugSection('Method', $method);
  314. $this->debugSection('Parameters', $params);
  315. $this->crawler = $this->client->request($method, $this->getFormUrl($form) . $query, $params);
  316. $this->debugResponse();
  317. }
  318. /**
  319. * @param \Symfony\Component\DomCrawler\Crawler $form
  320. *
  321. * @return string
  322. */
  323. protected function getFormUrl($form)
  324. {
  325. $action = $form->attr('action');
  326. $currentUrl = $this->client->getHistory()->current()->getUri();
  327. if (empty($action) || $action === '#') {
  328. return $currentUrl;
  329. }
  330. $build = parse_url($currentUrl);
  331. if ($build === false) {
  332. throw new TestRuntime("URL '$currentUrl' is malformed");
  333. }
  334. $uriParts = parse_url($action);
  335. if ($uriParts === false) {
  336. throw new TestRuntime("URI '$action' is malformed");
  337. }
  338. foreach ($uriParts as $part => $value) {
  339. if ($part === 'path' && strpos($value, '/') !== 0 && !empty($build[$part])) {
  340. // if it ends with a slash, relative paths are below it
  341. if (preg_match('~/$~', $build[$part])) {
  342. $build[$part] = $build[$part] . $value;
  343. continue;
  344. }
  345. $build[$part] = dirname($build[$part]) . '/' . $value;
  346. continue;
  347. }
  348. $build[$part] = $value;
  349. }
  350. return \GuzzleHttp\Url::buildUrl($build);
  351. }
  352. /**
  353. * @param \Symfony\Component\DomCrawler\Crawler $node
  354. *
  355. * @return \Symfony\Component\DomCrawler\Form|\Symfony\Component\DomCrawler\Field\ChoiceFormField[]|\Symfony\Component\DomCrawler\Field\FileFormField[]
  356. */
  357. protected function getFormFor($node)
  358. {
  359. $form = $node->parents()->filter('form')->first();
  360. if (!$form) {
  361. $this->fail('The selected node does not have a form ancestor.');
  362. }
  363. $action = $this->getFormUrl($form);
  364. if (isset($this->forms[$action])) {
  365. return $this->forms[$action];
  366. }
  367. $formSubmits = $form->filter('*[type=submit]');
  368. // Inject a submit button if there isn't one.
  369. if ($formSubmits->count() == 0) {
  370. $autoSubmit = new \DOMElement('input');
  371. $form->rewind();
  372. $autoSubmit = $form->current()->appendChild($autoSubmit);
  373. $autoSubmit->setAttribute('type', 'submit'); // for forms with no submits
  374. $autoSubmit->setAttribute('name', 'codeception_added_auto_submit');
  375. }
  376. // Retrieve the store the Form object.
  377. $this->forms[$action] = $form->form();
  378. return $this->forms[$action];
  379. }
  380. public function fillField($field, $value)
  381. {
  382. $input = $this->getFieldByLabelOrCss($field);
  383. $form = $this->getFormFor($input);
  384. $name = $input->attr('name');
  385. $dynamicField = $input->getNode(0)->tagName == 'textarea'
  386. ? new TextareaFormField($input->getNode(0))
  387. : new InputFormField($input->getNode(0));
  388. $formField = $this->matchFormField($name, $form, $dynamicField);
  389. $formField->setValue($value);
  390. }
  391. /**
  392. * @param $field
  393. *
  394. * @return \Symfony\Component\DomCrawler\Crawler
  395. */
  396. protected function getFieldsByLabelOrCss($field)
  397. {
  398. if (is_array($field)) {
  399. $input = $this->strictMatch($field);
  400. if (!count($input)) {
  401. throw new ElementNotFound($field);
  402. }
  403. return $input;
  404. }
  405. // by label
  406. $label = $this->strictMatch(['xpath' => sprintf('.//label[text()=%s]', Crawler::xpathLiteral($field))]);
  407. if (count($label)) {
  408. $label = $label->first();
  409. if ($label->attr('for')) {
  410. $input = $this->strictMatch(['id' => $label->attr('for')]);
  411. }
  412. }
  413. // by name
  414. if (!isset($input)) {
  415. $input = $this->strictMatch(['name' => $field]);
  416. }
  417. // by CSS and XPath
  418. if (!count($input)) {
  419. $input = $this->match($field);
  420. }
  421. if (!count($input)) {
  422. throw new ElementNotFound($field, 'Form field by Label or CSS');
  423. }
  424. return $input;
  425. }
  426. protected function getFieldByLabelOrCss($field)
  427. {
  428. $input = $this->getFieldsByLabelOrCss($field);
  429. return $input->first();
  430. }
  431. public function selectOption($select, $option)
  432. {
  433. $field = $this->getFieldByLabelOrCss($select);
  434. $form = $this->getFormFor($field);
  435. $fieldName = $field->attr('name');
  436. if ($field->attr('multiple')) {
  437. $fieldName = str_replace('[]', '', $fieldName);
  438. }
  439. if (is_array($option)) {
  440. $options = array();
  441. foreach ($option as $opt) {
  442. $options[] = $this->matchOption($field, $opt);
  443. }
  444. $form[$fieldName]->select($options);
  445. return;
  446. }
  447. $form[$fieldName]->select($this->matchOption($field, $option));
  448. }
  449. protected function matchOption(Crawler $field, $option)
  450. {
  451. $options = $field->filterXPath(sprintf('//option[text()=normalize-space("%s")]', $option));
  452. if ($options->count()) {
  453. return $options->first()->attr('value');
  454. }
  455. return $option;
  456. }
  457. public function checkOption($option)
  458. {
  459. $form = $this->getFormFor($field = $this->getFieldByLabelOrCss($option));
  460. $name = $field->attr('name');
  461. // If the name is an array than we compare objects to find right checkbox
  462. $formField = $this->matchFormField($name, $form, new ChoiceFormField($field->getNode(0)));
  463. $formField->tick();
  464. }
  465. public function uncheckOption($option)
  466. {
  467. $form = $this->getFormFor($field = $this->getFieldByLabelOrCss($option));
  468. $name = $field->attr('name');
  469. // If the name is an array than we compare objects to find right checkbox
  470. $formField = $this->matchFormField($name, $form, new ChoiceFormField($field->getNode(0)));
  471. $formField->untick();
  472. }
  473. public function attachFile($field, $filename)
  474. {
  475. $form = $this->getFormFor($field = $this->getFieldByLabelOrCss($field));
  476. $path = Configuration::dataDir() . $filename;
  477. $name = $field->attr('name');
  478. if (!is_readable($path)) {
  479. $this->fail("file $filename not found in Codeception data path. Only files stored in data path accepted");
  480. }
  481. $formField = $this->matchFormField($name, $form, new FileFormField($field->getNode(0)));
  482. if (is_array($formField)) {
  483. $this->fail("Field $name is ignored on upload, field $name is treated as array.");
  484. }
  485. $formField->upload($path);
  486. }
  487. /**
  488. * If your page triggers an ajax request, you can perform it manually.
  489. * This action sends a GET ajax request with specified params.
  490. *
  491. * See ->sendAjaxPostRequest for examples.
  492. *
  493. * @param $uri
  494. * @param $params
  495. */
  496. public function sendAjaxGetRequest($uri, $params = array())
  497. {
  498. $this->sendAjaxRequest('GET', $uri, $params);
  499. }
  500. /**
  501. * If your page triggers an ajax request, you can perform it manually.
  502. * This action sends a POST ajax request with specified params.
  503. * Additional params can be passed as array.
  504. *
  505. * Example:
  506. *
  507. * Imagine that by clicking checkbox you trigger ajax request which updates user settings.
  508. * We emulate that click by running this ajax request manually.
  509. *
  510. * ``` php
  511. * <?php
  512. * $I->sendAjaxPostRequest('/updateSettings', array('notifications' => true)); // POST
  513. * $I->sendAjaxGetRequest('/updateSettings', array('notifications' => true)); // GET
  514. *
  515. * ```
  516. *
  517. * @param $uri
  518. * @param $params
  519. */
  520. public function sendAjaxPostRequest($uri, $params = array())
  521. {
  522. $this->sendAjaxRequest('POST', $uri, $params);
  523. }
  524. /**
  525. * If your page triggers an ajax request, you can perform it manually.
  526. * This action sends an ajax request with specified method and params.
  527. *
  528. * Example:
  529. *
  530. * You need to perform an ajax request specifying the HTTP method.
  531. *
  532. * ``` php
  533. * <?php
  534. * $I->sendAjaxRequest('PUT', '/posts/7', array('title' => 'new title'));
  535. *
  536. * ```
  537. *
  538. * @param $method
  539. * @param $uri
  540. * @param $params
  541. */
  542. public function sendAjaxRequest($method, $uri, $params = array())
  543. {
  544. $this->client->request($method, $uri, $params, array(), array('HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'));
  545. $this->debugResponse();
  546. }
  547. protected function debugResponse()
  548. {
  549. $this->debugSection('Response', $this->getResponseStatusCode());
  550. $this->debugSection('Page', $this->client->getHistory()->current()->getUri());
  551. $this->debugSection('Cookies', $this->client->getInternalRequest()->getCookies());
  552. $this->debugSection('Headers', $this->client->getInternalResponse()->getHeaders());
  553. }
  554. protected function getResponseStatusCode()
  555. {
  556. // depending on Symfony version
  557. $response = $this->client->getInternalResponse();
  558. if (method_exists($response, 'getStatus')) {
  559. return $response->getStatus();
  560. }
  561. if (method_exists($response, 'getStatusCode')) {
  562. return $response->getStatusCode();
  563. }
  564. return "N/A";
  565. }
  566. /**
  567. * @param $selector
  568. *
  569. * @return Crawler
  570. */
  571. protected function match($selector)
  572. {
  573. if (is_array($selector)) {
  574. return $this->strictMatch($selector);
  575. }
  576. try {
  577. $selector = CssSelector::toXPath($selector);
  578. } catch (ParseException $e) {
  579. }
  580. if (!Locator::isXPath($selector)) {
  581. codecept_debug("XPath `$selector` is malformed!");
  582. return new \Symfony\Component\DomCrawler\Crawler;
  583. }
  584. return @$this->crawler->filterXPath($selector);
  585. }
  586. /**
  587. * @param array $by
  588. * @throws TestRuntime
  589. * @return Crawler
  590. */
  591. protected function strictMatch(array $by)
  592. {
  593. $type = key($by);
  594. $locator = $by[$type];
  595. switch ($type) {
  596. case 'id':
  597. return $this->crawler->filter("#$locator");
  598. case 'name':
  599. return @$this->crawler->filterXPath(sprintf('.//*[@name=%s]', Crawler::xpathLiteral($locator)));
  600. case 'css':
  601. return $this->crawler->filter($locator);
  602. case 'xpath':
  603. return @$this->crawler->filterXPath($locator);
  604. case 'link':
  605. return @$this->crawler->filterXPath(sprintf('.//a[.=%s]', Crawler::xpathLiteral($locator)));
  606. case 'class':
  607. return $this->crawler->filter(".$locator");
  608. default:
  609. throw new TestRuntime("Locator type '$by' is not defined. Use either: xpath, css, id, link, class, name");
  610. }
  611. }
  612. protected function filterByAttributes(Crawler $nodes, array $attributes)
  613. {
  614. foreach ($attributes as $attr => $val) {
  615. $nodes = $nodes->reduce(function(Crawler $node) use ($attr, $val) {
  616. return $node->attr($attr) == $val;
  617. });
  618. }
  619. return $nodes;
  620. }
  621. public function grabTextFrom($cssOrXPathOrRegex)
  622. {
  623. $nodes = $this->match($cssOrXPathOrRegex);
  624. if ($nodes->count()) {
  625. return $nodes->first()->text();
  626. }
  627. if (@preg_match($cssOrXPathOrRegex, $this->client->getInternalResponse()->getContent(), $matches)) {
  628. return $matches[1];
  629. }
  630. throw new ElementNotFound($cssOrXPathOrRegex, 'Element that matches CSS or XPath or Regex');
  631. }
  632. public function grabAttributeFrom($cssOrXpath, $attribute)
  633. {
  634. $nodes = $this->match($cssOrXpath);
  635. if (!$nodes->count()) {
  636. throw new ElementNotFound($cssOrXpath, 'Element that matches CSS or XPath');
  637. }
  638. return $nodes->first()->attr($attribute);
  639. }
  640. /**
  641. * @param $field
  642. *
  643. * @return array|mixed|null|string
  644. */
  645. public function grabValueFrom($field)
  646. {
  647. $nodes = $this->match($field);
  648. if (!$nodes->count()) {
  649. throw new ElementNotFound($field, 'Field');
  650. }
  651. if ($nodes->filter('textarea')->count()) {
  652. return $nodes->filter('textarea')->text();
  653. }
  654. if ($nodes->filter('input')->count()) {
  655. return $nodes->filter('input')->attr('value');
  656. }
  657. if ($nodes->filter('select')->count()) {
  658. /** @var \Symfony\Component\DomCrawler\Crawler $select */
  659. $select = $nodes->filter('select');
  660. $is_multiple = $select->attr('multiple');
  661. $results = array();
  662. foreach ($select->children() as $option) {
  663. /** @var \DOMElement $option */
  664. if ($option->getAttribute('selected') == 'selected') {
  665. $val = $option->getAttribute('value');
  666. if (!$is_multiple) {
  667. return $val;
  668. }
  669. $results[] = $val;
  670. }
  671. }
  672. if (!$is_multiple) {
  673. return null;
  674. }
  675. return $results;
  676. }
  677. $this->fail("Element $field is not a form field or does not contain a form field");
  678. }
  679. public function setCookie($name, $val)
  680. {
  681. $cookies = $this->client->getCookieJar();
  682. $cookies->set(new Cookie($name, $val));
  683. $this->debugSection('Cookies', $this->client->getCookieJar()->all());
  684. }
  685. public function grabCookie($name)
  686. {
  687. $this->debugSection('Cookies', $this->client->getCookieJar()->all());
  688. $cookies = $this->client->getCookieJar()->get($name);
  689. if (!$cookies) {
  690. return null;
  691. }
  692. return $cookies->getValue();
  693. }
  694. public function seeCookie($name)
  695. {
  696. $this->debugSection('Cookies', $this->client->getCookieJar()->all());
  697. $this->assertNotNull($this->client->getCookieJar()->get($name));
  698. }
  699. public function dontSeeCookie($name)
  700. {
  701. $this->debugSection('Cookies', $this->client->getCookieJar()->all());
  702. $this->assertNull($this->client->getCookieJar()->get($name));
  703. }
  704. public function resetCookie($name)
  705. {
  706. $this->client->getCookieJar()->expire($name);
  707. $this->debugSection('Cookies', $this->client->getCookieJar()->all());
  708. }
  709. public function seeElement($selector, $attributes = array())
  710. {
  711. $nodes = $this->match($selector);
  712. if (!empty($attributes)) {
  713. $nodes = $this->filterByAttributes($nodes, $attributes);
  714. $selector .= "' with attribute(s) '" . trim(json_encode($attributes),'{}');
  715. }
  716. $this->assertDomContains($nodes, $selector);
  717. }
  718. public function dontSeeElement($selector, $attributes = array())
  719. {
  720. $nodes = $this->match($selector);
  721. if (!empty($attributes)) {
  722. $nodes = $this->filterByAttributes($nodes, $attributes);
  723. $selector .= "' with attribute(s) '" . trim(json_encode($attributes),'{}');
  724. }
  725. $this->assertDomNotContains($nodes, $selector);
  726. }
  727. public function seeNumberOfElements($selector, $expected)
  728. {
  729. $counted = count($this->match($selector));
  730. if(is_array($expected)){
  731. list($floor,$ceil) = $expected;
  732. $this->assertTrue($floor<=$counted && $ceil>=$counted,
  733. 'Number of elements counted differs from expected range' );
  734. }else{
  735. $this->assertEquals($expected, $counted,
  736. 'Number of elements counted differs from expected number' );
  737. }
  738. }
  739. public function seeOptionIsSelected($select, $optionText)
  740. {
  741. $selected = $this->matchSelectedOption($select);
  742. $this->assertDomContains($selected, 'selected option');
  743. //If element is radio then we need to check value
  744. $value = $selected->getNode(0)->tagName == 'option' ? $selected->text() : $selected->getNode(0)->getAttribute('value');
  745. $this->assertEquals($optionText, $value);
  746. }
  747. public function dontSeeOptionIsSelected($select, $optionText)
  748. {
  749. $selected = $this->matchSelectedOption($select);
  750. if (!$selected->count()) {
  751. $this->assertEquals(0, $selected->count());
  752. return;
  753. }
  754. //If element is radio then we need to check value
  755. $value = $selected->getNode(0)->tagName == 'option' ? $selected->text() : $selected->getNode(0)->getAttribute('value');
  756. $this->assertNotEquals($optionText, $value);
  757. }
  758. protected function matchSelectedOption($select)
  759. {
  760. $nodes = $this->getFieldsByLabelOrCss($select);
  761. return $nodes->filter('option[selected],input:checked');
  762. }
  763. /**
  764. * Asserts that current page has 404 response status code.
  765. */
  766. public function seePageNotFound()
  767. {
  768. $this->seeResponseCodeIs(404);
  769. }
  770. /**
  771. * Checks that response code is equal to value provided.
  772. *
  773. * @param $code
  774. *
  775. * @return mixed
  776. */
  777. public function seeResponseCodeIs($code)
  778. {
  779. $this->assertEquals($code, $this->getResponseStatusCode());
  780. }
  781. public function seeInTitle($title)
  782. {
  783. $nodes = $this->crawler->filter('title');
  784. if (!$nodes->count()) {
  785. throw new ElementNotFound("<title>","Tag");
  786. }
  787. $this->assertContains($title, $nodes->first()->text(), "page title contains $title");
  788. }
  789. public function dontSeeInTitle($title)
  790. {
  791. $nodes = $this->crawler->filter('title');
  792. if (!$nodes->count()) {
  793. $this->assertTrue(true);
  794. return;
  795. }
  796. $this->assertNotContains($title, $nodes->first()->text(), "page title contains $title");
  797. }
  798. protected function assertDomContains($nodes, $message, $text = '')
  799. {
  800. $constraint = new CrawlerConstraint($text, $this->_getCurrentUri());
  801. $this->assertThat($nodes, $constraint, $message);
  802. }
  803. protected function assertDomNotContains($nodes, $message, $text = '')
  804. {
  805. $constraint = new CrawlerNotConstraint($text, $this->_getCurrentUri());
  806. $this->assertThat($nodes, $constraint, $message);
  807. }
  808. protected function assertPageContains($needle, $message = '')
  809. {
  810. $constraint = new PageConstraint($needle, $this->_getCurrentUri());
  811. $this->assertThat($this->client->getInternalResponse()->getContent(), $constraint,$message);
  812. }
  813. protected function assertPageNotContains($needle, $message = '')
  814. {
  815. $constraint = new PageConstraint($needle, $this->_getCurrentUri());
  816. $this->assertThatItsNot($this->client->getInternalResponse()->getContent(), $constraint,$message);
  817. }
  818. /**
  819. * @param $name
  820. * @param $form
  821. * @param $dynamicField
  822. * @return FileFormField
  823. */
  824. protected function matchFormField($name, $form, $dynamicField)
  825. {
  826. if (substr($name, -2) != '[]') return $form[$name];
  827. $name = substr($name, 0, -2);
  828. /** @var $item \Symfony\Component\DomCrawler\Field\FileFormField */
  829. foreach ($form[$name] as $item) {
  830. if ($item == $dynamicField) {
  831. return $item;
  832. }
  833. }
  834. return $item;
  835. }
  836. }