From e4284d2d91a1188a8ed8f59112e65e375f50e68f Mon Sep 17 00:00:00 2001
From: effulgentsia <alex.bronstein@acquia.com>
Date: Wed, 30 Jun 2021 14:26:18 -0700
Subject: [PATCH] Issue #3091870 by tedbow, phenaproxima, alexpott, bnjmnm,
 tim.plunkett, lauriii, mradcliffe: Fail Functional Javascript tests that
 throw Javascript errors

---
 .../MediaEmbedFilterConfigurationUiTest.php   |  7 ++++
 .../js_errors_test/js/js_errors_test.es6.js   | 11 ++++++
 .../js_errors_test/js/js_errors_test.js       | 16 ++++++++
 .../js_errors_test/js_errors_test.info.yml    |  5 +++
 .../js_errors_test.libraries.yml              |  6 +++
 .../js_errors_test/js_errors_test.routing.yml |  7 ++++
 .../src/Controller/JsErrorsTestController.php | 22 +++++++++++
 .../js/js_testing_log.es6.js                  | 35 +++++++++++++++++
 .../js_testing_log_test/js/js_testing_log.js  | 28 ++++++++++++++
 .../js_testing_log_test.info.yml}             |  0
 .../js_testing_log_test.libraries.yml}        |  2 +-
 .../js_testing_log_test.module}               |  6 +--
 .../PasswordWidgetThemeFunctionTest.php       |  7 ++++
 .../nightwatch_testing.info.yml               |  2 +-
 .../JavascriptErrorsSuppressionTest.php       | 38 +++++++++++++++++++
 .../JavascriptErrorsTest.php                  | 35 +++++++++++++++++
 .../WebDriverTestBase.php                     | 19 +++++++++-
 .../Assertions/deprecationErrorExists.js      |  2 +-
 .../Assertions/noDeprecationErrors.js         |  2 +-
 19 files changed, 241 insertions(+), 9 deletions(-)
 create mode 100644 core/modules/system/tests/modules/js_errors_test/js/js_errors_test.es6.js
 create mode 100644 core/modules/system/tests/modules/js_errors_test/js/js_errors_test.js
 create mode 100644 core/modules/system/tests/modules/js_errors_test/js_errors_test.info.yml
 create mode 100644 core/modules/system/tests/modules/js_errors_test/js_errors_test.libraries.yml
 create mode 100644 core/modules/system/tests/modules/js_errors_test/js_errors_test.routing.yml
 create mode 100644 core/modules/system/tests/modules/js_errors_test/src/Controller/JsErrorsTestController.php
 create mode 100644 core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.es6.js
 create mode 100644 core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.js
 rename core/modules/system/tests/modules/{js_deprecation_log_test/js_deprecation_log_test.info.yml => js_testing_log_test/js_testing_log_test.info.yml} (100%)
 rename core/modules/system/tests/modules/{js_deprecation_log_test/js_deprecation_log_test.libraries.yml => js_testing_log_test/js_testing_log_test.libraries.yml} (61%)
 rename core/modules/system/tests/modules/{js_deprecation_log_test/js_deprecation_log_test.module => js_testing_log_test/js_testing_log_test.module} (55%)
 create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php
 create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php

diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
index efae01fc10e4..13513802d783 100644
--- a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
@@ -15,6 +15,13 @@ class MediaEmbedFilterConfigurationUiTest extends MediaJavascriptTestBase {
    */
   protected $defaultTheme = 'stark';
 
+  /**
+   * {@inheritdoc}
+   *
+   * @todo Remove this class property in https://www.drupal.org/node/3091878/.
+   */
+  protected $failOnJavascriptConsoleErrors = FALSE;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/system/tests/modules/js_errors_test/js/js_errors_test.es6.js b/core/modules/system/tests/modules/js_errors_test/js/js_errors_test.es6.js
new file mode 100644
index 000000000000..421d036ee850
--- /dev/null
+++ b/core/modules/system/tests/modules/js_errors_test/js/js_errors_test.es6.js
@@ -0,0 +1,11 @@
+/**
+ * @file
+ *  Testing tools for JavaScript errors.
+ */
+(function ({ throwError, behaviors }) {
+  behaviors.testErrors = {
+    attach: () => {
+      throwError(new Error('A manually thrown error.'));
+    },
+  };
+})(Drupal);
diff --git a/core/modules/system/tests/modules/js_errors_test/js/js_errors_test.js b/core/modules/system/tests/modules/js_errors_test/js/js_errors_test.js
new file mode 100644
index 000000000000..a3357d06f801
--- /dev/null
+++ b/core/modules/system/tests/modules/js_errors_test/js/js_errors_test.js
@@ -0,0 +1,16 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function (_ref) {
+  var throwError = _ref.throwError,
+      behaviors = _ref.behaviors;
+  behaviors.testErrors = {
+    attach: function attach() {
+      throwError(new Error('A manually thrown error.'));
+    }
+  };
+})(Drupal);
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/js_errors_test/js_errors_test.info.yml b/core/modules/system/tests/modules/js_errors_test/js_errors_test.info.yml
new file mode 100644
index 000000000000..64c9225341eb
--- /dev/null
+++ b/core/modules/system/tests/modules/js_errors_test/js_errors_test.info.yml
@@ -0,0 +1,5 @@
+name: 'JS Errors test'
+description: 'Provides a JavaScript error that can be used for tests'
+type: module
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/js_errors_test/js_errors_test.libraries.yml b/core/modules/system/tests/modules/js_errors_test/js_errors_test.libraries.yml
new file mode 100644
index 000000000000..3454e7cb6344
--- /dev/null
+++ b/core/modules/system/tests/modules/js_errors_test/js_errors_test.libraries.yml
@@ -0,0 +1,6 @@
+errors_test:
+  version: VERSION
+  js:
+    js/js_errors_test.js: {}
+  dependencies:
+    - core/drupal
diff --git a/core/modules/system/tests/modules/js_errors_test/js_errors_test.routing.yml b/core/modules/system/tests/modules/js_errors_test/js_errors_test.routing.yml
new file mode 100644
index 000000000000..d096284e2529
--- /dev/null
+++ b/core/modules/system/tests/modules/js_errors_test/js_errors_test.routing.yml
@@ -0,0 +1,7 @@
+js_errors_test.errors:
+  path: '/js_errors_test'
+  defaults:
+    _controller: '\Drupal\js_errors_test\Controller\JsErrorsTestController::jsErrorsTest'
+    _title: 'JsErrorsTest'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/js_errors_test/src/Controller/JsErrorsTestController.php b/core/modules/system/tests/modules/js_errors_test/src/Controller/JsErrorsTestController.php
new file mode 100644
index 000000000000..99628a272c57
--- /dev/null
+++ b/core/modules/system/tests/modules/js_errors_test/src/Controller/JsErrorsTestController.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\js_errors_test\Controller;
+
+/**
+ * Test Controller loading js_errors_test/errors_test library.
+ */
+class JsErrorsTestController {
+
+  /**
+   * Renders page that has js_errors_test/errors_test library attached.
+   *
+   * @return string[][]
+   *   Render array.
+   */
+  public function jsErrorsTest(): array {
+    return [
+      '#attached' => ['library' => ['js_errors_test/errors_test']],
+    ];
+  }
+
+}
diff --git a/core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.es6.js b/core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.es6.js
new file mode 100644
index 000000000000..dd6fe578ed36
--- /dev/null
+++ b/core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.es6.js
@@ -0,0 +1,35 @@
+/**
+ * @file
+ *  Support code for testing JavaScript error handling in functional tests.
+ */
+(function (Drupal) {
+  if (typeof console !== 'undefined' && console.warn) {
+    const originalWarnFunction = console.warn;
+    console.warn = (warning) => {
+      const warnings = JSON.parse(
+        sessionStorage.getItem('js_testing_log_test.warnings') ||
+          JSON.stringify([]),
+      );
+      warnings.push(warning);
+      sessionStorage.setItem(
+        'js_testing_log_test.warnings',
+        JSON.stringify(warnings),
+      );
+      originalWarnFunction(warning);
+    };
+
+    const originalThrowFunction = Drupal.throwError;
+    Drupal.throwError = (error) => {
+      const errors = JSON.parse(
+        sessionStorage.getItem('js_testing_log_test.errors') ||
+          JSON.stringify([]),
+      );
+      errors.push(error.stack);
+      sessionStorage.setItem(
+        'js_testing_log_test.errors',
+        JSON.stringify(errors),
+      );
+      originalThrowFunction(error);
+    };
+  }
+})(Drupal);
diff --git a/core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.js b/core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.js
new file mode 100644
index 000000000000..2a5348723b0d
--- /dev/null
+++ b/core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.js
@@ -0,0 +1,28 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function (Drupal) {
+  if (typeof console !== 'undefined' && console.warn) {
+    var originalWarnFunction = console.warn;
+
+    console.warn = function (warning) {
+      var warnings = JSON.parse(sessionStorage.getItem('js_testing_log_test.warnings') || JSON.stringify([]));
+      warnings.push(warning);
+      sessionStorage.setItem('js_testing_log_test.warnings', JSON.stringify(warnings));
+      originalWarnFunction(warning);
+    };
+
+    var originalThrowFunction = Drupal.throwError;
+
+    Drupal.throwError = function (error) {
+      var errors = JSON.parse(sessionStorage.getItem('js_testing_log_test.errors') || JSON.stringify([]));
+      errors.push(error.stack);
+      sessionStorage.setItem('js_testing_log_test.errors', JSON.stringify(errors));
+      originalThrowFunction(error);
+    };
+  }
+})(Drupal);
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.info.yml b/core/modules/system/tests/modules/js_testing_log_test/js_testing_log_test.info.yml
similarity index 100%
rename from core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.info.yml
rename to core/modules/system/tests/modules/js_testing_log_test/js_testing_log_test.info.yml
diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml b/core/modules/system/tests/modules/js_testing_log_test/js_testing_log_test.libraries.yml
similarity index 61%
rename from core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml
rename to core/modules/system/tests/modules/js_testing_log_test/js_testing_log_test.libraries.yml
index 1744815c9861..d02c19893cee 100644
--- a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml
+++ b/core/modules/system/tests/modules/js_testing_log_test/js_testing_log_test.libraries.yml
@@ -1,6 +1,6 @@
 deprecation_log:
   version: VERSION
   js:
