@chaos-maker/playwright 0.4.0 → 0.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.
package/README.md CHANGED
@@ -59,6 +59,27 @@ test('handles slow network', async ({ page, chaos }) => {
59
59
 
60
60
  ### With Presets
61
61
 
62
+ Drop a built-in preset by name with the declarative `presets` field:
63
+
64
+ ```ts
65
+ await injectChaos(page, { presets: ['slow-api'] });
66
+ ```
67
+
68
+ Register your own bundle inline via `customPresets`:
69
+
70
+ ```ts
71
+ await injectChaos(page, {
72
+ customPresets: {
73
+ 'team-flow': {
74
+ network: { failures: [{ urlPattern: '/checkout', statusCode: 503, probability: 1 }] },
75
+ },
76
+ },
77
+ presets: ['team-flow'],
78
+ });
79
+ ```
80
+
81
+ The legacy spread style still works for migration:
82
+
62
83
  ```ts
63
84
  import { test, expect } from '@playwright/test';
64
85
  import { presets } from '@chaos-maker/core';
@@ -90,6 +111,49 @@ test('checkout handles combined chaos', async ({ page }) => {
90
111
  });
91
112
  ```
92
113
 
114
+ ### Rule Groups
115
+
116
+ Use Rule Groups to toggle a set of rules during a test without reinjecting chaos.
117
+
118
+ ```ts
119
+ import { test, expect } from '@playwright/test';
120
+ import { ChaosConfigBuilder } from '@chaos-maker/core';
121
+ import {
122
+ injectChaos,
123
+ enableGroup,
124
+ disableGroup,
125
+ enableSWGroup,
126
+ disableSWGroup,
127
+ } from '@chaos-maker/playwright';
128
+
129
+ test('toggles checkout chaos', async ({ page }) => {
130
+ const config = new ChaosConfigBuilder()
131
+ .defineGroup('payments', { enabled: false })
132
+ .inGroup('payments')
133
+ .failRequests('/api/pay', 503, 1)
134
+ .build();
135
+
136
+ await injectChaos(page, config);
137
+ await page.goto('/checkout');
138
+
139
+ await enableGroup(page, 'payments');
140
+ await expect(page.getByText('Try again')).toBeVisible();
141
+
142
+ await disableGroup(page, 'payments');
143
+ });
144
+ ```
145
+
146
+ With the fixture, the same helpers are available as `chaos.enableGroup(name)` and `chaos.disableGroup(name)`.
147
+
148
+ For Service Worker rules, use the SW helpers after `injectSWChaos`:
149
+
150
+ ```ts
151
+ await enableSWGroup(page, 'payments');
152
+ await disableSWGroup(page, 'payments');
153
+ ```
154
+
155
+ Browser-side `enableGroup` and `disableGroup` affect page rules from `injectChaos`. `enableSWGroup` and `disableSWGroup` affect Service Worker rules from `injectSWChaos`.
156
+
93
157
  ### SSE and GraphQL
94
158
 
95
159
  ```ts
@@ -133,6 +197,14 @@ Stop chaos and restore original `fetch`/`XHR`/DOM behavior.
133
197
 
134
198
  Retrieve the chaos event log from the page. Returns `ChaosEvent[]` — every chaos check emitted since injection, with `applied: true/false`.
135
199
 
200
+ ### `enableGroup(page, name)` / `disableGroup(page, name)`
201
+
202
+ Toggle a browser-side Rule Group at runtime.
203
+
204
+ ### `enableSWGroup(page, name, opts?)` / `disableSWGroup(page, name, opts?)`
205
+
206
+ Toggle a Service Worker Rule Group at runtime. Pass `opts.timeoutMs` to override the Service Worker acknowledgement timeout.
207
+
136
208
  ### Fixture: `chaos`
137
209
 
138
210
  Available when importing `test` from `@chaos-maker/playwright/fixture`:
@@ -140,6 +212,29 @@ Available when importing `test` from `@chaos-maker/playwright/fixture`:
140
212
  - `chaos.inject(config)` — same as `injectChaos(page, config)`
141
213
  - `chaos.remove()` — same as `removeChaos(page)` (also called automatically after each test)
142
214
  - `chaos.getLog()` — same as `getChaosLog(page)`
215
+ - `chaos.enableGroup(name)` — same as `enableGroup(page, name)`
216
+ - `chaos.disableGroup(name)` — same as `disableGroup(page, name)`
217
+ - `chaos.enableSWGroup(name, opts?)` — same as `enableSWGroup(page, name, opts)`
218
+ - `chaos.disableSWGroup(name, opts?)` — same as `disableSWGroup(page, name, opts)`
219
+
220
+ ## Validation
221
+
222
+ `injectChaos` validates the config from Node BEFORE any page touch. A malformed config throws `ChaosConfigError` synchronously from the test runner — your test fails before navigation, not in the browser console. `ChaosConfigError.issues` is a structured `ValidationIssue[]` with `path`, `code`, `ruleType`, and optional `expected` / `received`. See the [Rule Validation concept page](https://chaos-maker-dev.github.io/chaos-maker/concepts/validation/).
223
+
224
+ ```ts
225
+ import { injectChaos, ChaosConfigError } from '@chaos-maker/playwright';
226
+
227
+ try {
228
+ await injectChaos(page, config, {
229
+ validation: { unknownFields: 'warn' },
230
+ });
231
+ } catch (e) {
232
+ if (e instanceof ChaosConfigError) {
233
+ for (const issue of e.issues) console.error(issue.path, issue.code, issue.message);
234
+ }
235
+ throw e;
236
+ }
237
+ ```
143
238
 
144
239
  ## Debugging with trace
145
240
 
@@ -199,18 +294,29 @@ importScripts('/chaos-maker-sw.js');
199
294
  ```
200
295
 
201
296
  ```ts
202
- import { injectSWChaos, removeSWChaos, getSWChaosLog } from '@chaos-maker/playwright';
297
+ import {
298
+ injectSWChaos,
299
+ removeSWChaos,
300
+ getSWChaosLog,
301
+ enableSWGroup,
302
+ disableSWGroup,
303
+ } from '@chaos-maker/playwright';
203
304
 
204
305
  test('SW-fetched /api returns 503', async ({ page }) => {
205
306
  await page.goto('/app-with-sw/');
206
307
  // wait for controller after your app's SW registration
207
308
  await injectSWChaos(page, {
208
- network: { failures: [{ urlPattern: '/api/data', statusCode: 503, probability: 1 }] },
309
+ groups: [{ name: 'payments', enabled: false }],
310
+ network: {
311
+ failures: [{ urlPattern: '/api/data', statusCode: 503, probability: 1, group: 'payments' }],
312
+ },
209
313
  seed: 1,
210
314
  });
315
+ await enableSWGroup(page, 'payments');
211
316
  await page.click('#trigger');
212
317
  const log = await getSWChaosLog(page);
213
318
  expect(log.some(e => e.type === 'network:failure' && e.applied)).toBe(true);
319
+ await disableSWGroup(page, 'payments');
214
320
  await removeSWChaos(page);
215
321
  });
216
322
  ```
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { serializeForTransport } from "@chaos-maker/core";
2
+ import { serializeForTransport, validateChaosConfig as validateChaosConfig2 } from "@chaos-maker/core";
3
3
  import { resolve, dirname } from "path";
4
4
  import { createRequire } from "module";
5
5
  import { fileURLToPath } from "url";
@@ -50,6 +50,7 @@ function truncate(s, max) {
50
50
  return `\u2026${s.slice(-(max - 1))}`;
51
51
  }
52
52
  function shouldEmitStep(event, verbose) {
53
+ if (event.type === "debug") return false;
53
54
  if (event.applied) return true;
54
55
  return verbose;
55
56
  }
@@ -108,9 +109,14 @@ async function createTraceReporter(page, testInfo, opts = {}) {
108
109
  };
109
110
  }
110
111
 
112
+ // src/index.ts
113
+ import { Logger } from "@chaos-maker/core";
114
+ import { validateChaosConfig as validateChaosConfig3, ChaosConfigError } from "@chaos-maker/core";
115
+
111
116
  // src/sw.ts
112
- import { validateConfig, SW_BRIDGE_SOURCE } from "@chaos-maker/core";
117
+ import { validateChaosConfig, SW_BRIDGE_SOURCE } from "@chaos-maker/core";
113
118
  var BRIDGE_INIT_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.sw.bridgeInit");
119
+ var DEFAULT_SW_TOGGLE_TIMEOUT = 2e3;
114
120
  async function ensurePageBridge(page) {
115
121
  if (!page[BRIDGE_INIT_KEY]) {
116
122
  await page.addInitScript({ content: SW_BRIDGE_SOURCE });
@@ -120,7 +126,7 @@ async function ensurePageBridge(page) {
120
126
  });
121
127
  }
122
128
  async function injectSWChaos(page, config, opts = {}) {
123
- const validated = validateConfig(config);
129
+ const validated = validateChaosConfig(config, opts.validation);
124
130
  const timeoutMs = opts.timeoutMs ?? 1e4;
125
131
  await ensurePageBridge(page);
126
132
  const result = await page.evaluate(
@@ -146,6 +152,38 @@ async function removeSWChaos(page, opts = {}) {
146
152
  ).catch(() => {
147
153
  });
148
154
  }
155
+ async function enableSWGroup(page, name, opts = {}) {
156
+ if (typeof name !== "string") {
157
+ throw new Error("[chaos-maker] group name must be a string");
158
+ }
159
+ const nameNorm = name.trim();
160
+ if (!nameNorm) {
161
+ throw new Error("[chaos-maker] group name cannot be empty");
162
+ }
163
+ await toggleSWGroup(page, nameNorm, true, opts);
164
+ }
165
+ async function disableSWGroup(page, name, opts = {}) {
166
+ if (typeof name !== "string") {
167
+ throw new Error("[chaos-maker] group name must be a string");
168
+ }
169
+ const nameNorm = name.trim();
170
+ if (!nameNorm) {
171
+ throw new Error("[chaos-maker] group name cannot be empty");
172
+ }
173
+ await toggleSWGroup(page, nameNorm, false, opts);
174
+ }
175
+ async function toggleSWGroup(page, name, enabled, opts) {
176
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SW_TOGGLE_TIMEOUT;
177
+ await ensurePageBridge(page);
178
+ await page.evaluate(
179
+ async ({ n, e, t }) => {
180
+ const bridge = globalThis.__chaosMakerSWBridge;
181
+ if (!bridge) throw new Error("[chaos-maker] SW bridge missing \u2014 ensurePageBridge failed");
182
+ await bridge.toggleGroup(n, e, t);
183
+ },
184
+ { n: name, e: enabled, t: timeoutMs }
185
+ );
186
+ }
149
187
  async function getSWChaosLog(page) {
150
188
  return page.evaluate(() => {
151
189
  const bridge = globalThis.__chaosMakerSWBridge;
@@ -178,6 +216,7 @@ function getCoreUmdPath() {
178
216
  }
179
217
  var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
180
218
  async function injectChaos(page, config, opts = {}) {
219
+ const validated = validateChaosConfig2(config, opts.validation);
181
220
  const umdPath = getCoreUmdPath();
182
221
  const tracingEnabled = resolveTracing(opts);
183
222
  if (tracingEnabled) {
@@ -192,7 +231,7 @@ async function injectChaos(page, config, opts = {}) {
192
231
  page[TRACE_HANDLE_KEY] = handle;
193
232
  }
194
233
  }
195
- const serialized = serializeForTransport(config);
234
+ const serialized = serializeForTransport(validated);
196
235
  await page.addInitScript((cfg) => {
197
236
  const win = globalThis;
198
237
  win.__CHAOS_CONFIG__ = cfg;
@@ -234,6 +273,50 @@ async function getChaosLog(page) {
234
273
  return [];
235
274
  });
236
275
  }
276
+ async function enableGroup(page, name) {
277
+ if (typeof name !== "string") {
278
+ throw new Error("[chaos-maker] group name must be a string");
279
+ }
280
+ const nameNorm = name.trim();
281
+ if (!nameNorm) {
282
+ throw new Error("[chaos-maker] group name cannot be empty");
283
+ }
284
+ await page.evaluate(({ n }) => {
285
+ const utils = globalThis.chaosUtils;
286
+ if (!utils || !utils.instance) {
287
+ throw new Error("[chaos-maker] no chaos instance on page \u2014 call injectChaos first");
288
+ }
289
+ if (typeof utils.enableGroup !== "function") {
290
+ throw new Error("[chaos-maker] enableGroup API unavailable");
291
+ }
292
+ const result = utils.enableGroup(n);
293
+ if (result && result.success === false) {
294
+ throw new Error(`[chaos-maker] enableGroup('${n}') failed: ${result.message}`);
295
+ }
296
+ }, { n: nameNorm });
297
+ }
298
+ async function disableGroup(page, name) {
299
+ if (typeof name !== "string") {
300
+ throw new Error("[chaos-maker] group name must be a string");
301
+ }
302
+ const nameNorm = name.trim();
303
+ if (!nameNorm) {
304
+ throw new Error("[chaos-maker] group name cannot be empty");
305
+ }
306
+ await page.evaluate(({ n }) => {
307
+ const utils = globalThis.chaosUtils;
308
+ if (!utils || !utils.instance) {
309
+ throw new Error("[chaos-maker] no chaos instance on page \u2014 call injectChaos first");
310
+ }
311
+ if (typeof utils.disableGroup !== "function") {
312
+ throw new Error("[chaos-maker] disableGroup API unavailable");
313
+ }
314
+ const result = utils.disableGroup(n);
315
+ if (result && result.success === false) {
316
+ throw new Error(`[chaos-maker] disableGroup('${n}') failed: ${result.message}`);
317
+ }
318
+ }, { n: nameNorm });
319
+ }
237
320
  async function getChaosSeed(page) {
238
321
  return page.evaluate(() => {
239
322
  const win = globalThis;
@@ -247,10 +330,17 @@ async function getChaosSeed(page) {
247
330
  export {
248
331
  injectSWChaos,
249
332
  removeSWChaos,
333
+ enableSWGroup,
334
+ disableSWGroup,
250
335
  getSWChaosLog,
251
336
  getSWChaosLogFromSW,
252
337
  injectChaos,
253
338
  removeChaos,
254
339
  getChaosLog,
255
- getChaosSeed
340
+ enableGroup,
341
+ disableGroup,
342
+ getChaosSeed,
343
+ Logger,
344
+ validateChaosConfig3 as validateChaosConfig,
345
+ ChaosConfigError
256
346
  };
package/dist/fixture.cjs CHANGED
@@ -78,6 +78,7 @@ function truncate(s, max) {
78
78
  return `\u2026${s.slice(-(max - 1))}`;
79
79
  }
80
80
  function shouldEmitStep(event, verbose) {
81
+ if (event.type === "debug") return false;
81
82
  if (event.applied) return true;
82
83
  return verbose;
83
84
  }
@@ -136,8 +137,54 @@ async function createTraceReporter(page, testInfo, opts = {}) {
136
137
  };
137
138
  }
138
139
 
140
+ // src/index.ts
141
+ var import_core3 = require("@chaos-maker/core");
142
+ var import_core4 = require("@chaos-maker/core");
143
+
139
144
  // src/sw.ts
140
145
  var import_core = require("@chaos-maker/core");
146
+ var BRIDGE_INIT_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.sw.bridgeInit");
147
+ var DEFAULT_SW_TOGGLE_TIMEOUT = 2e3;
148
+ async function ensurePageBridge(page) {
149
+ if (!page[BRIDGE_INIT_KEY]) {
150
+ await page.addInitScript({ content: import_core.SW_BRIDGE_SOURCE });
151
+ page[BRIDGE_INIT_KEY] = true;
152
+ }
153
+ await page.evaluate(import_core.SW_BRIDGE_SOURCE).catch(() => {
154
+ });
155
+ }
156
+ async function enableSWGroup(page, name, opts = {}) {
157
+ if (typeof name !== "string") {
158
+ throw new Error("[chaos-maker] group name must be a string");
159
+ }
160
+ const nameNorm = name.trim();
161
+ if (!nameNorm) {
162
+ throw new Error("[chaos-maker] group name cannot be empty");
163
+ }
164
+ await toggleSWGroup(page, nameNorm, true, opts);
165
+ }
166
+ async function disableSWGroup(page, name, opts = {}) {
167
+ if (typeof name !== "string") {
168
+ throw new Error("[chaos-maker] group name must be a string");
169
+ }
170
+ const nameNorm = name.trim();
171
+ if (!nameNorm) {
172
+ throw new Error("[chaos-maker] group name cannot be empty");
173
+ }
174
+ await toggleSWGroup(page, nameNorm, false, opts);
175
+ }
176
+ async function toggleSWGroup(page, name, enabled, opts) {
177
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SW_TOGGLE_TIMEOUT;
178
+ await ensurePageBridge(page);
179
+ await page.evaluate(
180
+ async ({ n, e, t }) => {
181
+ const bridge = globalThis.__chaosMakerSWBridge;
182
+ if (!bridge) throw new Error("[chaos-maker] SW bridge missing \u2014 ensurePageBridge failed");
183
+ await bridge.toggleGroup(n, e, t);
184
+ },
185
+ { n: name, e: enabled, t: timeoutMs }
186
+ );
187
+ }
141
188
 
142
189
  // src/index.ts
143
190
  var import_meta = {};
@@ -153,6 +200,7 @@ function getCoreUmdPath() {
153
200
  }
154
201
  var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
155
202
  async function injectChaos(page, config, opts = {}) {
203
+ const validated = (0, import_core2.validateChaosConfig)(config, opts.validation);
156
204
  const umdPath = getCoreUmdPath();
157
205
  const tracingEnabled = resolveTracing(opts);
158
206
  if (tracingEnabled) {
@@ -167,7 +215,7 @@ async function injectChaos(page, config, opts = {}) {
167
215
  page[TRACE_HANDLE_KEY] = handle;
168
216
  }
169
217
  }
170
- const serialized = (0, import_core2.serializeForTransport)(config);
218
+ const serialized = (0, import_core2.serializeForTransport)(validated);
171
219
  await page.addInitScript((cfg) => {
172
220
  const win = globalThis;
173
221
  win.__CHAOS_CONFIG__ = cfg;
@@ -209,6 +257,50 @@ async function getChaosLog(page) {
209
257
  return [];
210
258
  });
211
259
  }
260
+ async function enableGroup(page, name) {
261
+ if (typeof name !== "string") {
262
+ throw new Error("[chaos-maker] group name must be a string");
263
+ }
264
+ const nameNorm = name.trim();
265
+ if (!nameNorm) {
266
+ throw new Error("[chaos-maker] group name cannot be empty");
267
+ }
268
+ await page.evaluate(({ n }) => {
269
+ const utils = globalThis.chaosUtils;
270
+ if (!utils || !utils.instance) {
271
+ throw new Error("[chaos-maker] no chaos instance on page \u2014 call injectChaos first");
272
+ }
273
+ if (typeof utils.enableGroup !== "function") {
274
+ throw new Error("[chaos-maker] enableGroup API unavailable");
275
+ }
276
+ const result = utils.enableGroup(n);
277
+ if (result && result.success === false) {
278
+ throw new Error(`[chaos-maker] enableGroup('${n}') failed: ${result.message}`);
279
+ }
280
+ }, { n: nameNorm });
281
+ }
282
+ async function disableGroup(page, name) {
283
+ if (typeof name !== "string") {
284
+ throw new Error("[chaos-maker] group name must be a string");
285
+ }
286
+ const nameNorm = name.trim();
287
+ if (!nameNorm) {
288
+ throw new Error("[chaos-maker] group name cannot be empty");
289
+ }
290
+ await page.evaluate(({ n }) => {
291
+ const utils = globalThis.chaosUtils;
292
+ if (!utils || !utils.instance) {
293
+ throw new Error("[chaos-maker] no chaos instance on page \u2014 call injectChaos first");
294
+ }
295
+ if (typeof utils.disableGroup !== "function") {
296
+ throw new Error("[chaos-maker] disableGroup API unavailable");
297
+ }
298
+ const result = utils.disableGroup(n);
299
+ if (result && result.success === false) {
300
+ throw new Error(`[chaos-maker] disableGroup('${n}') failed: ${result.message}`);
301
+ }
302
+ }, { n: nameNorm });
303
+ }
212
304
  async function getChaosSeed(page) {
213
305
  return page.evaluate(() => {
214
306
  const win = globalThis;
@@ -247,7 +339,11 @@ var test2 = import_test2.test.extend({
247
339
  },
248
340
  remove: () => removeChaos(page),
249
341
  getLog: () => getChaosLog(page),
250
- getSeed: () => getChaosSeed(page)
342
+ getSeed: () => getChaosSeed(page),
343
+ enableGroup: (name) => enableGroup(page, name),
344
+ disableGroup: (name) => disableGroup(page, name),
345
+ enableSWGroup: (name, opts) => enableSWGroup(page, name, opts),
346
+ disableSWGroup: (name, opts) => disableSWGroup(page, name, opts)
251
347
  };
252
348
  await use(fixture);
253
349
  await removeChaos(page);
@@ -2,13 +2,17 @@ import * as _playwright_test from '@playwright/test';
2
2
  export { expect } from '@playwright/test';
3
3
  import { ChaosConfig, ChaosEvent } from '@chaos-maker/core';
4
4
  export { ChaosConfig, ChaosEvent } from '@chaos-maker/core';
5
- import { InjectChaosOptions } from './index.cjs';
5
+ import { InjectChaosOptions, SWChaosOptions } from './index.cjs';
6
6
 
7
7
  interface ChaosFixture {
8
8
  inject: (config: ChaosConfig, opts?: InjectChaosOptions) => Promise<void>;
9
9
  remove: () => Promise<void>;
10
10
  getLog: () => Promise<ChaosEvent[]>;
11
11
  getSeed: () => Promise<number | null>;
12
+ enableGroup: (name: string) => Promise<void>;
13
+ disableGroup: (name: string) => Promise<void>;
14
+ enableSWGroup: (name: string, opts?: SWChaosOptions) => Promise<void>;
15
+ disableSWGroup: (name: string, opts?: SWChaosOptions) => Promise<void>;
12
16
  }
13
17
  /**
14
18
  * Extended Playwright test with a `chaos` fixture.
package/dist/fixture.d.ts CHANGED
@@ -2,13 +2,17 @@ import * as _playwright_test from '@playwright/test';
2
2
  export { expect } from '@playwright/test';
3
3
  import { ChaosConfig, ChaosEvent } from '@chaos-maker/core';
4
4
  export { ChaosConfig, ChaosEvent } from '@chaos-maker/core';
5
- import { InjectChaosOptions } from './index.js';
5
+ import { InjectChaosOptions, SWChaosOptions } from './index.js';
6
6
 
7
7
  interface ChaosFixture {
8
8
  inject: (config: ChaosConfig, opts?: InjectChaosOptions) => Promise<void>;
9
9
  remove: () => Promise<void>;
10
10
  getLog: () => Promise<ChaosEvent[]>;
11
11
  getSeed: () => Promise<number | null>;
12
+ enableGroup: (name: string) => Promise<void>;
13
+ disableGroup: (name: string) => Promise<void>;
14
+ enableSWGroup: (name: string, opts?: SWChaosOptions) => Promise<void>;
15
+ disableSWGroup: (name: string, opts?: SWChaosOptions) => Promise<void>;
12
16
  }
13
17
  /**
14
18
  * Extended Playwright test with a `chaos` fixture.
package/dist/fixture.js CHANGED
@@ -1,9 +1,13 @@
1
1
  import {
2
+ disableGroup,
3
+ disableSWGroup,
4
+ enableGroup,
5
+ enableSWGroup,
2
6
  getChaosLog,
3
7
  getChaosSeed,
4
8
  injectChaos,
5
9
  removeChaos
6
- } from "./chunk-KHKPTES5.js";
10
+ } from "./chunk-IJDMHBUQ.js";
7
11
 
8
12
  // src/fixture.ts
9
13
  import { test as base } from "@playwright/test";
@@ -34,7 +38,11 @@ var test = base.extend({
34
38
  },
35
39
  remove: () => removeChaos(page),
36
40
  getLog: () => getChaosLog(page),
37
- getSeed: () => getChaosSeed(page)
41
+ getSeed: () => getChaosSeed(page),
42
+ enableGroup: (name) => enableGroup(page, name),
43
+ disableGroup: (name) => disableGroup(page, name),
44
+ enableSWGroup: (name, opts) => enableSWGroup(page, name, opts),
45
+ disableSWGroup: (name, opts) => disableSWGroup(page, name, opts)
38
46
  };
39
47
  await use(fixture);
40
48
  await removeChaos(page);
package/dist/index.cjs CHANGED
@@ -20,6 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ChaosConfigError: () => import_core4.ChaosConfigError,
24
+ Logger: () => import_core3.Logger,
25
+ disableGroup: () => disableGroup,
26
+ disableSWGroup: () => disableSWGroup,
27
+ enableGroup: () => enableGroup,
28
+ enableSWGroup: () => enableSWGroup,
23
29
  getChaosLog: () => getChaosLog,
24
30
  getChaosSeed: () => getChaosSeed,
25
31
  getSWChaosLog: () => getSWChaosLog,
@@ -27,7 +33,8 @@ __export(index_exports, {
27
33
  injectChaos: () => injectChaos,
28
34
  injectSWChaos: () => injectSWChaos,
29
35
  removeChaos: () => removeChaos,
30
- removeSWChaos: () => removeSWChaos
36
+ removeSWChaos: () => removeSWChaos,
37
+ validateChaosConfig: () => import_core4.validateChaosConfig
31
38
  });
32
39
  module.exports = __toCommonJS(index_exports);
33
40
  var import_core2 = require("@chaos-maker/core");
@@ -81,6 +88,7 @@ function truncate(s, max) {
81
88
  return `\u2026${s.slice(-(max - 1))}`;
82
89
  }
83
90
  function shouldEmitStep(event, verbose) {
91
+ if (event.type === "debug") return false;
84
92
  if (event.applied) return true;
85
93
  return verbose;
86
94
  }
@@ -139,9 +147,14 @@ async function createTraceReporter(page, testInfo, opts = {}) {
139
147
  };
140
148
  }
141
149
 
150
+ // src/index.ts
151
+ var import_core3 = require("@chaos-maker/core");
152
+ var import_core4 = require("@chaos-maker/core");
153
+
142
154
  // src/sw.ts
143
155
  var import_core = require("@chaos-maker/core");
144
156
  var BRIDGE_INIT_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.sw.bridgeInit");
157
+ var DEFAULT_SW_TOGGLE_TIMEOUT = 2e3;
145
158
  async function ensurePageBridge(page) {
146
159
  if (!page[BRIDGE_INIT_KEY]) {
147
160
  await page.addInitScript({ content: import_core.SW_BRIDGE_SOURCE });
@@ -151,7 +164,7 @@ async function ensurePageBridge(page) {
151
164
  });
152
165
  }
153
166
  async function injectSWChaos(page, config, opts = {}) {
154
- const validated = (0, import_core.validateConfig)(config);
167
+ const validated = (0, import_core.validateChaosConfig)(config, opts.validation);
155
168
  const timeoutMs = opts.timeoutMs ?? 1e4;
156
169
  await ensurePageBridge(page);
157
170
  const result = await page.evaluate(
@@ -177,6 +190,38 @@ async function removeSWChaos(page, opts = {}) {
177
190
  ).catch(() => {
178
191
  });
179
192
  }
193
+ async function enableSWGroup(page, name, opts = {}) {
194
+ if (typeof name !== "string") {
195
+ throw new Error("[chaos-maker] group name must be a string");
196
+ }
197
+ const nameNorm = name.trim();
198
+ if (!nameNorm) {
199
+ throw new Error("[chaos-maker] group name cannot be empty");
200
+ }
201
+ await toggleSWGroup(page, nameNorm, true, opts);
202
+ }
203
+ async function disableSWGroup(page, name, opts = {}) {
204
+ if (typeof name !== "string") {
205
+ throw new Error("[chaos-maker] group name must be a string");
206
+ }
207
+ const nameNorm = name.trim();
208
+ if (!nameNorm) {
209
+ throw new Error("[chaos-maker] group name cannot be empty");
210
+ }
211
+ await toggleSWGroup(page, nameNorm, false, opts);
212
+ }
213
+ async function toggleSWGroup(page, name, enabled, opts) {
214
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SW_TOGGLE_TIMEOUT;
215
+ await ensurePageBridge(page);
216
+ await page.evaluate(
217
+ async ({ n, e, t }) => {
218
+ const bridge = globalThis.__chaosMakerSWBridge;
219
+ if (!bridge) throw new Error("[chaos-maker] SW bridge missing \u2014 ensurePageBridge failed");
220
+ await bridge.toggleGroup(n, e, t);
221
+ },
222
+ { n: name, e: enabled, t: timeoutMs }
223
+ );
224
+ }
180
225
  async function getSWChaosLog(page) {
181
226
  return page.evaluate(() => {
182
227
  const bridge = globalThis.__chaosMakerSWBridge;
@@ -210,6 +255,7 @@ function getCoreUmdPath() {
210
255
  }
211
256
  var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
212
257
  async function injectChaos(page, config, opts = {}) {
258
+ const validated = (0, import_core2.validateChaosConfig)(config, opts.validation);
213
259
  const umdPath = getCoreUmdPath();
214
260
  const tracingEnabled = resolveTracing(opts);
215
261
  if (tracingEnabled) {
@@ -224,7 +270,7 @@ async function injectChaos(page, config, opts = {}) {
224
270
  page[TRACE_HANDLE_KEY] = handle;
225
271
  }
226
272
  }
227
- const serialized = (0, import_core2.serializeForTransport)(config);
273
+ const serialized = (0, import_core2.serializeForTransport)(validated);
228
274
  await page.addInitScript((cfg) => {
229
275
  const win = globalThis;
230
276
  win.__CHAOS_CONFIG__ = cfg;
@@ -266,6 +312,50 @@ async function getChaosLog(page) {
266
312
  return [];
267
313
  });
268
314
  }
315
+ async function enableGroup(page, name) {
316
+ if (typeof name !== "string") {
317
+ throw new Error("[chaos-maker] group name must be a string");
318
+ }
319
+ const nameNorm = name.trim();
320
+ if (!nameNorm) {
321
+ throw new Error("[chaos-maker] group name cannot be empty");
322
+ }
323
+ await page.evaluate(({ n }) => {
324
+ const utils = globalThis.chaosUtils;
325
+ if (!utils || !utils.instance) {
326
+ throw new Error("[chaos-maker] no chaos instance on page \u2014 call injectChaos first");
327
+ }
328
+ if (typeof utils.enableGroup !== "function") {
329
+ throw new Error("[chaos-maker] enableGroup API unavailable");
330
+ }
331
+ const result = utils.enableGroup(n);
332
+ if (result && result.success === false) {
333
+ throw new Error(`[chaos-maker] enableGroup('${n}') failed: ${result.message}`);
334
+ }
335
+ }, { n: nameNorm });
336
+ }
337
+ async function disableGroup(page, name) {
338
+ if (typeof name !== "string") {
339
+ throw new Error("[chaos-maker] group name must be a string");
340
+ }
341
+ const nameNorm = name.trim();
342
+ if (!nameNorm) {
343
+ throw new Error("[chaos-maker] group name cannot be empty");
344
+ }
345
+ await page.evaluate(({ n }) => {
346
+ const utils = globalThis.chaosUtils;
347
+ if (!utils || !utils.instance) {
348
+ throw new Error("[chaos-maker] no chaos instance on page \u2014 call injectChaos first");
349
+ }
350
+ if (typeof utils.disableGroup !== "function") {
351
+ throw new Error("[chaos-maker] disableGroup API unavailable");
352
+ }
353
+ const result = utils.disableGroup(n);
354
+ if (result && result.success === false) {
355
+ throw new Error(`[chaos-maker] disableGroup('${n}') failed: ${result.message}`);
356
+ }
357
+ }, { n: nameNorm });
358
+ }
269
359
  async function getChaosSeed(page) {
270
360
  return page.evaluate(() => {
271
361
  const win = globalThis;
@@ -277,6 +367,12 @@ async function getChaosSeed(page) {
277
367
  }
278
368
  // Annotate the CommonJS export names for ESM import in node:
279
369
  0 && (module.exports = {
370
+ ChaosConfigError,
371
+ Logger,
372
+ disableGroup,
373
+ disableSWGroup,
374
+ enableGroup,
375
+ enableSWGroup,
280
376
  getChaosLog,
281
377
  getChaosSeed,
282
378
  getSWChaosLog,
@@ -284,5 +380,6 @@ async function getChaosSeed(page) {
284
380
  injectChaos,
285
381
  injectSWChaos,
286
382
  removeChaos,
287
- removeSWChaos
383
+ removeSWChaos,
384
+ validateChaosConfig
288
385
  });
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Page, TestInfo } from '@playwright/test';
2
- import { ChaosEvent, ChaosConfig } from '@chaos-maker/core';
3
- export { ChaosConfig, ChaosEvent, CorruptionStrategy, GraphQLOperationMatcher, NetworkAbortConfig, NetworkConfig, NetworkCorruptionConfig, NetworkCorsConfig, NetworkFailureConfig, NetworkLatencyConfig, NetworkRuleMatchers, SSECloseConfig, SSEConfig, SSECorruptConfig, SSECorruptionStrategy, SSEDelayConfig, SSEDropConfig, SSEEventTypeMatcher, WebSocketCloseConfig, WebSocketConfig, WebSocketCorruptConfig, WebSocketCorruptionStrategy, WebSocketDelayConfig, WebSocketDirection, WebSocketDropConfig } from '@chaos-maker/core';
2
+ import { ChaosEvent, ValidateChaosConfigOptions, ChaosConfig } from '@chaos-maker/core';
3
+ export { ChaosConfig, ChaosConfigError, ChaosDebugStage, ChaosEvent, ChaosLifecyclePhase, CorruptionStrategy, CustomRuleValidator, CustomValidatorMap, DebugOptions, GraphQLOperationMatcher, Logger, NetworkAbortConfig, NetworkConfig, NetworkCorruptionConfig, NetworkCorsConfig, NetworkFailureConfig, NetworkLatencyConfig, NetworkRuleMatchers, RuleType, SSECloseConfig, SSEConfig, SSECorruptConfig, SSECorruptionStrategy, SSEDelayConfig, SSEDropConfig, SSEEventTypeMatcher, ValidateChaosConfigOptions, ValidationIssue, ValidationIssueCode, WebSocketCloseConfig, WebSocketConfig, WebSocketCorruptConfig, WebSocketCorruptionStrategy, WebSocketDelayConfig, WebSocketDirection, WebSocketDropConfig, validateChaosConfig } from '@chaos-maker/core';
4
4
 
5
5
  /**
6
6
  * Shape of the JSON attachment written to `testInfo.attachments` on teardown.
@@ -28,6 +28,11 @@ interface SWChaosOptions {
28
28
  * SWs that do heavy work during `install`.
29
29
  */
30
30
  timeoutMs?: number;
31
+ /**
32
+ * Forwarded to `validateChaosConfig` before the config is posted
33
+ * to the SW. Malformed configs throw a `ChaosConfigError` from Node.
34
+ */
35
+ validation?: ValidateChaosConfigOptions;
31
36
  }
32
37
  interface InjectSWChaosResult {
33
38
  /** Seed used by the PRNG inside the SW. `null` if the ack did not carry one. */
@@ -56,6 +61,14 @@ declare function injectSWChaos(page: Page, config: ChaosConfig, opts?: SWChaosOp
56
61
  * the current controller and clears the page's in-memory log buffer.
57
62
  */
58
63
  declare function removeSWChaos(page: Page, opts?: SWChaosOptions): Promise<void>;
64
+ /**
65
+ * Enable a rule group inside the active SW chaos engine. Posts
66
+ * `__chaosMakerToggleGroup` over MessageChannel and resolves only after the SW
67
+ * acks. Engine state and request counters are preserved (no restart).
68
+ */
69
+ declare function enableSWGroup(page: Page, name: string, opts?: SWChaosOptions): Promise<void>;
70
+ /** Disable a rule group inside the active SW chaos engine. */
71
+ declare function disableSWGroup(page: Page, name: string, opts?: SWChaosOptions): Promise<void>;
59
72
  /**
60
73
  * Read the chaos event log buffered on the page side. Every event emitted by
61
74
  * the SW is broadcast to all controlled clients and captured here.
@@ -90,6 +103,13 @@ interface InjectChaosOptions {
90
103
  testInfo?: TestInfo;
91
104
  /** Pass through to the trace reporter. */
92
105
  traceOptions?: TraceReporterOptions;
106
+ /**
107
+ * Forwarded to `validateChaosConfig` before the config is
108
+ * serialized for the page. Use to relax unknown-field handling, hook
109
+ * deprecation events, or run custom per-`RuleType` validators. Malformed
110
+ * configs throw a `ChaosConfigError` synchronously from Node.
111
+ */
112
+ validation?: ValidateChaosConfigOptions;
93
113
  }
94
114
  /**
95
115
  * Inject chaos into a Playwright page. Call before `page.goto()` to ensure
@@ -119,10 +139,18 @@ declare function removeChaos(page: Page): Promise<void>;
119
139
  * Returns all events emitted since chaos was injected.
120
140
  */
121
141
  declare function getChaosLog(page: Page): Promise<ChaosEvent[]>;
142
+ /**
143
+ * Enable a rule group at runtime in the page-side chaos engine. Resolves once
144
+ * `page.evaluate` round-trips so the call is safe to await before triggering
145
+ * the assertion that depends on the group being live.
146
+ */
147
+ declare function enableGroup(page: Page, name: string): Promise<void>;
148
+ /** Disable a rule group at runtime in the page-side chaos engine. */
149
+ declare function disableGroup(page: Page, name: string): Promise<void>;
122
150
  /**
123
151
  * Retrieve the PRNG seed from a Playwright page.
124
152
  * Log this value on test failure to replay exact chaos decisions.
125
153
  */
126
154
  declare function getChaosSeed(page: Page): Promise<number | null>;
127
155
 
128
- export { type ChaosTraceAttachment, type InjectChaosOptions, type InjectSWChaosResult, type SWChaosOptions, type TraceReporterOptions, getChaosLog, getChaosSeed, getSWChaosLog, getSWChaosLogFromSW, injectChaos, injectSWChaos, removeChaos, removeSWChaos };
156
+ export { type ChaosTraceAttachment, type InjectChaosOptions, type InjectSWChaosResult, type SWChaosOptions, type TraceReporterOptions, disableGroup, disableSWGroup, enableGroup, enableSWGroup, getChaosLog, getChaosSeed, getSWChaosLog, getSWChaosLogFromSW, injectChaos, injectSWChaos, removeChaos, removeSWChaos };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Page, TestInfo } from '@playwright/test';
2
- import { ChaosEvent, ChaosConfig } from '@chaos-maker/core';
3
- export { ChaosConfig, ChaosEvent, CorruptionStrategy, GraphQLOperationMatcher, NetworkAbortConfig, NetworkConfig, NetworkCorruptionConfig, NetworkCorsConfig, NetworkFailureConfig, NetworkLatencyConfig, NetworkRuleMatchers, SSECloseConfig, SSEConfig, SSECorruptConfig, SSECorruptionStrategy, SSEDelayConfig, SSEDropConfig, SSEEventTypeMatcher, WebSocketCloseConfig, WebSocketConfig, WebSocketCorruptConfig, WebSocketCorruptionStrategy, WebSocketDelayConfig, WebSocketDirection, WebSocketDropConfig } from '@chaos-maker/core';
2
+ import { ChaosEvent, ValidateChaosConfigOptions, ChaosConfig } from '@chaos-maker/core';
3
+ export { ChaosConfig, ChaosConfigError, ChaosDebugStage, ChaosEvent, ChaosLifecyclePhase, CorruptionStrategy, CustomRuleValidator, CustomValidatorMap, DebugOptions, GraphQLOperationMatcher, Logger, NetworkAbortConfig, NetworkConfig, NetworkCorruptionConfig, NetworkCorsConfig, NetworkFailureConfig, NetworkLatencyConfig, NetworkRuleMatchers, RuleType, SSECloseConfig, SSEConfig, SSECorruptConfig, SSECorruptionStrategy, SSEDelayConfig, SSEDropConfig, SSEEventTypeMatcher, ValidateChaosConfigOptions, ValidationIssue, ValidationIssueCode, WebSocketCloseConfig, WebSocketConfig, WebSocketCorruptConfig, WebSocketCorruptionStrategy, WebSocketDelayConfig, WebSocketDirection, WebSocketDropConfig, validateChaosConfig } from '@chaos-maker/core';
4
4
 
5
5
  /**
6
6
  * Shape of the JSON attachment written to `testInfo.attachments` on teardown.
@@ -28,6 +28,11 @@ interface SWChaosOptions {
28
28
  * SWs that do heavy work during `install`.
29
29
  */
30
30
  timeoutMs?: number;
31
+ /**
32
+ * Forwarded to `validateChaosConfig` before the config is posted
33
+ * to the SW. Malformed configs throw a `ChaosConfigError` from Node.
34
+ */
35
+ validation?: ValidateChaosConfigOptions;
31
36
  }
32
37
  interface InjectSWChaosResult {
33
38
  /** Seed used by the PRNG inside the SW. `null` if the ack did not carry one. */
@@ -56,6 +61,14 @@ declare function injectSWChaos(page: Page, config: ChaosConfig, opts?: SWChaosOp
56
61
  * the current controller and clears the page's in-memory log buffer.
57
62
  */
58
63
  declare function removeSWChaos(page: Page, opts?: SWChaosOptions): Promise<void>;
64
+ /**
65
+ * Enable a rule group inside the active SW chaos engine. Posts
66
+ * `__chaosMakerToggleGroup` over MessageChannel and resolves only after the SW
67
+ * acks. Engine state and request counters are preserved (no restart).
68
+ */
69
+ declare function enableSWGroup(page: Page, name: string, opts?: SWChaosOptions): Promise<void>;
70
+ /** Disable a rule group inside the active SW chaos engine. */
71
+ declare function disableSWGroup(page: Page, name: string, opts?: SWChaosOptions): Promise<void>;
59
72
  /**
60
73
  * Read the chaos event log buffered on the page side. Every event emitted by
61
74
  * the SW is broadcast to all controlled clients and captured here.
@@ -90,6 +103,13 @@ interface InjectChaosOptions {
90
103
  testInfo?: TestInfo;
91
104
  /** Pass through to the trace reporter. */
92
105
  traceOptions?: TraceReporterOptions;
106
+ /**
107
+ * Forwarded to `validateChaosConfig` before the config is
108
+ * serialized for the page. Use to relax unknown-field handling, hook
109
+ * deprecation events, or run custom per-`RuleType` validators. Malformed
110
+ * configs throw a `ChaosConfigError` synchronously from Node.
111
+ */
112
+ validation?: ValidateChaosConfigOptions;
93
113
  }
94
114
  /**
95
115
  * Inject chaos into a Playwright page. Call before `page.goto()` to ensure
@@ -119,10 +139,18 @@ declare function removeChaos(page: Page): Promise<void>;
119
139
  * Returns all events emitted since chaos was injected.
120
140
  */
121
141
  declare function getChaosLog(page: Page): Promise<ChaosEvent[]>;
142
+ /**
143
+ * Enable a rule group at runtime in the page-side chaos engine. Resolves once
144
+ * `page.evaluate` round-trips so the call is safe to await before triggering
145
+ * the assertion that depends on the group being live.
146
+ */
147
+ declare function enableGroup(page: Page, name: string): Promise<void>;
148
+ /** Disable a rule group at runtime in the page-side chaos engine. */
149
+ declare function disableGroup(page: Page, name: string): Promise<void>;
122
150
  /**
123
151
  * Retrieve the PRNG seed from a Playwright page.
124
152
  * Log this value on test failure to replay exact chaos decisions.
125
153
  */
126
154
  declare function getChaosSeed(page: Page): Promise<number | null>;
127
155
 
128
- export { type ChaosTraceAttachment, type InjectChaosOptions, type InjectSWChaosResult, type SWChaosOptions, type TraceReporterOptions, getChaosLog, getChaosSeed, getSWChaosLog, getSWChaosLogFromSW, injectChaos, injectSWChaos, removeChaos, removeSWChaos };
156
+ export { type ChaosTraceAttachment, type InjectChaosOptions, type InjectSWChaosResult, type SWChaosOptions, type TraceReporterOptions, disableGroup, disableSWGroup, enableGroup, enableSWGroup, getChaosLog, getChaosSeed, getSWChaosLog, getSWChaosLogFromSW, injectChaos, injectSWChaos, removeChaos, removeSWChaos };
package/dist/index.js CHANGED
@@ -1,4 +1,10 @@
1
1
  import {
2
+ ChaosConfigError,
3
+ Logger,
4
+ disableGroup,
5
+ disableSWGroup,
6
+ enableGroup,
7
+ enableSWGroup,
2
8
  getChaosLog,
3
9
  getChaosSeed,
4
10
  getSWChaosLog,
@@ -6,9 +12,16 @@ import {
6
12
  injectChaos,
7
13
  injectSWChaos,
8
14
  removeChaos,
9
- removeSWChaos
10
- } from "./chunk-KHKPTES5.js";
15
+ removeSWChaos,
16
+ validateChaosConfig
17
+ } from "./chunk-IJDMHBUQ.js";
11
18
  export {
19
+ ChaosConfigError,
20
+ Logger,
21
+ disableGroup,
22
+ disableSWGroup,
23
+ enableGroup,
24
+ enableSWGroup,
12
25
  getChaosLog,
13
26
  getChaosSeed,
14
27
  getSWChaosLog,
@@ -16,5 +29,6 @@ export {
16
29
  injectChaos,
17
30
  injectSWChaos,
18
31
  removeChaos,
19
- removeSWChaos
32
+ removeSWChaos,
33
+ validateChaosConfig
20
34
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaos-maker/playwright",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Playwright adapter for @chaos-maker/core — one-line chaos injection in E2E tests",
6
6
  "keywords": [
@@ -49,7 +49,7 @@
49
49
  "dist"
50
50
  ],
51
51
  "dependencies": {
52
- "@chaos-maker/core": "0.4.0"
52
+ "@chaos-maker/core": "0.5.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@playwright/test": ">=1.40.0"