@elastic/synthetics 1.13.0 → 1.15.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.
Files changed (94) hide show
  1. package/dist/cli.js +4 -3
  2. package/dist/cli.js.map +1 -1
  3. package/dist/common_types.d.ts +9 -16
  4. package/dist/common_types.d.ts.map +1 -1
  5. package/dist/core/gatherer.d.ts +3 -1
  6. package/dist/core/gatherer.d.ts.map +1 -1
  7. package/dist/core/gatherer.js +42 -35
  8. package/dist/core/gatherer.js.map +1 -1
  9. package/dist/core/globals.d.ts +27 -0
  10. package/dist/core/globals.d.ts.map +1 -0
  11. package/dist/core/globals.js +41 -0
  12. package/dist/core/globals.js.map +1 -0
  13. package/dist/core/index.d.ts +0 -2
  14. package/dist/core/index.d.ts.map +1 -1
  15. package/dist/core/index.js +13 -25
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/core/mfa.d.ts +61 -0
  18. package/dist/core/mfa.d.ts.map +1 -0
  19. package/dist/core/mfa.js +33 -0
  20. package/dist/core/mfa.js.map +1 -0
  21. package/dist/core/runner.d.ts +30 -39
  22. package/dist/core/runner.d.ts.map +1 -1
  23. package/dist/core/runner.js +174 -179
  24. package/dist/core/runner.js.map +1 -1
  25. package/dist/dsl/journey.d.ts +24 -16
  26. package/dist/dsl/journey.d.ts.map +1 -1
  27. package/dist/dsl/journey.js +28 -15
  28. package/dist/dsl/journey.js.map +1 -1
  29. package/dist/dsl/monitor.d.ts +1 -0
  30. package/dist/dsl/monitor.d.ts.map +1 -1
  31. package/dist/dsl/monitor.js.map +1 -1
  32. package/dist/dsl/step.d.ts +12 -6
  33. package/dist/dsl/step.d.ts.map +1 -1
  34. package/dist/dsl/step.js +11 -3
  35. package/dist/dsl/step.js.map +1 -1
  36. package/dist/helpers.d.ts +1 -0
  37. package/dist/helpers.d.ts.map +1 -1
  38. package/dist/helpers.js +1 -0
  39. package/dist/helpers.js.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +35 -11
  43. package/dist/index.js.map +1 -1
  44. package/dist/loader.d.ts.map +1 -1
  45. package/dist/loader.js +26 -1
  46. package/dist/loader.js.map +1 -1
  47. package/dist/options.d.ts +1 -0
  48. package/dist/options.d.ts.map +1 -1
  49. package/dist/options.js +18 -0
  50. package/dist/options.js.map +1 -1
  51. package/dist/push/index.d.ts.map +1 -1
  52. package/dist/push/index.js +1 -1
  53. package/dist/push/index.js.map +1 -1
  54. package/dist/push/monitor.d.ts +1 -0
  55. package/dist/push/monitor.d.ts.map +1 -1
  56. package/dist/push/monitor.js +18 -1
  57. package/dist/push/monitor.js.map +1 -1
  58. package/dist/reporters/base.d.ts +3 -3
  59. package/dist/reporters/base.d.ts.map +1 -1
  60. package/dist/reporters/base.js +6 -6
  61. package/dist/reporters/base.js.map +1 -1
  62. package/dist/reporters/build_kite_cli.d.ts +2 -4
  63. package/dist/reporters/build_kite_cli.d.ts.map +1 -1
  64. package/dist/reporters/build_kite_cli.js +8 -10
  65. package/dist/reporters/build_kite_cli.js.map +1 -1
  66. package/dist/reporters/json.d.ts +4 -9
  67. package/dist/reporters/json.d.ts.map +1 -1
  68. package/dist/reporters/json.js +31 -40
  69. package/dist/reporters/json.js.map +1 -1
  70. package/dist/reporters/junit.d.ts +2 -2
  71. package/dist/reporters/junit.d.ts.map +1 -1
  72. package/dist/reporters/junit.js +7 -7
  73. package/dist/reporters/junit.js.map +1 -1
  74. package/package.json +5 -4
  75. package/src/cli.ts +4 -2
  76. package/src/common_types.ts +9 -16
  77. package/src/core/gatherer.ts +44 -37
  78. package/src/core/globals.ts +37 -0
  79. package/src/core/index.ts +12 -25
  80. package/src/core/mfa.ts +65 -0
  81. package/src/core/runner.ts +201 -202
  82. package/src/dsl/journey.ts +42 -23
  83. package/src/dsl/monitor.ts +1 -0
  84. package/src/dsl/step.ts +16 -7
  85. package/src/helpers.ts +4 -3
  86. package/src/index.ts +3 -2
  87. package/src/loader.ts +3 -0
  88. package/src/options.ts +22 -0
  89. package/src/push/index.ts +3 -4
  90. package/src/push/monitor.ts +20 -0
  91. package/src/reporters/base.ts +8 -12
  92. package/src/reporters/build_kite_cli.ts +13 -18
  93. package/src/reporters/json.ts +35 -52
  94. package/src/reporters/junit.ts +8 -10
