@chaos-maker/playwright 0.3.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,71 @@ 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
+
157
+ ### SSE and GraphQL
158
+
159
+ ```ts
160
+ await injectChaos(page, {
161
+ seed: 42,
162
+ sse: {
163
+ drops: [{ urlPattern: '/events', eventType: 'token', probability: 0.1 }],
164
+ },
165
+ network: {
166
+ failures: [{
167
+ urlPattern: '/graphql',
168
+ graphqlOperation: 'GetUser',
169
+ statusCode: 503,
170
+ probability: 1,
171
+ }],
172
+ },
173
+ });
174
+ await page.goto('/dashboard');
175
+ ```
176
+
177
+ SSE chaos and GraphQL operation matching use the same pre-navigation `injectChaos()` timing as fetch, XHR, and WebSocket chaos.
178
+
93
179
  ## API
94
180
 
95
181
  ### `injectChaos(page, config, opts?)`
@@ -111,6 +197,14 @@ Stop chaos and restore original `fetch`/`XHR`/DOM behavior.
111
197
 
112
198
  Retrieve the chaos event log from the page. Returns `ChaosEvent[]` — every chaos check emitted since injection, with `applied: true/false`.
113
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
+
114
208
  ### Fixture: `chaos`
115
209
 
116
210
  Available when importing `test` from `@chaos-maker/playwright/fixture`:
@@ -118,6 +212,29 @@ Available when importing `test` from `@chaos-maker/playwright/fixture`:
118
212
  - `chaos.inject(config)` — same as `injectChaos(page, config)`
119
213
  - `chaos.remove()` — same as `removeChaos(page)` (also called automatically after each test)
120
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
+ ```
121
238
 
122
239
  ## Debugging with trace
123
240
 
@@ -167,6 +284,49 @@ test('with direct API', async ({ page }, testInfo) => {
167
284
  });
168
285
  ```
169
286
 
287
+ ## Service Worker chaos
288
+
289
+ Intercept SW-originated fetches. Requires one line in your service-worker script.
290
+
291
+ ```js
292
+ // user's sw.js (classic)
293
+ importScripts('/chaos-maker-sw.js');
294
+ ```
295
+
296
+ ```ts
297
+ import {
298
+ injectSWChaos,
299
+ removeSWChaos,
300
+ getSWChaosLog,
301
+ enableSWGroup,
302
+ disableSWGroup,
303
+ } from '@chaos-maker/playwright';
304
+
305
+ test('SW-fetched /api returns 503', async ({ page }) => {
306
+ await page.goto('/app-with-sw/');
307
+ // wait for controller after your app's SW registration
308
+ await injectSWChaos(page, {
309
+ groups: [{ name: 'payments', enabled: false }],
310
+ network: {
311
+ failures: [{ urlPattern: '/api/data', statusCode: 503, probability: 1, group: 'payments' }],
312
+ },
313
+ seed: 1,
314
+ });
315
+ await enableSWGroup(page, 'payments');
316
+ await page.click('#trigger');
317
+ const log = await getSWChaosLog(page);
318
+ expect(log.some(e => e.type === 'network:failure' && e.applied)).toBe(true);
319
+ await disableSWGroup(page, 'payments');
320
+ await removeSWChaos(page);
321
+ });
322
+ ```
323
+
324
+ Two artifacts ship in `@chaos-maker/core`:
325
+ - `dist/sw.js` — IIFE bundle for classic SWs (`importScripts('/chaos-maker-sw.js')`).
326
+ - `dist/sw.mjs` — ESM bundle for module SWs (`import { installChaosSW } from '/chaos-maker-sw.mjs'`).
327
+
328
+ Serve whichever your SW type uses at a URL reachable from the service-worker scope.
329
+
170
330
  ## License
171
331
 
172
332
  [MIT](../../LICENSE)
@@ -1,4 +1,5 @@
1
1
  // src/index.ts
2
+ import { serializeForTransport, validateChaosConfig as validateChaosConfig2 } from "@chaos-maker/core";
2
3
  import { resolve, dirname } from "path";
3
4
  import { createRequire } from "module";
4
5
  import { fileURLToPath } from "url";
