@jahia/cypress 6.4.0 → 6.4.2

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.
@@ -1,21 +1,11 @@
1
- /**
2
- * Module for monitoring and reporting JavaScript errors and warnings in Cypress tests.
3
- * Provides methods to enable, disable, and check logger status.
4
- */
5
1
  /**
6
2
  * Strategy for handling JavaScript errors and warnings in Cypress tests.
7
3
  *
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
4
  * - failAfterEach: Collect all errors and warnings *during test* execution and fail if any issues are found.
15
5
  *
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.
6
+ * Proc: Allows each test to run, collects console errors and warnings,
7
+ * and fails the particular test by the end of its execution if any issues are found.
8
+ * Cons: Since the analysis happens in afterEach() hook, the rest of spec will be ignored.
19
9
  *
20
10
  * - failAfterAll: Collect all errors and warnings *after all tests* and fail at the end of the test suite.
21
11
  *
@@ -25,14 +15,13 @@
25
15
  * This is because the hook is executed after all tests are completed, so the last test is reported as failed.
26
16
  */
27
17
  declare enum STRATEGY {
28
- failFast = 0,
29
- failAfterAll = 1,
30
- failAfterEach = 2
18
+ failAfterEach = 0,
19
+ failAfterAll = 1
31
20
  }
32
21
  /**
33
22
  * Returns the current strategy for handling JavaScript errors and warnings in Cypress tests.
34
23
  * @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,
24
+ * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failAfterEach` strategy,
36
25
  * which is falsy in JavaScript, so we need to check if the variable is undefined.
37
26
  */
38
27
  declare function getStrategy(): STRATEGY;
@@ -61,9 +50,8 @@ declare function setAllowedJsWarnings(warnings: string[]): void;
61
50
  declare function disable(): void;
62
51
  /**
63
52
  * 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.
53
+ * It sets up listeners for console errors and warnings, collects them for each visited URL in each test,
54
+ * and throws an error if any issues are found after each or all tests are executed (depending on the strategy chosen).
67
55
  */
68
56
  declare function enable(): void;
69
57
  /**
@@ -1,10 +1,6 @@
1
1
  "use strict";
2
2
  /* eslint-disable brace-style */
3
3
  /* eslint-disable max-statements-per-line */
4
- /**
5
- * Module for monitoring and reporting JavaScript errors and warnings in Cypress tests.
6
- * Provides methods to enable, disable, and check logger status.
7
- */
8
4
  var __spreadArray = (this && this.__spreadArray) || function (to, from) {
9
5
  for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
10
6
  to[j] = from[i];
@@ -12,6 +8,10 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from) {
12
8
  };
13
9
  exports.__esModule = true;
14
10
  exports.jsErrorsLogger = void 0;
11
+ /**
12
+ * Module for monitoring and reporting JavaScript errors and warnings in Cypress tests.
13
+ * Provides methods to enable, disable, and check logger status.
14
+ */
15
15
  var envVarDisableAll = 'JAHIA_HOOKS_DISABLE';
16
16
  var envVarDisableJsLogger = 'JAHIA_HOOKS_DISABLE_JS_LOGGER';
17
17
  var envVarCollector = '__JS_LOGGER_FAILURES__';
