Skip to content
Snippets Groups Projects
Commit 525c3d95 authored by catch's avatar catch
Browse files

Issue #3211164 by alexpott, catch: Random errors in Javascript Testing

parent c5e16af1
Branches
Tags
26 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!1896Issue #2940605: Can only intentionally re-render an entity with references 20 times,!1101Issue #2412669 by claudiu.cristea, Julfabre, sidharrell, catch, daffie,...,!1039Issue #2556069 by claudiu.cristea, bnjmnm, lauriii, pfrenssen, Tim Bozeman,...,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!1012Issue #3226887: Hreflang on non-canonical content pages,!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links,!825Issue #3211838 by mondrake, longwave: Convert assertions involving use of...,!795Issue #3212005 by guilhermevp, tedbow, phenaproxima: Add @throws docs to...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10,!748#1091852 Display Bug when using #states (Forms API) with Ajax Request,!731Add a new recipe to Umami demo - Chicken souvlaki and couscous,!730Issue #3211810 by alexpott, xjm, Spokje, Amber Himes Matz, Kristen Pol,...,!700Issue #3185165 by Spokje, vipin.mittal18, Suresh Prabhu Parkala, lauriii,...,!594Put each entity type table into a details element on admin/config/regional/content-language,!592Issue #2957953: Editing menus user-experience has regressed,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!512Issue #3207771: Menu UI node type form documentation points to non-existent function,!485Sets the autocomplete attribute for username/password input field on login form.,!449Issue #2784233: Allow multiple vocabularies in the taxonomy filter,!429[regression] Pages Manage Fields, Manage form, Manage display should include name of content type or entity,!231Issue #2671162: summary text wysiwyg patch working fine on 9.2.0-dev,!43Resolve #3173180: Add UI for 'loading' html attribute to images,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
......@@ -5,3 +5,11 @@ js_webassert_test.js_webassert_test_form:
_title: 'JsWebAssertForm'
requirements:
_access: 'TRUE'
js_webassert_test.js_webassert_test_page:
path: '/js_webassert_test_page'
defaults:
_controller: '\Drupal\js_webassert_test\Controller\TestController::page'
_title: 'JsWebAssertPage'
requirements:
_access: 'TRUE'
<?php
namespace Drupal\js_webassert_test\Controller;
use Drupal\Core\Render\Markup;
class TestController {
public function page() {
$markup = <<<JS
<script>
var timesRun = 0;
var interval = setInterval(function() {
timesRun += 1;
// Clear the interval after 1.1 seconds as this is longer than the time
// WebDriverCurlService would retry for if retries are enabled but shorter
// than the 10 seconds JSWebAssert::waitForElementVisible() waits for.
if (timesRun === 1100) {
clearInterval(interval);
}
var p = document.createElement("p");
var txt = document.createTextNode("New Text!! ".concat(timesRun));
p.setAttribute("id", "test_text");
p.appendChild(txt);
var div = document.getElementById("test_container");
div.replaceChild(p, div.childNodes[0]);
}, 1);
</script>
<div id="test_container">
<p id="test_text"></p>
</div>
JS;
return [
// Javascript should not be injected into a page this way unless in test
// code.
'#markup' => Markup::create($markup),
];
}
}
......@@ -2,11 +2,13 @@
namespace Drupal\FunctionalJavascriptTests;
use Behat\Mink\Element\Element;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\ElementHtmlException;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Drupal\Tests\WebAssert;
use WebDriver\Exception\CurlExec;
/**
* Defines a class with methods for asserting presence of elements during tests.
......@@ -64,13 +66,9 @@ function isAjaxing(instance) {
* @see \Behat\Mink\Element\ElementInterface::findAll()
*/
public function waitForElement($selector, $locator, $timeout = 10000) {
$page = $this->session->getPage();
$result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
return $this->waitForHelper($timeout, function (Element $page) use ($selector, $locator) {
return $page->find($selector, $locator);
});
return $result;
}
/**
......@@ -90,13 +88,9 @@ public function waitForElement($selector, $locator, $timeout = 10000) {
* @see \Behat\Mink\Element\ElementInterface::findAll()
*/
public function waitForElementRemoved($selector, $locator, $timeout = 10000) {
$page = $this->session->getPage();
$result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
return (bool) $this->waitForHelper($timeout, function (Element $page) use ($selector, $locator) {
return !$page->find($selector, $locator);
});
return $result;
}
/**
......@@ -116,17 +110,13 @@ public function waitForElementRemoved($selector, $locator, $timeout = 10000) {
* @see \Behat\Mink\Element\ElementInterface::findAll()
*/
public function waitForElementVisible($selector, $locator, $timeout = 10000) {
$page = $this->session->getPage();
$result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
return $this->waitForHelper($timeout, function (Element $page) use ($selector, $locator) {
$element = $page->find($selector, $locator);
if (!empty($element) && $element->isVisible()) {
return $element;
}
return NULL;
});
return $result;
}
/**
......@@ -137,18 +127,43 @@ public function waitForElementVisible($selector, $locator, $timeout = 10000) {
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 10000.
*
* @return \Behat\Mink\Element\NodeElement|null
* The page element node if found and visible, NULL if not.
* @return bool
* TRUE if not found, FALSE if found.
*/
public function waitForText($text, $timeout = 10000) {
$page = $this->session->getPage();
return $page->waitFor($timeout / 1000, function () use ($page, $text) {
return (bool) $this->waitForHelper($timeout, function (Element $page) use ($text) {
$actual = preg_replace('/\s+/u', ' ', $page->getText());
$regex = '/' . preg_quote($text, '/') . '/ui';
return (bool) preg_match($regex, $actual);
});
}
/**
* Wraps waits in a function to catch curl exceptions to continue waiting.
*
* @param int $timeout
* Timeout in milliseconds.
* @param callable $callback
* Callback, which result is both used as waiting condition and returned.
*
* @return mixed
* The result of $callback.
*/
private function waitForHelper(int $timeout, callable $callback) {
WebDriverCurlService::disableRetry();
$wrapper = function (Element $element) use ($callback) {
try {
return call_user_func($callback, $element);
}
catch (CurlExec $e) {
return NULL;
}
};
$result = $this->session->getPage()->waitFor($timeout / 1000, $wrapper);
WebDriverCurlService::enableRetry();
return $result;
}
/**
* Waits for a button (input[type=submit|image|button|reset], button) with
* specified locator and returns it.
......
......@@ -106,6 +106,12 @@ public function testJsWebAssert() {
$this->assertNotEmpty($result);
$this->assertInstanceOf(NodeElement::class, $result);
$this->assertEquals(TRUE, $result->isVisible());
$this->drupalGet('js_webassert_test_page');
$result = $assert_session->waitForElementVisible('named', ['id', 'test_text']);
$this->assertSame('test_text', $result->getAttribute('id'));
// Ensure that the javascript has replaced the element 1100 times.
$assert_session->pageTextContains('New Text!! 1100');
}
}
......@@ -14,6 +14,31 @@
*/
class WebDriverCurlService extends CurlService {
/**
* Flag that indicates if retries are enabled.
*
* @var bool
*/
private static $retry = TRUE;
/**
* Enables retries.
*
* This is useful if the caller is implementing it's own waiting process.
*/
public static function enableRetry() {
static::$retry = TRUE;
}
/**
* Disables retries.
*
* This is useful if the caller is implementing it's own waiting process.
*/
public static function disableRetry() {
static::$retry = FALSE;
}
/**
* {@inheritdoc}
*/
......@@ -22,7 +47,8 @@ public function execute($requestMethod, $url, $parameters = NULL, $extraOptions
CURLOPT_FAILONERROR => TRUE,
];
$retries = 0;
while ($retries < 10) {
$max_retries = static::$retry ? 10 : 1;
while ($retries < $max_retries) {
try {
$customHeaders = [
'Content-Type: application/json;charset=UTF-8',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment