From 1c599397dd796cf70e811d421d845718de9a0f12 Mon Sep 17 00:00:00 2001
From: Tavo Nieves J
Date: Mon, 31 Aug 2020 16:57:52 -0500
Subject: [PATCH 1/2] Code Quality Commit
---
composer.json | 2 +-
readme.md | 4 +-
.../Exception/ExternalUrlException.php | 5 +-
src/Codeception/Lib/InnerBrowser.php | 2317 +++++------------
.../Lib/Model/ConflictsWithModuleTrait.php | 17 +
.../Lib/Model/ElementLocatorTrait.php | 16 +
src/Codeception/Lib/Model/InnerBrowserApi.php | 494 ++++
.../Lib/Model/PageSourceSaverTrait.php | 31 +
src/Codeception/Lib/Model/WebTrait.php | 516 ++++
src/Codeception/Util/HttpCode.php | 9 +
.../Constraints/CrawlerConstraintTest.php | 13 +-
.../Constraints/CrawlerNotConstraintTest.php | 12 +-
.../Codeception/Module/FrameworksTest.php | 26 +-
tests/unit/Codeception/Module/TestsForWeb.php | 59 +-
tests/unit/Codeception/Util/HttpCodeTest.php | 5 +-
15 files changed, 1856 insertions(+), 1670 deletions(-)
create mode 100644 src/Codeception/Lib/Model/ConflictsWithModuleTrait.php
create mode 100644 src/Codeception/Lib/Model/ElementLocatorTrait.php
create mode 100644 src/Codeception/Lib/Model/InnerBrowserApi.php
create mode 100644 src/Codeception/Lib/Model/PageSourceSaverTrait.php
create mode 100644 src/Codeception/Lib/Model/WebTrait.php
diff --git a/composer.json b/composer.json
index bdb7f4e..697917b 100644
--- a/composer.json
+++ b/composer.json
@@ -2,7 +2,7 @@
"name":"codeception/lib-innerbrowser",
"description":"Parent library for all Codeception framework modules and PhpBrowser",
"keywords":["codeception"],
- "homepage":"http://codeception.com/",
+ "homepage":"https://codeception.com/",
"type":"library",
"license":"MIT",
"authors":[
diff --git a/readme.md b/readme.md
index b524732..0a70fd4 100644
--- a/readme.md
+++ b/readme.md
@@ -1,4 +1,6 @@
-# Parent library for all Codeception framework modules and PhpBrowser
+# Codeception Lib InnerBrowser
+
+Parent library for all Codeception framework modules and PhpBrowser.
[](https://travis-ci.org/Codeception/lib-innerbrowser)
diff --git a/src/Codeception/Exception/ExternalUrlException.php b/src/Codeception/Exception/ExternalUrlException.php
index bcacbe3..c6cb65f 100644
--- a/src/Codeception/Exception/ExternalUrlException.php
+++ b/src/Codeception/Exception/ExternalUrlException.php
@@ -2,6 +2,9 @@
namespace Codeception\Exception;
-class ExternalUrlException extends \Exception
+use Exception;
+
+class ExternalUrlException extends Exception
{
+
}
diff --git a/src/Codeception/Lib/InnerBrowser.php b/src/Codeception/Lib/InnerBrowser.php
index 6a537ec..11bf16c 100644
--- a/src/Codeception/Lib/InnerBrowser.php
+++ b/src/Codeception/Lib/InnerBrowser.php
@@ -1,4 +1,5 @@
null, 'path' => '/', 'domain' => '', 'secure' => false];
+
/**
- * @api
- * @var \Symfony\Component\BrowserKit\AbstractBrowser
+ * @var array|Form[]
*/
- public $client;
+ protected $forms = [];
+
+ protected $internalDomains;
/**
- * @var array|\Symfony\Component\DomCrawler\Form[]
+ * @api
+ * @var AbstractBrowser
*/
- protected $forms = [];
+ public $client;
public $headers = [];
- protected $defaultCookieParameters = ['expires' => null, 'path' => '/', 'domain' => '', 'secure' => false];
+ /**
+ * Clicks the link or submits the form when the button is clicked
+ * @param DOMNode $node
+ * @return boolean clicked something
+ * @throws ModuleException|ExternalUrlException
+ */
+ private function clickButton(DOMNode $node)
+ {
+ /**
+ * First we check if the button is associated to a form.
+ * It is associated to a form when it has a nonempty form
+ */
+ $formAttribute = $node->attributes->getNamedItem('form');
+ if (isset($formAttribute)) {
+ $form = empty($formAttribute->nodeValue) ? null : $this->filterByCSS('#' . $formAttribute->nodeValue)->getNode(0);
+ } else {
+ // Check parents
+ $currentNode = $node;
+ $form = null;
+ while ($currentNode->parentNode !== null) {
+ $currentNode = $currentNode->parentNode;
+ if ($currentNode->nodeName === 'form') {
+ $form = $node;
+ break;
+ }
+ }
+ }
- protected $internalDomains;
+ if (isset($form)) {
+ $buttonName = $node->getAttribute('name');
+ if ($buttonName !== '') {
+ $formParams = [$buttonName => $node->getAttribute('value')];
+ } else {
+ $formParams = [];
+ }
+ $this->proceedSubmitForm(
+ new Crawler($form, $this->getAbsoluteUrlFor($this->_getCurrentUri()), $this->getBaseUrl()),
+ $formParams
+ );
+ return true;
+ }
- private $baseUrl;
+ // Check if the button is inside an anchor.
+ $currentNode = $node;
+ while ($currentNode->parentNode !== null) {
+ $currentNode = $currentNode->parentNode;
+ if ($currentNode->nodeName === 'a') {
+ $this->openHrefFromDomNode($currentNode);
+ return true;
+ }
+ }
+ throw new TestRuntimeException('Button is not inside a link or a form');
+ }
- public function _failed(TestInterface $test, $fail)
+ private function getBaseUrl()
{
- try {
- if (!$this->client || !$this->client->getInternalResponse()) {
- return;
- }
- } catch (BadMethodCallException $e) {
- //Symfony 5 throws exception if request() method threw an exception.
- //The "request()" method must be called before "Symfony\Component\BrowserKit\AbstractBrowser::getInternalResponse()"
- return;
+ return $this->baseUrl;
+ }
+
+ /**
+ * @return Crawler
+ * @throws ModuleException
+ */
+ private function getCrawler()
+ {
+ if (!$this->crawler) {
+ throw new ModuleException($this, 'Crawler is null. Perhaps you forgot to call "amOnPage"?');
}
- $filename = preg_replace('~\W~', '.', Descriptor::getTestSignatureUnique($test));
+ return $this->crawler;
+ }
- $extensions = [
- 'application/json' => 'json',
- 'text/xml' => 'xml',
- 'application/xml' => 'xml',
- 'text/plain' => 'txt'
- ];
+ /**
+ * Returns a crawler Form object for the form pointed to by the
+ * passed Crawler.
+ *
+ * The returned form is an independent Crawler created to take care
+ * of the following issues currently experienced by Crawler's form
+ * object:
+ * - input fields disabled at a higher level (e.g. by a surrounding
+ * fieldset) still return values
+ * - Codeception expects an empty value to match an unselected
+ * select box.
+ *
+ * The function clones the crawler's node and creates a new crawler
+ * because it destroys or adds to the DOM for the form to achieve
+ * the desired functionality. Other functions simply querying the
+ * DOM wouldn't expect them.
+ *
+ * @param Crawler $form the form
+ * @return Form
+ * @throws ModuleException
+ */
+ private function getFormFromCrawler(Crawler $form)
+ {
+ $fakeDom = new DOMDocument();
+ $fakeDom->appendChild($fakeDom->importNode($form->getNode(0), true));
+ $node = $fakeDom->documentElement;
+ $action = (string)$this->getFormUrl($form);
+ $cloned = new Crawler($node, $action, $this->getBaseUrl());
+ $shouldDisable = $cloned->filter(
+ 'input:disabled:not([disabled]),select option:disabled,select optgroup:disabled option:not([disabled]),textarea:disabled:not([disabled]),select:disabled:not([disabled])'
+ );
+ foreach ($shouldDisable as $field) {
+ $field->parentNode->removeChild($field);
+ }
+ return $cloned->form();
+ }
+ /**
+ * @return AbstractBrowser
+ * @throws ModuleException
+ */
+ private function getRunningClient()
+ {
try {
- $internalResponse = $this->client->getInternalResponse();
+ if ($this->client->getInternalRequest() === null) {
+ throw new ModuleException(
+ $this,
+ "Page not loaded. Use `\$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it"
+ );
+ }
} catch (BadMethodCallException $e) {
- $internalResponse = false;
+ //Symfony 5
+ throw new ModuleException(
+ $this,
+ "Page not loaded. Use `\$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it"
+ );
}
-
- $responseContentType = $internalResponse ? $internalResponse->getHeader('content-type') : '';
- list($responseMimeType) = explode(';', $responseContentType);
-
- $extension = isset($extensions[$responseMimeType]) ? $extensions[$responseMimeType] : 'html';
-
- $filename = mb_strcut($filename, 0, 244, 'utf-8') . '.fail.' . $extension;
- $this->_savePageSource($report = codecept_output_dir() . $filename);
- $test->getMetadata()->addReport('html', $report);
- $test->getMetadata()->addReport('response', $report);
+ return $this->client;
}
- public function _after(TestInterface $test)
+ private function openHrefFromDomNode(DOMNode $node)
{
- $this->client = null;
- $this->crawler = null;
- $this->forms = [];
- $this->headers = [];
+ $link = new Link($node, $this->getBaseUrl());
+ $this->amOnPage(preg_replace('/#.*/', '', $link->getUri()));
}
- public function _conflicts()
+ /**
+ * @return string
+ * @throws ModuleException
+ */
+ private function retrieveBaseUrl()
{
- return 'Codeception\Lib\Interfaces\Web';
+ $baseUrl = '';
+
+ $baseHref = $this->crawler->filter('base');
+ if (count($baseHref) > 0) {
+ $baseUrl = $baseHref->getNode(0)->getAttribute('href');
+ }
+ if ($baseUrl === '') {
+ $baseUrl = $this->_getCurrentUri();
+ }
+ return $this->getAbsoluteUrlFor($baseUrl);
}
- public function _findElements($locator)
+ private function stringifySelector($selector)
{
- return $this->match($locator);
+ if (is_array($selector)) {
+ return trim(json_encode($selector), '{}');
+ }
+ return $selector;
}
/**
- * Send custom request to a backend using method, uri, parameters, etc.
- * Use it in Helpers to create special request actions, like accessing API
- * Returns a string with response body.
- *
- * ```php
- * getModule('{{MODULE_NAME}}')->_request('POST', '/api/v1/users', ['name' => $name]);
- * $user = json_decode($userData);
- * return $user->id;
- * }
- * ?>
- * ```
- * Does not load the response into the module so you can't interact with response page (click, fill forms).
- * To load arbitrary page for interaction, use `_loadPage` method.
- *
- * @api
* @param $method
* @param $uri
* @param array $parameters
* @param array $files
* @param array $server
* @param null $content
- * @return mixed|Crawler
+ * @param bool $changeHistory
+ * @return mixed|Crawler|null
* @throws ExternalUrlException
- * @see `_loadPage`
+ * @throws ModuleException
*/
- public function _request(
+ protected function clientRequest(
$method,
$uri,
array $parameters = [],
array $files = [],
array $server = [],
- $content = null
+ $content = null,
+ $changeHistory = true
) {
- $this->clientRequest($method, $uri, $parameters, $files, $server, $content, true);
- return $this->_getResponseContent();
- }
-
- /**
- * Returns content of the last response
- * Use it in Helpers when you want to retrieve response of request performed by another module.
- *
- * ```php
- * assertStringContainsString($text, $this->getModule('{{MODULE_NAME}}')->_getResponseContent(), "response contains");
- * }
- * ?>
- * ```
- *
- * @api
- * @return string
- * @throws ModuleException
- */
- public function _getResponseContent()
- {
- return (string)$this->getRunningClient()->getInternalResponse()->getContent();
- }
-
- protected function clientRequest($method, $uri, array $parameters = [], array $files = [], array $server = [], $content = null, $changeHistory = true)
- {
- $this->debugSection("Request Headers", $this->headers);
+ $this->debugSection('Request Headers', $this->headers);
foreach ($this->headers as $header => $val) { // moved from REST module
@@ -219,8 +298,12 @@ protected function clientRequest($method, $uri, array $parameters = [], array $f
$maxRedirects = $this->client->getMaxRedirects();
} else {
//Symfony 2.7 support
- $isFollowingRedirects = ReflectionHelper::readPrivateProperty($this->client, 'followRedirects', 'Symfony\Component\BrowserKit\Client');
- $maxRedirects = ReflectionHelper::readPrivateProperty($this->client, 'maxRedirects', 'Symfony\Component\BrowserKit\Client');
+ $isFollowingRedirects = ReflectionHelper::readPrivateProperty(
+ $this->client, 'followRedirects', 'Symfony\Component\BrowserKit\Client'
+ );
+ $maxRedirects = ReflectionHelper::readPrivateProperty(
+ $this->client, 'maxRedirects', 'Symfony\Component\BrowserKit\Client'
+ );
}
if (!$isFollowingRedirects) {
@@ -235,201 +318,70 @@ protected function clientRequest($method, $uri, array $parameters = [], array $f
return $this->redirectIfNecessary($result, $maxRedirects, 0);
}
- protected function isInternalDomain($domain)
+ protected function assertDomContains($nodes, $message, $text = '')
{
- if ($this->internalDomains === null) {
- $this->internalDomains = $this->getInternalDomains();
- }
-
- foreach ($this->internalDomains as $pattern) {
- if (preg_match($pattern, $domain)) {
- return true;
- }
- }
- return false;
+ $constraint = new CrawlerConstraint($text, $this->_getCurrentUri());
+ $this->assertThat($nodes, $constraint, $message);
}
- /**
- * Opens a page with arbitrary request parameters.
- * Useful for testing multi-step forms on a specific step.
- *
- * ```php
- * getModule('{{MODULE_NAME}}')->_loadPage('POST', '/checkout/step2', ['order' => $orderId]);
- * }
- * ?>
- * ```
- *
- * @api
- * @param $method
- * @param $uri
- * @param array $parameters
- * @param array $files
- * @param array $server
- * @param null $content
- */
- public function _loadPage(
- $method,
- $uri,
- array $parameters = [],
- array $files = [],
- array $server = [],
- $content = null
- ) {
- $this->crawler = $this->clientRequest($method, $uri, $parameters, $files, $server, $content);
- $this->baseUrl = $this->retrieveBaseUrl();
- $this->forms = [];
+ protected function assertDomNotContains($nodes, $message, $text = '')
+ {
+ $constraint = new CrawlerNotConstraint($text, $this->_getCurrentUri());
+ $this->assertThat($nodes, $constraint, $message);
}
/**
- * @return Crawler
- * @throws ModuleException
+ * @param $needle
+ * @param string $message
*/
- private function getCrawler()
+ protected function assertPageContains($needle, $message = '')
{
- if (!$this->crawler) {
- throw new ModuleException($this, 'Crawler is null. Perhaps you forgot to call "amOnPage"?');
- }
- return $this->crawler;
+ $constraint = new PageConstraint($needle, $this->_getCurrentUri());
+ $this->assertThat(
+ $this->getNormalizedResponseContent(),
+ $constraint,
+ $message
+ );
}
- private function getRunningClient()
+ /**
+ * @param $needle
+ * @param string $message
+ */
+ protected function assertPageNotContains($needle, $message = '')
{
- try {
- if ($this->client->getInternalRequest() === null) {
- throw new ModuleException(
- $this,
- "Page not loaded. Use `\$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it"
- );
- }
- } catch (BadMethodCallException $e) {
- //Symfony 5
- throw new ModuleException(
- $this,
- "Page not loaded. Use `\$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it"
- );
- }
- return $this->client;
+ $constraint = new PageConstraint($needle, $this->_getCurrentUri());
+ $this->assertThatItsNot(
+ $this->getNormalizedResponseContent(),
+ $constraint,
+ $message
+ );
}
- public function _savePageSource($filename)
+ protected function assertPageSourceContains($needle, $message = '')
{
- file_put_contents($filename, $this->_getResponseContent());
- }
-
- /**
- * Authenticates user for HTTP_AUTH
- *
- * @param $username
- * @param $password
- */
- public function amHttpAuthenticated($username, $password)
- {
- $this->client->setServerParameter('PHP_AUTH_USER', $username);
- $this->client->setServerParameter('PHP_AUTH_PW', $password);
- }
-
- /**
- * Sets the HTTP header to the passed value - which is used on
- * subsequent HTTP requests through PhpBrowser.
- *
- * Example:
- * ```php
- * haveHttpHeader('X-Requested-With', 'Codeception');
- * $I->amOnPage('test-headers.php');
- * ?>
- * ```
- *
- * To use special chars in Header Key use HTML Character Entities:
- * Example:
- * Header with underscore - 'Client_Id'
- * should be represented as - 'Client_Id' or 'Client_Id'
- *
- * ```php
- * haveHttpHeader('Client_Id', 'Codeception');
- * ?>
- * ```
- *
- * @param string $name the name of the request header
- * @param string $value the value to set it to for subsequent
- * requests
- */
- public function haveHttpHeader($name, $value)
- {
- $name = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $name)))));
- $this->headers[$name] = $value;
- }
-
- /**
- * Deletes the header with the passed name. Subsequent requests
- * will not have the deleted header in its request.
- *
- * Example:
- * ```php
- * haveHttpHeader('X-Requested-With', 'Codeception');
- * $I->amOnPage('test-headers.php');
- * // ...
- * $I->deleteHeader('X-Requested-With');
- * $I->amOnPage('some-other-page.php');
- * ?>
- * ```
- *
- * @param string $name the name of the header to delete.
- */
- public function deleteHeader($name)
- {
- $name = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $name)))));
- unset($this->headers[$name]);
- }
-
-
- public function amOnPage($page)
- {
- $this->_loadPage('GET', $page);
+ $constraint = new PageConstraint($needle, $this->_getCurrentUri());
+ $this->assertThat(
+ $this->_getResponseContent(),
+ $constraint,
+ $message
+ );
}
- public function click($link, $context = null)
+ protected function assertPageSourceNotContains($needle, $message = '')
{
- if ($context) {
- $this->crawler = $this->match($context);
- }
-
- if (is_array($link)) {
- $this->clickByLocator($link);
- return;
- }
-
- $anchor = $this->strictMatch(['link' => $link]);
- if (!count($anchor)) {
- $anchor = $this->getCrawler()->selectLink($link);
- }
- if (count($anchor)) {
- $this->openHrefFromDomNode($anchor->getNode(0));
- return;
- }
-
- $buttonText = str_replace('"', "'", $link);
- $button = $this->crawler->selectButton($buttonText);
-
- if (count($button) && $this->clickButton($button->getNode(0))) {
- return;
- }
-
- try {
- $this->clickByLocator($link);
- } catch (MalformedLocatorException $e) {
- throw new ElementNotFound("name=$link", "'$link' is invalid CSS and XPath selector and Link or Button");
- }
+ $constraint = new PageConstraint($needle, $this->_getCurrentUri());
+ $this->assertThatItsNot(
+ $this->_getResponseContent(),
+ $constraint,
+ $message
+ );
}
/**
* @param $link
* @return bool
+ * @throws ModuleException|ExternalUrlException
*/
protected function clickByLocator($link)
{
@@ -453,347 +405,228 @@ protected function clickByLocator($link)
}
}
-
- /**
- * Clicks the link or submits the form when the button is clicked
- * @param \DOMNode $node
- * @return boolean clicked something
- */
- private function clickButton(\DOMNode $node)
- {
- /**
- * First we check if the button is associated to a form.
- * It is associated to a form when it has a nonempty form
- */
- $formAttribute = $node->attributes->getNamedItem('form');
- if (isset($formAttribute)) {
- $form = empty($formAttribute->nodeValue) ? null : $this->filterByCSS('#' . $formAttribute->nodeValue)->getNode(0);
- } else {
- // Check parents
- $currentNode = $node;
- $form = null;
- while ($currentNode->parentNode !== null) {
- $currentNode = $currentNode->parentNode;
- if ($currentNode->nodeName === 'form') {
- $form = $node;
- break;
- }
- }
- }
-
- if (isset($form)) {
- $buttonName = $node->getAttribute('name');
- if ($buttonName !== '') {
- $formParams = [$buttonName => $node->getAttribute('value')];
- } else {
- $formParams = [];
- }
- $this->proceedSubmitForm(
- new Crawler($form, $this->getAbsoluteUrlFor($this->_getCurrentUri()), $this->getBaseUrl()),
- $formParams
- );
- return true;
- }
-
- // Check if the button is inside an anchor.
- $currentNode = $node;
- while ($currentNode->parentNode !== null) {
- $currentNode = $currentNode->parentNode;
- if ($currentNode->nodeName === 'a') {
- $this->openHrefFromDomNode($currentNode);
- return true;
- }
- }
- throw new TestRuntimeException('Button is not inside a link or a form');
- }
-
- private function openHrefFromDomNode(\DOMNode $node)
+ protected function debugCookieJar()
{
- $link = new Link($node, $this->getBaseUrl());
- $this->amOnPage(preg_replace('/#.*/', '', $link->getUri()));
+ $cookies = $this->client->getCookieJar()->all();
+ $cookieStrings = array_map('strval', $cookies);
+ $this->debugSection('Cookie Jar', $cookieStrings);
}
- private function getBaseUrl()
+ /**
+ * @param $url
+ * @throws ModuleException
+ */
+ protected function debugResponse($url)
{
- return $this->baseUrl;
+ $this->debugSection('Page', $url);
+ $this->debugSection('Response', $this->getResponseStatusCode());
+ $this->debugSection('Request Cookies', $this->getRunningClient()->getInternalRequest()->getCookies());
+ $this->debugSection('Response Headers', $this->getRunningClient()->getInternalResponse()->getHeaders());
}
- private function retrieveBaseUrl()
+ protected function filterByAttributes(Crawler $nodes, array $attributes)
{
- $baseUrl = '';
-
- $baseHref = $this->crawler->filter('base');
- if (count($baseHref) > 0) {
- $baseUrl = $baseHref->getNode(0)->getAttribute('href');
- }
- if ($baseUrl === '') {
- $baseUrl = $this->_getCurrentUri();
+ foreach ($attributes as $attr => $val) {
+ $nodes = $nodes->reduce(
+ static function (Crawler $node) use ($attr, $val) {
+ return $node->attr($attr) === $val;
+ }
+ );
}
- return $this->getAbsoluteUrlFor($baseUrl);
+ return $nodes;
}
- public function see($text, $selector = null)
+ /**
+ * @param $locator
+ * @return Crawler
+ * @throws ModuleException
+ */
+ protected function filterByCSS($locator)
{
- if (!$selector) {
- $this->assertPageContains($text);
- return;
+ if (!Locator::isCSS($locator)) {
+ throw new MalformedLocatorException($locator, 'css');
}
-
- $nodes = $this->match($selector);
- $this->assertDomContains($nodes, $this->stringifySelector($selector), $text);
+ return $this->getCrawler()->filter($locator);
}
- public function dontSee($text, $selector = null)
+ /**
+ * @param $locator
+ * @return Crawler
+ * @throws ModuleException
+ */
+ protected function filterByXPath($locator)
{
- if (!$selector) {
- $this->assertPageNotContains($text);
- return;
+ if (!Locator::isXPath($locator)) {
+ throw new MalformedLocatorException($locator, 'xpath');
}
-
- $nodes = $this->match($selector);
- $this->assertDomNotContains($nodes, $this->stringifySelector($selector), $text);
+ return $this->getCrawler()->filterXPath($locator);
}
- public function seeInSource($raw)
+ /**
+ * Returns an absolute URL for the passed URI with the current URL
+ * as the base path.
+ *
+ * @param string $uri the absolute or relative URI
+ * @return string the absolute URL
+ * @throws TestRuntimeException|ModuleException if either the current
+ * URL or the passed URI can't be parsed
+ */
+ protected function getAbsoluteUrlFor($uri)
{
- $this->assertPageSourceContains($raw);
+ $currentUrl = $this->getRunningClient()->getHistory()->current()->getUri();
+ if (empty($uri) || strpos($uri, '#') === 0) {
+ return $currentUrl;
+ }
+ return Uri::mergeUrls($currentUrl, $uri);
}
- public function dontSeeInSource($raw)
+ /**
+ * @param $field
+ * @return object|Crawler
+ * @throws ModuleException
+ */
+ protected function getFieldByLabelOrCss($field)
{
- $this->assertPageSourceNotContains($raw);
+ $input = $this->getFieldsByLabelOrCss($field);
+ return $input->first();
}
- public function seeLink($text, $url = null)
+ /**
+ * @param $field
+ *
+ * @return Crawler
+ * @throws ModuleException
+ */
+ protected function getFieldsByLabelOrCss($field)
{
- $crawler = $this->getCrawler()->selectLink($text);
- if ($crawler->count() === 0) {
- $this->fail("No links containing text '$text' were found in page " . $this->_getCurrentUri());
+ if (is_array($field)) {
+ $input = $this->strictMatch($field);
+ if (!count($input)) {
+ throw new ElementNotFound($field);
+ }
+ return $input;
}
- if ($url) {
- $crawler = $crawler->filterXPath(sprintf('.//a[substring(@href, string-length(@href) - string-length(%1$s) + 1)=%1$s]', Crawler::xpathLiteral($url)));
- if ($crawler->count() === 0) {
- $this->fail("No links containing text '$text' and URL '$url' were found in page " . $this->_getCurrentUri());
+
+ // by label
+ $label = $this->strictMatch(['xpath' => sprintf('.//label[descendant-or-self::node()[text()[normalize-space()=%s]]]', Crawler::xpathLiteral($field))]);
+ if (count($label)) {
+ $label = $label->first();
+ if ($label->attr('for')) {
+ $input = $this->strictMatch(['id' => $label->attr('for')]);
+ } else {
+ $input = $this->strictMatch(['xpath' => sprintf('.//label[descendant-or-self::node()[text()[normalize-space()=%s]]]//input', Crawler::xpathLiteral($field))]);
}
}
- $this->assertTrue(true);
- }
- public function dontSeeLink($text, $url = '')
- {
- $crawler = $this->getCrawler()->selectLink($text);
- if (!$url && $crawler->count() > 0) {
- $this->fail("Link containing text '$text' was found in page " . $this->_getCurrentUri());
+ // by name
+ if (!isset($input)) {
+ $input = $this->strictMatch(['name' => $field]);
}
- $crawler = $crawler->filterXPath(
- sprintf('.//a[substring(@href, string-length(@href) - string-length(%1$s) + 1)=%1$s]',
- Crawler::xpathLiteral($url))
- );
- if ($crawler->count() > 0) {
- $this->fail("Link containing text '$text' and URL '$url' was found in page " . $this->_getCurrentUri());
+
+ // by CSS and XPath
+ if (!count($input)) {
+ $input = $this->match($field);
+ }
+
+ if (!count($input)) {
+ throw new ElementNotFound($field, 'Form field by Label or CSS');
}
+
+ return $input;
}
/**
- * @return string
+ * Returns the DomCrawler\Form object for the form pointed to by
+ * $node or its closes form parent.
+ *
+ * @param Crawler $node
+ * @return Form
* @throws ModuleException
*/
- public function _getCurrentUri()
+ protected function getFormFor(Crawler $node)
{
- return Uri::retrieveUri($this->getRunningClient()->getHistory()->current()->getUri());
- }
+ if (strcasecmp($node->first()->getNode(0)->tagName, 'form') === 0) {
+ $form = $node->first();
+ } else {
+ $form = $node->parents()->filter('form')->first();
+ }
+ if (!$form) {
+ $this->fail('The selected node is not a form and does not have a form ancestor.');
+ }
- public function seeInCurrentUrl($uri)
- {
- $this->assertStringContainsString($uri, $this->_getCurrentUri());
+ $identifier = $form->attr('id') ?: $form->attr('action');
+ if (!isset($this->forms[$identifier])) {
+ $this->forms[$identifier] = $this->getFormFromCrawler($form);
+ }
+ return $this->forms[$identifier];
}
- public function dontSeeInCurrentUrl($uri)
+ /**
+ * Returns the form action's absolute URL.
+ *
+ * @param Crawler $form
+ * @return string
+ * @throws TestRuntimeException|ModuleException if either the current
+ * URL or the URI of the form's action can't be parsed
+ */
+ protected function getFormUrl(Crawler $form)
{
- $this->assertStringNotContainsString($uri, $this->_getCurrentUri());
+ $action = $form->form()->getUri();
+ return $this->getAbsoluteUrlFor($action);
}
- public function seeCurrentUrlEquals($uri)
+ /**
+ * Returns an array of name => value pairs for the passed form.
+ *
+ * For form fields containing a name ending in [], an array is
+ * created out of all field values with the given name.
+ *
+ * @param Form $form
+ * @return array an array of name => value pairs
+ */
+ protected function getFormValuesFor(Form $form)
{
- $this->assertEquals(rtrim($uri, '/'), rtrim($this->_getCurrentUri(), '/'));
- }
-
- public function dontSeeCurrentUrlEquals($uri)
- {
- $this->assertNotEquals(rtrim($uri, '/'), rtrim($this->_getCurrentUri(), '/'));
- }
-
- public function seeCurrentUrlMatches($uri)
- {
- $this->assertRegExp($uri, $this->_getCurrentUri());
- }
-
- public function dontSeeCurrentUrlMatches($uri)
- {
- $this->assertNotRegExp($uri, $this->_getCurrentUri());
- }
-
- public function grabFromCurrentUrl($uri = null)
- {
- if (!$uri) {
- return $this->_getCurrentUri();
- }
- $matches = [];
- $res = preg_match($uri, $this->_getCurrentUri(), $matches);
- if (!$res) {
- $this->fail("Couldn't match $uri in " . $this->_getCurrentUri());
- }
- if (!isset($matches[1])) {
- $this->fail("Nothing to grab. A regex parameter required. Ex: '/user/(\\d+)'");
- }
- return $matches[1];
- }
-
- public function seeCheckboxIsChecked($checkbox)
- {
- $checkboxes = $this->getFieldsByLabelOrCss($checkbox);
- $this->assertDomContains($checkboxes->filter('input[checked=checked]'), 'checkbox');
- }
-
- public function dontSeeCheckboxIsChecked($checkbox)
- {
- $checkboxes = $this->getFieldsByLabelOrCss($checkbox);
- $this->assertEquals(0, $checkboxes->filter('input[checked=checked]')->count());
- }
-
- public function seeInField($field, $value)
- {
- $nodes = $this->getFieldsByLabelOrCss($field);
- $this->assert($this->proceedSeeInField($nodes, $value));
- }
-
- public function dontSeeInField($field, $value)
- {
- $nodes = $this->getFieldsByLabelOrCss($field);
- $this->assertNot($this->proceedSeeInField($nodes, $value));
- }
-
- public function seeInFormFields($formSelector, array $params)
- {
- $this->proceedSeeInFormFields($formSelector, $params, false);
- }
-
- public function dontSeeInFormFields($formSelector, array $params)
- {
- $this->proceedSeeInFormFields($formSelector, $params, true);
- }
-
- protected function proceedSeeInFormFields($formSelector, array $params, $assertNot)
- {
- $form = $this->match($formSelector)->first();
- if ($form->count() === 0) {
- throw new ElementNotFound($formSelector, 'Form');
- }
-
- $fields = [];
- foreach ($params as $name => $values) {
- $this->pushFormField($fields, $form, $name, $values);
- }
-
- foreach ($fields as list($field, $values)) {
- if (!is_array($values)) {
- $values = [$values];
+ $values = [];
+ $fields = $form->all();
+ foreach ($fields as $field) {
+ if ($field instanceof FileFormField || $field->isDisabled() || !$field->hasValue()) {
+ continue;
}
-
- foreach ($values as $value) {
- $ret = $this->proceedSeeInField($field, $value);
- if ($assertNot) {
- $this->assertNot($ret);
- } else {
- $this->assert($ret);
+ $fieldName = $this->getSubmissionFormFieldName($field->getName());
+ if (substr($field->getName(), -2) === '[]') {
+ if (!isset($values[$fieldName])) {
+ $values[$fieldName] = [];
}
+ $values[$fieldName][] = $field->getValue();
+ } else {
+ $values[$fieldName] = $field->getValue();
}
}
+ return $values;
}
/**
- * Map an array element passed to seeInFormFields to its corresponding field,
- * recursing through array values if the field is not found.
- *
- * @param array $fields The previously found fields.
- * @param Crawler $form The form in which to search for fields.
- * @param string $name The field's name.
- * @param mixed $values
- * @return void
- */
- protected function pushFormField(&$fields, $form, $name, $values)
- {
- $field = $form->filterXPath(sprintf('.//*[@name=%s]', Crawler::xpathLiteral($name)));
-
- if ($field->count()) {
- $fields[] = [$field, $values];
- } elseif (is_array($values)) {
- foreach ($values as $key => $value) {
- $this->pushFormField($fields, $form, "{$name}[$key]", $value);
- }
- } else {
- throw new ElementNotFound(
- sprintf('//*[@name=%s]', Crawler::xpathLiteral($name)),
- 'Form'
- );
- }
- }
-
- protected function proceedSeeInField(Crawler $fields, $value)
- {
- $testValues = $this->getValueAndTextFromField($fields);
- if (!is_array($testValues)) {
- $testValues = [$testValues];
- }
- if (is_bool($value) && $value === true && !empty($testValues)) {
- $value = reset($testValues);
- } elseif (empty($testValues)) {
- $testValues = [''];
- }
- return [
- 'Contains',
- (string)$value,
- $testValues,
- sprintf(
- 'Failed asserting that `%s` is in %s\'s value: %s',
- $value,
- $fields->getNode(0)->nodeName,
- var_export($testValues, true)
- )
- ];
- }
-
- /**
- * Get the values of a set of fields and also the texts of selected options.
- *
- * @param Crawler $nodes
- * @return array|mixed|string
+ * @param $requestParams
+ * @return array
*/
- protected function getValueAndTextFromField(Crawler $nodes)
+ protected function getFormPhpValues($requestParams)
{
- if ($nodes->filter('textarea')->count()) {
- return (new TextareaFormField($nodes->filter('textarea')->getNode(0)))->getValue();
- }
-
- $input = $nodes->filter('input');
- if ($input->count()) {
- return $this->getInputValue($input);
- }
-
- if ($nodes->filter('select')->count()) {
- $options = $nodes->filter('option[selected]');
- $values = [];
+ foreach ($requestParams as $name => $value) {
+ $qs = http_build_query([$name => $value], '', '&');
+ if (!empty($qs)) {
+ // If the field's name is of the form of "array[key]",
+ // we'll remove it from the request parameters
+ // and set the "array" key instead which will contain the actual array.
+ if (strpos($name, '[') && strpos($name, ']') > strpos($name, '[')) {
+ unset($requestParams[$name]);
+ }
- foreach ($options as $option) {
- $values[] = $option->getAttribute('value');
- $values[] = $option->textContent;
- $values[] = trim($option->textContent);
+ parse_str($qs, $expandedValue);
+ $varName = substr($name, 0, strlen(key($expandedValue)));
+ $requestParams = array_replace_recursive($requestParams, [$varName => current($expandedValue)]);
}
-
- return $values;
}
-
- $this->fail("Element $nodes is not a form field or does not contain a form field");
+ return $requestParams;
}
/**
@@ -819,655 +652,48 @@ protected function getInputValue($input)
}
/**
- * Strips out one pair of trailing square brackets from a field's
- * name.
- *
- * @param string $name the field name
- * @return string the name after stripping trailing square brackets
+ * @return string
*/
- protected function getSubmissionFormFieldName($name)
+ protected function getNormalizedResponseContent()
{
- if (substr($name, -2) === '[]') {
- return substr($name, 0, -2);
- }
- return $name;
- }
+ $content = $this->_getResponseContent();
+ // Since strip_tags has problems with JS code that contains
+ // an <= operator the script tags have to be removed manually first.
+ $content = preg_replace('##is', '', $content);
- /**
- * Replaces boolean values in $params with the corresponding field's
- * value for checkbox form fields.
- *
- * The function loops over all input checkbox fields, checking if a
- * corresponding key is set in $params. If it is, and the value is
- * boolean or an array containing booleans, the value(s) are
- * replaced in the array with the real value of the checkbox, and
- * the array is returned.
- *
- * @param Crawler $form the form to find checkbox elements
- * @param array $params the parameters to be submitted
- * @return array the $params array after replacing bool values
- */
- protected function setCheckboxBoolValues(Crawler $form, array $params)
- {
- $checkboxes = $form->filter('input[type=checkbox]');
- $chFoundByName = [];
- foreach ($checkboxes as $box) {
- $fieldName = $this->getSubmissionFormFieldName($box->getAttribute('name'));
- $pos = (!isset($chFoundByName[$fieldName])) ? 0 : $chFoundByName[$fieldName];
- $skip = !isset($params[$fieldName])
- || (!is_array($params[$fieldName]) && !is_bool($params[$fieldName]))
- || (is_array($params[$fieldName]) &&
- ($pos >= count($params[$fieldName]) || !is_bool($params[$fieldName][$pos]))
- );
+ $content = strip_tags($content);
+ $content = html_entity_decode($content, ENT_QUOTES);
+ $content = str_replace("\n", ' ', $content);
+ $content = preg_replace('/\s{2,}/', ' ', $content);
- if ($skip) {
- continue;
- }
- $values = $params[$fieldName];
- if ($values === true) {
- $params[$fieldName] = $box->hasAttribute('value') ? $box->getAttribute('value') : 'on';
- $chFoundByName[$fieldName] = $pos + 1;
- } elseif (is_array($values)) {
- if ($values[$pos] === true) {
- $params[$fieldName][$pos] = $box->hasAttribute('value') ? $box->getAttribute('value') : 'on';
- $chFoundByName[$fieldName] = $pos + 1;
- } else {
- array_splice($params[$fieldName], $pos, 1);
- }
- } else {
- unset($params[$fieldName]);
- }
- }
- return $params;
+ return $content;
}
/**
- * Submits the form currently selected in the passed Crawler, after
- * setting any values passed in $params and setting the value of the
- * passed button name.
- *
- * @param Crawler $frmCrawl the form to submit
- * @param array $params additional parameter values to set on the
- * form
- * @param string $button the name of a submit button in the form
+ * @return int|string
+ * @throws ModuleException
*/
- protected function proceedSubmitForm(Crawler $frmCrawl, array $params, $button = null)
- {
- $url = null;
- $form = $this->getFormFor($frmCrawl);
- $defaults = $this->getFormValuesFor($form);
- $merged = array_merge($defaults, $params);
- $requestParams = $this->setCheckboxBoolValues($frmCrawl, $merged);
-
- if (!empty($button)) {
- $btnCrawl = $frmCrawl->filterXPath(sprintf(
- '//*[not(@disabled) and @type="submit" and @name=%s]',
- Crawler::xpathLiteral($button)
- ));
- if (count($btnCrawl)) {
- $requestParams[$button] = $btnCrawl->attr('value');
- $formaction = $btnCrawl->attr('formaction');
- if ($formaction) {
- $url = $formaction;
- }
- }
- }
-
- if (!$url) {
- $url = $this->getFormUrl($frmCrawl);
- }
-
- if (strcasecmp($form->getMethod(), 'GET') === 0) {
- $url = Uri::mergeUrls($url, '?' . http_build_query($requestParams));
- }
-
- $url = preg_replace('/#.*/', '', $url);
-
- $this->debugSection('Uri', $url);
- $this->debugSection('Method', $form->getMethod());
- $this->debugSection('Parameters', $requestParams);
-
- $requestParams= $this->getFormPhpValues($requestParams);
-
- $this->crawler = $this->clientRequest(
- $form->getMethod(),
- $url,
- $requestParams,
- $form->getPhpFiles()
- );
- $this->forms = [];
- }
-
- public function submitForm($selector, array $params, $button = null)
- {
- $form = $this->match($selector)->first();
- if (!count($form)) {
- throw new ElementNotFound($this->stringifySelector($selector), 'Form');
- }
- $this->proceedSubmitForm($form, $params, $button);
- }
-
- /**
- * Returns an absolute URL for the passed URI with the current URL
- * as the base path.
- *
- * @param string $uri the absolute or relative URI
- * @return string the absolute URL
- * @throws \Codeception\Exception\TestRuntimeException if either the current
- * URL or the passed URI can't be parsed
- */
- protected function getAbsoluteUrlFor($uri)
- {
- $currentUrl = $this->getRunningClient()->getHistory()->current()->getUri();
- if (empty($uri) || strpos($uri, '#') === 0) {
- return $currentUrl;
- }
- return Uri::mergeUrls($currentUrl, $uri);
- }
-
- /**
- * Returns the form action's absolute URL.
- *
- * @param \Symfony\Component\DomCrawler\Crawler $form
- * @return string
- * @throws \Codeception\Exception\TestRuntimeException if either the current
- * URL or the URI of the form's action can't be parsed
- */
- protected function getFormUrl(Crawler $form)
- {
- $action = $form->form()->getUri();
- return $this->getAbsoluteUrlFor($action);
- }
-
- /**
- * Returns a crawler Form object for the form pointed to by the
- * passed Crawler.
- *
- * The returned form is an independent Crawler created to take care
- * of the following issues currently experienced by Crawler's form
- * object:
- * - input fields disabled at a higher level (e.g. by a surrounding
- * fieldset) still return values
- * - Codeception expects an empty value to match an unselected
- * select box.
- *
- * The function clones the crawler's node and creates a new crawler
- * because it destroys or adds to the DOM for the form to achieve
- * the desired functionality. Other functions simply querying the
- * DOM wouldn't expect them.
- *
- * @param Crawler $form the form
- * @return Form
- */
- private function getFormFromCrawler(Crawler $form)
- {
- $fakeDom = new \DOMDocument();
- $fakeDom->appendChild($fakeDom->importNode($form->getNode(0), true));
- $node = $fakeDom->documentElement;
- $action = (string)$this->getFormUrl($form);
- $cloned = new Crawler($node, $action, $this->getBaseUrl());
- $shouldDisable = $cloned->filter(
- 'input:disabled:not([disabled]),select option:disabled,select optgroup:disabled option:not([disabled]),textarea:disabled:not([disabled]),select:disabled:not([disabled])'
- );
- foreach ($shouldDisable as $field) {
- $field->parentNode->removeChild($field);
- }
- return $cloned->form();
- }
-
- /**
- * Returns the DomCrawler\Form object for the form pointed to by
- * $node or its closes form parent.
- *
- * @param \Symfony\Component\DomCrawler\Crawler $node
- * @return \Symfony\Component\DomCrawler\Form
- */
- protected function getFormFor(Crawler $node)
- {
- if (strcasecmp($node->first()->getNode(0)->tagName, 'form') === 0) {
- $form = $node->first();
- } else {
- $form = $node->parents()->filter('form')->first();
- }
- if (!$form) {
- $this->fail('The selected node is not a form and does not have a form ancestor.');
- }
-
- $identifier = $form->attr('id') ?: $form->attr('action');
- if (!isset($this->forms[$identifier])) {
- $this->forms[$identifier] = $this->getFormFromCrawler($form);
- }
- return $this->forms[$identifier];
- }
-
- /**
- * Returns an array of name => value pairs for the passed form.
- *
- * For form fields containing a name ending in [], an array is
- * created out of all field values with the given name.
- *
- * @param \Symfony\Component\DomCrawler\Form the form
- * @return array an array of name => value pairs
- */
- protected function getFormValuesFor(Form $form)
- {
- $values = [];
- $fields = $form->all();
- foreach ($fields as $field) {
- if ($field instanceof FileFormField || $field->isDisabled() || !$field->hasValue()) {
- continue;
- }
- $fieldName = $this->getSubmissionFormFieldName($field->getName());
- if (substr($field->getName(), -2) === '[]') {
- if (!isset($values[$fieldName])) {
- $values[$fieldName] = [];
- }
- $values[$fieldName][] = $field->getValue();
- } else {
- $values[$fieldName] = $field->getValue();
- }
- }
- return $values;
- }
-
- public function fillField($field, $value)
- {
- $input = $this->getFieldByLabelOrCss($field);
- $form = $this->getFormFor($input);
- $name = $input->attr('name');
-
- $dynamicField = $input->getNode(0)->tagName === 'textarea'
- ? new TextareaFormField($input->getNode(0))
- : new InputFormField($input->getNode(0));
- $formField = $this->matchFormField($name, $form, $dynamicField);
- $formField->setValue($value);
- $input->getNode(0)->setAttribute('value', htmlspecialchars($value));
- if ($input->getNode(0)->tagName === 'textarea') {
- $input->getNode(0)->nodeValue = htmlspecialchars($value);
- }
- }
-
- /**
- * @param $field
- *
- * @return \Symfony\Component\DomCrawler\Crawler
- */
- protected function getFieldsByLabelOrCss($field)
- {
- if (is_array($field)) {
- $input = $this->strictMatch($field);
- if (!count($input)) {
- throw new ElementNotFound($field);
- }
- return $input;
- }
-
- // by label
- $label = $this->strictMatch(['xpath' => sprintf('.//label[descendant-or-self::node()[text()[normalize-space()=%s]]]', Crawler::xpathLiteral($field))]);
- if (count($label)) {
- $label = $label->first();
- if ($label->attr('for')) {
- $input = $this->strictMatch(['id' => $label->attr('for')]);
- } else {
- $input = $this->strictMatch(['xpath' => sprintf('.//label[descendant-or-self::node()[text()[normalize-space()=%s]]]//input', Crawler::xpathLiteral($field))]);
- }
- }
-
- // by name
- if (!isset($input)) {
- $input = $this->strictMatch(['name' => $field]);
- }
-
- // by CSS and XPath
- if (!count($input)) {
- $input = $this->match($field);
- }
-
- if (!count($input)) {
- throw new ElementNotFound($field, 'Form field by Label or CSS');
- }
-
- return $input;
- }
-
- protected function getFieldByLabelOrCss($field)
- {
- $input = $this->getFieldsByLabelOrCss($field);
- return $input->first();
- }
-
- public function selectOption($select, $option)
- {
- $field = $this->getFieldByLabelOrCss($select);
- $form = $this->getFormFor($field);
- $fieldName = $this->getSubmissionFormFieldName($field->attr('name'));
-
- if (is_array($option)) {
- if (!isset($option[0])) { // strict option locator
- $form[$fieldName]->select($this->matchOption($field, $option));
- codecept_debug($option);
- return;
- }
- $options = [];
- foreach ($option as $opt) {
- $options[] = $this->matchOption($field, $opt);
- }
- $form[$fieldName]->select($options);
- return;
- }
-
- $dynamicField = new ChoiceFormField($field->getNode(0));
- $formField = $this->matchFormField($fieldName, $form, $dynamicField);
- $selValue = $this->matchOption($field, $option);
-
- if (is_array($formField)) {
- foreach ($formField as $field) {
- $values = $field->availableOptionValues();
- foreach ($values as $val) {
- if ($val === $option) {
- $field->select($selValue);
- return;
- }
- }
- }
- return;
- }
-
- $formField->select($this->matchOption($field, $option));
- }
-
- protected function matchOption(Crawler $field, $option)
- {
- if (isset($option['value'])) {
- return $option['value'];
- }
- if (isset($option['text'])) {
- $option = $option['text'];
- }
- $options = $field->filterXPath(sprintf('//option[text()=normalize-space("%s")]|//input[@type="radio" and @value=normalize-space("%s")]', $option, $option));
- if ($options->count()) {
- $firstMatchingDomNode = $options->getNode(0);
- if ($firstMatchingDomNode->tagName === 'option') {
- $firstMatchingDomNode->setAttribute('selected', 'selected');
- } else {
- $firstMatchingDomNode->setAttribute('checked', 'checked');
- }
- $valueAttribute = $options->first()->attr('value');
- //attr() returns null when option has no value attribute
- if ($valueAttribute !== null) {
- return $valueAttribute;
- }
- return $options->first()->text();
- }
- return $option;
- }
-
- public function checkOption($option)
- {
- $this->proceedCheckOption($option)->tick();
- }
-
- public function uncheckOption($option)
- {
- $this->proceedCheckOption($option)->untick();
- }
-
- /**
- * @param $option
- * @return ChoiceFormField
- */
- protected function proceedCheckOption($option)
- {
- $form = $this->getFormFor($field = $this->getFieldByLabelOrCss($option));
- $name = $field->attr('name');
-
- if ($field->getNode(0) === null) {
- throw new TestRuntimeException("Form field $name is not located");
- }
- // If the name is an array than we compare objects to find right checkbox
- $formField = $this->matchFormField($name, $form, new ChoiceFormField($field->getNode(0)));
- $field->getNode(0)->setAttribute('checked', 'checked');
- if (!$formField instanceof ChoiceFormField) {
- throw new TestRuntimeException("Form field $name is not a checkable");
- }
- return $formField;
- }
-
- public function attachFile($field, $filename)
- {
- $form = $this->getFormFor($field = $this->getFieldByLabelOrCss($field));
- $filePath = codecept_data_dir() . $filename;
- if (!file_exists($filePath)) {
- throw new \InvalidArgumentException("File does not exist: $filePath");
- }
- if (!is_readable($filePath)) {
- throw new \InvalidArgumentException("File is not readable: $filePath");
- }
-
- $name = $field->attr('name');
- $formField = $this->matchFormField($name, $form, new FileFormField($field->getNode(0)));
- if (is_array($formField)) {
- $this->fail("Field $name is ignored on upload, field $name is treated as array.");
- }
-
- $formField->upload($filePath);
- }
-
- /**
- * If your page triggers an ajax request, you can perform it manually.
- * This action sends a GET ajax request with specified params.
- *
- * See ->sendAjaxPostRequest for examples.
- *
- * @param $uri
- * @param $params
- */
- public function sendAjaxGetRequest($uri, $params = [])
- {
- $this->sendAjaxRequest('GET', $uri, $params);
- }
-
- /**
- * If your page triggers an ajax request, you can perform it manually.
- * This action sends a POST ajax request with specified params.
- * Additional params can be passed as array.
- *
- * Example:
- *
- * Imagine that by clicking checkbox you trigger ajax request which updates user settings.
- * We emulate that click by running this ajax request manually.
- *
- * ``` php
- * sendAjaxPostRequest('/updateSettings', array('notifications' => true)); // POST
- * $I->sendAjaxGetRequest('/updateSettings', array('notifications' => true)); // GET
- *
- * ```
- *
- * @param $uri
- * @param $params
- */
- public function sendAjaxPostRequest($uri, $params = [])
- {
- $this->sendAjaxRequest('POST', $uri, $params);
- }
-
- /**
- * If your page triggers an ajax request, you can perform it manually.
- * This action sends an ajax request with specified method and params.
- *
- * Example:
- *
- * You need to perform an ajax request specifying the HTTP method.
- *
- * ``` php
- * sendAjaxRequest('PUT', '/posts/7', array('title' => 'new title'));
- *
- * ```
- *
- * @param $method
- * @param $uri
- * @param $params
- */
- public function sendAjaxRequest($method, $uri, $params = [])
- {
- $this->clientRequest($method, $uri, $params, [], ['HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'], null, false);
- }
-
- /**
- * @param $url
- */
- protected function debugResponse($url)
- {
- $this->debugSection('Page', $url);
- $this->debugSection('Response', $this->getResponseStatusCode());
- $this->debugSection('Request Cookies', $this->getRunningClient()->getInternalRequest()->getCookies());
- $this->debugSection('Response Headers', $this->getRunningClient()->getInternalResponse()->getHeaders());
- }
-
- public function makeHtmlSnapshot($name = null)
- {
- if (empty($name)) {
- $name = uniqid(date("Y-m-d_H-i-s_"), true);
- }
- $debugDir = codecept_output_dir() . 'debug';
- if (!is_dir($debugDir)) {
- mkdir($debugDir, 0777);
- }
- $fileName = $debugDir . DIRECTORY_SEPARATOR . $name . '.html';
-
- $this->_savePageSource($fileName);
- $this->debugSection('Snapshot Saved', "file://$fileName");
- }
-
- public function _getResponseStatusCode()
- {
- return $this->getResponseStatusCode();
- }
-
- protected function getResponseStatusCode()
+ protected function getResponseStatusCode()
{
// depending on Symfony version
$response = $this->getRunningClient()->getInternalResponse();
if (method_exists($response, 'getStatusCode')) {
- return $response->getStatusCode();
- }
- if (method_exists($response, 'getStatus')) {
- return $response->getStatus();
- }
- return "N/A";
- }
-
- /**
- * @param $selector
- *
- * @return Crawler
- */
- protected function match($selector)
- {
- if (is_array($selector)) {
- return $this->strictMatch($selector);
- }
-
- if (Locator::isCSS($selector)) {
- return $this->getCrawler()->filter($selector);
- }
- if (Locator::isXPath($selector)) {
- return $this->getCrawler()->filterXPath($selector);
- }
- throw new MalformedLocatorException($selector, 'XPath or CSS');
- }
-
- /**
- * @param array $by
- * @throws TestRuntimeException
- * @return Crawler
- */
- protected function strictMatch(array $by)
- {
- $type = key($by);
- $locator = $by[$type];
- switch ($type) {
- case 'id':
- return $this->filterByCSS("#$locator");
- case 'name':
- return $this->filterByXPath(sprintf('.//*[@name=%s]', Crawler::xpathLiteral($locator)));
- case 'css':
- return $this->filterByCSS($locator);
- case 'xpath':
- return $this->filterByXPath($locator);
- case 'link':
- return $this->filterByXPath(sprintf('.//a[.=%s or contains(./@title, %s)]', Crawler::xpathLiteral($locator), Crawler::xpathLiteral($locator)));
- case 'class':
- return $this->filterByCSS(".$locator");
- default:
- throw new TestRuntimeException(
- "Locator type '$by' is not defined. Use either: xpath, css, id, link, class, name"
- );
- }
- }
-
- protected function filterByAttributes(Crawler $nodes, array $attributes)
- {
- foreach ($attributes as $attr => $val) {
- $nodes = $nodes->reduce(
- static function (Crawler $node) use ($attr, $val) {
- return $node->attr($attr) === $val;
- }
- );
- }
- return $nodes;
- }
-
- public function grabTextFrom($cssOrXPathOrRegex)
- {
- if (@preg_match($cssOrXPathOrRegex, $this->client->getInternalResponse()->getContent(), $matches)) {
- return $matches[1];
- }
- $nodes = $this->match($cssOrXPathOrRegex);
- if ($nodes->count()) {
- return $nodes->first()->text();
- }
- throw new ElementNotFound($cssOrXPathOrRegex, 'Element that matches CSS or XPath or Regex');
- }
-
- public function grabAttributeFrom($cssOrXpath, $attribute)
- {
- $nodes = $this->match($cssOrXpath);
- if (!$nodes->count()) {
- throw new ElementNotFound($cssOrXpath, 'Element that matches CSS or XPath');
+ return $response->getStatusCode();
}
- return $nodes->first()->attr($attribute);
- }
-
- public function grabMultiple($cssOrXpath, $attribute = null)
- {
- $result = [];
- $nodes = $this->match($cssOrXpath);
-
- foreach ($nodes as $node) {
- if ($attribute !== null) {
- $result[] = $node->getAttribute($attribute);
- } else {
- $result[] = $node->textContent;
- }
+ if (method_exists($response, 'getStatus')) {
+ return $response->getStatus();
}
- return $result;
+ return "N/A";
}
/**
- * @param $field
+ * Get the values of a set of fields and also the texts of selected options.
*
- * @return array|mixed|null|string
+ * @param Crawler $nodes
+ * @return array|mixed|string
*/
- public function grabValueFrom($field)
+ protected function getValueAndTextFromField(Crawler $nodes)
{
- $nodes = $this->match($field);
- if (!$nodes->count()) {
- throw new ElementNotFound($field, 'Field');
- }
-
if ($nodes->filter('textarea')->count()) {
return (new TextareaFormField($nodes->filter('textarea')->getNode(0)))->getValue();
}
@@ -1478,16 +704,13 @@ public function grabValueFrom($field)
}
if ($nodes->filter('select')->count()) {
- $field = new ChoiceFormField($nodes->filter('select')->getNode(0));
$options = $nodes->filter('option[selected]');
$values = [];
foreach ($options as $option) {
$values[] = $option->getAttribute('value');
- }
-
- if (!$field->isMultiple()) {
- return reset($values);
+ $values[] = $option->textContent;
+ $values[] = trim($option->textContent);
}
return $values;
@@ -1496,142 +719,108 @@ public function grabValueFrom($field)
$this->fail("Element $nodes is not a form field or does not contain a form field");
}
- public function setCookie($name, $val, array $params = [])
- {
- $cookies = $this->client->getCookieJar();
- $params = array_merge($this->defaultCookieParameters, $params);
-
- $expires = isset($params['expiry']) ? $params['expiry'] : null; // WebDriver compatibility
- $expires = isset($params['expires']) && !$expires ? $params['expires'] : null;
- $path = isset($params['path']) ? $params['path'] : null;
- $domain = isset($params['domain']) ? $params['domain'] : '';
- $secure = isset($params['secure']) ? $params['secure'] : false;
- $httpOnly = isset($params['httpOnly']) ? $params['httpOnly'] : true;
- $encodedValue = isset($params['encodedValue']) ? $params['encodedValue'] : false;
-
-
-
- $cookies->set(new Cookie($name, $val, $expires, $path, $domain, $secure, $httpOnly, $encodedValue));
- $this->debugCookieJar();
- }
-
- public function grabCookie($cookie, array $params = [])
- {
- $params = array_merge($this->defaultCookieParameters, $params);
- $this->debugCookieJar();
- $cookies = $this->getRunningClient()->getCookieJar()->get($cookie, $params['path'], $params['domain']);
- if (!$cookies) {
- return null;
- }
- return $cookies->getValue();
- }
-
/**
- * Grabs current page source code.
- *
- * @throws ModuleException if no page was opened.
+ * Strips out one pair of trailing square brackets from a field's
+ * name.
*
- * @return string Current page source code.
+ * @param string $name the field name
+ * @return string the name after stripping trailing square brackets
*/
- public function grabPageSource()
- {
- return $this->_getResponseContent();
- }
-
- public function seeCookie($cookie, array $params = [])
+ protected function getSubmissionFormFieldName($name)
{
- $params = array_merge($this->defaultCookieParameters, $params);
- $this->debugCookieJar();
- $this->assertNotNull($this->client->getCookieJar()->get($cookie, $params['path'], $params['domain']));
+ if (substr($name, -2) === '[]') {
+ return substr($name, 0, -2);
+ }
+ return $name;
}
- public function dontSeeCookie($cookie, array $params = [])
+ protected function isInternalDomain($domain)
{
- $params = array_merge($this->defaultCookieParameters, $params);
- $this->debugCookieJar();
- $this->assertNull($this->client->getCookieJar()->get($cookie, $params['path'], $params['domain']));
- }
+ if ($this->internalDomains === null) {
+ $this->internalDomains = $this->getInternalDomains();
+ }
- public function resetCookie($name, array $params = [])
- {
- $params = array_merge($this->defaultCookieParameters, $params);
- $this->client->getCookieJar()->expire($name, $params['path'], $params['domain']);
- $this->debugCookieJar();
+ foreach ($this->internalDomains as $pattern) {
+ if (preg_match($pattern, $domain)) {
+ return true;
+ }
+ }
+ return false;
}
- private function stringifySelector($selector)
+ /**
+ * @param $selector
+ *
+ * @return Crawler
+ * @throws ModuleException
+ */
+ protected function match($selector)
{
if (is_array($selector)) {
- return trim(json_encode($selector), '{}');
+ return $this->strictMatch($selector);
}
- return $selector;
- }
- public function seeElement($selector, $attributes = [])
- {
- $nodes = $this->match($selector);
- $selector = $this->stringifySelector($selector);
- if (!empty($attributes)) {
- $nodes = $this->filterByAttributes($nodes, $attributes);
- $selector .= "' with attribute(s) '" . trim(json_encode($attributes), '{}');
+ if (Locator::isCSS($selector)) {
+ return $this->getCrawler()->filter($selector);
}
- $this->assertDomContains($nodes, $selector);
- }
-
- public function dontSeeElement($selector, $attributes = [])
- {
- $nodes = $this->match($selector);
- $selector = $this->stringifySelector($selector);
- if (!empty($attributes)) {
- $nodes = $this->filterByAttributes($nodes, $attributes);
- $selector .= "' with attribute(s) '" . trim(json_encode($attributes), '{}');
+ if (Locator::isXPath($selector)) {
+ return $this->getCrawler()->filterXPath($selector);
}
- $this->assertDomNotContains($nodes, $selector);
+ throw new MalformedLocatorException($selector, 'XPath or CSS');
}
- public function seeNumberOfElements($selector, $expected)
+ /**
+ * @param $name
+ * @param $form
+ * @param $dynamicField
+ * @return FormField
+ */
+ protected function matchFormField($name, $form, $dynamicField)
{
- $counted = count($this->match($selector));
- if (is_array($expected)) {
- list($floor, $ceil) = $expected;
- $this->assertTrue(
- $floor <= $counted && $ceil >= $counted,
- 'Number of elements counted differs from expected range'
- );
- } else {
- $this->assertEquals(
- $expected,
- $counted,
- 'Number of elements counted differs from expected number'
- );
+ if (substr($name, -2) !== '[]') {
+ return $form[$name];
}
+ $name = substr($name, 0, -2);
+ /** @var $item FormField */
+ foreach ($form[$name] as $item) {
+ if ($item == $dynamicField) {
+ return $item;
+ }
+ }
+ throw new TestRuntimeException("None of form fields by {$name}[] were not matched");
}
- public function seeOptionIsSelected($selector, $optionText)
- {
- $selected = $this->matchSelectedOption($selector);
- $this->assertDomContains($selected, 'selected option');
- //If element is radio then we need to check value
- $value = $selected->getNode(0)->tagName === 'option'
- ? $selected->text()
- : $selected->getNode(0)->getAttribute('value');
- $this->assertEquals($optionText, $value);
- }
-
- public function dontSeeOptionIsSelected($selector, $optionText)
+ protected function matchOption(Crawler $field, $option)
{
- $selected = $this->matchSelectedOption($selector);
- if (!$selected->count()) {
- $this->assertEquals(0, $selected->count());
- return;
+ if (isset($option['value'])) {
+ return $option['value'];
+ }
+ if (isset($option['text'])) {
+ $option = $option['text'];
+ }
+ $options = $field->filterXPath(sprintf('//option[text()=normalize-space("%s")]|//input[@type="radio" and @value=normalize-space("%s")]', $option, $option));
+ if ($options->count()) {
+ $firstMatchingDomNode = $options->getNode(0);
+ if ($firstMatchingDomNode->tagName === 'option') {
+ $firstMatchingDomNode->setAttribute('selected', 'selected');
+ } else {
+ $firstMatchingDomNode->setAttribute('checked', 'checked');
+ }
+ $valueAttribute = $options->first()->attr('value');
+ //attr() returns null when option has no value attribute
+ if ($valueAttribute !== null) {
+ return $valueAttribute;
+ }
+ return $options->first()->text();
}
- //If element is radio then we need to check value
- $value = $selected->getNode(0)->tagName === 'option'
- ? $selected->text()
- : $selected->getNode(0)->getAttribute('value');
- $this->assertNotEquals($optionText, $value);
+ return $option;
}
+ /**
+ * @param $select
+ * @return object|Crawler
+ * @throws ModuleException
+ */
protected function matchSelectedOption($select)
{
$nodes = $this->getFieldsByLabelOrCss($select);
@@ -1643,245 +832,169 @@ protected function matchSelectedOption($select)
}
/**
- * Asserts that current page has 404 response status code.
- */
- public function seePageNotFound()
- {
- $this->seeResponseCodeIs(404);
- }
-
- /**
- * Checks that response code is equal to value provided.
- *
- * ```php
- * seeResponseCodeIs(200);
- *
- * // recommended \Codeception\Util\HttpCode
- * $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
- * ```
- *
- * @param $code
- */
- public function seeResponseCodeIs($code)
- {
- $failureMessage = sprintf(
- 'Expected HTTP Status Code: %s. Actual Status Code: %s',
- HttpCode::getDescription($code),
- HttpCode::getDescription($this->getResponseStatusCode())
- );
- $this->assertEquals($code, $this->getResponseStatusCode(), $failureMessage);
- }
-
- /**
- * Checks that response code is between a certain range. Between actually means [from <= CODE <= to]
- *
- * @param $from
- * @param $to
- */
- public function seeResponseCodeIsBetween($from, $to)
- {
- $failureMessage = sprintf(
- 'Expected HTTP Status Code between %s and %s. Actual Status Code: %s',
- HttpCode::getDescription($from),
- HttpCode::getDescription($to),
- HttpCode::getDescription($this->getResponseStatusCode())
- );
- $this->assertGreaterThanOrEqual($from, $this->getResponseStatusCode(), $failureMessage);
- $this->assertLessThanOrEqual($to, $this->getResponseStatusCode(), $failureMessage);
- }
-
- /**
- * Checks that response code is equal to value provided.
- *
- * ```php
- * dontSeeResponseCodeIs(200);
- *
- * // recommended \Codeception\Util\HttpCode
- * $I->dontSeeResponseCodeIs(\Codeception\Util\HttpCode::OK);
- * ```
- * @param $code
- */
- public function dontSeeResponseCodeIs($code)
- {
- $failureMessage = sprintf(
- 'Expected HTTP status code other than %s',
- HttpCode::getDescription($code)
- );
- $this->assertNotEquals($code, $this->getResponseStatusCode(), $failureMessage);
- }
-
- /**
- * Checks that the response code 2xx
- */
- public function seeResponseCodeIsSuccessful()
- {
- $this->seeResponseCodeIsBetween(200, 299);
- }
-
- /**
- * Checks that the response code 3xx
- */
- public function seeResponseCodeIsRedirection()
- {
- $this->seeResponseCodeIsBetween(300, 399);
- }
-
- /**
- * Checks that the response code is 4xx
- */
- public function seeResponseCodeIsClientError()
- {
- $this->seeResponseCodeIsBetween(400, 499);
- }
-
- /**
- * Checks that the response code is 5xx
+ * @param $option
+ * @return ChoiceFormField
+ * @throws ModuleException
*/
- public function seeResponseCodeIsServerError()
+ protected function proceedCheckOption($option)
{
- $this->seeResponseCodeIsBetween(500, 599);
- }
+ $form = $this->getFormFor($field = $this->getFieldByLabelOrCss($option));
+ $name = $field->attr('name');
- public function seeInTitle($title)
- {
- $nodes = $this->getCrawler()->filter('title');
- if (!$nodes->count()) {
- throw new ElementNotFound("", "Tag");
+ if ($field->getNode(0) === null) {
+ throw new TestRuntimeException("Form field $name is not located");
}
- $this->assertStringContainsString($title, $nodes->first()->text(), "page title contains $title");
- }
-
- public function dontSeeInTitle($title)
- {
- $nodes = $this->getCrawler()->filter('title');
- if (!$nodes->count()) {
- $this->assertTrue(true);
- return;
+ // If the name is an array than we compare objects to find right checkbox
+ $formField = $this->matchFormField($name, $form, new ChoiceFormField($field->getNode(0)));
+ $field->getNode(0)->setAttribute('checked', 'checked');
+ if (!$formField instanceof ChoiceFormField) {
+ throw new TestRuntimeException("Form field $name is not a checkable");
}
- $this->assertStringNotContainsString($title, $nodes->first()->text(), "page title contains $title");
- }
-
- protected function assertDomContains($nodes, $message, $text = '')
- {
- $constraint = new CrawlerConstraint($text, $this->_getCurrentUri());
- $this->assertThat($nodes, $constraint, $message);
- }
-
- protected function assertDomNotContains($nodes, $message, $text = '')
- {
- $constraint = new CrawlerNotConstraint($text, $this->_getCurrentUri());
- $this->assertThat($nodes, $constraint, $message);
- }
-
- protected function assertPageContains($needle, $message = '')
- {
- $constraint = new PageConstraint($needle, $this->_getCurrentUri());
- $this->assertThat(
- $this->getNormalizedResponseContent(),
- $constraint,
- $message
- );
- }
-
- protected function assertPageNotContains($needle, $message = '')
- {
- $constraint = new PageConstraint($needle, $this->_getCurrentUri());
- $this->assertThatItsNot(
- $this->getNormalizedResponseContent(),
- $constraint,
- $message
- );
- }
-
- protected function assertPageSourceContains($needle, $message = '')
- {
- $constraint = new PageConstraint($needle, $this->_getCurrentUri());
- $this->assertThat(
- $this->_getResponseContent(),
- $constraint,
- $message
- );
+ return $formField;
}
- protected function assertPageSourceNotContains($needle, $message = '')
+ protected function proceedSeeInField(Crawler $fields, $value)
{
- $constraint = new PageConstraint($needle, $this->_getCurrentUri());
- $this->assertThatItsNot(
- $this->_getResponseContent(),
- $constraint,
- $message
- );
+ $testValues = $this->getValueAndTextFromField($fields);
+ if (!is_array($testValues)) {
+ $testValues = [$testValues];
+ }
+ if (is_bool($value) && $value === true && !empty($testValues)) {
+ $value = reset($testValues);
+ } elseif (empty($testValues)) {
+ $testValues = [''];
+ }
+ return [
+ 'Contains',
+ (string)$value,
+ $testValues,
+ sprintf(
+ 'Failed asserting that `%s` is in %s\'s value: %s',
+ $value,
+ $fields->getNode(0)->nodeName,
+ var_export($testValues, true)
+ )
+ ];
}
/**
- * @param $name
- * @param $form
- * @param $dynamicField
- * @return FormField
+ * @param $formSelector
+ * @param array $params
+ * @param $assertNot
+ * @throws ModuleException
*/
- protected function matchFormField($name, $form, $dynamicField)
+ protected function proceedSeeInFormFields($formSelector, array $params, $assertNot)
{
- if (substr($name, -2) !== '[]') {
- return $form[$name];
+ $form = $this->match($formSelector)->first();
+ if ($form->count() === 0) {
+ throw new ElementNotFound($formSelector, 'Form');
}
- $name = substr($name, 0, -2);
- /** @var $item \Symfony\Component\DomCrawler\Field\FormField */
- foreach ($form[$name] as $item) {
- if ($item == $dynamicField) {
- return $item;
+
+ $fields = [];
+ foreach ($params as $name => $values) {
+ $this->pushFormField($fields, $form, $name, $values);
+ }
+
+ foreach ($fields as list($field, $values)) {
+ if (!is_array($values)) {
+ $values = [$values];
+ }
+
+ foreach ($values as $value) {
+ $ret = $this->proceedSeeInField($field, $value);
+ if ($assertNot) {
+ $this->assertNot($ret);
+ } else {
+ $this->assert($ret);
+ }
}
}
- throw new TestRuntimeException("None of form fields by {$name}[] were not matched");
}
/**
- * @param $locator
- * @return Crawler
+ * Submits the form currently selected in the passed Crawler, after
+ * setting any values passed in $params and setting the value of the
+ * passed button name.
+ *
+ * @param Crawler $frmCrawl the form to submit
+ * @param array $params additional parameter values to set on the
+ * form
+ * @param string $button the name of a submit button in the form
+ * @throws ExternalUrlException|ModuleException
*/
- protected function filterByCSS($locator)
+ protected function proceedSubmitForm(Crawler $frmCrawl, array $params, $button = null)
{
- if (!Locator::isCSS($locator)) {
- throw new MalformedLocatorException($locator, 'css');
+ $url = null;
+ $form = $this->getFormFor($frmCrawl);
+ $defaults = $this->getFormValuesFor($form);
+ $merged = array_merge($defaults, $params);
+ $requestParams = $this->setCheckboxBoolValues($frmCrawl, $merged);
+
+ if (!empty($button)) {
+ $btnCrawl = $frmCrawl->filterXPath(sprintf(
+ '//*[not(@disabled) and @type="submit" and @name=%s]',
+ Crawler::xpathLiteral($button)
+ ));
+ if (count($btnCrawl)) {
+ $requestParams[$button] = $btnCrawl->attr('value');
+ $formaction = $btnCrawl->attr('formaction');
+ if ($formaction) {
+ $url = $formaction;
+ }
+ }
}
- return $this->getCrawler()->filter($locator);
- }
- /**
- * @param $locator
- * @return Crawler
- */
- protected function filterByXPath($locator)
- {
- if (!Locator::isXPath($locator)) {
- throw new MalformedLocatorException($locator, 'xpath');
+ if (!$url) {
+ $url = $this->getFormUrl($frmCrawl);
}
- return $this->getCrawler()->filterXPath($locator);
+
+ if (strcasecmp($form->getMethod(), 'GET') === 0) {
+ $url = Uri::mergeUrls($url, '?' . http_build_query($requestParams));
+ }
+
+ $url = preg_replace('/#.*/', '', $url);
+
+ $this->debugSection('Uri', $url);
+ $this->debugSection('Method', $form->getMethod());
+ $this->debugSection('Parameters', $requestParams);
+
+ $requestParams= $this->getFormPhpValues($requestParams);
+
+ $this->crawler = $this->clientRequest(
+ $form->getMethod(),
+ $url,
+ $requestParams,
+ $form->getPhpFiles()
+ );
+ $this->forms = [];
}
/**
- * @param $requestParams
- * @return array
+ * Map an array element passed to seeInFormFields to its corresponding field,
+ * recursing through array values if the field is not found.
+ *
+ * @param array $fields The previously found fields.
+ * @param Crawler $form The form in which to search for fields.
+ * @param string $name The field's name.
+ * @param mixed $values
+ * @return void
*/
- protected function getFormPhpValues($requestParams)
+ protected function pushFormField(&$fields, $form, $name, $values)
{
- foreach ($requestParams as $name => $value) {
- $qs = http_build_query([$name => $value], '', '&');
- if (!empty($qs)) {
- // If the field's name is of the form of "array[key]",
- // we'll remove it from the request parameters
- // and set the "array" key instead which will contain the actual array.
- if (strpos($name, '[') && strpos($name, ']') > strpos($name, '[')) {
- unset($requestParams[$name]);
- }
+ $field = $form->filterXPath(sprintf('.//*[@name=%s]', Crawler::xpathLiteral($name)));
- parse_str($qs, $expandedValue);
- $varName = substr($name, 0, strlen(key($expandedValue)));
- $requestParams = array_replace_recursive($requestParams, [$varName => current($expandedValue)]);
+ if ($field->count()) {
+ $fields[] = [$field, $values];
+ } elseif (is_array($values)) {
+ foreach ($values as $key => $value) {
+ $this->pushFormField($fields, $form, "{$name}[$key]", $value);
}
+ } else {
+ throw new ElementNotFound(
+ sprintf('//*[@name=%s]', Crawler::xpathLiteral($name)),
+ 'Form'
+ );
}
- return $requestParams;
}
/**
@@ -1889,6 +1002,7 @@ protected function getFormPhpValues($requestParams)
* @param $maxRedirects
* @param $redirectCount
* @return mixed
+ * @throws ModuleException
*/
protected function redirectIfNecessary($result, $maxRedirects, $redirectCount)
{
@@ -1896,7 +1010,7 @@ protected function redirectIfNecessary($result, $maxRedirects, $redirectCount)
$statusCode = $this->getResponseStatusCode();
if ($locationHeader && $statusCode >= 300 && $statusCode < 400) {
if ($redirectCount === $maxRedirects) {
- throw new \LogicException(sprintf(
+ throw new LogicException(sprintf(
'The maximum number (%d) of redirections was reached.',
$maxRedirects
));
@@ -1913,75 +1027,80 @@ protected function redirectIfNecessary($result, $maxRedirects, $redirectCount)
}
/**
- * Switch to iframe or frame on the page.
- *
- * Example:
- * ``` html
- *
Bye warcraft
');
try {
$this->constraint->evaluate($nodes->filter('p'), 'selector');
- } catch (\PHPUnit\Framework\AssertionFailedError $fail) {
+ } catch (AssertionFailedError $fail) {
$this->assertStringContainsString(
"Failed asserting that any element by 'selector' on page /user",
$fail->getMessage()
@@ -44,7 +47,7 @@ public function testFailMessageResponseWhenMoreNodes()
$nodes = new Symfony\Component\DomCrawler\Crawler($html);
try {
$this->constraint->evaluate($nodes->filter('p'), 'selector');
- } catch (\PHPUnit\Framework\AssertionFailedError $fail) {
+ } catch (AssertionFailedError $fail) {
$this->assertStringContainsString(
"Failed asserting that any element by 'selector' on page /user",
$fail->getMessage()
@@ -63,7 +66,7 @@ public function testFailMessageResponseWithoutUrl()
$nodes = new Symfony\Component\DomCrawler\Crawler('
Bye world
Bye warcraft
');
try {
$this->constraint->evaluate($nodes->filter('p'), 'selector');
- } catch (\PHPUnit\Framework\AssertionFailedError $fail) {
+ } catch (AssertionFailedError $fail) {
$this->assertStringContainsString("Failed asserting that any element by 'selector'", $fail->getMessage());
$this->assertStringNotContainsString("Failed asserting that any element by 'selector' on page", $fail->getMessage());
return;
diff --git a/tests/unit/Codeception/Constraints/CrawlerNotConstraintTest.php b/tests/unit/Codeception/Constraints/CrawlerNotConstraintTest.php
index 500f177..1199740 100644
--- a/tests/unit/Codeception/Constraints/CrawlerNotConstraintTest.php
+++ b/tests/unit/Codeception/Constraints/CrawlerNotConstraintTest.php
@@ -1,8 +1,10 @@
Bye world
Bye warcraft
');
try {
$this->constraint->evaluate($nodes->filter('p'), 'selector');
- } catch (\PHPUnit\Framework\AssertionFailedError $fail) {
+ } catch (AssertionFailedError $fail) {
$this->assertStringContainsString("There was 'selector' element on page /user", $fail->getMessage());
$this->assertStringNotContainsString('+