codeceptjs 3.7.0-beta.7 → 3.7.0-beta.9

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 (52) hide show
  1. package/lib/actor.js +1 -2
  2. package/lib/ai.js +130 -121
  3. package/lib/codecept.js +4 -4
  4. package/lib/command/check.js +4 -0
  5. package/lib/command/run-workers.js +1 -53
  6. package/lib/command/workers/runTests.js +25 -189
  7. package/lib/container.js +16 -0
  8. package/lib/els.js +87 -106
  9. package/lib/event.js +18 -17
  10. package/lib/helper/Playwright.js +7 -1
  11. package/lib/listener/exit.js +5 -8
  12. package/lib/listener/globalTimeout.js +26 -9
  13. package/lib/listener/result.js +12 -0
  14. package/lib/listener/steps.js +0 -6
  15. package/lib/listener/store.js +9 -1
  16. package/lib/mocha/asyncWrapper.js +12 -2
  17. package/lib/mocha/cli.js +65 -31
  18. package/lib/mocha/hooks.js +32 -3
  19. package/lib/mocha/suite.js +27 -1
  20. package/lib/mocha/test.js +91 -7
  21. package/lib/mocha/types.d.ts +5 -0
  22. package/lib/output.js +2 -1
  23. package/lib/plugin/analyze.js +348 -0
  24. package/lib/plugin/commentStep.js +5 -0
  25. package/lib/plugin/customReporter.js +52 -0
  26. package/lib/plugin/heal.js +2 -2
  27. package/lib/plugin/pageInfo.js +140 -0
  28. package/lib/plugin/retryTo.js +10 -2
  29. package/lib/plugin/screenshotOnFail.js +11 -16
  30. package/lib/plugin/stepByStepReport.js +5 -4
  31. package/lib/plugin/stepTimeout.js +1 -1
  32. package/lib/plugin/tryTo.js +9 -1
  33. package/lib/recorder.js +4 -4
  34. package/lib/rerun.js +43 -42
  35. package/lib/result.js +161 -0
  36. package/lib/step/base.js +52 -4
  37. package/lib/step/func.js +46 -0
  38. package/lib/step/helper.js +3 -0
  39. package/lib/step/meta.js +9 -1
  40. package/lib/step/record.js +5 -5
  41. package/lib/step/section.js +55 -0
  42. package/lib/step.js +6 -0
  43. package/lib/steps.js +28 -1
  44. package/lib/store.js +2 -0
  45. package/lib/{step/timeout.js → timeout.js} +24 -0
  46. package/lib/utils.js +35 -0
  47. package/lib/workers.js +28 -38
  48. package/package.json +7 -6
  49. package/typings/promiseBasedTypes.d.ts +104 -0
  50. package/typings/types.d.ts +104 -0
  51. package/lib/listener/artifacts.js +0 -19
  52. package/lib/plugin/debugErrors.js +0 -67
package/lib/actor.js CHANGED
@@ -3,8 +3,7 @@ const MetaStep = require('./step/meta')
3
3
  const recordStep = require('./step/record')
4
4
  const container = require('./container')
5
5
  const { methodsOfObject } = require('./utils')
6
- const { TIMEOUT_ORDER } = require('./step/timeout')
7
- const recorder = require('./recorder')
6
+ const { TIMEOUT_ORDER } = require('./timeout')
8
7
  const event = require('./event')
9
8
  const store = require('./store')
10
9
  const output = require('./output')
package/lib/ai.js CHANGED
@@ -1,40 +1,44 @@
1
- const debug = require('debug')('codeceptjs:ai');
2
- const output = require('./output');
3
- const event = require('./event');
4
- const { removeNonInteractiveElements, minifyHtml, splitByChunks } = require('./html');
1
+ const debug = require('debug')('codeceptjs:ai')
2
+ const output = require('./output')
3
+ const event = require('./event')
4
+ const { removeNonInteractiveElements, minifyHtml, splitByChunks } = require('./html')
5
5
 
6
6
  const defaultHtmlConfig = {
7
7
  maxLength: 50000,
8
8
  simplify: true,
9
9
  minify: true,
10
10
  html: {},
11
- };
11
+ }
12
12
 
