@elastic/synthetics 1.0.0-beta.14 → 1.0.0-beta.18
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 +61 -0
- package/README.md +1 -0
- package/dist/cli.js +37 -21
- package/dist/cli.js.map +1 -1
- package/dist/common_types.d.ts +13 -4
- package/dist/common_types.d.ts.map +1 -1
- package/dist/core/gatherer.d.ts +3 -1
- package/dist/core/gatherer.d.ts.map +1 -1
- package/dist/core/gatherer.js +29 -3
- package/dist/core/gatherer.js.map +1 -1
- package/dist/core/runner.d.ts +23 -22
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +22 -9
- package/dist/core/runner.js.map +1 -1
- package/dist/formatter/javascript.d.ts +1 -0
- package/dist/formatter/javascript.d.ts.map +1 -1
- package/dist/formatter/javascript.js +8 -3
- package/dist/formatter/javascript.js.map +1 -1
- package/dist/helpers.d.ts +7 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +50 -2
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/parse_args.d.ts.map +1 -1
- package/dist/parse_args.js +5 -1
- package/dist/parse_args.js.map +1 -1
- package/dist/plugins/network.d.ts +1 -5
- package/dist/plugins/network.d.ts.map +1 -1
- package/dist/reporters/json.d.ts +4 -3
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +19 -9
- package/dist/reporters/json.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/src/cli.ts +42 -25
- package/src/common_types.ts +14 -4
- package/src/core/gatherer.ts +39 -4
- package/src/core/runner.ts +57 -34
- package/src/formatter/javascript.ts +9 -3
- package/src/helpers.ts +60 -2
- package/src/index.ts +1 -0
- package/src/parse_args.ts +10 -5
- package/src/reporters/json.ts +23 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elastic/synthetics",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.18",
|
|
4
4
|
"description": "Elastic synthetic monitoring agent",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"kleur": "^4.1.3",
|
|
47
47
|
"micromatch": "^4.0.4",
|
|
48
48
|
"playwright-chromium": "=1.14.0",
|
|
49
|
-
"sharp": "^0.
|
|
49
|
+
"sharp": "^0.29.3",
|
|
50
50
|
"snakecase-keys": "^3.2.1",
|
|
51
51
|
"sonic-boom": "^2.1.0",
|
|
52
52
|
"source-map-support": "^0.5.19",
|
|
@@ -54,23 +54,23 @@
|
|
|
54
54
|
"typescript": "^4.3.5"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@types/jest": "^
|
|
57
|
+
"@types/jest": "^27.4.0",
|
|
58
58
|
"@types/micromatch": "^4.0.1",
|
|
59
59
|
"@types/node": "^14.14.14",
|
|
60
60
|
"@types/sharp": "^0.28.2",
|
|
61
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
62
|
-
"@typescript-eslint/parser": "^
|
|
63
|
-
"eslint": "^
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
62
|
+
"@typescript-eslint/parser": "^5.0.0",
|
|
63
|
+
"eslint": "^8.0.1",
|
|
64
64
|
"husky": "^4.3.6",
|
|
65
|
-
"jest": "^
|
|
65
|
+
"jest": "^27.4.5",
|
|
66
66
|
"lint-staged": "^10.5.3",
|
|
67
67
|
"npm-pack-all": "^1.12.7",
|
|
68
|
-
"prettier": "^2.
|
|
69
|
-
"
|
|
70
|
-
"
|
|
68
|
+
"prettier": "^2.4.1",
|
|
69
|
+
"rimraf": "^3.0.2",
|
|
70
|
+
"ts-jest": "^27.1.2"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
|
-
"node": ">
|
|
73
|
+
"node": ">14.14.0",
|
|
74
74
|
"npm": "6"
|
|
75
75
|
}
|
|
76
76
|
}
|
package/src/cli.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
isDepInstalled,
|
|
38
38
|
isDirectory,
|
|
39
39
|
totalist,
|
|
40
|
+
parseNetworkConditions,
|
|
40
41
|
} from './helpers';
|
|
41
42
|
import { run } from './';
|
|
42
43
|
import { readConfig } from './config';
|
|
@@ -125,6 +126,25 @@ async function prepareSuites(inputs: string[]) {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
(async () => {
|
|
129
|
+
/**
|
|
130
|
+
* Transform `.ts` files out of the box by invoking
|
|
131
|
+
* the `ts-node` via `transpile-only` mode that compiles
|
|
132
|
+
* TS files without doing any extensive type checks.
|
|
133
|
+
*
|
|
134
|
+
* We must register `ts-node` _before_ loading inline
|
|
135
|
+
* scripts too because otherwise we will not be able to
|
|
136
|
+
* require `.ts` configuration files.
|
|
137
|
+
*/
|
|
138
|
+
/* eslint-disable-next-line @typescript-eslint/no-var-requires */
|
|
139
|
+
require('ts-node').register({
|
|
140
|
+
transpileOnly: true,
|
|
141
|
+
compilerOptions: {
|
|
142
|
+
esModuleInterop: true,
|
|
143
|
+
allowJs: true,
|
|
144
|
+
target: 'es2018',
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
128
148
|
if (options.inline) {
|
|
129
149
|
const source = await readStdin();
|
|
130
150
|
loadInlineScript(source);
|
|
@@ -140,20 +160,6 @@ async function prepareSuites(inputs: string[]) {
|
|
|
140
160
|
throw new Error(`cannot find module '${name}'`);
|
|
141
161
|
}
|
|
142
162
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Transform `.ts` files out of the box by invoking
|
|
145
|
-
* the `ts-node` via `transpile-only` mode that compiles
|
|
146
|
-
* TS files without doing any extensive type checks
|
|
147
|
-
*/
|
|
148
|
-
/* eslint-disable-next-line @typescript-eslint/no-var-requires */
|
|
149
|
-
require('ts-node').register({
|
|
150
|
-
transpileOnly: true,
|
|
151
|
-
compilerOptions: {
|
|
152
|
-
esModuleInterop: true,
|
|
153
|
-
allowJs: true,
|
|
154
|
-
target: 'es2018',
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
163
|
/**
|
|
158
164
|
* Handle piped files by reading the STDIN
|
|
159
165
|
* ex: ls example/suites/*.js | npx @elastic/synthetics
|
|
@@ -173,19 +179,30 @@ async function prepareSuites(inputs: string[]) {
|
|
|
173
179
|
/**
|
|
174
180
|
* Validate and handle configs
|
|
175
181
|
*/
|
|
176
|
-
const config =
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
const config =
|
|
183
|
+
options.config || !options.inline
|
|
184
|
+
? readConfig(environment, options.config)
|
|
185
|
+
: {};
|
|
186
|
+
const params = merge(config.params, options.params || {});
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Favor playwright options passed via cli to inline playwright options
|
|
190
|
+
*/
|
|
191
|
+
const playwrightOptions = merge.all([
|
|
192
|
+
config.playwrightOptions || {},
|
|
193
|
+
options.playwrightOptions || {},
|
|
194
|
+
{
|
|
195
|
+
headless: options.headless,
|
|
196
|
+
chromiumSandbox: options.sandbox,
|
|
197
|
+
ignoreHTTPSErrors: options.ignoreHttpsErrors,
|
|
198
|
+
},
|
|
199
|
+
]);
|
|
200
|
+
|
|
187
201
|
const results = await run({
|
|
188
202
|
params: Object.freeze(params),
|
|
203
|
+
networkConditions: options.throttling
|
|
204
|
+
? parseNetworkConditions(options.throttling as string)
|
|
205
|
+
: undefined,
|
|
189
206
|
environment,
|
|
190
207
|
playwrightOptions,
|
|
191
208
|
...options,
|
package/src/common_types.ts
CHANGED
|
@@ -37,6 +37,12 @@ import { reporters } from './reporters';
|
|
|
37
37
|
|
|
38
38
|
export type VoidCallback = () => void;
|
|
39
39
|
export type Params = Record<string, any>;
|
|
40
|
+
export type NetworkConditions = {
|
|
41
|
+
offline: boolean;
|
|
42
|
+
downloadThroughput: number;
|
|
43
|
+
uploadThroughput: number;
|
|
44
|
+
latency: number;
|
|
45
|
+
};
|
|
40
46
|
export type HooksArgs = {
|
|
41
47
|
env: string;
|
|
42
48
|
params: Params;
|
|
@@ -88,6 +94,12 @@ export type BrowserInfo = {
|
|
|
88
94
|
version: string;
|
|
89
95
|
};
|
|
90
96
|
|
|
97
|
+
export type Screenshot = {
|
|
98
|
+
timestamp: number;
|
|
99
|
+
step: Step;
|
|
100
|
+
data: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
91
103
|
export type NetworkInfo = {
|
|
92
104
|
url: string;
|
|
93
105
|
browser: BrowserInfo;
|
|
@@ -157,10 +169,8 @@ export type CliArgs = {
|
|
|
157
169
|
debug?: boolean;
|
|
158
170
|
ignoreHttpsErrors?: boolean;
|
|
159
171
|
params?: Params;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*/
|
|
163
|
-
suiteParams?: Params;
|
|
172
|
+
throttling?: boolean | string;
|
|
173
|
+
playwrightOptions?: LaunchOptions & BrowserContextOptions;
|
|
164
174
|
richEvents?: boolean;
|
|
165
175
|
};
|
|
166
176
|
|
package/src/core/gatherer.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { chromium, ChromiumBrowser } from 'playwright-chromium';
|
|
26
|
+
import { chromium, ChromiumBrowser, BrowserContext } from 'playwright-chromium';
|
|
27
27
|
import { PluginManager } from '../plugins';
|
|
28
28
|
import { RunOptions } from './runner';
|
|
29
29
|
import { log } from './logger';
|
|
@@ -37,21 +37,56 @@ export class Gatherer {
|
|
|
37
37
|
static browser: ChromiumBrowser;
|
|
38
38
|
|
|
39
39
|
static async setupDriver(options: RunOptions): Promise<Driver> {
|
|
40
|
-
|
|
40
|
+
log('Gatherer: setup driver');
|
|
41
|
+
const { wsEndpoint, playwrightOptions, networkConditions } = options;
|
|
42
|
+
|
|
41
43
|
if (Gatherer.browser == null) {
|
|
42
44
|
if (wsEndpoint) {
|
|
43
45
|
log(`Gatherer: connecting to WS endpoint: ${wsEndpoint}`);
|
|
44
46
|
Gatherer.browser = await chromium.connect({ wsEndpoint });
|
|
45
47
|
} else {
|
|
46
|
-
Gatherer.browser = await chromium.launch(
|
|
48
|
+
Gatherer.browser = await chromium.launch({
|
|
49
|
+
...playwrightOptions,
|
|
50
|
+
args: [
|
|
51
|
+
...(playwrightOptions?.headless ? ['--disable-gpu'] : []),
|
|
52
|
+
...(playwrightOptions?.args ?? []),
|
|
53
|
+
],
|
|
54
|
+
});
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
|
-
const context = await Gatherer.browser.newContext(
|
|
57
|
+
const context = await Gatherer.browser.newContext({
|
|
58
|
+
...playwrightOptions,
|
|
59
|
+
userAgent: await Gatherer.getUserAgent(),
|
|
60
|
+
});
|
|
61
|
+
await Gatherer.setNetworkConditions(context, networkConditions);
|
|
62
|
+
|
|
50
63
|
const page = await context.newPage();
|
|
51
64
|
const client = await context.newCDPSession(page);
|
|
52
65
|
return { browser: Gatherer.browser, context, page, client };
|
|
53
66
|
}
|
|
54
67
|
|
|
68
|
+
static async getUserAgent() {
|
|
69
|
+
const session = await Gatherer.browser.newBrowserCDPSession();
|
|
70
|
+
const { userAgent } = await session.send('Browser.getVersion');
|
|
71
|
+
return userAgent + ' Elastic/Synthetics';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static async setNetworkConditions(
|
|
75
|
+
context: BrowserContext,
|
|
76
|
+
networkConditions: RunOptions['networkConditions']
|
|
77
|
+
) {
|
|
78
|
+
if (networkConditions) {
|
|
79
|
+
context.on('page', async page => {
|
|
80
|
+
const context = page.context();
|
|
81
|
+
const client = await context.newCDPSession(page);
|
|
82
|
+
await client.send(
|
|
83
|
+
'Network.emulateNetworkConditions',
|
|
84
|
+
networkConditions
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
55
90
|
/**
|
|
56
91
|
* Starts recording all events related to the v8 devtools protocol
|
|
57
92
|
* https://chromedevtools.github.io/devtools-protocol/v8/
|
package/src/core/runner.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { once, EventEmitter } from 'events';
|
|
27
27
|
import { join } from 'path';
|
|
28
|
+
import { rm } from 'fs/promises';
|
|
28
29
|
import { Journey } from '../dsl/journey';
|
|
29
30
|
import { Step } from '../dsl/step';
|
|
30
31
|
import { reporters, Reporter } from '../reporters';
|
|
@@ -35,18 +36,19 @@ import {
|
|
|
35
36
|
runParallel,
|
|
36
37
|
generateUniqueId,
|
|
37
38
|
mkdirAsync,
|
|
38
|
-
rmdirAsync,
|
|
39
39
|
writeFileAsync,
|
|
40
40
|
} from '../helpers';
|
|
41
41
|
import {
|
|
42
42
|
StatusValue,
|
|
43
43
|
HooksCallback,
|
|
44
44
|
Params,
|
|
45
|
+
NetworkConditions,
|
|
45
46
|
PluginOutput,
|
|
46
47
|
CliArgs,
|
|
47
48
|
HooksArgs,
|
|
48
49
|
PlaywrightOptions,
|
|
49
50
|
Driver,
|
|
51
|
+
Screenshot,
|
|
50
52
|
} from '../common_types';
|
|
51
53
|
import { PageMetrics, PluginManager } from '../plugins';
|
|
52
54
|
import { PerformanceManager } from '../plugins';
|
|
@@ -64,19 +66,20 @@ export type RunOptions = Omit<
|
|
|
64
66
|
| 'capability'
|
|
65
67
|
| 'sandbox'
|
|
66
68
|
| 'headless'
|
|
69
|
+
| 'throttling'
|
|
67
70
|
> & {
|
|
68
71
|
environment?: string;
|
|
69
72
|
playwrightOptions?: PlaywrightOptions;
|
|
73
|
+
networkConditions?: NetworkConditions;
|
|
70
74
|
reporter?: CliArgs['reporter'] | Reporter;
|
|
71
75
|
};
|
|
72
76
|
|
|
73
|
-
type
|
|
77
|
+
type HookType = 'beforeAll' | 'afterAll';
|
|
78
|
+
export type SuiteHooks = Record<HookType, Array<HooksCallback>>;
|
|
79
|
+
|
|
80
|
+
type JourneyContext = {
|
|
74
81
|
params?: Params;
|
|
75
82
|
start: number;
|
|
76
|
-
end?: number;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
type JourneyContext = BaseContext & {
|
|
80
83
|
driver: Driver;
|
|
81
84
|
pluginManager: PluginManager;
|
|
82
85
|
};
|
|
@@ -100,32 +103,40 @@ type JourneyResult = {
|
|
|
100
103
|
|
|
101
104
|
type RunResult = Record<string, JourneyResult>;
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
interface StepEvent {
|
|
107
|
+
step: Step;
|
|
108
|
+
journey: Journey;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface JourneyEvent {
|
|
112
|
+
journey: Journey;
|
|
113
|
+
timestamp: number;
|
|
114
|
+
params?: Params;
|
|
115
|
+
}
|
|
105
116
|
|
|
106
117
|
interface Events {
|
|
107
|
-
start: {
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
start: {
|
|
119
|
+
numJourneys: number;
|
|
120
|
+
networkConditions?: NetworkConditions;
|
|
110
121
|
};
|
|
111
|
-
'journey:
|
|
122
|
+
'journey:register': {
|
|
112
123
|
journey: Journey;
|
|
113
|
-
timestamp: number;
|
|
114
|
-
params: Params;
|
|
115
124
|
};
|
|
116
|
-
'journey:
|
|
125
|
+
'journey:start': JourneyEvent;
|
|
126
|
+
'journey:end': JourneyEvent &
|
|
117
127
|
JourneyResult & {
|
|
118
|
-
|
|
128
|
+
start: number;
|
|
129
|
+
end: number;
|
|
119
130
|
options: RunOptions;
|
|
131
|
+
timestamp: number;
|
|
120
132
|
};
|
|
121
133
|
'journey:end:reported': unknown;
|
|
122
|
-
'step:start':
|
|
123
|
-
'step:end':
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
};
|
|
134
|
+
'step:start': StepEvent;
|
|
135
|
+
'step:end': StepEvent &
|
|
136
|
+
StepResult & {
|
|
137
|
+
start: number;
|
|
138
|
+
end: number;
|
|
139
|
+
};
|
|
129
140
|
end: unknown;
|
|
130
141
|
}
|
|
131
142
|
|
|
@@ -143,8 +154,12 @@ export default class Runner extends EventEmitter {
|
|
|
143
154
|
static screenshotPath = join(CACHE_PATH, 'screenshots');
|
|
144
155
|
|
|
145
156
|
static async createContext(options: RunOptions): Promise<JourneyContext> {
|
|
146
|
-
const start = monotonicTimeInSeconds();
|
|
147
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();
|
|
148
163
|
const pluginManager = await Gatherer.beginRecording(driver, options);
|
|
149
164
|
/**
|
|
150
165
|
* For each journey we create the screenshots folder for
|
|
@@ -174,12 +189,14 @@ export default class Runner extends EventEmitter {
|
|
|
174
189
|
*/
|
|
175
190
|
if (buffer) {
|
|
176
191
|
const fileName = `${generateUniqueId()}.json`;
|
|
192
|
+
const screenshot: Screenshot = {
|
|
193
|
+
step,
|
|
194
|
+
timestamp: getTimestamp(),
|
|
195
|
+
data: buffer.toString('base64'),
|
|
196
|
+
};
|
|
177
197
|
await writeFileAsync(
|
|
178
198
|
join(Runner.screenshotPath, fileName),
|
|
179
|
-
JSON.stringify(
|
|
180
|
-
step,
|
|
181
|
-
data: buffer.toString('base64'),
|
|
182
|
-
})
|
|
199
|
+
JSON.stringify(screenshot)
|
|
183
200
|
);
|
|
184
201
|
}
|
|
185
202
|
}
|
|
@@ -333,6 +350,7 @@ export default class Runner extends EventEmitter {
|
|
|
333
350
|
result: JourneyContext & JourneyResult,
|
|
334
351
|
options: RunOptions
|
|
335
352
|
) {
|
|
353
|
+
const end = monotonicTimeInSeconds();
|
|
336
354
|
const { pluginManager, start, status, error } = result;
|
|
337
355
|
const pluginOutput = pluginManager.output();
|
|
338
356
|
this.emit('journey:end', {
|
|
@@ -340,7 +358,8 @@ export default class Runner extends EventEmitter {
|
|
|
340
358
|
status,
|
|
341
359
|
error,
|
|
342
360
|
start,
|
|
343
|
-
end
|
|
361
|
+
end,
|
|
362
|
+
timestamp: getTimestamp(),
|
|
344
363
|
options,
|
|
345
364
|
...pluginOutput,
|
|
346
365
|
browserconsole: status == 'failed' ? pluginOutput.browserconsole : [],
|
|
@@ -353,7 +372,7 @@ export default class Runner extends EventEmitter {
|
|
|
353
372
|
await once(this, 'journey:end:reported');
|
|
354
373
|
}
|
|
355
374
|
// clear screenshots cache after each journey
|
|
356
|
-
await
|
|
375
|
+
await rm(Runner.screenshotPath, { recursive: true, force: true });
|
|
357
376
|
}
|
|
358
377
|
|
|
359
378
|
/**
|
|
@@ -372,6 +391,7 @@ export default class Runner extends EventEmitter {
|
|
|
372
391
|
};
|
|
373
392
|
this.emit('journey:end', {
|
|
374
393
|
journey,
|
|
394
|
+
timestamp: getTimestamp(),
|
|
375
395
|
start,
|
|
376
396
|
options,
|
|
377
397
|
end: monotonicTimeInSeconds(),
|
|
@@ -387,8 +407,8 @@ export default class Runner extends EventEmitter {
|
|
|
387
407
|
const result: JourneyResult = {
|
|
388
408
|
status: 'succeeded',
|
|
389
409
|
};
|
|
390
|
-
log(`Runner: start journey (${journey.name})`);
|
|
391
410
|
const context = await Runner.createContext(options);
|
|
411
|
+
log(`Runner: start journey (${journey.name})`);
|
|
392
412
|
try {
|
|
393
413
|
this.registerJourney(journey, context);
|
|
394
414
|
const hookArgs = {
|
|
@@ -419,7 +439,7 @@ export default class Runner extends EventEmitter {
|
|
|
419
439
|
}
|
|
420
440
|
|
|
421
441
|
async init(options: RunOptions) {
|
|
422
|
-
const { reporter, outfd } = options;
|
|
442
|
+
const { reporter, outfd, networkConditions } = options;
|
|
423
443
|
/**
|
|
424
444
|
* Set up the corresponding reporter and fallback
|
|
425
445
|
*/
|
|
@@ -428,7 +448,10 @@ export default class Runner extends EventEmitter {
|
|
|
428
448
|
? reporter
|
|
429
449
|
: reporters[reporter] || reporters['default'];
|
|
430
450
|
new Reporter(this, { fd: outfd });
|
|
431
|
-
this.emit('start', {
|
|
451
|
+
this.emit('start', {
|
|
452
|
+
numJourneys: this.journeys.length,
|
|
453
|
+
networkConditions,
|
|
454
|
+
});
|
|
432
455
|
/**
|
|
433
456
|
* Set up the directory for caching screenshots
|
|
434
457
|
*/
|
|
@@ -479,7 +502,7 @@ export default class Runner extends EventEmitter {
|
|
|
479
502
|
* Clear all cache data stored for post processing by
|
|
480
503
|
* the current synthetic agent run
|
|
481
504
|
*/
|
|
482
|
-
await
|
|
505
|
+
await rm(CACHE_PATH, { recursive: true, force: true });
|
|
483
506
|
this.currentJourney = null;
|
|
484
507
|
this.journeys = [];
|
|
485
508
|
this.active = false;
|
|
@@ -35,6 +35,7 @@ export type ActionInContext = {
|
|
|
35
35
|
isMainFrame: boolean;
|
|
36
36
|
action: Action;
|
|
37
37
|
committed?: boolean;
|
|
38
|
+
title?: string;
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
type Action = {
|
|
@@ -71,7 +72,7 @@ export class SyntheticsGenerator extends JavaScriptLanguageGenerator {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
generateAction(actionInContext: ActionInContext) {
|
|
74
|
-
const { action, pageAlias } = actionInContext;
|
|
75
|
+
const { action, pageAlias, title } = actionInContext;
|
|
75
76
|
if (action.name === 'openPage') {
|
|
76
77
|
return '';
|
|
77
78
|
}
|
|
@@ -88,7 +89,7 @@ export class SyntheticsGenerator extends JavaScriptLanguageGenerator {
|
|
|
88
89
|
if (this.insideStep) {
|
|
89
90
|
formatter.add(this.generateStepEnd());
|
|
90
91
|
}
|
|
91
|
-
formatter.add(this.generateStepStart(actionTitle(action)));
|
|
92
|
+
formatter.add(this.generateStepStart(title || actionTitle(action)));
|
|
92
93
|
} else {
|
|
93
94
|
formatter = new JavaScriptFormatter(this.isSuite ? 4 : 2);
|
|
94
95
|
}
|
|
@@ -264,11 +265,16 @@ function toAssertCall(pageAlias, action) {
|
|
|
264
265
|
const { command, selector, value } = action;
|
|
265
266
|
switch (command) {
|
|
266
267
|
case 'textContent':
|
|
268
|
+
case 'innerText':
|
|
267
269
|
return `expect(await ${pageAlias}.${command}(${quote(
|
|
268
270
|
selector
|
|
269
|
-
)})).
|
|
271
|
+
)})).toMatch(${quote(value)});`;
|
|
270
272
|
case 'isVisible':
|
|
271
273
|
case 'isHidden':
|
|
274
|
+
case 'isChecked':
|
|
275
|
+
case 'isEditable':
|
|
276
|
+
case 'isEnabled':
|
|
277
|
+
case 'isDisabled':
|
|
272
278
|
return `expect(await ${pageAlias}.${command}(${quote(
|
|
273
279
|
selector
|
|
274
280
|
)})).toBeTruthy();`;
|
package/src/helpers.ts
CHANGED
|
@@ -29,14 +29,13 @@ import { resolve, join, dirname } from 'path';
|
|
|
29
29
|
import fs from 'fs';
|
|
30
30
|
import { promisify } from 'util';
|
|
31
31
|
import { performance } from 'perf_hooks';
|
|
32
|
-
import { HooksArgs, HooksCallback } from './common_types';
|
|
32
|
+
import { HooksArgs, HooksCallback, NetworkConditions } from './common_types';
|
|
33
33
|
|
|
34
34
|
const lstatAsync = promisify(fs.lstat);
|
|
35
35
|
const readdirAsync = promisify(fs.readdir);
|
|
36
36
|
|
|
37
37
|
export const readFileAsync = promisify(fs.readFile);
|
|
38
38
|
export const writeFileAsync = promisify(fs.writeFile);
|
|
39
|
-
export const rmdirAsync = promisify(fs.rmdir);
|
|
40
39
|
export const mkdirAsync = promisify(fs.mkdir);
|
|
41
40
|
|
|
42
41
|
const SEPARATOR = '\n';
|
|
@@ -257,3 +256,62 @@ export const CACHE_PATH = join(cwd, '.synthetics', process.pid.toString());
|
|
|
257
256
|
export function getDurationInUs(duration: number) {
|
|
258
257
|
return Math.trunc(duration * 1e6);
|
|
259
258
|
}
|
|
259
|
+
|
|
260
|
+
export function megabitsToBytes(megabytes: number) {
|
|
261
|
+
return (megabytes * 1024 * 1024) / 8;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function bytesToMegabits(bytes: number) {
|
|
265
|
+
return (bytes / 1024 / 1024) * 8;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const DEFAULT_NETWORK_CONDITIONS: NetworkConditions = {
|
|
269
|
+
downloadThroughput: megabitsToBytes(5), // Devtools CDP expects format to be in bytes/second
|
|
270
|
+
uploadThroughput: megabitsToBytes(3), // Devtools CDP expects format to be in bytes/second
|
|
271
|
+
latency: 20, // milliseconds,
|
|
272
|
+
offline: false,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Tranforms CDP dev tools format back to cli args format
|
|
276
|
+
export function formatNetworkConditionsArgs(
|
|
277
|
+
networkConditions: NetworkConditions
|
|
278
|
+
) {
|
|
279
|
+
const d = bytesToMegabits(networkConditions.downloadThroughput);
|
|
280
|
+
const u = bytesToMegabits(networkConditions.uploadThroughput);
|
|
281
|
+
const l = networkConditions.latency;
|
|
282
|
+
return `${d}d/${u}u/${l}l`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export const DEFAULT_NETWORK_CONDITIONS_ARG = formatNetworkConditionsArgs(
|
|
286
|
+
DEFAULT_NETWORK_CONDITIONS
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
export function parseNetworkConditions(args: string): NetworkConditions {
|
|
290
|
+
const uploadToken = 'u';
|
|
291
|
+
const downloadToken = 'd';
|
|
292
|
+
const latencyToken = 'l';
|
|
293
|
+
const networkConditions = {
|
|
294
|
+
...DEFAULT_NETWORK_CONDITIONS,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const conditions = args.split('/');
|
|
298
|
+
|
|
299
|
+
conditions.forEach(condition => {
|
|
300
|
+
const value = condition.slice(0, condition.length - 1);
|
|
301
|
+
const token = condition.slice(-1);
|
|
302
|
+
|
|
303
|
+
switch (token) {
|
|
304
|
+
case uploadToken:
|
|
305
|
+
networkConditions.uploadThroughput = megabitsToBytes(Number(value));
|
|
306
|
+
break;
|
|
307
|
+
case downloadToken:
|
|
308
|
+
networkConditions.downloadThroughput = megabitsToBytes(Number(value));
|
|
309
|
+
break;
|
|
310
|
+
case latencyToken:
|
|
311
|
+
networkConditions.latency = Number(value);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return networkConditions;
|
|
317
|
+
}
|
package/src/index.ts
CHANGED
package/src/parse_args.ts
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { program, Option } from 'commander';
|
|
27
27
|
import { CliArgs } from './common_types';
|
|
28
28
|
import { reporters } from './reporters';
|
|
29
|
+
import { DEFAULT_NETWORK_CONDITIONS_ARG } from './helpers';
|
|
29
30
|
|
|
30
31
|
/* eslint-disable-next-line @typescript-eslint/no-var-requires */
|
|
31
32
|
const { name, version } = require('../package.json');
|
|
@@ -42,11 +43,6 @@ program
|
|
|
42
43
|
'JSON object that gets injected to all journeys',
|
|
43
44
|
JSON.parse
|
|
44
45
|
)
|
|
45
|
-
.option(
|
|
46
|
-
'-s, --suite-params <jsonstring>',
|
|
47
|
-
'DEPRECATED: Use --params instead',
|
|
48
|
-
JSON.parse
|
|
49
|
-
)
|
|
50
46
|
.addOption(
|
|
51
47
|
new Option('--reporter <value>', `output repoter format`).choices(
|
|
52
48
|
Object.keys(reporters)
|
|
@@ -104,6 +100,14 @@ program
|
|
|
104
100
|
'--quiet-exit-code',
|
|
105
101
|
'always return 0 as an exit code status, regardless of test pass / fail. Only return > 0 exit codes on internal errors where the suite could not be run'
|
|
106
102
|
)
|
|
103
|
+
.addOption(
|
|
104
|
+
new Option(
|
|
105
|
+
'--throttling <d/u/l>',
|
|
106
|
+
'List of options to throttle network conditions for download throughput (d) in megabits/second, upload throughput (u) in megabits/second and latency (l) in milliseconds.'
|
|
107
|
+
).default(DEFAULT_NETWORK_CONDITIONS_ARG)
|
|
108
|
+
)
|
|
109
|
+
.option('--no-throttling', 'Turns off default network throttling.')
|
|
110
|
+
.option('--playwright-options <jsonstring>', 'JSON object to pass in custom Playwright options for the agent. Options passed will be merged with Playwright options defined in your synthetics.config.js file. Options defined via --playwright-options take precedence.', JSON.parse)
|
|
107
111
|
.version(version)
|
|
108
112
|
.description('Run synthetic tests');
|
|
109
113
|
|
|
@@ -119,6 +123,7 @@ if (options.richEvents) {
|
|
|
119
123
|
options.ssblocks = true;
|
|
120
124
|
options.network = true;
|
|
121
125
|
options.quietExitCode = true;
|
|
126
|
+
options.trace = true;
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
if (options.capability) {
|