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

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