13
13
  const defaultPrompts = {
14
- writeStep: (html, input) => [{
15
- role: 'user',
16
- content: `I am test engineer writing test in CodeceptJS
14
+ writeStep: (html, input) => [
15
+ {
16
+ role: 'user',
17
+ content: `I am test engineer writing test in CodeceptJS
17
18
  I have opened web page and I want to use CodeceptJS to ${input} on this page
18
19
  Provide me valid CodeceptJS code to accomplish it
19
20
  Use only locators from this HTML: \n\n${html}`,
20
- },
21
+ },
21
22
  ],
22
23
 
23
24
  healStep: (html, { step, error, prevSteps }) => {
24
- return [{
25
- role: 'user',
26
- content: `As a test automation engineer I am testing web application using CodeceptJS.
25
+ return [
26
+ {
27
+ role: 'user',
28
+ content: `As a test automation engineer I am testing web application using CodeceptJS.
27
29
  I want to heal a test that fails. Here is the list of executed steps: ${prevSteps.map(s => s.toString()).join(', ')}
28
30
  Propose how to adjust ${step.toCode()} step to fix the test.
29
31
  Use locators in order of preference: semantic locator by text, CSS, XPath. Use codeblocks marked with \`\`\`
30
32
  Here is the error message: ${error.message}
31
33
  Here is HTML code of a page where the failure has happened: \n\n${html}`,
32
- }];
34
+ },
35
+ ]
33
36
  },
34
37
 
