@elastic/synthetics 1.0.0-beta.2 → 1.0.0-beta.22

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 (122) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/NOTICE.txt +250 -0
  3. package/README.md +2 -1
  4. package/dist/cli.js +87 -56
  5. package/dist/cli.js.map +1 -1
  6. package/dist/common_types.d.ts +116 -14
  7. package/dist/common_types.d.ts.map +1 -1
  8. package/dist/config.d.ts +2 -5
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +5 -5
  11. package/dist/config.js.map +1 -1
  12. package/dist/core/browser-service.js +5 -5
  13. package/dist/core/browser-service.js.map +1 -1
  14. package/dist/core/expect.d.ts +177 -0
  15. package/dist/core/expect.d.ts.map +1 -0
  16. package/dist/core/expect.js +33 -0
  17. package/dist/core/expect.js.map +1 -0
  18. package/dist/core/gatherer.d.ts +4 -7
  19. package/dist/core/gatherer.d.ts.map +1 -1
  20. package/dist/core/gatherer.js +39 -12
  21. package/dist/core/gatherer.js.map +1 -1
  22. package/dist/core/index.d.ts +5 -5
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/core/index.js +2 -2
  25. package/dist/core/index.js.map +1 -1
  26. package/dist/core/logger.d.ts +0 -1
  27. package/dist/core/logger.d.ts.map +1 -1
  28. package/dist/core/logger.js +3 -16
  29. package/dist/core/logger.js.map +1 -1
  30. package/dist/core/runner.d.ts +54 -45
  31. package/dist/core/runner.d.ts.map +1 -1
  32. package/dist/core/runner.js +181 -71
  33. package/dist/core/runner.js.map +1 -1
  34. package/dist/dsl/journey.d.ts +13 -4
  35. package/dist/dsl/journey.d.ts.map +1 -1
  36. package/dist/dsl/journey.js +40 -1
  37. package/dist/dsl/journey.js.map +1 -1
  38. package/dist/formatter/javascript.d.ts +73 -0
  39. package/dist/formatter/javascript.d.ts.map +1 -0
  40. package/dist/formatter/javascript.js +274 -0
  41. package/dist/formatter/javascript.js.map +1 -0
  42. package/dist/helpers.d.ts +33 -12
  43. package/dist/helpers.d.ts.map +1 -1
  44. package/dist/helpers.js +108 -54
  45. package/dist/helpers.js.map +1 -1
  46. package/dist/index.d.ts +8 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +6 -7
  49. package/dist/index.js.map +1 -1
  50. package/dist/parse_args.d.ts +5 -2
  51. package/dist/parse_args.d.ts.map +1 -1
  52. package/dist/parse_args.js +59 -16
  53. package/dist/parse_args.js.map +1 -1
  54. package/dist/plugins/browser-console.d.ts +5 -8
  55. package/dist/plugins/browser-console.d.ts.map +1 -1
  56. package/dist/plugins/browser-console.js +27 -8
  57. package/dist/plugins/browser-console.js.map +1 -1
  58. package/dist/plugins/network.d.ts +23 -20
  59. package/dist/plugins/network.d.ts.map +1 -1
  60. package/dist/plugins/network.js +149 -124
  61. package/dist/plugins/network.js.map +1 -1
  62. package/dist/plugins/performance.d.ts +5 -5
  63. package/dist/plugins/performance.d.ts.map +1 -1
  64. package/dist/plugins/performance.js +5 -5
  65. package/dist/plugins/performance.js.map +1 -1
  66. package/dist/plugins/plugin-manager.d.ts +11 -11
  67. package/dist/plugins/plugin-manager.d.ts.map +1 -1
  68. package/dist/plugins/plugin-manager.js +40 -24
  69. package/dist/plugins/plugin-manager.js.map +1 -1
  70. package/dist/plugins/tracing.d.ts +10 -25
  71. package/dist/plugins/tracing.d.ts.map +1 -1
  72. package/dist/plugins/tracing.js +40 -26
  73. package/dist/plugins/tracing.js.map +1 -1
  74. package/dist/reporters/base.d.ts +0 -1
  75. package/dist/reporters/base.d.ts.map +1 -1
  76. package/dist/reporters/base.js +34 -32
  77. package/dist/reporters/base.js.map +1 -1
  78. package/dist/reporters/json.d.ts +46 -59
  79. package/dist/reporters/json.d.ts.map +1 -1
  80. package/dist/reporters/json.js +188 -70
  81. package/dist/reporters/json.js.map +1 -1
  82. package/dist/reporters/junit.d.ts.map +1 -1
  83. package/dist/reporters/junit.js +7 -7
  84. package/dist/reporters/junit.js.map +1 -1
  85. package/dist/sdk/lh-trace-processor.d.ts +304 -0
  86. package/dist/sdk/lh-trace-processor.d.ts.map +1 -0
  87. package/dist/sdk/lh-trace-processor.js +832 -0
  88. package/dist/sdk/lh-trace-processor.js.map +1 -0
  89. package/dist/sdk/trace-metrics.d.ts +61 -0
  90. package/dist/sdk/trace-metrics.d.ts.map +1 -0
  91. package/dist/sdk/trace-metrics.js +252 -0
  92. package/dist/sdk/trace-metrics.js.map +1 -0
  93. package/dist/sdk/trace-processor.d.ts +106 -0
  94. package/dist/sdk/trace-processor.d.ts.map +1 -0
  95. package/dist/sdk/trace-processor.js +74 -0
  96. package/dist/sdk/trace-processor.js.map +1 -0
  97. package/dist/tsconfig.tsbuildinfo +1 -1575
  98. package/package.json +27 -16
  99. package/src/cli.ts +68 -45
  100. package/src/common_types.ts +138 -14
  101. package/src/config.ts +2 -5
  102. package/src/core/expect.ts +187 -0
  103. package/src/core/gatherer.ts +48 -24
  104. package/src/core/index.ts +6 -6
  105. package/src/core/logger.ts +1 -12
  106. package/src/core/runner.ts +259 -112
  107. package/src/dsl/journey.ts +28 -6
  108. package/src/formatter/javascript.ts +330 -0
  109. package/src/helpers.ts +114 -24
  110. package/src/index.ts +7 -6
  111. package/src/parse_args.ts +97 -22
  112. package/src/plugins/browser-console.ts +30 -13
  113. package/src/plugins/network.ts +175 -159
  114. package/src/plugins/performance.ts +7 -7
  115. package/src/plugins/plugin-manager.ts +47 -36
  116. package/src/plugins/tracing.ts +43 -54
  117. package/src/reporters/base.ts +29 -23
  118. package/src/reporters/json.ts +259 -80
  119. package/src/reporters/junit.ts +1 -1
  120. package/src/sdk/lh-trace-processor.ts +1033 -0
  121. package/src/sdk/trace-metrics.ts +287 -0
  122. package/src/sdk/trace-processor.ts +147 -0
