Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion phpstan.dist.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ includes:
- vendor/phpstan/phpstan-phpunit/rules.neon

parameters:
level: 8
level: 9
paths:
- src
- tests
Expand Down
44 changes: 32 additions & 12 deletions src/WebdriverClassicDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public function getWindowNames(): array

public function getWindowName(): string
{
$name = (string)$this->evaluateScript('window.name');
$name = $this->getAsString($this->evaluateScript('window.name'), 'Window name');

if ($name === '') {
$name = self::W3C_WINDOW_HANDLE_PREFIX . $this->getWebDriver()->getWindowHandle();
Expand Down Expand Up @@ -317,22 +317,22 @@ public function getText(
return trim(str_replace(
["\r\n", "\r", "\n", "\xc2\xa0"],
' ',
$this->getElementDomProperty($this->findElement($xpath), 'innerText')
$this->getAsString($this->getElementDomProperty($this->findElement($xpath), 'innerText'), 'The element\'s innerText')
));
}

public function getHtml(
#[Language('XPath')]
string $xpath
): string {
return $this->getElementDomProperty($this->findElement($xpath), 'innerHTML');
return $this->getAsString($this->getElementDomProperty($this->findElement($xpath), 'innerHTML'), 'The element\'s innerHTML');
}

public function getOuterHtml(
#[Language('XPath')]
string $xpath
): string {
return $this->getElementDomProperty($this->findElement($xpath), 'outerHTML');
return $this->getAsString($this->getElementDomProperty($this->findElement($xpath), 'outerHTML'), 'The element\'s outerHTML');
}

public function getAttribute(
Expand All @@ -344,7 +344,8 @@ public function getAttribute(
// so we cannot use webdriver api for this. See also: https://w3c.github.io/webdriver/#dfn-get-element-attribute
$escapedName = $this->jsonEncode($name, 'get attribute', 'attribute name');
$script = "return arguments[0].getAttribute($escapedName)";
return $this->executeJsOnXpath($xpath, $script);
$result = $this->executeJsOnXpath($xpath, $script);
return $result === null ? null : $this->getAsString($result, "The element's $name attribute");
}

/**
Expand Down Expand Up @@ -412,7 +413,7 @@ public function setValue(
if (is_array($value)) {
$this->deselectAllOptions($element);
foreach ($value as $option) {
$this->selectOptionOnElement($element, $option, true);
$this->selectOptionOnElement($element, $this->getAsString($option, 'Option value'), true);
}
return;
}
Expand Down Expand Up @@ -941,12 +942,13 @@ private function charToSynOptions($char, ?string $modifier = null): string
* Executes JS on a given element - pass in a js script string and argument[0] will
* be replaced with a reference to the result of the $xpath query
*
* @param string $xpath the xpath to search with
* @param string $script the script to execute
* Example:
* ```
* $this->executeJsOnXpath($xpath, 'return argument[0].childNodes.length');
* ```
*
* @return mixed
* @throws DriverException
* @example $this->executeJsOnXpath($xpath, 'return argument[0].childNodes.length');
*/
private function executeJsOnXpath(
#[Language('XPath')]
Expand All @@ -960,11 +962,13 @@ private function executeJsOnXpath(
/**
* Executes JS on a given element - pass in a js script string and argument[0] will contain a reference to the element
*
* @param RemoteWebElement $element the webdriver element
* @param string $script the script to execute
* Example:
* ```
* $this->executeJsOnElement($element, 'return argument[0].childNodes.length');
* ```
*
* @return mixed
* @throws DriverException
* @example $this->executeJsOnXpath($xpath, 'return argument[0].childNodes.length');
*/
private function executeJsOnElement(
RemoteWebElement $element,
Expand Down Expand Up @@ -1257,5 +1261,21 @@ private function getElementDomProperty(RemoteWebElement $element, string $proper
}
}

/**
* @param mixed $value
* @throws DriverException
*/
private function getAsString($value, string $name): string
{
if (!is_scalar($value)) {
$actualType = gettype($value);
throw new DriverException(
"$name should be a string or at least a scalar value, but received `$actualType` instead"
);
}

return (string)$value;
}

// </editor-fold>
}
25 changes: 25 additions & 0 deletions tests/Custom/WebDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Mink\WebdriverClassicDriver\Tests\Custom;

use Behat\Mink\Exception\DriverException;
use Facebook\WebDriver\Remote\RemoteWebElement;
use Mink\WebdriverClassicDriver\Tests\WebDriverMockingTrait;
use Mink\WebdriverClassicDriver\WebdriverClassicDriver;

Expand Down Expand Up @@ -69,4 +70,28 @@ public function testClassicDriverCanProvideBrowserName(): void
$this->driver->getBrowserName()
);
}

public function testThatDriverCatchesUnexpectedAttributeValueType(): void
{
$mockWebDriver = $this->createMockWebDriver();
$mockElement = $this->createMock(RemoteWebElement::class);
$mockWebDriver
->expects($this->once())
->method('findElement')
->willReturn($mockElement);
$mockWebDriver
->expects($this->once())
->method('executeScript')
->with('return arguments[0].getAttribute("some-attribute")', [$mockElement])
->willReturn(['invalid attribute value']);

$driver = new WebdriverClassicDriver('fake browser', [], 'example.com', fn() => $mockWebDriver);

$driver->start();

$this->expectException(DriverException::class);
$this->expectExceptionMessage('The element\'s some-attribute attribute should be a string or at least a scalar value, but received `array` instead');

$driver->getAttribute('//fake', 'some-attribute');
}
}
Loading