@elastic/synthetics 1.0.0-beta.4 → 1.0.0-beta.41

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 (220) hide show
  1. package/README.md +4 -3
  2. package/dist/cli.js +137 -160
  3. package/dist/cli.js.map +1 -1
  4. package/dist/common_types.d.ts +161 -31
  5. package/dist/common_types.d.ts.map +1 -1
  6. package/dist/config.d.ts +3 -5
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +7 -6
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/browser-service.d.ts +1 -1
  11. package/dist/core/browser-service.d.ts.map +1 -1
  12. package/dist/core/browser-service.js +7 -6
  13. package/dist/core/browser-service.js.map +1 -1
  14. package/dist/core/expect.d.ts +25 -25
  15. package/dist/core/expect.d.ts.map +1 -1
  16. package/dist/core/expect.js.map +1 -1
  17. package/dist/core/gatherer.d.ts +4 -8
  18. package/dist/core/gatherer.d.ts.map +1 -1
  19. package/dist/core/gatherer.js +53 -15
  20. package/dist/core/gatherer.js.map +1 -1
  21. package/dist/core/index.d.ts +5 -1
  22. package/dist/core/index.d.ts.map +1 -1
  23. package/dist/core/index.js +23 -11
  24. package/dist/core/index.js.map +1 -1
  25. package/dist/core/logger.d.ts +0 -1
  26. package/dist/core/logger.d.ts.map +1 -1
  27. package/dist/core/logger.js +3 -16
  28. package/dist/core/logger.js.map +1 -1
  29. package/dist/core/runner.d.ts +12 -63
  30. package/dist/core/runner.d.ts.map +1 -1
  31. package/dist/core/runner.js +156 -90
  32. package/dist/core/runner.js.map +1 -1
  33. package/dist/core/transform.d.ts +37 -0
  34. package/dist/core/transform.d.ts.map +1 -0
  35. package/dist/core/transform.js +148 -0
  36. package/dist/core/transform.js.map +1 -0
  37. package/dist/dsl/index.js +5 -1
  38. package/dist/dsl/index.js.map +1 -1
  39. package/dist/dsl/journey.d.ts +9 -4
  40. package/dist/dsl/journey.d.ts.map +1 -1
  41. package/dist/dsl/journey.js +37 -9
  42. package/dist/dsl/journey.js.map +1 -1
  43. package/dist/dsl/monitor.d.ts +83 -0
  44. package/dist/dsl/monitor.d.ts.map +1 -0
  45. package/dist/dsl/monitor.js +108 -0
  46. package/dist/dsl/monitor.js.map +1 -0
  47. package/dist/dsl/step.d.ts +3 -2
  48. package/dist/dsl/step.d.ts.map +1 -1
  49. package/dist/dsl/step.js +6 -1
  50. package/dist/dsl/step.js.map +1 -1
  51. package/dist/formatter/javascript.d.ts +98 -0
  52. package/dist/formatter/javascript.d.ts.map +1 -0
  53. package/dist/formatter/javascript.js +298 -0
  54. package/dist/formatter/javascript.js.map +1 -0
  55. package/dist/generator/index.d.ts +24 -0
  56. package/dist/generator/index.d.ts.map +1 -0
  57. package/dist/generator/index.js +241 -0
  58. package/dist/generator/index.js.map +1 -0
  59. package/dist/{reporters/reporter.js → generator/utils.d.ts} +5 -3
  60. package/dist/generator/utils.d.ts.map +1 -0
  61. package/dist/generator/utils.js +84 -0
  62. package/dist/generator/utils.js.map +1 -0
  63. package/dist/helpers.d.ts +28 -3
  64. package/dist/helpers.d.ts.map +1 -1
  65. package/dist/helpers.js +152 -20
  66. package/dist/helpers.js.map +1 -1
  67. package/dist/index.d.ts +6 -7
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +5 -26
  70. package/dist/index.js.map +1 -1
  71. package/dist/loader.d.ts +34 -0
  72. package/dist/loader.d.ts.map +1 -0
  73. package/dist/loader.js +149 -0
  74. package/dist/loader.js.map +1 -0
  75. package/dist/locations/index.d.ts +45 -0
  76. package/dist/locations/index.d.ts.map +1 -0
  77. package/dist/locations/index.js +83 -0
  78. package/dist/locations/index.js.map +1 -0
  79. package/dist/{reporters/reporter.d.ts → locations/public-locations.d.ts} +12 -8
  80. package/dist/locations/public-locations.d.ts.map +1 -0
  81. package/dist/locations/public-locations.js +41 -0
  82. package/dist/locations/public-locations.js.map +1 -0
  83. package/dist/options.d.ts +41 -0
  84. package/dist/options.d.ts.map +1 -0
  85. package/dist/options.js +198 -0
  86. package/dist/options.js.map +1 -0
  87. package/dist/plugins/browser-console.d.ts +5 -4
  88. package/dist/plugins/browser-console.d.ts.map +1 -1
  89. package/dist/plugins/browser-console.js +44 -24
  90. package/dist/plugins/browser-console.js.map +1 -1
  91. package/dist/plugins/index.js +5 -1
  92. package/dist/plugins/index.js.map +1 -1
  93. package/dist/plugins/network.d.ts +20 -33
  94. package/dist/plugins/network.d.ts.map +1 -1
  95. package/dist/plugins/network.js +177 -216
  96. package/dist/plugins/network.js.map +1 -1
  97. package/dist/plugins/performance.d.ts +4 -19
  98. package/dist/plugins/performance.d.ts.map +1 -1
  99. package/dist/plugins/performance.js +6 -5
  100. package/dist/plugins/performance.js.map +1 -1
  101. package/dist/plugins/plugin-manager.d.ts +9 -5
  102. package/dist/plugins/plugin-manager.d.ts.map +1 -1
  103. package/dist/plugins/plugin-manager.js +46 -30
  104. package/dist/plugins/plugin-manager.js.map +1 -1
  105. package/dist/plugins/tracing.d.ts +6 -6
  106. package/dist/plugins/tracing.d.ts.map +1 -1
  107. package/dist/plugins/tracing.js +10 -7
  108. package/dist/plugins/tracing.js.map +1 -1
  109. package/dist/push/bundler.d.ts +35 -0
  110. package/dist/push/bundler.d.ts.map +1 -0
  111. package/dist/push/bundler.js +131 -0
  112. package/dist/push/bundler.js.map +1 -0
  113. package/dist/push/index.d.ts +9 -0
  114. package/dist/push/index.d.ts.map +1 -0
  115. package/dist/push/index.js +227 -0
  116. package/dist/push/index.js.map +1 -0
  117. package/dist/push/kibana_api.d.ts +56 -0
  118. package/dist/push/kibana_api.d.ts.map +1 -0
  119. package/dist/push/kibana_api.js +135 -0
  120. package/dist/push/kibana_api.js.map +1 -0
  121. package/dist/push/monitor.d.ts +51 -0
  122. package/dist/push/monitor.d.ts.map +1 -0
  123. package/dist/push/monitor.js +244 -0
  124. package/dist/push/monitor.js.map +1 -0
  125. package/dist/{parse_args.d.ts → push/plugin.d.ts} +9 -6
  126. package/dist/push/plugin.d.ts.map +1 -0
  127. package/dist/push/plugin.js +66 -0
  128. package/dist/push/plugin.js.map +1 -0
  129. package/dist/push/request.d.ts +45 -0
  130. package/dist/push/request.d.ts.map +1 -0
  131. package/dist/push/request.js +109 -0
  132. package/dist/push/request.js.map +1 -0
  133. package/dist/push/utils.d.ts +31 -0
  134. package/dist/push/utils.d.ts.map +1 -0
  135. package/dist/push/utils.js +64 -0
  136. package/dist/push/utils.js.map +1 -0
  137. package/dist/reporters/base.d.ts +14 -6
  138. package/dist/reporters/base.d.ts.map +1 -1
  139. package/dist/reporters/base.js +58 -50
  140. package/dist/reporters/base.js.map +1 -1
  141. package/dist/reporters/index.d.ts +18 -8
  142. package/dist/reporters/index.d.ts.map +1 -1
  143. package/dist/reporters/index.js.map +1 -1
  144. package/dist/reporters/json.d.ts +17 -62
  145. package/dist/reporters/json.d.ts.map +1 -1
  146. package/dist/reporters/json.js +149 -179
  147. package/dist/reporters/json.js.map +1 -1
  148. package/dist/reporters/junit.d.ts +7 -1
  149. package/dist/reporters/junit.d.ts.map +1 -1
  150. package/dist/reporters/junit.js +90 -95
  151. package/dist/reporters/junit.js.map +1 -1
  152. package/dist/sdk/lh-trace-processor.d.ts +35 -9
  153. package/dist/sdk/lh-trace-processor.d.ts.map +1 -1
  154. package/dist/sdk/lh-trace-processor.js +114 -53
  155. package/dist/sdk/lh-trace-processor.js.map +1 -1
  156. package/dist/sdk/trace-metrics.d.ts +4 -4
  157. package/dist/sdk/trace-metrics.d.ts.map +1 -1
  158. package/dist/sdk/trace-metrics.js +3 -4
  159. package/dist/sdk/trace-metrics.js.map +1 -1
  160. package/dist/sdk/trace-processor.d.ts +11 -3
  161. package/dist/sdk/trace-processor.d.ts.map +1 -1
  162. package/dist/sdk/trace-processor.js +17 -9
  163. package/dist/sdk/trace-processor.js.map +1 -1
  164. package/package.json +39 -24
  165. package/src/cli.ts +219 -151
  166. package/src/common_types.ts +193 -31
  167. package/src/config.ts +3 -6
  168. package/src/core/expect.ts +58 -24
  169. package/src/core/gatherer.ts +64 -24
  170. package/src/core/index.ts +36 -15
  171. package/src/core/logger.ts +1 -12
  172. package/src/core/runner.ts +151 -136
  173. package/src/core/transform.ts +160 -0
  174. package/src/dsl/journey.ts +39 -6
  175. package/src/dsl/monitor.ts +140 -0
  176. package/src/dsl/step.ts +9 -2
  177. package/src/formatter/javascript.ts +367 -0
  178. package/src/generator/index.ts +308 -0
  179. package/src/generator/utils.ts +84 -0
  180. package/src/helpers.ts +159 -13
  181. package/src/index.ts +22 -22
  182. package/src/loader.ts +167 -0
  183. package/src/locations/index.ts +95 -0
  184. package/src/{reporters/reporter.ts → locations/public-locations.ts} +12 -9
  185. package/src/options.ts +227 -0
  186. package/src/plugins/browser-console.ts +30 -8
  187. package/src/plugins/network.ts +208 -267
  188. package/src/plugins/performance.ts +6 -22
  189. package/src/plugins/plugin-manager.ts +49 -31
  190. package/src/plugins/tracing.ts +8 -9
  191. package/src/push/bundler.ts +112 -0
  192. package/src/push/index.ts +282 -0
  193. package/src/push/kibana_api.ts +191 -0
  194. package/src/push/monitor.ts +276 -0
  195. package/src/push/plugin.ts +66 -0
  196. package/src/push/request.ts +141 -0
  197. package/src/push/utils.ts +77 -0
  198. package/src/reporters/base.ts +63 -52
  199. package/src/reporters/index.ts +26 -3
  200. package/src/reporters/json.ts +183 -202
  201. package/src/reporters/junit.ts +98 -93
  202. package/src/sdk/lh-trace-processor.ts +132 -68
  203. package/src/sdk/trace-metrics.ts +8 -4
  204. package/src/sdk/trace-processor.ts +28 -13
  205. package/templates/.github/workflows/run-synthetics.yml +27 -0
  206. package/templates/README.md +21 -0
  207. package/templates/journeys/advanced-example-helpers.ts +54 -0
  208. package/templates/journeys/advanced-example.journey.ts +43 -0
  209. package/templates/journeys/example.journey.ts +18 -0
  210. package/templates/lightweight/heartbeat.yml +8 -0
  211. package/templates/synthetics.config.ts +35 -0
  212. package/CHANGELOG.md +0 -131
  213. package/NOTICE.txt +0 -221
  214. package/dist/parse_args.d.ts.map +0 -1
  215. package/dist/parse_args.js +0 -95
  216. package/dist/parse_args.js.map +0 -1
  217. package/dist/reporters/reporter.d.ts.map +0 -1
  218. package/dist/reporters/reporter.js.map +0 -1
  219. package/dist/tsconfig.tsbuildinfo +0 -1888
  220. package/src/parse_args.ts +0 -135
