Chris@0: session->wait($timeout, $condition); Chris@0: if (!$result) { Chris@0: throw new \RuntimeException($message); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Waits for the specified selector and returns it when available. Chris@0: * Chris@0: * @param string $selector Chris@0: * The selector engine name. See ElementInterface::findAll() for the Chris@0: * supported selectors. Chris@0: * @param string|array $locator Chris@0: * The selector locator. Chris@0: * @param int $timeout Chris@0: * (Optional) Timeout in milliseconds, defaults to 10000. Chris@0: * Chris@0: * @return \Behat\Mink\Element\NodeElement|null Chris@0: * The page element node if found, NULL if not. Chris@0: * Chris@0: * @see \Behat\Mink\Element\ElementInterface::findAll() Chris@0: */ Chris@0: public function waitForElement($selector, $locator, $timeout = 10000) { Chris@0: $page = $this->session->getPage(); Chris@0: Chris@0: $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) { Chris@0: return $page->find($selector, $locator); Chris@0: }); Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Waits for the specified selector and returns it when available and visible. Chris@0: * Chris@0: * @param string $selector Chris@0: * The selector engine name. See ElementInterface::findAll() for the Chris@0: * supported selectors. Chris@0: * @param string|array $locator Chris@0: * The selector locator. Chris@0: * @param int $timeout Chris@0: * (Optional) Timeout in milliseconds, defaults to 10000. Chris@0: * Chris@0: * @return \Behat\Mink\Element\NodeElement|null Chris@0: * The page element node if found and visible, NULL if not. Chris@0: * Chris@0: * @see \Behat\Mink\Element\ElementInterface::findAll() Chris@0: */ Chris@0: public function waitForElementVisible($selector, $locator, $timeout = 10000) { Chris@0: $page = $this->session->getPage(); Chris@0: Chris@0: $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) { Chris@0: $element = $page->find($selector, $locator); Chris@0: if (!empty($element) && $element->isVisible()) { Chris@0: return $element; Chris@0: } Chris@0: return NULL; Chris@0: }); Chris@0: Chris@0: return $result; Chris@0: } Chris@17: Chris@17: /** Chris@17: * Waits for the specified text and returns its element when available. Chris@17: * Chris@17: * @param string $text Chris@17: * The text to wait for. Chris@17: * @param int $timeout Chris@17: * (Optional) Timeout in milliseconds, defaults to 10000. Chris@17: * Chris@17: * @return \Behat\Mink\Element\NodeElement|null Chris@17: * The page element node if found and visible, NULL if not. Chris@17: */ Chris@17: public function waitForText($text, $timeout = 10000) { Chris@17: $page = $this->session->getPage(); Chris@17: return $page->waitFor($timeout / 1000, function () use ($page, $text) { Chris@17: $actual = preg_replace('/\s+/u', ' ', $page->getText()); Chris@17: $regex = '/' . preg_quote($text, '/') . '/ui'; Chris@17: return (bool) preg_match($regex, $actual); Chris@17: }); Chris@17: } Chris@17: Chris@0: /** Chris@0: * Waits for a button (input[type=submit|image|button|reset], button) with Chris@0: * specified locator and returns it. Chris@0: * Chris@0: * @param string $locator Chris@0: * The button ID, value or alt string. Chris@0: * @param int $timeout Chris@0: * (Optional) Timeout in milliseconds, defaults to 10000. Chris@0: * Chris@0: * @return \Behat\Mink\Element\NodeElement|null Chris@0: * The page element node if found, NULL if not. Chris@0: */ Chris@0: public function waitForButton($locator, $timeout = 10000) { Chris@0: return $this->waitForElement('named', ['button', $locator], $timeout); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Waits for a link with specified locator and returns it when available. Chris@0: * Chris@0: * @param string $locator Chris@0: * The link ID, title, text or image alt. Chris@0: * @param int $timeout Chris@0: * (Optional) Timeout in milliseconds, defaults to 10000. Chris@0: * Chris@0: * @return \Behat\Mink\Element\NodeElement|null Chris@0: * The page element node if found, NULL if not. Chris@0: */ Chris@0: public function waitForLink($locator, $timeout = 10000) { Chris@0: return $this->waitForElement('named', ['link', $locator], $timeout); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Waits for a field with specified locator and returns it when available. Chris@0: * Chris@0: * @param string $locator Chris@0: * The input ID, name or label for the field (input, textarea, select). Chris@0: * @param int $timeout Chris@0: * (Optional) Timeout in milliseconds, defaults to 10000. Chris@0: * Chris@0: * @return \Behat\Mink\Element\NodeElement|null Chris@0: * The page element node if found, NULL if not. Chris@0: */ Chris@0: public function waitForField($locator, $timeout = 10000) { Chris@0: return $this->waitForElement('named', ['field', $locator], $timeout); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Waits for an element by its id and returns it when available. Chris@0: * Chris@0: * @param string $id Chris@0: * The element ID. Chris@0: * @param int $timeout Chris@0: * (Optional) Timeout in milliseconds, defaults to 10000. Chris@0: * Chris@0: * @return \Behat\Mink\Element\NodeElement|null Chris@0: * The page element node if found, NULL if not. Chris@0: */ Chris@0: public function waitForId($id, $timeout = 10000) { Chris@0: return $this->waitForElement('named', ['id', $id], $timeout); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Waits for the jQuery autocomplete delay duration. Chris@0: * Chris@0: * @see https://api.jqueryui.com/autocomplete/#option-delay Chris@0: */ Chris@0: public function waitOnAutocomplete() { Chris@0: // Wait for the autocomplete to be visible. Chris@0: return $this->waitForElementVisible('css', '.ui-autocomplete li'); Chris@0: } Chris@0: Chris@0: /** Chris@14: * Test that a node, or its specific corner, is visible in the viewport. Chris@0: * Chris@0: * Note: Always set the viewport size. This can be done with a PhantomJS Chris@0: * startup parameter or in your test with \Behat\Mink\Session->resizeWindow(). Chris@0: * Drupal CI Javascript tests by default use a viewport of 1024x768px. Chris@0: * Chris@0: * @param string $selector_type Chris@0: * The element selector type (CSS, XPath). Chris@0: * @param string|array $selector Chris@0: * The element selector. Note: the first found element is used. Chris@0: * @param bool|string $corner Chris@0: * (Optional) The corner to test: Chris@0: * topLeft, topRight, bottomRight, bottomLeft. Chris@0: * Or FALSE to check the complete element (default). Chris@0: * @param string $message Chris@0: * (optional) A message for the exception. Chris@0: * Chris@0: * @throws \Behat\Mink\Exception\ElementHtmlException Chris@0: * When the element doesn't exist. Chris@0: * @throws \Behat\Mink\Exception\ElementNotFoundException Chris@0: * When the element is not visible in the viewport. Chris@0: */ Chris@0: public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') { Chris@0: $node = $this->session->getPage()->find($selector_type, $selector); Chris@0: if ($node === NULL) { Chris@0: if (is_array($selector)) { Chris@0: $selector = implode(' ', $selector); Chris@0: } Chris@0: throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector); Chris@0: } Chris@0: Chris@0: // Check if the node is visible on the page, which is a prerequisite of Chris@0: // being visible in the viewport. Chris@0: if (!$node->isVisible()) { Chris@0: throw new ElementHtmlException($message, $this->session->getDriver(), $node); Chris@0: } Chris@0: Chris@0: $result = $this->checkNodeVisibilityInViewport($node, $corner); Chris@0: Chris@0: if (!$result) { Chris@0: throw new ElementHtmlException($message, $this->session->getDriver(), $node); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test that a node, or its specific corner, is not visible in the viewport. Chris@0: * Chris@0: * Note: the node should exist in the page, otherwise this assertion fails. Chris@0: * Chris@0: * @param string $selector_type Chris@0: * The element selector type (CSS, XPath). Chris@0: * @param string|array $selector Chris@0: * The element selector. Note: the first found element is used. Chris@0: * @param bool|string $corner Chris@0: * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft. Chris@0: * Or FALSE to check the complete element (default). Chris@0: * @param string $message Chris@0: * (optional) A message for the exception. Chris@0: * Chris@0: * @throws \Behat\Mink\Exception\ElementHtmlException Chris@0: * When the element doesn't exist. Chris@0: * @throws \Behat\Mink\Exception\ElementNotFoundException Chris@0: * When the element is not visible in the viewport. Chris@0: * Chris@0: * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport() Chris@0: */ Chris@0: public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') { Chris@0: $node = $this->session->getPage()->find($selector_type, $selector); Chris@0: if ($node === NULL) { Chris@0: if (is_array($selector)) { Chris@0: $selector = implode(' ', $selector); Chris@0: } Chris@0: throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector); Chris@0: } Chris@0: Chris@0: $result = $this->checkNodeVisibilityInViewport($node, $corner); Chris@0: Chris@0: if ($result) { Chris@0: throw new ElementHtmlException($message, $this->session->getDriver(), $node); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@14: * Check the visibility of a node, or its specific corner. Chris@0: * Chris@0: * @param \Behat\Mink\Element\NodeElement $node Chris@0: * A valid node. Chris@0: * @param bool|string $corner Chris@0: * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft. Chris@0: * Or FALSE to check the complete element (default). Chris@0: * Chris@0: * @return bool Chris@0: * Returns TRUE if the node is visible in the viewport, FALSE otherwise. Chris@0: * Chris@0: * @throws \Behat\Mink\Exception\UnsupportedDriverActionException Chris@0: * When an invalid corner specification is given. Chris@0: */ Chris@0: private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) { Chris@0: $xpath = $node->getXpath(); Chris@0: Chris@0: // Build the Javascript to test if the complete element or a specific corner Chris@0: // is in the viewport. Chris@0: switch ($corner) { Chris@0: case 'topLeft': Chris@0: $test_javascript_function = <<= 0 && Chris@0: r.top <= ly && Chris@0: r.left >= 0 && Chris@0: r.left <= lx Chris@0: ) Chris@0: } Chris@0: JS; Chris@0: break; Chris@0: Chris@0: case 'topRight': Chris@0: $test_javascript_function = <<= 0 && Chris@0: r.top <= ly && Chris@0: r.right >= 0 && Chris@0: r.right <= lx Chris@0: ); Chris@0: } Chris@0: JS; Chris@0: break; Chris@0: Chris@0: case 'bottomRight': Chris@0: $test_javascript_function = <<= 0 && Chris@0: r.bottom <= ly && Chris@0: r.right >= 0 && Chris@0: r.right <= lx Chris@0: ); Chris@0: } Chris@0: JS; Chris@0: break; Chris@0: Chris@0: case 'bottomLeft': Chris@0: $test_javascript_function = <<= 0 && Chris@0: r.bottom <= ly && Chris@0: r.left >= 0 && Chris@0: r.left <= lx Chris@0: ); Chris@0: } Chris@0: JS; Chris@0: break; Chris@0: Chris@0: case FALSE: Chris@0: $test_javascript_function = <<= 0 && Chris@0: r.left >= 0 && Chris@0: r.bottom <= ly && Chris@0: r.right <= lx Chris@0: ); Chris@0: } Chris@0: JS; Chris@0: break; Chris@0: Chris@0: // Throw an exception if an invalid corner parameter is given. Chris@0: default: Chris@0: throw new UnsupportedDriverActionException($corner, $this->session->getDriver()); Chris@0: } Chris@0: Chris@0: // Build the full Javascript test. The shared logic gets the corner Chris@0: // specific test logic injected. Chris@0: $full_javascript_visibility_test = <<session->evaluateScript($full_javascript_visibility_test); Chris@0: } Chris@0: Chris@0: }