35
- generatePageObject: (html, extraPrompt = '', rootLocator = null) => [{
36
- role: 'user',
37
- content: `As a test automation engineer I am creating a Page Object for a web application using CodeceptJS.
38
+ generatePageObject: (html, extraPrompt = '', rootLocator = null) => [
39
+ {
40
+ role: 'user',
41
+ content: `As a test automation engineer I am creating a Page Object for a web application using CodeceptJS.
38
42
  Here is an sample page object:
39
43
 
40
44
  const { I } = inject();
@@ -60,72 +64,73 @@ module.exports = {
60
64
  ${extraPrompt}
61
65
  ${rootLocator ? `All provided elements are inside '${rootLocator}'. Declare it as root variable and for every locator use locate(...).inside(root)` : ''}
62
66
  Add only locators from this HTML: \n\n${html}`,
63
- }],
64
- };
67
+ },
68
+ ],
69
+ }
65
70
 
66
71
  class AiAssistant {
67
72
  constructor() {
68
- this.totalTime = 0;
69
- this.numTokens = 0;
73
+ this.totalTime = 0
74
+ this.numTokens = 0
70
75
 
71
- this.reset();
72
- this.connectToEvents();
76
+ this.reset()
77
+ this.connectToEvents()
73
78
  }
74
79
 
75
80
  enable(config = {}) {
76
- debug('Enabling AI assistant');
77
- this.isEnabled = true;
81
+ debug('Enabling AI assistant')
82
+ this.isEnabled = true
78
83
 
79
- const { html, prompts, ...aiConfig } = config;
84
+ const { html, prompts, ...aiConfig } = config
80
85
 
81
- this.config = Object.assign(this.config, aiConfig);
82
- this.htmlConfig = Object.assign(defaultHtmlConfig, html);
83
- this.prompts = Object.assign(defaultPrompts, prompts);
86
+ this.config = Object.assign(this.config, aiConfig)
87
+ this.htmlConfig = Object.assign(defaultHtmlConfig, html)
88
+ this.prompts = Object.assign(defaultPrompts, prompts)
84
89
 
85
- debug('Config', this.config);
90
+ debug('Config', this.config)
86
91
  }
87
92
 
88
93
  reset() {
89
- this.numTokens = 0;
90
- this.isEnabled = false;
94
+ this.numTokens = 0
95
+ this.isEnabled = false
91
96
  this.config = {
92
97
  maxTokens: 1000000,
93
98
  request: null,
94
99
  response: parseCodeBlocks,
95
100
  // lets limit token usage to 1M
96
- };
97
- this.minifiedHtml = null;
98
- this.response = null;
99
- this.totalTime = 0;
101
+ }
102
+ this.minifiedHtml = null
103
+ this.response = null
104
+ this.totalTime = 0
100
105
  }
101
106
 
102
107
  disable() {
103
- this.isEnabled = false;
108
+ this.isEnabled = false
104
109
  }
105
110
 
106
111
  connectToEvents() {
107
112
  event.dispatcher.on(event.all.result, () => {
108
113
  if (this.isEnabled && this.numTokens > 0) {
109
- const numTokensK = Math.ceil(this.numTokens / 1000);
110
- const maxTokensK = Math.ceil(this.config.maxTokens / 1000);
111
- output.print(`AI assistant took ${this.totalTime}s and used ~${numTokensK}K input tokens. Tokens limit: ${maxTokensK}K`);
114
+ const numTokensK = Math.ceil(this.numTokens / 1000)
115
+ const maxTokensK = Math.ceil(this.config.maxTokens / 1000)
116
+ output.print(`AI assistant took ${this.totalTime}s and used ~${numTokensK}K input tokens. Tokens limit: ${maxTokensK}K`)
112
117
  }
113
- });
118
+ })
114
119
  }
115
120
 
116
121
  checkRequestFn() {
117
122
  if (!this.isEnabled) {
118
- debug('AI assistant is disabled');
119
- return;
123
+ debug('AI assistant is disabled')
124
+ return
120
125
  }
121
126
 
122
- if (this.config.request) return;
127
+ if (this.config.request) return
123
128
 
124
129
  const noRequestErrorMessage = `
125
- No request function is set for AI assistant.
126
- Please implement your own request function and set it in the config.
130
+ No request function is set for AI assistant.
127
131
 
128
- [!] AI request was decoupled from CodeceptJS. To connect to OpenAI or other AI service, please implement your own request function and set it in the config.
132
+ [!] AI request was decoupled from CodeceptJS. To connect to OpenAI or other AI service.
133
+ Please implement your own request function and set it in the config.
129
134
 
130
135
  Example (connect to OpenAI):
131
136
 
@@ -134,82 +139,80 @@ class AiAssistant {
134
139
  const OpenAI = require('openai');
135
140
  const openai = new OpenAI({ apiKey: process.env['OPENAI_API_KEY'] })
136
141
  const response = await openai.chat.completions.create({
137
- model: 'gpt-3.5-turbo-0125',
142
+ model: 'gpt-4o-mini',
138
143
  messages,
139
144
  });
140
145
  return response?.data?.choices[0]?.message?.content;
141
146
  }
142
147
  }
143
- `.trim();
148
+ `.trim()
144
149
 
145
- throw new Error(noRequestErrorMessage);
150
+ throw new Error(noRequestErrorMessage)
146
151
  }
147
152
 
148
153
  async setHtmlContext(html) {
149
- let processedHTML = html;
154
+ let processedHTML = html
150
155
 
151
156
  if (this.htmlConfig.simplify) {
152
- processedHTML = removeNonInteractiveElements(processedHTML, this.htmlConfig);
157
+ processedHTML = removeNonInteractiveElements(processedHTML, this.htmlConfig)
153
158
  }
154
159
 
155
- if (this.htmlConfig.minify) processedHTML = await minifyHtml(processedHTML);
156
- if (this.htmlConfig.maxLength) processedHTML = splitByChunks(processedHTML, this.htmlConfig.maxLength)[0];
160
+ if (this.htmlConfig.minify) processedHTML = await minifyHtml(processedHTML)
161
+ if (this.htmlConfig.maxLength) processedHTML = splitByChunks(processedHTML, this.htmlConfig.maxLength)[0]
157
162
 
158
- this.minifiedHtml = processedHTML;
163
+ this.minifiedHtml = processedHTML
159
164
  }
160
165
 
161
166
  getResponse() {
162
- return this.response || '';
167
+ return this.response || ''
163
168
  }
164
169
 
165
170
  async createCompletion(messages) {
166
- if (!this.isEnabled) return '';
167
-
168
- debug('Request', messages);
169
-
170
- this.checkRequestFn();
171
-
172
- this.response = null;
173
-
174
- this.calculateTokens(messages);
171
+ if (!this.isEnabled) return ''
175
172
 
176
173
  try {
177
- const startTime = process.hrtime();
178
- this.response = await this.config.request(messages);
179
- const endTime = process.hrtime(startTime);
180
- const executionTimeInSeconds = endTime[0] + endTime[1] / 1e9;
181
-
182
- this.totalTime += Math.round(executionTimeInSeconds);
183
- debug('AI response time', executionTimeInSeconds);
184
- debug('Response', this.response);
185
- this.stopWhenReachingTokensLimit();
186
- return this.response;
174
+ this.checkRequestFn()
175
+ debug('Request', messages)
176
+
177
+ this.response = null
178
+
179
+ this.calculateTokens(messages)
180
+ const startTime = process.hrtime()
181
+ this.response = await this.config.request(messages)
182
+ const endTime = process.hrtime(startTime)
183
+ const executionTimeInSeconds = endTime[0] + endTime[1] / 1e9
184
+
185
+ this.totalTime += Math.round(executionTimeInSeconds)
186
+ debug('AI response time', executionTimeInSeconds)
187
+ debug('Response', this.response)
188
+ this.stopWhenReachingTokensLimit()
189
+ return this.response
187
190
  } catch (err) {
188
- debug(err.response);
189
- output.print('');
190
- output.error(`AI service error: ${err.message}`);
191
- if (err?.response?.data?.error?.code) output.error(err?.response?.data?.error?.code);
192
- if (err?.response?.data?.error?.message) output.error(err?.response?.data?.error?.message);
193
- this.stopWhenReachingTokensLimit();
194
- return '';
191
+ debug(err.response)
192
+ output.print('')
193
+ output.error(`AI service error: ${err.message}`)
194
+ if (err?.response?.data?.error?.code) output.error(err?.response?.data?.error?.code)
195
+ if (err?.response?.data?.error?.message) output.error(err?.response?.data?.error?.message)
196
+ this.stopWhenReachingTokensLimit()
197
+ return ''
195
198
  }
196
199
  }
197
200
 
198
201
  async healFailedStep(failureContext) {
199
- if (!this.isEnabled) return [];
200
- if (!failureContext.html) throw new Error('No HTML context provided');
202
+ if (!this.isEnabled) return []
203
+ if (!failureContext.html) throw new Error('No HTML context provided')
201
204
 
202
- await this.setHtmlContext(failureContext.html);
205
+ await this.setHtmlContext(failureContext.html)
203
206
 
204
207
  if (!this.minifiedHtml) {
205
- debug('HTML context is empty after removing non-interactive elements & minification');
206
- return [];
208
+ debug('HTML context is empty after removing non-interactive elements & minification')
209
+ return []
207
210
  }
208
211
 
209
- const response = await this.createCompletion(this.prompts.healStep(this.minifiedHtml, failureContext));
210
- if (!response) return [];
212
+ const response = await this.createCompletion(this.prompts.healStep(this.minifiedHtml, failureContext))
213
+ if (!response) return []
211
214
 
212
- return this.config.response(response);
215
+ return this.config.response(response)
213
216
  }
214
217
 
215
218
  /**
@@ -219,13 +222,13 @@ class AiAssistant {
219
222
  * @returns
220
223
  */
221
224
  async generatePageObject(extraPrompt = null, locator = null) {
222
- if (!this.isEnabled) return [];
223
- if (!this.minifiedHtml) throw new Error('No HTML context provided');
225
+ if (!this.isEnabled) return []
226
+ if (!this.minifiedHtml) throw new Error('No HTML context provided')
224
227
 
225
- const response = await this.createCompletion(this.prompts.generatePageObject(this.minifiedHtml, locator, extraPrompt));
226
- if (!response) return [];
228
+ const response = await this.createCompletion(this.prompts.generatePageObject(this.minifiedHtml, locator, extraPrompt))
229
+ if (!response) return []
227
230
 
228
- return this.config.response(response);
231
+ return this.config.response(response)
229
232
  }
230
233
 
231
234
  calculateTokens(messages) {
@@ -233,66 +236,72 @@ class AiAssistant {
233
236
  // this approach was tested via https://platform.openai.com/tokenizer
234
237
  // we need it to display current tokens usage so users could analyze effectiveness of AI
235
238
 
236
- const inputString = messages.map(m => m.content).join(' ').trim();
237
- const numWords = (inputString.match(/[^\s\-:=]+/g) || []).length;
239
+ const inputString = messages
240
+ .map(m => m.content)
241
+ .join(' ')
242
+ .trim()
243
+ const numWords = (inputString.match(/[^\s\-:=]+/g) || []).length
238
244
 
239
245
  // 2.5 token is constant for average HTML input
240
- const tokens = numWords * 2.5;
246
+ const tokens = numWords * 2.5
241
247
 
242
- this.numTokens += tokens;
248
+ this.numTokens += tokens
243
249
 
244
- return tokens;
250
+ return tokens
245
251
  }
246
252
 
247
253
  stopWhenReachingTokensLimit() {
248
- if (this.numTokens < this.config.maxTokens) return;
254
+ if (this.numTokens < this.config.maxTokens) return
249
255
 
250
- output.print(`AI assistant has reached the limit of ${this.config.maxTokens} tokens in this session. It will be disabled now`);
251
- this.disable();
256
+ output.print(`AI assistant has reached the limit of ${this.config.maxTokens} tokens in this session. It will be disabled now`)
257
+ this.disable()
252
258
  }
253
259
 
254
260
  async writeSteps(input) {
255
- if (!this.isEnabled) return;
256
- if (!this.minifiedHtml) throw new Error('No HTML context provided');
261
+ if (!this.isEnabled) return
262
+ if (!this.minifiedHtml) throw new Error('No HTML context provided')
257
263
 
258
- const snippets = [];
264
+ const snippets = []
259
265
 
260
- const response = await this.createCompletion(this.prompts.writeStep(this.minifiedHtml, input));
261
- if (!response) return;
262
- snippets.push(...this.config.response(response));
266
+ const response = await this.createCompletion(this.prompts.writeStep(this.minifiedHtml, input))
267
+ if (!response) return
268
+ snippets.push(...this.config.response(response))
263
269
 
264
- debug(snippets[0]);
270
+ debug(snippets[0])
265
271
 
266
- return snippets[0];
272
+ return snippets[0]
267
273
  }
268
274
  }
269
275
 
270
276
  function parseCodeBlocks(response) {
271
277
  // Regular expression pattern to match code snippets
272
- const codeSnippetPattern = /```(?:javascript|js|typescript|ts)?\n([\s\S]+?)\n```/g;
278
+ const codeSnippetPattern = /```(?:javascript|js|typescript|ts)?\n([\s\S]+?)\n```/g
273
279
 
274
280
  // Array to store extracted code snippets
275
- const codeSnippets = [];
281
+ const codeSnippets = []
276
282
 
277
- response = response.split('\n').map(line => line.trim()).join('\n');
283
+ response = response
284
+ .split('\n')
285
+ .map(line => line.trim())
286
+ .join('\n')
278
287
 
279
288
  // Iterate over matches and extract code snippets
280
- let match;
289
+ let match
281
290
  while ((match = codeSnippetPattern.exec(response)) !== null) {
282
- codeSnippets.push(match[1]);
291
+ codeSnippets.push(match[1])
283
292
  }
284
293
 
285
294
  // Remove "Scenario", "Feature", and "require()" lines
286
295
  const modifiedSnippets = codeSnippets.map(snippet => {
287
- const lines = snippet.split('\n');
296
+ const lines = snippet.split('\n')
288
297
 
289
- const filteredLines = lines.filter(line => !line.includes('I.amOnPage') && !line.startsWith('Scenario') && !line.startsWith('Feature') && !line.includes('= require('));
298
+ const filteredLines = lines.filter(line => !line.includes('I.amOnPage') && !line.startsWith('Scenario') && !line.startsWith('Feature') && !line.includes('= require('))
290
299
 
291
- return filteredLines.join('\n');
300
+ return filteredLines.join('\n')
292
301
  // remove snippets that move from current url
293
- }); // .filter(snippet => !line.includes('I.amOnPage'));
302
+ }) // .filter(snippet => !line.includes('I.amOnPage'));
294
303
 