-    js/js_deprecation_log.js: { weight: -100 }
+    js/js_testing_log.js: {}
   dependencies:
     - core/drupal
diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module b/core/modules/system/tests/modules/js_testing_log_test/js_testing_log_test.module
similarity index 55%
rename from core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module
rename to core/modules/system/tests/modules/js_testing_log_test/js_testing_log_test.module
index a86a2f21fbd2..d140ecfdaff7 100644
--- a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module
+++ b/core/modules/system/tests/modules/js_testing_log_test/js_testing_log_test.module
@@ -8,14 +8,14 @@
 /**
  * Implements hook_page_attachments().
  */
-function js_deprecation_log_test_page_attachments(array &$attachments) {
+function js_testing_log_test_page_attachments(array &$attachments) {
   // Unconditionally attach an asset to the page.
-  $attachments['#attached']['library'][] = 'js_deprecation_log_test/deprecation_log';
+  $attachments['#attached']['library'][] = 'js_testing_log_test/deprecation_log';
 }
 
 /**
  * Implements hook_js_settings_alter().
  */
-function js_deprecation_log_test_js_settings_alter(&$settings) {
+function js_testing_log_test_js_settings_alter(&$settings) {
   $settings['suppressDeprecationErrors'] = FALSE;
 }
diff --git a/core/modules/user/tests/src/FunctionalJavascript/PasswordWidgetThemeFunctionTest.php b/core/modules/user/tests/src/FunctionalJavascript/PasswordWidgetThemeFunctionTest.php
index af11642b9814..0794c9e42398 100644
--- a/core/modules/user/tests/src/FunctionalJavascript/PasswordWidgetThemeFunctionTest.php
+++ b/core/modules/user/tests/src/FunctionalJavascript/PasswordWidgetThemeFunctionTest.php
@@ -15,6 +15,13 @@ class PasswordWidgetThemeFunctionTest extends WebDriverTestBase {
    */
   protected $defaultTheme = 'password_theme_function_test';
 
+  /**
+   * {@inheritdoc}
+   *
+   * @todo Remove this class property in https://www.drupal.org/node/3217947.
+   */
+  protected $failOnJavascriptConsoleErrors = FALSE;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/profiles/nightwatch_testing/nightwatch_testing.info.yml b/core/profiles/nightwatch_testing/nightwatch_testing.info.yml
index 04289408ed93..6e3f05ad741c 100644
--- a/core/profiles/nightwatch_testing/nightwatch_testing.info.yml
+++ b/core/profiles/nightwatch_testing/nightwatch_testing.info.yml
@@ -4,4 +4,4 @@ description: 'Minimal profile for running Nightwatch tests. Includes absolutely
 version: VERSION
 hidden: true
 install:
-  - js_deprecation_log_test
+  - js_testing_log_test
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php
new file mode 100644
index 000000000000..ad0bd4821759
--- /dev/null
+++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\FunctionalJavascriptTests;
+
+/**
+ * Tests that Drupal.throwError can be suppressed to allow a test to pass.
+ *
+ * @group javascript
+ */
+class JavascriptErrorsSuppressionTest extends WebDriverTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['js_errors_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $failOnJavascriptConsoleErrors = FALSE;
+
+  /**
+   * Tests that JavaScript console errors can be suppressed.
+   */
+  public function testJavascriptErrors(): void {
+    // Visit page that will throw a JavaScript console error.
+    $this->drupalGet('js_errors_test');
+    // Ensure that errors from previous page loads will be
+    // detected.
+    $this->drupalGet('user');
+  }
+
+}
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php
new file mode 100644
index 000000000000..bb020b2918fc
--- /dev/null
+++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\FunctionalJavascriptTests;
+
+/**
+ * Tests that Drupal.throwError will cause a deprecation warning.
+ *
+ * @group javascript
+ * @group legacy
+ */
+class JavascriptErrorsTest extends WebDriverTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['js_errors_test'];
+
+  /**
+   * Tests that JavaScript console errors will result in a deprecation warning.
+   */
+  public function testJavascriptErrors(): void {
+    $this->expectDeprecation('Not failing JavaScript test for JavaScript errors is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. This test had the following JavaScript errors: Error: A manually thrown error.');
+    // Visit page that will throw a JavaScript console error.
+    $this->drupalGet('js_errors_test');
+    // Ensure that errors from previous page loads will be
+    // detected.
+    $this->drupalGet('user');
+  }
+
+}
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php b/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
index 516062a80409..20f047827351 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
@@ -16,6 +16,13 @@
  */
 abstract class WebDriverTestBase extends BrowserTestBase {
 
+  /**
+   * Determines if a test should fail on JavaScript console errors.
+   *
+   * @var bool
+   */
+  protected $failOnJavascriptConsoleErrors = TRUE;
+
   /**
    * Disables CSS animations in tests for more reliable testing.
    *
@@ -61,7 +68,7 @@ protected function initMink() {
    */
   protected function installModulesFromClassProperty(ContainerInterface $container) {
     self::$modules = [
-      'js_deprecation_log_test',
+      'js_testing_log_test',
       'jquery_keyevent_polyfill_test',
     ];
     if ($this->disableCssAnimations) {
@@ -102,12 +109,20 @@ protected function tearDown() {
         throw new \RuntimeException('Unfinished AJAX requests while tearing down a test');
       }
 
-      $warnings = $this->getSession()->evaluateScript("JSON.parse(sessionStorage.getItem('js_deprecation_log_test.warnings') || JSON.stringify([]))");
+      $warnings = $this->getSession()->evaluateScript("JSON.parse(sessionStorage.getItem('js_testing_log_test.warnings') || JSON.stringify([]))");
       foreach ($warnings as $warning) {
         if (strpos($warning, '[Deprecation]') === 0) {
           @trigger_error('Javascript Deprecation:' . substr($warning, 13), E_USER_DEPRECATED);
         }
       }
+      if ($this->failOnJavascriptConsoleErrors) {
+        $errors = $this->getSession()->evaluateScript("JSON.parse(sessionStorage.getItem('js_testing_log_test.errors') || JSON.stringify([]))");
+        if (!empty($errors)) {
+          $all_errors = implode("\n", $errors);
+          @trigger_error("Not failing JavaScript test for JavaScript errors is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. This test had the following JavaScript errors: $all_errors. See https://www.drupal.org/node/3221100", E_USER_DEPRECATED);
+        }
+      }
+
     }
     parent::tearDown();
   }
diff --git a/core/tests/Drupal/Nightwatch/Assertions/deprecationErrorExists.js b/core/tests/Drupal/Nightwatch/Assertions/deprecationErrorExists.js
index 999b8083e79e..0b8b9f64f56c 100644
--- a/core/tests/Drupal/Nightwatch/Assertions/deprecationErrorExists.js
+++ b/core/tests/Drupal/Nightwatch/Assertions/deprecationErrorExists.js
@@ -18,6 +18,6 @@ module.exports.assertion = function (expected) {
   this.command = (callback) =>
     // eslint-disable-next-line prefer-arrow-callback
     this.api.execute(function () {
-      return window.sessionStorage.getItem('js_deprecation_log_test.warnings');
+      return window.sessionStorage.getItem('js_testing_log_test.warnings');
     }, callback);
 };
diff --git a/core/tests/Drupal/Nightwatch/Assertions/noDeprecationErrors.js b/core/tests/Drupal/Nightwatch/Assertions/noDeprecationErrors.js
index 2dec7bf464b9..8d5ab79b9e8a 100644
--- a/core/tests/Drupal/Nightwatch/Assertions/noDeprecationErrors.js
+++ b/core/tests/Drupal/Nightwatch/Assertions/noDeprecationErrors.js
@@ -18,6 +18,6 @@ module.exports.assertion = function () {
   this.command = (callback) =>
     // eslint-disable-next-line prefer-arrow-callback
     this.api.execute(function () {
-      return window.sessionStorage.getItem('js_deprecation_log_test.warnings');
+      return window.sessionStorage.getItem('js_testing_log_test.warnings');
     }, callback);
 };
-- 
GitLab