diff --git a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.js new file mode 100644 index 0000000..40d4845 --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.js @@ -0,0 +1,22 @@ +/** + * @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 = { + attach: function (context) { + $('input[name="test_assert_wait_on_ajax_input"]').val('js_webassert_test'); + } + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.info.yml b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.info.yml new file mode 100644 index 0000000..787ef14 --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.info.yml @@ -0,0 +1,6 @@ +name: 'JS WebAssert test module' +type: module +description: 'Module for the JSWebAssert test.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.libraries.yml b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.libraries.yml new file mode 100644 index 0000000..02fad58 --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.libraries.yml @@ -0,0 +1,7 @@ +js_webassert_test: + version: VERSION + js: + js/js_webassert_test.js: {} + dependencies: + - core/jquery + - core/drupal diff --git a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.routing.yml b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.routing.yml new file mode 100644 index 0000000..2be8984 --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.routing.yml @@ -0,0 +1,7 @@ +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' diff --git a/core/modules/system/tests/modules/js_webassert_test/src/Form/JsWebAssertTestForm.php b/core/modules/system/tests/modules/js_webassert_test/src/Form/JsWebAssertTestForm.php new file mode 100644 index 0000000..b8d82a5 --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/src/Form/JsWebAssertTestForm.php @@ -0,0 +1,173 @@ +'; + $form['#suffix'] = ''; + + // 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_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 using the OpenModalDialogCommand and + // set the attachments for this Ajax response. + $form['#attached']['library'][] = 'js_webassert_test/js_webassert_test'; + + $form['test_assert_wait_on_ajax_input'] = [ + '#type' => 'textfield', + '#name' => 'test_assert_wait_on_ajax_input', + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php b/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php index 94a9108..642ce0c 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php @@ -6,6 +6,8 @@ use Behat\Mink\Exception\ElementHtmlException; use Behat\Mink\Exception\ElementNotFoundException; use Behat\Mink\Exception\UnsupportedDriverActionException; +use Behat\Mink\Selector\Xpath\Manipulator; +use Behat\Mink\Session; use Drupal\Tests\WebAssert; /** @@ -14,6 +16,19 @@ class JSWebAssert extends WebAssert { /** + * A XPath manipulator. + * + * @var \Behat\Mink\Selector\Xpath\Manipulator + */ + protected $xpathManipulator; + + public function __construct(Session $session, $base_url = '') { + parent::__construct($session, $base_url); + + $this->xpathManipulator = new Manipulator(); + } + + /** * Waits for AJAX request to be completed. * * @param int $timeout @@ -26,21 +41,114 @@ 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 = <<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(); + $xpath = $this->session->getSelectorsHandler()->selectorToXpath($selector, $locator); + $xpath = $this->xpathManipulator->prepend($xpath, $page->getXpath()); + + $condition = 'document.evaluate("' . str_replace(PHP_EOL, '', $xpath) . '", document, null, XPathResult.BOOLEAN_TYPE, null).booleanValue'; + $this->session->wait($timeout, $condition); + + return $page->find($selector, $locator); + } + + /** + * 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. + * + * @return \Behat\Mink\Element\NodeElement|null + * The page element node if found, NULL if not. + */ + public function waitForButton($locator) { + return $this->waitForElement('named', array('button', $locator)); + } + + /** + * Waits for a link with specified locator and returns it when available. + * + * @param string $locator + * The link ID, title, text or image alt. + * + * @return \Behat\Mink\Element\NodeElement|null + * The page element node if found, NULL if not. + */ + public function waitForLink($locator) { + return $this->waitForElement('named', array('link', $locator)); + } + + /** + * 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). + * + * @return \Behat\Mink\Element\NodeElement|null + * The page element node if found, NULL if not. + */ + public function waitForField($locator) { + return $this->waitForElement('named', array('field', $locator)); + } + + /** + * Waits for an element by its id and returns it when available. + * + * @param string $id + * The element ID. + * + * @return \Behat\Mink\Element\NodeElement|null + * The page element node if found, NULL if not. + */ + public function waitForId($id) { + return $this->waitForElement('named', array('id', $id)); + } + + /** * 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->waitForElement('css', '.ui-autocomplete li'); } /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php new file mode 100644 index 0000000..b9d125a --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php @@ -0,0 +1,74 @@ +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 the wait...() methods by first checking the fields aren't available + // and then are available after the wait method. + $test_button->click(); + $result = $page->findButton('Added button'); + $this->assertEmpty($result); + $result = $assert_session->waitForButton('Added button'); + $this->assertNotEmpty($result); + + $test_link->click(); + $result = $page->findLink('Added link'); + $this->assertEmpty($result); + $result = $assert_session->waitForLink('Added link'); + $this->assertNotEmpty($result); + + $test_field->click(); + $result = $page->findField('added_field'); + $this->assertEmpty($result); + $result = $assert_session->waitForField('added_field'); + $this->assertNotEmpty($result); + + $test_id->click(); + $result = $page->findById('js_webassert_test_field_id'); + $this->assertEmpty($result); + $result = $assert_session->waitForId('js_webassert_test_field_id'); + $this->assertNotEmpty($result); + + // Test waitOnAjaxRequest. Verify the element is available after the wait + // and the behaviors have run on completing by checking the value. + $test_wait_on_ajax->click(); + $result = $page->findField('test_assert_wait_on_ajax_input'); + $this->assertEmpty($result); + $assert_session->assertWaitOnAjaxRequest(); + $result = $page->findField('test_assert_wait_on_ajax_input'); + $this->assertNotEmpty($result); + $this->assertEquals('js_webassert_test', $result->getValue()); + } + +}