295
- return modifiedSnippets.filter(snippet => !!snippet);
304
+ return modifiedSnippets.filter(snippet => !!snippet)
296
305
  }
297
306
 
298
- module.exports = new AiAssistant();
307
+ module.exports = new AiAssistant()
package/lib/codecept.js CHANGED
@@ -105,8 +105,8 @@ class Codecept {
105
105
  // default hooks
106
106
  runHook(require('./listener/store'))
107
107
  runHook(require('./listener/steps'))
108
- runHook(require('./listener/artifacts'))
109
108
  runHook(require('./listener/config'))
109
+ runHook(require('./listener/result'))
110
110
  runHook(require('./listener/helpers'))
111
111
  runHook(require('./listener/globalTimeout'))
112
112
  runHook(require('./listener/globalRetry'))
@@ -199,13 +199,13 @@ class Codecept {
199
199
  mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
200
200
  }
201
201
  const done = () => {
202
- event.emit(event.all.result, this)
203
- event.emit(event.all.after, this)
202
+ event.emit(event.all.result, container.result())
203
+ event.emit(event.all.after)
204
204
  resolve()
205
205
  }
206
206
 
207
207
  try {
208
- event.emit(event.all.before, this)
208
+ event.emit(event.all.before)
209
209
  mocha.run(() => done())
210
210
  } catch (e) {
211
211
  output.error(e.stack)
@@ -22,6 +22,7 @@ module.exports = async function (options) {
22
22
  config: false,
23
23
  container: false,
24
24
  pageObjects: false,
25
+ plugins: false,
25
26
  helpers: false,
26
27
  setup: false,
27
28
  tests: false,
@@ -115,6 +116,9 @@ module.exports = async function (options) {
115
116
  }
116
117
  printCheck('page objects', checks['pageObjects'], `Total: ${Object.keys(pageObjects).length} support objects`)
117
118
 
119
+ checks.plugins = true // how to check plugins?
120
+ printCheck('plugins', checks['plugins'], Object.keys(container.plugins()).join(', '))
121
+
118
122
  if (Object.keys(helpers).length) {
119
123
  const suite = container.mocha().suite
120
124
  const test = createTest('test', () => {})
@@ -8,12 +8,6 @@ const Workers = require('../workers')
8
8
  module.exports = async function (workerCount, selectedRuns, options) {
9
9
  process.env.profile = options.profile
10
10
 
11
- const suiteArr = []
12
- const passedTestArr = []
13
- const failedTestArr = []
14
- const skippedTestArr = []
15
- const stepArr = []
16
-
17
11
  const { config: testConfig, override = '' } = options
18
12
  const overrideConfigs = tryOrDefault(() => JSON.parse(override), {})
19
13
  const by = options.suites ? 'suite' : 'test'
@@ -35,65 +29,19 @@ module.exports = async function (workerCount, selectedRuns, options) {
35
29
  const workers = new Workers(numberOfWorkers, config)
36
30
  workers.overrideConfig(overrideConfigs)
37
31
 
38
- workers.on(event.suite.before, suite => {
39
- suiteArr.push(suite)
40
- })
41
-
42
- workers.on(event.step.passed, step => {
43
- stepArr.push(step)
44
- })
45
-
46
- workers.on(event.step.failed, step => {
47
- stepArr.push(step)
48
- })
49
-
50
32
  workers.on(event.test.failed, test => {
51
- failedTestArr.push(test)
52
33
  output.test.failed(test)
53
34
  })
54
35
 
55
36
  workers.on(event.test.passed, test => {
56
- passedTestArr.push(test)
57
37
  output.test.passed(test)
58
38
  })
59
39
 
60
40
  workers.on(event.test.skipped, test => {
61
- skippedTestArr.push(test)
62
41
  output.test.skipped(test)
63
42
  })
64
43
 
65
- workers.on(event.all.result, () => {
66
- // expose test stats after all workers finished their execution
67
- function addStepsToTest(test, stepArr) {
68
- stepArr.test.steps.forEach(step => {
69
- if (test.steps.length === 0) {
70
- test.steps.push(step)
71
- }
72
- })
73
- }
74
-
75
- stepArr.forEach(step => {
76
- passedTestArr.forEach(test => {
77
- if (step.test.title === test.title) {
78
- addStepsToTest(test, step)
79
- }
80
- })
81
-
82
- failedTestArr.forEach(test => {
83
- if (step.test.title === test.title) {
84
- addStepsToTest(test, step)
85
- }
86
- })
87
- })
88
-
89
- event.dispatcher.emit(event.workers.result, {
90
- suites: suiteArr,
91
- tests: {
92
- passed: passedTestArr,
93
- failed: failedTestArr,
94
- skipped: skippedTestArr,
95
- },
96
- })
44
+ workers.on(event.all.result, result => {
97
45
  workers.printResults()
98
46
  })
99
47