419 lines
10 KiB
PHP
419 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Laravel\Dusk;
|
|
|
|
use Exception;
|
|
use Facebook\WebDriver\WebDriverBy;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Support\Traits\Macroable;
|
|
use InvalidArgumentException;
|
|
|
|
class ElementResolver
|
|
{
|
|
use Macroable;
|
|
|
|
/**
|
|
* The remote web driver instance.
|
|
*
|
|
* @var \Facebook\WebDriver\Remote\RemoteWebDriver
|
|
*/
|
|
public $driver;
|
|
|
|
/**
|
|
* The selector prefix for the resolver.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $prefix;
|
|
|
|
/**
|
|
* Set the elements the resolver should use as shortcuts.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $elements = [];
|
|
|
|
/**
|
|
* The button finding methods.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $buttonFinders = [
|
|
'findById',
|
|
'findButtonBySelector',
|
|
'findButtonByName',
|
|
'findButtonByValue',
|
|
'findButtonByText',
|
|
];
|
|
|
|
/**
|
|
* Create a new element resolver instance.
|
|
*
|
|
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
|
|
* @param string $prefix
|
|
* @return void
|
|
*/
|
|
public function __construct($driver, $prefix = 'body')
|
|
{
|
|
$this->driver = $driver;
|
|
$this->prefix = trim($prefix);
|
|
}
|
|
|
|
/**
|
|
* Set the page elements the resolver should use as shortcuts.
|
|
*
|
|
* @param array $elements
|
|
* @return $this
|
|
*/
|
|
public function pageElements(array $elements)
|
|
{
|
|
$this->elements = $elements;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given input "field".
|
|
*
|
|
* @param string $field
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function resolveForTyping($field)
|
|
{
|
|
if (! is_null($element = $this->findById($field))) {
|
|
return $element;
|
|
}
|
|
|
|
return $this->firstOrFail([
|
|
"input[name='{$field}']", "textarea[name='{$field}']", $field,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given select "field".
|
|
*
|
|
* @param string $field
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function resolveForSelection($field)
|
|
{
|
|
if (! is_null($element = $this->findById($field))) {
|
|
return $element;
|
|
}
|
|
|
|
return $this->firstOrFail([
|
|
"select[name='{$field}']", $field,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolve all the options with the given value on the select field.
|
|
*
|
|
* @param string $field
|
|
* @param array $values
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function resolveSelectOptions($field, array $values)
|
|
{
|
|
$options = $this->resolveForSelection($field)
|
|
->findElements(WebDriverBy::tagName('option'));
|
|
|
|
if (empty($options)) {
|
|
return [];
|
|
}
|
|
|
|
return array_filter($options, function ($option) use ($values) {
|
|
return in_array($option->getAttribute('value'), $values);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given radio "field" / value.
|
|
*
|
|
* @param string $field
|
|
* @param string|null $value
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*
|
|
* @throws \Exception
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
public function resolveForRadioSelection($field, $value = null)
|
|
{
|
|
if (! is_null($element = $this->findById($field))) {
|
|
return $element;
|
|
}
|
|
|
|
if (is_null($value)) {
|
|
throw new InvalidArgumentException(
|
|
"No value was provided for radio button [{$field}]."
|
|
);
|
|
}
|
|
|
|
return $this->firstOrFail([
|
|
"input[type=radio][name='{$field}'][value='{$value}']", $field,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given checkbox "field".
|
|
*
|
|
* @param string|null $field
|
|
* @param string|null $value
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function resolveForChecking($field, $value = null)
|
|
{
|
|
if (! is_null($element = $this->findById($field))) {
|
|
return $element;
|
|
}
|
|
|
|
$selector = 'input[type=checkbox]';
|
|
|
|
if (! is_null($field)) {
|
|
$selector .= "[name='{$field}']";
|
|
}
|
|
|
|
if (! is_null($value)) {
|
|
$selector .= "[value='{$value}']";
|
|
}
|
|
|
|
return $this->firstOrFail([
|
|
$selector, $field,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given file "field".
|
|
*
|
|
* @param string $field
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function resolveForAttachment($field)
|
|
{
|
|
if (! is_null($element = $this->findById($field))) {
|
|
return $element;
|
|
}
|
|
|
|
return $this->firstOrFail([
|
|
"input[type=file][name='{$field}']", $field,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given "field".
|
|
*
|
|
* @param string $field
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function resolveForField($field)
|
|
{
|
|
if (! is_null($element = $this->findById($field))) {
|
|
return $element;
|
|
}
|
|
|
|
return $this->firstOrFail([
|
|
"input[name='{$field}']", "textarea[name='{$field}']",
|
|
"select[name='{$field}']", "button[name='{$field}']", $field,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given button.
|
|
*
|
|
* @param string $button
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
public function resolveForButtonPress($button)
|
|
{
|
|
foreach ($this->buttonFinders as $method) {
|
|
if (! is_null($element = $this->{$method}($button))) {
|
|
return $element;
|
|
}
|
|
}
|
|
|
|
throw new InvalidArgumentException(
|
|
"Unable to locate button [{$button}]."
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given button by selector.
|
|
*
|
|
* @param string $button
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
|
|
*/
|
|
protected function findButtonBySelector($button)
|
|
{
|
|
if (! is_null($element = $this->find($button))) {
|
|
return $element;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given button by name.
|
|
*
|
|
* @param string $button
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
|
|
*/
|
|
protected function findButtonByName($button)
|
|
{
|
|
if (! is_null($element = $this->find("input[type=submit][name='{$button}']")) ||
|
|
! is_null($element = $this->find("input[type=button][value='{$button}']")) ||
|
|
! is_null($element = $this->find("button[name='{$button}']"))) {
|
|
return $element;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given button by value.
|
|
*
|
|
* @param string $button
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
|
|
*/
|
|
protected function findButtonByValue($button)
|
|
{
|
|
foreach ($this->all('input[type=submit]') as $element) {
|
|
if ($element->getAttribute('value') === $button) {
|
|
return $element;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the element for a given button by text.
|
|
*
|
|
* @param string $button
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
|
|
*/
|
|
protected function findButtonByText($button)
|
|
{
|
|
foreach ($this->all('button') as $element) {
|
|
if (Str::contains($element->getText(), $button)) {
|
|
return $element;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempt to find the selector by ID.
|
|
*
|
|
* @param string $selector
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
|
|
*/
|
|
protected function findById($selector)
|
|
{
|
|
if (preg_match('/^#[\w\-:]+$/', $selector)) {
|
|
return $this->driver->findElement(WebDriverBy::id(substr($selector, 1)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find an element by the given selector or return null.
|
|
*
|
|
* @param string $selector
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
|
|
*/
|
|
public function find($selector)
|
|
{
|
|
try {
|
|
return $this->findOrFail($selector);
|
|
} catch (Exception $e) {
|
|
//
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the first element matching the given selectors.
|
|
*
|
|
* @param array $selectors
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function firstOrFail($selectors)
|
|
{
|
|
foreach ((array) $selectors as $selector) {
|
|
try {
|
|
return $this->findOrFail($selector);
|
|
} catch (Exception $e) {
|
|
//
|
|
}
|
|
}
|
|
|
|
throw $e;
|
|
}
|
|
|
|
/**
|
|
* Find an element by the given selector or throw an exception.
|
|
*
|
|
* @param string $selector
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
|
*/
|
|
public function findOrFail($selector)
|
|
{
|
|
if (! is_null($element = $this->findById($selector))) {
|
|
return $element;
|
|
}
|
|
|
|
return $this->driver->findElement(
|
|
WebDriverBy::cssSelector($this->format($selector))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Find the elements by the given selector or return an empty array.
|
|
*
|
|
* @param string $selector
|
|
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
|
|
*/
|
|
public function all($selector)
|
|
{
|
|
try {
|
|
return $this->driver->findElements(
|
|
WebDriverBy::cssSelector($this->format($selector))
|
|
);
|
|
} catch (Exception $e) {
|
|
//
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Format the given selector with the current prefix.
|
|
*
|
|
* @param string $selector
|
|
* @return string
|
|
*/
|
|
public function format($selector)
|
|
{
|
|
$sortedElements = collect($this->elements)->sortByDesc(function ($element, $key) {
|
|
return strlen($key);
|
|
})->toArray();
|
|
|
|
$selector = str_replace(
|
|
array_keys($sortedElements), array_values($sortedElements), $originalSelector = $selector
|
|
);
|
|
|
|
if (Str::startsWith($selector, '@') && $selector === $originalSelector) {
|
|
$selector = '[dusk="'.explode('@', $selector)[1].'"]';
|
|
}
|
|
|
|
return trim($this->prefix.' '.$selector);
|
|
}
|
|
}
|