@@ -49,6 +50,7 @@ function truncate(s, max) {
49
50
  return `\u2026${s.slice(-(max - 1))}`;
50
51
  }
51
52
  function shouldEmitStep(event, verbose) {
53
+ if (event.type === "debug") return false;
52
54
  if (event.applied) return true;
53
55
  return verbose;
54
56
  }
@@ -107,6 +109,100 @@ async function createTraceReporter(page, testInfo, opts = {}) {
107
109
  };
108
110
  }
109
111
 
112
+ // src/index.ts
113
+ import { Logger } from "@chaos-maker/core";
114
+ import { validateChaosConfig as validateChaosConfig3, ChaosConfigError } from "@chaos-maker/core";
115
+
116
+ // src/sw.ts
117
+ import { validateChaosConfig, SW_BRIDGE_SOURCE } from "@chaos-maker/core";
118
+ var BRIDGE_INIT_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.sw.bridgeInit");
119
+ var DEFAULT_SW_TOGGLE_TIMEOUT = 2e3;
120
+ async function ensurePageBridge(page) {
121
+ if (!page[BRIDGE_INIT_KEY]) {
122
+ await page.addInitScript({ content: SW_BRIDGE_SOURCE });
123
+ page[BRIDGE_INIT_KEY] = true;
124
+ }
125
+ await page.evaluate(SW_BRIDGE_SOURCE).catch(() => {
126
+ });
127
+ }
128
+ async function injectSWChaos(page, config, opts = {}) {
129
+ const validated = validateChaosConfig(config, opts.validation);
130
+ const timeoutMs = opts.timeoutMs ?? 1e4;
131
+ await ensurePageBridge(page);
132
+ const result = await page.evaluate(
133
+ async ({ cfg, timeoutMs: timeoutMs2 }) => {
134
+ const bridge = globalThis.__chaosMakerSWBridge;
135
+ if (!bridge) throw new Error("[chaos-maker] SW bridge missing from page \u2014 ensurePageBridge failed");
136
+ return await bridge.apply(cfg, timeoutMs2);
137
+ },
138
+ { cfg: validated, timeoutMs }
139
+ );
140
+ return result;
141
+ }
142
+ async function removeSWChaos(page, opts = {}) {
143
+ const timeoutMs = opts.timeoutMs ?? 5e3;
144
+ await page.evaluate(
145
+ async ({ timeoutMs: timeoutMs2 }) => {
146
+ const bridge = globalThis.__chaosMakerSWBridge;
147
+ if (!bridge) return;
148
+ await bridge.stop(timeoutMs2);
149
+ bridge.clearLocalLog();
150
+ },
151
+ { timeoutMs }
152
+ ).catch(() => {
153
+ });
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
+ }
187
+ async function getSWChaosLog(page) {
188
+ return page.evaluate(() => {
189
+ const bridge = globalThis.__chaosMakerSWBridge;
190
+ if (!bridge) return [];
191
+ return bridge.getLocalLog();
192
+ });
193
+ }
194
+ async function getSWChaosLogFromSW(page, opts = {}) {
195
+ const timeoutMs = opts.timeoutMs ?? 5e3;
196
+ return page.evaluate(
197
+ async ({ timeoutMs: timeoutMs2 }) => {
198
+ const bridge = globalThis.__chaosMakerSWBridge;
199
+ if (!bridge) return [];
200
+ return bridge.getRemoteLog(timeoutMs2);
201
+ },
202
+ { timeoutMs }
203
+ );
204
+ }
205
+
110
206
  // src/index.ts
111
207
  var cachedUmdPath = null;
112
208
  function getCoreUmdPath() {
@@ -120,6 +216,7 @@ function getCoreUmdPath() {
120
216
  }
121
217
  var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
122
218
  async function injectChaos(page, config, opts = {}) {
219
+ const validated = validateChaosConfig2(config, opts.validation);
123
220
  const umdPath = getCoreUmdPath();
124
221
  const tracingEnabled = resolveTracing(opts);
125
222
  if (tracingEnabled) {
@@ -134,10 +231,11 @@ async function injectChaos(page, config, opts = {}) {
134
231
  page[TRACE_HANDLE_KEY] = handle;
135
232
  }
136
233
  }
234
+ const serialized = serializeForTransport(validated);
137
235
  await page.addInitScript((cfg) => {
138
236
  const win = globalThis;
139
237
  win.__CHAOS_CONFIG__ = cfg;
140
- }, config);
238
+ }, serialized);
141
239
  await page.addInitScript({ path: umdPath });