@@ -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,7 +23,6 @@
23
23
  *
24
24
  */
25
25
 
26
- import { once, EventEmitter } from 'events';
27
26
  import { join } from 'path';
28
27
  import { mkdir, rm, writeFile } from 'fs/promises';
29
28
  import { Journey } from '../dsl/journey';
@@ -37,106 +36,50 @@ import {
37
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
- environment?: string;
65
- params?: Params;
66
- reporter?: CliArgs['reporter'] | Reporter;
67
- };
54
+ type HookType = 'beforeAll' | 'afterAll';
55
+ export type SuiteHooks = Record<HookType, Array<HooksCallback>>;
68
56
 
69
- type BaseContext = {
57
+ type JourneyContext = {
70
58
  params?: Params;
71
59
  start: number;
72
- end?: number;
73
- };
74
-
75
- type JourneyContext = BaseContext & {
76
60
  driver: Driver;
77
61
  pluginManager: PluginManager;
78
62
  };
79
63
 
80
- type StepResult = {
81
- status: StatusValue;
82
- url?: string;
83
- metrics?: Metrics;
84
- error?: Error;
85
- };
86
-
87
- type JourneyResult = {
88
- status: StatusValue;
89
- error?: Error;
90
- };
91
-
92
64
  type RunResult = Record<string, JourneyResult>;
93
65
 
94
- type HookType = 'beforeAll' | 'afterAll';
95
- export type SuiteHooks = Record<HookType, Array<HooksCallback>>;
96
-
97
- interface Events {
98
- start: { numJourneys: number };
99
- 'journey:register': {
100
- journey: Journey;
101
- };
102
- 'journey:start': {
103
- journey: Journey;
104
- timestamp: number;
105
- params: Params;
106
- };
107
- 'journey:end': BaseContext &
108
- JourneyResult &
109
- PluginOutput & {
110
- journey: Journey;
111
- options: RunOptions;
112
- };
113
- 'journey:end:reported': unknown;
114
- 'step:start': { journey: Journey; step: Step };
115
- 'step:end': StepResult & {
116
- start: number;
117
- end: number;
118
- journey: Journey;
119
- step: Step;
120
- };
121
- end: unknown;
122
- }
123
-
124
- export default interface Runner {
125
- on<K extends keyof Events>(name: K, listener: (v: Events[K]) => void): this;
126
- emit<K extends keyof Events>(event: K, args: Events[K]): boolean;
127
- }
128
-
129
- export default class Runner extends EventEmitter {
130
- active = false;
131
- currentJourney?: Journey = null;
66
+ export default class Runner {
67
+ #active = false;
68
+ #reporter: Reporter;
69
+ #currentJourney?: Journey = null;
132
70
  journeys: Journey[] = [];
133
71
  hooks: SuiteHooks = { beforeAll: [], afterAll: [] };
134
72
  hookError: Error | undefined;
73
+ monitor?: Monitor;
135
74
  static screenshotPath = join(CACHE_PATH, 'screenshots');
136
75
 
137
76
  static async createContext(options: RunOptions): Promise<JourneyContext> {
138
- const start = monotonicTimeInSeconds();
139
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();
140
83
  const pluginManager = await Gatherer.beginRecording(driver, options);
141
84
  /**
142
85
  * For each journey we create the screenshots folder for
@@ -152,11 +95,11 @@ export default class Runner extends EventEmitter {
152
95
  }
153
96
 
154
97
  async captureScreenshot(page: Driver['page'], step: Step) {
155
- await page.waitForLoadState('load');
156
98
  const buffer = await page
157
99
  .screenshot({
158
100
  type: 'jpeg',
159
101
  quality: 80,
102
+ timeout: 5000,
160
103
  })
161
104
  .catch(() => {});
162
105
  /**
@@ -166,23 +109,50 @@ export default class Runner extends EventEmitter {
166
109
  */
167
110
  if (buffer) {
168
111
  const fileName = `${generateUniqueId()}.json`;
112
+ const screenshot: Screenshot = {
113
+ step,
114
+ timestamp: getTimestamp(),
115
+ data: buffer.toString('base64'),
116
+ };
169
117
  await writeFile(
170
118
  join(Runner.screenshotPath, fileName),
171
- JSON.stringify({
172
- step,
173
- data: buffer.toString('base64'),
174
- })
119
+ JSON.stringify(screenshot)
175
120
  );
176
121
  }
177
122
  }
178
123
 
124
+ get currentJourney() {
125
+ return this.#currentJourney;
126
+ }
127
+
179
128
  addHook(type: HookType, callback: HooksCallback) {
180
129
  this.hooks[type].push(callback);
181
130
  }
182
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
+
183
140
  addJourney(journey: Journey) {
184
141
  this.journeys.push(journey);
185
- 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 });
186
156
  }
187
157
 
188
158
  async runBeforeAllHook(args: HooksArgs) {
@@ -214,7 +184,7 @@ export default class Runner extends EventEmitter {
214
184
  status: 'succeeded',
215
185
  };
216
186
  log(`Runner: start step (${step.name})`);
217
- const { metrics, screenshots } = options;
187
+ const { metrics, screenshots, filmstrips, trace } = options;
218
188
  const { driver, pluginManager } = context;
219
189
  /**
220
190
  * URL needs to be the first navigation request of any step
@@ -225,22 +195,49 @@ export default class Runner extends EventEmitter {
225
195
  if (!data.url && req.isNavigationRequest()) {
226
196
  data.url = req.url();
227
197
  }
228
- driver.page.off('request', captureUrl);
198
+ driver.context.off('request', captureUrl);
229
199
  };
230
- driver.page.on('request', captureUrl);
200
+ driver.context.on('request', captureUrl);
201
+
231
202
  try {
203
+ /**
204
+ * Set up plugin manager context and also register
205
+ * step level plugins
206
+ */
207
+ const traceEnabled = trace || filmstrips;
232
208
  pluginManager.onStep(step);
209
+ traceEnabled && (await pluginManager.start('trace'));
210
+ // call the step definition
233
211
  await step.callback();
212
+ /**
213
+ * Collect all step level metrics
214
+ */
234
215
  if (metrics) {
235
- 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);
236
223
  }
237
224
  } catch (error) {
238
225
  data.status = 'failed';
239
226
  data.error = error;
240
227
  } finally {
241
- data.url ??= driver.page.url();
242
- if (screenshots && screenshots !== 'off') {
243
- await this.captureScreenshot(driver.page, step);
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
+ }
244
241
  }
