@elastic/synthetics 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/bundles/lib/index.js +432 -0
  2. package/dist/cli.js +3 -3
  3. package/dist/cli.js.map +1 -1
  4. package/dist/common_types.d.ts +12 -2
  5. package/dist/common_types.d.ts.map +1 -1
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +7 -1
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/browser-service.js +2 -2
  11. package/dist/core/browser-service.js.map +1 -1
  12. package/dist/core/expect.d.ts +1 -152
  13. package/dist/core/expect.d.ts.map +1 -1
  14. package/dist/core/expect.js +14 -5
  15. package/dist/core/expect.js.map +1 -1
  16. package/dist/core/gatherer.d.ts +1 -1
  17. package/dist/core/gatherer.d.ts.map +1 -1
  18. package/dist/core/gatherer.js +5 -5
  19. package/dist/core/gatherer.js.map +1 -1
  20. package/dist/core/runner.d.ts.map +1 -1
  21. package/dist/core/runner.js +8 -7
  22. package/dist/core/runner.js.map +1 -1
  23. package/dist/dsl/journey.d.ts +1 -1
  24. package/dist/dsl/journey.d.ts.map +1 -1
  25. package/dist/dsl/monitor.d.ts +4 -0
  26. package/dist/dsl/monitor.d.ts.map +1 -1
  27. package/dist/dsl/monitor.js.map +1 -1
  28. package/dist/helpers.d.ts +0 -5
  29. package/dist/helpers.d.ts.map +1 -1
  30. package/dist/helpers.js +1 -24
  31. package/dist/helpers.js.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/options.d.ts +2 -1
  35. package/dist/options.d.ts.map +1 -1
  36. package/dist/options.js +13 -4
  37. package/dist/options.js.map +1 -1
  38. package/dist/plugins/browser-console.d.ts +3 -2
  39. package/dist/plugins/browser-console.d.ts.map +1 -1
  40. package/dist/plugins/browser-console.js +33 -7
  41. package/dist/plugins/browser-console.js.map +1 -1
  42. package/dist/plugins/network.d.ts +1 -0
  43. package/dist/plugins/network.d.ts.map +1 -1
  44. package/dist/plugins/network.js +15 -2
  45. package/dist/plugins/network.js.map +1 -1
  46. package/dist/push/kibana_api.d.ts.map +1 -1
  47. package/dist/push/kibana_api.js +1 -1
  48. package/dist/push/kibana_api.js.map +1 -1
  49. package/dist/push/monitor.d.ts.map +1 -1
  50. package/dist/push/monitor.js +10 -2
  51. package/dist/push/monitor.js.map +1 -1
  52. package/dist/push/utils.js +1 -1
  53. package/dist/push/utils.js.map +1 -1
  54. package/dist/reporters/base.d.ts +3 -0
  55. package/dist/reporters/base.d.ts.map +1 -1
  56. package/dist/reporters/base.js +20 -22
  57. package/dist/reporters/base.js.map +1 -1
  58. package/dist/reporters/index.d.ts +1 -0
  59. package/dist/reporters/index.d.ts.map +1 -1
  60. package/dist/reporters/index.js.map +1 -1
  61. package/dist/reporters/json.d.ts.map +1 -1
  62. package/dist/reporters/json.js +29 -1
  63. package/dist/reporters/json.js.map +1 -1
  64. package/dist/reporters/junit.d.ts.map +1 -1
  65. package/dist/reporters/junit.js +4 -4
  66. package/dist/reporters/junit.js.map +1 -1
  67. package/dist/reporters/reporter-util.d.ts +35 -0
  68. package/dist/reporters/reporter-util.d.ts.map +1 -0
  69. package/dist/reporters/reporter-util.js +192 -0
  70. package/dist/reporters/reporter-util.js.map +1 -0
  71. package/package.json +15 -7
  72. package/src/cli.ts +3 -3
  73. package/src/common_types.ts +15 -2
  74. package/src/config.ts +12 -1
  75. package/src/core/browser-service.ts +1 -1
  76. package/src/core/expect.ts +13 -191
  77. package/src/core/gatherer.ts +1 -1
  78. package/src/core/runner.ts +15 -9
  79. package/src/dsl/journey.ts +1 -1
  80. package/src/dsl/monitor.ts +4 -0
  81. package/src/helpers.ts +0 -24
  82. package/src/index.ts +1 -1
  83. package/src/options.ts +16 -4
  84. package/src/plugins/browser-console.ts +39 -8
  85. package/src/plugins/network.ts +16 -3
  86. package/src/push/kibana_api.ts +4 -3
  87. package/src/push/monitor.ts +13 -2
  88. package/src/push/utils.ts +1 -1
  89. package/src/reporters/base.ts +26 -33
  90. package/src/reporters/index.ts +5 -1
  91. package/src/reporters/json.ts +33 -3
  92. package/src/reporters/junit.ts +5 -5
  93. package/src/reporters/reporter-util.ts +217 -0
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@elastic/synthetics",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Elastic synthetic monitoring agent",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "clean": "rimraf dist",
9
9
  "prepublish": "npm run clean && npm run build",
