@elastic/synthetics 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/bundles/lib/index.js +432 -0
  2. package/dist/cli.js +23 -14
  3. package/dist/cli.js.map +1 -1
  4. package/dist/common_types.d.ts +51 -40
  5. package/dist/common_types.d.ts.map +1 -1
  6. package/dist/config.d.ts +2 -2
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +7 -1
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/browser-service.js +2 -2
  11. package/dist/core/browser-service.js.map +1 -1
  12. package/dist/core/expect.d.ts +1 -152
  13. package/dist/core/expect.d.ts.map +1 -1
  14. package/dist/core/expect.js +14 -5
  15. package/dist/core/expect.js.map +1 -1
  16. package/dist/core/gatherer.d.ts +1 -1
  17. package/dist/core/gatherer.d.ts.map +1 -1
  18. package/dist/core/gatherer.js +5 -5
  19. package/dist/core/gatherer.js.map +1 -1
  20. package/dist/core/runner.d.ts +4 -4
  21. package/dist/core/runner.d.ts.map +1 -1
  22. package/dist/core/runner.js +8 -7
  23. package/dist/core/runner.js.map +1 -1
  24. package/dist/core/transform.d.ts +62 -2
  25. package/dist/core/transform.d.ts.map +1 -1
  26. package/dist/dsl/journey.d.ts +5 -5
  27. package/dist/dsl/journey.d.ts.map +1 -1
  28. package/dist/dsl/journey.js +0 -1
  29. package/dist/dsl/journey.js.map +1 -1
  30. package/dist/dsl/monitor.d.ts +8 -3
  31. package/dist/dsl/monitor.d.ts.map +1 -1
  32. package/dist/dsl/monitor.js +1 -0
  33. package/dist/dsl/monitor.js.map +1 -1
  34. package/dist/formatter/javascript.d.ts +6 -6
  35. package/dist/formatter/javascript.d.ts.map +1 -1
  36. package/dist/generator/index.d.ts +1 -1
  37. package/dist/generator/index.d.ts.map +1 -1
  38. package/dist/generator/index.js +1 -1
  39. package/dist/generator/index.js.map +1 -1
  40. package/dist/helpers.d.ts +1 -6
  41. package/dist/helpers.d.ts.map +1 -1
  42. package/dist/helpers.js +1 -24
  43. package/dist/helpers.js.map +1 -1
  44. package/dist/index.d.ts +1 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/locations/index.d.ts +13 -5
  47. package/dist/locations/index.d.ts.map +1 -1
  48. package/dist/locations/index.js +30 -14
  49. package/dist/locations/index.js.map +1 -1
  50. package/dist/options.d.ts +5 -2
  51. package/dist/options.d.ts.map +1 -1
  52. package/dist/options.js +20 -3
  53. package/dist/options.js.map +1 -1
  54. package/dist/plugins/browser-console.d.ts +3 -2
  55. package/dist/plugins/browser-console.d.ts.map +1 -1
  56. package/dist/plugins/browser-console.js +33 -7
  57. package/dist/plugins/browser-console.js.map +1 -1
  58. package/dist/plugins/network.d.ts +1 -0
  59. package/dist/plugins/network.d.ts.map +1 -1
  60. package/dist/plugins/network.js +15 -2
  61. package/dist/plugins/network.js.map +1 -1
  62. package/dist/plugins/plugin-manager.d.ts +3 -3
  63. package/dist/plugins/plugin-manager.d.ts.map +1 -1
  64. package/dist/plugins/tracing.d.ts +1 -1
  65. package/dist/plugins/tracing.d.ts.map +1 -1
  66. package/dist/push/index.d.ts +1 -1
  67. package/dist/push/index.d.ts.map +1 -1
  68. package/dist/push/index.js +7 -6
  69. package/dist/push/index.js.map +1 -1
  70. package/dist/push/kibana_api.d.ts +5 -5
  71. package/dist/push/kibana_api.d.ts.map +1 -1
  72. package/dist/push/kibana_api.js +1 -1
  73. package/dist/push/kibana_api.js.map +1 -1
  74. package/dist/push/monitor.d.ts +3 -3
  75. package/dist/push/monitor.d.ts.map +1 -1
  76. package/dist/push/monitor.js +22 -9
  77. package/dist/push/monitor.js.map +1 -1
  78. package/dist/push/request.d.ts +2 -2
  79. package/dist/push/request.d.ts.map +1 -1
  80. package/dist/push/utils.d.ts +3 -3
  81. package/dist/push/utils.d.ts.map +1 -1
  82. package/dist/push/utils.js +1 -1
  83. package/dist/push/utils.js.map +1 -1
  84. package/dist/reporters/base.d.ts +3 -0
  85. package/dist/reporters/base.d.ts.map +1 -1
  86. package/dist/reporters/base.js +20 -22
  87. package/dist/reporters/base.js.map +1 -1
  88. package/dist/reporters/index.d.ts +4 -3
  89. package/dist/reporters/index.d.ts.map +1 -1
  90. package/dist/reporters/index.js.map +1 -1
  91. package/dist/reporters/json.d.ts +5 -5
  92. package/dist/reporters/json.d.ts.map +1 -1
  93. package/dist/reporters/json.js +29 -1
  94. package/dist/reporters/json.js.map +1 -1
  95. package/dist/reporters/junit.d.ts.map +1 -1
  96. package/dist/reporters/junit.js +4 -4
  97. package/dist/reporters/junit.js.map +1 -1
  98. package/dist/reporters/reporter-util.d.ts +35 -0
  99. package/dist/reporters/reporter-util.d.ts.map +1 -0
  100. package/dist/reporters/reporter-util.js +192 -0
  101. package/dist/reporters/reporter-util.js.map +1 -0
  102. package/dist/sdk/trace-processor.d.ts +4 -4
  103. package/dist/sdk/trace-processor.d.ts.map +1 -1
  104. package/package.json +20 -11
  105. package/src/cli.ts +23 -22
  106. package/src/common_types.ts +17 -3
  107. package/src/config.ts +12 -1
  108. package/src/core/browser-service.ts +1 -1
  109. package/src/core/expect.ts +13 -191
  110. package/src/core/gatherer.ts +1 -1
  111. package/src/core/runner.ts +15 -9
  112. package/src/dsl/journey.ts +1 -2
  113. package/src/dsl/monitor.ts +5 -0
  114. package/src/generator/index.ts +2 -1
  115. package/src/helpers.ts +0 -24
  116. package/src/index.ts +3 -1
  117. package/src/locations/index.ts +37 -12
  118. package/src/options.ts +28 -3
  119. package/src/plugins/browser-console.ts +39 -8
  120. package/src/plugins/network.ts +16 -3
  121. package/src/push/index.ts +9 -6
  122. package/src/push/kibana_api.ts +4 -3
  123. package/src/push/monitor.ts +26 -9
  124. package/src/push/utils.ts +3 -3
  125. package/src/reporters/base.ts +26 -33
  126. package/src/reporters/index.ts +5 -1
  127. package/src/reporters/json.ts +33 -3
  128. package/src/reporters/junit.ts +5 -5
  129. package/src/reporters/reporter-util.ts +217 -0
@@ -23,13 +23,16 @@
23
23
  *
24
24
  */
25
25
 
26
- import type { ConsoleMessage } from 'playwright-core';
27
- import { BrowserMessage, Driver } from '../common_types';
26
+ import type { ConsoleMessage, WebError } from 'playwright-core';
27
+ import { BrowserMessage, Driver, StatusValue } from '../common_types';
28
28
  import { log } from '../core/logger';
29
29
  import { Step } from '../dsl';
30
30
  import { getTimestamp } from '../helpers';
31
31
 
32
- const defaultMessageLimit = 1000;
32
+ const DEFAULT_MSG_LIMIt = 1000;
33
+ const SUCCESSFUL_MSG_LIMIT = 100;
34
+
35
+ const allowedTypes = ['error', 'warning', 'log'];
33
36
 
34
37
  export class BrowserConsole {
35
38
  private messages: BrowserMessage[] = [];
@@ -42,7 +45,7 @@ export class BrowserConsole {
42
45
  return;
43
46
  }
44
47
  const type = msg.type();
45
- if (type === 'error' || type === 'warning') {
48
+ if (allowedTypes.includes(type)) {
46
49
  const { name, index } = this._currentStep;
47
50
  this.messages.push({
48
51
  timestamp: getTimestamp(),
@@ -55,11 +58,12 @@ export class BrowserConsole {
55
58
  }
56
59
  };
57
60
 
58
- private pageErrorEventListener = (error: Error) => {
61
+ private webErrorListener = (webErr: WebError) => {
59
62
  if (!this._currentStep) {
60
63
  return;
61
64
  }
62
65
  const { name, index } = this._currentStep;
66
+ const error = webErr.error();
63
67
  this.messages.push({
64
68
  timestamp: getTimestamp(),
65
69
  text: error.message,
@@ -72,7 +76,7 @@ export class BrowserConsole {
72
76
  };
73
77
 
74
78
  private enforceMessagesLimit() {
75
- if (this.messages.length > defaultMessageLimit) {
79
+ if (this.messages.length > DEFAULT_MSG_LIMIt) {
76
80
  this.messages.splice(0, 1);
77
81
  }
78
82
  }
@@ -80,13 +84,40 @@ export class BrowserConsole {
80
84
  start() {
81
85
  log(`Plugins: started collecting console events`);
82
86
  this.driver.context.on('console', this.consoleEventListener);
83
- this.driver.page.on('pageerror', this.pageErrorEventListener);
87
+ this.driver.context.on('weberror', this.webErrorListener);
84
88
  }
85
89
 
86
90
  stop() {
87
91
  this.driver.context.off('console', this.consoleEventListener);
88
- this.driver.page.off('pageerror', this.pageErrorEventListener);
92
+ this.driver.context.off('weberror', this.webErrorListener);
89
93
  log(`Plugins: stopped collecting console events`);
90
94
  return this.messages;
91
95
  }
92
96
  }
97
+
98
+ export function filterBrowserMessages(
99
+ messages: BrowserMessage[],
100
+ status: StatusValue
101
+ ) {
102
+ if (status == 'skipped') {
103
+ return [];
104
+ } else if (status === 'failed' || messages.length <= SUCCESSFUL_MSG_LIMIT) {
105
+ return messages;
106
+ }
107
+ // collect 100 messages from the browser console when the test is successful,
108
+ // giving priority to errors and warnings
109
+ const result = messages.filter(msg => msg.type === 'error');
110
+ if (result.length >= SUCCESSFUL_MSG_LIMIT) {
111
+ return result.slice(-SUCCESSFUL_MSG_LIMIT);
112
+ }
113
+
114
+ // collect warnings
115
+ result.push(...messages.filter(msg => msg.type === 'warning'));
116
+ if (result.length >= SUCCESSFUL_MSG_LIMIT) {
117
+ return result.slice(-SUCCESSFUL_MSG_LIMIT);
118
+ }
119
+
120
+ // collect logs
121
+ result.push(...messages.filter(msg => msg.type === 'log'));
122
+ return result.slice(-SUCCESSFUL_MSG_LIMIT);
123
+ }
@@ -23,7 +23,7 @@
23
23
  *
24
24
  */
25
25
 
26
- import { Page, Request, Response } from 'playwright-chromium';
26
+ import { Frame, Page, Request, Response } from 'playwright-core';
27
27
  import { NetworkInfo, BrowserInfo, Driver } from '../common_types';
28
28
  import { log } from '../core/logger';
29
29
  import { Step } from '../dsl';
@@ -93,6 +93,17 @@ export class NetworkManager {
93
93
  race.then(() => this._barrierPromises.delete(race));
94
94
  }
95
95
 
96
+ private _nullableFrameBarrier(req: Request): Frame | null {
97
+ try {
98
+ return req.frame();
99
+ } catch (_) {
100
+ // frame might be unavailable for certain requests if they are issued
101
+ // before the frame is created - true for navigation requests
102
+ // https://playwright.dev/docs/api/class-request#request-frame
103
+ }
104
+ return null;
105
+ }
106
+
96
107
  async start() {
97
108
  log(`Plugins: started collecting network events`);
98
109
  const { client, context } = this.driver;
@@ -226,7 +237,8 @@ export class NetworkManager {
226
237
  };
227
238
  this._calcTotalTime(networkEntry, timing);
228
239
 
229
- const page = request.frame().page();
240
+ const frame = this._nullableFrameBarrier(request);
241
+ const page = frame?.page();
230
242
  this._addBarrier(
231
243
  page,
232
244
  request.allHeaders().then(reqHeaders => {
@@ -278,7 +290,8 @@ export class NetworkManager {
278
290
  return;
279
291
  }
280
292
 
281
- const page = request.frame().page();
293
+ const frame = this._nullableFrameBarrier(request);
294
+ const page = frame?.page();
282
295
  this._addBarrier(
283
296
  page,
284
297
  request.sizes().then(sizes => {
package/src/push/index.ts CHANGED
@@ -73,8 +73,12 @@ export async function push(monitors: Monitor[], options: PushOptions) {
73
73
  return await pushLegacy(monitors, options);
74
74
  }
75
75
 
76
- const local = getLocalMonitors(monitors);
77
76
  const { monitors: remote } = await bulkGetMonitors(options);
77
+
78
+ progress(`bundling ${monitors.length} monitors`);
79
+ const schemas = await buildMonitorSchema(monitors, true);
80
+ const local = getLocalMonitors(schemas);
81
+
78
82
  const { newIDs, changedIDs, removedIDs, unchangedIDs } = diffMonitorHashIDs(
79
83
  local,
80
84
  remote
@@ -83,9 +87,6 @@ export async function push(monitors: Monitor[], options: PushOptions) {
83
87
 
84
88
  const updatedMonitors = new Set<string>([...changedIDs, ...newIDs]);
85
89
  if (updatedMonitors.size > 0) {
86
- const toBundle = monitors.filter(m => updatedMonitors.has(m.config.id));
87
- progress(`bundling ${toBundle.length} monitors`);
88
- const schemas = await buildMonitorSchema(toBundle, true);
89
90
  const chunks = getChunks(schemas, CHUNK_SIZE);
90
91
  for (const chunk of chunks) {
91
92
  await liveProgress(
@@ -159,7 +160,7 @@ export function formatDuplicateError(monitors: Set<Monitor>) {
159
160
 
160
161
  const INSTALLATION_HELP = `Run 'npx @elastic/synthetics init' to create project with default settings.`;
161
162
 
162
- export async function loadSettings() {
163
+ export async function loadSettings(ignoreMissing = false) {
163
164
  try {
164
165
  const config = await readConfig(process.env['NODE_ENV'] || 'development');
165
166
  // Missing config file, fake throw to capture as missing file
@@ -168,9 +169,11 @@ export async function loadSettings() {
168
169
  }
169
170
  return config.project || ({} as ProjectSettings);
170
171
  } catch (e) {
171
- throw error(`Aborted (missing synthetics config file), Project not set up correctly.
172
+ if (!ignoreMissing) {
173
+ throw error(`Aborted (missing synthetics config file), Project not set up correctly.
172
174
 
173
175
  ${INSTALLATION_HELP}`);
176
+ }
174
177
  }
175
178
  }
176
179
 
@@ -124,8 +124,8 @@ export async function bulkDeleteMonitors(
124
124
  }
125
125
 
126
126
  type StatusResponse = {
127
- version: {
128
- number: number;
127
+ kibana: {
128
+ version: string;
129
129
  };
130
130
  };
131
131
 
@@ -135,7 +135,8 @@ export async function getVersion(options: PushOptions) {
135
135
  method: 'GET',
136
136
  auth: options.auth,
137
137
  });
138
- return data.version.number;
138
+
139
+ return data.kibana.version;
139
140
  }
140
141
 
141
142
  export type LegacyAPISchema = {
@@ -110,12 +110,12 @@ export function diffMonitors(
110
110
  return result;
111
111
  }
112
112
 
113
- export function getLocalMonitors(monitors: Monitor[]) {
113
+ export function getLocalMonitors(schemas: MonitorSchema[]) {
114
114
  const data: MonitorHashID[] = [];
115
- for (const monitor of monitors) {
115
+ for (const schema of schemas) {
116
116
  data.push({
117
- journey_id: monitor.config.id,
118
- hash: monitor.hash(),
117
+ journey_id: schema.id,
118
+ hash: schema.hash,
119
119
  });
120
120
  }
121
121
  return data;
@@ -137,14 +137,20 @@ export async function buildMonitorSchema(monitors: Monitor[], isV2: boolean) {
137
137
  ...config,
138
138
  locations: translateLocation(config.locations),
139
139
  };
140
- if (isV2) {
141
- schema.hash = monitor.hash();
142
- }
140
+
143
141
  if (type === 'browser') {
144
142
  const outPath = join(bundlePath, config.name + '.zip');
145
143
  const content = await bundler.build(source.file, outPath);
144
+ monitor.setContent(content);
146
145
  Object.assign(schema, { content, filter });
147
146
  }
147
+ /**
148
+ * Generate hash only after the bundled content is created
149
+ * to capture code changes in imported files
150
+ */
151
+ if (isV2) {
152
+ schema.hash = monitor.hash();
153
+ }
148
154
  schemas.push(schema);
149
155
  }
150
156
 
@@ -236,12 +242,15 @@ export function buildMonitorFromYaml(
236
242
  config['private_locations'] ||
237
243
  config.privateLocations ||
238
244
  options.privateLocations;
239
- delete config['private_locations'];
245
+ const retestOnFailure =
246
+ config['retest_on_failure'] ?? options.retestOnFailure;
240
247
  const alertConfig = parseAlertConfig(config, options.alert);
248
+
241
249
  const mon = new Monitor({
242
250
  locations: options.locations,
243
251
  tags: options.tags,
244
- ...config,
252
+ ...normalizeConfig(config),
253
+ retestOnFailure,
245
254
  privateLocations,
246
255
  schedule: schedule || options.schedule,
247
256
  alert: alertConfig,
@@ -257,6 +266,14 @@ export function buildMonitorFromYaml(
257
266
  return mon;
258
267
  }
259
268
 
269
+ // Deletes unncessary fields from the lightweight monitor config
270
+ // that is not supported by the Kibana API
271
+ function normalizeConfig(config: MonitorConfig) {
272
+ delete config['private_locations'];
273
+ delete config['retest_on_failure'];
274
+ return config;
275
+ }
276
+
260
277
  export const parseAlertConfig = (
261
278
  config: MonitorConfig,
262
279
  gConfig?: AlertConfig
package/src/push/utils.ts CHANGED
@@ -64,7 +64,7 @@ export function generateURL(options: PushOptions, operation: Operation) {
64
64
  const url = removeTrailingSlash(options.url);
65
65
  switch (operation) {
66
66
  case 'status':
67
- return `${url}/s/${options.space}/api/status`;
67
+ return `${url}/s/${options.space}/api/stats`;
68
68
  case 'bulk_get':
69
69
  case 'bulk_update':
70
70
  case 'bulk_delete':
@@ -81,7 +81,7 @@ export function generateURL(options: PushOptions, operation: Operation) {
81
81
  /**
82
82
  * Bulk API is supported for push monitors only from 8.6.0 and above
83
83
  */
84
- export function isBulkAPISupported(version: number) {
84
+ export function isBulkAPISupported(version: string) {
85
85
  return semver.satisfies(version, '>=8.6.0');
86
86
  }
87
87
 
@@ -103,6 +103,6 @@ export function isLightweightMonitorSupported(
103
103
  * Lighweight monitor param options are supported only from
104
104
  * 8.7.2 and above
105
105
  */
106
- export function isParamOptionSupported(version: number) {
106
+ export function isParamOptionSupported(version: string) {
107
107
  return semver.satisfies(version, '>=8.7.2');
108
108
  }
@@ -24,14 +24,9 @@
24
24
  */
25
25
 
26
26
  import SonicBoom from 'sonic-boom';
27
- import { green, red, cyan } from 'kleur/colors';
28
- import {
29
- symbols,
30
- indent,
31
- now,
32
- findPWLogsIndexes,
33
- rewriteErrorStack,
34
- } from '../helpers';
27
+ import { green, red, cyan, gray } from 'kleur/colors';
28
+ import { renderError, serializeError } from './reporter-util';
29
+ import { symbols, indent, now } from '../helpers';
35
30
  import { Reporter, ReporterOptions } from '.';
36
31
  import {
37
32
  JourneyEndResult,
@@ -40,25 +35,6 @@ import {
40
35
  } from '../common_types';
41
36
  import { Journey, Step } from '../dsl';
42
37
 
43
- function renderError(error) {
44
- let output = '';
45
- const outer = indent('');
46
- const inner = indent(outer);
47
- const container = outer + '---\n';
48
- output += container;
49
- let stack = error.stack;
50
- if (stack) {
51
- output += inner + 'stack: |-\n';
52
- stack = rewriteErrorStack(stack, findPWLogsIndexes(stack));
53
- const lines = String(stack).split('\n');
54
- for (const line of lines) {
55
- output += inner + ' ' + line + '\n';
56
- }
57
- }
58
- output += container;
59
- return red(output);
60
- }
61
-
62
38
  function renderDuration(durationMs) {
63
39
  return parseInt(durationMs);
64
40
  }
@@ -66,14 +42,17 @@ function renderDuration(durationMs) {
66
42
  export default class BaseReporter implements Reporter {
67
43
  stream: SonicBoom;
68
44
  fd: number;
45
+ dryRun: boolean;
69
46
  metrics = {
70
47
  succeeded: 0,
71
48
  failed: 0,
72
49
  skipped: 0,
50
+ registered: 0,
73
51
  };
74
52
 
75
53
  constructor(options: ReporterOptions = {}) {
76
54
  this.fd = options.fd || process.stdout.fd;
55
+ this.dryRun = options.dryRun ?? false;
77
56
  /**
78
57
  * minLength is set to 1 byte to make sure we flush the
79
58
  * content even if its the last byte on the stream buffer
@@ -82,17 +61,25 @@ export default class BaseReporter implements Reporter {
82
61
  this.stream = new SonicBoom({ fd: this.fd, sync: true, minLength: 1 });
83
62
  }
84
63
 
64
+ onJourneyRegister(journey: Journey): void {
65
+ this.write(`\nJourney: ${journey.name}`);
66
+ this.metrics.registered++;
67
+ }
68
+
85
69
  onJourneyStart(journey: Journey, {}: JourneyStartResult) {
86
70
  this.write(`\nJourney: ${journey.name}`);
87
71
  }
88
72
 
89
73
  onStepEnd(_: Journey, step: Step, result: StepEndResult) {
90
74
  const { status, end, start, error } = result;
91
- const message = `${symbols[status]} Step: '${
92
- step.name
93
- }' ${status} (${renderDuration((end - start) * 1000)} ms)`;
75
+ const message = `${symbols[status]} Step: '${step.name}' ${status} ${gray(
76
+ '(' + renderDuration((end - start) * 1000) + ' ms)'
77
+ )}`;
94
78
  this.write(indent(message));
95
- error && this.write(renderError(error));
79
+ if (error) {
80
+ const message = renderError(serializeError(error));
81
+ this.write(indent(message) + '\n');
82
+ }
96
83
  this.metrics[status]++;
97
84
  }
98
85
 
@@ -105,14 +92,20 @@ export default class BaseReporter implements Reporter {
105
92
  * be executed which means the error happened in one of the hooks
106
93
  */
107
94
  if (total === 0 && error) {
108
- this.write(renderError(error));
95
+ const message = renderError(serializeError(error));
96
+ this.write(indent(message) + '\n');
109
97
  }
110
98
  }
111
99
 
112
100
  onEnd() {
113
- const { failed, succeeded, skipped } = this.metrics;
101
+ const { failed, succeeded, skipped, registered } = this.metrics;
114
102
  const total = failed + succeeded + skipped;
115
103
 
104
+ if (this.dryRun) {
105
+ this.write(`\n${registered} journey(s) registered`);
106
+ return;
107
+ }
108
+
116
109
  let message = '\n';
117
110
  if (total === 0) {
118
111
  message = 'No tests found!';
@@ -34,7 +34,11 @@ import {
34
34
  StepEndResult,
35
35
  } from '../common_types';
36
36
 
37
- export type ReporterOptions = { fd?: number; colors?: boolean };
37
+ export type ReporterOptions = {
38
+ fd?: number;
39
+ colors?: boolean;
40
+ dryRun?: boolean;
41
+ };
38
42
  export type BuiltInReporterName = 'default' | 'json' | 'junit';
39
43
  export type ReporterInstance = new (opts: ReporterOptions) => Reporter;
40
44
  export const reporters: {
@@ -28,8 +28,8 @@ import { join } from 'path';
28
28
  import sharp from 'sharp';
29
29
  import { createHash } from 'crypto';
30
30
  import BaseReporter from './base';
31
+ import { renderError, serializeError, stripAnsiCodes } from './reporter-util';
31
32
  import {
32
- formatError,
33
33
  getTimestamp,
34
34
  CACHE_PATH,
35
35
  totalist,
@@ -189,6 +189,36 @@ export function formatNetworkFields(network: NetworkInfo) {
189
189
  return { ecs, payload };
190
190
  }
191
191
 
192
+ /**
193
+ * formatJSONError formats the error in a structured format
194
+ * we restructure the error with code frame and stack trace for test errors
195
+ */
196
+ function formatJSONError(error: Error | any, type: OutputType) {
197
+ if (error == null) {
198
+ return;
199
+ }
200
+
201
+ /**
202
+ * Do not process browser errors - console.error and unhandled exceptions
203
+ */
204
+ if (type != null && type === 'journey/browserconsole') {
205
+ return {
206
+ name: error.name,
207
+ message: error.message,
208
+ stack: error.stack,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Do not highlight source for these errors and strip ANSI codes
214
+ */
215
+ return {
216
+ name: error.name,
217
+ message: stripAnsiCodes(error.message.split('\n')[0]),
218
+ stack: stripAnsiCodes(renderError(serializeError(error, false))),
219
+ };
220
+ }
221
+
192
222
  function journeyInfo(
193
223
  journey: OutputFields['journey'],
194
224
  type: OutputFields['type'],
@@ -317,7 +347,7 @@ export default class JSONReporter extends BaseReporter {
317
347
  });
318
348
  }
319
349
 
320
- onJourneyRegister(journey: Journey): void {
350
+ override onJourneyRegister(journey: Journey): void {
321
351
  this.writeJSON({
322
352
  type: 'journey/register',
323
353
  journey,
@@ -550,7 +580,7 @@ export default class JSONReporter extends BaseReporter {
550
580
  payload,
551
581
  blob,
552
582
  blob_mime,
553
- error: formatError(error),
583
+ error: formatJSONError(error, type),
554
584
  url,
555
585
  package_version: version,
556
586
  });
@@ -31,8 +31,9 @@ import {
31
31
  StepEndResult,
32
32
  } from '../common_types';
33
33
  import { Journey, Step } from '../dsl';
34
- import { formatError, indent, now } from '../helpers';
34
+ import { indent, now } from '../helpers';
35
35
  import BaseReporter from './base';
36
+ import { serializeError, stripAnsiCodes } from './reporter-util';
36
37
 
37
38
  type XMLEntry = {
38
39
  name: string;
@@ -97,14 +98,13 @@ export default class JUnitReporter extends BaseReporter {
97
98
 
98
99
  entry.attributes.tests++;
99
100
  if (status === 'failed') {
100
- const { name, message, stack } = formatError(error);
101
101
  caseEntry.children.push({
102
102
  name: 'failure',
103
103
  attributes: {
104
- message,
105
- type: name,
104
+ message: error.message,
105
+ type: error.name,
106
106
  },
107
- text: stack,
107
+ text: stripAnsiCodes(serializeError(error).stack),
108
108
  });
109
109
  entry.attributes.failures++;
110
110
  } else if (status === 'skipped') {