245
242
  }
246
243
  log(`Runner: end step (${step.name})`);
@@ -256,7 +253,7 @@ export default class Runner extends EventEmitter {
256
253
  let skipStep = false;
257
254
  for (const step of journey.steps) {
258
255
  const start = monotonicTimeInSeconds();
259
- this.emit('step:start', { journey, step });
256
+ this.#reporter?.onStepStart?.(journey, step);
260
257
  let data: StepResult = { status: 'succeeded' };
261
258
  if (skipStep) {
262
259
  data.status = 'skipped';
@@ -267,9 +264,7 @@ export default class Runner extends EventEmitter {
267
264
  */
268
265
  if (data.error) skipStep = true;
269
266
  }
270
- this.emit('step:end', {
271
- journey,
272
- step,
267
+ this.#reporter?.onStepEnd?.(journey, step, {
273
268
  start,
274
269
  end: monotonicTimeInSeconds(),
275
270
  ...data,
@@ -283,12 +278,16 @@ export default class Runner extends EventEmitter {
283
278
  }
284
279
 
285
280
  registerJourney(journey: Journey, context: JourneyContext) {
286
- this.currentJourney = journey;
281
+ this.#currentJourney = journey;
287
282
  const timestamp = getTimestamp();
288
283
  const { params } = context;
289
- this.emit('journey:start', { journey, timestamp, params });
284
+ this.#reporter?.onJourneyStart?.(journey, {
285
+ timestamp,
286
+ params,
287
+ });
290
288
  /**
291
- * Load and register the steps for the current journey
289
+ * Exeucute the journey callback which would
290
+ * register the steps for the current journey
292
291
  */
293
292
  journey.callback({ ...context.driver, params });
294
293
  }
@@ -298,25 +297,19 @@ export default class Runner extends EventEmitter {
298
297
  result: JourneyContext & JourneyResult,
299
298
  options: RunOptions
300
299
  ) {
300
+ const end = monotonicTimeInSeconds();
301
301
  const { pluginManager, start, status, error } = result;
302
302
  const pluginOutput = await pluginManager.output();
303
- this.emit('journey:end', {
304
- journey,
303
+ await this.#reporter?.onJourneyEnd?.(journey, {
305
304
  status,
306
305
  error,
307
306
  start,
308
- end: monotonicTimeInSeconds(),
307
+ end,
308
+ timestamp: getTimestamp(),
309
309
  options,
310
310
  ...pluginOutput,
311
311
  browserconsole: status == 'failed' ? pluginOutput.browserconsole : [],
312
312
  });
313
- /**
314
- * Wait for the all the reported events to be consumed aschronously
315
- * by reporter.
316
- */
317
- if (options.reporter === 'json') {
318
- await once(this, 'journey:end:reported');
319
- }
320
313
  // clear screenshots cache after each journey
321
314
  await rm(Runner.screenshotPath, { recursive: true, force: true });
322
315
  }
@@ -326,8 +319,7 @@ export default class Runner extends EventEmitter {
326
319
  */
327
320
  async runFakeJourney(journey: Journey, options: RunOptions) {
328
321
  const start = monotonicTimeInSeconds();
329
- this.emit('journey:start', {
330
- journey,
322
+ this.#reporter.onJourneyStart?.(journey, {
331
323
  timestamp: getTimestamp(),
332
324
  params: options.params,
333
325
  });
@@ -335,25 +327,20 @@ export default class Runner extends EventEmitter {
335
327
  status: 'failed',
336
328
  error: this.hookError,
337
329
  };
338
- this.emit('journey:end', {
339
- journey,
330
+ await this.#reporter.onJourneyEnd?.(journey, {
331
+ timestamp: getTimestamp(),
340
332
  start,
341
333
  options,
342
334
  end: monotonicTimeInSeconds(),
343
335
  ...result,
344
336
  });
345
- if (options.reporter === 'json') {
346
- await once(this, 'journey:end:reported');
347
- }
348
337
  return result;
349
338
  }
350
339
 
351
340
  async runJourney(journey: Journey, options: RunOptions) {
352
- const result: JourneyResult = {
353
- status: 'succeeded',
354
- };
355
- log(`Runner: start journey (${journey.name})`);
341
+ const result: JourneyResult = { status: 'succeeded' };
356
342
  const context = await Runner.createContext(options);
343
+ log(`Runner: start journey (${journey.name})`);
357
344
  try {
358
345
  this.registerJourney(journey, context);
359
346
  const hookArgs = {
@@ -384,28 +371,56 @@ export default class Runner extends EventEmitter {
384
371
  }
385
372
 
386
373
  async init(options: RunOptions) {
387
- const { reporter, outfd } = options;
388
- /**
389
- * Set up the corresponding reporter and fallback
390
- */
391
- const Reporter =
392
- typeof reporter === 'function'
393
- ? reporter
394
- : reporters[reporter] || reporters['default'];
395
- new Reporter(this, { fd: outfd });
396
- this.emit('start', { numJourneys: this.journeys.length });
374
+ this.setReporter(options);
375
+ this.#reporter.onStart?.({
376
+ numJourneys: this.journeys.length,
377
+ networkConditions: options.networkConditions,
378
+ });
397
379
  /**
398
380
  * Set up the directory for caching screenshots
399
381
  */
400
382
  await mkdir(CACHE_PATH, { recursive: true });
401
383
  }
402
384
 
385
+ buildMonitors(options: RunOptions) {
386
+ /**
387
+ * Update the global monitor configuration required for
388
+ * setting defaults
389
+ */
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
+ const monitors: Monitor[] = [];
401
+ for (const journey of this.journeys) {
402
+ if (!journey.isMatch(match, tags)) {
403
+ continue;
404
+ }
405
+ this.#currentJourney = journey;
406
+ /**
407
+ * Execute dummy callback to get all monitor specific
408
+ * configurations for the current journey
409
+ */
410
+ journey.callback({ params: options.params } as any);
411
+ journey.monitor.update(this.monitor?.config);
412
+ journey.monitor.validate();
413
+ monitors.push(journey.monitor);
414
+ }
415
+ return monitors;
416
+ }
417
+
403
418
  async run(options: RunOptions) {
404
419
  const result: RunResult = {};
405
- if (this.active) {
420
+ if (this.#active) {
406
421
  return result;
407
422
  }
408
- this.active = true;
423
+ this.#active = true;
409
424
  log(`Runner: run ${this.journeys.length} journeys`);
410
425
  this.init(options);
411
426
  await this.runBeforeAllHook({
@@ -419,7 +434,7 @@ export default class Runner extends EventEmitter {
419
434
  * Used by heartbeat to gather all registered journeys
420
435
  */
421
436
  if (dryRun) {
422
- this.emit('journey:register', { journey });
437
+ this.#reporter.onJourneyRegister?.(journey);
423
438
  continue;
424
439
  }
425
440
  if (!journey.isMatch(match, tags)) {
@@ -440,14 +455,14 @@ export default class Runner extends EventEmitter {
440
455
  }
441
456
 
442
457
  async reset() {
458
+ this.#currentJourney = null;
459
+ this.journeys = [];
460
+ this.#active = false;
443
461
  /**
444
462
  * Clear all cache data stored for post processing by
445
463
  * the current synthetic agent run
446
464
  */
447
465
  await rm(CACHE_PATH, { recursive: true, force: true });
448
- this.currentJourney = null;
449
- this.journeys = [];
450
- this.active = false;
451
- this.emit('end', {});
466
+ await this.#reporter?.onEnd?.();
452
467
  }
453
468
  }
@@ -0,0 +1,160 @@
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
+ import { addHook } from 'pirates';
27
+ import {
28
+ transformSync,
29
+ Loader,
30
+ CommonOptions,
31
+ TransformOptions,
32
+ } from 'esbuild';
33
+ import path from 'path';
34
+ import sourceMapSupport from 'source-map-support';
35
+
36
+ // Cache that holds the sourcemap content for each file
37
+ const sourceMaps: Map<string, string> = new Map();
38
+
39
+ // Register the source-map-support library to resolve the sourcemaps
40
+ // for the files that are transpiled by esbuild
41
+ /**
42
+ * Register the source-map-support library to resolve the sourcemaps
43
+ * for the files that are transpiled by esbuild and cache the sourcemaps
44
+ * for each file.
45
+ *
46
+ * Caching the sourcemap file is important, as it is used by the Synthetics agent
47
+ * to resolve the stack traces of journey, monitor and step functions.
48
+ */
49
+ sourceMapSupport.install({
50
+ environment: 'node',
51
+ handleUncaughtExceptions: false,
52
+ retrieveSourceMap(source) {
53
+ if (!sourceMaps.has(source)) return null;
54
+ return {
55
+ map: JSON.parse(sourceMaps.get(source)),
56
+ url: source,
57
+ };
58
+ },
59
+ });
60
+
61
+ /**
62
+ * Default list of files and corresponding loaders we support
63
+ * while pushing project based monitors
64
+ */
65
+ const LOADERS: Record<string, Loader> = {
66
+ '.ts': 'ts',
67
+ '.js': 'js',
68
+ '.mjs': 'js',
69
+ '.cjs': 'js',
70
+ };
71
+
72
+ const getLoader = (filename: string) => {
73
+ const ext = path.extname(filename);
74
+ return LOADERS[ext] || 'default';
75
+ };
76
+
77
+ export function commonOptions(): CommonOptions {
78
+ /**
79
+ * We are not minifying the code as we want it to be in sync with previous
80
+ * transformation phase and introduce it later.
81
+ */
82
+ return {
83
+ minify: false,
84
+ minifyIdentifiers: false,
85
+ minifySyntax: false,
86
+ minifyWhitespace: false,
87
+ sourcemap: 'both',
88
+ sourcesContent: false,
89
+ platform: 'node',
90
+ logLevel: 'silent',
91
+ format: 'cjs',
92
+ target: `node${process.version.slice(1)}`,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Transform the given code using esbuild and save the corresponding
98
+ * map file in memory to be retrived later.
99
+ */
100
+ export function transform(
101
+ code: string,
102
+ filename: string,
103
+ options: TransformOptions = {}
104
+ ) {
105
+ const result = transformSync(code, {
106
+ ...commonOptions(),
107
+ sourcefile: filename,
108
+ loader: getLoader(filename),
109
+ /**
110
+ * Add this only for the transformation phase, using it on
111
+ * bundling phase would disable tree shaking and uncessary bloat
112
+ *
113
+ * Ensures backwards compatability with tsc's implicit strict behaviour
114
+ */
115
+ tsconfigRaw: {
116
+ compilerOptions: {
117
+ alwaysStrict: true,
118
+ },
119
+ },
120
+ ...options,
121
+ });
122
+
123
+ const warnings = result.warnings;
124
+ if (warnings && warnings.length > 0) {
125
+ for (const warning of warnings) {
126
+ console.log(warning.location);
127
+ console.log(warning.text);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Cache the sourcemap contents in memory, so we can look it up
133
+ * later when we try to resolve the sourcemap for a given file.
134
+ *
135
+ * Every time the synthetics agent is started, we register the source-map-support
136
+ * library to resolve the sourcemaps for the files that are transpiled by esbuild.
137
+ */
138
+ if (result.map) {
139
+ sourceMaps.set(filename, result.map);
140
+ }
141
+ return result;
142
+ }
143
+
144
+ /**
145
+ * Install the pirates hook to transform the code on the fly
146
+ * for all of the imported files.
147
+ */
148
+ export function installTransform() {
149
+ const revertPirates = addHook(
150
+ (source: string, filename: string) => {
151
+ const { code } = transform(source, filename);
152
+ return code;
153
+ },
154
+ { exts: ['.ts', '.js', '.mjs', '.cjs'] }
155
+ );
156
+
157
+ return () => {
158
+ revertPirates();
159
+ };
160
+ }