@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +1 -0
  3. package/dist/cli.js +37 -21
  4. package/dist/cli.js.map +1 -1
  5. package/dist/common_types.d.ts +13 -4
  6. package/dist/common_types.d.ts.map +1 -1
  7. package/dist/core/gatherer.d.ts +3 -1
  8. package/dist/core/gatherer.d.ts.map +1 -1
  9. package/dist/core/gatherer.js +29 -3
  10. package/dist/core/gatherer.js.map +1 -1
  11. package/dist/core/runner.d.ts +23 -22
  12. package/dist/core/runner.d.ts.map +1 -1
  13. package/dist/core/runner.js +22 -9
  14. package/dist/core/runner.js.map +1 -1
  15. package/dist/formatter/javascript.d.ts +1 -0
  16. package/dist/formatter/javascript.d.ts.map +1 -1
  17. package/dist/formatter/javascript.js +8 -3
  18. package/dist/formatter/javascript.js.map +1 -1
  19. package/dist/helpers.d.ts +7 -2
  20. package/dist/helpers.d.ts.map +1 -1
  21. package/dist/helpers.js +50 -2
  22. package/dist/helpers.js.map +1 -1
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/parse_args.d.ts.map +1 -1
  26. package/dist/parse_args.js +5 -1
  27. package/dist/parse_args.js.map +1 -1
  28. package/dist/plugins/network.d.ts +1 -5
  29. package/dist/plugins/network.d.ts.map +1 -1
  30. package/dist/reporters/json.d.ts +4 -3
  31. package/dist/reporters/json.d.ts.map +1 -1
  32. package/dist/reporters/json.js +19 -9
  33. package/dist/reporters/json.js.map +1 -1
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +11 -11
  36. package/src/cli.ts +42 -25
  37. package/src/common_types.ts +14 -4
  38. package/src/core/gatherer.ts +39 -4
  39. package/src/core/runner.ts +57 -34
  40. package/src/formatter/javascript.ts +9 -3
  41. package/src/helpers.ts +60 -2
  42. package/src/index.ts +1 -0
  43. package/src/parse_args.ts +10 -5
  44. 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.14",
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.28.3",
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": "^26.0.19",
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": "^3.10.1",
62
- "@typescript-eslint/parser": "^3.10.1",
63
- "eslint": "^7.15.0",
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": "^26.6.3",
65
+ "jest": "^27.4.5",
66
66
  "lint-staged": "^10.5.3",
67
67
  "npm-pack-all": "^1.12.7",
68
- "prettier": "^2.2.1",
69
- "ts-jest": "^26.4.4",
70
- "rimraf": "^3.0.2"
68
+ "prettier": "^2.4.1",
69
+ "rimraf": "^3.0.2",
70
+ "ts-jest": "^27.1.2"
71
71
  },
72
72
  "engines": {
73
- "node": ">12.0.0",
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 = readConfig(environment, options.config);
177
- const params = merge(
178
- config.params,
179
- options.suiteParams || {},
180
- options.params || {}
181
- );
182
- const playwrightOptions = merge(config.playwrightOptions, {
183
- headless: options.headless,
184
- chromiumSandbox: options.sandbox,
185
- ignoreHTTPSErrors: options.ignoreHttpsErrors,
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,
@@ -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
- * @deprecated use params instead
162
- */
163
- suiteParams?: Params;
172
+ throttling?: boolean | string;
173
+ playwrightOptions?: LaunchOptions & BrowserContextOptions;
164
174
  richEvents?: boolean;
165
175
  };
166
176
 
@@ -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
- const { wsEndpoint, playwrightOptions } = options;
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(playwrightOptions);
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(playwrightOptions);
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/
@@ -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 BaseContext = {
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
- type HookType = 'beforeAll' | 'afterAll';
104
- export type SuiteHooks = Record<HookType, Array<HooksCallback>>;
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: { numJourneys: number };
108
- 'journey:register': {
109
- journey: Journey;
118
+ start: {
119
+ numJourneys: number;
120
+ networkConditions?: NetworkConditions;
110
121
  };
111
- 'journey:start': {
122
+ 'journey:register': {
112
123
  journey: Journey;
113
- timestamp: number;
114
- params: Params;
115
124
  };
116
- 'journey:end': BaseContext &
125
+ 'journey:start': JourneyEvent;
126
+ 'journey:end': JourneyEvent &
117
127
  JourneyResult & {
118
- journey: Journey;
128
+ start: number;
129
+ end: number;
119
130
  options: RunOptions;
131
+ timestamp: number;
120
132
  };
121
133
  'journey:end:reported': unknown;
122
- 'step:start': { journey: Journey; step: Step };
123
- 'step:end': StepResult & {
124
- start: number;
125
- end: number;
126
- journey: Journey;
127
- step: Step;
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: monotonicTimeInSeconds(),
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 rmdirAsync(Runner.screenshotPath, { recursive: true });
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', { numJourneys: this.journeys.length });
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 rmdirAsync(CACHE_PATH, { recursive: true });
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
- )})).toBe(${quote(value)});`;
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
@@ -72,3 +72,4 @@ export type { default as Runner } from './core/runner';
72
72
  export type { Reporter, ReporterOptions } from './reporters';
73
73
 
74
74
  export type { SyntheticsConfig } from './common_types';
75
+ export type { ActionInContext } from './formatter/javascript';
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) {