diff --git a/tests/acceptance/features/core/Actor.php b/tests/acceptance/features/core/Actor.php index d5987f4e84..0e45aea335 100644 --- a/tests/acceptance/features/core/Actor.php +++ b/tests/acceptance/features/core/Actor.php @@ -165,85 +165,9 @@ class Actor { public function find(Locator $elementLocator, $timeout = 0, $timeoutStep = 0.5) { $timeout = $timeout * $this->findTimeoutMultiplier; - return self::findInternal($this->session, $elementLocator, $timeout, $timeoutStep); - } + $elementFinder = new ElementFinder($this->session, $elementLocator, $timeout, $timeoutStep); - /** - * Finds an element in the given Mink Session. - * - * The timeout is not affected by the multiplier set using - * setFindTimeoutMultiplier(). - * - * @see find($session, $elementLocator, $timeout, $timeoutStep) - */ - private static function findInternal(\Behat\Mink\Session $session, Locator $elementLocator, $timeout, $timeoutStep) { - $element = null; - $selector = $elementLocator->getSelector(); - $locator = $elementLocator->getLocator(); - $ancestorElement = self::findAncestorElement($session, $elementLocator, $timeout, $timeoutStep); - - $findCallback = function() use (&$element, $selector, $locator, $ancestorElement) { - $element = $ancestorElement->find($selector, $locator); - - return $element !== null; - }; - if (!Utils::waitFor($findCallback, $timeout, $timeoutStep)) { - $message = $elementLocator->getDescription() . " could not be found"; - if ($timeout > 0) { - $message = $message . " after $timeout seconds"; - } - throw new NoSuchElementException($message); - } - - return $element; - } - - /** - * Returns the ancestor element from which the given locator will be looked - * for. - * - * If the ancestor of the given locator is another locator the element for - * the ancestor locator is found and returned. If the ancestor of the given - * locator is already an element that element is the one returned. If the - * given locator has no ancestor then the base document element is returned. - * - * The timeout is used only when finding the element for the ancestor - * locator; if the timeout expires a NoSuchElementException is thrown. - * - * @param \Behat\Mink\Session $session the Mink Session to get the ancestor - * element from. - * @param Locator $elementLocator the locator for the element to get its - * ancestor. - * @param float $timeout the number of seconds (decimals allowed) to wait at - * most for the ancestor element to appear. - * @param float $timeoutStep the number of seconds (decimals allowed) to - * wait before trying to find the ancestor element again. - * @return \Behat\Mink\Element\Element the ancestor element found. - * @throws NoSuchElementException if the ancestor element can not be found. - */ - private static function findAncestorElement(\Behat\Mink\Session $session, Locator $elementLocator, $timeout, $timeoutStep) { - $ancestorElement = $elementLocator->getAncestor(); - if ($ancestorElement instanceof Locator) { - try { - $ancestorElement = self::findInternal($session, $ancestorElement, $timeout, $timeoutStep); - } catch (NoSuchElementException $exception) { - // Little hack to show the stack of ancestor elements that could - // not be found, as Behat only shows the message of the last - // exception in the chain. - $message = $exception->getMessage() . "\n" . - $elementLocator->getDescription() . " could not be found"; - if ($timeout > 0) { - $message = $message . " after $timeout seconds"; - } - throw new NoSuchElementException($message, $exception); - } - } - - if ($ancestorElement === null) { - $ancestorElement = $session->getPage(); - } - - return $ancestorElement; + return $elementFinder->find(); } /** diff --git a/tests/acceptance/features/core/ElementFinder.php b/tests/acceptance/features/core/ElementFinder.php new file mode 100644 index 0000000000..9e1457a686 --- /dev/null +++ b/tests/acceptance/features/core/ElementFinder.php @@ -0,0 +1,176 @@ +. + * + */ + +/** + * Command object to find Mink elements. + * + * The element locator is relative to its ancestor (either another locator or an + * actual element); if it has no ancestor then the base document element is + * used. + * + * Sometimes an element may not be found simply because it has not appeared yet; + * for those cases ElementFinder supports trying again to find the element + * several times before giving up. The timeout parameter controls how much time + * to wait, at most, to find the element; the timeoutStep parameter controls how + * much time to wait before trying again to find the element. If ancestor + * locators need to be found the timeout is applied individually to each one, + * that is, if the timeout is 10 seconds the method will wait up to 10 seconds + * to find the ancestor of the ancestor and, then, up to 10 seconds to find the + * ancestor and, then, up to 10 seconds to find the element. By default the + * timeout is 0, so the element and its ancestor will be looked for just once; + * the default time to wait before retrying is half a second. + * + * In any case, if the element, or its ancestors, can not be found a + * NoSuchElementException is thrown. + */ +class ElementFinder { + + /** + * Finds an element in the given Mink Session. + * + * @see ElementFinder + */ + private static function findInternal(\Behat\Mink\Session $session, Locator $elementLocator, $timeout, $timeoutStep) { + $element = null; + $selector = $elementLocator->getSelector(); + $locator = $elementLocator->getLocator(); + $ancestorElement = self::findAncestorElement($session, $elementLocator, $timeout, $timeoutStep); + + $findCallback = function() use (&$element, $selector, $locator, $ancestorElement) { + $element = $ancestorElement->find($selector, $locator); + + return $element !== null; + }; + if (!Utils::waitFor($findCallback, $timeout, $timeoutStep)) { + $message = $elementLocator->getDescription() . " could not be found"; + if ($timeout > 0) { + $message = $message . " after $timeout seconds"; + } + throw new NoSuchElementException($message); + } + + return $element; + } + + /** + * Returns the ancestor element from which the given locator will be looked + * for. + * + * If the ancestor of the given locator is another locator the element for + * the ancestor locator is found and returned. If the ancestor of the given + * locator is already an element that element is the one returned. If the + * given locator has no ancestor then the base document element is returned. + * + * The timeout is used only when finding the element for the ancestor + * locator; if the timeout expires a NoSuchElementException is thrown. + * + * @param \Behat\Mink\Session $session the Mink Session to get the ancestor + * element from. + * @param Locator $elementLocator the locator for the element to get its + * ancestor. + * @param float $timeout the number of seconds (decimals allowed) to wait at + * most for the ancestor element to appear. + * @param float $timeoutStep the number of seconds (decimals allowed) to + * wait before trying to find the ancestor element again. + * @return \Behat\Mink\Element\Element the ancestor element found. + * @throws NoSuchElementException if the ancestor element can not be found. + */ + private static function findAncestorElement(\Behat\Mink\Session $session, Locator $elementLocator, $timeout, $timeoutStep) { + $ancestorElement = $elementLocator->getAncestor(); + if ($ancestorElement instanceof Locator) { + try { + $ancestorElement = self::findInternal($session, $ancestorElement, $timeout, $timeoutStep); + } catch (NoSuchElementException $exception) { + // Little hack to show the stack of ancestor elements that could + // not be found, as Behat only shows the message of the last + // exception in the chain. + $message = $exception->getMessage() . "\n" . + $elementLocator->getDescription() . " could not be found"; + if ($timeout > 0) { + $message = $message . " after $timeout seconds"; + } + throw new NoSuchElementException($message, $exception); + } + } + + if ($ancestorElement === null) { + $ancestorElement = $session->getPage(); + } + + return $ancestorElement; + } + + /** + * @var \Behat\Mink\Session + */ + private $session; + + /** + * @param Locator + */ + private $elementLocator; + + /** + * @var float + */ + private $timeout; + + /** + * @var float + */ + private $timeoutStep; + + /** + * Creates a new ElementFinder. + * + * @param \Behat\Mink\Session $session the Mink Session to get the element + * from. + * @param Locator $elementLocator the locator for the element. + * @param float $timeout the number of seconds (decimals allowed) to wait at + * most for the element to appear. + * @param float $timeoutStep the number of seconds (decimals allowed) to + * wait before trying to find the element again. + */ + public function __construct(\Behat\Mink\Session $session, Locator $elementLocator, $timeout, $timeoutStep) { + $this->session = $session; + $this->elementLocator = $elementLocator; + $this->timeout = $timeout; + $this->timeoutStep = $timeoutStep; + } + + /** + * Finds an element using the parameters set in the constructor of this + * ElementFinder. + * + * If the element, or its ancestors, can not be found a + * NoSuchElementException is thrown. + * + * @return \Behat\Mink\Element\Element the element found. + * @throws NoSuchElementException if the element, or its ancestor, can not + * be found. + */ + public function find() { + return self::findInternal($this->session, $this->elementLocator, $this->timeout, $this->timeoutStep); + } + +}