@chaos-maker/playwright 0.1.0 → 0.2.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 +53 -1
- package/dist/chunk-XVP3BFFM.js +193 -0
- package/dist/fixture.cjs +177 -9
- package/dist/fixture.d.cts +7 -2
- package/dist/fixture.d.ts +7 -2
- package/dist/fixture.js +26 -4
- package/dist/index.cjs +150 -1
- package/dist/index.d.cts +47 -4
- package/dist/index.d.ts +47 -4
- package/dist/index.js +3 -1
- package/package.json +6 -5
- package/dist/chunk-BZSMGQHL.js +0 -45
package/README.md
CHANGED
|
@@ -92,12 +92,16 @@ test('checkout handles combined chaos', async ({ page }) => {
|
|
|
92
92
|
|
|
93
93
|
## API
|
|
94
94
|
|
|
95
|
-
### `injectChaos(page, config)`
|
|
95
|
+
### `injectChaos(page, config, opts?)`
|
|
96
96
|
|
|
97
97
|
Inject chaos into a Playwright page. **Call before `page.goto()`** to ensure all network requests are intercepted from the start.
|
|
98
98
|
|
|
99
99
|
- `page` — Playwright `Page` instance
|
|
100
100
|
- `config` — `ChaosConfig` object (see [@chaos-maker/core](../core/) for full config reference)
|
|
101
|
+
- `opts` — optional. `InjectChaosOptions`:
|
|
102
|
+
- `tracing?: boolean | 'auto'` — emit chaos events into the Playwright trace (see [Debugging with trace](#debugging-with-trace)). Requires `testInfo` when `true`.
|
|
103
|
+
- `testInfo?: TestInfo` — active Playwright `TestInfo` (supplied automatically by the fixture).
|
|
104
|
+
- `traceOptions?: { verbose?: boolean; attachmentName?: string }` — tune trace output.
|
|
101
105
|
|
|
102
106
|
### `removeChaos(page)`
|
|
103
107
|
|
|
@@ -115,6 +119,54 @@ Available when importing `test` from `@chaos-maker/playwright/fixture`:
|
|
|
115
119
|
- `chaos.remove()` — same as `removeChaos(page)` (also called automatically after each test)
|
|
116
120
|
- `chaos.getLog()` — same as `getChaosLog(page)`
|
|
117
121
|
|
|
122
|
+
## Debugging with trace
|
|
123
|
+
|
|
124
|
+
When a chaos test fails, the Playwright trace viewer is the first place to look. Enable tracing in your Playwright config and use the fixture — every applied chaos decision appears inline in the trace action timeline as a `chaos:<type>` step, and the full event log is attached as `chaos-log.json`.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// playwright.config.ts
|
|
128
|
+
export default defineConfig({
|
|
129
|
+
use: {
|
|
130
|
+
trace: 'on-first-retry', // or 'on' / 'retain-on-failure'
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { test, expect } from '@chaos-maker/playwright/fixture';
|
|
137
|
+
|
|
138
|
+
test('flaky checkout', async ({ page, chaos }) => {
|
|
139
|
+
await chaos.inject({
|
|
140
|
+
network: {
|
|
141
|
+
failures: [{ urlPattern: '/api/pay', statusCode: 503, probability: 1.0 }],
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
await page.goto('/checkout');
|
|
145
|
+
await page.click('#pay');
|
|
146
|
+
await expect(page.getByText('Order placed')).toBeVisible(); // fails
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
On failure, open the trace (`pnpm exec playwright show-trace ...`). You'll see a step like:
|
|
151
|
+
|
|
152
|
+
> `chaos:network:failure /api/pay → 503`
|
|
153
|
+
|
|
154
|
+
…alongside the `page.click` and the failing assertion. The `chaos-log.json` attachment contains the full event stream plus the PRNG seed for exact replay.
|
|
155
|
+
|
|
156
|
+
**Tracing is auto-enabled** by the fixture whenever your project's `use.trace` is anything other than `'off'`. Opt out per-call with `chaos.inject(config, { tracing: false })`.
|
|
157
|
+
|
|
158
|
+
**Direct API users** must supply `testInfo` explicitly:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { injectChaos } from '@chaos-maker/playwright';
|
|
162
|
+
import { test } from '@playwright/test';
|
|
163
|
+
|
|
164
|
+
test('with direct API', async ({ page }, testInfo) => {
|
|
165
|
+
await injectChaos(page, config, { tracing: true, testInfo });
|
|
166
|
+
// ...
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
118
170
|
## License
|
|
119
171
|
|
|
120
172
|
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { resolve, dirname } from "path";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
// src/trace.ts
|
|
7
|
+
import { test } from "@playwright/test";
|
|
8
|
+
var CHAOS_BINDING = "__chaosMakerReport";
|
|
9
|
+
function formatStepTitle(event) {
|
|
10
|
+
const prefix = `chaos:${event.type}`;
|
|
11
|
+
const d = event.detail ?? {};
|
|
12
|
+
const parts = [];
|
|
13
|
+
const subject = d.url ?? d.selector;
|
|
14
|
+
if (subject) parts.push(truncate(subject, 48));
|
|
15
|
+
const outcome = formatOutcome(event);
|
|
16
|
+
if (outcome) parts.push(`\u2192 ${outcome}`);
|
|
17
|
+
if (!event.applied) parts.push("(skipped)");
|
|
18
|
+
return parts.length > 0 ? `${prefix} ${parts.join(" ")}` : prefix;
|
|
19
|
+
}
|
|
20
|
+
function formatOutcome(event) {
|
|
21
|
+
const d = event.detail ?? {};
|
|
22
|
+
switch (event.type) {
|
|
23
|
+
case "network:failure":
|
|
24
|
+
return d.statusCode != null ? String(d.statusCode) : null;
|
|
25
|
+
case "network:latency":
|
|
26
|
+
return d.delayMs != null ? `+${d.delayMs}ms` : null;
|
|
27
|
+
case "network:abort":
|
|
28
|
+
return "abort";
|
|
29
|
+
case "network:corruption":
|
|
30
|
+
return d.strategy ?? "corrupted";
|
|
31
|
+
case "network:cors":
|
|
32
|
+
return "cors-block";
|
|
33
|
+
case "ui:assault":
|
|
34
|
+
return d.action ?? null;
|
|
35
|
+
case "websocket:drop":
|
|
36
|
+
return d.direction ? `drop ${d.direction}` : "drop";
|
|
37
|
+
case "websocket:delay":
|
|
38
|
+
return d.delayMs != null ? `delay ${d.direction ?? ""} +${d.delayMs}ms` : "delay";
|
|
39
|
+
case "websocket:corrupt":
|
|
40
|
+
return d.strategy ?? "corrupt";
|
|
41
|
+
case "websocket:close":
|
|
42
|
+
return d.closeCode != null ? `close ${d.closeCode}` : "close";
|
|
43
|
+
default:
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function truncate(s, max) {
|
|
48
|
+
if (s.length <= max) return s;
|
|
49
|
+
return `\u2026${s.slice(-(max - 1))}`;
|
|
50
|
+
}
|
|
51
|
+
function shouldEmitStep(event, verbose) {
|
|
52
|
+
if (event.applied) return true;
|
|
53
|
+
return verbose;
|
|
54
|
+
}
|
|
55
|
+
async function createTraceReporter(page, testInfo, opts = {}) {
|
|
56
|
+
const verbose = opts.verbose ?? false;
|
|
57
|
+
const attachmentName = opts.attachmentName ?? "chaos-log.json";
|
|
58
|
+
const events = [];
|
|
59
|
+
const handler = (_source, event) => {
|
|
60
|
+
events.push(event);
|
|
61
|
+
if (!shouldEmitStep(event, verbose)) return;
|
|
62
|
+
const title = formatStepTitle(event);
|
|
63
|
+
test.step(title, async () => {
|
|
64
|
+
}).catch(() => {
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
await page.exposeBinding(CHAOS_BINDING, handler);
|
|
68
|
+
await page.addInitScript((bindingName) => {
|
|
69
|
+
const win = globalThis;
|
|
70
|
+
const attach = () => {
|
|
71
|
+
const utils = win.chaosUtils;
|
|
72
|
+
if (!utils || !utils.instance) return false;
|
|
73
|
+
if (utils.__chaosMakerTraceBound === utils.instance) return true;
|
|
74
|
+
utils.__chaosMakerTraceBound = utils.instance;
|
|
75
|
+
utils.instance.on("*", (event) => {
|
|
76
|
+
try {
|
|
77
|
+
if (typeof win[bindingName] === "function") {
|
|
78
|
+
win[bindingName](event);
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
if (attach()) return;
|
|
86
|
+
const intervalId = setInterval(() => {
|
|
87
|
+
if (attach()) clearInterval(intervalId);
|
|
88
|
+
}, 10);
|
|
89
|
+
setTimeout(() => clearInterval(intervalId), 5e3);
|
|
90
|
+
}, CHAOS_BINDING);
|
|
91
|
+
return {
|
|
92
|
+
events,
|
|
93
|
+
dispose: async (seed = null) => {
|
|
94
|
+
const payload = {
|
|
95
|
+
seed,
|
|
96
|
+
eventCount: events.length,
|
|
97
|
+
events
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
await testInfo.attach(attachmentName, {
|
|
101
|
+
body: Buffer.from(JSON.stringify(payload, null, 2), "utf-8"),
|
|
102
|
+
contentType: "application/json"
|
|
103
|
+
});
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/index.ts
|
|
111
|
+
var cachedUmdPath = null;
|
|
112
|
+
function getCoreUmdPath() {
|
|
113
|
+
if (cachedUmdPath) return cachedUmdPath;
|
|
114
|
+
const currentDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
115
|
+
const req = createRequire(resolve(currentDir, "package.json"));
|
|
116
|
+
const coreEntry = req.resolve("@chaos-maker/core");
|
|
117
|
+
const coreDistDir = dirname(coreEntry);
|
|
118
|
+
cachedUmdPath = resolve(coreDistDir, "chaos-maker.umd.js");
|
|
119
|
+
return cachedUmdPath;
|
|
120
|
+
}
|
|
121
|
+
var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
|
|
122
|
+
async function injectChaos(page, config, opts = {}) {
|
|
123
|
+
const umdPath = getCoreUmdPath();
|
|
124
|
+
const tracingEnabled = resolveTracing(opts);
|
|
125
|
+
if (tracingEnabled) {
|
|
126
|
+
if (!opts.testInfo) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"[chaos-maker] tracing requires a `testInfo` in InjectChaosOptions. Use the fixture (`@chaos-maker/playwright/fixture`) or pass testInfo explicitly."
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const existing = page[TRACE_HANDLE_KEY];
|
|
132
|
+
if (!existing) {
|
|
133
|
+
const handle = await createTraceReporter(page, opts.testInfo, opts.traceOptions);
|
|
134
|
+
page[TRACE_HANDLE_KEY] = handle;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await page.addInitScript((cfg) => {
|
|
138
|
+
const win = globalThis;
|
|
139
|
+
win.__CHAOS_CONFIG__ = cfg;
|
|
140
|
+
}, config);
|
|
141
|
+
await page.addInitScript({ path: umdPath });
|
|
142
|
+
}
|
|
143
|
+
function resolveTracing(opts) {
|
|
144
|
+
if (opts.tracing === true) return true;
|
|
145
|
+
if (opts.tracing === false || opts.tracing === void 0) return false;
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
async function removeChaos(page) {
|
|
149
|
+
const handle = page[TRACE_HANDLE_KEY];
|
|
150
|
+
let seed = null;
|
|
151
|
+
if (handle) {
|
|
152
|
+
try {
|
|
153
|
+
seed = await getChaosSeed(page);
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
await page.evaluate(() => {
|
|
158
|
+
const win = globalThis;
|
|
159
|
+
if (win.chaosUtils) {
|
|
160
|
+
win.chaosUtils.stop();
|
|
161
|
+
}
|
|
162
|
+
}).catch(() => {
|
|
163
|
+
});
|
|
164
|
+
if (handle) {
|
|
165
|
+
await handle.dispose(seed);
|
|
166
|
+
delete page[TRACE_HANDLE_KEY];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function getChaosLog(page) {
|
|
170
|
+
return page.evaluate(() => {
|
|
171
|
+
const win = globalThis;
|
|
172
|
+
if (win.chaosUtils) {
|
|
173
|
+
return win.chaosUtils.getLog();
|
|
174
|
+
}
|
|
175
|
+
return [];
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
async function getChaosSeed(page) {
|
|
179
|
+
return page.evaluate(() => {
|
|
180
|
+
const win = globalThis;
|
|
181
|
+
if (win.chaosUtils) {
|
|
182
|
+
return win.chaosUtils.getSeed();
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export {
|
|
189
|
+
injectChaos,
|
|
190
|
+
removeChaos,
|
|
191
|
+
getChaosLog,
|
|
192
|
+
getChaosSeed
|
|
193
|
+
};
|
package/dist/fixture.cjs
CHANGED
|
@@ -20,16 +20,122 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/fixture.ts
|
|
21
21
|
var fixture_exports = {};
|
|
22
22
|
__export(fixture_exports, {
|
|
23
|
-
expect: () =>
|
|
24
|
-
test: () =>
|
|
23
|
+
expect: () => import_test3.expect,
|
|
24
|
+
test: () => test2
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(fixture_exports);
|
|
27
|
-
var
|
|
27
|
+
var import_test2 = require("@playwright/test");
|
|
28
28
|
|
|
29
29
|
// src/index.ts
|
|
30
30
|
var import_path = require("path");
|
|
31
31
|
var import_module = require("module");
|
|
32
32
|
var import_url = require("url");
|
|
33
|
+
|
|
34
|
+
// src/trace.ts
|
|
35
|
+
var import_test = require("@playwright/test");
|
|
36
|
+
var CHAOS_BINDING = "__chaosMakerReport";
|
|
37
|
+
function formatStepTitle(event) {
|
|
38
|
+
const prefix = `chaos:${event.type}`;
|
|
39
|
+
const d = event.detail ?? {};
|
|
40
|
+
const parts = [];
|
|
41
|
+
const subject = d.url ?? d.selector;
|
|
42
|
+
if (subject) parts.push(truncate(subject, 48));
|
|
43
|
+
const outcome = formatOutcome(event);
|
|
44
|
+
if (outcome) parts.push(`\u2192 ${outcome}`);
|
|
45
|
+
if (!event.applied) parts.push("(skipped)");
|
|
46
|
+
return parts.length > 0 ? `${prefix} ${parts.join(" ")}` : prefix;
|
|
47
|
+
}
|
|
48
|
+
function formatOutcome(event) {
|
|
49
|
+
const d = event.detail ?? {};
|
|
50
|
+
switch (event.type) {
|
|
51
|
+
case "network:failure":
|
|
52
|
+
return d.statusCode != null ? String(d.statusCode) : null;
|
|
53
|
+
case "network:latency":
|
|
54
|
+
return d.delayMs != null ? `+${d.delayMs}ms` : null;
|
|
55
|
+
case "network:abort":
|
|
56
|
+
return "abort";
|
|
57
|
+
case "network:corruption":
|
|
58
|
+
return d.strategy ?? "corrupted";
|
|
59
|
+
case "network:cors":
|
|
60
|
+
return "cors-block";
|
|
61
|
+
case "ui:assault":
|
|
62
|
+
return d.action ?? null;
|
|
63
|
+
case "websocket:drop":
|
|
64
|
+
return d.direction ? `drop ${d.direction}` : "drop";
|
|
65
|
+
case "websocket:delay":
|
|
66
|
+
return d.delayMs != null ? `delay ${d.direction ?? ""} +${d.delayMs}ms` : "delay";
|
|
67
|
+
case "websocket:corrupt":
|
|
68
|
+
return d.strategy ?? "corrupt";
|
|
69
|
+
case "websocket:close":
|
|
70
|
+
return d.closeCode != null ? `close ${d.closeCode}` : "close";
|
|
71
|
+
default:
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function truncate(s, max) {
|
|
76
|
+
if (s.length <= max) return s;
|
|
77
|
+
return `\u2026${s.slice(-(max - 1))}`;
|
|
78
|
+
}
|
|
79
|
+
function shouldEmitStep(event, verbose) {
|
|
80
|
+
if (event.applied) return true;
|
|
81
|
+
return verbose;
|
|
82
|
+
}
|
|
83
|
+
async function createTraceReporter(page, testInfo, opts = {}) {
|
|
84
|
+
const verbose = opts.verbose ?? false;
|
|
85
|
+
const attachmentName = opts.attachmentName ?? "chaos-log.json";
|
|
86
|
+
const events = [];
|
|
87
|
+
const handler = (_source, event) => {
|
|
88
|
+
events.push(event);
|
|
89
|
+
if (!shouldEmitStep(event, verbose)) return;
|
|
90
|
+
const title = formatStepTitle(event);
|
|
91
|
+
import_test.test.step(title, async () => {
|
|
92
|
+
}).catch(() => {
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
await page.exposeBinding(CHAOS_BINDING, handler);
|
|
96
|
+
await page.addInitScript((bindingName) => {
|
|
97
|
+
const win = globalThis;
|
|
98
|
+
const attach = () => {
|
|
99
|
+
const utils = win.chaosUtils;
|
|
100
|
+
if (!utils || !utils.instance) return false;
|
|
101
|
+
if (utils.__chaosMakerTraceBound === utils.instance) return true;
|
|
102
|
+
utils.__chaosMakerTraceBound = utils.instance;
|
|
103
|
+
utils.instance.on("*", (event) => {
|
|
104
|
+
try {
|
|
105
|
+
if (typeof win[bindingName] === "function") {
|
|
106
|
+
win[bindingName](event);
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
};
|
|
113
|
+
if (attach()) return;
|
|
114
|
+
const intervalId = setInterval(() => {
|
|
115
|
+
if (attach()) clearInterval(intervalId);
|
|
116
|
+
}, 10);
|
|
117
|
+
setTimeout(() => clearInterval(intervalId), 5e3);
|
|
118
|
+
}, CHAOS_BINDING);
|
|
119
|
+
return {
|
|
120
|
+
events,
|
|
121
|
+
dispose: async (seed = null) => {
|
|
122
|
+
const payload = {
|
|
123
|
+
seed,
|
|
124
|
+
eventCount: events.length,
|
|
125
|
+
events
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
await testInfo.attach(attachmentName, {
|
|
129
|
+
body: Buffer.from(JSON.stringify(payload, null, 2), "utf-8"),
|
|
130
|
+
contentType: "application/json"
|
|
131
|
+
});
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/index.ts
|
|
33
139
|
var import_meta = {};
|
|
34
140
|
var cachedUmdPath = null;
|
|
35
141
|
function getCoreUmdPath() {
|
|
@@ -41,21 +147,53 @@ function getCoreUmdPath() {
|
|
|
41
147
|
cachedUmdPath = (0, import_path.resolve)(coreDistDir, "chaos-maker.umd.js");
|
|
42
148
|
return cachedUmdPath;
|
|
43
149
|
}
|
|
44
|
-
|
|
150
|
+
var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
|
|
151
|
+
async function injectChaos(page, config, opts = {}) {
|
|
45
152
|
const umdPath = getCoreUmdPath();
|
|
153
|
+
const tracingEnabled = resolveTracing(opts);
|
|
154
|
+
if (tracingEnabled) {
|
|
155
|
+
if (!opts.testInfo) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"[chaos-maker] tracing requires a `testInfo` in InjectChaosOptions. Use the fixture (`@chaos-maker/playwright/fixture`) or pass testInfo explicitly."
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const existing = page[TRACE_HANDLE_KEY];
|
|
161
|
+
if (!existing) {
|
|
162
|
+
const handle = await createTraceReporter(page, opts.testInfo, opts.traceOptions);
|
|
163
|
+
page[TRACE_HANDLE_KEY] = handle;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
46
166
|
await page.addInitScript((cfg) => {
|
|
47
167
|
const win = globalThis;
|
|
48
168
|
win.__CHAOS_CONFIG__ = cfg;
|
|
49
169
|
}, config);
|
|
50
170
|
await page.addInitScript({ path: umdPath });
|
|
51
171
|
}
|
|
172
|
+
function resolveTracing(opts) {
|
|
173
|
+
if (opts.tracing === true) return true;
|
|
174
|
+
if (opts.tracing === false || opts.tracing === void 0) return false;
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
52
177
|
async function removeChaos(page) {
|
|
178
|
+
const handle = page[TRACE_HANDLE_KEY];
|
|
179
|
+
let seed = null;
|
|
180
|
+
if (handle) {
|
|
181
|
+
try {
|
|
182
|
+
seed = await getChaosSeed(page);
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
}
|
|
53
186
|
await page.evaluate(() => {
|
|
54
187
|
const win = globalThis;
|
|
55
188
|
if (win.chaosUtils) {
|
|
56
189
|
win.chaosUtils.stop();
|
|
57
190
|
}
|
|
191
|
+
}).catch(() => {
|
|
58
192
|
});
|
|
193
|
+
if (handle) {
|
|
194
|
+
await handle.dispose(seed);
|
|
195
|
+
delete page[TRACE_HANDLE_KEY];
|
|
196
|
+
}
|
|
59
197
|
}
|
|
60
198
|
async function getChaosLog(page) {
|
|
61
199
|
return page.evaluate(() => {
|
|
@@ -66,15 +204,45 @@ async function getChaosLog(page) {
|
|
|
66
204
|
return [];
|
|
67
205
|
});
|
|
68
206
|
}
|
|
207
|
+
async function getChaosSeed(page) {
|
|
208
|
+
return page.evaluate(() => {
|
|
209
|
+
const win = globalThis;
|
|
210
|
+
if (win.chaosUtils) {
|
|
211
|
+
return win.chaosUtils.getSeed();
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
69
216
|
|
|
70
217
|
// src/fixture.ts
|
|
71
|
-
var
|
|
72
|
-
|
|
73
|
-
|
|
218
|
+
var import_test3 = require("@playwright/test");
|
|
219
|
+
function shouldAutoTrace(testInfo) {
|
|
220
|
+
const trace = testInfo.project.use?.trace;
|
|
221
|
+
if (trace == null) return false;
|
|
222
|
+
if (typeof trace === "string") return trace !== "off";
|
|
223
|
+
if (typeof trace === "object" && trace !== null && "mode" in trace) {
|
|
224
|
+
return trace.mode !== "off";
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
var test2 = import_test2.test.extend({
|
|
229
|
+
chaos: async ({ page }, use, testInfo) => {
|
|
230
|
+
const autoTrace = shouldAutoTrace(testInfo);
|
|
74
231
|
const fixture = {
|
|
75
|
-
inject: (config) =>
|
|
232
|
+
inject: (config, opts = {}) => {
|
|
233
|
+
let tracing = opts.tracing;
|
|
234
|
+
if (tracing === void 0 || tracing === "auto") {
|
|
235
|
+
tracing = autoTrace;
|
|
236
|
+
}
|
|
237
|
+
return injectChaos(page, config, {
|
|
238
|
+
...opts,
|
|
239
|
+
tracing,
|
|
240
|
+
testInfo: opts.testInfo ?? testInfo
|
|
241
|
+
});
|
|
242
|
+
},
|
|
76
243
|
remove: () => removeChaos(page),
|
|
77
|
-
getLog: () => getChaosLog(page)
|
|
244
|
+
getLog: () => getChaosLog(page),
|
|
245
|
+
getSeed: () => getChaosSeed(page)
|
|
78
246
|
};
|
|
79
247
|
await use(fixture);
|
|
80
248
|
await removeChaos(page);
|
package/dist/fixture.d.cts
CHANGED
|
@@ -2,15 +2,20 @@ 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
6
|
|
|
6
7
|
interface ChaosFixture {
|
|
7
|
-
inject: (config: ChaosConfig) => Promise<void>;
|
|
8
|
+
inject: (config: ChaosConfig, opts?: InjectChaosOptions) => Promise<void>;
|
|
8
9
|
remove: () => Promise<void>;
|
|
9
10
|
getLog: () => Promise<ChaosEvent[]>;
|
|
11
|
+
getSeed: () => Promise<number | null>;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Extended Playwright test with a `chaos` fixture.
|
|
13
15
|
*
|
|
16
|
+
* Tracing is auto-enabled when the project's `use.trace` config is not `'off'`.
|
|
17
|
+
* Override with `chaos.inject(config, { tracing: false })` to opt out.
|
|
18
|
+
*
|
|
14
19
|
* @example
|
|
15
20
|
* ```ts
|
|
16
21
|
* import { test, expect } from '@chaos-maker/playwright/fixture';
|
|
@@ -31,4 +36,4 @@ declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArg
|
|
|
31
36
|
chaos: ChaosFixture;
|
|
32
37
|
}, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
|
|
33
38
|
|
|
34
|
-
export { type ChaosFixture, test };
|
|
39
|
+
export { type ChaosFixture, InjectChaosOptions, test };
|
package/dist/fixture.d.ts
CHANGED
|
@@ -2,15 +2,20 @@ 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
6
|
|
|
6
7
|
interface ChaosFixture {
|
|
7
|
-
inject: (config: ChaosConfig) => Promise<void>;
|
|
8
|
+
inject: (config: ChaosConfig, opts?: InjectChaosOptions) => Promise<void>;
|
|
8
9
|
remove: () => Promise<void>;
|
|
9
10
|
getLog: () => Promise<ChaosEvent[]>;
|
|
11
|
+
getSeed: () => Promise<number | null>;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Extended Playwright test with a `chaos` fixture.
|
|
13
15
|
*
|
|
16
|
+
* Tracing is auto-enabled when the project's `use.trace` config is not `'off'`.
|
|
17
|
+
* Override with `chaos.inject(config, { tracing: false })` to opt out.
|
|
18
|
+
*
|
|
14
19
|
* @example
|
|
15
20
|
* ```ts
|
|
16
21
|
* import { test, expect } from '@chaos-maker/playwright/fixture';
|
|
@@ -31,4 +36,4 @@ declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArg
|
|
|
31
36
|
chaos: ChaosFixture;
|
|
32
37
|
}, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
|
|
33
38
|
|
|
34
|
-
export { type ChaosFixture, test };
|
|
39
|
+
export { type ChaosFixture, InjectChaosOptions, test };
|
package/dist/fixture.js
CHANGED
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getChaosLog,
|
|
3
|
+
getChaosSeed,
|
|
3
4
|
injectChaos,
|
|
4
5
|
removeChaos
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XVP3BFFM.js";
|
|
6
7
|
|
|
7
8
|
// src/fixture.ts
|
|
8
9
|
import { test as base } from "@playwright/test";
|
|
9
10
|
import { expect } from "@playwright/test";
|
|
11
|
+
function shouldAutoTrace(testInfo) {
|
|
12
|
+
const trace = testInfo.project.use?.trace;
|
|
13
|
+
if (trace == null) return false;
|
|
14
|
+
if (typeof trace === "string") return trace !== "off";
|
|
15
|
+
if (typeof trace === "object" && trace !== null && "mode" in trace) {
|
|
16
|
+
return trace.mode !== "off";
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
10
20
|
var test = base.extend({
|
|
11
|
-
chaos: async ({ page }, use) => {
|
|
21
|
+
chaos: async ({ page }, use, testInfo) => {
|
|
22
|
+
const autoTrace = shouldAutoTrace(testInfo);
|
|
12
23
|
const fixture = {
|
|
13
|
-
inject: (config) =>
|
|
24
|
+
inject: (config, opts = {}) => {
|
|
25
|
+
let tracing = opts.tracing;
|
|
26
|
+
if (tracing === void 0 || tracing === "auto") {
|
|
27
|
+
tracing = autoTrace;
|
|
28
|
+
}
|
|
29
|
+
return injectChaos(page, config, {
|
|
30
|
+
...opts,
|
|
31
|
+
tracing,
|
|
32
|
+
testInfo: opts.testInfo ?? testInfo
|
|
33
|
+
});
|
|
34
|
+
},
|
|
14
35
|
remove: () => removeChaos(page),
|
|
15
|
-
getLog: () => getChaosLog(page)
|
|
36
|
+
getLog: () => getChaosLog(page),
|
|
37
|
+
getSeed: () => getChaosSeed(page)
|
|
16
38
|
};
|
|
17
39
|
await use(fixture);
|
|
18
40
|
await removeChaos(page);
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
getChaosLog: () => getChaosLog,
|
|
24
|
+
getChaosSeed: () => getChaosSeed,
|
|
24
25
|
injectChaos: () => injectChaos,
|
|
25
26
|
removeChaos: () => removeChaos
|
|
26
27
|
});
|
|
@@ -28,6 +29,112 @@ module.exports = __toCommonJS(index_exports);
|
|
|
28
29
|
var import_path = require("path");
|
|
29
30
|
var import_module = require("module");
|
|
30
31
|
var import_url = require("url");
|
|
32
|
+
|
|
33
|
+
// src/trace.ts
|
|
34
|
+
var import_test = require("@playwright/test");
|
|
35
|
+
var CHAOS_BINDING = "__chaosMakerReport";
|
|
36
|
+
function formatStepTitle(event) {
|
|
37
|
+
const prefix = `chaos:${event.type}`;
|
|
38
|
+
const d = event.detail ?? {};
|
|
39
|
+
const parts = [];
|
|
40
|
+
const subject = d.url ?? d.selector;
|
|
41
|
+
if (subject) parts.push(truncate(subject, 48));
|
|
42
|
+
const outcome = formatOutcome(event);
|
|
43
|
+
if (outcome) parts.push(`\u2192 ${outcome}`);
|
|
44
|
+
if (!event.applied) parts.push("(skipped)");
|
|
45
|
+
return parts.length > 0 ? `${prefix} ${parts.join(" ")}` : prefix;
|
|
46
|
+
}
|
|
47
|
+
function formatOutcome(event) {
|
|
48
|
+
const d = event.detail ?? {};
|
|
49
|
+
switch (event.type) {
|
|
50
|
+
case "network:failure":
|
|
51
|
+
return d.statusCode != null ? String(d.statusCode) : null;
|
|
52
|
+
case "network:latency":
|
|
53
|
+
return d.delayMs != null ? `+${d.delayMs}ms` : null;
|
|
54
|
+
case "network:abort":
|
|
55
|
+
return "abort";
|
|
56
|
+
case "network:corruption":
|
|
57
|
+
return d.strategy ?? "corrupted";
|
|
58
|
+
case "network:cors":
|
|
59
|
+
return "cors-block";
|
|
60
|
+
case "ui:assault":
|
|
61
|
+
return d.action ?? null;
|
|
62
|
+
case "websocket:drop":
|
|
63
|
+
return d.direction ? `drop ${d.direction}` : "drop";
|
|
64
|
+
case "websocket:delay":
|
|
65
|
+
return d.delayMs != null ? `delay ${d.direction ?? ""} +${d.delayMs}ms` : "delay";
|
|
66
|
+
case "websocket:corrupt":
|
|
67
|
+
return d.strategy ?? "corrupt";
|
|
68
|
+
case "websocket:close":
|
|
69
|
+
return d.closeCode != null ? `close ${d.closeCode}` : "close";
|
|
70
|
+
default:
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function truncate(s, max) {
|
|
75
|
+
if (s.length <= max) return s;
|
|
76
|
+
return `\u2026${s.slice(-(max - 1))}`;
|
|
77
|
+
}
|
|
78
|
+
function shouldEmitStep(event, verbose) {
|
|
79
|
+
if (event.applied) return true;
|
|
80
|
+
return verbose;
|
|
81
|
+
}
|
|
82
|
+
async function createTraceReporter(page, testInfo, opts = {}) {
|
|
83
|
+
const verbose = opts.verbose ?? false;
|
|
84
|
+
const attachmentName = opts.attachmentName ?? "chaos-log.json";
|
|
85
|
+
const events = [];
|
|
86
|
+
const handler = (_source, event) => {
|
|
87
|
+
events.push(event);
|
|
88
|
+
if (!shouldEmitStep(event, verbose)) return;
|
|
89
|
+
const title = formatStepTitle(event);
|
|
90
|
+
import_test.test.step(title, async () => {
|
|
91
|
+
}).catch(() => {
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
await page.exposeBinding(CHAOS_BINDING, handler);
|
|
95
|
+
await page.addInitScript((bindingName) => {
|
|
96
|
+
const win = globalThis;
|
|
97
|
+
const attach = () => {
|
|
98
|
+
const utils = win.chaosUtils;
|
|
99
|
+
if (!utils || !utils.instance) return false;
|
|
100
|
+
if (utils.__chaosMakerTraceBound === utils.instance) return true;
|
|
101
|
+
utils.__chaosMakerTraceBound = utils.instance;
|
|
102
|
+
utils.instance.on("*", (event) => {
|
|
103
|
+
try {
|
|
104
|
+
if (typeof win[bindingName] === "function") {
|
|
105
|
+
win[bindingName](event);
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
if (attach()) return;
|
|
113
|
+
const intervalId = setInterval(() => {
|
|
114
|
+
if (attach()) clearInterval(intervalId);
|
|
115
|
+
}, 10);
|
|
116
|
+
setTimeout(() => clearInterval(intervalId), 5e3);
|
|
117
|
+
}, CHAOS_BINDING);
|
|
118
|
+
return {
|
|
119
|
+
events,
|
|
120
|
+
dispose: async (seed = null) => {
|
|
121
|
+
const payload = {
|
|
122
|
+
seed,
|
|
123
|
+
eventCount: events.length,
|
|
124
|
+
events
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
await testInfo.attach(attachmentName, {
|
|
128
|
+
body: Buffer.from(JSON.stringify(payload, null, 2), "utf-8"),
|
|
129
|
+
contentType: "application/json"
|
|
130
|
+
});
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/index.ts
|
|
31
138
|
var import_meta = {};
|
|
32
139
|
var cachedUmdPath = null;
|
|
33
140
|
function getCoreUmdPath() {
|
|
@@ -39,21 +146,53 @@ function getCoreUmdPath() {
|
|
|
39
146
|
cachedUmdPath = (0, import_path.resolve)(coreDistDir, "chaos-maker.umd.js");
|
|
40
147
|
return cachedUmdPath;
|
|
41
148
|
}
|
|
42
|
-
|
|
149
|
+
var TRACE_HANDLE_KEY = /* @__PURE__ */ Symbol.for("chaos-maker.playwright.traceHandle");
|
|
150
|
+
async function injectChaos(page, config, opts = {}) {
|
|
43
151
|
const umdPath = getCoreUmdPath();
|
|
152
|
+
const tracingEnabled = resolveTracing(opts);
|
|
153
|
+
if (tracingEnabled) {
|
|
154
|
+
if (!opts.testInfo) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
"[chaos-maker] tracing requires a `testInfo` in InjectChaosOptions. Use the fixture (`@chaos-maker/playwright/fixture`) or pass testInfo explicitly."
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const existing = page[TRACE_HANDLE_KEY];
|
|
160
|
+
if (!existing) {
|
|
161
|
+
const handle = await createTraceReporter(page, opts.testInfo, opts.traceOptions);
|
|
162
|
+
page[TRACE_HANDLE_KEY] = handle;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
44
165
|
await page.addInitScript((cfg) => {
|
|
45
166
|
const win = globalThis;
|
|
46
167
|
win.__CHAOS_CONFIG__ = cfg;
|
|
47
168
|
}, config);
|
|
48
169
|
await page.addInitScript({ path: umdPath });
|
|
49
170
|
}
|
|
171
|
+
function resolveTracing(opts) {
|
|
172
|
+
if (opts.tracing === true) return true;
|
|
173
|
+
if (opts.tracing === false || opts.tracing === void 0) return false;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
50
176
|
async function removeChaos(page) {
|
|
177
|
+
const handle = page[TRACE_HANDLE_KEY];
|
|
178
|
+
let seed = null;
|
|
179
|
+
if (handle) {
|
|
180
|
+
try {
|
|
181
|
+
seed = await getChaosSeed(page);
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
51
185
|
await page.evaluate(() => {
|
|
52
186
|
const win = globalThis;
|
|
53
187
|
if (win.chaosUtils) {
|
|
54
188
|
win.chaosUtils.stop();
|
|
55
189
|
}
|
|
190
|
+
}).catch(() => {
|
|
56
191
|
});
|
|
192
|
+
if (handle) {
|
|
193
|
+
await handle.dispose(seed);
|
|
194
|
+
delete page[TRACE_HANDLE_KEY];
|
|
195
|
+
}
|
|
57
196
|
}
|
|
58
197
|
async function getChaosLog(page) {
|
|
59
198
|
return page.evaluate(() => {
|
|
@@ -64,9 +203,19 @@ async function getChaosLog(page) {
|
|
|
64
203
|
return [];
|
|
65
204
|
});
|
|
66
205
|
}
|
|
206
|
+
async function getChaosSeed(page) {
|
|
207
|
+
return page.evaluate(() => {
|
|
208
|
+
const win = globalThis;
|
|
209
|
+
if (win.chaosUtils) {
|
|
210
|
+
return win.chaosUtils.getSeed();
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
67
215
|
// Annotate the CommonJS export names for ESM import in node:
|
|
68
216
|
0 && (module.exports = {
|
|
69
217
|
getChaosLog,
|
|
218
|
+
getChaosSeed,
|
|
70
219
|
injectChaos,
|
|
71
220
|
removeChaos
|
|
72
221
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,45 @@
|
|
|
1
|
-
import { Page } from '@playwright/test';
|
|
1
|
+
import { TestInfo, Page } from '@playwright/test';
|
|
2
2
|
import { ChaosEvent, ChaosConfig } from '@chaos-maker/core';
|
|
3
|
-
export { ChaosConfig, ChaosEvent } from '@chaos-maker/core';
|
|
3
|
+
export { ChaosConfig, ChaosEvent, WebSocketCloseConfig, WebSocketConfig, WebSocketCorruptConfig, WebSocketCorruptionStrategy, WebSocketDelayConfig, WebSocketDirection, WebSocketDropConfig } from '@chaos-maker/core';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Shape of the JSON attachment written to `testInfo.attachments` on teardown.
|
|
7
|
+
*/
|
|
8
|
+
interface ChaosTraceAttachment {
|
|
9
|
+
seed: number | null;
|
|
10
|
+
eventCount: number;
|
|
11
|
+
events: ChaosEvent[];
|
|
12
|
+
}
|
|
13
|
+
interface TraceReporterOptions {
|
|
14
|
+
/** Emit `test.step` for `applied:false` diagnostic events too. Default false. */
|
|
15
|
+
verbose?: boolean;
|
|
16
|
+
/** Attachment name. Default `chaos-log.json`. */
|
|
17
|
+
attachmentName?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for `injectChaos`. Most callers can omit this entirely; defaults
|
|
22
|
+
* preserve backward compatibility with the v0.1.x signature.
|
|
23
|
+
*/
|
|
24
|
+
interface InjectChaosOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Emit chaos events into the Playwright trace as `test.step` entries and
|
|
27
|
+
* attach the full event log on test end.
|
|
28
|
+
*
|
|
29
|
+
* - `true` — always on. Requires `testInfo`.
|
|
30
|
+
* - `false` (default for direct `injectChaos()` calls) — off.
|
|
31
|
+
* - `'auto'` (default for the fixture) — on when Playwright tracing is
|
|
32
|
+
* enabled in the project config; no-op otherwise.
|
|
33
|
+
*/
|
|
34
|
+
tracing?: boolean | 'auto';
|
|
35
|
+
/**
|
|
36
|
+
* Active Playwright `TestInfo`, required when `tracing` is truthy.
|
|
37
|
+
* The fixture supplies this automatically.
|
|
38
|
+
*/
|
|
39
|
+
testInfo?: TestInfo;
|
|
40
|
+
/** Pass through to the trace reporter. */
|
|
41
|
+
traceOptions?: TraceReporterOptions;
|
|
42
|
+
}
|
|
5
43
|
/**
|
|
6
44
|
* Inject chaos into a Playwright page. Call before `page.goto()` to ensure
|
|
7
45
|
* all network requests are intercepted from the start.
|
|
@@ -20,7 +58,7 @@ export { ChaosConfig, ChaosEvent } from '@chaos-maker/core';
|
|
|
20
58
|
* });
|
|
21
59
|
* ```
|
|
22
60
|
*/
|
|
23
|
-
declare function injectChaos(page: Page, config: ChaosConfig): Promise<void>;
|
|
61
|
+
declare function injectChaos(page: Page, config: ChaosConfig, opts?: InjectChaosOptions): Promise<void>;
|
|
24
62
|
/**
|
|
25
63
|
* Remove chaos from a Playwright page. Restores original fetch/XHR/DOM behavior.
|
|
26
64
|
*/
|
|
@@ -30,5 +68,10 @@ declare function removeChaos(page: Page): Promise<void>;
|
|
|
30
68
|
* Returns all events emitted since chaos was injected.
|
|
31
69
|
*/
|
|
32
70
|
declare function getChaosLog(page: Page): Promise<ChaosEvent[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Retrieve the PRNG seed from a Playwright page.
|
|
73
|
+
* Log this value on test failure to replay exact chaos decisions.
|
|
74
|
+
*/
|
|
75
|
+
declare function getChaosSeed(page: Page): Promise<number | null>;
|
|
33
76
|
|
|
34
|
-
export { getChaosLog, injectChaos, removeChaos };
|
|
77
|
+
export { type ChaosTraceAttachment, type InjectChaosOptions, type TraceReporterOptions, getChaosLog, getChaosSeed, injectChaos, removeChaos };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,45 @@
|
|
|
1
|
-
import { Page } from '@playwright/test';
|
|
1
|
+
import { TestInfo, Page } from '@playwright/test';
|
|
2
2
|
import { ChaosEvent, ChaosConfig } from '@chaos-maker/core';
|
|
3
|
-
export { ChaosConfig, ChaosEvent } from '@chaos-maker/core';
|
|
3
|
+
export { ChaosConfig, ChaosEvent, WebSocketCloseConfig, WebSocketConfig, WebSocketCorruptConfig, WebSocketCorruptionStrategy, WebSocketDelayConfig, WebSocketDirection, WebSocketDropConfig } from '@chaos-maker/core';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Shape of the JSON attachment written to `testInfo.attachments` on teardown.
|
|
7
|
+
*/
|
|
8
|
+
interface ChaosTraceAttachment {
|
|
9
|
+
seed: number | null;
|
|
10
|
+
eventCount: number;
|
|
11
|
+
events: ChaosEvent[];
|
|
12
|
+
}
|
|
13
|
+
interface TraceReporterOptions {
|
|
14
|
+
/** Emit `test.step` for `applied:false` diagnostic events too. Default false. */
|
|
15
|
+
verbose?: boolean;
|
|
16
|
+
/** Attachment name. Default `chaos-log.json`. */
|
|
17
|
+
attachmentName?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for `injectChaos`. Most callers can omit this entirely; defaults
|
|
22
|
+
* preserve backward compatibility with the v0.1.x signature.
|
|
23
|
+
*/
|
|
24
|
+
interface InjectChaosOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Emit chaos events into the Playwright trace as `test.step` entries and
|
|
27
|
+
* attach the full event log on test end.
|
|
28
|
+
*
|
|
29
|
+
* - `true` — always on. Requires `testInfo`.
|
|
30
|
+
* - `false` (default for direct `injectChaos()` calls) — off.
|
|
31
|
+
* - `'auto'` (default for the fixture) — on when Playwright tracing is
|
|
32
|
+
* enabled in the project config; no-op otherwise.
|
|
33
|
+
*/
|
|
34
|
+
tracing?: boolean | 'auto';
|
|
35
|
+
/**
|
|
36
|
+
* Active Playwright `TestInfo`, required when `tracing` is truthy.
|
|
37
|
+
* The fixture supplies this automatically.
|
|
38
|
+
*/
|
|
39
|
+
testInfo?: TestInfo;
|
|
40
|
+
/** Pass through to the trace reporter. */
|
|
41
|
+
traceOptions?: TraceReporterOptions;
|
|
42
|
+
}
|
|
5
43
|
/**
|
|
6
44
|
* Inject chaos into a Playwright page. Call before `page.goto()` to ensure
|
|
7
45
|
* all network requests are intercepted from the start.
|
|
@@ -20,7 +58,7 @@ export { ChaosConfig, ChaosEvent } from '@chaos-maker/core';
|
|
|
20
58
|
* });
|
|
21
59
|
* ```
|
|
22
60
|
*/
|
|
23
|
-
declare function injectChaos(page: Page, config: ChaosConfig): Promise<void>;
|
|
61
|
+
declare function injectChaos(page: Page, config: ChaosConfig, opts?: InjectChaosOptions): Promise<void>;
|
|
24
62
|
/**
|
|
25
63
|
* Remove chaos from a Playwright page. Restores original fetch/XHR/DOM behavior.
|
|
26
64
|
*/
|
|
@@ -30,5 +68,10 @@ declare function removeChaos(page: Page): Promise<void>;
|
|
|
30
68
|
* Returns all events emitted since chaos was injected.
|
|
31
69
|
*/
|
|
32
70
|
declare function getChaosLog(page: Page): Promise<ChaosEvent[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Retrieve the PRNG seed from a Playwright page.
|
|
73
|
+
* Log this value on test failure to replay exact chaos decisions.
|
|
74
|
+
*/
|
|
75
|
+
declare function getChaosSeed(page: Page): Promise<number | null>;
|
|
33
76
|
|
|
34
|
-
export { getChaosLog, injectChaos, removeChaos };
|
|
77
|
+
export { type ChaosTraceAttachment, type InjectChaosOptions, type TraceReporterOptions, getChaosLog, getChaosSeed, injectChaos, removeChaos };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chaos-maker/playwright",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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.
|
|
52
|
+
"@chaos-maker/core": "0.2.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@playwright/test": ">=1.40.0"
|
|
@@ -61,12 +61,13 @@
|
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@playwright/test": "^1.45.0",
|
|
64
|
-
"tsup": "^8.
|
|
64
|
+
"tsup": "^8.5.1",
|
|
65
65
|
"typescript": "^5.4.5",
|
|
66
|
-
"vitest": "^
|
|
66
|
+
"vitest": "^3.2.4"
|
|
67
67
|
},
|
|
68
68
|
"scripts": {
|
|
69
69
|
"build": "tsup",
|
|
70
|
-
"test": "vitest"
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:watch": "vitest"
|
|
71
72
|
}
|
|
72
73
|
}
|
package/dist/chunk-BZSMGQHL.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { resolve, dirname } from "path";
|
|
3
|
-
import { createRequire } from "module";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
var cachedUmdPath = null;
|
|
6
|
-
function getCoreUmdPath() {
|
|
7
|
-
if (cachedUmdPath) return cachedUmdPath;
|
|
8
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const req = createRequire(resolve(currentDir, "package.json"));
|
|
10
|
-
const coreEntry = req.resolve("@chaos-maker/core");
|
|
11
|
-
const coreDistDir = dirname(coreEntry);
|
|
12
|
-
cachedUmdPath = resolve(coreDistDir, "chaos-maker.umd.js");
|
|
13
|
-
return cachedUmdPath;
|
|
14
|
-
}
|
|
15
|
-
async function injectChaos(page, config) {
|
|
16
|
-
const umdPath = getCoreUmdPath();
|
|
17
|
-
await page.addInitScript((cfg) => {
|
|
18
|
-
const win = globalThis;
|
|
19
|
-
win.__CHAOS_CONFIG__ = cfg;
|
|
20
|
-
}, config);
|
|
21
|
-
await page.addInitScript({ path: umdPath });
|
|
22
|
-
}
|
|
23
|
-
async function removeChaos(page) {
|
|
24
|
-
await page.evaluate(() => {
|
|
25
|
-
const win = globalThis;
|
|
26
|
-
if (win.chaosUtils) {
|
|
27
|
-
win.chaosUtils.stop();
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
async function getChaosLog(page) {
|
|
32
|
-
return page.evaluate(() => {
|
|
33
|
-
const win = globalThis;
|
|
34
|
-
if (win.chaosUtils) {
|
|
35
|
-
return win.chaosUtils.getLog();
|
|
36
|
-
}
|
|
37
|
-
return [];
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export {
|
|
42
|
-
injectChaos,
|
|
43
|
-
removeChaos,
|
|
44
|
-
getChaosLog
|
|
45
|
-
};
|