@@ -23,47 +23,63 @@
23
23
  *
24
24
  */
25
25
 
26
- import { EventEmitter } from 'events';
26
+ import { once, EventEmitter } from 'events';
27
+ import { join } from 'path';
28
+ import { rm } from 'fs/promises';
27
29
  import { Journey } from '../dsl/journey';
28
30
  import { Step } from '../dsl/step';
29
31
  import { reporters, Reporter } from '../reporters';
30
- import { getMonotonicTime, getTimestamp, runParallel } from '../helpers';
32
+ import {
33
+ CACHE_PATH,
34
+ monotonicTimeInSeconds,
35
+ getTimestamp,
36
+ runParallel,
37
+ generateUniqueId,
38
+ mkdirAsync,
39
+ writeFileAsync,
40
+ } from '../helpers';
31
41
  import {
32
42
  StatusValue,
33
- FilmStrip,
34
- NetworkInfo,
35
- VoidCallback,
43
+ HooksCallback,
44
+ Params,
45
+ NetworkConditions,
46
+ PluginOutput,
36
47
  CliArgs,
48
+ HooksArgs,
49
+ PlaywrightOptions,
50
+ Driver,
51
+ Screenshot,
37
52
  } from '../common_types';
38
- import { BrowserMessage, PluginManager } from '../plugins';
39
- import { PerformanceManager, Metrics } from '../plugins';
40
- import { Driver, Gatherer } from './gatherer';
53
+ import { PageMetrics, PluginManager } from '../plugins';
54
+ import { PerformanceManager } from '../plugins';
55
+ import { Gatherer } from './gatherer';
41
56
  import { log } from './logger';
