@applitools/eyes-cypress 3.23.9 → 3.24.0-beta.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +2195 -2186
  2. package/LICENSE +25 -25
  3. package/README.md +778 -769
  4. package/bin/eyes-setup.js +21 -21
  5. package/commands.js +2 -2
  6. package/dist/browser/spec-driver.js +104 -0
  7. package/dist/plugin/handler.js +55 -0
  8. package/eyes-index.d.ts +34 -34
  9. package/index.js +2 -2
  10. package/package.json +97 -81
  11. package/src/browser/commands.js +167 -147
  12. package/src/browser/eyesCheckMapping.js +71 -0
  13. package/src/browser/eyesOpenMapping.js +34 -0
  14. package/src/browser/makeSend.js +18 -22
  15. package/src/browser/refer.js +57 -0
  16. package/src/browser/sendRequest.js +16 -16
  17. package/src/browser/socket.js +143 -0
  18. package/src/browser/socketCommands.js +81 -0
  19. package/src/browser/spec-driver.ts +109 -0
  20. package/src/pem/server.cert +22 -22
  21. package/src/pem/server.key +27 -27
  22. package/src/plugin/concurrencyMsg.js +8 -8
  23. package/src/plugin/config.js +54 -53
  24. package/src/plugin/defaultPort.js +1 -1
  25. package/src/plugin/errorDigest.js +96 -96
  26. package/src/plugin/getErrorsAndDiffs.js +34 -34
  27. package/src/plugin/handleTestResults.js +39 -0
  28. package/src/plugin/handler.ts +58 -0
  29. package/src/plugin/hooks.js +49 -42
  30. package/src/plugin/isGlobalHooksSupported.js +13 -13
  31. package/src/plugin/pluginExport.js +60 -57
  32. package/src/plugin/server.js +98 -46
  33. package/src/plugin/startPlugin.js +13 -34
  34. package/src/plugin/webSocket.js +130 -0
  35. package/src/setup/addEyesCommands.js +24 -24
  36. package/src/setup/addEyesCypressPlugin.js +15 -15
  37. package/src/setup/getCypressConfig.js +16 -16
  38. package/src/setup/getFilePath.js +22 -22
  39. package/src/setup/handleCommands.js +23 -23
  40. package/src/setup/handlePlugin.js +23 -23
  41. package/src/setup/handleTypeScript.js +21 -21
  42. package/src/setup/isCommandsDefined.js +7 -7
  43. package/src/setup/isPluginDefined.js +7 -7
  44. package/test/fixtures/testAppCopies/.gitignore +1 -1
  45. package/src/browser/eyesCheckWindow.js +0 -132
  46. package/src/browser/getAllBlobs.js +0 -14
  47. package/src/browser/getBrowserInfo.js +0 -39
  48. package/src/browser/makeHandleCypressViewport.js +0 -22
  49. package/src/browser/poll.js +0 -25
  50. package/src/plugin/app.js +0 -42
  51. package/src/plugin/handlers.js +0 -205
  52. package/src/plugin/makeHandleBatchResultsFile.js +0 -17
  53. package/src/plugin/pollingHandler.js +0 -46
  54. package/src/plugin/processCloseAndAbort.js +0 -33
  55. package/src/plugin/runningTests.js +0 -27
  56. package/src/plugin/waitForBatch.js +0 -33
