diff --git a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.js new file mode 100644 index 0000000..3fa82d9 --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_ajax_request.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_wait_for_ajax_request = { + 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/js_webassert_test.wait_for_element.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.js new file mode 100644 index 0000000..e11067b --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.wait_for_element.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_wait_for_element = { + attach: function (context) { + $('#js_webassert_test_element_invisible').show(); + } + }; + +})(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..3a17b1d --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.libraries.yml @@ -0,0 +1,14 @@ +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 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..087378b --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/src/Form/JsWebAssertTestForm.php @@ -0,0 +1,207 @@ +'; + $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_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) { + + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php b/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php index 94a9108..c2f553f 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; /** @@ -26,21 +28,140 @@ 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(); + + $result = $page->waitFor($timeout, 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, function() use ($page, $selector, $locator) { + if($element = $page->find($selector, $locator)) { + return $element->isVisible(); + } + 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. + * + * @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->waitForElementVisible('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..908d742 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php @@ -0,0 +1,82 @@ +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. + $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()); + + $test_wait_on_element_visible->click(); + $result = $page->findButton('Added WaitForElementVisible'); + $this->assertEmpty($result); + $result = $assert_session->waitForElementVisible('named', array('button', 'Added WaitForElementVisible')); + $this->assertNotEmpty($result); + $this->assertEquals(TRUE, $result->isVisible()); + } + +}