@jahia/cypress 6.0.0 → 6.2.0

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.
@@ -0,0 +1,281 @@
1
+ /* eslint-disable brace-style */
2
+ /* eslint-disable max-statements-per-line */
3
+ /**
4
+ * Module for monitoring and reporting JavaScript errors and warnings in Cypress tests.
5
+ * Provides methods to enable, disable, and check logger status.
6
+ */
7
+
8
+ const envVarDisabled = 'JS_LOGGER_DISABLED';
9
+ const envVarCollector = '__JS_LOGGER_FAILURES__';
10
+ const envVarAllowedWarnings = '__JS_LOGGER_ALLOWED_WARNINGS__';
11
+ const envVarStrategy = '__JS_LOGGER_STRATEGY__';
12
+
13
+ /**
14
+ * Strategy for handling JavaScript errors and warnings in Cypress tests.
15
+ * - failFast: Fail *immediately* when an error is detected.
16
+ * - failAfterEach: Collect all errors and warnings *during test* execution and fail if any issues are found.
17
+ * - failAfterAll: Collect all errors and warnings *after all tests* and fail at the end of the test suite.
18
+ */
19
+ enum STRATEGY { failFast, failAfterAll, failAfterEach }
20
+
21
+ /**
22
+ * Returns the current strategy for handling JavaScript errors and warnings in Cypress tests.
23
+ * @returns {STRATEGY} - The current strategy for handling JavaScript errors and warnings.
24
+ * @note be careful with Cypress.env(envVarStrategy), since it might return `0` for `failFast` strategy,
25
+ * which is falsy in JavaScript, so we need to check if the variable is undefined.
26
+ */
27
+ function getStrategy(): STRATEGY {
28
+ return typeof Cypress.env(envVarStrategy) === 'undefined' ? STRATEGY.failFast : Cypress.env(envVarStrategy);
29
+ }
30
+
31
+ /**
32
+ * Sets the strategy for handling JavaScript errors and warnings in Cypress tests.
33
+ * @param {STRATEGY} strategy - Strategy for handling JavaScript errors and warnings.
34
+ * @throws {Error} If an invalid strategy is provided.
35
+ * @returns {void}
36
+ */
37
+ function setStrategy(strategy: STRATEGY): void { Cypress.env(envVarStrategy, strategy); }
38
+
39
+ /**
40
+ * Checks if the js errors and warnings logger is disabled.
41
+ * @returns {boolean} - true if the logger is disabled, false otherwise.
42
+ */
43
+ function isDisabled(): boolean { return Cypress.env(envVarDisabled) === true; }
44
+
45
+ /**
46
+ * Sets the list of allowed warnings that will not be reported by the logger.
47
+ * @param warnings {string[]} - Array of warning messages to be allowed.
48
+ * @return {void}
49
+ */
50
+ function setAllowedJsWarnings(warnings: string[]): void { Cypress.env(envVarAllowedWarnings, warnings); }
51
+
52
+ /**
53
+ * Attaches custom hooks to Cypress events to monitor and report JavaScript errors and warnings.
54
+ * This method is called automatically in registerSupport.ts#registerSupport
55
+ * It sets up listeners for console errors and warnings, collects them after each test,
56
+ * and throws an error if any issues are found after all tests are executed.
57
+ */
58
+ function attachHooks(): void {
59
+ // Skip hook attachment if the logger is disabled (e.g. from CI/CD pipeline)
60
+ if (isDisabled()) { return; }
61
+
62
+ switch (getStrategy()) {
63
+ case STRATEGY.failAfterAll:
64
+ attachFailAfterAll();
65
+ break;
66
+ case STRATEGY.failAfterEach:
67
+ attachFailAfterEach();
68
+ break;
69
+ default:
70
+ attachFailFast();
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Custom hook implementation which fails the test after all tests are executed if any JavaScript errors or warnings are found.
76
+ *
77
+ * Proc: Allows all tests to run, collects console errors and warnings, and fails the test suite at the end if any issues are found.
78
+ * This is useful for reporting all issues at once after all tests are executed, rather than failing immediately on the first issue.
79
+ * Cons: Reporting might be confusing, e.g. - cypress will report the very last test as failed, while many tests might have issues.
80
+ * This is because the hook is executed after all tests are completed, so the last test is reported as failed.
81
+ */
82
+ function attachFailAfterAll(): void {
83
+ /**
84
+ * Custom 'window:before:load' hook to spy on console errors and warnings
85
+ * This hook is executed before the page is loaded, allowing us to capture console messages.
86
+ */
87
+ Cypress.on('window:before:load', window => {
88
+ // Skip 'window:before:load' hook if the logger is not enabled
89
+ // Double-check in case if functionality was disabled within the test-case code
90
+ // to skip js validations for some particular test and enable it afterward
91
+ if (isDisabled()) { return; }
92
+
93
+ cy.spy(window.console, 'error').as('errors');
94
+ cy.spy(window.console, 'warn').as('warnings');
95
+ });
96
+
97
+ /**
98
+ * Custom 'afterEach' hook to collect console errors and warnings
99
+ */
100
+ afterEach(() => {
101
+ // Skip 'afterEach' hook if the logger is not enabled
102
+ // Double-check in case if functionality was disabled within the test-case code
103
+ // to skip js validations for some particular test and enable it afterward
104
+ if (isDisabled()) { return; }
105
+
106
+ let consoleIssues = [];
107
+ const allowedWarnings = Cypress.env(envVarAllowedWarnings) || [];
108
+
109
+ // All errors should be collected
110
+ cy.get('@errors')
111
+ .invoke('getCalls')
112
+ .then(calls => { consoleIssues = calls; });
113
+
114
+ // Only warnings not in the allowed list should be collected
115
+ cy.get('@warnings')
116
+ .invoke('getCalls')
117
+ .each((call: { args: string[]}) => {
118
+ call.args.forEach((arg: string) => {
119
+ if (!allowedWarnings.some((item: string) => arg.includes(item))) {
120
+ consoleIssues.push(arg);
121
+ }
122
+ });
123
+ });
124
+
125
+ // Collect all issues and store them in the Cypress environment variable for later usage
126
+ cy.then(() => {
127
+ if (consoleIssues.length > 0) {
128
+ Cypress.env(envVarCollector, [...(Cypress.env(envVarCollector) || []), {
129
+ test: Cypress.currentTest.title,
130
+ errors: consoleIssues
131
+ }]);
132
+ }
133
+ });
134
+ });
135
+
136
+ /**
137
+ * Custom 'after' hook to analyze collected errors and warnings after all tests are executed.
138
+ */
139
+ after(() => {
140
+ // Skip 'after' hook if the logger is not enabled
141
+ // Double-check in case if functionality was disabled within the test-case code
142
+ // to skip js validations for some particular test and enable it afterward
143
+ if (isDisabled()) { return; }
144
+
145
+ // Analyze collected errors and warnings
146
+ const failures = Cypress.env(envVarCollector) || [];
147
+ cy.log('[JS ERRORS LOGGER] Analyze collected issues').then(() => {
148
+ if (failures.length > 0) {
149
+ // Format the error message for each test
150
+ const errorMessage = failures.map((failure: { test: string; errors: string[]; }) => {
151
+ return `TEST: ${failure.test}\nISSUES:\n${failure.errors.map((e: string) => `- ${e}`).join('\n')}`;
152
+ }).join('\n\n');
153
+ // Throw an error with the collected issues
154
+ throw new Error('CONSOLE ERRORS and WARNINGS FOUND:\n\n' + errorMessage);
155
+ } else {
156
+ cy.log('[JS ERRORS LOGGER] No console errors or warnings found.');
157
+ }
158
+ });
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Custom hook implementation which fails the test after each test execution if any JavaScript errors or warnings are found.
164
+ *
165
+ * Proc: Allows each test to run, collects console errors and warnings, and fails the particular test by the end of it's execution
166
+ * if any issues are found.
167
+ * Cons: If errors or warnings were found during beforeEach or afterEach hook(s), the rest of spec will be ignored.
168
+ */
169
+ function attachFailAfterEach(): void {
170
+ // Double-check in case if functionality was disabled within the test-case code
171
+ // to skip js validations for some particular test and enable it afterward
172
+ if (isDisabled()) { return; }
173
+
174
+ /**
175
+ * Custom 'window:before:load' hook to spy on console errors and warnings
176
+ * This hook is executed before the page is loaded, allowing us to capture console messages.
177
+ */
178
+ Cypress.on('window:before:load', window => {
179
+ cy.spy(window.console, 'error').as('errors');
180
+ cy.spy(window.console, 'warn').as('warnings');
181
+ });
182
+
183
+ /**
184
+ * Custom 'afterEach' hook to collect console errors and warnings
185
+ */
186
+ afterEach(() => {
187
+ let consoleIssues = [];
188
+ const allowedWarnings = Cypress.env(envVarAllowedWarnings) || [];
189
+
190
+ // All errors should be collected
191
+ cy.get('@errors')
192
+ .invoke('getCalls')
193
+ .then(calls => { consoleIssues = calls; });
194
+
195
+ // Only warnings not in the allowed list should be collected
196
+ cy.get('@warnings')
197
+ .invoke('getCalls')
198
+ .each((call: { args: string[]}) => {
199
+ call.args.forEach((arg: string) => {
200
+ if (!allowedWarnings.some((item: string) => arg.includes(item))) {
201
+ consoleIssues.push(arg);
202
+ }
203
+ });
204
+ });
205
+
206
+ // Analyze collected errors and warnings
207
+ cy.log('[JS ERRORS LOGGER] Analyze collected issues').then(() => {
208
+ if (consoleIssues.length > 0) {
209
+ // Format the error message for each test
210
+ const errorMessage = consoleIssues.map((e: string) => `- ${e}`).join('\n');
211
+ // Throw an error with the collected issues
212
+ throw new Error('CONSOLE ERRORS and WARNINGS FOUND:\n\n' + errorMessage);
213
+ } else {
214
+ cy.log('[JS ERRORS LOGGER] No console errors or warnings found.');
215
+ }
216
+ });
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Custom hook implementation which fails the test immediately when a JavaScript error or warning is detected.
222
+ *
223
+ * Proc: Allows each test to run, looks for console errors and warnings, and fails the particular test IMMEDIATELLY
224
+ * if any issues are found.
225
+ * Cons: If errors or warnings were found during beforeEach or afterEach hook(s), the rest of spec will be ignored.
226
+ */
227
+ function attachFailFast(): void {
228
+ // Double-check in case if functionality was disabled within the test-case code
229
+ // to skip js validations for some particular test and enable it afterward
230
+ if (isDisabled()) { return; }
231
+
232
+ /**
233
+ * Custom 'window:before:load' hook to spy on console errors and warnings
234
+ * This hook is executed before the page is loaded, allowing us to capture console messages.
235
+ */
236
+ Cypress.on('window:before:load', window => {
237
+ cy.spy(window.console, 'error').as('errors');
238
+ cy.spy(window.console, 'warn').as('warnings');
239
+ });
240
+
241
+ Cypress.on('window:load', () => {
242
+ // DOM should be loaded and parsed by now, so we can check for console errors and warnings
243
+ let consoleIssues = [];
244
+ const allowedWarnings = Cypress.env(envVarAllowedWarnings) || [];
245
+
246
+ // All errors should be collected
247
+ cy.get('@errors')
248
+ .invoke('getCalls')
249
+ .then(calls => { consoleIssues = calls; });
250
+
251
+ // Only warnings not in the allowed list should be collected
252
+ cy.get('@warnings')
253
+ .invoke('getCalls')
254
+ .each((call: { args: string[]}) => {
255
+ call.args.forEach((arg: string) => {
256
+ if (!allowedWarnings.some((item: string) => arg.includes(item))) {
257
+ consoleIssues.push(arg);
258
+ }
259
+ });
260
+ }).then(() => {
261
+ if (consoleIssues.length > 0) {
262
+ // Format the error message for each test
263
+ const errorMessage = consoleIssues.map((e: string) => `- ${e}`).join('\n');
264
+ // Throw an error with the collected issues
265
+ throw new Error('CONSOLE ERRORS and WARNINGS FOUND:\n' + errorMessage);
266
+ } else {
267
+ cy.log('[JS ERRORS LOGGER] No console errors or warnings found.');
268
+ }
269
+ });
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Exports the jsLogger module with methods to attach hooks, enable/disable logging, and set allowed warnings.
275
+ */
276
+ export const jsErrorsLogger = {
277
+ attachHooks,
278
+ setAllowedJsWarnings,
279
+ setStrategy,
280
+ STRATEGY
281
+ };
@@ -33,7 +33,8 @@ export const executeGroovy = function (scriptFile: string, replacements?: { [key
33
33
  files: [{
34
34
  fileName: scriptFile,
35
35
  replacements,
36
- type: 'text/plain'
36
+ type: 'text/plain',
37
+ encoding: 'utf-8'
37
38
  }],
38
39
  jahiaServer
39
40
  }).then(r => r[0]);
@@ -47,6 +47,15 @@ function processContent(formFile: FormFile) {
47
47
  }
48
48
 
49
49
  formFile.fileContent = content;
50
+ // If the file has an encoding, create a Blob with the new Blob constructor. It handle correctly the encoding
51
+ // for the groovy scripts
52
+ // Did not change for all files because jar files can be added as well, which are binary files
53
+ // In that case the function binaryStringToBlob will be used
54
+ if (formFile.encoding) {
55
+ return new Blob([content], {type: formFile.type});
56
+ }
57
+
58
+ // Default to binary string to blob conversion
50
59
  return Cypress.Blob.binaryStringToBlob(content, formFile.type);
51
60
  }
52
61
 
@@ -5,6 +5,8 @@ import {logout} from './logout';
5
5
  import {fixture} from './fixture';
6
6
  import {repeatUntil} from './repeatUntil';
7
7
  import {step} from './testStep';
8
+ // FUNCTIONALITY IS TEMPORARY DISABLED DUE TO UNEXPECTED ISSUES
9
+ // import {jsErrorsLogger} from './jsErrorsLogger';
8
10
 
9
11
  export const registerSupport = (): void => {
10
12
  Cypress.Commands.add('apolloClient', apolloClient);
@@ -24,4 +26,11 @@ export const registerSupport = (): void => {
24
26
  Cypress.Commands.overwrite('fixture', fixture);
25
27
 
26
28
  Cypress.Commands.add('step', step);
29
+
30
+ // Since registerSupport() function is called in each repo from e2e.js,
31
+ // attaching the JavaScript errors logger hooks here ensures that logger is initialized automatically
32
+ // for all tests without needing to call it explicitly in each test file.
33
+ // This is useful for capturing and logging JavaScript errors across all tests.
34
+ // FUNCTIONALITY IS TEMPORARY DISABLED DUE TO UNEXPECTED ISSUES
35
+ // jsErrorsLogger.attachHooks();
27
36
  };
@@ -1,78 +1,108 @@
1
1
  /**
2
- * Helper class to decorate Cypress log messages with different log levels (INFO and DEBUG at the moment).
3
- * Class contains only static methods and should not be instantiated.
2
+ * Helper module to decorate Cypress log messages with different log levels (INFO and DEBUG at the moment).
4
3
  * @example
4
+ * // Switch default logging verbosity to DEBUG
5
+ * Log.setVerbosity(Log.LEVELS.DEBUG);
6
+ *
5
7
  * Log.info('This is an info message');
6
8
  * Log.debug('This is a debug message');
7
- * Log.json('DEBUG', myJSON);
9
+ * Log.json(Log.LEVELS.DEBUG, myJSON);
8
10
  * Log.info('My info message').then(() => { ... });
9
- * @note The log level can be set by changing the `Log.LEVEL` variable in the code (default is `INFO`).
11
+ *
12
+ * @note The log verbosity can be set by calling `Log.setVerbosity(Log.LEVELS.DEBUG)` in the code (default is `INFO`).
10
13
  * It tells the logger to log only messages with the given level and above.
11
14
  */
12
- export class Log {
13
- // The default log level is set to 'INFO' and can be changed by setting the Log.LEVEL variable in the code
14
- static LEVEL = 'INFO';
15
- // The log levels are defined in the levels array and are ordered from the lowest to the highest priority
16
- private static levels = ['DEBUG', 'INFO'];
17
15
 
18
- /**
19
- * Logs INFO message
20
- * @param {string} message - log message
21
- * @returns {Cypress.Chainable} - Cypress chainable object
22
- */
23
- static info(message: string): Cypress.Chainable {
24
- return Log._send_('INFO', message);
25
- }
16
+ // ENV variable to store the logging verbosity level
17
+ const envVarLoggingVerbosity = '__LOG_VERBOSITY__';
26
18
 
27
- /**
28
- * Logs DEBUG message
29
- * @param {string} message - log message
30
- * @returns {Cypress.Chainable} - Cypress chainable object
31
- */
32
- static debug(message: string): Cypress.Chainable {
33
- return Log._send_('DEBUG', message);
34
- }
19
+ /**
20
+ * Logging levels enumerator.
21
+ */
22
+ enum LEVEL { DEBUG, INFO }
35
23
 
36
- /**
37
- * Logs JSON object with logging level given
38
- * @param {string} level - log level (e.g. 'INFO', 'DEBUG')
39
- * @param {string} json - json object to be logged
40
- * @returns {Cypress.Chainable} - Cypress chainable object
41
- */
42
- static json(level: string, json: string): Cypress.Chainable {
43
- return Log._send_(level, JSON.stringify(json, null, 2));
44
- }
24
+ /**
25
+ * Return the current logging verbosity level.
26
+ * @returns {LEVEL} - current logging level set in Cypress environment variable `__LOG_VERBOSITY__`
27
+ * @note be careful with Cypress.env(envVarLoggingVerbosity), since it might return `0` for `DEBUG` level,
28
+ * which is falsy in JavaScript, so we need to check if the variable is undefined.
29
+ */
30
+ function getVerbosity(): LEVEL {
31
+ return typeof Cypress.env(envVarLoggingVerbosity) === 'undefined' ? LEVEL.INFO : Cypress.env(envVarLoggingVerbosity);
32
+ }
33
+
34
+ /**
35
+ * Sets the logging verbosity level for the logger. Messages with a level lower than the set level will not be logged.
36
+ * @param {LEVEL} level - log level to be set (e.g. 'DEBUG', 'INFO')
37
+ * @return {void}
38
+ */
39
+ function setVerbosity(level: LEVEL): void {
40
+ Cypress.env(envVarLoggingVerbosity, level);
41
+ }
42
+
43
+ /**
44
+ * Logs INFO message
45
+ * @param {string} message - log message
46
+ * @returns {Cypress.Chainable} - Cypress chainable object
47
+ */
48
+ function info(message: string): Cypress.Chainable {
49
+ return _send_(Log.LEVEL.INFO, message);
50
+ }
51
+
52
+ /**
53
+ * Logs DEBUG message
54
+ * @param {string} message - log message
55
+ * @returns {Cypress.Chainable} - Cypress chainable object
56
+ */
57
+ function debug(message: string): Cypress.Chainable {
58
+ return _send_(Log.LEVEL.DEBUG, message);
59
+ }
60
+
61
+ /**
62
+ * Logs JSON object with logging level given
63
+ * @param {LEVEL} level - log level (e.g. 'INFO', 'DEBUG')
64
+ * @param {string} text - json object to be logged
65
+ * @returns {Cypress.Chainable} - Cypress chainable object
66
+ */
67
+ function json(level: LEVEL, text: string): Cypress.Chainable {
68
+ return _send_(level, JSON.stringify(text, null, 2));
69
+ }
45
70
 
46
- /**
47
- * Private method to send the log message to Cypress log
48
- * @param {string} level - log level (e.g. 'INFO', 'DEBUG')
49
- * @param {string} message - log message
50
- * @note The method checks if the log level is enabled before sending the message to Cypress log
51
- * and uses the Cypress.log method to display the message in the Cypress log
52
- * @note The method is private and should not be called directly
53
- * Use the public methods (info, debug, error, warning) to send log messages
54
- * @returns {Cypress.Chainable} - Cypress chainable object
55
- * @private
56
- */
57
- private static _send_(level: string, message: string): Cypress.Chainable {
58
- // Check if the log level is valid
59
- if (!Log.levels.includes(level.toUpperCase())) {
60
- throw new Error(`Log level "${level}" is not supported. Supported levels are: ${Log.levels.join(', ')}`);
61
- }
71
+ /**
72
+ * Private method to send the log message to Cypress log
73
+ * @param {LEVEL} level - log level (e.g. 'INFO', 'DEBUG')
74
+ * @param {string} message - log message
75
+ * @note The method checks if the log level is enabled before sending the message to Cypress log
76
+ * and uses the Cypress.log method to display the message in the Cypress log
77
+ * @note The method is private and should not be called directly
78
+ * Use the public methods (info, debug, error, warning) to send log messages
79
+ * @returns {Cypress.Chainable} - Cypress chainable object
80
+ * @private
81
+ */
82
+ function _send_(level: LEVEL, message: string): Cypress.Chainable {
83
+ // Check if the log level is valid
84
+ if (!Object.values(Log.LEVEL).includes(level)) {
85
+ throw new Error(`Log level "${level}" is not supported. Supported levels are: ${Log.LEVEL}`);
86
+ }
62
87
 
63
- // Check if the log level is enabled,
64
- // take into account the log level set in Cypress.env('LOG_LEVEL') and the log level set in the Log.LEVEL variable.
65
- // If the log level is enabled, send the message to Cypress log.
66
- if (
67
- (Log.levels.includes(Cypress.env('LOG_LEVEL')?.toUpperCase()) && Log.levels.indexOf(level.toUpperCase()) >= Log.levels.indexOf(Cypress.env('LOG_LEVEL')?.toUpperCase())) ||
68
- Log.levels.indexOf(level.toUpperCase()) >= Log.levels.indexOf(Log.LEVEL)
69
- ) {
70
- // Send the message to Cypress log
71
- // use cy.then() to ensure that the log message is sent in the correct order
72
- // and use cy.wrap() to return the Cypress chainable object
73
- return cy.then(() => {
74
- Cypress.log({displayName: `[ ${level.toUpperCase()} ]`, message: `${message}`});
75
- }).then(cy.wrap);
76
- }
88
+ // Check if the log level is enabled,
89
+ // take into account the log level set in Cypress.env('LOG_LEVEL') and the log level set in the Log.LEVEL variable.
90
+ // If the log level is enabled, send the message to Cypress log.
91
+ if (level >= getVerbosity()) {
92
+ // Send the message to Cypress log
93
+ // use cy.then() to ensure that the log message is sent in the correct order
94
+ // and use cy.wrap() to return the Cypress chainable object
95
+ return cy.then(() => {
96
+ Cypress.log({displayName: `[ ${Log.LEVEL[level].toUpperCase()} ]`, message: `${message}`});
97
+ }).then(cy.wrap);
77
98
  }
78
99
  }
100
+
101
+ // Export the Log module with methods to log messages and set the logging level
102
+ export const Log = {
103
+ info,
104
+ debug,
105
+ json,
106
+ setVerbosity,
107
+ LEVEL
108
+ };