diff --git a/composer.json b/composer.json
index b70c2fb6..df45450d 100644
--- a/composer.json
+++ b/composer.json
@@ -2,7 +2,7 @@
"name":"codeception/module-webdriver",
"description":"WebDriver module for Codeception",
"keywords":["codeception", "browser-testing", "acceptance-testing"],
- "homepage":"http://codeception.com/",
+ "homepage":"https://codeception.com/",
"type":"library",
"license":"MIT",
"authors":[
@@ -19,7 +19,9 @@
"minimum-stability": "RC",
"require": {
- "php": ">=5.6.0 <9.0",
+ "php": "^7.4 | ^8.0",
+ "ext-json": "*",
+ "ext-mbstring": "*",
"codeception/codeception": "^4.0",
"php-webdriver/webdriver": "^1.8.0"
},
diff --git a/readme.md b/readme.md
index 1ff2a2e5..50801ca2 100644
--- a/readme.md
+++ b/readme.md
@@ -1,4 +1,6 @@
-# WebDriver module for Codeception
+# Codeception Module WebDriver
+
+A WebDriver module for Codeception.
[![Chrome Tests](https://github.com/Codeception/module-webdriver/actions/workflows/webdriver-chrome.yml/badge.svg)](https://github.com/Codeception/module-webdriver/actions/workflows/webdriver-chrome.yml)
[![Chrome Headless Tests](https://github.com/Codeception/module-webdriver/actions/workflows/webdriver-chrome-headless.yml/badge.svg)](https://github.com/Codeception/module-webdriver/actions/workflows/webdriver-chrome-headless.yml)
@@ -7,7 +9,7 @@
## Installation
```
-composer require --dev "codeception/module-webdriver"
+composer require "codeception/module-webdriver" --dev
```
## Documentation
diff --git a/src/Codeception/Exception/ConnectionException.php b/src/Codeception/Exception/ConnectionException.php
index f0a81e9f..62e2d955 100644
--- a/src/Codeception/Exception/ConnectionException.php
+++ b/src/Codeception/Exception/ConnectionException.php
@@ -1,6 +1,11 @@
getModule('{{MODULE_NAME}}')->_saveScreenshot(codecept_output_dir().'screenshot_1.png');
* ```
* @api
- * @param $filename
*/
- public function _saveScreenshot($filename);
+ public function _saveScreenshot(string $filename);
}
diff --git a/src/Codeception/Lib/Interfaces/SessionSnapshot.php b/src/Codeception/Lib/Interfaces/SessionSnapshot.php
index 00ee66a8..74617d95 100644
--- a/src/Codeception/Lib/Interfaces/SessionSnapshot.php
+++ b/src/Codeception/Lib/Interfaces/SessionSnapshot.php
@@ -1,4 +1,5 @@
saveSessionSnapshot('login');
* }
- * ?>
* ```
*
- * @param $name
* @return mixed
*/
- public function saveSessionSnapshot($name);
+ public function saveSessionSnapshot(string $name);
/**
* Loads cookies from a saved snapshot.
@@ -41,18 +40,16 @@ public function saveSessionSnapshot($name);
*
* See [saveSessionSnapshot](#saveSessionSnapshot)
*
- * @param $name
* @return mixed
*/
- public function loadSessionSnapshot($name);
+ public function loadSessionSnapshot(string $name);
/**
* Deletes session snapshot.
*
* See [saveSessionSnapshot](#saveSessionSnapshot)
*
- * @param $name
* @return mixed
*/
- public function deleteSessionSnapshot($name);
+ public function deleteSessionSnapshot(string $name);
}
diff --git a/src/Codeception/Module/WebDriver.php b/src/Codeception/Module/WebDriver.php
index eaff6c1f..01a742a3 100644
--- a/src/Codeception/Module/WebDriver.php
+++ b/src/Codeception/Module/WebDriver.php
@@ -1,7 +1,10 @@
'http',
'host' => '127.0.0.1',
@@ -401,40 +414,61 @@ class WebDriver extends CodeceptionModule implements
'webdriver_proxy_port' => null,
];
- protected $wdHost;
+ protected ?string $wdHost = null;
+
+ /**
+ * @var mixed
+ */
protected $capabilities;
+
+ /**
+ * @var float|int|null
+ */
protected $connectionTimeoutInMs;
+
+ /**
+ * @var float|int|null
+ */
protected $requestTimeoutInMs;
+
protected $test;
- protected $sessions = [];
- protected $sessionSnapshots = [];
+
+ protected array $sessions = [];
+
+ protected array $sessionSnapshots = [];
+
+ /**
+ * @var mixed
+ */
protected $webdriverProxy;
- protected $webdriverProxyPort;
/**
- * @var RemoteWebDriver
+ * @var mixed
*/
- public $webDriver;
+ protected $webdriverProxyPort;
+
+ public ?RemoteWebDriver $webDriver = null;
/**
- * @var RemoteWebElement
+ * @var RemoteWebDriver|RemoteWebElement
*/
- protected $baseElement;
+ protected $baseElement = null;
- public function _requires()
+ public function _requires(): array
{
- return ['Facebook\WebDriver\Remote\RemoteWebDriver' => '"php-webdriver/webdriver": "^1.0.1"'];
+ return [RemoteWebDriver::class => '"php-webdriver/webdriver": "^1.0.1"'];
}
/**
- * @return RemoteWebElement
* @throws ModuleException
+ * @return RemoteWebDriver|RemoteWebElement|WebDriverSearchContext
*/
protected function getBaseElement()
{
if (!$this->baseElement) {
throw new ModuleException($this, "Page not loaded. Use `\$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it");
}
+
return $this->baseElement;
}
@@ -446,6 +480,7 @@ public function _initialize()
if ($proxy = $this->getProxy()) {
$this->capabilities[WebDriverCapabilityType::PROXY] = $proxy;
}
+
$this->connectionTimeoutInMs = $this->config['connection_timeout'] * 1000;
$this->requestTimeoutInMs = $this->config['request_timeout'] * 1000;
$this->webdriverProxy = $this->config['webdriver_proxy'];
@@ -495,23 +530,23 @@ public function _initialize()
* In this case, please ensure that `\Helper\Acceptance` is loaded before WebDriver so new capabilities could be applied.
*
* @api
- * @param \Closure $capabilityFunction
*/
- public function _capabilities(\Closure $capabilityFunction)
+ public function _capabilities(Closure $capabilityFunction): void
{
$this->capabilities = $capabilityFunction($this->capabilities);
}
- public function _conflicts()
+ public function _conflicts(): string
{
- return 'Codeception\Lib\Interfaces\Web';
+ return WebInterface::class;
}
public function _before(TestInterface $test)
{
- if (!isset($this->webDriver) && $this->config['start']) {
+ if ($this->webDriver === null && $this->config['start']) {
$this->_initializeSession();
}
+
$this->setBaseElement();
$test->getMetadata()->setCurrent(
@@ -533,15 +568,15 @@ public function _before(TestInterface $test)
* $this->getModule('WebDriver')->_restart(['browser' => $browser]); // reconfigure + restart
* ```
*
- * @param array $config
* @api
*/
- public function _restart($config = [])
+ public function _restart(array $config = []): void
{
$this->webDriver->quit();
if (!empty($config)) {
$this->_reconfigure($config);
}
+
$this->_initializeSession();
}
@@ -550,32 +585,34 @@ protected function onReconfigure()
$this->_initialize();
}
- protected function loadFirefoxProfile()
+ protected function loadFirefoxProfile(): void
{
if (!array_key_exists('firefox_profile', $this->config['capabilities'])) {
return;
}
$firefox_profile = $this->config['capabilities']['firefox_profile'];
- if (file_exists($firefox_profile) === false) {
+ if (!file_exists($firefox_profile)) {
throw new ModuleConfigException(
__CLASS__,
"Firefox profile does not exist under given path " . $firefox_profile
);
}
+
// Set firefox profile as capability
$this->capabilities['firefox_profile'] = file_get_contents($firefox_profile);
}
- protected function initialWindowSize()
+ protected function initialWindowSize(): void
{
if ($this->config['window_size'] == 'maximize') {
$this->maximizeWindow();
return;
}
- $size = explode('x', $this->config['window_size']);
+
+ $size = explode('x', (string) $this->config['window_size']);
if (count($size) == 2) {
- $this->resizeWindow(intval($size[0]), intval($size[1]));
+ $this->resizeWindow((int) $size[0], (int) $size[1]);
}
}
@@ -585,12 +622,13 @@ public function _after(TestInterface $test)
$this->stopAllSessions();
return;
}
- if ($this->config['clear_cookies'] && isset($this->webDriver)) {
+
+ if ($this->config['clear_cookies'] && $this->webDriver !== null) {
try {
$this->webDriver->manage()->deleteAllCookies();
- } catch (\Exception $e) {
+ } catch (Exception $exception) {
// may cause fatal errors when not handled
- $this->debug("Error, can't clean cookies after a test: " . $e->getMessage());
+ $this->debug("Error, can't clean cookies after a test: " . $exception->getMessage());
}
}
}
@@ -598,26 +636,25 @@ public function _after(TestInterface $test)
public function _failed(TestInterface $test, $fail)
{
$this->debugWebDriverLogs($test);
- $filename = preg_replace('~[^a-zA-Z0-9\x80-\xff]~', '.', Descriptor::getTestSignatureUnique($test));
+ $filename = preg_replace('#[^a-zA-Z0-9\x80-\xff]#', '.', Descriptor::getTestSignatureUnique($test));
$outputDir = codecept_output_dir();
$this->_saveScreenshot($report = $outputDir . mb_strcut($filename, 0, 245, 'utf-8') . '.fail.png');
$test->getMetadata()->addReport('png', $report);
$this->_savePageSource($report = $outputDir . mb_strcut($filename, 0, 244, 'utf-8') . '.fail.html');
$test->getMetadata()->addReport('html', $report);
- $this->debug("Screenshot and page source were saved into '$outputDir' dir");
+ $this->debug("Screenshot and page source were saved into '{$outputDir}' dir");
}
/**
* Print out latest Selenium Logs in debug mode
- *
- * @param \Codeception\TestInterface $test
*/
- public function debugWebDriverLogs(TestInterface $test = null)
+ public function debugWebDriverLogs(TestInterface $test = null): void
{
- if (!isset($this->webDriver)) {
+ if ($this->webDriver === null) {
$this->debug('WebDriver::debugWebDriverLogs method has been called when webDriver is not set');
return;
}
+
// don't show logs if log entries not set
if (!$this->config['debug_log_entries']) {
return;
@@ -636,6 +673,7 @@ public function debugWebDriverLogs(TestInterface $test = null)
$this->debugSection("Selenium {$logType} Logs", " EMPTY ");
continue;
}
+
$this->debugSection("Selenium {$logType} Logs", "\n" . $this->formatLogEntries($logEntries));
if ($logType === 'browser' && $this->config['log_js_errors']
@@ -644,7 +682,7 @@ public function debugWebDriverLogs(TestInterface $test = null)
$this->logJSErrors($test, $logEntries);
}
}
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->debug('Unable to retrieve Selenium logs : ' . $e->getMessage());
}
}
@@ -653,11 +691,8 @@ public function debugWebDriverLogs(TestInterface $test = null)
* Turns an array of log entries into a human-readable string.
* Each log entry is an array with the keys "timestamp", "level", and "message".
* See https://code.google.com/p/selenium/wiki/JsonWireProtocol#Log_Entry_JSON_Object
- *
- * @param array $logEntries
- * @return string
*/
- protected function formatLogEntries(array $logEntries)
+ protected function formatLogEntries(array $logEntries): string
{
$formattedLogs = '';
@@ -668,20 +703,18 @@ protected function formatLogEntries(array $logEntries)
'.' . ($logEntry['timestamp'] % 1000);
$formattedLogs .= "{$time} {$logEntry['level']} - {$logEntry['message']}\n";
}
+
return $formattedLogs;
}
/**
* Logs JavaScript errors as comments.
- *
- * @param ScenarioDriven $test
- * @param array $browserLogEntries
*/
- protected function logJSErrors(ScenarioDriven $test, array $browserLogEntries)
+ protected function logJSErrors(ScenarioDriven $test, array $browserLogEntries): void
{
foreach ($browserLogEntries as $logEntry) {
- if (true === isset($logEntry['level'])
- && true === isset($logEntry['message'])
+ if (isset($logEntry['level'])
+ && isset($logEntry['message'])
&& $this->isJSError($logEntry['level'], $logEntry['message'])
) {
// Timestamp is in milliseconds, but date() requires seconds.
@@ -696,12 +729,8 @@ protected function logJSErrors(ScenarioDriven $test, array $browserLogEntries)
/**
* Determines if the log entry is an error.
* The decision is made depending on browser and log-level.
- *
- * @param string $logEntryLevel
- * @param string $message
- * @return bool
*/
- protected function isJSError($logEntryLevel, $message)
+ protected function isJSError(string $logEntryLevel, string $message): bool
{
return
(
@@ -717,11 +746,12 @@ public function _afterSuite()
$this->stopAllSessions();
}
- protected function stopAllSessions()
+ protected function stopAllSessions(): void
{
foreach ($this->sessions as $session) {
$this->_closeSession($session);
}
+
$this->webDriver = null;
$this->baseElement = null;
}
@@ -729,8 +759,8 @@ protected function stopAllSessions()
public function amOnSubdomain($subdomain)
{
$url = $this->config['url'];
- $url = preg_replace('~(https?:\/\/)(.*\.)(.*\.)~', "$1$3", $url); // removing current subdomain
- $url = preg_replace('~(https?:\/\/)(.*)~', "$1$subdomain.$2", $url); // inserting new
+ $url = preg_replace('#(https?:\/\/)(.*\.)(.*\.)#', "$1$3", $url); // removing current subdomain
+ $url = preg_replace('#(https?:\/\/)(.*)#', sprintf('$1%s.$2', $subdomain), $url); // inserting new
$this->_reconfigure(['url' => $url]);
}
@@ -749,10 +779,11 @@ public function _getUrl()
"Module connection failure. The URL for client can't bre retrieved"
);
}
+
return $this->config['url'];
}
- protected function getProxy()
+ protected function getProxy(): ?array
{
$proxyConfig = [];
if ($this->config['http_proxy']) {
@@ -761,57 +792,65 @@ protected function getProxy()
$proxyConfig['httpProxy'] .= ':' . $this->config['http_proxy_port'];
}
}
+
if ($this->config['ssl_proxy']) {
$proxyConfig['sslProxy'] = $this->config['ssl_proxy'];
if ($this->config['ssl_proxy_port']) {
$proxyConfig['sslProxy'] .= ':' . $this->config['ssl_proxy_port'];
}
}
+
if (!empty($proxyConfig)) {
$proxyConfig['proxyType'] = 'manual';
return $proxyConfig;
}
+
return null;
}
/**
* Uri of currently opened page.
- * @return string
* @api
* @throws ModuleException
*/
- public function _getCurrentUri()
+ public function _getCurrentUri(): string
{
$url = $this->webDriver->getCurrentURL();
if ($url == 'about:blank' || strpos($url, 'data:') === 0) {
throw new ModuleException($this, 'Current url is blank, no page was opened');
}
+
return Uri::retrieveUri($url);
}
- public function _saveScreenshot($filename)
+ public function _saveScreenshot(string $filename)
{
- if (!isset($this->webDriver)) {
+ if ($this->webDriver === null) {
$this->debug('WebDriver::_saveScreenshot method has been called when webDriver is not set');
return;
}
+
try {
$this->webDriver->takeScreenshot($filename);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->debug('Unable to retrieve screenshot from Selenium : ' . $e->getMessage());
return;
}
}
- public function _saveElementScreenshot($selector, $filename)
+ /**
+ * @param WebDriverBy|array $selector
+ */
+ public function _saveElementScreenshot($selector, string $filename): void
{
- if (!isset($this->webDriver)) {
+ if ($this->webDriver === null) {
$this->debug('WebDriver::_saveElementScreenshot method has been called when webDriver is not set');
return;
}
+
try {
$this->matchFirstOrFail($this->webDriver, $selector)->takeElementScreenshot($filename);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->debug('Unable to retrieve element screenshot from Selenium : ' . $e->getMessage());
return;
}
@@ -824,17 +863,17 @@ public function _findElements($locator)
/**
* Saves HTML source of a page to a file
- * @param $filename
*/
public function _savePageSource($filename)
{
- if (!isset($this->webDriver)) {
+ if ($this->webDriver === null) {
$this->debug('WebDriver::_savePageSource method has been called when webDriver is not set');
return;
}
+
try {
file_put_contents($filename, $this->webDriver->getPageSource());
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->debug('Unable to retrieve source page from Selenium : ' . $e->getMessage());
}
}
@@ -850,21 +889,21 @@ public function _savePageSource($filename)
* $I->makeScreenshot();
* // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png
* ```
- *
- * @param $name
*/
- public function makeScreenshot($name = null)
+ public function makeScreenshot(string $name = null): void
{
if (empty($name)) {
$name = uniqid(date("Y-m-d_H-i-s_"));
}
+
$debugDir = codecept_log_dir() . 'debug';
if (!is_dir($debugDir)) {
- mkdir($debugDir, 0777);
+ mkdir($debugDir);
}
+
$screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png';
$this->_saveScreenshot($screenName);
- $this->debugSection('Screenshot Saved', "file://$screenName");
+ $this->debugSection('Screenshot Saved', "file://{$screenName}");
}
/**
@@ -879,20 +918,22 @@ public function makeScreenshot($name = null)
* // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png
* ```
*
- * @param $name
+ * @param WebDriverBy|array $selector
*/
- public function makeElementScreenshot($selector, $name = null)
+ public function makeElementScreenshot($selector, string $name = null): void
{
if (empty($name)) {
$name = uniqid(date("Y-m-d_H-i-s_"));
}
+
$debugDir = codecept_log_dir() . 'debug';
if (!is_dir($debugDir)) {
- mkdir($debugDir, 0777);
+ mkdir($debugDir);
}
+
$screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png';
$this->_saveElementScreenshot($selector, $screenName);
- $this->debugSection('Screenshot Saved', "file://$screenName");
+ $this->debugSection('Screenshot Saved', "file://{$screenName}");
}
public function makeHtmlSnapshot($name = null)
@@ -900,14 +941,16 @@ public function makeHtmlSnapshot($name = null)
if (empty($name)) {
$name = uniqid(date("Y-m-d_H-i-s_"));
}
+
$debugDir = codecept_output_dir() . 'debug';
if (!is_dir($debugDir)) {
- mkdir($debugDir, 0777);
+ mkdir($debugDir);
}
+
$fileName = $debugDir . DIRECTORY_SEPARATOR . $name . '.html';
$this->_savePageSource($fileName);
- $this->debugSection('Snapshot Saved', "file://$fileName");
+ $this->debugSection('Snapshot Saved', "file://{$fileName}");
}
@@ -920,32 +963,28 @@ public function makeHtmlSnapshot($name = null)
* $I->resizeWindow(800, 600);
*
* ```
- *
- * @param int $width
- * @param int $height
*/
- public function resizeWindow($width, $height)
+ public function resizeWindow(int $width, int $height): void
{
$this->webDriver->manage()->window()->setSize(new WebDriverDimension($width, $height));
}
- private function debugCookies()
+ private function debugCookies(): void
{
$result = [];
$cookies = $this->webDriver->manage()->getCookies();
foreach ($cookies as $cookie) {
$result[] = is_array($cookie) ? $cookie : $cookie->toArray();
}
- $this->debugSection('Cookies', json_encode($result));
+
+ $this->debugSection('Cookies', json_encode($result, JSON_THROW_ON_ERROR));
}
public function seeCookie($cookie, array $params = [])
{
$cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params);
$cookies = array_map(
- function ($c) {
- return $c['name'];
- },
+ fn($c) => $c['name'],
$cookies
);
$this->debugCookies();
@@ -956,9 +995,7 @@ public function dontSeeCookie($cookie, array $params = [])
{
$cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params);
$cookies = array_map(
- function ($c) {
- return $c['name'];
- },
+ fn($c) => $c['name'],
$cookies
);
$this->debugCookies();
@@ -985,6 +1022,7 @@ public function setCookie($cookie, $value, array $params = [], $showDebug = true
$params[$key] = $default;
}
}
+
$this->webDriver->manage()->addCookie($params);
if ($showDebug) {
$this->debugCookies();
@@ -1004,6 +1042,7 @@ public function grabCookie($cookie, array $params = [])
if (empty($cookies)) {
return null;
}
+
$cookie = reset($cookies);
return $cookie['value'];
}
@@ -1012,10 +1051,9 @@ public function grabCookie($cookie, array $params = [])
* Grabs current page source code.
*
* @throws ModuleException if no page was opened.
- *
* @return string Current page source code.
*/
- public function grabPageSource()
+ public function grabPageSource(): string
{
// Make sure that some page was opened.
$this->_getCurrentUri();
@@ -1029,13 +1067,13 @@ protected function filterCookies($cookies, $params = [])
if (!isset($params[$filter])) {
continue;
}
+
$cookies = array_filter(
$cookies,
- function ($item) use ($filter, $params) {
- return $item[$filter] == $params[$filter];
- }
+ fn($item): bool => $item[$filter] == $params[$filter]
);
}
+
return $cookies;
}
@@ -1059,6 +1097,7 @@ public function see($text, $selector = null)
if (!$selector) {
return $this->assertPageContains($text);
}
+
$this->enableImplicitWait();
$nodes = $this->matchVisible($selector);
$this->disableImplicitWait();
@@ -1070,6 +1109,7 @@ public function dontSee($text, $selector = null)
if (!$selector) {
return $this->assertPageNotContains($text);
}
+
$nodes = $this->matchVisible($selector);
$this->assertNodesNotContain($text, $nodes, $selector);
}
@@ -1091,29 +1131,23 @@ public function dontSeeInSource($raw)
* seeInPageSource('assertThat(
$this->webDriver->getPageSource(),
- new PageConstraint($text, $this->_getCurrentUri()),
- ''
+ new PageConstraint($text, $this->_getCurrentUri())
);
}
/**
* Checks that the page source doesn't contain the given string.
- *
- * @param $text
*/
- public function dontSeeInPageSource($text)
+ public function dontSeeInPageSource(string $text): void
{
$this->assertThatItsNot(
$this->webDriver->getPageSource(),
- new PageConstraint($text, $this->_getCurrentUri()),
- ''
+ new PageConstraint($text, $this->_getCurrentUri())
);
}
@@ -1123,18 +1157,22 @@ public function click($link, $context = null)
if ($context) {
$page = $this->matchFirstOrFail($this->webDriver, $context);
}
+
$el = $this->_findClickable($page, $link);
- if (!$el) { // check one more time if this was a CSS selector we didn't match
+ if ($el === null) { // check one more time if this was a CSS selector we didn't match
try {
$els = $this->match($page, $link);
- } catch (MalformedLocatorException $e) {
- throw new ElementNotFound("name=$link", "'$link' is invalid CSS and XPath selector and Link or Button");
+ } catch (MalformedLocatorException $exception) {
+ throw new ElementNotFound("name={$link}", "'{$link}' is invalid CSS and XPath selector and Link or Button");
}
+
$el = reset($els);
}
+
if (!$el) {
throw new ElementNotFound($link, 'Link or Button or CSS or XPath');
}
+
$el->click();
}
@@ -1156,14 +1194,14 @@ public function click($link, $context = null)
* $el = $module->_findClickable($topBar, 'Click Me');
*
* ```
+ * @param RemoteWebDriver|RemoteWebElement $page WebDriver instance or an element to search within
+ * @param WebDriverBy|array $link A link text or locator to click
+ * @return RemoteWebElement|WebDriverElement|null
* @api
- * @param RemoteWebDriver $page WebDriver instance or an element to search within
- * @param $link a link text or locator to click
- * @return WebDriverElement
*/
public function _findClickable($page, $link)
{
- if (is_array($link) or ($link instanceof WebDriverBy)) {
+ if (is_array($link) || $link instanceof WebDriverBy) {
return $this->matchFirstOrFail($page, $link);
}
@@ -1172,33 +1210,33 @@ public function _findClickable($page, $link)
return $this->matchFirstOrFail($page, $link);
}
- $locator = static::xpathLiteral(trim($link));
+ $locator = static::xPathLiteral(trim((string) $link));
// narrow
$xpath = Locator::combine(
- ".//a[normalize-space(.)=$locator]",
- ".//button[normalize-space(.)=$locator]",
- ".//a/img[normalize-space(@alt)=$locator]/ancestor::a",
- ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][normalize-space(@value)=$locator]"
+ ".//a[normalize-space(.)={$locator}]",
+ ".//button[normalize-space(.)={$locator}]",
+ ".//a/img[normalize-space(@alt)={$locator}]/ancestor::a",
+ ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][normalize-space(@value)={$locator}]"
);
$els = $page->findElements(WebDriverBy::xpath($xpath));
- if (count($els)) {
+ if (count($els) > 0) {
return reset($els);
}
// wide
$xpath = Locator::combine(
- ".//a[./@href][((contains(normalize-space(string(.)), $locator)) or contains(./@title, $locator) or .//img[contains(./@alt, $locator)])]",
- ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][contains(./@value, $locator)]",
- ".//input[./@type = 'image'][contains(./@alt, $locator)]",
- ".//button[contains(normalize-space(string(.)), $locator)]",
- ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][./@name = $locator or ./@title = $locator]",
- ".//button[./@name = $locator or ./@title = $locator]"
+ ".//a[./@href][((contains(normalize-space(string(.)), {$locator})) or contains(./@title, {$locator}) or .//img[contains(./@alt, {$locator})])]",
+ ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][contains(./@value, {$locator})]",
+ ".//input[./@type = 'image'][contains(./@alt, {$locator})]",
+ ".//button[contains(normalize-space(string(.)), {$locator})]",
+ ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][./@name = {$locator} or ./@title = {$locator}]",
+ ".//button[./@name = {$locator} or ./@title = {$locator}]"
);
$els = $page->findElements(WebDriverBy::xpath($xpath));
- if (count($els)) {
+ if (count($els) > 0) {
return reset($els);
}
@@ -1206,30 +1244,32 @@ public function _findClickable($page, $link)
}
/**
- * @param $selector
- * @return WebDriverElement[]
- * @throws \Codeception\Exception\ElementNotFound
+ * @param WebDriverElement|WebDriverBy|array|string $selector
+ * @return RemoteWebElement[]|WebDriverElement[]
+ * @throws ElementNotFound
*/
- protected function findFields($selector)
+ protected function findFields($selector): array
{
if ($selector instanceof WebDriverElement) {
return [$selector];
}
+
if (is_array($selector) || ($selector instanceof WebDriverBy)) {
$fields = $this->match($this->webDriver, $selector);
if (empty($fields)) {
throw new ElementNotFound($selector);
}
+
return $fields;
}
- $locator = static::xpathLiteral(trim($selector));
+ $locator = static::xPathLiteral(trim((string) $selector));
// by text or label
$xpath = Locator::combine(
// @codingStandardsIgnoreStart
- ".//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = $locator) or ./@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or ./@placeholder = $locator)]",
- ".//label[contains(normalize-space(string(.)), $locator)]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]"
+ ".//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = {$locator}) or ./@id = //label[contains(normalize-space(string(.)), {$locator})]/@for) or ./@placeholder = {$locator})]",
+ ".//label[contains(normalize-space(string(.)), {$locator})]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]"
// @codingStandardsIgnoreEnd
);
$fields = $this->webDriver->findElements(WebDriverBy::xpath($xpath));
@@ -1238,7 +1278,7 @@ protected function findFields($selector)
}
// by name
- $xpath = ".//*[self::input | self::textarea | self::select][@name = $locator]";
+ $xpath = ".//*[self::input | self::textarea | self::select][@name = {$locator}]";
$fields = $this->webDriver->findElements(WebDriverBy::xpath($xpath));
if (!empty($fields)) {
return $fields;
@@ -1254,9 +1294,8 @@ protected function findFields($selector)
}
/**
- * @param $selector
- * @return WebDriverElement
- * @throws \Codeception\Exception\ElementNotFound
+ * @return WebDriverElement|false
+ * @throws ElementNotFound
*/
protected function findField($selector)
{
@@ -1272,12 +1311,14 @@ public function seeLink($text, $url = null)
$currentUri = $this->_getCurrentUri();
if (empty($nodes)) {
- $this->fail("No links containing text '$text' were found in page $currentUri");
+ $this->fail("No links containing text '{$text}' were found in page {$currentUri}");
}
+
if ($url) {
$nodes = $this->filterNodesByHref($url, $nodes);
}
- $this->assertNotEmpty($nodes, "No links containing text '$text' and URL '$url' were found in page $currentUri");
+
+ $this->assertNotEmpty($nodes, "No links containing text '{$text}' and URL '{$url}' were found in page {$currentUri}");
}
public function dontSeeLink($text, $url = null)
@@ -1285,32 +1326,25 @@ public function dontSeeLink($text, $url = null)
$nodes = $this->getBaseElement()->findElements(WebDriverBy::partialLinkText($text));
$currentUri = $this->_getCurrentUri();
if (!$url) {
- $this->assertEmpty($nodes, "Link containing text '$text' was found in page $currentUri");
+ $this->assertEmpty($nodes, "Link containing text '{$text}' was found in page {$currentUri}");
} else {
$nodes = $this->filterNodesByHref($url, $nodes);
- $this->assertEmpty($nodes, "Link containing text '$text' and URL '$url' was found in page $currentUri");
+ $this->assertEmpty($nodes, "Link containing text '{$text}' and URL '{$url}' was found in page {$currentUri}");
}
}
- /**
- * @param string $url
- * @param $nodes
- * @return array
- */
- private function filterNodesByHref($url, $nodes)
+ private function filterNodesByHref(string $url, array $nodes): ?array
{
//current uri can be relative, merging it with configured base url gives absolute url
$absoluteCurrentUrl = Uri::mergeUrls($this->_getUrl(), $this->_getCurrentUri());
$expectedUrl = Uri::mergeUrls($absoluteCurrentUrl, $url);
-
- $nodes = array_filter(
+ return array_filter(
$nodes,
- function (WebDriverElement $e) use ($expectedUrl, $absoluteCurrentUrl) {
+ function (WebDriverElement $e) use ($expectedUrl, $absoluteCurrentUrl): bool {
$elementHref = Uri::mergeUrls($absoluteCurrentUrl, $e->getAttribute('href'));
return $elementHref === $expectedUrl;
}
);
- return $nodes;
}
public function seeInCurrentUrl($uri)
@@ -1348,14 +1382,17 @@ 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());
+ $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];
}
@@ -1397,6 +1434,7 @@ protected function proceedSeeInFormFields($formSelector, array $params, $assertN
if (empty($form)) {
throw new ElementNotFound($formSelector, "Form via CSS or XPath");
}
+
$form = reset($form);
$els = [];
@@ -1405,7 +1443,7 @@ protected function proceedSeeInFormFields($formSelector, array $params, $assertN
}
foreach ($els as $arrayElement) {
- list($el, $values) = $arrayElement;
+ [$el, $values] = $arrayElement;
if (!is_array($values)) {
$values = [$values];
@@ -1430,17 +1468,16 @@ protected function proceedSeeInFormFields($formSelector, array $params, $assertN
* @param RemoteWebElement $form The form in which to search for fields.
* @param string $name The field's name.
* @param mixed $values
- * @return void
*/
- protected function pushFormField(&$els, $form, $name, $values)
+ protected function pushFormField(array &$els, RemoteWebElement $form, string $name, $values): void
{
$el = $form->findElements(WebDriverBy::name($name));
- if ($el) {
+ if ($el !== []) {
$els[] = [$el, $values];
} elseif (is_array($values)) {
foreach ($values as $key => $value) {
- $this->pushFormField($els, $form, "{$name}[$key]", $value);
+ $this->pushFormField($els, $form, "{$name}[{$key}]", $value);
}
} else {
throw new ElementNotFound($name);
@@ -1449,10 +1486,9 @@ protected function pushFormField(&$els, $form, $name, $values)
/**
* @param RemoteWebElement[] $elements
- * @param $value
- * @return array
+ * @param mixed $value
*/
- protected function proceedSeeInField(array $elements, $value)
+ protected function proceedSeeInField(array $elements, $value): array
{
$strField = reset($elements)->getAttribute('name');
if (reset($elements)->getTagName() === 'select') {
@@ -1467,6 +1503,7 @@ protected function proceedSeeInField(array $elements, $value)
if (is_bool($value)) {
$currentValues = [false];
}
+
foreach ($elements as $el) {
switch ($el->getTagName()) {
case 'input':
@@ -1482,12 +1519,14 @@ protected function proceedSeeInField(array $elements, $value)
} else {
$currentValues[] = $el->getAttribute('value');
}
+
break;
case 'option':
// no break we need the trim text and the value also
if (!$el->isSelected()) {
break;
}
+
$currentValues[] = $el->getText();
case 'textarea':
// we include trimmed and real value of textarea for check
@@ -1502,7 +1541,7 @@ protected function proceedSeeInField(array $elements, $value)
'Contains',
$value,
$currentValues,
- "Failed testing for '$value' in $strField's value: '" . implode("', '", $currentValues) . "'"
+ "Failed testing for '{$value}' in {$strField}'s value: '" . implode("', '", $currentValues) . "'"
];
}
@@ -1518,9 +1557,11 @@ public function selectOption($select, $option)
break;
}
}
+
if (!$radio) {
- throw new ElementNotFound($select, "Radiobutton with value or name '$option in");
+ throw new ElementNotFound($select, "Radiobutton with value or name '{$option} in");
}
+
$radio->click();
return;
}
@@ -1529,6 +1570,7 @@ public function selectOption($select, $option)
if ($wdSelect->isMultiple()) {
$wdSelect->deselectAll();
}
+
if (!is_array($option)) {
$option = [$option];
}
@@ -1540,7 +1582,7 @@ public function selectOption($select, $option)
try {
$wdSelect->selectByVisibleText($opt);
$matched = true;
- } catch (NoSuchElementException $e) {
+ } catch (NoSuchElementException $exception) {
}
}
}
@@ -1554,7 +1596,7 @@ public function selectOption($select, $option)
try {
$wdSelect->selectByValue($opt);
$matched = true;
- } catch (NoSuchElementException $e) {
+ } catch (NoSuchElementException $exception) {
}
}
}
@@ -1571,14 +1613,16 @@ public function selectOption($select, $option)
if (!$optElement->isSelected()) {
$optElement->click();
}
- } catch (NoSuchElementException $e) {
+ } catch (NoSuchElementException $exception) {
// exception treated at the end
}
}
+
if ($matched) {
return;
}
- throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value");
+
+ throw new ElementNotFound(json_encode($option, JSON_THROW_ON_ERROR), "Option inside {$select} matched by name or value");
}
/**
@@ -1606,10 +1650,11 @@ public function _initializeSession()
if (!is_null($this->config['pageload_timeout'])) {
$this->webDriver->manage()->timeouts()->pageLoadTimeout($this->config['pageload_timeout']);
}
+
$this->setBaseElement();
$this->initialWindowSize();
- } catch (WebDriverCurlException $e) {
- codecept_debug('Curl error: ' . $e->getMessage());
+ } catch (WebDriverCurlException $exception) {
+ codecept_debug('Curl error: ' . $exception->getMessage());
throw new ConnectionException("Can't connect to WebDriver at {$this->wdHost}. Make sure that ChromeDriver, GeckoDriver or Selenium Server is running.");
}
}
@@ -1617,8 +1662,8 @@ public function _initializeSession()
/**
* Loads current RemoteWebDriver instance as a session
*
- * @api
* @param RemoteWebDriver $session
+ * @api
*/
public function _loadSession($session)
{
@@ -1630,7 +1675,6 @@ public function _loadSession($session)
* Returns current WebDriver session for saving
*
* @api
- * @return RemoteWebDriver
*/
public function _backupSession()
{
@@ -1654,18 +1698,18 @@ public function _backupSession()
*/
public function _closeSession($webDriver = null)
{
- if (!$webDriver and $this->webDriver) {
+ if (!$webDriver && $this->webDriver) {
$webDriver = $this->webDriver;
}
+
if (!$webDriver) {
return;
}
+
try {
$webDriver->quit();
unset($webDriver);
- } catch (UnknownServerException $e) {
- // Session already closed so nothing to do
- } catch (UnknownErrorException $e) {
+ } catch (UnknownErrorException $exception) {
// Session already closed so nothing to do
}
}
@@ -1673,10 +1717,9 @@ public function _closeSession($webDriver = null)
/**
* Unselect an option in the given select box.
*
- * @param $select
- * @param $option
+ * @param mixed $option
*/
- public function unselectOption($select, $option)
+ public function unselectOption(WebDriverElement $select, $option): void
{
$el = $this->findField($select);
@@ -1707,75 +1750,81 @@ public function unselectOption($select, $option)
if ($matched) {
return;
}
- throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value");
+
+ throw new ElementNotFound(json_encode($option), "Option inside {$select} matched by name or value");
}
/**
* @param $context
- * @param $radioOrCheckbox
- * @param bool $byValue
- * @return mixed|null
+ * @param WebDriverElement|WebDriverBy $radioOrCheckbox
+ * @return mixed
*/
- protected function findCheckable($context, $radioOrCheckbox, $byValue = false)
+ protected function findCheckable($context, $radioOrCheckbox, bool $byValue = false)
{
if ($radioOrCheckbox instanceof WebDriverElement) {
return $radioOrCheckbox;
}
- if (is_array($radioOrCheckbox) or ($radioOrCheckbox instanceof WebDriverBy)) {
+ if (is_array($radioOrCheckbox) || $radioOrCheckbox instanceof WebDriverBy) {
return $this->matchFirstOrFail($this->getBaseElement(), $radioOrCheckbox);
}
- $locator = static::xpathLiteral($radioOrCheckbox);
+ $locator = static::xPathLiteral($radioOrCheckbox);
if ($context instanceof WebDriverElement && $context->getTagName() === 'input') {
$contextType = $context->getAttribute('type');
if (!in_array($contextType, ['checkbox', 'radio'], true)) {
return null;
}
- $nameLiteral = static::xpathLiteral($context->getAttribute('name'));
- $typeLiteral = static::xpathLiteral($contextType);
- $inputLocatorFragment = "input[@type = $typeLiteral][@name = $nameLiteral]";
+
+ $nameLiteral = static::xPathLiteral($context->getAttribute('name'));
+ $typeLiteral = static::xPathLiteral($contextType);
+ $inputLocatorFragment = "input[@type = {$typeLiteral}][@name = {$nameLiteral}]";
$xpath = Locator::combine(
// @codingStandardsIgnoreStart
- "ancestor::form//{$inputLocatorFragment}[(@id = ancestor::form//label[contains(normalize-space(string(.)), $locator)]/@for) or @placeholder = $locator]",
+ "ancestor::form//{$inputLocatorFragment}[(@id = ancestor::form//label[contains(normalize-space(string(.)), {$locator})]/@for) or @placeholder = {$locator}]",
// @codingStandardsIgnoreEnd
- "ancestor::form//label[contains(normalize-space(string(.)), $locator)]//{$inputLocatorFragment}"
+ "ancestor::form//label[contains(normalize-space(string(.)), {$locator})]//{$inputLocatorFragment}"
);
if ($byValue) {
- $xpath = Locator::combine($xpath, "ancestor::form//{$inputLocatorFragment}[@value = $locator]");
+ $xpath = Locator::combine($xpath, "ancestor::form//{$inputLocatorFragment}[@value = {$locator}]");
}
} else {
$xpath = Locator::combine(
// @codingStandardsIgnoreStart
- "//input[@type = 'checkbox' or @type = 'radio'][(@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or @placeholder = $locator or @name = $locator]",
+ "//input[@type = 'checkbox' or @type = 'radio'][(@id = //label[contains(normalize-space(string(.)), {$locator})]/@for) or @placeholder = {$locator} or @name = {$locator}]",
// @codingStandardsIgnoreEnd
- "//label[contains(normalize-space(string(.)), $locator)]//input[@type = 'radio' or @type = 'checkbox']"
+ "//label[contains(normalize-space(string(.)), {$locator})]//input[@type = 'radio' or @type = 'checkbox']"
);
if ($byValue) {
- $xpath = Locator::combine($xpath, "//input[@type = 'checkbox' or @type = 'radio'][@value = $locator]");
+ $xpath = Locator::combine($xpath, sprintf("//input[@type = 'checkbox' or @type = 'radio'][@value = %s]", $locator));
}
}
+
$els = $context->findElements(WebDriverBy::xpath($xpath));
- if (count($els)) {
+ if (count($els) > 0) {
return reset($els);
}
+
$els = $context->findElements(WebDriverBy::xpath(str_replace('ancestor::form', '', $xpath)));
- if (count($els)) {
+ if (count($els) > 0) {
return reset($els);
}
+
$els = $this->match($context, $radioOrCheckbox);
- if (count($els)) {
+ if (count($els) > 0) {
return reset($els);
}
+
return null;
}
- protected function matchCheckables($selector)
+ protected function matchCheckables($selector): array
{
$els = $this->match($this->webDriver, $selector);
- if (!count($els)) {
+ if ($els === []) {
throw new ElementNotFound($selector, "Element containing radio by CSS or XPath");
}
+
return $els;
}
@@ -1785,9 +1834,11 @@ public function checkOption($option)
if (!$field) {
throw new ElementNotFound($option, "Checkbox or Radio by Label or CSS or XPath");
}
+
if ($field->isSelected()) {
return;
}
+
$field->click();
}
@@ -1797,9 +1848,11 @@ public function uncheckOption($option)
if (!$field) {
throw new ElementNotFound($option, "Checkbox by Label or CSS or XPath");
}
+
if (!$field->isSelected()) {
return;
}
+
$field->click();
}
@@ -1818,9 +1871,9 @@ public function fillField($field, $value)
* $I->clearField('#username');
* ```
*
- * @param $field
+ * @param mixed $field
*/
- public function clearField($field)
+ public function clearField($field): void
{
$el = $this->findField($field);
$el->clear();
@@ -1828,32 +1881,33 @@ public function clearField($field)
/**
* Type in characters on active element.
- * With a second parameter you can specify delay between key presses.
- *
+ * With a second parameter you can specify delay between key presses.
+ *
* ```php
* click('#input');
- *
+ *
* // type text in active element
* $I->type('Hello world');
- *
+ *
* // type text with a 1sec delay between chars
* $I->type('Hello World', 1);
* ```
- *
+ *
* This might be useful when you an input reacts to typing and you need to slow it down to emulate human behavior.
* For instance, this is how Credit Card fields can be filled in.
- *
- * @param $text
- * @param $delay [sec]
+ *
+ * @param int $delay [sec]
*/
- public function type($text, $delay = 0) {
+ public function type(string $text, int $delay = 0): void
+ {
$keys = str_split($text);
foreach ($keys as $key) {
sleep($delay);
$this->webDriver->getKeyboard()->pressKey($key);
}
+
sleep($delay);
}
@@ -1863,11 +1917,13 @@ public function attachFile($field, $filename)
// in order to be compatible on different OS
$filePath = codecept_data_dir() . $filename;
if (!file_exists($filePath)) {
- throw new \InvalidArgumentException("File does not exist: $filePath");
+ throw new InvalidArgumentException("File does not exist: {$filePath}");
}
+
if (!is_readable($filePath)) {
- throw new \InvalidArgumentException("File is not readable: $filePath");
+ throw new InvalidArgumentException("File is not readable: {$filePath}");
}
+
// in order for remote upload to be enabled
$el->setFileDetector(new LocalFileDetector());
@@ -1875,35 +1931,38 @@ public function attachFile($field, $filename)
if ($this->isPhantom()) {
$el->setFileDetector(new UselessFileDetector());
}
+
$el->sendKeys(realpath($filePath));
}
/**
* Grabs all visible text from the current page.
- *
- * @return string
*/
- protected function getVisibleText()
+ protected function getVisibleText(): ?string
{
if ($this->getBaseElement() instanceof RemoteWebElement) {
return $this->getBaseElement()->getText();
}
+
$els = $this->getBaseElement()->findElements(WebDriverBy::cssSelector('body'));
if (isset($els[0])) {
return $els[0]->getText();
}
+
return '';
}
public function grabTextFrom($cssOrXPathOrRegex)
{
$els = $this->match($this->getBaseElement(), $cssOrXPathOrRegex, false);
- if (count($els)) {
+ if ($els !== []) {
return $els[0]->getText();
}
+
if (@preg_match($cssOrXPathOrRegex, $this->webDriver->getPageSource(), $matches)) {
return $matches[1];
}
+
throw new ElementNotFound($cssOrXPathOrRegex, 'CSS or XPath or Regex');
}
@@ -1921,6 +1980,7 @@ public function grabValueFrom($field)
$select = new WebDriverSelect($el);
return $select->getFirstSelectedOption()->getAttribute('value');
}
+
return $el->getAttribute('value');
}
@@ -1928,27 +1988,26 @@ public function grabMultiple($cssOrXpath, $attribute = null)
{
$els = $this->match($this->getBaseElement(), $cssOrXpath);
return array_map(
- function (WebDriverElement $e) use ($attribute) {
+ function (WebDriverElement $e) use ($attribute): ?string {
if ($attribute) {
return $e->getAttribute($attribute);
}
+
return $e->getText();
},
$els
);
}
-
protected function filterByAttributes($els, array $attributes)
{
foreach ($attributes as $attr => $value) {
$els = array_filter(
$els,
- function (WebDriverElement $el) use ($attr, $value) {
- return $el->getAttribute($attr) == $value;
- }
+ fn(WebDriverElement $el): bool => $el->getAttribute($attr) == $value
);
}
+
return $els;
}
@@ -1974,13 +2033,11 @@ public function dontSeeElement($selector, $attributes = [])
* ``` php
* seeElementInDOM('//form/input[type=hidden]');
- * ?>
* ```
*
* @param $selector
- * @param array $attributes
*/
- public function seeElementInDOM($selector, $attributes = [])
+ public function seeElementInDOM($selector, array $attributes = []): void
{
$this->enableImplicitWait();
$els = $this->match($this->getBaseElement(), $selector);
@@ -1993,10 +2050,9 @@ public function seeElementInDOM($selector, $attributes = [])
/**
* Opposite of `seeElementInDOM`.
*
- * @param $selector
- * @param array $attributes
+ * @param mixed $selector
*/
- public function dontSeeElementInDOM($selector, $attributes = [])
+ public function dontSeeElementInDOM($selector, array $attributes = []): void
{
$els = $this->match($this->getBaseElement(), $selector);
$els = $this->filterByAttributes($els, $attributes);
@@ -2007,7 +2063,7 @@ public function seeNumberOfElements($selector, $expected)
{
$counted = count($this->matchVisible($selector));
if (is_array($expected)) {
- list($floor, $ceil) = $expected;
+ [$floor, $ceil] = $expected;
$this->assertTrue(
$floor <= $counted && $ceil >= $counted,
'Number of elements counted differs from expected range'
@@ -2025,7 +2081,7 @@ public function seeNumberOfElementsInDOM($selector, $expected)
{
$counted = count($this->match($this->getBaseElement(), $selector));
if (is_array($expected)) {
- list($floor, $ceil) = $expected;
+ [$floor, $ceil] = $expected;
$this->assertTrue(
$floor <= $counted && $ceil >= $counted,
'Number of elements counted differs from expected range'
@@ -2047,16 +2103,16 @@ public function seeOptionIsSelected($selector, $optionText)
foreach ($els as $k => $el) {
$els[$k] = $this->findCheckable($el, $optionText, true);
}
+
$this->assertNotEmpty(
array_filter(
$els,
- function ($e) {
- return $e && $e->isSelected();
- }
+ fn($e): bool => $e && $e->isSelected()
)
);
return;
}
+
$select = new WebDriverSelect($el);
$this->assertNodesContain($optionText, $select->getAllSelectedOptions(), 'option');
}
@@ -2069,16 +2125,16 @@ public function dontSeeOptionIsSelected($selector, $optionText)
foreach ($els as $k => $el) {
$els[$k] = $this->findCheckable($el, $optionText, true);
}
+
$this->assertEmpty(
array_filter(
$els,
- function ($e) {
- return $e && $e->isSelected();
- }
+ fn($e): bool => $e && $e->isSelected()
)
);
return;
}
+
$select = new WebDriverSelect($el);
$this->assertNodesNotContain($optionText, $select->getAllSelectedOptions(), 'option');
}
@@ -2098,22 +2154,24 @@ public function dontSeeInTitle($title)
* Don't confuse popups with modal windows,
* as created by [various libraries](http://jster.net/category/windows-modals-popups).
*/
- public function acceptPopup()
+ public function acceptPopup(): void
{
if ($this->isPhantom()) {
throw new ModuleException($this, 'PhantomJS does not support working with popups');
}
+
$this->webDriver->switchTo()->alert()->accept();
}
/**
* Dismisses the active JavaScript popup, as created by `window.alert`, `window.confirm`, or `window.prompt`.
*/
- public function cancelPopup()
+ public function cancelPopup(): void
{
if ($this->isPhantom()) {
throw new ModuleException($this, 'PhantomJS does not support working with popups');
}
+
$this->webDriver->switchTo()->alert()->dismiss();
}
@@ -2123,19 +2181,20 @@ public function cancelPopup()
*
* @param $text
*
- * @throws \Codeception\Exception\ModuleException
+ * @throws ModuleException
*/
- public function seeInPopup($text)
+ public function seeInPopup($text): void
{
if ($this->isPhantom()) {
throw new ModuleException($this, 'PhantomJS does not support working with popups');
}
+
$alert = $this->webDriver->switchTo()->alert();
try {
$this->assertStringContainsString($text, $alert->getText());
- } catch (\PHPUnit\Framework\AssertionFailedError $e) {
+ } catch (PHPUnitAssertionFailedError $failedError) {
$alert->dismiss();
- throw $e;
+ throw $failedError;
}
}
@@ -2143,19 +2202,18 @@ public function seeInPopup($text)
* Checks that the active JavaScript popup,
* as created by `window.alert`|`window.confirm`|`window.prompt`, does NOT contain the given string.
*
- * @param $text
- *
- * @throws \Codeception\Exception\ModuleException
+ * @throws ModuleException
*/
- public function dontSeeInPopup($text)
+ public function dontSeeInPopup(string $text): void
{
if ($this->isPhantom()) {
throw new ModuleException($this, 'PhantomJS does not support working with popups');
}
+
$alert = $this->webDriver->switchTo()->alert();
try {
$this->assertStringNotContainsString($text, $alert->getText());
- } catch (\PHPUnit\Framework\AssertionFailedError $e) {
+ } catch (PHPUnitAssertionFailedError $e) {
$alert->dismiss();
throw $e;
}
@@ -2165,21 +2223,21 @@ public function dontSeeInPopup($text)
* Enters text into a native JavaScript prompt popup, as created by `window.prompt`.
*
* @param $keys
- *
- * @throws \Codeception\Exception\ModuleException
+ * @throws ModuleException
*/
- public function typeInPopup($keys)
+ public function typeInPopup($keys): void
{
if ($this->isPhantom()) {
throw new ModuleException($this, 'PhantomJS does not support working with popups');
}
+
$this->webDriver->switchTo()->alert()->sendKeys($keys);
}
/**
* Reloads the current page.
*/
- public function reloadPage()
+ public function reloadPage(): void
{
$this->webDriver->navigate()->refresh();
}
@@ -2187,7 +2245,7 @@ public function reloadPage()
/**
* Moves back in history.
*/
- public function moveBack()
+ public function moveBack(): void
{
$this->webDriver->navigate()->back();
$this->debug($this->_getCurrentUri());
@@ -2196,17 +2254,18 @@ public function moveBack()
/**
* Moves forward in history.
*/
- public function moveForward()
+ public function moveForward(): void
{
$this->webDriver->navigate()->forward();
$this->debug($this->_getCurrentUri());
}
- protected function getSubmissionFormFieldName($name)
+ protected function getSubmissionFormFieldName(string $name): string
{
if (substr($name, -2) === '[]') {
return substr($name, 0, -2);
}
+
return $name;
}
@@ -2293,7 +2352,6 @@ protected function getSubmissionFormFieldName($name)
* $I->submitForm('//form[@id=my-form]', $form, 'submitButton');
* // $I->amOnPage('/path/to/form-page') may be needed
* $I->seeInFormFields('//form[@id=my-form]', $form);
- * ?>
* ```
*
* Parameter values must be set to arrays for multiple input fields
@@ -2319,7 +2377,6 @@ protected function getSubmissionFormFieldName($name)
* 'second option value',
* ]
* ]);
- * ?>
* ```
*
* Mixing string and boolean values for a checkbox's value is not supported
@@ -2364,10 +2421,6 @@ protected function getSubmissionFormFieldName($name)
* - 'submitButton'
* - ['name' => 'submitButton']
* - WebDriverBy::name('submitButton')
- *
- * @param $selector
- * @param $params
- * @param $button
*/
public function submitForm($selector, array $params, $button = null)
{
@@ -2381,6 +2434,7 @@ public function submitForm($selector, array $params, $button = null)
if (!isset($params[$fieldName])) {
continue;
}
+
$value = $params[$fieldName];
if (is_array($value) && $field->getTagName() !== 'select') {
if ($field->getAttribute('type') === 'checkbox' || $field->getAttribute('type') === 'radio') {
@@ -2393,6 +2447,7 @@ public function submitForm($selector, array $params, $button = null)
break;
}
}
+
if (!$found && !empty($value) && is_bool(reset($value))) {
$value = array_pop($params[$fieldName]);
}
@@ -2421,7 +2476,7 @@ public function submitForm($selector, array $params, $button = null)
$form->getAttribute('action') ? $form->getAttribute('action') : $this->_getCurrentUri()
);
$this->debugSection('Method', $form->getAttribute('method') ? $form->getAttribute('method') : 'GET');
- $this->debugSection('Parameters', json_encode($params));
+ $this->debugSection('Parameters', json_encode($params, JSON_THROW_ON_ERROR));
$submitted = false;
if (!empty($button)) {
@@ -2445,6 +2500,7 @@ public function submitForm($selector, array $params, $button = null)
if (!$submitted) {
$form->submit();
}
+
$this->debugSection('Page', $this->_getCurrentUri());
}
@@ -2459,20 +2515,16 @@ public function submitForm($selector, array $params, $button = null)
* $I->waitForElementChange('#menu', function(WebDriverElement $el) {
* return $el->isDisplayed();
* }, 100);
- * ?>
* ```
*
* @param $element
- * @param \Closure $callback
* @param int $timeout seconds
- * @throws \Codeception\Exception\ElementNotFound
+ * @throws ElementNotFound
*/
- public function waitForElementChange($element, \Closure $callback, $timeout = 30)
+ public function waitForElementChange($element, Closure $callback, int $timeout = 30): void
{
$el = $this->matchFirstOrFail($this->getBaseElement(), $element);
- $checker = function () use ($el, $callback) {
- return $callback($el);
- };
+ $checker = fn() => $callback($el);
$this->webDriver->wait($timeout)->until($checker);
}
@@ -2484,14 +2536,13 @@ public function waitForElementChange($element, \Closure $callback, $timeout = 30
* waitForElement('#agree_button', 30); // secs
* $I->click('#agree_button');
- * ?>
* ```
*
* @param $element
* @param int $timeout seconds
- * @throws \Exception
+ * @throws Exception
*/
- public function waitForElement($element, $timeout = 10)
+ public function waitForElement($element, int $timeout = 10): void
{
$condition = WebDriverExpectedCondition::presenceOfElementLocated($this->getLocator($element));
$this->webDriver->wait($timeout)->until($condition);
@@ -2505,14 +2556,13 @@ public function waitForElement($element, $timeout = 10)
* waitForElementVisible('#agree_button', 30); // secs
* $I->click('#agree_button');
- * ?>
* ```
*
* @param $element
* @param int $timeout seconds
- * @throws \Exception
+ * @throws Exception
*/
- public function waitForElementVisible($element, $timeout = 10)
+ public function waitForElementVisible($element, int $timeout = 10): void
{
$condition = WebDriverExpectedCondition::visibilityOfElementLocated($this->getLocator($element));
$this->webDriver->wait($timeout)->until($condition);
@@ -2525,14 +2575,13 @@ public function waitForElementVisible($element, $timeout = 10)
* ``` php
* waitForElementNotVisible('#agree_button', 30); // secs
- * ?>
* ```
*
* @param $element
* @param int $timeout seconds
- * @throws \Exception
+ * @throws Exception
*/
- public function waitForElementNotVisible($element, $timeout = 10)
+ public function waitForElementNotVisible($element, int $timeout = 10): void
{
$condition = WebDriverExpectedCondition::invisibilityOfElementLocated($this->getLocator($element));
$this->webDriver->wait($timeout)->until($condition);
@@ -2546,14 +2595,13 @@ public function waitForElementNotVisible($element, $timeout = 10)
* waitForElementClickable('#agree_button', 30); // secs
* $I->click('#agree_button');
- * ?>
* ```
*
* @param $element
* @param int $timeout seconds
- * @throws \Exception
+ * @throws Exception
*/
- public function waitForElementClickable($element, $timeout = 10)
+ public function waitForElementClickable($element, int $timeout = 10): void
{
$condition = WebDriverExpectedCondition::elementToBeClickable($this->getLocator($element));
$this->webDriver->wait($timeout)->until($condition);
@@ -2570,15 +2618,12 @@ public function waitForElementClickable($element, $timeout = 10)
* waitForText('foo', 30); // secs
* $I->waitForText('foo', 30, '.title'); // secs
- * ?>
* ```
*
- * @param string $text
* @param int $timeout seconds
- * @param string $selector optional
- * @throws \Exception
+ * @throws Exception
*/
- public function waitForText($text, $timeout = 10, $selector = null)
+ public function waitForText(string $text, int $timeout = 10, string $selector = null): void
{
$message = sprintf(
'Waited for %d secs but text %s still not found',
@@ -2586,12 +2631,12 @@ public function waitForText($text, $timeout = 10, $selector = null)
Locator::humanReadableString($text)
);
if (!$selector) {
- $condition = WebDriverExpectedCondition::textToBePresentInElement(WebDriverBy::xpath('//body'), $text);
+ $condition = WebDriverExpectedCondition::elementTextContains(WebDriverBy::xpath('//body'), $text);
$this->webDriver->wait($timeout)->until($condition, $message);
return;
}
- $condition = WebDriverExpectedCondition::textToBePresentInElement($this->getLocator($selector), $text);
+ $condition = WebDriverExpectedCondition::elementTextContains($this->getLocator($selector), $text);
$this->webDriver->wait($timeout)->until($condition, $message);
}
@@ -2599,9 +2644,9 @@ public function waitForText($text, $timeout = 10, $selector = null)
* Wait for $timeout seconds.
*
* @param int|float $timeout secs
- * @throws \Codeception\Exception\TestRuntimeException
+ * @throws TestRuntimeException
*/
- public function wait($timeout)
+ public function wait($timeout): void
{
if ($timeout >= 1000) {
throw new TestRuntimeException(
@@ -2610,7 +2655,8 @@ public function wait($timeout)
Please note that wait method accepts number of seconds as parameter."
);
}
- usleep($timeout * 1000000);
+
+ usleep($timeout * 1_000_000);
}
/**
@@ -2619,7 +2665,7 @@ public function wait($timeout)
*
* ``` php
* $I->executeInSelenium(function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {
- * $webdriver->get('http://google.com');
+ * $webdriver->get('https://google.com');
* });
* ```
*
@@ -2628,9 +2674,9 @@ public function wait($timeout)
* Try not to use this command on a regular basis.
* If Codeception lacks a feature you need, please implement it and submit a patch.
*
- * @param \Closure $function
+ * @param Closure $function
*/
- public function executeInSelenium(\Closure $function)
+ public function executeInSelenium(Closure $function)
{
return $function($this->webDriver);
}
@@ -2642,7 +2688,7 @@ public function executeInSelenium(\Closure $function)
*
* Example:
* ``` html
- *
+ *
* ```
*
* ``` php
@@ -2652,7 +2698,6 @@ public function executeInSelenium(\Closure $function)
* $I->switchToWindow("another_window");
* # switch to parent window
* $I->switchToWindow();
- * ?>
* ```
*
* If the window has no name, match it by switching to next active tab using `switchToNextTab` method.
@@ -2666,12 +2711,9 @@ public function executeInSelenium(\Closure $function)
* $last_window = end($handles);
* $webdriver->switchTo()->window($last_window);
* });
- * ?>
* ```
- *
- * @param string|null $name
*/
- public function switchToWindow($name = null)
+ public function switchToWindow(string $name = null): void
{
$this->webDriver->switchTo()->window($name);
}
@@ -2681,7 +2723,7 @@ public function switchToWindow($name = null)
*
* Example:
* ``` html
- *