@@ -37,7 +37,6 @@ import {
37
37
  } from '../helpers';
38
38
  import {
39
39
  HooksCallback,
40
- Params,
41
40
  HooksArgs,
42
41
  Driver,
43
42
  Screenshot,
@@ -47,7 +46,6 @@ import {
47
46
  PushOptions,
48
47
  } from '../common_types';
49
48
  import {
50
- PluginManager,
51
49
  PerformanceManager,
52
50
  filterBrowserMessages,
53
51
  } from '../plugins';
@@ -58,50 +56,49 @@ import { Monitor, MonitorConfig } from '../dsl/monitor';
58
56
  type HookType = 'beforeAll' | 'afterAll';
59
57
  export type SuiteHooks = Record<HookType, Array<HooksCallback>>;
60
58
 
61
- type JourneyContext = {
62
- params?: Params;
63
- browserDelay: number;
64
- start: number;
65
- driver: Driver;
66
- pluginManager: PluginManager;
67
- };
68
-
69
59
  type RunResult = Record<string, JourneyResult>;
70
60
 
71
- export default class Runner {
61
+ export interface RunnerInfo {
62
+ /**
63
+ * Processed configuration from the CLI args and the config file
64
+ */
65
+ readonly config: RunOptions;
66
+ /**
67
+ * Currently active journey
68
+ */
69
+ readonly journey: Journey | undefined;
70
+ /**
71
+ * All registerd journeys
72
+ */
73
+ readonly journeys: Journey[];
74
+ }
75
+
76
+ export default class Runner implements RunnerInfo {
72
77
  #active = false;
73
78
  #reporter: Reporter;
74
79
  #currentJourney?: Journey = null;
75
- journeys: Journey[] = [];
76
- hooks: SuiteHooks = { beforeAll: [], afterAll: [] };
77
- hookError: Error | undefined;
78
- monitor?: Monitor;
79
- static screenshotPath = join(CACHE_PATH, 'screenshots');
80
+ #journeys: Journey[] = [];
81
+ #hooks: SuiteHooks = { beforeAll: [], afterAll: [] };
82
+ #screenshotPath = join(CACHE_PATH, 'screenshots');
83
+ #driver?: Driver;
84
+ #browserDelay = -1;
85
+ #hookError: Error | undefined;
86
+ #monitor?: Monitor;
87
+ config: RunOptions;
80
88
 
81
- static async createContext(options: RunOptions): Promise<JourneyContext> {
82
- const browserStart = monotonicTimeInSeconds();
83
- const driver = await Gatherer.setupDriver(options);
84
- /**
85
- * Do not include browser launch/context creation duration
86
- * as part of journey duration
87
- */
88
- const start = monotonicTimeInSeconds();
89
- const pluginManager = await Gatherer.beginRecording(driver, options);
90
- /**
91
- * For each journey we create the screenshots folder for
92
- * caching all screenshots and clear them at end of each journey
93
- */
94
- await mkdir(this.screenshotPath, { recursive: true });
95
- return {
96
- start,
97
- browserDelay: start - browserStart,
98
- params: options.params,
99
- driver,
100
- pluginManager,
101
- };
89
+ get journey() {
90
+ return this.#currentJourney;
91
+ }
92
+
93
+ get journeys() {
94
+ return this.#journeys;
95
+ }
96
+
97
+ get hooks() {
98
+ return this.#hooks;
102
99
  }
103
100
 
104
- async captureScreenshot(page: Driver['page'], step: Step) {
101
+ private async captureScreenshot(page: Driver['page'], step: Step) {
105
102
  try {
106
103
  const buffer = await page.screenshot({
107
104
  type: 'jpeg',
@@ -120,7 +117,7 @@ export default class Runner {
120
117
  data: buffer.toString('base64'),
121
118
  };
122
119
  await writeFile(
123
- join(Runner.screenshotPath, fileName),
120
+ join(this.#screenshotPath, fileName),
124
121
  JSON.stringify(screenshot)
125
122
  );
126
123
  log(`Runner: captured screenshot for (${step.name})`);
@@ -130,28 +127,28 @@ export default class Runner {
130
127
  }
131
128
  }
132
129
 
133
- get currentJourney() {
134
- return this.#currentJourney;
130
+ _addHook(type: HookType, callback: HooksCallback) {
131
+ this.#hooks[type].push(callback);
135
132
  }
136
133
 
137
- addHook(type: HookType, callback: HooksCallback) {
138
- this.hooks[type].push(callback);
134
+ private buildHookArgs() {
135
+ return { env: this.config.environment, params: this.config.params, info: this as RunnerInfo };
139
136
  }
140
137
 
141
- updateMonitor(config: MonitorConfig) {
142
- if (!this.monitor) {
143
- this.monitor = new Monitor(config);
138
+ _updateMonitor(config: MonitorConfig) {
139
+ if (!this.#monitor) {
140
+ this.#monitor = new Monitor(config);
144
141
  return;
145
142
  }
146
- this.monitor.update(config);
143
+ this.#monitor.update(config);
147
144
  }
148
145
 
149
- addJourney(journey: Journey) {
150
- this.journeys.push(journey);
146
+ _addJourney(journey: Journey) {
147
+ this.#journeys.push(journey);
151
148
  this.#currentJourney = journey;
152
149
  }
153
150
 
154
- setReporter(options: RunOptions) {
151
+ private setReporter(options: RunOptions) {
155
152
  /**
156
153
  * Set up the corresponding reporter and fallback
157
154
  * to default reporter if not provided
@@ -164,75 +161,72 @@ export default class Runner {
164
161
  this.#reporter = new Reporter({ fd: outfd, dryRun });
165
162
  }
166
163
 
167
- async runBeforeAllHook(args: HooksArgs) {
164
+ async #runBeforeAllHook(args: HooksArgs) {
168
165
  log(`Runner: beforeAll hooks`);
169
- await runParallel(this.hooks.beforeAll, args);
166
+ await runParallel(this.#hooks.beforeAll, args);
170
167
  }
171
168
 
172
- async runAfterAllHook(args: HooksArgs) {
169
+ async #runAfterAllHook(args: HooksArgs) {
173
170
  log(`Runner: afterAll hooks`);
174
- await runParallel(this.hooks.afterAll, args);
171
+ await runParallel(this.#hooks.afterAll, args);
175
172
  }
176
173
 
177
- async runBeforeHook(journey: Journey, args: HooksArgs) {
174
+ async #runBeforeHook(journey: Journey, args: HooksArgs) {
178
175
  log(`Runner: before hooks for (${journey.name})`);
179
- await runParallel(journey.hooks.before, args);
176
+ await runParallel(journey._getHook('before'), args);
180
177
  }
181
178
 
182
- async runAfterHook(journey: Journey, args: HooksArgs) {
179
+ async #runAfterHook(journey: Journey, args: HooksArgs) {
183
180
  log(`Runner: after hooks for (${journey.name})`);
184
- await runParallel(journey.hooks.after, args);
181
+ await runParallel(journey._getHook('after'), args);
185
182
  }
186
183
 
187
- async runStep(
184
+ async #runStep(
188
185
  step: Step,
189
- context: JourneyContext,
190
186
  options: RunOptions
191
187
  ): Promise<StepResult> {
192
- const data: StepResult = {
193
- status: 'succeeded',
194
- };
195
188
  log(`Runner: start step (${step.name})`);
196
189
  const { metrics, screenshots, filmstrips, trace } = options;
197
- const { driver, pluginManager } = context;
198
190
  /**
199
191
  * URL needs to be the first navigation request of any step
200
192
  * Listening for request solves the case where `about:blank` would be
201
193
  * reported for failed navigations
202
194
  */
203
195
  const captureUrl = req => {
204
- if (!data.url && req.isNavigationRequest()) {
205
- data.url = req.url();
196
+ if (!step.url && req.isNavigationRequest()) {
197
+ step.url = req.url();
206
198
  }
207
- driver.context.off('request', captureUrl);
199
+ this.#driver.context.off('request', captureUrl);
208
200
  };
209
- driver.context.on('request', captureUrl);
201
+ this.#driver.context.on('request', captureUrl);
210
202
 
203
+ const data: StepResult = {};
211
204
  const traceEnabled = trace || filmstrips;
212
205
  try {
213
206
  /**
214
207
  * Set up plugin manager context and also register
215
208
  * step level plugins
216
209
  */
217
- pluginManager.onStep(step);
218
- traceEnabled && (await pluginManager.start('trace'));
210
+ Gatherer.pluginManager.onStep(step);
211
+ traceEnabled && (await Gatherer.pluginManager.start('trace'));
219
212
  // invoke the step callback by extracting to a variable to get better stack trace
220
- const cb = step.callback;
213
+ const cb = step.cb;
221
214
  await cb();
215
+ step.status = "succeeded"
222
216
  } catch (error) {
223
- data.status = 'failed';
224
- data.error = error;
217
+ step.status = 'failed';
218
+ step.error = error;
225
219
  } finally {
226
220
  /**
227
221
  * Collect all step level metrics and trace events
228
222
  */
229
223
  if (metrics) {
230
224
  data.pagemetrics = await (
231
- pluginManager.get('performance') as PerformanceManager
225
+ Gatherer.pluginManager.get('performance') as PerformanceManager
232
226
  ).getMetrics();
233
227
  }
234
228
  if (traceEnabled) {
235
- const traceOutput = await pluginManager.stop('trace');
229
+ const traceOutput = await Gatherer.pluginManager.stop('trace');
236
230
  Object.assign(data, traceOutput);
237
231
  }
238
232
  /**
@@ -241,31 +235,30 @@ export default class Runner {
241
235
  *
242
236
  * Last open page will get us the correct screenshot
243
237
  */
244
- const pages = driver.context.pages();
238
+ const pages = this.#driver.context.pages();
245
239
  const page = pages[pages.length - 1];
246
240
  if (page) {
247
- data.url ??= page.url();
241
+ step.url ??= page.url();
248
242
  if (screenshots && screenshots !== 'off') {
249
243
  await this.captureScreenshot(page, step);
250
244
  }
251
245
  }
252
246
  }
253
247
  log(`Runner: end step (${step.name})`);
254
- return data;
248
+ return data
255
249
  }
256
250
 
257
- async runSteps(
251
+ async #runSteps(
258
252
  journey: Journey,
259
- context: JourneyContext,
260
253
  options: RunOptions
261
254
  ) {
262
255
  const results: Array<StepResult> = [];
263
256
  const isOnlyExists = journey.steps.filter(s => s.only).length > 0;
264
257
  let skipStep = false;
265
258
  for (const step of journey.steps) {
266
- const start = monotonicTimeInSeconds();
259
+ step._startTime = monotonicTimeInSeconds();
267
260
  this.#reporter?.onStepStart?.(journey, step);
268
- let data: StepResult = { status: 'succeeded' };
261
+ let data: StepResult = {};
269
262
  /**
270
263
  * Skip the step
271
264
  * - if the step is marked as skip
@@ -277,20 +270,17 @@ export default class Runner {
277
270
  (skipStep && !step.only) ||
278
271
  (isOnlyExists && !step.only)
279
272
  ) {
280
- data.status = 'skipped';
273
+ step.status = 'skipped';
281
274
  } else {
282
- data = await this.runStep(step, context, options);
275
+ data = await this.#runStep(step, options);
283
276
  /**
284
277
  * skip next steps if the previous step returns error
285
278
  */
286
- if (data.error && !step.soft) skipStep = true;
279
+ if (step.error && !step.soft) skipStep = true;
287
280
  }
288
- this.#reporter?.onStepEnd?.(journey, step, {
289
- start,
290
- end: monotonicTimeInSeconds(),
291
- ...data,
292
- });
293
- if (options.pauseOnError && data.error) {
281
+ step.duration = monotonicTimeInSeconds() - step._startTime;
282
+ this.#reporter?.onStepEnd?.(journey, step, data);
283
+ if (options.pauseOnError && step.error) {
294
284
  await new Promise(r => process.stdin.on('data', r));
295
285
  }
296
286
  results.push(data);
@@ -298,121 +288,115 @@ export default class Runner {
298
288
  return results;
299
289
  }
300
290
 
301
- registerJourney(journey: Journey, context: JourneyContext) {
302
- this.#currentJourney = journey;
303
- const timestamp = getTimestamp();
304
- const { params } = context;
291
+ async #startJourney(journey: Journey, options: RunOptions) {
292
+ journey._startTime = monotonicTimeInSeconds();
293
+ this.#driver = await Gatherer.setupDriver(options);
294
+ await Gatherer.beginRecording(this.#driver, options);
295
+ /**
296
+ * For each journey we create the screenshots folder for
297
+ * caching all screenshots and clear them at end of each journey
298
+ */
299
+ await mkdir(this.#screenshotPath, { recursive: true });
300
+ const params = options.params
305
301
  this.#reporter?.onJourneyStart?.(journey, {
306
- timestamp,
302
+ timestamp: getTimestamp(),
307
303
  params,
308
304
  });
309
305
  /**
310
306
  * Exeucute the journey callback which registers the steps for current journey
311
307
  */
312
- journey.callback({ ...context.driver, params });
308
+ journey.cb({ ...this.#driver, params, info: this });
313
309
  }
314
310
 
315
- async endJourney(
316
- journey,
317
- result: JourneyContext & JourneyResult,
311
+ async #endJourney(
312
+ journey: Journey,
313
+ result: JourneyResult,
318
314
  options: RunOptions
319
315
  ) {
320
- const end = monotonicTimeInSeconds();
321
- const { pluginManager, start, status, error, browserDelay } = result;
322
- const pluginOutput = await pluginManager.output();
316
+ // Enhance the journey results
317
+ const pOutput = await Gatherer.pluginManager.output();
318
+ const bConsole = filterBrowserMessages(pOutput.browserconsole, journey.status);
323
319
  await this.#reporter?.onJourneyEnd?.(journey, {
324
- status,
325
- error,
326
- start,
327
- end,
328
- browserDelay,
320
+ browserDelay: this.#browserDelay,
329
321
  timestamp: getTimestamp(),
330
322
  options,
331
- ...pluginOutput,
332
- browserconsole: filterBrowserMessages(
333
- pluginOutput.browserconsole,
334
- status
335
- ),
323
+ networkinfo: pOutput.networkinfo,
324
+ browserconsole: bConsole,
336
325
  });
326
+ await Gatherer.endRecording();
327
+ await Gatherer.dispose(this.#driver)
337
328
  // clear screenshots cache after each journey
338
- await rm(Runner.screenshotPath, { recursive: true, force: true });
329
+ await rm(this.#screenshotPath, { recursive: true, force: true });
330
+ return Object.assign(result, {
331
+ networkinfo: pOutput.networkinfo,
332
+ browserconsole: bConsole,
333
+ ...journey,
334
+ });
339
335
  }
340
336
 
341
337
  /**
342
338
  * Simulate a journey run to capture errors in the beforeAll hook
343
339
  */
344
- async runFakeJourney(journey: Journey, options: RunOptions) {
340
+ async #runFakeJourney(journey: Journey, options: RunOptions) {
345
341
  const start = monotonicTimeInSeconds();
346
342
  this.#reporter.onJourneyStart?.(journey, {
347
343
  timestamp: getTimestamp(),
348
344
  params: options.params,
349
345
  });
350
- const result: JourneyResult = {
351
- status: 'failed',
352
- error: this.hookError,
353
- };
346
+
347
+ // Mark the journey as failed and report the hook error as journey error
348
+ journey.status = 'failed';
349
+ journey.error = this.#hookError;
350
+ journey.duration = monotonicTimeInSeconds() - start;
351
+
354
352
  await this.#reporter.onJourneyEnd?.(journey, {
355
353
  timestamp: getTimestamp(),
356
- start,
357
354
  options,
358
- browserDelay: 0,
359
- end: monotonicTimeInSeconds(),
360
- ...result,
355
+ browserDelay: this.#browserDelay,
361
356
  });
362
- return result;
357
+ return journey;
363
358
  }
364
359
 
365
- async runJourney(journey: Journey, options: RunOptions) {
366
- const result: JourneyResult = { status: 'succeeded' };
367
- const context = await Runner.createContext(options);
360
+ async _runJourney(journey: Journey, options: RunOptions) {
361
+ this.#currentJourney = journey;
368
362
  log(`Runner: start journey (${journey.name})`);
363
+ let result: JourneyResult = {};
364
+ const hookArgs = { env: options.environment, params: options.params, info: this };
369
365
  try {
370
- this.registerJourney(journey, context);
371
- const hookArgs = {
372
- env: options.environment,
373
- params: options.params,
374
- };
375
- await this.runBeforeHook(journey, hookArgs);
376
- const stepResults = await this.runSteps(journey, context, options);
377
- /**
378
- * Mark journey as failed if any intermediate step fails
379
- */
380
- for (const stepResult of stepResults) {
381
- if (stepResult.status === 'failed') {
382
- result.status = stepResult.status;
383
- result.error = stepResult.error;
366
+ await this.#startJourney(journey, options);
367
+ await this.#runBeforeHook(journey, hookArgs);
368
+ const stepResults = await this.#runSteps(journey, options);
369
+ journey.status = "succeeded";
370
+ // Mark journey as failed if any one of the step fails
371
+ for (const step of journey.steps) {
372
+ if (step.status === 'failed') {
373
+ journey.status = step.status;
374
+ journey.error = step.error;
384
375
  }
385
376
  }
386
- result.steps = stepResults;
387
- await this.runAfterHook(journey, hookArgs);
377
+ result.stepsresults = stepResults;
388
378
  } catch (e) {
389
- result.status = 'failed';
390
- result.error = e;
379
+ journey.status = 'failed';
380
+ journey.error = e;
391
381
  } finally {
392
- await this.endJourney(journey, { ...context, ...result }, options);
393
- await Gatherer.dispose(context.driver);
382
+ journey.duration = monotonicTimeInSeconds() - journey._startTime;
383
+ // Run after hook on journey failure and capture the uncaught error as
384
+ // journey error, hook is purposely run before to capture errors during reporting
385
+ await this.#runAfterHook(journey, hookArgs).catch(e => {
386
+ journey.status = 'failed';
387
+ journey.error = e;
388
+ })
389
+ result = await this.#endJourney(journey, result, options);
394
390
  }
395
391
  log(`Runner: end journey (${journey.name})`);
396
392
  return result;
397
393
  }
398
394
 
399
- async init(options: RunOptions) {
400
- this.setReporter(options);
401
- this.#reporter.onStart?.({
402
- numJourneys: this.journeys.length,
403
- networkConditions: options.networkConditions,
404
- });
405
- /**
406
- * Set up the directory for caching screenshots
407
- */
408
- await mkdir(CACHE_PATH, { recursive: true });
409
- }
410
-
411
- buildMonitors(options: PushOptions) {
395
+ _buildMonitors(options: PushOptions) {
412
396
  /**
413
397
  * Update the global monitor configuration required for setting defaults
414
398
  */
415
- this.updateMonitor({
399
+ this._updateMonitor({
416
400
  throttling: options.throttling,
417
401
  schedule: options.schedule,
418
402
  locations: options.locations,
@@ -427,7 +411,7 @@ export default class Runner {
427
411
  });
428
412
 
429
413
  const monitors: Monitor[] = [];
430
- for (const journey of this.journeys) {
414
+ for (const journey of this.#journeys) {
431
415
  this.#currentJourney = journey;
432
416
  if (journey.skip) {
433
417
  throw new Error(
@@ -442,72 +426,87 @@ export default class Runner {
442
426
  * - filter out monitors based on matched tags and name after applying both
443
427
  * global and local monitor configurations
444
428
  */
445
- journey.callback({ params: options.params } as any);
446
- journey.monitor.update(this.monitor?.config);
429
+ journey.cb({ params: options.params } as any);
430
+ const monitor = journey._getMonitor();
431
+ monitor.update(this.#monitor?.config);
447
432
  if (
448
- !journey.monitor.isMatch(
433
+ !monitor.isMatch(
449
434
  options.grepOpts?.match,
450
435
  options.grepOpts?.tags
451
436
  )
452
437
  ) {
453
438
  continue;
454
439
  }
455
- journey.monitor.validate();
456
- monitors.push(journey.monitor);
440
+ monitor.validate();
441
+ monitors.push(monitor);
457
442
  }
458
443
  return monitors;
459
444
  }
460
445
 
461
- async run(options: RunOptions) {
462
- const result: RunResult = {};
446
+ async #init(options: RunOptions) {
447
+ this.setReporter(options);
448
+ this.#reporter.onStart?.({
449
+ numJourneys: this.#journeys.length,
450
+ networkConditions: options.networkConditions,
451
+ });
452
+ /**
453
+ * Set up the directory for caching screenshots
454
+ */
455
+ await mkdir(CACHE_PATH, { recursive: true });
456
+ }
457
+
458
+ async _run(options: RunOptions): Promise<RunResult> {
459
+ this.config = options;
460
+ let result: RunResult = {};
463
461
  if (this.#active) {
464
462
  return result;
465
463
  }
466
464
  this.#active = true;
467
- log(`Runner: run ${this.journeys.length} journeys`);
468
- this.init(options);
469
- await this.runBeforeAllHook({
470
- env: options.environment,
471
- params: options.params,
472
- }).catch(e => (this.hookError = e));
473
-
465
+ log(`Runner: run ${this.#journeys.length} journeys`);
466
+ this.#init(options);
467
+ const hookArgs = this.buildHookArgs();
468
+ await this.#runBeforeAllHook(hookArgs).catch(e => (this.#hookError = e));
474
469
  const { dryRun, grepOpts } = options;
475
- /**
476
- * Skip other journeys when using `.only`
477
- */
478
- const onlyJournerys = this.journeys.filter(j => j.only);
470
+
471
+ // collect all journeys with `.only` annotation and skip the rest
472
+ const onlyJournerys = this.#journeys.filter(j => j.only);
479
473
  if (onlyJournerys.length > 0) {
480
- this.journeys = onlyJournerys;
474
+ this.#journeys = onlyJournerys;
475
+ } else {
476
+ // filter journeys based on tags and skip annotations
477
+ this.#journeys = this.#journeys.filter(j => j._isMatch(grepOpts?.match, grepOpts?.tags) && !j.skip);
481
478
  }
482
479
 
483
- for (const journey of this.journeys) {
484
- /**
485
- * Used by heartbeat to gather all registered journeys
486
- */
487
- if (dryRun) {
488
- this.#reporter.onJourneyRegister?.(journey);
489
- continue;
490
- }
491
- if (!journey.isMatch(grepOpts?.match, grepOpts?.tags) || journey.skip) {
492
- continue;
493
- }
494
- const journeyResult: JourneyResult = this.hookError
495
- ? await this.runFakeJourney(journey, options)
496
- : await this.runJourney(journey, options);
480
+ // Used by heartbeat to gather all registered journeys
481
+ if (dryRun) {
482
+ this.#journeys.forEach(journey => this.#reporter.onJourneyRegister?.(journey))
483
+ } else if (this.#journeys.length > 0) {
484
+ result = await this._runJourneys(options);
485
+ }
486
+ await this.#runAfterAllHook(hookArgs).catch(async () => await this._reset());
487
+ await this._reset();
488
+ return result;
489
+ }
490
+
491
+ async _runJourneys(options: RunOptions) {
492
+ const result: RunResult = {};
493
+ const browserStart = monotonicTimeInSeconds();
494
+ await Gatherer.launchBrowser(options);
495
+ this.#browserDelay = monotonicTimeInSeconds() - browserStart;
496
+
497
+ for (const journey of this.#journeys) {
498
+ const journeyResult: JourneyResult = this.#hookError
499
+ ? await this.#runFakeJourney(journey, options)
500
+ : await this._runJourney(journey, options);
497
501
  result[journey.name] = journeyResult;
498
502
  }
499
503
  await Gatherer.stop();
500
- await this.runAfterAllHook({
501
- env: options.environment,
502
- params: options.params,
503
- });
504
- await this.reset();
505
504
  return result;
506
505
  }
507
506
 
508
- async reset() {
507
+ async _reset() {
509
508
  this.#currentJourney = null;
510
- this.journeys = [];
509
+ this.#journeys = [];
511
510
  this.#active = false;
512
511
  /**
513
512
  * Clear all cache data stored for post processing by