@jahia/cypress 6.4.0 → 6.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,17 +5,11 @@
5
5
  /**
6
6
  * Strategy for handling JavaScript errors and warnings in Cypress tests.
7
7
  *
8
- * - failFast: Fail *immediately* when an error is detected.
9
- *
10
- * Proc: Allows each test to run, looks for console errors and warnings, and fails the particular test IMMEDIATELY
11
- * if any issues are found.
12
- * Cons: If errors or warnings were found during beforeEach or afterEach hook(s), the rest of spec will be ignored.
13
- *
14
8
  * - failAfterEach: Collect all errors and warnings *during test* execution and fail if any issues are found.
15
9
  *
16
- * Proc: Allows each test to run, collects console errors and warnings, and fails the particular test by the end of it's execution
17
- * if any issues are found.
18
- * Cons: If errors or warnings were found during beforeEach or afterEach hook(s), the rest of spec will be ignored.
10
+ * Proc: Allows each test to run, collects console errors and warnings,
11
+ * and fails the particular test by the end of its execution if any issues are found.
12
+ * Cons: Since the analysis happens in afterEach() hook, the rest of spec will be ignored.
19
13
  *
20
14
  * - failAfterAll: Collect all errors and warnings *after all tests* and fail at the end of the test suite.
21
15
  *
@@ -25,14 +19,13 @@
25
19
  * This is because the hook is executed after all tests are completed, so the last test is reported as failed.
26
20
  */
27
21
  declare enum STRATEGY {
28
- failFast = 0,
29
- failAfterAll = 1,
30
- failAfterEach = 2
22
+ failAfterEach = 0,
23
+ failAfterAll = 1
31
24
  }
32
25
  /**
33
26
  * Returns the current strategy for handling JavaScript errors and warnings in Cypress tests.
34
27
  * @returns {STRATEGY} - The current strategy for handling JavaScript errors and warnings.
35
- * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failFast` strategy,
28
+ * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failAfterEach` strategy,
36
29
  * which is falsy in JavaScript, so we need to check if the variable is undefined.
37
30
  */
38
31
  declare function getStrategy(): STRATEGY;
@@ -61,9 +54,8 @@ declare function setAllowedJsWarnings(warnings: string[]): void;
61
54
  declare function disable(): void;
62
55
  /**
63
56
  * Attaches custom hooks to Cypress events to monitor and report JavaScript errors and warnings.
64
- * This method is called automatically in registerSupport.ts#registerSupport
65
- * It sets up listeners for console errors and warnings, collects them after each test,
66
- * and throws an error if any issues are found after all tests are executed.
57
+ * It sets up listeners for console errors and warnings, collects them for each visited URL in each test,
58
+ * and throws an error if any issues are found after each or all tests are executed (depending on the strategy chosen).
67
59
  */
68
60
  declare function enable(): void;
69
61
  /**
@@ -20,17 +20,11 @@ var envVarStrategy = '__JS_LOGGER_STRATEGY__';
20
20
  /**
21
21
  * Strategy for handling JavaScript errors and warnings in Cypress tests.
22
22
  *
23
- * - failFast: Fail *immediately* when an error is detected.
24
- *
25
- * Proc: Allows each test to run, looks for console errors and warnings, and fails the particular test IMMEDIATELY
26
- * if any issues are found.
27
- * Cons: If errors or warnings were found during beforeEach or afterEach hook(s), the rest of spec will be ignored.
28
- *
29
23
  * - failAfterEach: Collect all errors and warnings *during test* execution and fail if any issues are found.
30
24
  *
31
- * Proc: Allows each test to run, collects console errors and warnings, and fails the particular test by the end of it's execution
32
- * if any issues are found.
33
- * Cons: If errors or warnings were found during beforeEach or afterEach hook(s), the rest of spec will be ignored.
25
+ * Proc: Allows each test to run, collects console errors and warnings,
26
+ * and fails the particular test by the end of its execution if any issues are found.
27
+ * Cons: Since the analysis happens in afterEach() hook, the rest of spec will be ignored.
34
28
  *
35
29
  * - failAfterAll: Collect all errors and warnings *after all tests* and fail at the end of the test suite.
36
30
  *
@@ -41,18 +35,31 @@ var envVarStrategy = '__JS_LOGGER_STRATEGY__';
41
35
  */
42
36
  var STRATEGY;
43
37
  (function (STRATEGY) {
44
- STRATEGY[STRATEGY["failFast"] = 0] = "failFast";
38
+ STRATEGY[STRATEGY["failAfterEach"] = 0] = "failAfterEach";
45
39
  STRATEGY[STRATEGY["failAfterAll"] = 1] = "failAfterAll";
46
- STRATEGY[STRATEGY["failAfterEach"] = 2] = "failAfterEach";
47
40
  })(STRATEGY || (STRATEGY = {}));
