@elastic/synthetics 1.0.0-beta.3 → 1.0.0-beta.32

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