. * */ /** * 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; } /** * Returns the description of the element to find. * * @return string the description of the element to find. */ public function getDescription() { return $this->elementLocator->getDescription(); } /** * Returns the timeout. * * @return float the number of seconds (decimals allowed) to wait at most * for the element to appear. */ public function getTimeout() { return $this->timeout; } /** * Returns the timeout step. * * @return float the number of seconds (decimals allowed) to wait before * trying to find the element again. */ public function getTimeoutStep() { return $this->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); } }