41
+ /**
42
+ * Returns an emoji based on the type of message.
43
+ * @param {string} type
44
+ */
45
+ function getEmoji(type) {
46
+ switch (type) {
47
+ case 'warn':
48
+ return '⚠️';
49
+ case 'error':
50
+ return '❌️';
51
+ default:
52
+ return '';
53
+ }
54
+ }
48
55
  /**
49
56
  * Returns the current strategy for handling JavaScript errors and warnings in Cypress tests.
50
57
  * @returns {STRATEGY} - The current strategy for handling JavaScript errors and warnings.
51
- * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failFast` strategy,
58
+ * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failAfterEach` strategy,
52
59
  * which is falsy in JavaScript, so we need to check if the variable is undefined.
53
60
  */
54
61
  function getStrategy() {
55
- return typeof Cypress.env(envVarStrategy) === 'undefined' ? STRATEGY.failFast : Cypress.env(envVarStrategy);
62
+ return typeof Cypress.env(envVarStrategy) === 'undefined' ? STRATEGY.failAfterAll : Cypress.env(envVarStrategy);
56
63
  }
57
64
  /**
58
65
  * Sets the strategy for handling JavaScript errors and warnings in Cypress tests.
@@ -89,11 +96,14 @@ function getAllowedJsWarnings() { return Cypress.env(envVarAllowedWarnings) || [
89
96
  function setAllowedJsWarnings(warnings) { Cypress.env(envVarAllowedWarnings, warnings); }
90
97
  /**
91
98
  * Attaches a custom JavaScript interceptor to capture console errors and warnings.
92
- * This interceptor is executed before the page is loaded, allowing us to spy on console messages.
93
99
  */
