Commit 3af641f1 authored by alexpott's avatar alexpott

Issue #2837676 by michielnugter, droplet, alexpott, Lendude, jibran, klausi,...

Issue #2837676 by michielnugter, droplet, alexpott, Lendude, jibran, klausi, Wim Leers: Provide a better way to validate all javascript activity is completed
parent e14a020d
/**
* @file
* Testing behavior for JSWebAssertTest.
*/
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Makes changes in the DOM to be able to test the completion of AJAX in assertWaitOnAjaxRequest.
*/
Drupal.behaviors.js_webassert_test_wait_for_ajax_request = {
attach: function (context) {
$('input[name="test_assert_wait_on_ajax_input"]').val('js_webassert_test');
}
};
})(jQuery, Drupal, drupalSettings);
/**
* @file
* Testing behavior for JSWebAssertTest.
*/
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Makes changes in the DOM to be able to test the completion of AJAX in assertWaitOnAjaxRequest.
*/
Drupal.behaviors.js_webassert_test_wait_for_element = {
attach: function (context) {
$('#js_webassert_test_element_invisible').show();
}
};
})(jQuery, Drupal, drupalSettings);
name: 'JS WebAssert test module'
type: module
description: 'Module for the JSWebAssert test.'
package: Testing
version: VERSION
core: 8.x
wait_for_ajax_request:
version: VERSION
js:
js/js_webassert_test.wait_for_ajax_request.js: {}
dependencies:
- core/jquery
- core/drupal
wait_for_element:
version: VERSION
js:
js/js_webassert_test.wait_for_element.js: {}
dependencies:
- core/jquery
- core/drupal
js_webassert_test.js_webassert_test_form:
path: '/js_webassert_test_form'
defaults:
_form: 'Drupal\js_webassert_test\Form\JsWebAssertTestForm'
_title: 'JsWebAssertForm'
requirements:
_access: 'TRUE'
<?php
namespace Drupal\js_webassert_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Test form for JSWebAssert JavaScriptTestBase.
*/
class JsWebAssertTestForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'js_webassert_test_form';
}
/**
* Form for testing the addition of various types of elements via AJAX.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['#prefix'] = '<div id="js_webassert_test_form_wrapper">';
$form['#suffix'] = '</div>';
// Button to test for the waitForButton() assertion.
$form['test_button'] = [
'#type' => 'submit',
'#value' => $this->t('Add button'),
'#button_type' => 'primary',
'#ajax' => [
'callback' => 'Drupal\js_webassert_test\Form\JsWebAssertTestForm::addButton',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
'wrapper' => 'js_webassert_test_form_wrapper',
],
];
// Button to test for the waitForLink() assertion.
$form['test_link'] = [
'#type' => 'submit',
'#value' => $this->t('Add link'),
'#button_type' => 'primary',
'#ajax' => [
'callback' => 'Drupal\js_webassert_test\Form\JsWebAssertTestForm::addLink',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
'wrapper' => 'js_webassert_test_form_wrapper',
],
];
// Button to test for the waitForField() assertion.
$form['test_field'] = [
'#type' => 'submit',
'#value' => $this->t('Add field'),
'#button_type' => 'primary',
'#ajax' => [
'callback' => 'Drupal\js_webassert_test\Form\JsWebAssertTestForm::addField',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
'wrapper' => 'js_webassert_test_form_wrapper',
],
];
// Button to test for the waitForId() assertion.
$form['test_id'] = [
'#type' => 'submit',
'#value' => $this->t('Add ID'),
'#button_type' => 'primary',
'#ajax' => [
'callback' => 'Drupal\js_webassert_test\Form\JsWebAssertTestForm::addId',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
'wrapper' => 'js_webassert_test_form_wrapper',
],
];
// Button to test the assertWaitOnAjaxRequest() assertion.
$form['test_wait_for_element_visible'] = [
'#type' => 'submit',
'#value' => $this->t('Test waitForElementVisible'),
'#button_type' => 'primary',
'#ajax' => [
'callback' => 'Drupal\js_webassert_test\Form\JsWebAssertTestForm::addWaitForElementVisible',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
'wrapper' => 'js_webassert_test_form_wrapper',
],
];
// Button to test the assertWaitOnAjaxRequest() assertion.
$form['test_assert_wait_on_ajax_request'] = [
'#type' => 'submit',
'#value' => $this->t('Test assertWaitOnAjaxRequest'),
'#button_type' => 'primary',
'#ajax' => [
'callback' => 'Drupal\js_webassert_test\Form\JsWebAssertTestForm::addAssertWaitOnAjaxRequest',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
'wrapper' => 'js_webassert_test_form_wrapper',
],
];
return $form;
}
/**
* Ajax callback for the "Add button" button.
*/
public static function addButton(array $form, FormStateInterface $form_state) {
$form['added_button'] = [
'#type' => 'submit',
'#value' => 'Added button',
'#button_type' => 'primary',
];
return $form;
}
/**
* Ajax callback for the "Add link" button.
*/
public static function addLink(array $form, FormStateInterface $form_state) {
$form['added_link'] = [
'#title' => 'Added link',
'#type' => 'link',
'#url' => Url::fromRoute('js_webassert_test.js_webassert_test_form')
];
return $form;
}
/**
* Ajax callback for the "Add field" button.
*/
public static function addField(array $form, FormStateInterface $form_state) {
$form['added_field'] = [
'#type' => 'textfield',
'#title' => 'Added textfield',
'#name' => 'added_field',
];
return $form;
}
/**
* Ajax callback for the "Add ID" button.
*/
public static function addId(array $form, FormStateInterface $form_state) {
$form['added_id'] = [
'#id' => 'js_webassert_test_field_id',
'#type' => 'submit',
'#value' => 'Added ID',
'#button_type' => 'primary',
];
return $form;
}
/**
* Ajax callback for the "Test waitForAjax" button.
*/
public static function addAssertWaitOnAjaxRequest(array $form, FormStateInterface $form_state) {
// Attach the library necessary for this test.
$form['#attached']['library'][] = 'js_webassert_test/wait_for_ajax_request';
$form['test_assert_wait_on_ajax_input'] = [
'#type' => 'textfield',
'#name' => 'test_assert_wait_on_ajax_input',
];
return $form;
}
/**
* Ajax callback for the "Test waitForElementVisible" button.
*/
public static function addWaitForElementVisible(array $form, FormStateInterface $form_state) {
// Attach the library necessary for this test.
$form['#attached']['library'][] = 'js_webassert_test/wait_for_element';
$form['element_invisible'] = [
'#id' => 'js_webassert_test_element_invisible',
'#type' => 'submit',
'#value' => 'Added WaitForElementVisible',
'#button_type' => 'primary',
'#attributes' => [
'style' => ['display: none;'],
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}
......@@ -26,21 +26,149 @@ class JSWebAssert extends WebAssert {
* be displayed.
*/
public function assertWaitOnAjaxRequest($timeout = 10000, $message = 'Unable to complete AJAX request.') {
$result = $this->session->wait($timeout, '(typeof(jQuery)=="undefined" || (0 === jQuery.active && 0 === jQuery(\':animated\').length))');
$condition = <<<JS
(function() {
function isAjaxing(instance) {
return instance && instance.ajaxing === true;
}
return (
// Assert no AJAX request is running (via jQuery or Drupal) and no
// animation is running.
(typeof jQuery === 'undefined' || (jQuery.active === 0 && jQuery(':animated').length === 0)) &&
(typeof Drupal === 'undefined' || typeof Drupal.ajax === 'undefined' || !Drupal.ajax.instances.some(isAjaxing))
);
}());
JS;
$result = $this->session->wait($timeout, $condition);
if (!$result) {
throw new \RuntimeException($message);
}
}
/**
* Waits for the specified selector and returns it when available.
*
* @param string $selector
* The selector engine name. See ElementInterface::findAll() for the
* supported selectors.
* @param string|array $locator
* The selector locator.
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 10000.
*
* @return \Behat\Mink\Element\NodeElement|null
* The page element node if found, NULL if not.
*
* @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 $page->find($selector, $locator);
});
return $result;
}
/**
* Waits for the specified selector and returns it when available and visible.
*
* @param string $selector
* The selector engine name. See ElementInterface::findAll() for the
* supported selectors.
* @param string|array $locator
* The selector locator.
* @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.
*
* @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) {
$element = $page->find($selector, $locator);
if (!empty($element) && $element->isVisible()) {
return $element;
}
return NULL;
});
return $result;
}
/**
* Waits for a button (input[type=submit|image|button|reset], button) with
* specified locator and returns it.
*
* @param string $locator
* The button ID, value or alt string.
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 10000.
*
* @return \Behat\Mink\Element\NodeElement|null
* The page element node if found, NULL if not.
*/
public function waitForButton($locator, $timeout = 10000) {
return $this->waitForElement('named', array('button', $locator), $timeout);
}
/**
* Waits for a link with specified locator and returns it when available.
*
* @param string $locator
* The link ID, title, text or image alt.
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 10000.
*
* @return \Behat\Mink\Element\NodeElement|null
* The page element node if found, NULL if not.
*/
public function waitForLink($locator, $timeout = 10000) {
return $this->waitForElement('named', array('link', $locator), $timeout);
}
/**
* Waits for a field with specified locator and returns it when available.
*
* @param string $locator
* The input ID, name or label for the field (input, textarea, select).
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 10000.
*
* @return \Behat\Mink\Element\NodeElement|null
* The page element node if found, NULL if not.
*/
public function waitForField($locator, $timeout = 10000) {
return $this->waitForElement('named', array('field', $locator), $timeout);
}
/**
* Waits for an element by its id and returns it when available.
*
* @param string $id
* The element ID.
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 10000.
*
* @return \Behat\Mink\Element\NodeElement|null
* The page element node if found, NULL if not.
*/
public function waitForId($id, $timeout = 10000) {
return $this->waitForElement('named', array('id', $id), $timeout);
}
/**
* Waits for the jQuery autocomplete delay duration.
*
* @see https://api.jqueryui.com/autocomplete/#option-delay
*/
public function waitOnAutocomplete() {
// Drupal is using the default delay value of 300 milliseconds.
$this->session->wait(300);
$this->assertWaitOnAjaxRequest();
// Wait for the autocomplete to be visible.
return $this->waitForElementVisible('css', '.ui-autocomplete li');
}
/**
......
<?php
namespace Drupal\FunctionalJavascriptTests\Tests;
use Behat\Mink\Element\NodeElement;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Tests for the JSWebAssert class.
*
* @group javascript
*/
class JSWebAssertTest extends JavascriptTestBase {
/**
* Required modules.
*
* @var array
*/
public static $modules = ['js_webassert_test'];
/**
* Tests that JSWebAssert assertions work correctly.
*/
public function testJsWebAssert() {
$this->drupalGet('js_webassert_test_form');
$session = $this->getSession();
$assert_session = $this->assertSession();
$page = $session->getPage();
$test_button = $page->findButton('Add button');
$test_link = $page->findButton('Add link');
$test_field = $page->findButton('Add field');
$test_id = $page->findButton('Add ID');
$test_wait_on_ajax = $page->findButton('Test assertWaitOnAjaxRequest');
$test_wait_on_element_visible = $page->findButton('Test waitForElementVisible');
// Test the wait...() methods by first checking the fields aren't available
// and then are available after the wait method.
$result = $page->findButton('Added button');
$this->assertEmpty($result);
$test_button->click();
$result = $assert_session->waitForButton('Added button');
$this->assertNotEmpty($result);
$this->assertTrue($result instanceof NodeElement);
$result = $page->findLink('Added link');
$this->assertEmpty($result);
$test_link->click();
$result = $assert_session->waitForLink('Added link');
$this->assertNotEmpty($result);
$this->assertTrue($result instanceof NodeElement);
$result = $page->findField('added_field');
$this->assertEmpty($result);
$test_field->click();
$result = $assert_session->waitForField('added_field');
$this->assertNotEmpty($result);
$this->assertTrue($result instanceof NodeElement);
$result = $page->findById('js_webassert_test_field_id');
$this->assertEmpty($result);
$test_id->click();
$result = $assert_session->waitForId('js_webassert_test_field_id');
$this->assertNotEmpty($result);
$this->assertTrue($result instanceof NodeElement);
// Test waitOnAjaxRequest. Verify the element is available after the wait
// and the behaviors have run on completing by checking the value.
$result = $page->findField('test_assert_wait_on_ajax_input');
$this->assertEmpty($result);
$test_wait_on_ajax->click();
$assert_session->assertWaitOnAjaxRequest();
$result = $page->findField('test_assert_wait_on_ajax_input');
$this->assertNotEmpty($result);
$this->assertTrue($result instanceof NodeElement);
$this->assertEquals('js_webassert_test', $result->getValue());
$result = $page->findButton('Added WaitForElementVisible');
$this->assertEmpty($result);
$test_wait_on_element_visible->click();
$result = $assert_session->waitForElementVisible('named', array('button', 'Added WaitForElementVisible'));
$this->assertNotEmpty($result);
$this->assertTrue($result instanceof NodeElement);
$this->assertEquals(TRUE, $result->isVisible());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment