@elastic/synthetics 1.4.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.
- package/dist/bundles/lib/index.js +432 -0
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/common_types.d.ts +12 -2
- package/dist/common_types.d.ts.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -1
- package/dist/config.js.map +1 -1
- package/dist/core/browser-service.js +2 -2
- package/dist/core/browser-service.js.map +1 -1
- package/dist/core/expect.d.ts +1 -152
- package/dist/core/expect.d.ts.map +1 -1
- package/dist/core/expect.js +14 -5
- package/dist/core/expect.js.map +1 -1
- package/dist/core/gatherer.d.ts +1 -1
- package/dist/core/gatherer.d.ts.map +1 -1
- package/dist/core/gatherer.js +5 -5
- package/dist/core/gatherer.js.map +1 -1
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +8 -7
- package/dist/core/runner.js.map +1 -1
- package/dist/dsl/journey.d.ts +1 -1
- package/dist/dsl/journey.d.ts.map +1 -1
- package/dist/dsl/monitor.d.ts +4 -0
- package/dist/dsl/monitor.d.ts.map +1 -1
- package/dist/dsl/monitor.js.map +1 -1
- package/dist/helpers.d.ts +0 -5
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +1 -24
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/options.d.ts +2 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +13 -4
- package/dist/options.js.map +1 -1
- package/dist/plugins/browser-console.d.ts +3 -2
- package/dist/plugins/browser-console.d.ts.map +1 -1
- package/dist/plugins/browser-console.js +33 -7
- package/dist/plugins/browser-console.js.map +1 -1
- package/dist/plugins/network.d.ts +1 -0
- package/dist/plugins/network.d.ts.map +1 -1
- package/dist/plugins/network.js +15 -2
- package/dist/plugins/network.js.map +1 -1
- package/dist/push/kibana_api.d.ts.map +1 -1
- package/dist/push/kibana_api.js +1 -1
- package/dist/push/kibana_api.js.map +1 -1
- package/dist/push/monitor.d.ts.map +1 -1
- package/dist/push/monitor.js +10 -2
- package/dist/push/monitor.js.map +1 -1
- package/dist/push/utils.js +1 -1
- package/dist/push/utils.js.map +1 -1
- package/dist/reporters/base.d.ts +3 -0
- package/dist/reporters/base.d.ts.map +1 -1
- package/dist/reporters/base.js +20 -22
- package/dist/reporters/base.js.map +1 -1
- package/dist/reporters/index.d.ts +1 -0
- package/dist/reporters/index.d.ts.map +1 -1
- package/dist/reporters/index.js.map +1 -1
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +29 -1
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/junit.d.ts.map +1 -1
- package/dist/reporters/junit.js +4 -4
- package/dist/reporters/junit.js.map +1 -1
- package/dist/reporters/reporter-util.d.ts +35 -0
- package/dist/reporters/reporter-util.d.ts.map +1 -0
- package/dist/reporters/reporter-util.js +192 -0
- package/dist/reporters/reporter-util.js.map +1 -0
- package/package.json +15 -7
- package/src/cli.ts +3 -3
- package/src/common_types.ts +15 -2
- package/src/config.ts +12 -1
- package/src/core/browser-service.ts +1 -1
- package/src/core/expect.ts +13 -191
- package/src/core/gatherer.ts +1 -1
- package/src/core/runner.ts +15 -9
- package/src/dsl/journey.ts +1 -1
- package/src/dsl/monitor.ts +4 -0
- package/src/helpers.ts +0 -24
- package/src/index.ts +1 -1
- package/src/options.ts +16 -4
- package/src/plugins/browser-console.ts +39 -8
- package/src/plugins/network.ts +16 -3
- package/src/push/kibana_api.ts +4 -3
- package/src/push/monitor.ts +13 -2
- package/src/push/utils.ts +1 -1
- package/src/reporters/base.ts +26 -33
- package/src/reporters/index.ts +5 -1
- package/src/reporters/json.ts +33 -3
- package/src/reporters/junit.ts +5 -5
- package/src/reporters/reporter-util.ts +217 -0
package/src/plugins/network.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { Page, Request, Response } from 'playwright-
|
|
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
|
|
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
|
|
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/kibana_api.ts
CHANGED
|
@@ -124,8 +124,8 @@ export async function bulkDeleteMonitors(
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
type StatusResponse = {
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
138
|
+
|
|
139
|
+
return data.kibana.version;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
export type LegacyAPISchema = {
|
package/src/push/monitor.ts
CHANGED
|
@@ -242,12 +242,15 @@ export function buildMonitorFromYaml(
|
|
|
242
242
|
config['private_locations'] ||
|
|
243
243
|
config.privateLocations ||
|
|
244
244
|
options.privateLocations;
|
|
245
|
-
|
|
245
|
+
const retestOnFailure =
|
|
246
|
+
config['retest_on_failure'] ?? options.retestOnFailure;
|
|
246
247
|
const alertConfig = parseAlertConfig(config, options.alert);
|
|
248
|
+
|
|
247
249
|
const mon = new Monitor({
|
|
248
250
|
locations: options.locations,
|
|
249
251
|
tags: options.tags,
|
|
250
|
-
...config,
|
|
252
|
+
...normalizeConfig(config),
|
|
253
|
+
retestOnFailure,
|
|
251
254
|
privateLocations,
|
|
252
255
|
schedule: schedule || options.schedule,
|
|
253
256
|
alert: alertConfig,
|
|
@@ -263,6 +266,14 @@ export function buildMonitorFromYaml(
|
|
|
263
266
|
return mon;
|
|
264
267
|
}
|
|
265
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
|
+
|
|
266
277
|
export const parseAlertConfig = (
|
|
267
278
|
config: MonitorConfig,
|
|
268
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/
|
|
67
|
+
return `${url}/s/${options.space}/api/stats`;
|
|
68
68
|
case 'bulk_get':
|
|
69
69
|
case 'bulk_update':
|
|
70
70
|
case 'bulk_delete':
|
package/src/reporters/base.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
75
|
+
const message = `${symbols[status]} Step: '${step.name}' ${status} ${gray(
|
|
76
|
+
'(' + renderDuration((end - start) * 1000) + ' ms)'
|
|
77
|
+
)}`;
|
|
94
78
|
this.write(indent(message));
|
|
95
|
-
|
|
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
|
-
|
|
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!';
|
package/src/reporters/index.ts
CHANGED
|
@@ -34,7 +34,11 @@ import {
|
|
|
34
34
|
StepEndResult,
|
|
35
35
|
} from '../common_types';
|
|
36
36
|
|
|
37
|
-
export type ReporterOptions = {
|
|
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: {
|
package/src/reporters/json.ts
CHANGED
|
@@ -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:
|
|
583
|
+
error: formatJSONError(error, type),
|
|
554
584
|
url,
|
|
555
585
|
package_version: version,
|
|
556
586
|
});
|
package/src/reporters/junit.ts
CHANGED
|
@@ -31,8 +31,9 @@ import {
|
|
|
31
31
|
StepEndResult,
|
|
32
32
|
} from '../common_types';
|
|
33
33
|
import { Journey, Step } from '../dsl';
|
|
34
|
-
import {
|
|
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') {
|
|
@@ -0,0 +1,217 @@
|
|
|
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 { fileURLToPath } from 'url';
|
|
27
|
+
import { resolve, sep } from 'path';
|
|
28
|
+
import { readFileSync } from 'fs';
|
|
29
|
+
import { inspect } from 'util';
|
|
30
|
+
import { gray } from 'kleur/colors';
|
|
31
|
+
import { codeFrameColumns } from '@babel/code-frame';
|
|
32
|
+
import StackUtils from 'stack-utils';
|
|
33
|
+
import { Location, StackFrame, TestError } from '../common_types';
|
|
34
|
+
|
|
35
|
+
const stackUtils = new StackUtils({ internals: StackUtils.nodeInternals() });
|
|
36
|
+
|
|
37
|
+
const SYNTHETICS_PATH = resolve(__dirname, '..', '..');
|
|
38
|
+
const PW_CORE_PATH = require.resolve('playwright-core');
|
|
39
|
+
|
|
40
|
+
function prepareStackFrame(line: string): StackFrame {
|
|
41
|
+
const frame = stackUtils.parseLine(line);
|
|
42
|
+
if (!frame) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// ignore node internals
|
|
46
|
+
if (frame.file?.startsWith('internal') || frame.file?.startsWith('node:')) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// handle relative URLs
|
|
51
|
+
let file = frame.file?.startsWith('file://')
|
|
52
|
+
? fileURLToPath(frame.file)
|
|
53
|
+
: resolve(process.cwd(), frame.file);
|
|
54
|
+
|
|
55
|
+
// ignore node_modules
|
|
56
|
+
if (file.includes(`${sep}node_modules${sep}`)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// filter library and PW files
|
|
61
|
+
if (!filterLibInternals(file)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// When we bundle the journeys, we store the absolute path of the journey
|
|
66
|
+
// files and write the sourcemap relative to these files. When we try to
|
|
67
|
+
// extract the sourcemap file location, the stacktrace will be relatively
|
|
68
|
+
// resolved to these files which would be under `journeys` folder. So we strip
|
|
69
|
+
// these extra `journeys` from the path to get the correct file location.
|
|
70
|
+
if (frame.file?.includes('journeys/journeys')) {
|
|
71
|
+
file = file.replace('journeys/journeys', 'journeys');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
file,
|
|
76
|
+
line: frame.line || 0,
|
|
77
|
+
column: frame.column || 0,
|
|
78
|
+
function: frame.function,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function filterLibInternals(file: string) {
|
|
83
|
+
// To ignore filtering the stack trace on tests
|
|
84
|
+
if (process.env.TEST_OVERRIDE) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (file.startsWith(SYNTHETICS_PATH)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
// should have been filtered by node_modules, but just in case
|
|
91
|
+
if (file.startsWith(PW_CORE_PATH)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function constructStackFromFrames(frames: StackFrame[]) {
|
|
97
|
+
const stackLines: string[] = [];
|
|
98
|
+
for (const frame of frames) {
|
|
99
|
+
stackLines.push(
|
|
100
|
+
` at ${frame.function ? frame.function + ' ' : ''}(${frame.file}:${
|
|
101
|
+
frame.line
|
|
102
|
+
}:${frame.column})`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return stackLines;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function prepareError(error: Error) {
|
|
109
|
+
const stack = error.stack;
|
|
110
|
+
const lines = stack.split('\n');
|
|
111
|
+
let startAt = lines.findIndex(line => line.startsWith(' at '));
|
|
112
|
+
if (startAt === -1) {
|
|
113
|
+
startAt = lines.length;
|
|
114
|
+
}
|
|
115
|
+
const message = lines.slice(0, startAt).join('\n');
|
|
116
|
+
const stackLines = lines.slice(startAt);
|
|
117
|
+
// figure out the location of the journey/step that throws the error and
|
|
118
|
+
// correct stack line corresponding to that error
|
|
119
|
+
const stackFrames: StackFrame[] = [];
|
|
120
|
+
for (const line of stackLines) {
|
|
121
|
+
const frame = prepareStackFrame(line);
|
|
122
|
+
if (!frame || !frame.file) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
stackFrames.push(frame);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let location: Location | undefined;
|
|
129
|
+
if (stackFrames.length) {
|
|
130
|
+
const frame = stackFrames[0];
|
|
131
|
+
location = {
|
|
132
|
+
file: frame.file,
|
|
133
|
+
column: frame.column || 0,
|
|
134
|
+
line: frame.line || 0,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
message,
|
|
140
|
+
stack: constructStackFromFrames(stackFrames).join('\n'),
|
|
141
|
+
location,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function highLightSource(location: Location): string {
|
|
146
|
+
if (!location) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const source = readFileSync(location.file, 'utf-8');
|
|
152
|
+
const code = codeFrameColumns(
|
|
153
|
+
source,
|
|
154
|
+
{ start: location },
|
|
155
|
+
{ highlightCode: true }
|
|
156
|
+
);
|
|
157
|
+
return code;
|
|
158
|
+
} catch (_) {
|
|
159
|
+
// ignore error
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// serializeError prefers receiving proper Errors, but since at runtime
|
|
164
|
+
// non Error exceptions can be thrown, it tolerates though. The
|
|
165
|
+
// redundant type Error | any expresses that.
|
|
166
|
+
export function serializeError(
|
|
167
|
+
error: Error | any,
|
|
168
|
+
highlightCode = true
|
|
169
|
+
): TestError {
|
|
170
|
+
if (!(error instanceof Error) || !error.stack) {
|
|
171
|
+
return { message: `thrown: ${inspect(error)}` };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const testErr: TestError = { message: error.message };
|
|
175
|
+
const { message, stack, location } = prepareError(error);
|
|
176
|
+
testErr.message = message;
|
|
177
|
+
if (stack) {
|
|
178
|
+
testErr.stack = stack;
|
|
179
|
+
}
|
|
180
|
+
if (location && highlightCode) {
|
|
181
|
+
testErr.location = location;
|
|
182
|
+
testErr.source = highLightSource(testErr.location);
|
|
183
|
+
}
|
|
184
|
+
return testErr;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function renderError(error: TestError) {
|
|
188
|
+
const summary = [];
|
|
189
|
+
// push empty string to add a new line before rendering error
|
|
190
|
+
summary.push('');
|
|
191
|
+
summary.push(error.message);
|
|
192
|
+
|
|
193
|
+
if (error.source) {
|
|
194
|
+
summary.push('');
|
|
195
|
+
summary.push(error.source);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (error.stack) {
|
|
199
|
+
summary.push('');
|
|
200
|
+
summary.push(gray(error.stack));
|
|
201
|
+
}
|
|
202
|
+
summary.join('');
|
|
203
|
+
|
|
204
|
+
return summary.join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// strip color codes and ansi escape codes
|
|
208
|
+
const ansiRegex = new RegExp(
|
|
209
|
+
[
|
|
210
|
+
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
211
|
+
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))',
|
|
212
|
+
].join('|'),
|
|
213
|
+
'g'
|
|
214
|
+
);
|
|
215
|
+
export function stripAnsiCodes(msg = '') {
|
|
216
|
+
return msg.replace(ansiRegex, '');
|
|
217
|
+
}
|