@@ -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,40 +96,55 @@ 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() {
95
- Cypress.on('window:before:load', function (window) {
96
- // Skip 'window:before:load' hook if the logger is not enabled
101
+ /**
102
+ * Custom 'window:before:load' hook to attach interceptors before the page is loaded and spy on console messages.
103
+ */
104
+ cy.on('window:before:load', function (window) {
105
+ // Skip 'window:before:load' hook if the logger is disabled
97
106
  if (isDisabled()) {
98
107
  return;
99
108
  }
100
- // Spy on console.error and console.warn to capture errors and warnings
109
+ // Spy on console.error and console.warn methods to capture errors and warnings
101
110
  cy.spy(window.console, 'error').as('errors');
102
111
  cy.spy(window.console, 'warn').as('warnings');
103
112
  });
113
+ /**
114
+ * Custom 'window:load' hook to collect JavaScript errors and warnings right after the page is loaded.
115
+ */
116
+ cy.on('window:load', function (win) {
117
+ // Skip 'window:load' hook if the logger is disabled
118
+ if (isDisabled()) {
119
+ return;
120
+ }
121
+ // Collect errors and warnings after the page is fully loaded
122
+ collectIssues(win);
123
+ });
104
124
  }
105
125
  /**
106
126
  * Collects JavaScript errors and warnings using the spies set up in attachJsInterceptor.
107
127
  * @returns {Cypress.Chainable} - Cypress chainable object that resolves when issues are collected.
108
128
  */
109
- function collectIssues() {
110
- var allowedWarnings = getAllowedJsWarnings();
129
+ function collectIssues(win) {
111
130
  var consoleIssues = [];
112
131
  // Look for console errors and warnings, collected by the spies
113
132
  return cy.get('@errors')
114
133
  .invoke('getCalls')
115
134
  .then(function (errorCalls) {
116
135
  // All errors should be collected
117
- consoleIssues = errorCalls.flatMap(function (call) { return call.args; });
118
- // Analyze warnings
119
- cy.get('@warnings')
136
+ consoleIssues = errorCalls.flatMap(function (call) { return call.args.map(function (arg) { return ({ type: 'error', msg: String(arg) }); }); });
137
+ })
138
+ .then(function () {
139
+ // Analyze warnings - return the chain to maintain proper async flow
140
+ return cy.get('@warnings')
120
141
  .invoke('getCalls')
121
142
  .then(function (warningCalls) {
143
+ var allowedWarnings = getAllowedJsWarnings();
122
144
  warningCalls.flatMap(function (call) { return call.args; }).forEach(function (arg) {
123
145
  // Only warnings not in the allowed list should be collected
124
146
  if (!allowedWarnings.some(function (item) { return arg.includes(item); })) {
125
- consoleIssues.push(arg);
147
+ consoleIssues.push({ type: 'warn', msg: String(arg) });
126
148
  }
127
149
  });
128
150
  });
@@ -131,25 +153,33 @@ function collectIssues() {
131
153
  // Update the Cypress environment variable with the collected issues
132
154
  if (consoleIssues.length > 0) {
133
155
  setCollectedIssues(__spreadArray(__spreadArray([], getCollectedIssues()), [
134
- { test: Cypress.currentTest.title, errors: consoleIssues }
156
+ { url: win.location.href, test: Cypress.currentTest.title, errors: consoleIssues }
135
157
  ]));
136
158
  }
137
- })
138
- .then(function () {
139
- // Return a Cypress chainable object to allow chaining
140
- return cy.wrap(null, { log: false });
141
159
  });
142
160
  }
143
161
  /**
144
162
  * Analyzes collected JavaScript errors and warnings and throws an error if any were found.
145
163
  */
146
164
  function analyzeIssues() {
147
- cy.then(function () {
148
- var failures = getCollectedIssues();
165
+ return cy.wrap(getCollectedIssues()).then(function (failures) {
149
166
  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');
167
+ // Group all issues by test title
168
+ var groupedByTest = failures.reduce(function (acc, failure) {
169
+ acc[failure.test] = acc[failure.test] || [];
170
+ acc[failure.test].push(failure);
171
+ return acc;
172
+ }, {});
173
+ // Format the error message for each test with its collected issues
174
+ var errorMessage = Object.entries(groupedByTest).map(function (_a) {
175
+ var test = _a[0], items = _a[1];
176
+ var urlsAndErrors = items.map(function (item) {
177
+ return "URL: " + item.url + "\nISSUES:\n" + item.errors.map(function (e) { return "- " + (e.type === 'warn' ? getEmoji('warn') : getEmoji('error')) + " " + e.msg; }).join('\n');
178
+ }).join('\n\n');
179
+ // Return the formatted message for the test;
180
+ // Intentionally use fixed-width (50 chars) separators for better readability,
181
+ // when the message might be wrapped
182
+ return getEmoji('error') + "\uFE0F TEST: " + test.trim() + " " + getEmoji('error') + "\uFE0F\n" + '-'.repeat(50) + "\n" + urlsAndErrors + "\n" + '='.repeat(50);
153
183
  }).join('\n\n');
154
184
  // Reset the collector for the next test run
155
185
  setCollectedIssues([]);
@@ -165,61 +195,41 @@ function analyzeIssues() {
165
195
  function disable() { Cypress.env(envVarDisableJsLogger, true); }
166
196
  /**
167
197
  * 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.
198
+ * It sets up listeners for console errors and warnings, collects them for each visited URL in each test,
199
+ * and throws an error if any issues are found after each or all tests are executed (depending on the strategy chosen).
171
200
  */
172
201
  function enable() {
173
- // Ensure the logger is enabled by default
202
+ // Ensure the logger is enabled
174
203
  Cypress.env(envVarDisableJsLogger, false);
175
- // Attach errors and warnings collector
176
- attachJsInterceptor();
177
204
  /**
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.
205
+ * Attach Cypress hooks forconsole messages collecting before EACH test execution.
206
+ * Use 'beforeEach' hook and local (cy) context instead of global (Cypress) one
207
+ * to ensure proper async flow and avoid events and hooks flakiness.
180
208
  */
181
- afterEach(function () {
182
- // Skip the hook if the logger is disabled
183
- if (isDisabled()) {
184
- return;
185
- }
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
- }
209
+ before(function () {
210
+ attachJsInterceptor();
199
211
  });
200
212
  /**
201
- * Custom 'after' hook to analyze collected errors and warnings after all tests are executed.
213
+ * Custom 'afterEach' hook to analyze JavaScript errors and warnings after EACH test execution.
202
214
  */
203
- after(function () {
204
- // 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
- if (isDisabled() || getStrategy() !== STRATEGY.failAfterAll) {
215
+ afterEach(function () {
216
+ // Skip the hook if the logger is disabled or if the strategy is not failAfterEach
217
+ if (isDisabled() || (getStrategy() !== STRATEGY.failAfterEach)) {
207
218
  return;
208
219
  }
209
220
  // Analyze collected errors and warnings
210
221
  analyzeIssues();
211
222
  });
212
223
  /**
213
- * Custom 'window:load' hook to collect JavaScript errors and warnings right after the page is loaded.
214
- * Applicable only for the failFast strategy.
224
+ * Custom 'after' hook to analyze JavaScript errors and warnings after ALL tests execution.
215
225
  */
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) {
226
+ after(function () {
227
+ // Skip the hook if the logger is disabled or if the strategy is not failAfterAll
228
+ if (isDisabled() || (getStrategy() !== STRATEGY.failAfterAll)) {
219
229
  return;
220
230
  }
221
- // Collect issues immediately after the window is loaded and analyze them
222
- collectIssues().then(function () { return analyzeIssues(); });
231
+ // Analyze collected errors and warnings
232
+ analyzeIssues();
223
233
  });
224
234
  }
225
235
  /**
@@ -15,53 +15,59 @@ 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
-
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.
31
+ - **Hint:** To make reporting less confusing, dummy test can be added by the end of the spec to provide more clarity on why the spec failed, e.g:
50
32
 
51
33
  ```typescript
52
- import { jsErrorsLogger } from '@jahia/cypress';
34
+ describe('Tests for the UI module', () => {
35
+ it('Should validate flow A', () => { ... });
53
36
 
54
- // Attach Logger
55
- jsErrorsLogger.enable();
37
+ it('Should validate flow B', () => { ... });
56
38
 
57
- // Set preferrable error handling strategy
58
- jsErrorsLogger.setStrategy(jsErrorsLogger.STRATEGY.failFast);
39
+ it('Should validate flow C', () => { ... });
40
+ ...
59
41
 
60
- // Define allowed warnings that won't trigger failures
61
- jsErrorsLogger.setAllowedJsWarnings([
62
- 'Warning: React Hook',
63
- 'Warning: componentWillReceiveProps'
64
- ]);
42
+ // Dummy test to fail if any errors or warnings appear in the browser console,
43
+ // providing clearer insight into execution and failure reasons.
44
+ // Analysis itself will happen inside jsErrorsLogger module (if one is enabled).
45
+ it('Should ensure errors and warnings absense in browser console', () => {
46
+ cy.log('Analyze console messages');
47
+ });
48
+ });
49
+ ```
50
+ Say, there were JavaScript errors and warnings in all tests. Without that dummy test, the very last test in spec will be marked by Cypress as failed, even though it might pass:
51
+ ```
52
+ ✓ Should validate flow A
53
+ ✓ Should validate flow B
54
+ - Should validate flow C
55
+ ... <errors/warnings list for each visited url, grouped by test> ...
56
+ ```
57
+ But with that dummy test it will be much clearer what exactly happened:
58
+ ```
59
+ ✓ Should validate flow A
60
+ ✓ Should validate flow B
61
+ ✓ Should validate flow C
62
+ - Should ensure errors and warnings absence in browser console during spec execution
63
+ ... <errors/warnings list for each visited url, grouped by test> ...
64
+ ```
65
+ And in case of JavaScript errors and warnings absense (and all tests passed) it will also be much clearer - what validations were performed:
66
+ ```
67
+ ✓ Should validate flow A
68
+ ✓ Should validate flow B
69
+ ✓ Should validate flow C
70
+ ✓ Should ensure errors and warnings absense in browser console during spec execution
65
71
  ```
66
72
 
67
73
  ## Usage
@@ -72,11 +78,10 @@ jsErrorsLogger.setAllowedJsWarnings([
72
78
  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
79
 
74
80
  ```typescript
75
- import { jsErrorsLogger } from '@jahia/cypress';
81
+ import {jsErrorsLogger} from '@jahia/cypress';
76
82
 
77
- before(() => {
78
- jsErrorsLogger.enable();
79
- });
83
+ // Enable and attach JS Errors Logger
84
+ jsErrorsLogger.enable();
80
85
  ```
81
86
 
82
87
  ### Disabling the Logger
@@ -91,7 +96,7 @@ export JAHIA_HOOKS_DISABLE_JS_LOGGER="true"
91
96
  #### Disable for the specific Spec
92
97
 
93
98
  ```typescript
94
- import { jsErrorsLogger } from '@jahia/cypress';
99
+ import {jsErrorsLogger} from '@jahia/cypress';
95
100
 
96
101
  describe('Tests with disabled JS logger', () => {
97
102
  before(() => {
@@ -104,35 +109,76 @@ describe('Tests with disabled JS logger', () => {
104
109
  });
105
110
  ```
106
111
 
112
+ ## Configuration
113
+
114
+ ### Environment Variables
115
+
116
+ | Variable | Type | Description |
117
+ |---------------------------------|------|-------------|
118
+ | `JAHIA_HOOKS_DISABLE_JS_LOGGER` | boolean | Disables the logger when set to `true` |
119
+
120
+ ### Programmatic Configuration
121
+
122
+ 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.
123
+
124
+ ```typescript
125
+ import {jsErrorsLogger} from '@jahia/cypress';
126
+
127
+ // Enable and attach JS Errors Logger
128
+ jsErrorsLogger.enable();
129
+
130
+ // Set preferrable error handling strategy
131
+ jsErrorsLogger.setStrategy(jsErrorsLogger.STRATEGY.failAfterAll);
132
+
133
+ // Define allowed warnings that won't trigger failures
134
+ jsErrorsLogger.setAllowedJsWarnings([
135
+ 'Warning: React Hook',
136
+ 'Warning: componentWillReceiveProps'
137
+ ]);
138
+ ```
139
+
107
140
  ## Error Reporting Format
108
141
 
109
- ### Single Test Error (failFast/failAfterEach)
142
+ ### Single Test Error (failAfterEach)
110
143
 
111
144
  ```
112
- Error: CONSOLE ERRORS and WARNINGS FOUND:
113
- - TypeError: Cannot read property 'foo' of undefined
114
- - Warning: React Hook useEffect has missing dependency
145
+ CONSOLE ERRORS and WARNINGS FOUND:
146
+
147
+ ❌️ TEST: Should be authenticated when correct credentials and code are provided: ❌️
148
+ --------------------------------------------------
149
+ URL: http://localhost:8080/jahia/dashboard
150
+ ISSUES:
151
+ - ⚠️ Unsatisfied version 5.0.1 from @jahia/jcontent of shared singleton module redux (required ^4.0.5)
152
+ - ⚠️ Unsatisfied version 9.2.0 from @jahia/jcontent of shared singleton module react-redux (required ^8.0.5)
153
+ - ❌️ TypeError: Cannot read property 'user' of undefined
115
154
  ```
116
155
 
117
156
  ### Multiple Test Errors (failAfterAll)
118
157
 
119
158
  ```
120
- Error: CONSOLE ERRORS and WARNINGS FOUND:
159
+ CONSOLE ERRORS and WARNINGS FOUND:
121
160
 
122
- TEST: should load homepage
161
+ ❌️ TEST: Should be authenticated when correct credentials and code are provided: ❌️
162
+ --------------------------------------------------
163
+ URL: http://localhost:8080/jahia/dashboard
123
164
  ISSUES:
124
- - TypeError: Cannot read property 'user' of undefined
125
- - Warning: componentWillMount is deprecated
126
-
127
- TEST: should handle form submission
165
+ - ⚠️ No satisfying version (^1.11.9) of shared module dayjs found in shared scope default.
166
+ - ⚠️ No satisfying version (^3.0.6) of shared module @jahia/react-material found in shared scope default.
167
+ - ❌️ TypeError: Cannot read property 'user' of undefined
168
+ ==================================================
169
+
170
+ ❌️ TEST: Should be authenticated on a specific site when correct credentials and code are provided: ❌️
171
+ --------------------------------------------------
172
+ URL: http://localhost:8080/jahia/admin
128
173
  ISSUES:
129
- - ReferenceError: handleSubmit is not defined
174
+ - ⚠️ No satisfying version (^3.0.6) of shared module @jahia/react-material found in shared scope default.
175
+ ==================================================
130
176
  ```
131
177
 
132
178
  ## Best Practices
133
179
 
134
180
  ### Development Environment
135
- - Use `STRATEGY.failFast` for immediate feedback during development
181
+ - Use `STRATEGY.failAfterEach` for immediate feedback during development
136
182
  - Configure comprehensive allowed warnings list for known, acceptable warnings
137
183
  - Enable detailed logging for debugging purposes
138
184
 
@@ -161,4 +207,4 @@ ISSUES:
161
207
 
162
208
  | Enum | Values | Description |
163
209
  |------|--------|-------------|
164
- | `STRATEGY` | `failFast`, `failAfterAll`, `failAfterEach` | Error handling strategies |
210
+ | `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.2",
4
4
  "scripts": {
5
5
  "build": "tsc",
6
6
  "lint": "eslint src -c .eslintrc.json --ext .ts --max-warnings=0"
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable brace-style */
2
2
  /* eslint-disable max-statements-per-line */
3
+
3
4
  /**
4
5
  * Module for monitoring and reporting JavaScript errors and warnings in Cypress tests.
5
6
  * Provides methods to enable, disable, and check logger status.
@@ -14,17 +15,11 @@ const envVarStrategy = '__JS_LOGGER_STRATEGY__';
14
15
  /**
15
16
  * Strategy for handling JavaScript errors and warnings in Cypress tests.
16
17
  *
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
18
  * - failAfterEach: Collect all errors and warnings *during test* execution and fail if any issues are found.
24
19
  *
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.
20
+ * Proc: Allows each test to run, collects console errors and warnings,
21
+ * and fails the particular test by the end of its execution if any issues are found.
22
+ * Cons: Since the analysis happens in afterEach() hook, the rest of spec will be ignored.
28
23
  *
29
24
  * - failAfterAll: Collect all errors and warnings *after all tests* and fail at the end of the test suite.
30
25
  *
@@ -33,25 +28,41 @@ const envVarStrategy = '__JS_LOGGER_STRATEGY__';
33
28
  * Cons: Reporting might be confusing, e.g. - cypress will report the very last test as failed, while many tests might have issues.
34
29
  * This is because the hook is executed after all tests are completed, so the last test is reported as failed.
35
30
  */
36
- enum STRATEGY { failFast, failAfterAll, failAfterEach }
31
+ enum STRATEGY { failAfterEach, failAfterAll }
37
32
 
38
33
  /**
39
34
  * Auxiliary type to represent a single item in the collector.
40
35
  * It contains the test title and an array of error or warning messages collected during the test.
41
36
  */
42
37
  type CollectorItem = {
38
+ url: string; // URL of the current page where the issue was found
43
39
  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
40
+ errors: {type: string; msg: string}[]; // Array of error or warning messages collected during the test
45
41
  };
46
42
 
43
+ /**
44
+ * Returns an emoji based on the type of message.
45
+ * @param {string} type
46
+ */
47
+ function getEmoji(type: string): string {
48
+ switch (type) {
49
+ case 'warn':
50
+ return '⚠️';
51
+ case 'error':
52
+ return '❌️';
53
+ default:
54
+ return '';
55
+ }
56
+ }
57
+
47
58
  /**
48
59
  * Returns the current strategy for handling JavaScript errors and warnings in Cypress tests.
49
60
  * @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,
61
+ * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failAfterEach` strategy,
51
62
  * which is falsy in JavaScript, so we need to check if the variable is undefined.
52
63
  */
53
64
  function getStrategy(): STRATEGY {
54
- return typeof Cypress.env(envVarStrategy) === 'undefined' ? STRATEGY.failFast : Cypress.env(envVarStrategy);
65
+ return typeof Cypress.env(envVarStrategy) === 'undefined' ? STRATEGY.failAfterAll : Cypress.env(envVarStrategy);
55
66
  }
56
67
 
57
68
  /**
@@ -95,41 +106,53 @@ function setAllowedJsWarnings(warnings: string[]): void { Cypress.env(envVarAllo
95
106
 
96
107
  /**
97
108
  * 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
109
  */
100
110
  function attachJsInterceptor(): void {
101
- Cypress.on('window:before:load', window => {
102
- // Skip 'window:before:load' hook if the logger is not enabled
111
+ /**
112
+ * Custom 'window:before:load' hook to attach interceptors before the page is loaded and spy on console messages.
113
+ */
114
+ cy.on('window:before:load', window => {
115
+ // Skip 'window:before:load' hook if the logger is disabled
103
116
  if (isDisabled()) { return; }
104
-
105
- // Spy on console.error and console.warn to capture errors and warnings
117
+ // Spy on console.error and console.warn methods 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
+ cy.on('window:load', win => {
126
+ // Skip 'window:load' hook if the logger is disabled
127
+ if (isDisabled()) { return; }
128
+ // Collect errors and warnings after the page is fully loaded
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 {
116
- const allowedWarnings = getAllowedJsWarnings();
117
- let consoleIssues: string[] = [];
137
+ function collectIssues(win: Cypress.AUTWindow): Cypress.Chainable {
138
+ let consoleIssues: {type: string, msg: string}[] = [];
118
139
 
119
140
  // Look for console errors and warnings, collected by the spies
120
141
  return cy.get('@errors')
121
142
  .invoke('getCalls')
122
143
  .then(errorCalls => {
123
144
  // All errors should be collected
124
- consoleIssues = errorCalls.flatMap((call: { args: string[] }) => call.args);
125
-
126
- // Analyze warnings
127
- cy.get('@warnings')
145
+ consoleIssues = errorCalls.flatMap((call: { args: unknown[] }) => call.args.map((arg: string) => ({type: 'error', msg: String(arg)})));
146
+ })
147
+ .then(() => {
148
+ // Analyze warnings - return the chain to maintain proper async flow
149
+ return cy.get('@warnings')
128
150
  .invoke('getCalls')
129
151
  .then(warningCalls => {
130
- warningCalls.flatMap((call: { args: string[] }) => call.args).forEach((arg: string) => {
152
+ const allowedWarnings = getAllowedJsWarnings();
153
+ warningCalls.flatMap((call: { args: unknown[] }) => call.args).forEach((arg: string) => {
131
154
  // Only warnings not in the allowed list should be collected
132
- if (!allowedWarnings.some((item: string) => arg.includes(item))) { consoleIssues.push(arg); }
155
+ if (!allowedWarnings.some((item: string) => arg.includes(item))) { consoleIssues.push({type: 'warn', msg: String(arg)}); }
133
156
  });
134
157
  });
135
158
  })
@@ -138,27 +161,41 @@ function collectIssues(): Cypress.Chainable {
138
161
  if (consoleIssues.length > 0) {
139
162
  setCollectedIssues([
140
163
  ...getCollectedIssues(),
141
- {test: Cypress.currentTest.title, errors: consoleIssues}
164
+ {url: win.location.href, test: Cypress.currentTest.title, errors: consoleIssues}
142
165
  ]);
143
166
  }
144
- })
145
- .then(() => {
146
- // Return a Cypress chainable object to allow chaining
147
- return cy.wrap(null, {log: false});
148
167
  });
149
168
  }
150
169
 
151
170
  /**
152
171
  * Analyzes collected JavaScript errors and warnings and throws an error if any were found.
153
172
  */
154
- function analyzeIssues(): void {
155
- cy.then(() => {
156
- const failures = getCollectedIssues();
173
+ function analyzeIssues(): Cypress.Chainable {
174
+ return cy.wrap(getCollectedIssues()).then(failures => {
157
175
  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')}`;
176
+ // Group all issues by test title
177
+ const groupedByTest = failures.reduce((acc: Record<string, CollectorItem[]>, failure) => {
178
+ acc[failure.test] = acc[failure.test] || [];
179
+ acc[failure.test].push(failure);
180
+
181
+ return acc;
182
+ }, {} as Record<string, CollectorItem[]>);
183
+
184
+ // Format the error message for each test with its collected issues
185
+ const errorMessage = Object.entries(groupedByTest).map(([test, items]) => {
186
+ const urlsAndErrors = items.map(item =>
187
+ `URL: ${item.url}\nISSUES:\n${item.errors.map((e: {
188
+ type: string;
189
+ msg: string
190
+ }) => `- ${e.type === 'warn' ? getEmoji('warn') : getEmoji('error')} ${e.msg}`).join('\n')}`
191
+ ).join('\n\n');
192
+
193
+ // Return the formatted message for the test;
194
+ // Intentionally use fixed-width (50 chars) separators for better readability,
195
+ // when the message might be wrapped
196
+ return `${getEmoji('error')}️ TEST: ${test.trim()} ${getEmoji('error')}️\n${'-'.repeat(50)}\n${urlsAndErrors}\n${'='.repeat(50)}`;
161
197
  }).join('\n\n');
198
+
162
199
  // Reset the collector for the next test run
163
200
  setCollectedIssues([]);
164
201
 
@@ -176,60 +213,40 @@ function disable(): void { Cypress.env(envVarDisableJsLogger, true); }
176
213
 
177
214
  /**
178
215
  * 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.
216
+ * It sets up listeners for console errors and warnings, collects them for each visited URL in each test,
217
+ * and throws an error if any issues are found after each or all tests are executed (depending on the strategy chosen).
182
218
  */
183
219
  function enable(): void {
184
- // Ensure the logger is enabled by default
220
+ // Ensure the logger is enabled
185
221
  Cypress.env(envVarDisableJsLogger, false);
186
222
 
187
- // Attach errors and warnings collector
188
- attachJsInterceptor();
189
-
190
223
  /**
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.
224
+ * Attach Cypress hooks forconsole messages collecting before EACH test execution.
225
+ * Use 'beforeEach' hook and local (cy) context instead of global (Cypress) one
226
+ * to ensure proper async flow and avoid events and hooks flakiness.
193
227
  */
194
- 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
- }
228
+ before(() => {
229
+ attachJsInterceptor();
209
230
  });
210
231
 
211
232
  /**
212
- * Custom 'after' hook to analyze collected errors and warnings after all tests are executed.
233
+ * Custom 'afterEach' hook to analyze JavaScript errors and warnings after EACH test execution.
213
234
  */
214
- after(() => {
215
- // 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
- if (isDisabled() || getStrategy() !== STRATEGY.failAfterAll) { return; }
218
-
235
+ afterEach(() => {
236
+ // Skip the hook if the logger is disabled or if the strategy is not failAfterEach
237
+ if (isDisabled() || (getStrategy() !== STRATEGY.failAfterEach)) { return; }
219
238
  // Analyze collected errors and warnings
220
239
  analyzeIssues();
221
240
  });
222
241
 
223
242
  /**
224
- * Custom 'window:load' hook to collect JavaScript errors and warnings right after the page is loaded.
225
- * Applicable only for the failFast strategy.
243
+ * Custom 'after' hook to analyze JavaScript errors and warnings after ALL tests execution.
226
244
  */
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());
245
+ after(() => {
246
+ // Skip the hook if the logger is disabled or if the strategy is not failAfterAll
247
+ if (isDisabled() || (getStrategy() !== STRATEGY.failAfterAll)) { return; }
248
+ // Analyze collected errors and warnings
249
+ analyzeIssues();
233
250
  });
234
251
  }
235
252