@@ -1,96 +1,96 @@
1
- 'use strict';
2
-
3
- const colors = {
4
- green: '\x1b[32m',
5
- red: '\x1b[31m',
6
- teal: '\x1b[38;5;86m',
7
- orange: '\x1b[38;5;214m',
8
- reset: '\x1b[0m',
9
- };
10
-
11
- const formatByStatus = {
12
- Passed: {
13
- color: 'green',
14
- symbol: '\u2713',
15
- title: tests => `Passed - ${tests} tests`,
16
- },
17
- Failed: {
18
- color: 'red',
19
- symbol: '\u2716',
20
- title: tests => `Errors - ${tests} tests`,
21
- },
22
- Unresolved: {
23
- color: 'orange',
24
- symbol: '\u26A0',
25
- title: tests => `Diffs detected - ${tests} tests`,
26
- },
27
- };
28
-
29
- function errorDigest({passed, failed, diffs, logger, isInteractive}) {
30
- logger.log('errorDigest: diff errors', diffs);
31
- logger.log('errorDigest: test errors', failed);
32
-
33
- const testResultsUrl = diffs.length ? colorify(diffs[0].getUrl(), 'teal') : '';
34
- const testResultsPrefix = testResultsUrl ? 'See details at:' : '';
35
- const footer = testResultsUrl
36
- ? `\n${indent()}${colorify(testResultsPrefix)} ${testResultsUrl}`
37
- : '';
38
- return (
39
- colorify('Eyes-Cypress detected diffs or errors during execution of visual tests.') +
40
- colorify(` ${testResultsPrefix} ${testResultsUrl}`) +
41
- testResultsToString(passed, 'Passed') +
42
- testResultsToString(diffs, 'Unresolved') +
43
- testResultsToString(failed, 'Failed') +
44
- footer +
45
- '\n\n'
46
- );
47
-
48
- function testResultsToString(testResultsArr, category) {
49
- const {color, title, symbol, chalkFunction} = formatByStatus[category];
50
- const results = testResultsArr.reduce((acc, testResults) => {
51
- if (!testResults.isEmpty) {
52
- const error = hasError(testResults) ? stringifyError(testResults) : undefined;
53
- acc.push(
54
- `${colorify(symbol, color)} ${colorify(error || stringifyTestResults(testResults))}`,
55
- );
56
- }
57
- return acc;
58
- }, []);
59
-
60
- const coloredTitle = results.length
61
- ? colorify(title(results.length), color, chalkFunction)
62
- : '';
63
- return testResultsSection(coloredTitle, results);
64
- }
65
-
66
- function colorify(msg, color = 'reset') {
67
- return isInteractive ? msg : `${colors[color]}${msg}${colors.reset}`;
68
- }
69
- }
70
-
71
- function stringifyTestResults(testResults) {
72
- const hostDisplaySize = testResults.getHostDisplaySize();
73
- const viewport = hostDisplaySize ? `[${hostDisplaySize}]` : '';
74
- const testName = `${testResults.getName()} ${viewport}`;
75
- return testName + (testResults.error ? ` : ${testResults.error}` : '');
76
- }
77
-
78
- function testResultsSection(title, results) {
79
- return results.length ? `${indent()}${title}${indent(3)}${results.join(indent(3))}` : '';
80
- }
81
-
82
- function stringifyError(testResults) {
83
- return testResults.error
84
- ? stringifyTestResults(testResults)
85
- : `[Eyes test not started] : ${testResults}`;
86
- }
87
-
88
- function indent(spaces = 2) {
89
- return `\n ${' '.repeat(spaces)}`;
90
- }
91
-
92
- function hasError(testResult) {
93
- return testResult.error || testResult instanceof Error;
94
- }
95
-
96
- module.exports = errorDigest;
1
+ 'use strict';
2
+
3
+ const colors = {
4
+ green: '\x1b[32m',
5
+ red: '\x1b[31m',
6
+ teal: '\x1b[38;5;86m',
7
+ orange: '\x1b[38;5;214m',
8
+ reset: '\x1b[0m',
9
+ };
10
+
11
+ const formatByStatus = {
12
+ Passed: {
13
+ color: 'green',
14
+ symbol: '\u2713',
15
+ title: tests => `Passed - ${tests} tests`,
16
+ },
17
+ Failed: {
18
+ color: 'red',
19
+ symbol: '\u2716',
20
+ title: tests => `Errors - ${tests} tests`,
21
+ },
22
+ Unresolved: {
23
+ color: 'orange',
24
+ symbol: '\u26A0',
25
+ title: tests => `Diffs detected - ${tests} tests`,
26
+ },
27
+ };
28
+
29
+ function errorDigest({passed, failed, diffs, logger, isInteractive}) {
30
+ logger.log('errorDigest: diff errors', diffs);
31
+ logger.log('errorDigest: test errors', failed);
32
+
33
+ const testResultsUrl = diffs.length ? colorify(diffs[0].getUrl(), 'teal') : '';
34
+ const testResultsPrefix = testResultsUrl ? 'See details at:' : '';
35
+ const footer = testResultsUrl
36
+ ? `\n${indent()}${colorify(testResultsPrefix)} ${testResultsUrl}`
37
+ : '';
38
+ return (
39
+ colorify('Eyes-Cypress detected diffs or errors during execution of visual tests.') +
40
+ colorify(` ${testResultsPrefix} ${testResultsUrl}`) +
41
+ testResultsToString(passed, 'Passed') +
42
+ testResultsToString(diffs, 'Unresolved') +
43
+ testResultsToString(failed, 'Failed') +
44
+ footer +
45
+ '\n\n'
46
+ );
47
+
48
+ function testResultsToString(testResultsArr, category) {
49
+ const {color, title, symbol, chalkFunction} = formatByStatus[category];
50
+ const results = testResultsArr.reduce((acc, testResults) => {
51
+ if (!testResults.isEmpty) {
52
+ const error = hasError(testResults) ? stringifyError(testResults) : undefined;
53
+ acc.push(
54
+ `${colorify(symbol, color)} ${colorify(error || stringifyTestResults(testResults))}`,
55
+ );
56
+ }
57
+ return acc;
58
+ }, []);
59
+
60
+ const coloredTitle = results.length
61
+ ? colorify(title(results.length), color, chalkFunction)
62
+ : '';
63
+ return testResultsSection(coloredTitle, results);
64
+ }
65
+
66
+ function colorify(msg, color = 'reset') {
67
+ return isInteractive ? msg : `${colors[color]}${msg}${colors.reset}`;
68
+ }
69
+ }
70
+
71
+ function stringifyTestResults(testResults) {
72
+ const hostDisplaySize = testResults.getHostDisplaySize();
73
+ const viewport = hostDisplaySize ? `[${hostDisplaySize}]` : '';
74
+ const testName = `${testResults.getName()} ${viewport}`;
75
+ return testName + (testResults.error ? ` : ${testResults.error}` : '');
76
+ }
77
+
78
+ function testResultsSection(title, results) {
79
+ return results.length ? `${indent()}${title}${indent(3)}${results.join(indent(3))}` : '';
80
+ }
81
+
82
+ function stringifyError(testResults) {
83
+ return testResults.error
84
+ ? stringifyTestResults(testResults)
85
+ : `[Eyes test not started] : ${testResults}`;
86
+ }
87
+
88
+ function indent(spaces = 2) {
89
+ return `\n ${' '.repeat(spaces)}`;
90
+ }
91
+
92
+ function hasError(testResult) {
93
+ return testResult.error || testResult instanceof Error;
94
+ }
95
+
96
+ module.exports = errorDigest;
@@ -1,34 +1,34 @@
1
- 'use strict';
2
-
3
- function getErrorsAndDiffs(testResultsArr) {
4
- return testResultsArr.reduce(
5
- ({failed, diffs, passed}, testResults) => {
6
- if (testResults instanceof Error || testResults.error) {
7
- failed.push(testResults);
8
- } else {
9
- const testStatus = testResults.getStatus();
10
- if (testStatus === 'Passed') {
11
- passed.push(testResults);
12
- } else {
13
- if (testStatus === 'Unresolved') {
14
- if (testResults.getIsNew()) {
15
- testResults.error = new Error(
16
- `${testResults.getName()}. Please approve the new baseline at ${testResults.getUrl()}`,
17
- );
18
- failed.push(testResults);
19
- } else {
20
- diffs.push(testResults);
21
- }
22
- } else if (testStatus === 'Failed') {
23
- failed.push(testResults);
24
- }
25
- }
26
- }
27
-
28
- return {failed, diffs, passed};
29
- },
30
- {failed: [], diffs: [], passed: []},
31
- );
32
- }
33
-
34
- module.exports = getErrorsAndDiffs;
1
+ 'use strict';
2
+
3
+ function getErrorsAndDiffs(testResultsArr) {
4
+ return testResultsArr.reduce(
5
+ ({failed, diffs, passed}, testResults) => {
6
+ if (testResults instanceof Error || testResults.error) {
7
+ failed.push(testResults);
8
+ } else {
9
+ const testStatus = testResults.getStatus();
10
+ if (testStatus === 'Passed') {
11
+ passed.push(testResults);
12
+ } else {
13
+ if (testStatus === 'Unresolved') {
14
+ if (testResults.getIsNew()) {
15
+ testResults.error = new Error(
16
+ `${testResults.getName()}. Please approve the new baseline at ${testResults.getUrl()}`,
17
+ );
18
+ failed.push(testResults);
19
+ } else {
20
+ diffs.push(testResults);
21
+ }
22
+ } else if (testStatus === 'Failed') {
23
+ failed.push(testResults);
24
+ }
25
+ }
26
+ }
27
+
28
+ return {failed, diffs, passed};
29
+ },
30
+ {failed: [], diffs: [], passed: []},
31
+ );
32
+ }
33
+
34
+ module.exports = getErrorsAndDiffs;
@@ -0,0 +1,39 @@
1
+ const errorDigest = require('./errorDigest');
2
+ const {makeLogger} = require('@applitools/logger');
3
+ const getErrorsAndDiffs = require('./getErrorsAndDiffs');
4
+ const {promisify} = require('util');
5
+ const fs = require('fs');
6
+ const writeFile = promisify(fs.writeFile);
7
+ const {TestResultsFormatter} = require('@applitools/visual-grid-client');
8
+ const {resolve} = require('path');
9
+
10
+ function printTestResults(testResultsArr) {
11
+ const logger = makeLogger({
12
+ level: testResultsArr.resultConfig.showLogs ? 'info' : 'silent',
13
+ label: 'eyes',
14
+ });
15
+ const {passed, failed, diffs} = getErrorsAndDiffs(testResultsArr.testResults);
16
+ if ((failed.length || diffs.length) && !!testResultsArr.resultConfig.eyesFailCypressOnDiff) {
17
+ throw new Error(
18
+ errorDigest({
19
+ passed,
20
+ failed,
21
+ diffs,
22
+ logger,
23
+ isInteractive: !testResultsArr.resultConfig.isTextTerminal,
24
+ }),
25
+ );
26
+ }
27
+ }
28
+ function handleBatchResultsFile(results, tapFileConfig) {
29
+ try {
30
+ const formatter = new TestResultsFormatter(results);
31
+ const fileName = tapFileConfig.tapFileName || `${new Date().toISOString()}-eyes.tap`;
32
+ const tapFile = resolve(tapFileConfig.tapDirPath, fileName);
33
+ return writeFile(tapFile, formatter.asHierarchicTAPString(false, true));
34
+ } catch (ex) {
35
+ console.log(ex);
36
+ }
37
+ }
38
+
39
+ module.exports = {printTestResults, handleBatchResultsFile};
@@ -0,0 +1,58 @@
1
+ import {Server as HTTPServer, request} from 'http'
2
+ import {Server as WSServer} from 'ws'
3
+
4
+ const {name, version} = require('../../package.json') // TODO is the handshake needed at all?
5
+ const TOKEN_HEADER = 'x-eyes-universal-token'
6
+ const TOKEN = `${name}@${version}`
7
+
8
+ // TODO make default port 0 and return actual port
9
+ export async function makeHandler({port = 31077, singleton = true, lazy = false} = {}): Promise<{
10
+ server?: WSServer
11
+ port: number
12
+ }> {
13
+ const http = new HTTPServer()
14
+ http.on('request', (request, response) => {
15
+ if (request.url === '/handshake') {
16
+ if (request.headers[TOKEN_HEADER] === TOKEN) {
17
+ response.writeHead(200, {[TOKEN_HEADER]: TOKEN})
18
+ } else {
19
+ response.writeHead(400)
20
+ }
21
+ response.end()
22
+ }
23
+ })
24
+
25
+ http.listen(port, 'localhost')
26
+
27
+ return new Promise((resolve, reject) => {
28
+ http.on('listening', () => {
29
+ const ws = new WSServer({server: http, path: '/eyes'})
30
+ ws.on('close', () => http.close())
31
+ resolve({server: ws, port})
32
+ })
33
+
34
+ http.on('error', async (err: Error & {code: string}) => {
35
+ if (!lazy && err.code === 'EADDRINUSE') {
36
+ if (singleton && (await isHandshakable(port))) {
37
+ return resolve({port})
38
+ } else {
39
+ return resolve(await makeHandler({port: port + 1, singleton}))
40
+ }
41
+ }
42
+ reject(err)
43
+ })
44
+ })
45
+ }
46
+
47
+ async function isHandshakable(port: number) {
48
+ return new Promise(resolve => {
49
+ const handshake = request(`http://localhost:${port}/handshake`, {
50
+ headers: {[TOKEN_HEADER]: TOKEN},
51
+ })
52
+ handshake.on('response', ({statusCode, headers}) => {
53
+ resolve(statusCode === 200 && headers[TOKEN_HEADER] === TOKEN)
54
+ })
55
+ handshake.on('error', () => resolve(false))
56
+ handshake.end()
57
+ })
58
+ }
@@ -1,42 +1,49 @@
1
- 'use strict';
2
- const makeWaitForBatch = require('./waitForBatch');
3
- const makeHandleBatchResultsFile = require('./makeHandleBatchResultsFile');
4
- const getErrorsAndDiffs = require('./getErrorsAndDiffs');
5
- const processCloseAndAbort = require('./processCloseAndAbort');
6
- const errorDigest = require('./errorDigest');
7
- const runningTests = require('./runningTests');
8
-
9
- function makeGlobalRunHooks({visualGridClient, logger}) {
10
- let waitForBatch;
11
-
12
- return {
13
- 'before:run': ({config}) => {
14
- const {isTextTerminal, eyesTestConcurrency: testConcurrency} = config;
15
- if (!config.isTextTerminal) return;
16
-
17
- waitForBatch = makeWaitForBatch({
18
- logger: (logger.extend && logger.extend('waitForBatch')) || console,
19
- testConcurrency,
20
- processCloseAndAbort,
21
- getErrorsAndDiffs,
22
- errorDigest,
23
- isInteractive: !isTextTerminal,
24
- handleBatchResultsFile: makeHandleBatchResultsFile(config),
25
- });
26
- },
27
-
28
- 'after:run': async ({config}) => {
29
- if (!config.isTextTerminal) return;
30
-
31
- try {
32
- await waitForBatch(runningTests.tests, visualGridClient.closeBatch);
33
- } catch (e) {
34
- if (!!config.eyesFailCypressOnDiff) {
35
- throw e;
36
- }
37
- }
38
- },
39
- };
40
- }
41
-
42
- module.exports = makeGlobalRunHooks;
1
+ 'use strict';
2
+ const flatten = require('lodash.flatten');
3
+ const {TestResults} = require('@applitools/visual-grid-client');
4
+ const handleTestResults = require('./handleTestResults');
5
+
6
+ function makeGlobalRunHooks({closeAllEyes, closeBatches, closeUniversalServer}) {
7
+ return {
8
+ 'before:run': ({config}) => {
9
+ if (!config.isTextTerminal) return;
10
+ },
11
+
12
+ 'after:run': async ({config}) => {
13
+ try {
14
+ if (!config.isTextTerminal) return;
15
+ const resultConfig = {
16
+ showLogs: config.showLogs,
17
+ eyesFailCypressOnDiff: config.eyesFailCypressOnDiff,
18
+ isTextTerminal: config.isTextTerminal,
19
+ };
20
+ const testResults = await closeAllEyes();
21
+ const testResultsArr = [];
22
+ for (const result of flatten(testResults)) {
23
+ testResultsArr.push(new TestResults(result));
24
+ }
25
+ if (!config.appliConfFile.dontCloseBatches) {
26
+ await closeBatches({
27
+ batchIds: [config.appliConfFile.batch.id],
28
+ serverUrl: config.appliConfFile.serverUrl,
29
+ proxy: config.appliConfFile.proxy,
30
+ apiKey: config.appliConfFile.apiKey,
31
+ });
32
+ }
33
+
34
+ if (config.appliConfFile.tapDirPath) {
35
+ await handleTestResults.handleBatchResultsFile(testResultsArr, {
36
+ tapDirPath: config.appliConfFile.tapDirPath,
37
+ tapFileName: config.appliConfFile.tapFileName,
38
+ });
39
+ }
40
+
41
+ handleTestResults.printTestResults({testResults: testResultsArr, resultConfig});
42
+ } finally {
43
+ closeUniversalServer();
44
+ }
45
+ },
46
+ };
47
+ }
48
+
49
+ module.exports = makeGlobalRunHooks;
@@ -1,13 +1,13 @@
1
- const CYPRESS_SUPPORTED_VERSION = '6.2.0';
2
- const CYPRESS_NO_FLAG_VERSION = '6.7.0';
3
-
4
- function isGlobalHooksSupported(config) {
5
- const {version, experimentalRunEvents} = config;
6
-
7
- return (
8
- version >= CYPRESS_NO_FLAG_VERSION ||
9
- (version >= CYPRESS_SUPPORTED_VERSION && !!experimentalRunEvents)
10
- );
11
- }
12
-
13
- module.exports = isGlobalHooksSupported;
1
+ const CYPRESS_SUPPORTED_VERSION = '6.2.0';
2
+ const CYPRESS_NO_FLAG_VERSION = '6.7.0';
3
+
4
+ function isGlobalHooksSupported(config) {
5
+ const {version, experimentalRunEvents} = config;
6
+
7
+ return (
8
+ version >= CYPRESS_NO_FLAG_VERSION ||
9
+ (version >= CYPRESS_SUPPORTED_VERSION && !!experimentalRunEvents)
10
+ );
11
+ }
12
+
13
+ module.exports = isGlobalHooksSupported;
@@ -1,57 +1,60 @@
1
- 'use strict';
2
- const isGlobalHooksSupported = require('./isGlobalHooksSupported');
3
- const {presult} = require('@applitools/functional-commons');
4
-
5
- function makePluginExport({startServer, eyesConfig, globalHooks}) {
6
- return function pluginExport(pluginModule) {
7
- let closeEyesServer;
8
- const pluginModuleExports = pluginModule.exports;
9
- pluginModule.exports = async function(...args) {
10
- const {eyesPort, closeServer} = await startServer();
11
- closeEyesServer = closeServer;
12
- const [origOn, config] = args;
13
- const isGlobalHookCalledFromUserHandlerMap = new Map();
14
- eyesConfig.eyesIsGlobalHooksSupported = isGlobalHooksSupported(config);
15
- const moduleExportsResult = await pluginModuleExports(onThatCallsUserDefinedHandler, config);
16
-
17
- if (eyesConfig.eyesIsGlobalHooksSupported) {
18
- for (const [eventName, eventHandler] of Object.entries(globalHooks)) {
19
- if (!isGlobalHookCalledFromUserHandlerMap.get(eventName)) {
20
- origOn.call(this, eventName, eventHandler);
21
- }
22
- }
23
- }
24
-
25
- return Object.assign({}, eyesConfig, {eyesPort}, moduleExportsResult);
26
-
27
- // This piece of code exists because at the point of writing, Cypress does not support multiple event handlers:
28
- // https://github.com/cypress-io/cypress/issues/5240#issuecomment-948277554
29
- // So we wrap Cypress' `on` function in order to wrap the user-defined handler. This way we can call our own handler
30
- // in addition to the user's handler
31
- function onThatCallsUserDefinedHandler(eventName, handler) {
32
- const isRunEvent = eventName === 'before:run' || eventName === 'after:run';
33
- let handlerToCall = handler;
34
- if (eyesConfig.eyesIsGlobalHooksSupported && isRunEvent) {
35
- handlerToCall = handlerThatCallsUserDefinedHandler;
36
- isGlobalHookCalledFromUserHandlerMap.set(eventName, true);
37
- }
38
- return origOn.call(this, eventName, handlerToCall);
39
-
40
- async function handlerThatCallsUserDefinedHandler() {
41
- const [err] = await presult(
42
- Promise.resolve(globalHooks[eventName].apply(this, arguments)),
43
- );
44
- await handler.apply(this, arguments);
45
- if (err) {
46
- throw err;
47
- }
48
- }
49
- }
50
- };
51
- return function getCloseServer() {
52
- return closeEyesServer;
53
- };
54
- };
55
- }
56
-
57
- module.exports = makePluginExport;
1
+ 'use strict';
2
+ const isGlobalHooksSupported = require('./isGlobalHooksSupported');
3
+ const {presult} = require('@applitools/functional-commons');
4
+ const makeGlobalRunHooks = require('./hooks');
5
+
6
+ function makePluginExport({startServer, eyesConfig}) {
7
+ return function pluginExport(pluginModule) {
8
+ let eyesServer;
9
+ const pluginModuleExports = pluginModule.exports;
10
+ pluginModule.exports = async function(...args) {
11
+ const {server, port, closeAllEyes, closeBatches, closeUniversalServer} = await startServer();
12
+ eyesServer = server;
13
+
14
+ const globalHooks = makeGlobalRunHooks({closeAllEyes, closeBatches, closeUniversalServer});
15
+
16
+ const [origOn, config] = args;
17
+ const isGlobalHookCalledFromUserHandlerMap = new Map();
18
+ eyesConfig.eyesIsGlobalHooksSupported = isGlobalHooksSupported(config);
19
+ const moduleExportsResult = await pluginModuleExports(onThatCallsUserDefinedHandler, config);
20
+ if (eyesConfig.eyesIsGlobalHooksSupported) {
21
+ for (const [eventName, eventHandler] of Object.entries(globalHooks)) {
22
+ if (!isGlobalHookCalledFromUserHandlerMap.get(eventName)) {
23
+ origOn.call(this, eventName, eventHandler);
24
+ }
25
+ }
26
+ }
27
+
28
+ return Object.assign({}, eyesConfig, {eyesPort: port}, moduleExportsResult);
29
+
30
+ // This piece of code exists because at the point of writing, Cypress does not support multiple event handlers:
31
+ // https://github.com/cypress-io/cypress/issues/5240#issuecomment-948277554
32
+ // So we wrap Cypress' `on` function in order to wrap the user-defined handler. This way we can call our own handler
33
+ // in addition to the user's handler
34
+ function onThatCallsUserDefinedHandler(eventName, handler) {
35
+ const isRunEvent = eventName === 'before:run' || eventName === 'after:run';
36
+ let handlerToCall = handler;
37
+ if (eyesConfig.eyesIsGlobalHooksSupported && isRunEvent) {
38
+ handlerToCall = handlerThatCallsUserDefinedHandler;
39
+ isGlobalHookCalledFromUserHandlerMap.set(eventName, true);
40
+ }
41
+ return origOn.call(this, eventName, handlerToCall);
42
+
43
+ async function handlerThatCallsUserDefinedHandler() {
44
+ const [err] = await presult(
45
+ Promise.resolve(globalHooks[eventName].apply(this, arguments)),
46
+ );
47
+ await handler.apply(this, arguments);
48
+ if (err) {
49
+ throw err;
50
+ }
51
+ }
52
+ }
53
+ };
54
+ return function getCloseServer() {
55
+ return eyesServer.close();
56
+ };
57
+ };
58
+ }
59
+
60
+ module.exports = makePluginExport;