@elastic/synthetics 1.0.0-beta.2 → 1.0.0-beta.22
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/CHANGELOG.md +235 -0
- package/NOTICE.txt +250 -0
- package/README.md +2 -1
- package/dist/cli.js +87 -56
- package/dist/cli.js.map +1 -1
- package/dist/common_types.d.ts +116 -14
- package/dist/common_types.d.ts.map +1 -1
- package/dist/config.d.ts +2 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -5
- package/dist/config.js.map +1 -1
- package/dist/core/browser-service.js +5 -5
- package/dist/core/browser-service.js.map +1 -1
- package/dist/core/expect.d.ts +177 -0
- package/dist/core/expect.d.ts.map +1 -0
- package/dist/core/expect.js +33 -0
- package/dist/core/expect.js.map +1 -0
- package/dist/core/gatherer.d.ts +4 -7
- package/dist/core/gatherer.d.ts.map +1 -1
- package/dist/core/gatherer.js +39 -12
- package/dist/core/gatherer.js.map +1 -1
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/logger.d.ts +0 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +3 -16
- package/dist/core/logger.js.map +1 -1
- package/dist/core/runner.d.ts +54 -45
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +181 -71
- package/dist/core/runner.js.map +1 -1
- package/dist/dsl/journey.d.ts +13 -4
- package/dist/dsl/journey.d.ts.map +1 -1
- package/dist/dsl/journey.js +40 -1
- package/dist/dsl/journey.js.map +1 -1
- package/dist/formatter/javascript.d.ts +73 -0
- package/dist/formatter/javascript.d.ts.map +1 -0
- package/dist/formatter/javascript.js +274 -0
- package/dist/formatter/javascript.js.map +1 -0
- package/dist/helpers.d.ts +33 -12
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +108 -54
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/parse_args.d.ts +5 -2
- package/dist/parse_args.d.ts.map +1 -1
- package/dist/parse_args.js +59 -16
- package/dist/parse_args.js.map +1 -1
- package/dist/plugins/browser-console.d.ts +5 -8
- package/dist/plugins/browser-console.d.ts.map +1 -1
- package/dist/plugins/browser-console.js +27 -8
- package/dist/plugins/browser-console.js.map +1 -1
- package/dist/plugins/network.d.ts +23 -20
- package/dist/plugins/network.d.ts.map +1 -1
- package/dist/plugins/network.js +149 -124
- package/dist/plugins/network.js.map +1 -1
- package/dist/plugins/performance.d.ts +5 -5
- package/dist/plugins/performance.d.ts.map +1 -1
- package/dist/plugins/performance.js +5 -5
- package/dist/plugins/performance.js.map +1 -1
- package/dist/plugins/plugin-manager.d.ts +11 -11
- package/dist/plugins/plugin-manager.d.ts.map +1 -1
- package/dist/plugins/plugin-manager.js +40 -24
- package/dist/plugins/plugin-manager.js.map +1 -1
- package/dist/plugins/tracing.d.ts +10 -25
- package/dist/plugins/tracing.d.ts.map +1 -1
- package/dist/plugins/tracing.js +40 -26
- package/dist/plugins/tracing.js.map +1 -1
- package/dist/reporters/base.d.ts +0 -1
- package/dist/reporters/base.d.ts.map +1 -1
- package/dist/reporters/base.js +34 -32
- package/dist/reporters/base.js.map +1 -1
- package/dist/reporters/json.d.ts +46 -59
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +188 -70
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/junit.d.ts.map +1 -1
- package/dist/reporters/junit.js +7 -7
- package/dist/reporters/junit.js.map +1 -1
- package/dist/sdk/lh-trace-processor.d.ts +304 -0
- package/dist/sdk/lh-trace-processor.d.ts.map +1 -0
- package/dist/sdk/lh-trace-processor.js +832 -0
- package/dist/sdk/lh-trace-processor.js.map +1 -0
- package/dist/sdk/trace-metrics.d.ts +61 -0
- package/dist/sdk/trace-metrics.d.ts.map +1 -0
- package/dist/sdk/trace-metrics.js +252 -0
- package/dist/sdk/trace-metrics.js.map +1 -0
- package/dist/sdk/trace-processor.d.ts +106 -0
- package/dist/sdk/trace-processor.d.ts.map +1 -0
- package/dist/sdk/trace-processor.js +74 -0
- package/dist/sdk/trace-processor.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1575
- package/package.json +27 -16
- package/src/cli.ts +68 -45
- package/src/common_types.ts +138 -14
- package/src/config.ts +2 -5
- package/src/core/expect.ts +187 -0
- package/src/core/gatherer.ts +48 -24
- package/src/core/index.ts +6 -6
- package/src/core/logger.ts +1 -12
- package/src/core/runner.ts +259 -112
- package/src/dsl/journey.ts +28 -6
- package/src/formatter/javascript.ts +330 -0
- package/src/helpers.ts +114 -24
- package/src/index.ts +7 -6
- package/src/parse_args.ts +97 -22
- package/src/plugins/browser-console.ts +30 -13
- package/src/plugins/network.ts +175 -159
- package/src/plugins/performance.ts +7 -7
- package/src/plugins/plugin-manager.ts +47 -36
- package/src/plugins/tracing.ts +43 -54
- package/src/reporters/base.ts +29 -23
- package/src/reporters/json.ts +259 -80
- package/src/reporters/junit.ts +1 -1
- package/src/sdk/lh-trace-processor.ts +1033 -0
- package/src/sdk/trace-metrics.ts +287 -0
- package/src/sdk/trace-processor.ts +147 -0
package/src/core/runner.ts
CHANGED
|
@@ -23,47 +23,63 @@
|
|
|
23
23
|
*
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { EventEmitter } from 'events';
|
|
26
|
+
import { once, EventEmitter } from 'events';
|
|
27
|
+
import { join } from 'path';
|
|
28
|
+
import { rm } from 'fs/promises';
|
|
27
29
|
import { Journey } from '../dsl/journey';
|
|
28
30
|
import { Step } from '../dsl/step';
|
|
29
31
|
import { reporters, Reporter } from '../reporters';
|
|
30
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
CACHE_PATH,
|
|
34
|
+
monotonicTimeInSeconds,
|
|
35
|
+
getTimestamp,
|
|
36
|
+
runParallel,
|
|
37
|
+
generateUniqueId,
|
|
38
|
+
mkdirAsync,
|
|
39
|
+
writeFileAsync,
|
|
40
|
+
} from '../helpers';
|
|
31
41
|
import {
|
|
32
42
|
StatusValue,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
HooksCallback,
|
|
44
|
+
Params,
|
|
45
|
+
NetworkConditions,
|
|
46
|
+
PluginOutput,
|
|
36
47
|
CliArgs,
|
|
48
|
+
HooksArgs,
|
|
49
|
+
PlaywrightOptions,
|
|
50
|
+
Driver,
|
|
51
|
+
Screenshot,
|
|
37
52
|
} from '../common_types';
|
|
38
|
-
import {
|
|
39
|
-
import { PerformanceManager
|
|
40
|
-
import {
|
|
53
|
+
import { PageMetrics, PluginManager } from '../plugins';
|
|
54
|
+
import { PerformanceManager } from '../plugins';
|
|
55
|
+
import { Gatherer } from './gatherer';
|
|
41
56
|
import { log } from './logger';
|
|
42
57
|
|
|
43
58
|
export type RunOptions = Omit<
|
|
44
59
|
CliArgs,
|
|
45
60
|
| 'debug'
|
|
46
|
-
| 'json'
|
|
47
61
|
| 'pattern'
|
|
48
62
|
| 'inline'
|
|
49
63
|
| 'require'
|
|
50
|
-
| 'suiteParams'
|
|
51
64
|
| 'reporter'
|
|
52
|
-
| '
|
|
65
|
+
| 'richEvents'
|
|
66
|
+
| 'capability'
|
|
67
|
+
| 'sandbox'
|
|
68
|
+
| 'headless'
|
|
69
|
+
| 'throttling'
|
|
53
70
|
> & {
|
|
54
|
-
|
|
71
|
+
environment?: string;
|
|
72
|
+
playwrightOptions?: PlaywrightOptions;
|
|
73
|
+
networkConditions?: NetworkConditions;
|
|
55
74
|
reporter?: CliArgs['reporter'] | Reporter;
|
|
56
75
|
};
|
|
57
76
|
|
|
58
|
-
type
|
|
77
|
+
type HookType = 'beforeAll' | 'afterAll';
|
|
78
|
+
export type SuiteHooks = Record<HookType, Array<HooksCallback>>;
|
|
59
79
|
|
|
60
|
-
type
|
|
61
|
-
params?:
|
|
80
|
+
type JourneyContext = {
|
|
81
|
+
params?: Params;
|
|
62
82
|
start: number;
|
|
63
|
-
end?: number;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
type JourneyContext = BaseContext & {
|
|
67
83
|
driver: Driver;
|
|
68
84
|
pluginManager: PluginManager;
|
|
69
85
|
};
|
|
@@ -71,59 +87,85 @@ type JourneyContext = BaseContext & {
|
|
|
71
87
|
type StepResult = {
|
|
72
88
|
status: StatusValue;
|
|
73
89
|
url?: string;
|
|
74
|
-
metrics?: Metrics;
|
|
75
90
|
error?: Error;
|
|
76
|
-
|
|
91
|
+
pagemetrics?: PageMetrics;
|
|
92
|
+
filmstrips?: PluginOutput['filmstrips'];
|
|
93
|
+
metrics?: PluginOutput['metrics'];
|
|
94
|
+
traces?: PluginOutput['traces'];
|
|
77
95
|
};
|
|
78
96
|
|
|
79
97
|
type JourneyResult = {
|
|
80
98
|
status: StatusValue;
|
|
81
99
|
error?: Error;
|
|
100
|
+
networkinfo?: PluginOutput['networkinfo'];
|
|
101
|
+
browserconsole?: PluginOutput['browserconsole'];
|
|
82
102
|
};
|
|
83
103
|
|
|
84
104
|
type RunResult = Record<string, JourneyResult>;
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
interface StepEvent {
|
|
107
|
+
step: Step;
|
|
108
|
+
journey: Journey;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface JourneyEvent {
|
|
112
|
+
journey: Journey;
|
|
113
|
+
timestamp: number;
|
|
114
|
+
params?: Params;
|
|
115
|
+
}
|
|
88
116
|
|
|
89
117
|
interface Events {
|
|
90
|
-
start: {
|
|
91
|
-
|
|
92
|
-
|
|
118
|
+
start: {
|
|
119
|
+
numJourneys: number;
|
|
120
|
+
networkConditions?: NetworkConditions;
|
|
93
121
|
};
|
|
94
|
-
'journey:
|
|
122
|
+
'journey:register': {
|
|
95
123
|
journey: Journey;
|
|
96
|
-
timestamp: number;
|
|
97
|
-
params: RunParamaters;
|
|
98
124
|
};
|
|
99
|
-
'journey:
|
|
125
|
+
'journey:start': JourneyEvent;
|
|
126
|
+
'journey:end': JourneyEvent &
|
|
100
127
|
JourneyResult & {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
128
|
+
start: number;
|
|
129
|
+
end: number;
|
|
130
|
+
options: RunOptions;
|
|
131
|
+
timestamp: number;
|
|
132
|
+
};
|
|
133
|
+
'journey:end:reported': unknown;
|
|
134
|
+
'step:start': StepEvent;
|
|
135
|
+
'step:end': StepEvent &
|
|
136
|
+
StepResult & {
|
|
137
|
+
start: number;
|
|
138
|
+
end: number;
|
|
105
139
|
};
|
|
106
|
-
'step:start': { journey: Journey; step: Step };
|
|
107
|
-
'step:end': StepResult & {
|
|
108
|
-
start: number;
|
|
109
|
-
end: number;
|
|
110
|
-
journey: Journey;
|
|
111
|
-
step: Step;
|
|
112
|
-
};
|
|
113
140
|
end: unknown;
|
|
114
141
|
}
|
|
115
142
|
|
|
116
|
-
export default
|
|
143
|
+
export default interface Runner {
|
|
144
|
+
on<K extends keyof Events>(name: K, listener: (v: Events[K]) => void): this;
|
|
145
|
+
emit<K extends keyof Events>(event: K, args: Events[K]): boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default class Runner extends EventEmitter {
|
|
117
149
|
active = false;
|
|
118
|
-
eventEmitter = new EventEmitter();
|
|
119
150
|
currentJourney?: Journey = null;
|
|
120
151
|
journeys: Journey[] = [];
|
|
121
152
|
hooks: SuiteHooks = { beforeAll: [], afterAll: [] };
|
|
153
|
+
hookError: Error | undefined;
|
|
154
|
+
static screenshotPath = join(CACHE_PATH, 'screenshots');
|
|
122
155
|
|
|
123
156
|
static async createContext(options: RunOptions): Promise<JourneyContext> {
|
|
124
|
-
const start = getMonotonicTime();
|
|
125
157
|
const driver = await Gatherer.setupDriver(options);
|
|
158
|
+
/**
|
|
159
|
+
* Do not include browser launch/context creation duration
|
|
160
|
+
* as part of journey duration
|
|
161
|
+
*/
|
|
162
|
+
const start = monotonicTimeInSeconds();
|
|
126
163
|
const pluginManager = await Gatherer.beginRecording(driver, options);
|
|
164
|
+
/**
|
|
165
|
+
* For each journey we create the screenshots folder for
|
|
166
|
+
* caching all screenshots and clear them at end of each journey
|
|
167
|
+
*/
|
|
168
|
+
await mkdirAsync(this.screenshotPath, { recursive: true });
|
|
127
169
|
return {
|
|
128
170
|
start,
|
|
129
171
|
params: options.params,
|
|
@@ -132,7 +174,34 @@ export default class Runner {
|
|
|
132
174
|
};
|
|
133
175
|
}
|
|
134
176
|
|
|
135
|
-
|
|
177
|
+
async captureScreenshot(page: Driver['page'], step: Step) {
|
|
178
|
+
await page.waitForLoadState('load');
|
|
179
|
+
const buffer = await page
|
|
180
|
+
.screenshot({
|
|
181
|
+
type: 'jpeg',
|
|
182
|
+
quality: 80,
|
|
183
|
+
})
|
|
184
|
+
.catch(() => {});
|
|
185
|
+
/**
|
|
186
|
+
* Write the screenshot image buffer with additional details (step
|
|
187
|
+
* information) which could be extracted at the end of
|
|
188
|
+
* each journey without impacting the step timing information
|
|
189
|
+
*/
|
|
190
|
+
if (buffer) {
|
|
191
|
+
const fileName = `${generateUniqueId()}.json`;
|
|
192
|
+
const screenshot: Screenshot = {
|
|
193
|
+
step,
|
|
194
|
+
timestamp: getTimestamp(),
|
|
195
|
+
data: buffer.toString('base64'),
|
|
196
|
+
};
|
|
197
|
+
await writeFileAsync(
|
|
198
|
+
join(Runner.screenshotPath, fileName),
|
|
199
|
+
JSON.stringify(screenshot)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
addHook(type: HookType, callback: HooksCallback) {
|
|
136
205
|
this.hooks[type].push(callback);
|
|
137
206
|
}
|
|
138
207
|
|
|
@@ -141,33 +210,24 @@ export default class Runner {
|
|
|
141
210
|
this.currentJourney = journey;
|
|
142
211
|
}
|
|
143
212
|
|
|
144
|
-
|
|
145
|
-
log(`Runner: emit> ${e}`);
|
|
146
|
-
this.eventEmitter.emit(e, v);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
on<K extends keyof Events>(e: K, cb: (v: Events[K]) => void) {
|
|
150
|
-
this.eventEmitter.on(e, cb);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async runBeforeAllHook() {
|
|
213
|
+
async runBeforeAllHook(args: HooksArgs) {
|
|
154
214
|
log(`Runner: beforeAll hooks`);
|
|
155
|
-
await runParallel(this.hooks.beforeAll);
|
|
215
|
+
await runParallel(this.hooks.beforeAll, args);
|
|
156
216
|
}
|
|
157
217
|
|
|
158
|
-
async runAfterAllHook() {
|
|
218
|
+
async runAfterAllHook(args: HooksArgs) {
|
|
159
219
|
log(`Runner: afterAll hooks`);
|
|
160
|
-
await runParallel(this.hooks.afterAll);
|
|
220
|
+
await runParallel(this.hooks.afterAll, args);
|
|
161
221
|
}
|
|
162
222
|
|
|
163
|
-
async runBeforeHook(journey: Journey) {
|
|
223
|
+
async runBeforeHook(journey: Journey, args: HooksArgs) {
|
|
164
224
|
log(`Runner: before hooks for (${journey.name})`);
|
|
165
|
-
await runParallel(journey.hooks.before);
|
|
225
|
+
await runParallel(journey.hooks.before, args);
|
|
166
226
|
}
|
|
167
227
|
|
|
168
|
-
async runAfterHook(journey: Journey) {
|
|
228
|
+
async runAfterHook(journey: Journey, args: HooksArgs) {
|
|
169
229
|
log(`Runner: after hooks for (${journey.name})`);
|
|
170
|
-
await runParallel(journey.hooks.after);
|
|
230
|
+
await runParallel(journey.hooks.after, args);
|
|
171
231
|
}
|
|
172
232
|
|
|
173
233
|
async runStep(
|
|
@@ -179,7 +239,7 @@ export default class Runner {
|
|
|
179
239
|
status: 'succeeded',
|
|
180
240
|
};
|
|
181
241
|
log(`Runner: start step (${step.name})`);
|
|
182
|
-
const { metrics, screenshots } = options;
|
|
242
|
+
const { metrics, screenshots, filmstrips, trace } = options;
|
|
183
243
|
const { driver, pluginManager } = context;
|
|
184
244
|
/**
|
|
185
245
|
* URL needs to be the first navigation request of any step
|
|
@@ -190,28 +250,49 @@ export default class Runner {
|
|
|
190
250
|
if (!data.url && req.isNavigationRequest()) {
|
|
191
251
|
data.url = req.url();
|
|
192
252
|
}
|
|
193
|
-
driver.
|
|
253
|
+
driver.context.off('request', captureUrl);
|
|
194
254
|
};
|
|
195
|
-
driver.
|
|
255
|
+
driver.context.on('request', captureUrl);
|
|
256
|
+
|
|
196
257
|
try {
|
|
258
|
+
/**
|
|
259
|
+
* Set up plugin manager context and also register
|
|
260
|
+
* step level plugins
|
|
261
|
+
*/
|
|
262
|
+
const traceEnabled = trace || filmstrips;
|
|
197
263
|
pluginManager.onStep(step);
|
|
264
|
+
traceEnabled && (await pluginManager.start('trace'));
|
|
265
|
+
// call the step definition
|
|
198
266
|
await step.callback();
|
|
267
|
+
/**
|
|
268
|
+
* Collect all step level metrics
|
|
269
|
+
*/
|
|
199
270
|
if (metrics) {
|
|
200
|
-
data.
|
|
271
|
+
data.pagemetrics = await (
|
|
272
|
+
pluginManager.get('performance') as PerformanceManager
|
|
273
|
+
).getMetrics();
|
|
274
|
+
}
|
|
275
|
+
if (traceEnabled) {
|
|
276
|
+
const traceOutput = await pluginManager.stop('trace');
|
|
277
|
+
Object.assign(data, traceOutput);
|
|
201
278
|
}
|
|
202
279
|
} catch (error) {
|
|
203
280
|
data.status = 'failed';
|
|
204
281
|
data.error = error;
|
|
205
282
|
} finally {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Capture screenshot for the newly created pages
|
|
285
|
+
* via popup or new windows/tabs
|
|
286
|
+
*
|
|
287
|
+
* Last open page will get us the correct screenshot
|
|
288
|
+
*/
|
|
289
|
+
const pages = driver.context.pages();
|
|
290
|
+
const page = pages[pages.length - 1];
|
|
291
|
+
if (page) {
|
|
292
|
+
data.url ??= page.url();
|
|
293
|
+
if (screenshots && screenshots !== 'off') {
|
|
294
|
+
await this.captureScreenshot(page, step);
|
|
295
|
+
}
|
|
215
296
|
}
|
|
216
297
|
}
|
|
217
298
|
log(`Runner: end step (${step.name})`);
|
|
@@ -226,7 +307,7 @@ export default class Runner {
|
|
|
226
307
|
const results: Array<StepResult> = [];
|
|
227
308
|
let skipStep = false;
|
|
228
309
|
for (const step of journey.steps) {
|
|
229
|
-
const start =
|
|
310
|
+
const start = monotonicTimeInSeconds();
|
|
230
311
|
this.emit('step:start', { journey, step });
|
|
231
312
|
let data: StepResult = { status: 'succeeded' };
|
|
232
313
|
if (skipStep) {
|
|
@@ -242,7 +323,7 @@ export default class Runner {
|
|
|
242
323
|
journey,
|
|
243
324
|
step,
|
|
244
325
|
start,
|
|
245
|
-
end:
|
|
326
|
+
end: monotonicTimeInSeconds(),
|
|
246
327
|
...data,
|
|
247
328
|
});
|
|
248
329
|
if (options.pauseOnError && data.error) {
|
|
@@ -264,35 +345,77 @@ export default class Runner {
|
|
|
264
345
|
journey.callback({ ...context.driver, params });
|
|
265
346
|
}
|
|
266
347
|
|
|
267
|
-
async endJourney(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
} =
|
|
348
|
+
async endJourney(
|
|
349
|
+
journey,
|
|
350
|
+
result: JourneyContext & JourneyResult,
|
|
351
|
+
options: RunOptions
|
|
352
|
+
) {
|
|
353
|
+
const end = monotonicTimeInSeconds();
|
|
354
|
+
const { pluginManager, start, status, error } = result;
|
|
355
|
+
const pluginOutput = pluginManager.output();
|
|
274
356
|
this.emit('journey:end', {
|
|
275
357
|
journey,
|
|
276
358
|
status,
|
|
277
359
|
error,
|
|
278
|
-
params,
|
|
279
360
|
start,
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
361
|
+
end,
|
|
362
|
+
timestamp: getTimestamp(),
|
|
363
|
+
options,
|
|
364
|
+
...pluginOutput,
|
|
365
|
+
browserconsole: status == 'failed' ? pluginOutput.browserconsole : [],
|
|
366
|
+
});
|
|
367
|
+
/**
|
|
368
|
+
* Wait for the all the reported events to be consumed aschronously
|
|
369
|
+
* by reporter.
|
|
370
|
+
*/
|
|
371
|
+
if (options.reporter === 'json') {
|
|
372
|
+
await once(this, 'journey:end:reported');
|
|
373
|
+
}
|
|
374
|
+
// clear screenshots cache after each journey
|
|
375
|
+
await rm(Runner.screenshotPath, { recursive: true, force: true });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Simulate a journey run to capture errors in the beforeAll hook
|
|
380
|
+
*/
|
|
381
|
+
async runFakeJourney(journey: Journey, options: RunOptions) {
|
|
382
|
+
const start = monotonicTimeInSeconds();
|
|
383
|
+
this.emit('journey:start', {
|
|
384
|
+
journey,
|
|
385
|
+
timestamp: getTimestamp(),
|
|
386
|
+
params: options.params,
|
|
387
|
+
});
|
|
388
|
+
const result: JourneyResult = {
|
|
389
|
+
status: 'failed',
|
|
390
|
+
error: this.hookError,
|
|
391
|
+
};
|
|
392
|
+
this.emit('journey:end', {
|
|
393
|
+
journey,
|
|
394
|
+
timestamp: getTimestamp(),
|
|
395
|
+
start,
|
|
396
|
+
options,
|
|
397
|
+
end: monotonicTimeInSeconds(),
|
|
398
|
+
...result,
|
|
284
399
|
});
|
|
400
|
+
if (options.reporter === 'json') {
|
|
401
|
+
await once(this, 'journey:end:reported');
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
285
404
|
}
|
|
286
405
|
|
|
287
406
|
async runJourney(journey: Journey, options: RunOptions) {
|
|
288
407
|
const result: JourneyResult = {
|
|
289
408
|
status: 'succeeded',
|
|
290
409
|
};
|
|
291
|
-
log(`Runner: start journey (${journey.name})`);
|
|
292
410
|
const context = await Runner.createContext(options);
|
|
411
|
+
log(`Runner: start journey (${journey.name})`);
|
|
293
412
|
try {
|
|
294
413
|
this.registerJourney(journey, context);
|
|
295
|
-
|
|
414
|
+
const hookArgs = {
|
|
415
|
+
env: options.environment,
|
|
416
|
+
params: options.params,
|
|
417
|
+
};
|
|
418
|
+
await this.runBeforeHook(journey, hookArgs);
|
|
296
419
|
const stepResults = await this.runSteps(journey, context, options);
|
|
297
420
|
/**
|
|
298
421
|
* Mark journey as failed if any intermediate step fails
|
|
@@ -303,26 +426,20 @@ export default class Runner {
|
|
|
303
426
|
result.error = stepResult.error;
|
|
304
427
|
}
|
|
305
428
|
}
|
|
306
|
-
await this.runAfterHook(journey);
|
|
429
|
+
await this.runAfterHook(journey, hookArgs);
|
|
307
430
|
} catch (e) {
|
|
308
431
|
result.status = 'failed';
|
|
309
432
|
result.error = e;
|
|
310
433
|
} finally {
|
|
311
|
-
await this.endJourney(journey, { ...context, ...result });
|
|
434
|
+
await this.endJourney(journey, { ...context, ...result }, options);
|
|
312
435
|
await Gatherer.dispose(context.driver);
|
|
313
436
|
}
|
|
314
437
|
log(`Runner: end journey (${journey.name})`);
|
|
315
438
|
return result;
|
|
316
439
|
}
|
|
317
440
|
|
|
318
|
-
async
|
|
319
|
-
const
|
|
320
|
-
if (this.active) {
|
|
321
|
-
return result;
|
|
322
|
-
}
|
|
323
|
-
this.active = true;
|
|
324
|
-
log(`Runner: run ${this.journeys.length} journeys`);
|
|
325
|
-
const { reporter, journeyName, outfd } = options;
|
|
441
|
+
async init(options: RunOptions) {
|
|
442
|
+
const { reporter, outfd, networkConditions } = options;
|
|
326
443
|
/**
|
|
327
444
|
* Set up the corresponding reporter and fallback
|
|
328
445
|
*/
|
|
@@ -331,34 +448,64 @@ export default class Runner {
|
|
|
331
448
|
? reporter
|
|
332
449
|
: reporters[reporter] || reporters['default'];
|
|
333
450
|
new Reporter(this, { fd: outfd });
|
|
334
|
-
this.emit('start', {
|
|
335
|
-
|
|
451
|
+
this.emit('start', {
|
|
452
|
+
numJourneys: this.journeys.length,
|
|
453
|
+
networkConditions,
|
|
454
|
+
});
|
|
455
|
+
/**
|
|
456
|
+
* Set up the directory for caching screenshots
|
|
457
|
+
*/
|
|
458
|
+
await mkdirAsync(CACHE_PATH, { recursive: true });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async run(options: RunOptions) {
|
|
462
|
+
const result: RunResult = {};
|
|
463
|
+
if (this.active) {
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
this.active = true;
|
|
467
|
+
log(`Runner: run ${this.journeys.length} journeys`);
|
|
468
|
+
this.init(options);
|
|
469
|
+
await this.runBeforeAllHook({
|
|
470
|
+
env: options.environment,
|
|
471
|
+
params: options.params,
|
|
472
|
+
}).catch(e => (this.hookError = e));
|
|
473
|
+
|
|
474
|
+
const { dryRun, match, tags } = options;
|
|
336
475
|
for (const journey of this.journeys) {
|
|
337
476
|
/**
|
|
338
477
|
* Used by heartbeat to gather all registered journeys
|
|
339
478
|
*/
|
|
340
|
-
if (
|
|
479
|
+
if (dryRun) {
|
|
341
480
|
this.emit('journey:register', { journey });
|
|
342
481
|
continue;
|
|
343
482
|
}
|
|
344
|
-
|
|
345
|
-
if (journeyName && journey.name != journeyName) {
|
|
483
|
+
if (!journey.isMatch(match, tags)) {
|
|
346
484
|
continue;
|
|
347
485
|
}
|
|
348
|
-
const journeyResult =
|
|
486
|
+
const journeyResult: JourneyResult = this.hookError
|
|
487
|
+
? await this.runFakeJourney(journey, options)
|
|
488
|
+
: await this.runJourney(journey, options);
|
|
349
489
|
result[journey.name] = journeyResult;
|
|
350
490
|
}
|
|
351
491
|
await Gatherer.stop();
|
|
352
|
-
await this.runAfterAllHook(
|
|
353
|
-
|
|
354
|
-
|
|
492
|
+
await this.runAfterAllHook({
|
|
493
|
+
env: options.environment,
|
|
494
|
+
params: options.params,
|
|
495
|
+
});
|
|
496
|
+
await this.reset();
|
|
355
497
|
return result;
|
|
356
498
|
}
|
|
357
499
|
|
|
358
|
-
reset() {
|
|
359
|
-
|
|
500
|
+
async reset() {
|
|
501
|
+
/**
|
|
502
|
+
* Clear all cache data stored for post processing by
|
|
503
|
+
* the current synthetic agent run
|
|
504
|
+
*/
|
|
505
|
+
await rm(CACHE_PATH, { recursive: true, force: true });
|
|
360
506
|
this.currentJourney = null;
|
|
361
507
|
this.journeys = [];
|
|
362
508
|
this.active = false;
|
|
509
|
+
this.emit('end', {});
|
|
363
510
|
}
|
|
364
511
|
}
|
package/src/dsl/journey.ts
CHANGED
|
@@ -24,35 +24,38 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { Browser, Page, BrowserContext, CDPSession } from 'playwright-chromium';
|
|
27
|
+
import micromatch, { isMatch } from 'micromatch';
|
|
27
28
|
import { Step } from './step';
|
|
28
|
-
import { VoidCallback } from '../common_types';
|
|
29
|
+
import { VoidCallback, HooksCallback, Params } from '../common_types';
|
|
29
30
|
|
|
30
31
|
export type JourneyOptions = {
|
|
31
32
|
name: string;
|
|
32
33
|
id?: string;
|
|
34
|
+
tags?: string[];
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
type HookType = 'before' | 'after';
|
|
36
|
-
export type Hooks = Record<HookType, Array<
|
|
37
|
-
|
|
38
|
+
export type Hooks = Record<HookType, Array<HooksCallback>>;
|
|
38
39
|
export type JourneyCallback = (options: {
|
|
39
40
|
page: Page;
|
|
40
41
|
context: BrowserContext;
|
|
41
42
|
browser: Browser;
|
|
42
43
|
client: CDPSession;
|
|
43
|
-
params:
|
|
44
|
+
params: Params;
|
|
44
45
|
}) => void;
|
|
45
46
|
|
|
46
47
|
export class Journey {
|
|
47
48
|
name: string;
|
|
48
49
|
id?: string;
|
|
50
|
+
tags?: string[];
|
|
49
51
|
callback: JourneyCallback;
|
|
50
52
|
steps: Step[] = [];
|
|
51
53
|
hooks: Hooks = { before: [], after: [] };
|
|
52
54
|
|
|
53
55
|
constructor(options: JourneyOptions, callback: JourneyCallback) {
|
|
54
56
|
this.name = options.name;
|
|
55
|
-
this.id = options.id;
|
|
57
|
+
this.id = options.id || options.name;
|
|
58
|
+
this.tags = options.tags;
|
|
56
59
|
this.callback = callback;
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -62,7 +65,26 @@ export class Journey {
|
|
|
62
65
|
return step;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
addHook(type: HookType, callback:
|
|
68
|
+
addHook(type: HookType, callback: HooksCallback) {
|
|
66
69
|
this.hooks[type].push(callback);
|
|
67
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Matches journeys based on the provided args. Proitize tags over match
|
|
73
|
+
* - tags pattern that matches only tags
|
|
74
|
+
* - match pattern that matches both name and tags
|
|
75
|
+
*/
|
|
76
|
+
isMatch(matchPattern: string, tagsPattern: Array<string>) {
|
|
77
|
+
if (tagsPattern) {
|
|
78
|
+
return this.tagsMatch(tagsPattern);
|
|
79
|
+
}
|
|
80
|
+
if (matchPattern) {
|
|
81
|
+
return isMatch(this.name, matchPattern) || this.tagsMatch(matchPattern);
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
tagsMatch(pattern) {
|
|
87
|
+
const matchess = micromatch(this.tags || ['*'], pattern);
|
|
88
|
+
return matchess.length > 0;
|
|
89
|
+
}
|
|
68
90
|
}
|