142
240
  }
143
241
  function resolveTracing(opts) {
@@ -175,6 +273,50 @@ async function getChaosLog(page) {
175
273
  return [];
176
274
  });
177
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
+ }
178
320
  async function getChaosSeed(page) {
179
321
  return page.evaluate(() => {
180
322
  const win = globalThis;
@@ -186,8 +328,19 @@ async function getChaosSeed(page) {
186
328
  }
187
329
 
188
330
  export {
331
+ injectSWChaos,
332
+ removeSWChaos,
333
+ enableSWGroup,
334
+ disableSWGroup,
335
+ getSWChaosLog,
336
+ getSWChaosLogFromSW,
189
337
  injectChaos,
190
338
  removeChaos,
191
339
  getChaosLog,
192
- getChaosSeed
340
+ enableGroup,
341
+ disableGroup,
342
+ getChaosSeed,
343
+ Logger,
344
+ validateChaosConfig3 as validateChaosConfig,
345
+ ChaosConfigError
193
346
  };
package/dist/fixture.cjs CHANGED
@@ -27,6 +27,7 @@ module.exports = __toCommonJS(fixture_exports);
27
27
  var import_test2 = require("@playwright/test");
28
28
 
29
29
  // src/index.ts
30
+ var import_core2 = require("@chaos-maker/core");
30
31
  var import_path = require("path");
31
32
  var import_module = require("module");
32
33
  var import_url = require("url");
@@ -77,6 +78,7 @@ function truncate(s, max) {
77
78
  return `\u2026${s.slice(-(max - 1))}`;
78
79
  }
79
80
  function shouldEmitStep(event, verbose) {
81
+ if (event.type === "debug") return false;
80
82
  if (event.applied) return true;
81
83
  return verbose;
82
84
  }
@@ -135,6 +137,55 @@ async function createTraceReporter(page, testInfo, opts = {}) {
135
137
  };
136
138
  }
137
139
 
140
+ // src/index.ts
141
+ var import_core3 = require("@chaos-maker/core");
142
+ var import_core4 = require("@chaos-maker/core");
143
+
144
+ // src/sw.ts
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
+ }
188
+
138
189
  // src/index.ts
139
190
  var import_meta = {};
140
191
  var cachedUmdPath = null;
@@ -149,6 +200,7 @@ function getCoreUmdPath() {
149
200
  }
150
201
  var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
151
202
  async function injectChaos(page, config, opts = {}) {
203
+ const validated = (0, import_core2.validateChaosConfig)(config, opts.validation);
152
204
  const umdPath = getCoreUmdPath();
153
205
  const tracingEnabled = resolveTracing(opts);
154
206
  if (tracingEnabled) {
@@ -163,10 +215,11 @@ async function injectChaos(page, config, opts = {}) {
163
215
  page[TRACE_HANDLE_KEY] = handle;
164
216
  }
165
217
  }
218
+ const serialized = (0, import_core2.serializeForTransport)(validated);
166
219
  await page.addInitScript((cfg) => {
167
220
  const win = globalThis;
168
221
  win.__CHAOS_CONFIG__ = cfg;
169
- }, config);
222
+ }, serialized);
170
223
  await page.addInitScript({ path: umdPath });
171
224
  }
172
225
  function resolveTracing(opts) {
@@ -204,6 +257,50 @@ async function getChaosLog(page) {
204
257
  return [];
205
258
  });
206
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
+ }
207
304
  async function getChaosSeed(page) {
208
305
  return page.evaluate(() => {
209
306
  const win = globalThis;
@@ -242,7 +339,11 @@ var test2 = import_test2.test.extend({
242
339
  },
243
340
  remove: () => removeChaos(page),
244
341
  getLog: () => getChaosLog(page),
245
- 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)
246
347
  };
247
348
  await use(fixture);
248
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-XVP3BFFM.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,12 +20,24 @@ 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,
31
+ getSWChaosLog: () => getSWChaosLog,
32
+ getSWChaosLogFromSW: () => getSWChaosLogFromSW,
25
33
  injectChaos: () => injectChaos,
26
- removeChaos: () => removeChaos
34
+ injectSWChaos: () => injectSWChaos,
35
+ removeChaos: () => removeChaos,
36
+ removeSWChaos: () => removeSWChaos,
37
+ validateChaosConfig: () => import_core4.validateChaosConfig
27
38
  });
