@chaos-maker/playwright 0.4.0 → 0.6.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/dist/fixture.cjs CHANGED
@@ -27,97 +27,66 @@ 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
+ var import_core4 = require("@chaos-maker/core");
31
31
  var import_path = require("path");
32
32
  var import_module = require("module");
33
33
  var import_url = require("url");
34
34
 
35
35
  // src/trace.ts
36
36
  var import_test = require("@playwright/test");
37
+ var import_core = require("@chaos-maker/core");
38
+ var import_core2 = require("@chaos-maker/core");
37
39
  var CHAOS_BINDING = "__chaosMakerReport";
38
- function formatStepTitle(event) {
39
- const prefix = `chaos:${event.type}`;
40
- const d = event.detail ?? {};
41
- const parts = [];
42
- const subject = d.url ?? d.selector;
43
- if (subject) parts.push(truncate(subject, 48));
44
- const outcome = formatOutcome(event);
45
- if (outcome) parts.push(`\u2192 ${outcome}`);
46
- if (!event.applied) parts.push("(skipped)");
47
- return parts.length > 0 ? `${prefix} ${parts.join(" ")}` : prefix;
48
- }
49
- function formatOutcome(event) {
50
- const d = event.detail ?? {};
51
- switch (event.type) {
52
- case "network:failure":
53
- return d.statusCode != null ? String(d.statusCode) : null;
54
- case "network:latency":
55
- return d.delayMs != null ? `+${d.delayMs}ms` : null;
56
- case "network:abort":
57
- return "abort";
58
- case "network:corruption":
59
- return d.strategy ?? "corrupted";
60
- case "network:cors":
61
- return "cors-block";
62
- case "ui:assault":
63
- return d.action ?? null;
64
- case "websocket:drop":
65
- return d.direction ? `drop ${d.direction}` : "drop";
66
- case "websocket:delay":
67
- return d.delayMs != null ? `delay ${d.direction ?? ""} +${d.delayMs}ms` : "delay";
68
- case "websocket:corrupt":
69
- return d.strategy ?? "corrupt";
70
- case "websocket:close":
71
- return d.closeCode != null ? `close ${d.closeCode}` : "close";
72
- default:
73
- return null;
74
- }
75
- }
76
- function truncate(s, max) {
77
- if (s.length <= max) return s;
78
- return `\u2026${s.slice(-(max - 1))}`;
79
- }
80
- function shouldEmitStep(event, verbose) {
81
- if (event.applied) return true;
82
- return verbose;
83
- }
40
+ var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
41
+ var TRACE_BINDING_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceBinding");
84
42
  async function createTraceReporter(page, testInfo, opts = {}) {
43
+ const existing = page[TRACE_HANDLE_KEY];
44
+ if (existing) return existing;
85
45
  const verbose = opts.verbose ?? false;
86
46
  const attachmentName = opts.attachmentName ?? "chaos-log.json";
87
47
  const events = [];
88
48
  const handler = (_source, event) => {
89
49
  events.push(event);
90
- if (!shouldEmitStep(event, verbose)) return;
91
- const title = formatStepTitle(event);
50
+ if (!(0, import_core.shouldEmitStep)(event, verbose)) return;
51
+ const title = (0, import_core.formatStepTitle)(event);
92
52
  import_test.test.step(title, async () => {
93
53
  }).catch(() => {
94
54
  });
95
55
  };
96
- await page.exposeBinding(CHAOS_BINDING, handler);
97
- await page.addInitScript((bindingName) => {
98
- const win = globalThis;
99
- const attach = () => {
100
- const utils = win.chaosUtils;
101
- if (!utils || !utils.instance) return false;
102
- if (utils.__chaosMakerTraceBound === utils.instance) return true;
103
- utils.__chaosMakerTraceBound = utils.instance;
104
- utils.instance.on("*", (event) => {
105
- try {
106
- if (typeof win[bindingName] === "function") {
107
- win[bindingName](event);
56
+ let state = page[TRACE_BINDING_KEY];
57
+ if (!state) {
58
+ state = { handler };
59
+ page[TRACE_BINDING_KEY] = state;
60
+ await page.exposeBinding(CHAOS_BINDING, (source, event) => {
61
+ state.handler(source, event);
62
+ });
63
+ await page.addInitScript((bindingName) => {
64
+ const win = globalThis;
65
+ const attach = () => {
66
+ const utils = win.chaosUtils;
67
+ if (!utils || !utils.instance) return false;
68
+ if (utils.__chaosMakerTraceBound === utils.instance) return true;
69
+ utils.__chaosMakerTraceBound = utils.instance;
70
+ utils.instance.on("*", (event) => {
71
+ try {
72
+ if (typeof win[bindingName] === "function") {
73
+ win[bindingName](event);
74
+ }
75
+ } catch {
108
76
  }
109
- } catch {
110
- }
111
- });
112
- return true;
113
- };
114
- if (attach()) return;
115
- const intervalId = setInterval(() => {
116
- if (attach()) clearInterval(intervalId);
117
- }, 10);
118
- setTimeout(() => clearInterval(intervalId), 5e3);
119
- }, CHAOS_BINDING);
120
- return {
77
+ });
78
+ return true;
79
+ };
80
+ if (attach()) return;
81
+ const intervalId = setInterval(() => {
82
+ if (attach()) clearInterval(intervalId);
83
+ }, 10);
84
+ setTimeout(() => clearInterval(intervalId), 5e3);
85
+ }, CHAOS_BINDING);
86
+ } else {
87
+ state.handler = handler;
88
+ }
89
+ const handle = {
121
90
  events,
122
91
  dispose: async (seed = null) => {
123
92
  const payload = {
@@ -132,12 +101,63 @@ async function createTraceReporter(page, testInfo, opts = {}) {
132
101
  });
133
102
  } catch {
134
103
  }
104
+ if (page[TRACE_HANDLE_KEY] === handle) {
105
+ delete page[TRACE_HANDLE_KEY];
106
+ }
135
107
  }
136
108
  };
109
+ page[TRACE_HANDLE_KEY] = handle;
110
+ return handle;
137
111
  }
138
112
 
113
+ // src/index.ts
114
+ var import_core5 = require("@chaos-maker/core");
115
+ var import_core6 = require("@chaos-maker/core");
116
+
139
117
  // src/sw.ts
140
- var import_core = require("@chaos-maker/core");
118
+ var import_core3 = require("@chaos-maker/core");
119
+ var BRIDGE_INIT_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.sw.bridgeInit");
120
+ var DEFAULT_SW_TOGGLE_TIMEOUT = 2e3;
121
+ async function ensurePageBridge(page) {
122
+ if (!page[BRIDGE_INIT_KEY]) {
123
+ await page.addInitScript({ content: import_core3.SW_BRIDGE_SOURCE });
124
+ page[BRIDGE_INIT_KEY] = true;
125
+ }
126
+ await page.evaluate(import_core3.SW_BRIDGE_SOURCE).catch(() => {
127
+ });
128
+ }
129
+ async function enableSWGroup(page, name, opts = {}) {
130
+ if (typeof name !== "string") {
131
+ throw new Error("[chaos-maker] group name must be a string");
132
+ }
133
+ const nameNorm = name.trim();
134
+ if (!nameNorm) {
135
+ throw new Error("[chaos-maker] group name cannot be empty");
136
+ }
137
+ await toggleSWGroup(page, nameNorm, true, opts);
138
+ }
139
+ async function disableSWGroup(page, name, opts = {}) {
140
+ if (typeof name !== "string") {
141
+ throw new Error("[chaos-maker] group name must be a string");
142
+ }
143
+ const nameNorm = name.trim();
144
+ if (!nameNorm) {
145
+ throw new Error("[chaos-maker] group name cannot be empty");
146
+ }
147
+ await toggleSWGroup(page, nameNorm, false, opts);
148
+ }
149
+ async function toggleSWGroup(page, name, enabled, opts) {
150
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SW_TOGGLE_TIMEOUT;
151
+ await ensurePageBridge(page);
152
+ await page.evaluate(
153
+ async ({ n, e, t }) => {
154
+ const bridge = globalThis.__chaosMakerSWBridge;
155
+ if (!bridge) throw new Error("[chaos-maker] SW bridge missing \u2014 ensurePageBridge failed");
156
+ await bridge.toggleGroup(n, e, t);
157
+ },
158
+ { n: name, e: enabled, t: timeoutMs }
159
+ );
160
+ }
141
161
 
142
162
  // src/index.ts
143
163
  var import_meta = {};
@@ -151,8 +171,9 @@ function getCoreUmdPath() {
151
171
  cachedUmdPath = (0, import_path.resolve)(coreDistDir, "chaos-maker.umd.js");
152
172
  return cachedUmdPath;
153
173
  }
154
- var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
174
+ var TRACE_HANDLE_KEY2 = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
155
175
  async function injectChaos(page, config, opts = {}) {
176
+ const validated = (0, import_core4.validateChaosConfig)(config, opts.validation);
156
177
  const umdPath = getCoreUmdPath();
157
178
  const tracingEnabled = resolveTracing(opts);
158
179
  if (tracingEnabled) {
@@ -161,13 +182,13 @@ async function injectChaos(page, config, opts = {}) {
161
182
  "[chaos-maker] tracing requires a `testInfo` in InjectChaosOptions. Use the fixture (`@chaos-maker/playwright/fixture`) or pass testInfo explicitly."
162
183
  );
163
184
  }
164
- const existing = page[TRACE_HANDLE_KEY];
185
+ const existing = page[TRACE_HANDLE_KEY2];
165
186
  if (!existing) {
166
187
  const handle = await createTraceReporter(page, opts.testInfo, opts.traceOptions);
167
- page[TRACE_HANDLE_KEY] = handle;
188
+ page[TRACE_HANDLE_KEY2] = handle;
168
189
  }
169
190
  }
170
- const serialized = (0, import_core2.serializeForTransport)(config);
191
+ const serialized = (0, import_core4.serializeForTransport)(validated);
171
192
  await page.addInitScript((cfg) => {
172
193
  const win = globalThis;
173
194
  win.__CHAOS_CONFIG__ = cfg;
@@ -180,7 +201,7 @@ function resolveTracing(opts) {
180
201
  return false;
181
202
  }
182
203
  async function removeChaos(page) {
183
- const handle = page[TRACE_HANDLE_KEY];
204
+ const handle = page[TRACE_HANDLE_KEY2];
184
205
  let seed = null;
185
206
  if (handle) {
186
207
  try {
@@ -197,7 +218,7 @@ async function removeChaos(page) {
197
218
  });
198
219
  if (handle) {
199
220
  await handle.dispose(seed);
200
- delete page[TRACE_HANDLE_KEY];
221
+ delete page[TRACE_HANDLE_KEY2];
201
222
  }
202
223
  }
203
224
  async function getChaosLog(page) {
@@ -209,6 +230,50 @@ async function getChaosLog(page) {
209
230
  return [];
210
231
  });
211
232
  }
233
+ async function enableGroup(page, name) {
234
+ if (typeof name !== "string") {
235
+ throw new Error("[chaos-maker] group name must be a string");
236
+ }
237
+ const nameNorm = name.trim();
238
+ if (!nameNorm) {
239
+ throw new Error("[chaos-maker] group name cannot be empty");
240
+ }
241
+ await page.evaluate(({ n }) => {
242
+ const utils = globalThis.chaosUtils;
243
+ if (!utils || !utils.instance) {
244
+ throw new Error("[chaos-maker] no chaos instance on page \u2014 call injectChaos first");
245
+ }
246
+ if (typeof utils.enableGroup !== "function") {
247
+ throw new Error("[chaos-maker] enableGroup API unavailable");
248
+ }
249
+ const result = utils.enableGroup(n);
250
+ if (result && result.success === false) {
251
+ throw new Error(`[chaos-maker] enableGroup('${n}') failed: ${result.message}`);
252
+ }
253
+ }, { n: nameNorm });
254
+ }
255
+ async function disableGroup(page, name) {
256
+ if (typeof name !== "string") {
257
+ throw new Error("[chaos-maker] group name must be a string");
258
+ }
259
+ const nameNorm = name.trim();
260
+ if (!nameNorm) {
261
+ throw new Error("[chaos-maker] group name cannot be empty");
262
+ }
263
+ await page.evaluate(({ n }) => {
264
+ const utils = globalThis.chaosUtils;
265
+ if (!utils || !utils.instance) {
266
+ throw new Error("[chaos-maker] no chaos instance on page \u2014 call injectChaos first");
267
+ }
268
+ if (typeof utils.disableGroup !== "function") {
269
+ throw new Error("[chaos-maker] disableGroup API unavailable");
270
+ }
271
+ const result = utils.disableGroup(n);
272
+ if (result && result.success === false) {
273
+ throw new Error(`[chaos-maker] disableGroup('${n}') failed: ${result.message}`);
274
+ }
275
+ }, { n: nameNorm });
276
+ }
212
277
  async function getChaosSeed(page) {
213
278
  return page.evaluate(() => {
214
279
  const win = globalThis;
@@ -247,7 +312,11 @@ var test2 = import_test2.test.extend({
247
312
  },
248
313
  remove: () => removeChaos(page),
249
314
  getLog: () => getChaosLog(page),
250
- getSeed: () => getChaosSeed(page)
315
+ getSeed: () => getChaosSeed(page),
316
+ enableGroup: (name) => enableGroup(page, name),
317
+ disableGroup: (name) => disableGroup(page, name),
318
+ enableSWGroup: (name, opts) => enableSWGroup(page, name, opts),
319
+ disableSWGroup: (name, opts) => disableSWGroup(page, name, opts)
251
320
  };
252
321
  await use(fixture);
253
322
  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-ZJZRSTXG.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);