94
100
  function attachJsInterceptor() {
101
+ /**
102
+ * Custom 'window:before:load' hook to attach interceptors before the page is loaded,
103
+ * allowing us to spy on console messages.
104
+ */
95
105
  Cypress.on('window:before:load', function (window) {
96
- // Skip 'window:before:load' hook if the logger is not enabled
106
+ // Skip 'window:before:load' hook if the logger is disabled
97
107
  if (isDisabled()) {
98
108
  return;
99
109
  }
@@ -101,20 +111,34 @@ function attachJsInterceptor() {
101
111
  cy.spy(window.console, 'error').as('errors');
102
112
  cy.spy(window.console, 'warn').as('warnings');
103
113
  });
114
+ /**
115
+ * Custom 'window:load' hook to collect JavaScript errors and warnings right after the page is loaded.
116
+ */
117
+ Cypress.on('window:load', function (win) {
118
+ // Skip 'window:load' hook if the logger is disabled
119
+ if (isDisabled()) {
120
+ return;
121
+ }
122
+ // Collect issues immediately after the window is loaded and analyze them
123
+ collectIssues(win);
124
+ });
104
125
  }
105
126
  /**
106
127
  * Collects JavaScript errors and warnings using the spies set up in attachJsInterceptor.
107
128
  * @returns {Cypress.Chainable} - Cypress chainable object that resolves when issues are collected.
108
129
  */
109
- function collectIssues() {
130
+ function collectIssues(win) {
110
131
  var allowedWarnings = getAllowedJsWarnings();
111
132
  var consoleIssues = [];
133
+ var url = win.location.href;
112
134
  // Look for console errors and warnings, collected by the spies
113
135
  return cy.get('@errors')
114
136
  .invoke('getCalls')
115
137
  .then(function (errorCalls) {
116
138
  // All errors should be collected
117
- consoleIssues = errorCalls.flatMap(function (call) { return call.args; });
139
+ consoleIssues = errorCalls.flatMap(function (call) { return call.args.map(function (arg) { return ({ type: 'error', msg: String(arg) }); }); });
140
+ })
141
+ .then(function () {
118
142
  // Analyze warnings
119
143
  cy.get('@warnings')
120
144
  .invoke('getCalls')
@@ -122,7 +146,7 @@ function collectIssues() {
122
146
  warningCalls.flatMap(function (call) { return call.args; }).forEach(function (arg) {
123
147
  // Only warnings not in the allowed list should be collected
124
148
  if (!allowedWarnings.some(function (item) { return arg.includes(item); })) {
125
- consoleIssues.push(arg);
149
+ consoleIssues.push({ type: 'warn', msg: String(arg) });
126
150
  }
127
151
  });
128
152
  });
@@ -131,13 +155,9 @@ function collectIssues() {
131
155
  // Update the Cypress environment variable with the collected issues
132
156
  if (consoleIssues.length > 0) {
133
157
  setCollectedIssues(__spreadArray(__spreadArray([], getCollectedIssues()), [
134
- { test: Cypress.currentTest.title, errors: consoleIssues }
158
+ { url: url, test: Cypress.currentTest.title, errors: consoleIssues }
135
159
  ]));
136
160
  }
137
- })
138
- .then(function () {
139
- // Return a Cypress chainable object to allow chaining
140
- return cy.wrap(null, { log: false });
141
161
  });
142
162
  }
143
163
  /**
@@ -147,9 +167,22 @@ function analyzeIssues() {
147
167
  cy.then(function () {
148
168
  var failures = getCollectedIssues();
149
169
  if (failures.length > 0) {
150
- // Format the error message for each test
151
- var errorMessage = failures.map(function (failure) {
152
- return "TEST: " + failure.test + "\nISSUES:\n" + failure.errors.map(function (e) { return "- " + e; }).join('\n');
170
+ // Group all issues by test title
171
+ var groupedByTest = failures.reduce(function (acc, failure) {
172
+ acc[failure.test] = acc[failure.test] || [];
173
+ acc[failure.test].push(failure);
174
+ return acc;
175
+ }, {});
176
+ // Format the error message for each test with its collected issues
177
+ var errorMessage = Object.entries(groupedByTest).map(function (_a) {
178
+ var test = _a[0], items = _a[1];
179
+ var urlsAndErrors = items.map(function (item) {
180
+ return "URL: " + item.url + "\nISSUES:\n" + item.errors.map(function (e) { return "- " + (e.type === 'warn' ? getEmoji('warn') : getEmoji('error')) + " " + e.msg; }).join('\n');
181
+ }).join('\n\n');
182
+ // Return the formatted message for the test;
183
+ // Intentionally use fixed-width (50 chars) separators for better readability,
184
+ // when the message might be wrapped
185
+ return getEmoji('error') + "\uFE0F TEST: " + test.trim() + " " + getEmoji('error') + "\uFE0F\n" + '-'.repeat(50) + "\n" + urlsAndErrors + "\n" + '='.repeat(50);
153
186
  }).join('\n\n');
154
187
  // Reset the collector for the next test run
155
188
  setCollectedIssues([]);
@@ -165,9 +198,8 @@ function analyzeIssues() {
165
198
  function disable() { Cypress.env(envVarDisableJsLogger, true); }
166
199
  /**
167
200
  * Attaches custom hooks to Cypress events to monitor and report JavaScript errors and warnings.
168
- * This method is called automatically in registerSupport.ts#registerSupport
169
- * It sets up listeners for console errors and warnings, collects them after each test,
170
- * and throws an error if any issues are found after all tests are executed.
201
+ * It sets up listeners for console errors and warnings, collects them for each visited URL in each test,
202
+ * and throws an error if any issues are found after each or all tests are executed (depending on the strategy chosen).
171
203
  */
172
204
  function enable() {
173
205
  // Ensure the logger is enabled by default
@@ -175,52 +207,27 @@ function enable() {
175
207
  // Attach errors and warnings collector
176
208
  attachJsInterceptor();
177
209
  /**
178
- * Custom 'afterEach' hook to collect JavaScript errors and warnings after each test execution.
179
- * The behavior of this hook depends on the strategy set for the logger.
210
+ * Custom 'afterEach' hook to analyze JavaScript errors and warnings after EACH test execution.
180
211
  */
181
212
  afterEach(function () {
182
- // Skip the hook if the logger is disabled
183
- if (isDisabled()) {
213
+ // Skip the hook if the logger is disabled or if the strategy is not failAfterEach
214
+ if (isDisabled() || getStrategy() !== STRATEGY.failAfterEach) {
184
215
  return;
185
216
  }
186
- // Depending on the strategy, collect issues and analyze them
187
- // If the strategy is failFast, issues will be collected in 'window:load'
188
- if (getStrategy() === STRATEGY.failAfterEach) {
189
- // Collect issues after each test and analyze them immediately
190
- collectIssues().then(function () { return analyzeIssues(); });
191
- }
192
- else if (getStrategy() === STRATEGY.failAfterAll) {
193
- // Collect issues after each test, but analyze them only after all tests are executed
194
- collectIssues();
195
- }
196
- else {
197
- // Do nothing for failFast strategy, issues will be collected and analyzed in 'window:load' hook
198
- }
217
+ // Analyze collected errors and warnings
218
+ analyzeIssues();
199
219
  });
200
220
  /**
201
- * Custom 'after' hook to analyze collected errors and warnings after all tests are executed.
221
+ * Custom 'after' hook to analyze JavaScript errors and warnings after ALL tests execution.
202
222
  */
203
223
  after(function () {
204
224
  // Skip the hook if the logger is disabled or if the strategy is not failAfterAll
205
- // This hook is only relevant for the failAfterAll strategy, where we analyze issues after
206
225
  if (isDisabled() || getStrategy() !== STRATEGY.failAfterAll) {
207
226
  return;
208
227
  }
209
228
  // Analyze collected errors and warnings
210
229
  analyzeIssues();
211
230
  });
212
- /**
213
- * Custom 'window:load' hook to collect JavaScript errors and warnings right after the page is loaded.
214
- * Applicable only for the failFast strategy.
215
- */
216
- Cypress.on('window:load', function () {
217
- // Skip the hook if the logger is disabled or if the strategy is not failFast
218
- if (isDisabled() || getStrategy() !== STRATEGY.failFast) {
219
- return;
220
- }
221
- // Collect issues immediately after the window is loaded and analyze them
222
- collectIssues().then(function () { return analyzeIssues(); });
223
- });
224
231
  }
225
232
  /**
226
233
  * Exports the jsLogger module with methods to attach hooks, enable/disable logging, and set allowed warnings.
@@ -15,55 +15,20 @@ The JavaScript Errors Logger is a comprehensive monitoring and reporting module
15
15
 
16
16
  The logger supports three distinct strategies for handling JavaScript errors and warnings:
17
17
 
18
- ### 1. Fail Fast (default)
19
- - **Strategy**: `STRATEGY.failFast`
20
- - **Behavior**: Fails immediately when an error or warning is detected; the rest of tests will be executed
21
- - **Use Case**: Best for development environments where immediate feedback is crucial
22
- - **Pros**: Quick identification of issues
23
- - **Cons**: May stop test execution on first error, preventing discovery of additional issues
24
-
25
- ### 2. Fail After Each Test
18
+ ### 1. Fail After Each Test
26
19
  - **Strategy**: `STRATEGY.failAfterEach`
27
20
  - **Behavior**: Collects errors/warnings during test execution and fails at the end of the one; the rest of tests will be skipped
28
21
  - **Use Case**: Suitable when you want the test to complete but still get immediate feedback
29
22
  - **Pros**: Allows test to be executed till the end before providing a report
30
- - **Cons**: Additional log review might be needed to identify - where the error or warning occur
23
+ - **Cons**: Since the analysis happens in afterEach() hook, the rest of spec will be ignored
31
24
 
32
- ### 3. Fail After All Tests
25
+ ### 2. Fail After All Tests (default)
33
26
  - **Strategy**: `STRATEGY.failAfterAll`
34
27
  - **Behavior**: Collects all errors/warnings and reports them after the entire test suite completes; the last test will be marked as failed
35
28
  - **Use Case**: Ideal for CI/CD environments where you want a complete test run overview
36
29
  - **Pros**: Complete test suite execution with comprehensive error reporting
37
30
  - **Cons**: Error reporting may be confusing as the last test will be marked as failed, since the errors analysis and reporting is done in after() hook
38
31
 
39
- ## Configuration
40
-
41
- ### Environment Variables
42
-
43
- | Variable | Type | Description |
44
- |---------------------------------|------|-------------|
45
- | `JAHIA_HOOKS_DISABLE_JS_LOGGER` | boolean | Disables the logger when set to `true` |
46
-
47
- ### Programmatic Configuration
48
-
49
- It is **strongly** recommended to add custom configuration in project's common files, e.g. `tests/cypress/support/e2e.js` to have it applied to all test-cases within the project.
50
-
51
- ```typescript
52
- import { jsErrorsLogger } from '@jahia/cypress';
53
-
54
- // Attach Logger
55
- jsErrorsLogger.enable();
56
-
57
- // Set preferrable error handling strategy
58
- jsErrorsLogger.setStrategy(jsErrorsLogger.STRATEGY.failFast);
59
-
60
- // Define allowed warnings that won't trigger failures
61
- jsErrorsLogger.setAllowedJsWarnings([
62
- 'Warning: React Hook',
63
- 'Warning: componentWillReceiveProps'
64
- ]);
65
- ```
66
-
67
32
  ## Usage
68
33
 
69
34
  ### Basic Setup
@@ -72,11 +37,10 @@ jsErrorsLogger.setAllowedJsWarnings([
72
37
  This call should only be used in `tests/cypress/support/e2e.js`. Add the following code in the repo where functionality should be used:
73
38
 
74
39
  ```typescript
75
- import { jsErrorsLogger } from '@jahia/cypress';
40
+ import {jsErrorsLogger} from '@jahia/cypress';
76
41
 
77
- before(() => {
78
- jsErrorsLogger.enable();
79
- });
42
+ // Enable and attach JS Errors Logger
43
+ jsErrorsLogger.enable();
80
44
  ```
81
45
 
82
46
  ### Disabling the Logger
@@ -91,7 +55,7 @@ export JAHIA_HOOKS_DISABLE_JS_LOGGER="true"
91
55
  #### Disable for the specific Spec
92
56
 
93
57
  ```typescript
94
- import { jsErrorsLogger } from '@jahia/cypress';
58
+ import {jsErrorsLogger} from '@jahia/cypress';
95
59
 
96
60
  describe('Tests with disabled JS logger', () => {
97
61
  before(() => {
@@ -104,35 +68,76 @@ describe('Tests with disabled JS logger', () => {
104
68
  });
105
69
  ```
106
70
 
71
+ ## Configuration
72
+
73
+ ### Environment Variables
74
+
75
+ | Variable | Type | Description |
76
+ |---------------------------------|------|-------------|
77
+ | `JAHIA_HOOKS_DISABLE_JS_LOGGER` | boolean | Disables the logger when set to `true` |
78
+
79
+ ### Programmatic Configuration
80
+
81
+ It is **strongly** recommended to add custom configuration in project's common files, e.g. `tests/cypress/support/e2e.js` to have it applied to all test-cases within the project.
82
+
83
+ ```typescript
84
+ import {jsErrorsLogger} from '@jahia/cypress';
85
+
86
+ // Enable and attach JS Errors Logger
87
+ jsErrorsLogger.enable();
88
+
89
+ // Set preferrable error handling strategy
90
+ jsErrorsLogger.setStrategy(jsErrorsLogger.STRATEGY.failAfterAll);
91
+
92
+ // Define allowed warnings that won't trigger failures
93
+ jsErrorsLogger.setAllowedJsWarnings([
94
+ 'Warning: React Hook',
95
+ 'Warning: componentWillReceiveProps'
96
+ ]);
97
+ ```
98
+
107
99
  ## Error Reporting Format
108
100
 
109
- ### Single Test Error (failFast/failAfterEach)
101
+ ### Single Test Error (failAfterEach)
110
102
 
111
103
  ```
112
- Error: CONSOLE ERRORS and WARNINGS FOUND:
113
- - TypeError: Cannot read property 'foo' of undefined
114
- - Warning: React Hook useEffect has missing dependency
104
+ CONSOLE ERRORS and WARNINGS FOUND:
105
+
106
+ ❌️ TEST: Should be authenticated when correct credentials and code are provided: ❌️
107
+ --------------------------------------------------
108
+ URL: http://localhost:8080/jahia/dashboard
109
+ ISSUES:
110
+ - ⚠️ Unsatisfied version 5.0.1 from @jahia/jcontent of shared singleton module redux (required ^4.0.5)
111
+ - ⚠️ Unsatisfied version 9.2.0 from @jahia/jcontent of shared singleton module react-redux (required ^8.0.5)
112
+ - ❌️ TypeError: Cannot read property 'user' of undefined
115
113
  ```
116
114
 
117
115
  ### Multiple Test Errors (failAfterAll)
118
116
 
119
117
  ```
120
- Error: CONSOLE ERRORS and WARNINGS FOUND:
118
+ CONSOLE ERRORS and WARNINGS FOUND:
121
119
 
122
- TEST: should load homepage
120
+ ❌️ TEST: Should be authenticated when correct credentials and code are provided: ❌️
121
+ --------------------------------------------------
122
+ URL: http://localhost:8080/jahia/dashboard
123
123
  ISSUES:
124
- - TypeError: Cannot read property 'user' of undefined
125
- - Warning: componentWillMount is deprecated
126
-
127
- TEST: should handle form submission
124
+ - ⚠️ No satisfying version (^1.11.9) of shared module dayjs found in shared scope default.
125
+ - ⚠️ No satisfying version (^3.0.6) of shared module @jahia/react-material found in shared scope default.
126
+ - ❌️ TypeError: Cannot read property 'user' of undefined
127
+ ==================================================
128
+
129
+ ❌️ TEST: Should be authenticated on a specific site when correct credentials and code are provided: ❌️
130
+ --------------------------------------------------
131
+ URL: http://localhost:8080/jahia/admin
128
132
  ISSUES:
129
- - ReferenceError: handleSubmit is not defined
133
+ - ⚠️ No satisfying version (^3.0.6) of shared module @jahia/react-material found in shared scope default.
134
+ ==================================================
130
135
  ```
131
136
 
132
137
  ## Best Practices
133
138
 
134
139
  ### Development Environment
135
- - Use `STRATEGY.failFast` for immediate feedback during development
140
+ - Use `STRATEGY.failAfterEach` for immediate feedback during development
136
141
  - Configure comprehensive allowed warnings list for known, acceptable warnings
137
142
  - Enable detailed logging for debugging purposes
138
143
 
@@ -161,4 +166,4 @@ ISSUES:
161
166
 
162
167
  | Enum | Values | Description |
163
168
  |------|--------|-------------|
164
- | `STRATEGY` | `failFast`, `failAfterAll`, `failAfterEach` | Error handling strategies |
169
+ | `STRATEGY` | `failAfterAll`, `failAfterEach` | Error handling strategies |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jahia/cypress",
3
- "version": "6.4.0",
3
+ "version": "6.4.1",
4
4
  "scripts": {
5
5
  "build": "tsc",
6
6
  "lint": "eslint src -c .eslintrc.json --ext .ts --max-warnings=0"
@@ -14,17 +14,11 @@ const envVarStrategy = '__JS_LOGGER_STRATEGY__';
14
14
  /**
15
15
  * Strategy for handling JavaScript errors and warnings in Cypress tests.
16
16
  *
17
- * - failFast: Fail *immediately* when an error is detected.
18
- *
19
- * Proc: Allows each test to run, looks for console errors and warnings, and fails the particular test IMMEDIATELY
20
- * if any issues are found.
21
- * Cons: If errors or warnings were found during beforeEach or afterEach hook(s), the rest of spec will be ignored.
22
- *
23
17
  * - failAfterEach: Collect all errors and warnings *during test* execution and fail if any issues are found.
24
18
  *
25
- * Proc: Allows each test to run, collects console errors and warnings, and fails the particular test by the end of it's execution
26
- * if any issues are found.
27
- * Cons: If errors or warnings were found during beforeEach or afterEach hook(s), the rest of spec will be ignored.
19
+ * Proc: Allows each test to run, collects console errors and warnings,
20
+ * and fails the particular test by the end of its execution if any issues are found.
21
+ * Cons: Since the analysis happens in afterEach() hook, the rest of spec will be ignored.
28
22
  *
29
23
  * - failAfterAll: Collect all errors and warnings *after all tests* and fail at the end of the test suite.
30
24
  *
@@ -33,25 +27,41 @@ const envVarStrategy = '__JS_LOGGER_STRATEGY__';
33
27
  * Cons: Reporting might be confusing, e.g. - cypress will report the very last test as failed, while many tests might have issues.
34
28
  * This is because the hook is executed after all tests are completed, so the last test is reported as failed.
35
29
  */
36
- enum STRATEGY { failFast, failAfterAll, failAfterEach }
30
+ enum STRATEGY { failAfterEach, failAfterAll }
37
31
 
38
32
  /**
39
33
  * Auxiliary type to represent a single item in the collector.
40
34
  * It contains the test title and an array of error or warning messages collected during the test.
41
35
  */
42
36
  type CollectorItem = {
37
+ url: string; // URL of the current page where the issue was found
43
38
  test: string; // The title of the test where the issue was found
44
- errors: string[]; // Array of error or warning messages collected during the test
39
+ errors: {type: string; msg: string}[]; // Array of error or warning messages collected during the test
45
40
  };
46
41
 
42
+ /**
43
+ * Returns an emoji based on the type of message.
44
+ * @param {string} type
45
+ */
46
+ function getEmoji(type: string): string {
47
+ switch (type) {
48
+ case 'warn':
49
+ return '⚠️';
50
+ case 'error':
51
+ return '❌️';
52
+ default:
53
+ return '';
54
+ }
55
+ }
56
+
47
57
  /**
48
58
  * Returns the current strategy for handling JavaScript errors and warnings in Cypress tests.
49
59
  * @returns {STRATEGY} - The current strategy for handling JavaScript errors and warnings.
50
- * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failFast` strategy,
60
+ * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failAfterEach` strategy,
51
61
  * which is falsy in JavaScript, so we need to check if the variable is undefined.
52
62
  */
53
63
  function getStrategy(): STRATEGY {
54
- return typeof Cypress.env(envVarStrategy) === 'undefined' ? STRATEGY.failFast : Cypress.env(envVarStrategy);
64
+ return typeof Cypress.env(envVarStrategy) === 'undefined' ? STRATEGY.failAfterAll : Cypress.env(envVarStrategy);
55
65
  }
56
66
 
57
67
  /**
@@ -95,41 +105,55 @@ function setAllowedJsWarnings(warnings: string[]): void { Cypress.env(envVarAllo
95
105
 
96
106
  /**
97
107
  * Attaches a custom JavaScript interceptor to capture console errors and warnings.
98
- * This interceptor is executed before the page is loaded, allowing us to spy on console messages.
99
108
  */
100
109
  function attachJsInterceptor(): void {
110
+ /**
111
+ * Custom 'window:before:load' hook to attach interceptors before the page is loaded,
112
+ * allowing us to spy on console messages.
113
+ */
101
114
  Cypress.on('window:before:load', window => {
102
- // Skip 'window:before:load' hook if the logger is not enabled
115
+ // Skip 'window:before:load' hook if the logger is disabled
103
116
  if (isDisabled()) { return; }
104
-
105
117
  // Spy on console.error and console.warn to capture errors and warnings
106
118
  cy.spy(window.console, 'error').as('errors');
107
119
  cy.spy(window.console, 'warn').as('warnings');
108
120
  });
121
+
122
+ /**
123
+ * Custom 'window:load' hook to collect JavaScript errors and warnings right after the page is loaded.
124
+ */
125
+ Cypress.on('window:load', win => {
126
+ // Skip 'window:load' hook if the logger is disabled
127
+ if (isDisabled()) { return; }
128
+ // Collect issues immediately after the window is loaded and analyze them
129
+ collectIssues(win);
130
+ });
109
131
  }
110
132
 
111
133
  /**
112
134
  * Collects JavaScript errors and warnings using the spies set up in attachJsInterceptor.
113
135
  * @returns {Cypress.Chainable} - Cypress chainable object that resolves when issues are collected.
114
136
  */
115
- function collectIssues(): Cypress.Chainable {
137
+ function collectIssues(win: Cypress.AUTWindow): Cypress.Chainable {
116
138
  const allowedWarnings = getAllowedJsWarnings();
117
- let consoleIssues: string[] = [];
139
+ let consoleIssues: {type: string, msg: string}[] = [];
140
+ const url = win.location.href;
118
141
 
119
142
  // Look for console errors and warnings, collected by the spies
120
143
  return cy.get('@errors')
121
144
  .invoke('getCalls')
122
145
  .then(errorCalls => {
123
146
  // All errors should be collected
124
- consoleIssues = errorCalls.flatMap((call: { args: string[] }) => call.args);
125
-
147
+ consoleIssues = errorCalls.flatMap((call: { args: unknown[] }) => call.args.map((arg: string) => ({type: 'error', msg: String(arg)})));
148
+ })
149
+ .then(() => {
126
150
  // Analyze warnings
127
151
  cy.get('@warnings')
128
152
  .invoke('getCalls')
129
153
  .then(warningCalls => {
130
- warningCalls.flatMap((call: { args: string[] }) => call.args).forEach((arg: string) => {
154
+ warningCalls.flatMap((call: { args: unknown[] }) => call.args).forEach((arg: string) => {
131
155
  // Only warnings not in the allowed list should be collected
132
- if (!allowedWarnings.some((item: string) => arg.includes(item))) { consoleIssues.push(arg); }
156
+ if (!allowedWarnings.some((item: string) => arg.includes(item))) { consoleIssues.push({type: 'warn', msg: String(arg)}); }
133
157
  });
134
158
  });
135
159
  })
@@ -138,13 +162,9 @@ function collectIssues(): Cypress.Chainable {
138
162
  if (consoleIssues.length > 0) {
139
163
  setCollectedIssues([
140
164
  ...getCollectedIssues(),
141
- {test: Cypress.currentTest.title, errors: consoleIssues}
165
+ {url: url, test: Cypress.currentTest.title, errors: consoleIssues}
142
166
  ]);
143
167
  }
144
- })
145
- .then(() => {
146
- // Return a Cypress chainable object to allow chaining
147
- return cy.wrap(null, {log: false});
148
168
  });
149
169
  }
150
170
 
@@ -154,11 +174,31 @@ function collectIssues(): Cypress.Chainable {
154
174
  function analyzeIssues(): void {
155
175
  cy.then(() => {
156
176
  const failures = getCollectedIssues();
177
+
157
178
  if (failures.length > 0) {
158
- // Format the error message for each test
159
- const errorMessage = failures.map((failure: { test: string; errors: string[]; }) => {
160
- return `TEST: ${failure.test}\nISSUES:\n${failure.errors.map((e: string) => `- ${e}`).join('\n')}`;
179
+ // Group all issues by test title
180
+ const groupedByTest = failures.reduce((acc: Record<string, CollectorItem[]>, failure) => {
181
+ acc[failure.test] = acc[failure.test] || [];
182
+ acc[failure.test].push(failure);
183
+
184
+ return acc;
185
+ }, {} as Record<string, CollectorItem[]>);
186
+
187
+ // Format the error message for each test with its collected issues
188
+ const errorMessage = Object.entries(groupedByTest).map(([test, items]) => {
189
+ const urlsAndErrors = items.map(item =>
190
+ `URL: ${item.url}\nISSUES:\n${item.errors.map((e: {
191
+ type: string;
192
+ msg: string
193
+ }) => `- ${e.type === 'warn' ? getEmoji('warn') : getEmoji('error')} ${e.msg}`).join('\n')}`
194
+ ).join('\n\n');
195
+
196
+ // Return the formatted message for the test;
197
+ // Intentionally use fixed-width (50 chars) separators for better readability,
198
+ // when the message might be wrapped
199
+ return `${getEmoji('error')}️ TEST: ${test.trim()} ${getEmoji('error')}️\n${'-'.repeat(50)}\n${urlsAndErrors}\n${'='.repeat(50)}`;
161
200
  }).join('\n\n');
201
+
162
202
  // Reset the collector for the next test run
163
203
  setCollectedIssues([]);
164
204
 
@@ -176,9 +216,8 @@ function disable(): void { Cypress.env(envVarDisableJsLogger, true); }
176
216
 
177
217
  /**
178
218
  * Attaches custom hooks to Cypress events to monitor and report JavaScript errors and warnings.
179
- * This method is called automatically in registerSupport.ts#registerSupport
180
- * It sets up listeners for console errors and warnings, collects them after each test,
181
- * and throws an error if any issues are found after all tests are executed.
219
+ * It sets up listeners for console errors and warnings, collects them for each visited URL in each test,
220
+ * and throws an error if any issues are found after each or all tests are executed (depending on the strategy chosen).
182
221
  */
183
222
  function enable(): void {
184
223
  // Ensure the logger is enabled by default
@@ -188,49 +227,24 @@ function enable(): void {
188
227
  attachJsInterceptor();
189
228
 
190
229
  /**
191
- * Custom 'afterEach' hook to collect JavaScript errors and warnings after each test execution.
192
- * The behavior of this hook depends on the strategy set for the logger.
230
+ * Custom 'afterEach' hook to analyze JavaScript errors and warnings after EACH test execution.
193
231
  */
194
232
  afterEach(() => {
195
- // Skip the hook if the logger is disabled
196
- if (isDisabled()) { return; }
197
-
198
- // Depending on the strategy, collect issues and analyze them
199
- // If the strategy is failFast, issues will be collected in 'window:load'
200
- if (getStrategy() === STRATEGY.failAfterEach) {
201
- // Collect issues after each test and analyze them immediately
202
- collectIssues().then(() => analyzeIssues());
203
- } else if (getStrategy() === STRATEGY.failAfterAll) {
204
- // Collect issues after each test, but analyze them only after all tests are executed
205
- collectIssues();
206
- } else {
207
- // Do nothing for failFast strategy, issues will be collected and analyzed in 'window:load' hook
208
- }
233
+ // Skip the hook if the logger is disabled or if the strategy is not failAfterEach
234
+ if (isDisabled() || getStrategy() !== STRATEGY.failAfterEach) { return; }
235
+ // Analyze collected errors and warnings
236
+ analyzeIssues();
209
237
  });
210
238
 
211
239
  /**
212
- * Custom 'after' hook to analyze collected errors and warnings after all tests are executed.
240
+ * Custom 'after' hook to analyze JavaScript errors and warnings after ALL tests execution.
213
241
  */
214
242
  after(() => {
215
243
  // Skip the hook if the logger is disabled or if the strategy is not failAfterAll
216
- // This hook is only relevant for the failAfterAll strategy, where we analyze issues after
217
244
  if (isDisabled() || getStrategy() !== STRATEGY.failAfterAll) { return; }
218
-
219
245
  // Analyze collected errors and warnings
220
246
  analyzeIssues();
221
247
  });
222
-
223
- /**
224
- * Custom 'window:load' hook to collect JavaScript errors and warnings right after the page is loaded.
225
- * Applicable only for the failFast strategy.
226
- */
227
- Cypress.on('window:load', () => {
228
- // Skip the hook if the logger is disabled or if the strategy is not failFast
229
- if (isDisabled() || getStrategy() !== STRATEGY.failFast) { return; }
230
-
231
- // Collect issues immediately after the window is loaded and analyze them
232
- collectIssues().then(() => analyzeIssues());
233
- });
234
248
  }
235
249
 
236
250
  /**