28
39
  module.exports = __toCommonJS(index_exports);
40
+ var import_core2 = require("@chaos-maker/core");
29
41
  var import_path = require("path");
30
42
  var import_module = require("module");
31
43
  var import_url = require("url");
@@ -76,6 +88,7 @@ function truncate(s, max) {
76
88
  return `\u2026${s.slice(-(max - 1))}`;
77
89
  }
78
90
  function shouldEmitStep(event, verbose) {
91
+ if (event.type === "debug") return false;
79
92
  if (event.applied) return true;
80
93
  return verbose;
81
94
  }
@@ -134,6 +147,100 @@ async function createTraceReporter(page, testInfo, opts = {}) {
134
147
  };
135
148
  }
136
149
 
150
+ // src/index.ts
151
+ var import_core3 = require("@chaos-maker/core");
152
+ var import_core4 = require("@chaos-maker/core");
153
+
154
+ // src/sw.ts
155
+ var import_core = require("@chaos-maker/core");
156
+ var BRIDGE_INIT_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.sw.bridgeInit");
157
+ var DEFAULT_SW_TOGGLE_TIMEOUT = 2e3;
158
+ async function ensurePageBridge(page) {
159
+ if (!page[BRIDGE_INIT_KEY]) {
160
+ await page.addInitScript({ content: import_core.SW_BRIDGE_SOURCE });
161
+ page[BRIDGE_INIT_KEY] = true;
162
+ }
163
+ await page.evaluate(import_core.SW_BRIDGE_SOURCE).catch(() => {
164
+ });
165
+ }
166
+ async function injectSWChaos(page, config, opts = {}) {
167
+ const validated = (0, import_core.validateChaosConfig)(config, opts.validation);
168
+ const timeoutMs = opts.timeoutMs ?? 1e4;
169
+ await ensurePageBridge(page);
170
+ const result = await page.evaluate(
171
+ async ({ cfg, timeoutMs: timeoutMs2 }) => {
172
+ const bridge = globalThis.__chaosMakerSWBridge;
173
+ if (!bridge) throw new Error("[chaos-maker] SW bridge missing from page \u2014 ensurePageBridge failed");
174
+ return await bridge.apply(cfg, timeoutMs2);
175
+ },
176
+ { cfg: validated, timeoutMs }
177
+ );
178
+ return result;
179
+ }
180
+ async function removeSWChaos(page, opts = {}) {
181
+ const timeoutMs = opts.timeoutMs ?? 5e3;
182
+ await page.evaluate(
183
+ async ({ timeoutMs: timeoutMs2 }) => {
184
+ const bridge = globalThis.__chaosMakerSWBridge;
185
+ if (!bridge) return;
186
+ await bridge.stop(timeoutMs2);
187
+ bridge.clearLocalLog();
188
+ },
189
+ { timeoutMs }
190
+ ).catch(() => {
191
+ });
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
+ }
225
+ async function getSWChaosLog(page) {
226
+ return page.evaluate(() => {
227
+ const bridge = globalThis.__chaosMakerSWBridge;
228
+ if (!bridge) return [];
229
+ return bridge.getLocalLog();
230
+ });
231
+ }
232
+ async function getSWChaosLogFromSW(page, opts = {}) {
233
+ const timeoutMs = opts.timeoutMs ?? 5e3;
234
+ return page.evaluate(
235
+ async ({ timeoutMs: timeoutMs2 }) => {
236
+ const bridge = globalThis.__chaosMakerSWBridge;
237
+ if (!bridge) return [];
238
+ return bridge.getRemoteLog(timeoutMs2);
239
+ },
240
+ { timeoutMs }
241
+ );
242
+ }
243
+
137
244
  // src/index.ts