10
- "build": "tsc",
10
+ "build:lib": "tsc",
11
11
  "build:locations": "sh utils/update_locations.sh",
12
+ "build": "node utils/build.js",
12
13
  "watch": "tsc -w",
13
14
  "lint": "eslint . --rulesdir utils/eslint-rules",
14
15
  "lint:fix": "npm run lint -- --fix",
@@ -40,32 +41,38 @@
40
41
  "author": "",
41
42
  "license": "MIT",
42
43
  "dependencies": {
44
+ "@babel/code-frame": "^7.22.13",
43
45
  "archiver": "^5.3.1",
44
46
  "commander": "^10.0.1",
45
47
  "deepmerge": "^4.3.1",
46
48
  "enquirer": "^2.3.6",
47
49
  "esbuild": "^0.18.11",
48
- "expect": "^28.1.3",
49
50
  "http-proxy": "^1.18.1",
50
51
  "kleur": "^4.1.5",
51
52
  "micromatch": "^4.0.5",
52
53
  "pirates": "^4.0.5",
53
- "playwright-chromium": "=1.37.0",
54
- "playwright-core": "=1.37.0",
54
+ "playwright": "=1.38.0",
55
+ "playwright-chromium": "=1.38.1",
56
+ "playwright-core": "=1.38.1",
55
57
  "semver": "^7.5.2",
56
- "sharp": "^0.32.0",
58
+ "sharp": "^0.32.6",
57
59
  "snakecase-keys": "^4.0.1",
58
60
  "sonic-boom": "^3.3.0",
59
61
  "source-map-support": "^0.5.21",
62
+ "stack-utils": "^2.0.6",
60
63
  "undici": "^5.21.2",
61
64
  "yaml": "^2.2.2"
62
65
  },
