@elastic/synthetics 1.4.0 → 1.5.1
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/request.d.ts.map +1 -1
- package/dist/push/request.js +4 -3
- package/dist/push/request.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 +34 -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 +16 -8
- 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/request.ts +12 -4
- 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 +39 -3
- package/src/reporters/junit.ts +5 -5
- package/src/reporters/reporter-util.ts +217 -0
package/src/dsl/journey.ts
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
BrowserContext,
|
|
30
30
|
CDPSession,
|
|
31
31
|
APIRequestContext,
|
|
32
|
-
} from 'playwright-
|
|
32
|
+
} from 'playwright-core';
|
|
33
33
|
import { Step } from './step';
|
|
34
34
|
import { VoidCallback, HooksCallback, Params, Location } from '../common_types';
|
|
35
35
|
import { Monitor, MonitorConfig } from './monitor';
|
package/src/dsl/monitor.ts
CHANGED
package/src/helpers.ts
CHANGED
|
@@ -234,30 +234,6 @@ export function rewriteErrorStack(stack: string, indexes: [number, number]) {
|
|
|
234
234
|
return stack;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
// formatError prefers receiving proper Errors, but since at runtime
|
|
238
|
-
// non Error exceptions can be thrown, it tolerates though. The
|
|
239
|
-
// redundant type Error | any expresses that.
|
|
240
|
-
export function formatError(error: Error | any) {
|
|
241
|
-
if (error === undefined || error === null) {
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (!(error instanceof Error)) {
|
|
246
|
-
return {
|
|
247
|
-
message: `Error "${error}" received, with type "${typeof error}". (Do not throw exceptions without using \`new Error("my message")\`)`,
|
|
248
|
-
name: '',
|
|
249
|
-
stack: '',
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
const { name, message, stack } = error;
|
|
253
|
-
const indexes = findPWLogsIndexes(message);
|
|
254
|
-
return {
|
|
255
|
-
name,
|
|
256
|
-
message: rewriteErrorMessage(message, indexes[0]),
|
|
257
|
-
stack: rewriteErrorStack(stack, indexes),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
237
|
const cwd = process.cwd();
|
|
262
238
|
/**
|
|
263
239
|
* All the settings that are related to the Synthetics is stored
|
package/src/index.ts
CHANGED
package/src/options.ts
CHANGED
|
@@ -31,10 +31,10 @@ import { THROTTLING_WARNING_MSG, error, warn } from './helpers';
|
|
|
31
31
|
|
|
32
32
|
type Mode = 'run' | 'push';
|
|
33
33
|
|
|
34
|
-
export function normalizeOptions(
|
|
34
|
+
export async function normalizeOptions(
|
|
35
35
|
cliArgs: CliArgs,
|
|
36
36
|
mode: Mode = 'run'
|
|
37
|
-
): RunOptions {
|
|
37
|
+
): Promise<RunOptions> {
|
|
38
38
|
const options: RunOptions = {
|
|
39
39
|
...cliArgs,
|
|
40
40
|
environment: process.env['NODE_ENV'] || 'development',
|
|
@@ -85,7 +85,7 @@ export function normalizeOptions(
|
|
|
85
85
|
*/
|
|
86
86
|
const config =
|
|
87
87
|
cliArgs.config || !cliArgs.inline
|
|
88
|
-
? readConfig(options.environment, cliArgs.config)
|
|
88
|
+
? await readConfig(options.environment, cliArgs.config)
|
|
89
89
|
: {};
|
|
90
90
|
|
|
91
91
|
/**
|
|
@@ -106,7 +106,7 @@ export function normalizeOptions(
|
|
|
106
106
|
);
|
|
107
107
|
options.playwrightOptions = {
|
|
108
108
|
...playwrightOpts,
|
|
109
|
-
headless: cliArgs.headless
|
|
109
|
+
headless: getHeadlessFlag(cliArgs.headless, playwrightOpts?.headless),
|
|
110
110
|
chromiumSandbox: cliArgs.sandbox ?? playwrightOpts?.chromiumSandbox,
|
|
111
111
|
ignoreHTTPSErrors:
|
|
112
112
|
cliArgs.ignoreHttpsErrors ?? playwrightOpts?.ignoreHTTPSErrors,
|
|
@@ -140,6 +140,18 @@ export function normalizeOptions(
|
|
|
140
140
|
return options;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
export function getHeadlessFlag(
|
|
144
|
+
cliHeadless: boolean,
|
|
145
|
+
configHeadless?: boolean
|
|
146
|
+
) {
|
|
147
|
+
// if cliHeadless is false, then we don't care about configHeadless
|
|
148
|
+
if (!cliHeadless) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
// default is headless
|
|
152
|
+
return configHeadless ?? true;
|
|
153
|
+
}
|
|
154
|
+
|
|
143
155
|
export function validatePushOptions(opts: PushOptions) {
|
|
144
156
|
if (opts.tags || opts.match) {
|
|
145
157
|
throw error(`Aborted. Invalid CLI flags.
|
|
@@ -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
|
|
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
|
|
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
|
|
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 >
|
|
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.
|
|
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.
|
|
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
|
+
}
|
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/request.ts
CHANGED
|
@@ -48,7 +48,8 @@ export async function sendRequest(options: APIRequestOptions) {
|
|
|
48
48
|
'user-agent': `Elastic/Synthetics ${version}`,
|
|
49
49
|
'kbn-xsrf': 'true',
|
|
50
50
|
},
|
|
51
|
-
|
|
51
|
+
// align with the default timeout of the kibana route
|
|
52
|
+
headersTimeout: 2 * 60 * 1000,
|
|
52
53
|
});
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -56,9 +57,16 @@ export async function sendReqAndHandleError<T>(
|
|
|
56
57
|
options: APIRequestOptions
|
|
57
58
|
): Promise<T> {
|
|
58
59
|
const { statusCode, body } = await sendRequest(options);
|
|
59
|
-
return
|
|
60
|
+
return (
|
|
61
|
+
await handleError(statusCode, options.url, body)
|
|
62
|
+
).json() as Promise<T>;
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
type APIError = {
|
|
66
|
+
error: string;
|
|
67
|
+
message: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
62
70
|
// Handle bad status code errors from Kibana API and format the
|
|
63
71
|
// error message to be displayed to the user.
|
|
64
72
|
// returns the response stream if no error is found
|
|
@@ -70,9 +78,9 @@ export async function handleError(
|
|
|
70
78
|
if (statusCode === 404) {
|
|
71
79
|
throw formatNotFoundError(url, await body.text());
|
|
72
80
|
} else if (!ok(statusCode)) {
|
|
73
|
-
let parsed:
|
|
81
|
+
let parsed: APIError;
|
|
74
82
|
try {
|
|
75
|
-
parsed = await body.json();
|
|
83
|
+
parsed = (await body.json()) as APIError;
|
|
76
84
|
} catch (e) {
|
|
77
85
|
throw formatAPIError(
|
|
78
86
|
statusCode,
|
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,
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
JourneyEndResult,
|
|
53
53
|
PageMetrics,
|
|
54
54
|
} from '../common_types';
|
|
55
|
+
import { inspect } from 'util';
|
|
55
56
|
|
|
56
57
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
57
58
|
const { version, name } = require('../../package.json');
|
|
@@ -189,6 +190,41 @@ export function formatNetworkFields(network: NetworkInfo) {
|
|
|
189
190
|
return { ecs, payload };
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
/**
|
|
194
|
+
* formatJSONError formats the error in a structured format
|
|
195
|
+
* we restructure the error with code frame and stack trace for test errors
|
|
196
|
+
*/
|
|
197
|
+
function formatJSONError(error: Error | any, type: OutputType) {
|
|
198
|
+
if (error == null) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Early exit for non Error objects
|
|
203
|
+
if (!(error instanceof Error) || !error.stack) {
|
|
204
|
+
return { message: `thrown: ${inspect(error)}` };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Do not process browser errors - console.error and unhandled exceptions
|
|
209
|
+
*/
|
|
210
|
+
if (type != null && type === 'journey/browserconsole') {
|
|
211
|
+
return {
|
|
212
|
+
name: error.name,
|
|
213
|
+
message: error.message,
|
|
214
|
+
stack: error.stack,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Do not highlight source for these errors and strip ANSI codes
|
|
220
|
+
*/
|
|
221
|
+
return {
|
|
222
|
+
name: error.name,
|
|
223
|
+
message: stripAnsiCodes(error.message.split('\n')[0]),
|
|
224
|
+
stack: stripAnsiCodes(renderError(serializeError(error, false))),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
192
228
|
function journeyInfo(
|
|
193
229
|
journey: OutputFields['journey'],
|
|
194
230
|
type: OutputFields['type'],
|
|
@@ -317,7 +353,7 @@ export default class JSONReporter extends BaseReporter {
|
|
|
317
353
|
});
|
|
318
354
|
}
|
|
319
355
|
|
|
320
|
-
onJourneyRegister(journey: Journey): void {
|
|
356
|
+
override onJourneyRegister(journey: Journey): void {
|
|
321
357
|
this.writeJSON({
|
|
322
358
|
type: 'journey/register',
|
|
323
359
|
journey,
|
|
@@ -550,7 +586,7 @@ export default class JSONReporter extends BaseReporter {
|
|
|
550
586
|
payload,
|
|
551
587
|
blob,
|
|
552
588
|
blob_mime,
|
|
553
|
-
error:
|
|
589
|
+
error: formatJSONError(error, type),
|
|
554
590
|
url,
|
|
555
591
|
package_version: version,
|
|
556
592
|
});
|
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') {
|