138
245
  var import_meta = {};
139
246
  var cachedUmdPath = null;
@@ -148,6 +255,7 @@ function getCoreUmdPath() {
148
255
  }
149
256
  var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
150
257
  async function injectChaos(page, config, opts = {}) {
258
+ const validated = (0, import_core2.validateChaosConfig)(config, opts.validation);
151
259
  const umdPath = getCoreUmdPath();
152
260
  const tracingEnabled = resolveTracing(opts);
153
261
  if (tracingEnabled) {
@@ -162,10 +270,11 @@ async function injectChaos(page, config, opts = {}) {
162
270
  page[TRACE_HANDLE_KEY] = handle;
163
271
  }
164
272
  }
273
+ const serialized = (0, import_core2.serializeForTransport)(validated);
165
274
  await page.addInitScript((cfg) => {
166
275
  const win = globalThis;
167
276
  win.__CHAOS_CONFIG__ = cfg;
168
- }, config);
277
+ }, serialized);
169
278
  await page.addInitScript({ path: umdPath });
170
279
  }
171
280
  function resolveTracing(opts) {
@@ -203,6 +312,50 @@ async function getChaosLog(page) {
203
312
  return [];
204
313
  });
205
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
+ }
206
359
  async function getChaosSeed(page) {
207
360
  return page.evaluate(() => {
208
361
  const win = globalThis;
@@ -214,8 +367,19 @@ async function getChaosSeed(page) {
214
367
  }
215
368
  // Annotate the CommonJS export names for ESM import in node:
216
369
  0 && (module.exports = {
370
+ ChaosConfigError,
371
+ Logger,
372
+ disableGroup,
373
+ disableSWGroup,
374
+ enableGroup,
375
+ enableSWGroup,
217
376
  getChaosLog,
218
377
  getChaosSeed,
378
+ getSWChaosLog,
379
+ getSWChaosLogFromSW,
219
380
  injectChaos,
220
- removeChaos
381
+ injectSWChaos,
382
+ removeChaos,
383
+ removeSWChaos,
384
+ validateChaosConfig
221
385
  });
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { TestInfo, Page } from '@playwright/test';
2
- import { ChaosEvent, ChaosConfig } from '@chaos-maker/core';
3
- export { ChaosConfig, ChaosEvent, WebSocketCloseConfig, WebSocketConfig, WebSocketCorruptConfig, WebSocketCorruptionStrategy, WebSocketDelayConfig, WebSocketDirection, WebSocketDropConfig } from '@chaos-maker/core';
1
+ import { Page, TestInfo } from '@playwright/test';
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.
@@ -17,6 +17,70 @@ interface TraceReporterOptions {
17
17
  attachmentName?: string;
18
18
  }
19
19
 
20
+ /**
21
+ * Options accepted by {@link injectSWChaos} / {@link removeSWChaos} /
22
+ * {@link getSWChaosLog}.
23
+ */
24
+ interface SWChaosOptions {
25
+ /**
26
+ * Maximum milliseconds to wait for `navigator.serviceWorker.controller` and
27
+ * the SW's ack message. Defaults to `10000`. Raise for slow CI workers or
28
+ * SWs that do heavy work during `install`.
29
+ */
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;
36
+ }
37
+ interface InjectSWChaosResult {
38
+ /** Seed used by the PRNG inside the SW. `null` if the ack did not carry one. */
39
+ seed: number | null;
40
+ }
41
+ /**
42
+ * Configure Service-Worker chaos for a Playwright page. Call **after**
43
+ * `page.goto(...)` so there is a SW registration + controller to target.
44
+ *
45
+ * Requires the user's service worker to load the chaos SW bundle — typically
46
+ * via `importScripts('/path/to/chaos-maker-sw.js')` (classic SW) or
47
+ * `import { installChaosSW } from '@chaos-maker/core/sw'; installChaosSW();`
48
+ * (module SW).
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * await page.goto('/');
53
+ * await injectSWChaos(page, {
54
+ * network: { failures: [{ urlPattern: '/api', statusCode: 503, probability: 1 }] },
55
+ * });
56
+ * ```
57
+ */
58
+ declare function injectSWChaos(page: Page, config: ChaosConfig, opts?: SWChaosOptions): Promise<InjectSWChaosResult>;
59
+ /**
60
+ * Stop Service-Worker chaos for a Playwright page. Posts `__chaosMakerStop` to
61
+ * the current controller and clears the page's in-memory log buffer.
62
+ */
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>;
72
+ /**
73
+ * Read the chaos event log buffered on the page side. Every event emitted by
74
+ * the SW is broadcast to all controlled clients and captured here.
75
+ */
76
+ declare function getSWChaosLog(page: Page): Promise<ChaosEvent[]>;
77
+ /**
78
+ * Ask the SW for its in-memory log. Useful when debugging a race where the
79
+ * page-side listener missed an early broadcast (e.g. first-paint navigation).
80
+ * Prefer {@link getSWChaosLog} in normal assertions.
81
+ */
82
+ declare function getSWChaosLogFromSW(page: Page, opts?: SWChaosOptions): Promise<ChaosEvent[]>;
83
+
20
84
  /**
21
85
  * Options for `injectChaos`. Most callers can omit this entirely; defaults
22
86
  * preserve backward compatibility with the v0.1.x signature.
@@ -39,6 +103,13 @@ interface InjectChaosOptions {
39
103
  testInfo?: TestInfo;
40
104
  /** Pass through to the trace reporter. */
41
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;
42
113
  }
43
114
  /**
44
115
  * Inject chaos into a Playwright page. Call before `page.goto()` to ensure
@@ -68,10 +139,18 @@ declare function removeChaos(page: Page): Promise<void>;
68
139
  * Returns all events emitted since chaos was injected.
69
140
  */
70
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>;
71
150
  /**
72
151
  * Retrieve the PRNG seed from a Playwright page.
73
152
  * Log this value on test failure to replay exact chaos decisions.
74
153
  */
75
154
  declare function getChaosSeed(page: Page): Promise<number | null>;
76
155
 
77
- export { type ChaosTraceAttachment, type InjectChaosOptions, type TraceReporterOptions, getChaosLog, getChaosSeed, injectChaos, removeChaos };
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
- import { TestInfo, Page } from '@playwright/test';
2
- import { ChaosEvent, ChaosConfig } from '@chaos-maker/core';
3
- export { ChaosConfig, ChaosEvent, WebSocketCloseConfig, WebSocketConfig, WebSocketCorruptConfig, WebSocketCorruptionStrategy, WebSocketDelayConfig, WebSocketDirection, WebSocketDropConfig } from '@chaos-maker/core';
1
+ import { Page, TestInfo } from '@playwright/test';
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.
@@ -17,6 +17,70 @@ interface TraceReporterOptions {
17
17
  attachmentName?: string;
18
18
  }
19
19
 
20
+ /**
21
+ * Options accepted by {@link injectSWChaos} / {@link removeSWChaos} /
22
+ * {@link getSWChaosLog}.
23
+ */
24
+ interface SWChaosOptions {
25
+ /**
26
+ * Maximum milliseconds to wait for `navigator.serviceWorker.controller` and
27
+ * the SW's ack message. Defaults to `10000`. Raise for slow CI workers or
28
+ * SWs that do heavy work during `install`.
29
+ */
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;
36
+ }
37
+ interface InjectSWChaosResult {
38
+ /** Seed used by the PRNG inside the SW. `null` if the ack did not carry one. */
39
+ seed: number | null;
40
+ }
41
+ /**
42
+ * Configure Service-Worker chaos for a Playwright page. Call **after**
43
+ * `page.goto(...)` so there is a SW registration + controller to target.
44
+ *
45
+ * Requires the user's service worker to load the chaos SW bundle — typically
46
+ * via `importScripts('/path/to/chaos-maker-sw.js')` (classic SW) or
47
+ * `import { installChaosSW } from '@chaos-maker/core/sw'; installChaosSW();`
48
+ * (module SW).
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * await page.goto('/');
53
+ * await injectSWChaos(page, {
54
+ * network: { failures: [{ urlPattern: '/api', statusCode: 503, probability: 1 }] },
55
+ * });
56
+ * ```
57
+ */
58
+ declare function injectSWChaos(page: Page, config: ChaosConfig, opts?: SWChaosOptions): Promise<InjectSWChaosResult>;
59
+ /**
60
+ * Stop Service-Worker chaos for a Playwright page. Posts `__chaosMakerStop` to
61
+ * the current controller and clears the page's in-memory log buffer.
62
+ */
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>;
72
+ /**
73
+ * Read the chaos event log buffered on the page side. Every event emitted by
74
+ * the SW is broadcast to all controlled clients and captured here.
75
+ */
76
+ declare function getSWChaosLog(page: Page): Promise<ChaosEvent[]>;
77
+ /**
78
+ * Ask the SW for its in-memory log. Useful when debugging a race where the
79
+ * page-side listener missed an early broadcast (e.g. first-paint navigation).
80
+ * Prefer {@link getSWChaosLog} in normal assertions.
81
+ */
82
+ declare function getSWChaosLogFromSW(page: Page, opts?: SWChaosOptions): Promise<ChaosEvent[]>;
83
+
20
84
  /**
21
85
  * Options for `injectChaos`. Most callers can omit this entirely; defaults
22
86
  * preserve backward compatibility with the v0.1.x signature.
@@ -39,6 +103,13 @@ interface InjectChaosOptions {
39
103
  testInfo?: TestInfo;
40
104
  /** Pass through to the trace reporter. */
41
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;
42
113
  }
43
114
  /**
44
115
  * Inject chaos into a Playwright page. Call before `page.goto()` to ensure
@@ -68,10 +139,18 @@ declare function removeChaos(page: Page): Promise<void>;
68
139
  * Returns all events emitted since chaos was injected.
69
140
  */
70
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>;
71
150
  /**
72
151
  * Retrieve the PRNG seed from a Playwright page.
73
152
  * Log this value on test failure to replay exact chaos decisions.
74
153
  */
75
154
  declare function getChaosSeed(page: Page): Promise<number | null>;
76
155
 
77
- export { type ChaosTraceAttachment, type InjectChaosOptions, type TraceReporterOptions, getChaosLog, getChaosSeed, injectChaos, removeChaos };
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,12 +1,34 @@
1
1
  import {
2
+ ChaosConfigError,
3
+ Logger,
4
+ disableGroup,
5
+ disableSWGroup,
6
+ enableGroup,
7
+ enableSWGroup,
2
8
  getChaosLog,
3
9
  getChaosSeed,
10
+ getSWChaosLog,
11
+ getSWChaosLogFromSW,
4
12
  injectChaos,
5
- removeChaos
6
- } from "./chunk-XVP3BFFM.js";
13
+ injectSWChaos,
14
+ removeChaos,
15
+ removeSWChaos,
16
+ validateChaosConfig
17
+ } from "./chunk-IJDMHBUQ.js";
7
18
  export {
19
+ ChaosConfigError,
20
+ Logger,
21
+ disableGroup,
22
+ disableSWGroup,
23
+ enableGroup,
24
+ enableSWGroup,
8
25
  getChaosLog,
9
26
  getChaosSeed,
27
+ getSWChaosLog,
28
+ getSWChaosLogFromSW,
10
29
  injectChaos,
11
- removeChaos
30
+ injectSWChaos,
31
+ removeChaos,
32
+ removeSWChaos,
33
+ validateChaosConfig
12
34
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaos-maker/playwright",
3
- "version": "0.3.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": [
@@ -13,10 +13,10 @@
13
13
  "license": "MIT",
14
14
  "repository": {
15
15
  "type": "git",
16
- "url": "https://github.com/jvjithin/chaos-maker.git",
16
+ "url": "https://github.com/chaos-maker-dev/chaos-maker.git",
17
17
  "directory": "packages/playwright"
18
18
  },
19
- "homepage": "https://github.com/jvjithin/chaos-maker",
19
+ "homepage": "https://github.com/chaos-maker-dev/chaos-maker",
20
20
  "engines": {
21
21
  "node": ">=18"
22
22
  },
@@ -49,7 +49,7 @@
49
49
  "dist"
50
50
  ],
51
51
  "dependencies": {
52
- "@chaos-maker/core": "0.3.0"
52
+ "@chaos-maker/core": "0.5.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@playwright/test": ">=1.40.0"