63
66
  "devDependencies": {
67
+ "@commitlint/cli": "^17.7.1",
68
+ "@commitlint/config-conventional": "^17.7.0",
69
+ "@semantic-release/exec": "^6.0.3",
70
+ "@types/babel__code-frame": "^7.0.3",
64
71
  "@types/jest": "^28.1.8",
65
72
  "@types/micromatch": "^4.0.2",
66
73
  "@types/node": "^16.11.59",
67
- "@types/sharp": "^0.28.6",
68
74
  "@types/semver": "^7",
75
+ "@types/stack-utils": "^2.0.1",
69
76
  "@typescript-eslint/eslint-plugin": "^5.38.0",
70
77
  "@typescript-eslint/parser": "^5.38.0",
71
78
  "eslint": "^8.23.1",
@@ -76,6 +83,7 @@
76
83
  "lint-staged": "^10.5.3",
77
84
  "prettier": "^2.7.1",
78
85
  "rimraf": "^3.0.2",
86
+ "semantic-release": "^21.1.1",
79
87
  "ts-jest": "^29.1.1",
80
88
  "typescript": "^5.1.6",
81
89
  "unzipper": "^0.10.11"
package/src/cli.ts CHANGED
@@ -134,7 +134,7 @@ program
134
134
  .action(async (cliArgs: CliArgs) => {
135
135
  const tearDown = await globalSetup(cliArgs, program.args);
136
136
  try {
137
- const options = normalizeOptions(cliArgs, 'run');
137
+ const options = await normalizeOptions(cliArgs, 'run');
138
138
  const results = await run(options);
139
139
  /**
140
140
  * Exit with error status if any journey fails
@@ -196,14 +196,14 @@ program
196
196
  ]);
197
197
  try {
198
198
  const settings = await loadSettings();
199
- const options = normalizeOptions(
199
+ const options = (await normalizeOptions(
200
200
  {
201
201
  ...program.opts(),
202
202
  ...settings,
203
203
  ...cmdOpts,
204
204
  },
205
205
  'push'
206
- ) as PushOptions;
206
+ )) as PushOptions;
207
207
  await validatePush(options, settings);
208
208
  const monitors = runner.buildMonitors(options);
209
209
  if ((options as CliArgs).throttling == null) {
@@ -31,7 +31,7 @@ import {
31
31
  ChromiumBrowserContext,
32
32
  Page,
33
33
  APIRequestContext,
34
- } from 'playwright-chromium';
34
+ } from 'playwright-core';
35
35
  import { Step } from './dsl';
36
36
  import { BuiltInReporterName, ReporterInstance } from './reporters';
37
37
  import { AlertConfig, MonitorConfig } from './dsl/monitor';
@@ -43,6 +43,10 @@ export type Location = {
43
43
  column: number;
44
44
  };
45
45
 
46
+ export type StackFrame = Location & {
47
+ function?: string;
48
+ };
49
+
46
50
  export type Params = Record<string, any>;
47
51
  export type HooksArgs = {
48
52
  env: string;
@@ -213,7 +217,6 @@ type BaseArgs = {
213
217
  schedule?: MonitorConfig['schedule'];
214
218
  locations?: MonitorConfig['locations'];
215
219
  privateLocations?: MonitorConfig['privateLocations'];
216
- alert?: AlertConfig;
217
220
  };
218
221
 
219
222
  export type CliArgs = BaseArgs & {
@@ -244,6 +247,8 @@ export type PushOptions = Partial<ProjectSettings> &
244
247
  auth: string;
245
248
  kibanaVersion?: string;
246
249
  yes?: boolean;
250
+ alert?: AlertConfig;
251
+ retestOnFailure?: MonitorConfig['retestOnFailure'];
247
252
  };
248
253
 
249
254
  export type ProjectSettings = {
@@ -274,6 +279,14 @@ export type JourneyResult = {
274
279
  browserconsole?: PluginOutput['browserconsole'];
275
280
  };
276
281
 
282
+ export type TestError = {
283
+ message: string;
284
+ stack?: string;
285
+ location?: Location;
286
+ // source location highlighting the error source
287
+ source?: string;
288
+ };
289
+
277
290
  export type StepResult = {
278
291
  status: StatusValue;
279
292
  url?: string;
package/src/config.ts CHANGED
@@ -27,7 +27,10 @@ import { isAbsolute, resolve, dirname } from 'path';
27
27
  import { SyntheticsConfig } from './common_types';
28
28
  import { isFile } from './helpers';
29
29
 
30
- export function readConfig(env: string, config?: string): SyntheticsConfig {
30
+ export async function readConfig(
31
+ env: string,
32
+ config?: string
33
+ ): Promise<SyntheticsConfig> {
31
34
  let options = {};
32
35
  const cwd = process.cwd();
33
36
  /**
@@ -47,6 +50,14 @@ export function readConfig(env: string, config?: string): SyntheticsConfig {
47
50
  }
48
51
  if (typeof options === 'function') {
49
52
  options = options(env);
53
+ const optionsPromise = options as Promise<SyntheticsConfig>;
54
+ if (
55
+ optionsPromise != null &&
56
+ typeof optionsPromise.then === 'function' &&
57
+ typeof optionsPromise.catch === 'function'
58
+ ) {
59
+ options = await optionsPromise;
60
+ }
50
61
  }
51
62
  return options;
52
63
  }
@@ -26,7 +26,7 @@
26
26
  import { URL } from 'url';
27
27
  import { createServer } from 'http';
28
28
  import { createProxyServer } from 'http-proxy';
29
- import { chromium } from 'playwright-chromium';
29
+ import { chromium } from 'playwright-core';
30
30
  import { log } from './logger';
31
31
 
32
32
  export class BrowserService {
@@ -23,199 +23,21 @@
23
23
  *
24
24
  */
25
25
 
26
- import expectLib from 'expect';
27
- import type {
28
- AsymmetricMatcher,
29
- MatcherState,
30
- ExpectedAssertionsErrors,
31
- Inverse,
32
- PromiseMatchers,
33
- } from 'expect/build/types';
26
+ /* eslint-disable @typescript-eslint/no-var-requires */
27
+ const expectLib = require('../../dist/bundles/lib/index').expect;
34
28
 
35
- // Copied source code from expect/build/types.d.ts
36
- export declare type Expect = {
37
- <T = unknown>(actual: T): synthetics.Matchers<T>;
38
- assertions(arg0: number): void;
39
- extend(arg0: any): void;
40
- extractExpectedAssertionsErrors: () => ExpectedAssertionsErrors;
41
- getState(): MatcherState;
42
- hasAssertions(): void;
43
- setState(state: Partial<MatcherState>): void;
44
- any(expectedObject: any): AsymmetricMatcher;
45
- anything(): AsymmetricMatcher;
46
- arrayContaining(sample: Array<unknown>): AsymmetricMatcher;
47
- objectContaining(sample: Record<string, unknown>): AsymmetricMatcher;
48
- stringContaining(expected: string): AsymmetricMatcher;
49
- stringMatching(expected: string | RegExp): AsymmetricMatcher;
50
- };
29
+ function notSupported(name: string) {
30
+ throw new Error(`expect.${name} is not supported in @elastic/synthetics.`);
31
+ }
51
32
 
52
33
  /**
53
- * Override matchers from expect in our synthetic namespace to
54
- * reduce support for spy and snapshot matchers which is
55
- * impelemed via jest matchers
56
- *
57
- * We declare the types in global synthetics namespace which allows
58
- * users to extend expect functionality without type errors
34
+ * Exclude toHaveScreenshot and toMatchSnapshot from our custom expect
35
+ * since they are expected to be running inside PW test runner for it to work properly.
59
36
  */
60
- declare global {
61
- export namespace synthetics {
62
- export interface Matchers<R> {
63
- /**
64
- * If you know how to test something, `.not` lets you test its opposite.
65
- */
66
- not: Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
67
- /**
68
- * Use resolves to unwrap the value of a fulfilled promise so any other
69
- * matcher can be chained. If the promise is rejected the assertion fails.
70
- */
71
- resolves: Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
72
- /**
73
- * Unwraps the reason of a rejected promise so any other matcher can be chained.
74
- * If the promise is fulfilled the assertion fails.
75
- */
76
- rejects: Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
77
- /**
78
- * Checks that a value is what you expect. It uses `===` to check strict equality.
79
- * Don't use `toBe` with floating-point numbers.
80
- */
81
- toBe(
82
- expected: unknown
83
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
84
- /**
85
- * Using exact equality with floating point numbers is a bad idea.
86
- * Rounding means that intuitive things fail.
87
- * The default for numDigits is 2.
88
- */
89
- toBeCloseTo(
90
- expected: number,
91
- numDigits?: number
92
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
93
- /**
94
- * Ensure that a variable is not undefined.
95
- */
96
- toBeDefined(): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
97
- /**
98
- * When you don't care what a value is, you just want to
99
- * ensure a value is false in a boolean context.
100
- */
101
- toBeFalsy(): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
102
- /**
103
- * For comparing floating point numbers.
104
- */
105
- toBeGreaterThan(
106
- expected: number | bigint
107
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
108
- /**
109
- * For comparing floating point numbers.
110
- */
111
- toBeGreaterThanOrEqual(
112
- expected: number | bigint
113
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
114
- /**
115
- * Ensure that an object is an instance of a class.
116
- * This matcher uses `instanceof` underneath.
117
- */
118
- /* eslint-disable-next-line */
119
- toBeInstanceOf(
120
- expected: unknown
121
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
122
- /**
123
- * For comparing floating point numbers.
124
- */
125
- toBeLessThan(
126
- expected: number | bigint
127
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
128
- /**
129
- * For comparing floating point numbers.
130
- */
131
- toBeLessThanOrEqual(
132
- expected: number | bigint
133
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
134
- /**
135
- * This is the same as `.toBe(null)` but the error messages are a bit nicer.
136
- * So use `.toBeNull()` when you want to check that something is null.
137
- */
138
- toBeNull(): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
139
- /**
140
- * Use when you don't care what a value is, you just want to ensure a value
141
- * is true in a boolean context. In JavaScript, there are six falsy values:
142
- * `false`, `0`, `''`, `null`, `undefined`, and `NaN`. Everything else is truthy.
143
- */
144
- toBeTruthy(): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
145
- /**
146
- * Used to check that a variable is undefined.
147
- */
148
- toBeUndefined(): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
149
- /**
150
- * Used to check that a variable is NaN.
151
- */
152
- toBeNaN(): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
153
- /**
154
- * Used when you want to check that an item is in a list.
155
- * For testing the items in the list, this uses `===`, a strict equality check.
156
- */
157
- toContain(
158
- expected: unknown
159
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
160
- /**
161
- * Used when you want to check that an item is in a list.
162
- * For testing the items in the list, this matcher recursively checks the
163
- * equality of all fields, rather than checking for object identity.
164
- */
165
- toContainEqual(
166
- expected: unknown
167
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
168
- /**
169
- * Used when you want to check that two objects have the same value.
170
- * This matcher recursively checks the equality of all fields, rather than checking for object identity.
171
- */
172
- toEqual(
173
- expected: unknown
174
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
175
- /**
176
- * Used to check that an object has a `.length` property
177
- * and it is set to a certain numeric value.
178
- */
179
- toHaveLength(
180
- expected: number
181
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
182
- /**
183
- * Use to check if property at provided reference keyPath exists for an object.
184
- * For checking deeply nested properties in an object you may use dot notation or an array containing
185
- * the keyPath for deep references.
186
- *
187
- * Optionally, you can provide a value to check if it's equal to the value present at keyPath
188
- * on the target object. This matcher uses 'deep equality' (like `toEqual()`) and recursively checks
189
- * the equality of all fields.
190
- *
191
- * @example
192
- *
193
- * expect(houseForSale).toHaveProperty('kitchen.area', 20);
194
- */
195
- toHaveProperty(
196
- keyPath: string | Array<string>,
197
- value?: unknown
198
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
199
- /**
200
- * Check that a string matches a regular expression.
201
- */
202
- toMatch(
203
- expected: string | RegExp
204
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
205
- /**
206
- * Used to check that a JavaScript object matches a subset of the properties of an object
207
- */
208
- toMatchObject(
209
- expected: Record<string, unknown> | Array<unknown>
210
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
211
- /**
212
- * Use to test that objects have the same types as well as structure.
213
- */
214
- toStrictEqual(
215
- expected: unknown
216
- ): Matchers<R> & Inverse<Matchers<R>> & PromiseMatchers;
217
- }
218
- }
219
- }
37
+ expectLib.extend({
38
+ toHaveScreenshot: () => notSupported('toHaveScreenshot'),
39
+ toMatchSnapshot: () => notSupported('toMatchSnapshot'),
40
+ });
220
41
 
221
- export const expect: Expect = expectLib;
42
+ export const expect: typeof import('../../bundles/node_modules/playwright/types/test').expect =
43
+ expectLib;
@@ -29,7 +29,7 @@ import {
29
29
  BrowserContext,
30
30
  request as apiRequest,
31
31
  selectors,
32
- } from 'playwright-chromium';
32
+ } from 'playwright-core';
33
33
  import { PluginManager } from '../plugins';
34
34
  import { log } from './logger';
35
35
  import { Driver, NetworkConditions, RunOptions } from '../common_types';
@@ -46,8 +46,11 @@ import {
46
46
  StepResult,
47
47
  PushOptions,
48
48
  } from '../common_types';
49
- import { PluginManager } from '../plugins';
50
- import { PerformanceManager } from '../plugins';
49
+ import {
50
+ PluginManager,
51
+ PerformanceManager,
52
+ filterBrowserMessages,
53
+ } from '../plugins';
51
54
  import { Gatherer } from './gatherer';
52
55
  import { log } from './logger';
53
56
  import { Monitor, MonitorConfig } from '../dsl/monitor';
@@ -149,12 +152,12 @@ export default class Runner {
149
152
  * Set up the corresponding reporter and fallback
150
153
  * to default reporter if not provided
151
154
  */
152
- const { reporter, outfd } = options;
155
+ const { reporter, outfd, dryRun } = options;
153
156
  const Reporter =
154
157
  typeof reporter === 'function'
155
158
  ? reporter
156
159
  : reporters[reporter] || reporters['default'];
157
- this.#reporter = new Reporter({ fd: outfd });
160
+ this.#reporter = new Reporter({ fd: outfd, dryRun });
158
161
  }
159
162
 
160
163
  async runBeforeAllHook(args: HooksArgs) {
@@ -209,8 +212,9 @@ export default class Runner {
209
212
  */
210
213
  pluginManager.onStep(step);
211
214
  traceEnabled && (await pluginManager.start('trace'));
212
- // call the step definition
213
- await step.callback();
215
+ // invoke the step callback by extracting to a variable to get better stack trace
216
+ const cb = step.callback;
217
+ await cb();
214
218
  } catch (error) {
215
219
  data.status = 'failed';
216
220
  data.error = error;
@@ -288,8 +292,7 @@ export default class Runner {
288
292
  params,
289
293
  });
290
294
  /**
291
- * Exeucute the journey callback which would
292
- * register the steps for the current journey
295
+ * Exeucute the journey callback which registers the steps for current journey
293
296
  */
294
297
  journey.callback({ ...context.driver, params });
295
298
  }
@@ -310,7 +313,10 @@ export default class Runner {
310
313
  timestamp: getTimestamp(),
311
314
  options,
312
315
  ...pluginOutput,
313
- browserconsole: status == 'failed' ? pluginOutput.browserconsole : [],
316
+ browserconsole: filterBrowserMessages(
317
+ pluginOutput.browserconsole,
318
+ status
319
+ ),
314
320
  });
315
321
  // clear screenshots cache after each journey
316
322
  await rm(Runner.screenshotPath, { recursive: true, force: true });
@@ -29,7 +29,7 @@ import {
29
29
  BrowserContext,
30
30
  CDPSession,
31
31
  APIRequestContext,
32
- } from 'playwright-chromium';
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';
@@ -72,6 +72,10 @@ export type MonitorConfig = {
72
72
  params?: Params;
73
73
  playwrightOptions?: PlaywrightOptions;
74
74
  alert?: AlertConfig;
75
+ /**
76
+ * By default, the monitor will be retested on failure
77
+ */
78
+ retestOnFailure?: boolean;
75
79
  };
76
80
 
77
81
  type MonitorFilter = {
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
@@ -55,7 +55,7 @@ export type {
55
55
  CDPSession,
56
56
  APIRequestContext,
57
57
  devices,
58
- } from 'playwright-chromium';
58
+ } from 'playwright-core';
59
59
 
60
60
  /**
61
61
  * Export the types necessary to write custom reporters
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 ?? playwrightOpts?.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 defaultMessageLimit = 1000;
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 === 'error' || type === 'warning') {
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 pageErrorEventListener = (error: Error) => {
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 > defaultMessageLimit) {
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.page.on('pageerror', this.pageErrorEventListener);
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.page.off('pageerror', this.pageErrorEventListener);
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
+ }