42
57
 
43
58
  export type RunOptions = Omit<
44
59
  CliArgs,
45
60
  | 'debug'
46
- | 'json'
47
61
  | 'pattern'
48
62
  | 'inline'
49
63
  | 'require'
50
- | 'suiteParams'
51
64
  | 'reporter'
52
- | 'environment'
65
+ | 'richEvents'
66
+ | 'capability'
67
+ | 'sandbox'
68
+ | 'headless'
69
+ | 'throttling'
53
70
  > & {
54
- params?: RunParamaters;
71
+ environment?: string;
72
+ playwrightOptions?: PlaywrightOptions;
73
+ networkConditions?: NetworkConditions;
55
74
  reporter?: CliArgs['reporter'] | Reporter;
56
75
  };
57
76
 
58
- type RunParamaters = { environment: string } & Record<string, unknown>;
77
+ type HookType = 'beforeAll' | 'afterAll';
78
+ export type SuiteHooks = Record<HookType, Array<HooksCallback>>;
59
79
 
60
- type BaseContext = {
61
- params?: RunParamaters;
80
+ type JourneyContext = {
81
+ params?: Params;
62
82
  start: number;
63
- end?: number;
64
- };
65
-
66
- type JourneyContext = BaseContext & {
67
83
  driver: Driver;
68
84
  pluginManager: PluginManager;
69
85
  };
@@ -71,59 +87,85 @@ type JourneyContext = BaseContext & {
71
87
  type StepResult = {
72
88
  status: StatusValue;
73
89
  url?: string;
74
- metrics?: Metrics;
75
90
  error?: Error;
76
- screenshot?: string;
91
+ pagemetrics?: PageMetrics;
92
+ filmstrips?: PluginOutput['filmstrips'];
93
+ metrics?: PluginOutput['metrics'];
94
+ traces?: PluginOutput['traces'];
77
95
  };
78
96
 
79
97
  type JourneyResult = {
80
98
  status: StatusValue;
81
99
  error?: Error;
100
+ networkinfo?: PluginOutput['networkinfo'];
101
+ browserconsole?: PluginOutput['browserconsole'];
82
102
  };
83
103
 
84
104
  type RunResult = Record<string, JourneyResult>;
85
105
 
86
- type HookType = 'beforeAll' | 'afterAll';
87
- export type SuiteHooks = Record<HookType, Array<VoidCallback>>;
106
+ interface StepEvent {
107
+ step: Step;
108
+ journey: Journey;
109
+ }
110
+
111
+ interface JourneyEvent {
112
+ journey: Journey;
113
+ timestamp: number;
114
+ params?: Params;
115
+ }
88
116
 
89
117
  interface Events {
90
- start: { numJourneys: number };
91
- 'journey:register': {
92
- journey: Journey;
118
+ start: {
119
+ numJourneys: number;
120
+ networkConditions?: NetworkConditions;
93
121
  };
94
- 'journey:start': {
122
+ 'journey:register': {
95
123
  journey: Journey;
96
- timestamp: number;
97
- params: RunParamaters;
98
124
  };
99
- 'journey:end': BaseContext &
125
+ 'journey:start': JourneyEvent;
126
+ 'journey:end': JourneyEvent &
100
127
  JourneyResult & {
101
- journey: Journey;
102
- filmstrips?: Array<FilmStrip>;
103
- networkinfo?: Array<NetworkInfo>;
104
- browserconsole?: Array<BrowserMessage>;
128
+ start: number;
129
+ end: number;
130
+ options: RunOptions;
131
+ timestamp: number;
132
+ };
133
+ 'journey:end:reported': unknown;
134
+ 'step:start': StepEvent;
135
+ 'step:end': StepEvent &
136
+ StepResult & {
137
+ start: number;
138
+ end: number;
105
139
  };
106
- 'step:start': { journey: Journey; step: Step };
107
- 'step:end': StepResult & {
108
- start: number;
109
- end: number;
110
- journey: Journey;
111
- step: Step;
112
- };
113
140
  end: unknown;
114
141
  }
115
142
 
116
- export default class Runner {
143
+ export default interface Runner {
144
+ on<K extends keyof Events>(name: K, listener: (v: Events[K]) => void): this;
145
+ emit<K extends keyof Events>(event: K, args: Events[K]): boolean;
146
+ }
147
+
148
+ export default class Runner extends EventEmitter {
117
149
  active = false;
118
- eventEmitter = new EventEmitter();
119
150
  currentJourney?: Journey = null;
120
151
  journeys: Journey[] = [];
121
152
  hooks: SuiteHooks = { beforeAll: [], afterAll: [] };
153
+ hookError: Error | undefined;
154
+ static screenshotPath = join(CACHE_PATH, 'screenshots');
122
155
 
123
156
  static async createContext(options: RunOptions): Promise<JourneyContext> {
124
- const start = getMonotonicTime();
125
157
  const driver = await Gatherer.setupDriver(options);
158
+ /**
159
+ * Do not include browser launch/context creation duration
160
+ * as part of journey duration
161
+ */
162
+ const start = monotonicTimeInSeconds();
126
163
  const pluginManager = await Gatherer.beginRecording(driver, options);
164
+ /**
165
+ * For each journey we create the screenshots folder for
166
+ * caching all screenshots and clear them at end of each journey
167
+ */
168
+ await mkdirAsync(this.screenshotPath, { recursive: true });
127
169
  return {
128
170
  start,
129
171
  params: options.params,
@@ -132,7 +174,34 @@ export default class Runner {
132
174
  };
133
175
  }
134
176
 
135
- addHook(type: HookType, callback: VoidCallback) {
177
+ async captureScreenshot(page: Driver['page'], step: Step) {
178
+ await page.waitForLoadState('load');
179
+ const buffer = await page
180
+ .screenshot({
181
+ type: 'jpeg',
182
+ quality: 80,
183
+ })
184
+ .catch(() => {});
185
+ /**
186
+ * Write the screenshot image buffer with additional details (step
187
+ * information) which could be extracted at the end of
188
+ * each journey without impacting the step timing information
189
+ */
190
+ if (buffer) {
191
+ const fileName = `${generateUniqueId()}.json`;
192
+ const screenshot: Screenshot = {
193
+ step,
194
+ timestamp: getTimestamp(),
195
+ data: buffer.toString('base64'),
196
+ };
197
+ await writeFileAsync(
198
+ join(Runner.screenshotPath, fileName),
199
+ JSON.stringify(screenshot)
200
+ );
201
+ }
202
+ }
203
+
204
+ addHook(type: HookType, callback: HooksCallback) {
136
205
  this.hooks[type].push(callback);
137
206
  }
138
207
 
@@ -141,33 +210,24 @@ export default class Runner {
141
210
  this.currentJourney = journey;
142
211
  }
143
212
 
144
- emit<K extends keyof Events>(e: K, v: Events[K]) {
145
- log(`Runner: emit> ${e}`);
146
- this.eventEmitter.emit(e, v);
147
- }
148
-
149
- on<K extends keyof Events>(e: K, cb: (v: Events[K]) => void) {
150
- this.eventEmitter.on(e, cb);
151
- }
152
-
153
- async runBeforeAllHook() {
213
+ async runBeforeAllHook(args: HooksArgs) {
154
214
  log(`Runner: beforeAll hooks`);
155
- await runParallel(this.hooks.beforeAll);
215
+ await runParallel(this.hooks.beforeAll, args);
156
216
  }
157
217
 
158
- async runAfterAllHook() {
218
+ async runAfterAllHook(args: HooksArgs) {
159
219
  log(`Runner: afterAll hooks`);
160
- await runParallel(this.hooks.afterAll);
220
+ await runParallel(this.hooks.afterAll, args);
161
221
  }
162
222
 
163
- async runBeforeHook(journey: Journey) {
223
+ async runBeforeHook(journey: Journey, args: HooksArgs) {
164
224
  log(`Runner: before hooks for (${journey.name})`);
165
- await runParallel(journey.hooks.before);
225
+ await runParallel(journey.hooks.before, args);
166
226
  }
167
227
 
168
- async runAfterHook(journey: Journey) {
228
+ async runAfterHook(journey: Journey, args: HooksArgs) {
169
229
  log(`Runner: after hooks for (${journey.name})`);
170
- await runParallel(journey.hooks.after);
230
+ await runParallel(journey.hooks.after, args);
171
231
  }
172
232
 
173
233
  async runStep(
@@ -179,7 +239,7 @@ export default class Runner {
179
239
  status: 'succeeded',
180
240
  };
181
241
  log(`Runner: start step (${step.name})`);
182
- const { metrics, screenshots } = options;
242
+ const { metrics, screenshots, filmstrips, trace } = options;
183
243
  const { driver, pluginManager } = context;
184
244
  /**
185
245
  * URL needs to be the first navigation request of any step
@@ -190,28 +250,49 @@ export default class Runner {
190
250
  if (!data.url && req.isNavigationRequest()) {
191
251
  data.url = req.url();
192
252
  }
193
- driver.page.off('request', captureUrl);
253
+ driver.context.off('request', captureUrl);
194
254
  };
195
- driver.page.on('request', captureUrl);
255
+ driver.context.on('request', captureUrl);
256
+
196
257
  try {
258
+ /**
259
+ * Set up plugin manager context and also register
260
+ * step level plugins
261
+ */
262
+ const traceEnabled = trace || filmstrips;
197
263
  pluginManager.onStep(step);
264
+ traceEnabled && (await pluginManager.start('trace'));
265
+ // call the step definition
198
266
  await step.callback();
267
+ /**
268
+ * Collect all step level metrics
269
+ */
199
270
  if (metrics) {
200
- data.metrics = await pluginManager.get(PerformanceManager).getMetrics();
271
+ data.pagemetrics = await (
272
+ pluginManager.get('performance') as PerformanceManager
273
+ ).getMetrics();
274
+ }
275
+ if (traceEnabled) {
276
+ const traceOutput = await pluginManager.stop('trace');
277
+ Object.assign(data, traceOutput);
201
278
  }
202
279
  } catch (error) {
203
280
  data.status = 'failed';
204
281
  data.error = error;
205
282
  } finally {
206
- data.url ??= driver.page.url();
207
- if (screenshots) {
208
- await driver.page.waitForLoadState('load');
209
- data.screenshot = (
210
- await driver.page.screenshot({
211
- type: 'jpeg',
212
- quality: 80,
213
- })
214
- ).toString('base64');
283
+ /**
284
+ * Capture screenshot for the newly created pages
285
+ * via popup or new windows/tabs
286
+ *
287
+ * Last open page will get us the correct screenshot
288
+ */
289
+ const pages = driver.context.pages();
290
+ const page = pages[pages.length - 1];
291
+ if (page) {
292
+ data.url ??= page.url();
293
+ if (screenshots && screenshots !== 'off') {
294
+ await this.captureScreenshot(page, step);
295
+ }
215
296
  }
216
297
  }
217
298
  log(`Runner: end step (${step.name})`);
@@ -226,7 +307,7 @@ export default class Runner {
226
307
  const results: Array<StepResult> = [];
227
308
  let skipStep = false;
228
309
  for (const step of journey.steps) {
229
- const start = getMonotonicTime();
310
+ const start = monotonicTimeInSeconds();
230
311
  this.emit('step:start', { journey, step });
231
312
  let data: StepResult = { status: 'succeeded' };
232
313
  if (skipStep) {
@@ -242,7 +323,7 @@ export default class Runner {
242
323
  journey,
243
324
  step,
244
325
  start,
245
- end: getMonotonicTime(),
326
+ end: monotonicTimeInSeconds(),
246
327
  ...data,
247
328
  });
248
329
  if (options.pauseOnError && data.error) {
@@ -264,35 +345,77 @@ export default class Runner {
264
345
  journey.callback({ ...context.driver, params });
265
346
  }
266
347
 
267
- async endJourney(journey, result: JourneyContext & JourneyResult) {
268
- const { pluginManager, start, params, status, error } = result;
269
- const {
270
- filmstrips,
271
- networkinfo,
272
- browserconsole,
273
- } = await pluginManager.output();
348
+ async endJourney(
349
+ journey,
350
+ result: JourneyContext & JourneyResult,
351
+ options: RunOptions
352
+ ) {
353
+ const end = monotonicTimeInSeconds();
354
+ const { pluginManager, start, status, error } = result;
355
+ const pluginOutput = pluginManager.output();
274
356
  this.emit('journey:end', {
275
357
  journey,
276
358
  status,
277
359
  error,
278
- params,
279
360
  start,
280
- end: getMonotonicTime(),
281
- filmstrips,
282
- networkinfo,
283
- browserconsole: status == 'failed' ? browserconsole : null,
361
+ end,
362
+ timestamp: getTimestamp(),
363
+ options,
364
+ ...pluginOutput,
365
+ browserconsole: status == 'failed' ? pluginOutput.browserconsole : [],
366
+ });
367
+ /**
368
+ * Wait for the all the reported events to be consumed aschronously
369
+ * by reporter.
370
+ */
371
+ if (options.reporter === 'json') {
372
+ await once(this, 'journey:end:reported');
373
+ }
374
+ // clear screenshots cache after each journey
375
+ await rm(Runner.screenshotPath, { recursive: true, force: true });
376
+ }
377
+
378
+ /**
379
+ * Simulate a journey run to capture errors in the beforeAll hook
380
+ */
381
+ async runFakeJourney(journey: Journey, options: RunOptions) {
382
+ const start = monotonicTimeInSeconds();
383
+ this.emit('journey:start', {
384
+ journey,
385
+ timestamp: getTimestamp(),
386
+ params: options.params,
387
+ });
388
+ const result: JourneyResult = {
389
+ status: 'failed',
390
+ error: this.hookError,
391
+ };
392
+ this.emit('journey:end', {
393
+ journey,
394
+ timestamp: getTimestamp(),
395
+ start,
396
+ options,
397
+ end: monotonicTimeInSeconds(),
398
+ ...result,
284
399
  });
400
+ if (options.reporter === 'json') {
401
+ await once(this, 'journey:end:reported');
402
+ }
403
+ return result;
285
404
  }
286
405
 
287
406
  async runJourney(journey: Journey, options: RunOptions) {
288
407
  const result: JourneyResult = {
289
408
  status: 'succeeded',
290
409
  };
291
- log(`Runner: start journey (${journey.name})`);
292
410
  const context = await Runner.createContext(options);
411
+ log(`Runner: start journey (${journey.name})`);
293
412
  try {
294
413
  this.registerJourney(journey, context);
295
- await this.runBeforeHook(journey);
414
+ const hookArgs = {
415
+ env: options.environment,
416
+ params: options.params,
417
+ };
418
+ await this.runBeforeHook(journey, hookArgs);
296
419
  const stepResults = await this.runSteps(journey, context, options);
297
420
  /**
298
421
  * Mark journey as failed if any intermediate step fails
@@ -303,26 +426,20 @@ export default class Runner {
303
426
  result.error = stepResult.error;
304
427
  }
305
428
  }
306
- await this.runAfterHook(journey);
429
+ await this.runAfterHook(journey, hookArgs);
307
430
  } catch (e) {
308
431
  result.status = 'failed';
309
432
  result.error = e;
310
433
  } finally {
311
- await this.endJourney(journey, { ...context, ...result });
434
+ await this.endJourney(journey, { ...context, ...result }, options);
312
435
  await Gatherer.dispose(context.driver);
313
436
  }
314
437
  log(`Runner: end journey (${journey.name})`);
315
438
  return result;
316
439
  }
317
440
 
318
- async run(options: RunOptions) {
319
- const result: RunResult = {};
320
- if (this.active) {
321
- return result;
322
- }
323
- this.active = true;
324
- log(`Runner: run ${this.journeys.length} journeys`);
325
- const { reporter, journeyName, outfd } = options;
441
+ async init(options: RunOptions) {
442
+ const { reporter, outfd, networkConditions } = options;
326
443
  /**
327
444
  * Set up the corresponding reporter and fallback
328
445
  */
@@ -331,34 +448,64 @@ export default class Runner {
331
448
  ? reporter
332
449
  : reporters[reporter] || reporters['default'];
333
450
  new Reporter(this, { fd: outfd });
334
- this.emit('start', { numJourneys: this.journeys.length });
335
- await this.runBeforeAllHook();
451
+ this.emit('start', {
452
+ numJourneys: this.journeys.length,
453
+ networkConditions,
454
+ });
455
+ /**
456
+ * Set up the directory for caching screenshots
457
+ */
458
+ await mkdirAsync(CACHE_PATH, { recursive: true });
459
+ }
460
+
461
+ async run(options: RunOptions) {
462
+ const result: RunResult = {};
463
+ if (this.active) {
464
+ return result;
465
+ }
466
+ 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
+
474
+ const { dryRun, match, tags } = options;
336
475
  for (const journey of this.journeys) {
337
476
  /**
338
477
  * Used by heartbeat to gather all registered journeys
339
478
  */
340
- if (options.dryRun) {
479
+ if (dryRun) {
341
480
  this.emit('journey:register', { journey });
342
481
  continue;
343
482
  }
344
- // TODO: Replace functionality with journey.only
345
- if (journeyName && journey.name != journeyName) {
483
+ if (!journey.isMatch(match, tags)) {
346
484
  continue;
347
485
  }
348
- const journeyResult = await this.runJourney(journey, options);
486
+ const journeyResult: JourneyResult = this.hookError
487
+ ? await this.runFakeJourney(journey, options)
488
+ : await this.runJourney(journey, options);
349
489
  result[journey.name] = journeyResult;
350
490
  }
351
491
  await Gatherer.stop();
352
- await this.runAfterAllHook();
353
- this.reset();
354
- this.emit('end', {});
492
+ await this.runAfterAllHook({
493
+ env: options.environment,
494
+ params: options.params,
495
+ });
496
+ await this.reset();
355
497
  return result;
356
498
  }
357
499
 
358
- reset() {
359
- log('Runner: reset');
500
+ async reset() {
501
+ /**
502
+ * Clear all cache data stored for post processing by
503
+ * the current synthetic agent run
504
+ */
505
+ await rm(CACHE_PATH, { recursive: true, force: true });
360
506
  this.currentJourney = null;
361
507
  this.journeys = [];
362
508
  this.active = false;
509
+ this.emit('end', {});
363
510
  }
364
511
  }
@@ -24,35 +24,38 @@
24
24
  */
25
25
 
26
26
  import { Browser, Page, BrowserContext, CDPSession } from 'playwright-chromium';
27
+ import micromatch, { isMatch } from 'micromatch';
27
28
  import { Step } from './step';
28
- import { VoidCallback } from '../common_types';
29
+ import { VoidCallback, HooksCallback, Params } from '../common_types';
29
30
 
30
31
  export type JourneyOptions = {
31
32
  name: string;
32
33
  id?: string;
34
+ tags?: string[];
33
35
  };
34
36
 
35
37
  type HookType = 'before' | 'after';
36
- export type Hooks = Record<HookType, Array<VoidCallback>>;
37
-
38
+ export type Hooks = Record<HookType, Array<HooksCallback>>;
38
39
  export type JourneyCallback = (options: {
39
40
  page: Page;
40
41
  context: BrowserContext;
41
42
  browser: Browser;
42
43
  client: CDPSession;
43
- params: Record<string, any>;
44
+ params: Params;
44
45
  }) => void;
45
46
 
46
47
  export class Journey {
47
48
  name: string;
48
49
  id?: string;
50
+ tags?: string[];
49
51
  callback: JourneyCallback;
50
52
  steps: Step[] = [];
51
53
  hooks: Hooks = { before: [], after: [] };
52
54
 
53
55
  constructor(options: JourneyOptions, callback: JourneyCallback) {
54
56
  this.name = options.name;
55
- this.id = options.id;
57
+ this.id = options.id || options.name;
58
+ this.tags = options.tags;
56
59
  this.callback = callback;
57
60
  }
58
61
 
@@ -62,7 +65,26 @@ export class Journey {
62
65
  return step;
63
66
  }
64
67
 
65
- addHook(type: HookType, callback: VoidCallback) {
68
+ addHook(type: HookType, callback: HooksCallback) {
66
69
  this.hooks[type].push(callback);
67
70
  }
71
+ /**
72
+ * Matches journeys based on the provided args. Proitize tags over match
73
+ * - tags pattern that matches only tags
74
+ * - match pattern that matches both name and tags
75
+ */
76
+ isMatch(matchPattern: string, tagsPattern: Array<string>) {
77
+ if (tagsPattern) {
78
+ return this.tagsMatch(tagsPattern);
79
+ }
80
+ if (matchPattern) {
81
+ return isMatch(this.name, matchPattern) || this.tagsMatch(matchPattern);
82
+ }
83
+ return true;
84
+ }
85
+
86
+ tagsMatch(pattern) {
87
+ const matchess = micromatch(this.tags || ['*'], pattern);
88
+ return matchess.length > 0;
89
+ }
68
90
  }