@mizchi/playwright-faults 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mizchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @mizchi/playwright-faults
2
+
3
+ Playwright fault-injection primitives at three layers, extracted from chaosbringer:
4
+
5
+ | Layer | API surface | Where |
6
+ |---|---|---|
7
+ | Network | `FaultRule` + `faults.{status, abort, delay}` builders | Playwright `route()` |
8
+ | Page lifecycle | `LifecycleFault` + `compileLifecycleFaults` + `PlaywrightLifecycleExecutor` | Playwright `Page` / `BrowserContext` / CDP at named stages |
9
+ | JS runtime | `RuntimeFault` + `compileRuntimeFaults` + `buildRuntimeFaultsScript` | Playwright `addInitScript` per page nav |
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pnpm add @mizchi/playwright-faults
15
+ pnpm add playwright # peer
16
+ ```
17
+
18
+ Requires Node 20+.
19
+
20
+ ## Network-level faults (FaultRule)
21
+
22
+ ```ts
23
+ import { faults } from "@mizchi/playwright-faults";
24
+
25
+ const rules = [
26
+ faults.status(500, { urlPattern: /\/api\// }),
27
+ faults.abort({ urlPattern: /tracking/ }),
28
+ faults.delay(2000, { urlPattern: /\/api\// }),
29
+ ];
30
+
31
+ // Wire into Playwright's route() yourself, or pass to chaosbringer:
32
+ // new ChaosCrawler({ baseUrl, faultInjection: rules })
33
+ ```
34
+
35
+ ## Page-lifecycle faults
36
+
37
+ ```ts
38
+ import {
39
+ PlaywrightLifecycleExecutor,
40
+ compileLifecycleFaults,
41
+ executeLifecycleAction,
42
+ lifecycleFaultsAtStage,
43
+ shouldFireProbability,
44
+ } from "@mizchi/playwright-faults";
45
+
46
+ const compiled = compileLifecycleFaults([
47
+ { when: "afterLoad", action: { kind: "clear-storage", scopes: ["localStorage"] } },
48
+ { when: "betweenActions", action: { kind: "cpu-throttle", rate: 4 } },
49
+ ]);
50
+
51
+ const executor = new PlaywrightLifecycleExecutor(page, browserContext);
52
+ for (const cf of lifecycleFaultsAtStage(compiled, "afterLoad", url)) {
53
+ if (shouldFireProbability(cf.fault.probability, rng)) {
54
+ await executeLifecycleAction(executor, cf.fault.action);
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## JS-runtime faults (addInitScript)
60
+
61
+ ```ts
62
+ import { buildRuntimeFaultsScript, compileRuntimeFaults, mergeRuntimeStats } from "@mizchi/playwright-faults";
63
+
64
+ const compiled = compileRuntimeFaults([
65
+ { action: { kind: "flaky-fetch", rejectionMessage: "synthetic network error" }, probability: 0.1 },
66
+ { action: { kind: "clock-skew", skewMs: 10 * 60 * 1000 } },
67
+ ]);
68
+
69
+ await page.addInitScript(buildRuntimeFaultsScript(compiled, seed));
70
+
71
+ // After navigations: drain per-page stats and merge
72
+ const pageStats = await page.evaluate(() => globalThis.__chaosbringerRuntimeStats);
73
+ mergeRuntimeStats(compiled, pageStats);
74
+ ```
75
+
76
+ ## RNG contract
77
+
78
+ Functions that need randomness (`shouldFireProbability`, etc.) accept any object with `next(): number` returning `[0, 1)`. Bring your own — chaosbringer uses its seeded mulberry32; vitest tests can pass `Math.random`-flavor stubs.
79
+
80
+ ## License
81
+
82
+ MIT
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Small helper functions that build FaultRule objects without the
3
+ * discriminated-union ceremony. Exported from the package root.
4
+ *
5
+ * import { faults } from "chaosbringer";
6
+ * const rules = [
7
+ * faults.status(500, { urlPattern: /\/api\// }),
8
+ * faults.abort({ urlPattern: /tracking/ }),
9
+ * faults.delay(2000, { urlPattern: /\/api\// }),
10
+ * ];
11
+ */
12
+ import type { FaultRule, LifecycleFault, LifecycleStage, RuntimeFault, StorageScope, UrlMatcher } from "./types.js";
13
+ export interface FaultHelperOptions {
14
+ urlPattern: UrlMatcher;
15
+ methods?: string[];
16
+ probability?: number;
17
+ name?: string;
18
+ }
19
+ /** Common options accepted by every lifecycle fault helper. */
20
+ export interface LifecycleHelperOptions {
21
+ /** Override the helper's default lifecycle stage. */
22
+ when?: LifecycleStage;
23
+ /** Restrict the fault to URLs matching this matcher. */
24
+ urlPattern?: UrlMatcher;
25
+ /** 0..1, default 1.0. Uses the crawler's seeded RNG. */
26
+ probability?: number;
27
+ /** Override the auto-derived stats name. */
28
+ name?: string;
29
+ }
30
+ export declare const faults: {
31
+ /** Respond with `status` (and optional body / content-type). */
32
+ status(status: number, opts: FaultHelperOptions & {
33
+ body?: string;
34
+ contentType?: string;
35
+ }): FaultRule;
36
+ /** Abort the request (e.g. to simulate a blocked third-party or transport failure). */
37
+ abort(opts: FaultHelperOptions & {
38
+ errorCode?: string;
39
+ }): FaultRule;
40
+ /** Wait `ms` milliseconds, then continue the request unchanged. */
41
+ delay(ms: number, opts: FaultHelperOptions): FaultRule;
42
+ /**
43
+ * Apply CDP CPU throttling. `rate` is a multiplier ≥ 1 (1 = no throttle,
44
+ * 4 = ~4× slower). Default stage is `beforeNavigation` so the load itself
45
+ * is slowed.
46
+ */
47
+ cpu(rate: number, opts?: LifecycleHelperOptions): LifecycleFault;
48
+ /**
49
+ * Wipe the listed storage scopes. Cookies are cleared at the BrowserContext
50
+ * level; `localStorage` / `sessionStorage` / `indexedDB` are cleared in-page.
51
+ * Default stage is `afterLoad` so the page exists when the wipe runs.
52
+ */
53
+ clearStorage(opts: LifecycleHelperOptions & {
54
+ scopes: StorageScope[];
55
+ }): LifecycleFault;
56
+ /**
57
+ * Drop entries from the Service Worker `caches` API. With no `cacheNames`,
58
+ * every cache is dropped. Default stage is `beforeActions` so the wipe
59
+ * happens after invariants but before chaos clicks.
60
+ */
61
+ evictCache(opts?: LifecycleHelperOptions & {
62
+ cacheNames?: string[];
63
+ }): LifecycleFault;
64
+ /**
65
+ * Set a single key/value in `localStorage` or `sessionStorage`. Empty value
66
+ * mimics a logged-out / token-cleared state without dropping unrelated keys.
67
+ */
68
+ tamperStorage(opts: LifecycleHelperOptions & {
69
+ scope: "localStorage" | "sessionStorage";
70
+ key: string;
71
+ value: string;
72
+ }): LifecycleFault;
73
+ /**
74
+ * Reject `window.fetch` calls before any network round-trip. Different
75
+ * from `faults.abort()` (request-scoped, applied via Playwright `route`):
76
+ * `flakyFetch` rejects the Promise client-side with a TypeError, so any
77
+ * `try/catch` and Service-Worker fallbacks downstream of `fetch` engage
78
+ * just like a real "Failed to fetch" event.
79
+ */
80
+ flakyFetch(opts?: RuntimeHelperOptions & {
81
+ rejectionMessage?: string;
82
+ }): RuntimeFault;
83
+ /**
84
+ * Skew `Date.now()`, `performance.now()`, and the no-arg `Date`
85
+ * constructor forward by `skewMs`. Use to surface token-expiry,
86
+ * cache-bust, and "clock drift" code paths without waiting real time.
87
+ */
88
+ clockSkew(skewMs: number, opts?: RuntimeHelperOptions): RuntimeFault;
89
+ };
90
+ export interface RuntimeHelperOptions {
91
+ /** Restrict the fault to pages whose URL matches this matcher. */
92
+ urlPattern?: UrlMatcher;
93
+ /** 0..1, default 1.0. Rolled per call against the in-page seeded RNG. */
94
+ probability?: number;
95
+ /** Override the auto-derived stats name. */
96
+ name?: string;
97
+ }
98
+ //# sourceMappingURL=faults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faults.d.ts","sourceRoot":"","sources":["../src/faults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAUD,+DAA+D;AAC/D,MAAM,WAAW,sBAAsB;IACrC,qDAAqD;IACrD,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,wDAAwD;IACxD,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAcD,eAAO,MAAM,MAAM;IACjB,gEAAgE;mBAEtD,MAAM,QACR,kBAAkB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GACjE,SAAS;IAaZ,uFAAuF;gBAC3E,kBAAkB,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAWnE,mEAAmE;cACzD,MAAM,QAAQ,kBAAkB,GAAG,SAAS;IAQtD;;;;OAIG;cACO,MAAM,SAAS,sBAAsB,GAAG,cAAc;IAWhE;;;;OAIG;uBAEK,sBAAsB,GAAG;QAAE,MAAM,EAAE,YAAY,EAAE,CAAA;KAAE,GACxD,cAAc;IAWjB;;;;OAIG;sBAEM,sBAAsB,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACxD,cAAc;IAWjB;;;OAGG;wBAEK,sBAAsB,GAAG;QAC7B,KAAK,EAAE,cAAc,GAAG,gBAAgB,CAAC;QACzC,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACf,GACA,cAAc;IAajB;;;;;;OAMG;sBACe,oBAAoB,GAAG;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,YAAY;IAYrF;;;;OAIG;sBACe,MAAM,SAAS,oBAAoB,GAAG,YAAY;CAOrE,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,kEAAkE;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
package/dist/faults.js ADDED
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Small helper functions that build FaultRule objects without the
3
+ * discriminated-union ceremony. Exported from the package root.
4
+ *
5
+ * import { faults } from "chaosbringer";
6
+ * const rules = [
7
+ * faults.status(500, { urlPattern: /\/api\// }),
8
+ * faults.abort({ urlPattern: /tracking/ }),
9
+ * faults.delay(2000, { urlPattern: /\/api\// }),
10
+ * ];
11
+ */
12
+ function applyCommon(rule, opts) {
13
+ rule.urlPattern = opts.urlPattern;
14
+ if (opts.methods !== undefined)
15
+ rule.methods = opts.methods;
16
+ if (opts.probability !== undefined)
17
+ rule.probability = opts.probability;
18
+ if (opts.name !== undefined)
19
+ rule.name = opts.name;
20
+ return rule;
21
+ }
22
+ function applyLifecycleCommon(fault, opts, defaultStage) {
23
+ fault.when = opts?.when ?? defaultStage;
24
+ if (opts?.urlPattern !== undefined)
25
+ fault.urlPattern = opts.urlPattern;
26
+ if (opts?.probability !== undefined)
27
+ fault.probability = opts.probability;
28
+ if (opts?.name !== undefined)
29
+ fault.name = opts.name;
30
+ return fault;
31
+ }
32
+ export const faults = {
33
+ /** Respond with `status` (and optional body / content-type). */
34
+ status(status, opts) {
35
+ const rule = {
36
+ urlPattern: opts.urlPattern,
37
+ fault: {
38
+ kind: "status",
39
+ status,
40
+ ...(opts.body !== undefined ? { body: opts.body } : {}),
41
+ ...(opts.contentType !== undefined ? { contentType: opts.contentType } : {}),
42
+ },
43
+ };
44
+ return applyCommon(rule, opts);
45
+ },
46
+ /** Abort the request (e.g. to simulate a blocked third-party or transport failure). */
47
+ abort(opts) {
48
+ const rule = {
49
+ urlPattern: opts.urlPattern,
50
+ fault: {
51
+ kind: "abort",
52
+ ...(opts.errorCode !== undefined ? { errorCode: opts.errorCode } : {}),
53
+ },
54
+ };
55
+ return applyCommon(rule, opts);
56
+ },
57
+ /** Wait `ms` milliseconds, then continue the request unchanged. */
58
+ delay(ms, opts) {
59
+ const rule = {
60
+ urlPattern: opts.urlPattern,
61
+ fault: { kind: "delay", ms },
62
+ };
63
+ return applyCommon(rule, opts);
64
+ },
65
+ /**
66
+ * Apply CDP CPU throttling. `rate` is a multiplier ≥ 1 (1 = no throttle,
67
+ * 4 = ~4× slower). Default stage is `beforeNavigation` so the load itself
68
+ * is slowed.
69
+ */
70
+ cpu(rate, opts) {
71
+ if (!Number.isFinite(rate) || rate < 1) {
72
+ throw new Error(`faults.cpu: rate must be a finite number >= 1 (got ${rate})`);
73
+ }
74
+ const fault = {
75
+ when: "beforeNavigation",
76
+ action: { kind: "cpu-throttle", rate },
77
+ };
78
+ return applyLifecycleCommon(fault, opts, "beforeNavigation");
79
+ },
80
+ /**
81
+ * Wipe the listed storage scopes. Cookies are cleared at the BrowserContext
82
+ * level; `localStorage` / `sessionStorage` / `indexedDB` are cleared in-page.
83
+ * Default stage is `afterLoad` so the page exists when the wipe runs.
84
+ */
85
+ clearStorage(opts) {
86
+ if (!opts.scopes || opts.scopes.length === 0) {
87
+ throw new Error("faults.clearStorage: at least one scope is required");
88
+ }
89
+ const fault = {
90
+ when: "afterLoad",
91
+ action: { kind: "clear-storage", scopes: [...opts.scopes] },
92
+ };
93
+ return applyLifecycleCommon(fault, opts, "afterLoad");
94
+ },
95
+ /**
96
+ * Drop entries from the Service Worker `caches` API. With no `cacheNames`,
97
+ * every cache is dropped. Default stage is `beforeActions` so the wipe
98
+ * happens after invariants but before chaos clicks.
99
+ */
100
+ evictCache(opts) {
101
+ const fault = {
102
+ when: "beforeActions",
103
+ action: opts?.cacheNames !== undefined
104
+ ? { kind: "evict-cache", cacheNames: [...opts.cacheNames] }
105
+ : { kind: "evict-cache" },
106
+ };
107
+ return applyLifecycleCommon(fault, opts, "beforeActions");
108
+ },
109
+ /**
110
+ * Set a single key/value in `localStorage` or `sessionStorage`. Empty value
111
+ * mimics a logged-out / token-cleared state without dropping unrelated keys.
112
+ */
113
+ tamperStorage(opts) {
114
+ const fault = {
115
+ when: "afterLoad",
116
+ action: {
117
+ kind: "tamper-storage",
118
+ scope: opts.scope,
119
+ key: opts.key,
120
+ value: opts.value,
121
+ },
122
+ };
123
+ return applyLifecycleCommon(fault, opts, "afterLoad");
124
+ },
125
+ /**
126
+ * Reject `window.fetch` calls before any network round-trip. Different
127
+ * from `faults.abort()` (request-scoped, applied via Playwright `route`):
128
+ * `flakyFetch` rejects the Promise client-side with a TypeError, so any
129
+ * `try/catch` and Service-Worker fallbacks downstream of `fetch` engage
130
+ * just like a real "Failed to fetch" event.
131
+ */
132
+ flakyFetch(opts) {
133
+ const fault = {
134
+ action: {
135
+ kind: "flaky-fetch",
136
+ ...(opts?.rejectionMessage !== undefined
137
+ ? { rejectionMessage: opts.rejectionMessage }
138
+ : {}),
139
+ },
140
+ };
141
+ return applyRuntimeCommon(fault, opts);
142
+ },
143
+ /**
144
+ * Skew `Date.now()`, `performance.now()`, and the no-arg `Date`
145
+ * constructor forward by `skewMs`. Use to surface token-expiry,
146
+ * cache-bust, and "clock drift" code paths without waiting real time.
147
+ */
148
+ clockSkew(skewMs, opts) {
149
+ if (!Number.isFinite(skewMs) || !Number.isInteger(skewMs)) {
150
+ throw new Error(`faults.clockSkew: skewMs must be a finite integer (got ${skewMs})`);
151
+ }
152
+ const fault = { action: { kind: "clock-skew", skewMs } };
153
+ return applyRuntimeCommon(fault, opts);
154
+ },
155
+ };
156
+ function applyRuntimeCommon(fault, opts) {
157
+ if (opts?.urlPattern !== undefined)
158
+ fault.urlPattern = opts.urlPattern;
159
+ if (opts?.probability !== undefined)
160
+ fault.probability = opts.probability;
161
+ if (opts?.name !== undefined)
162
+ fault.name = opts.name;
163
+ return fault;
164
+ }
165
+ //# sourceMappingURL=faults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faults.js","sourceRoot":"","sources":["../src/faults.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type { Fault, FaultInjectionStats, FaultRule, LifecycleAction, LifecycleFault, LifecycleFaultStats, LifecycleStage, Rng, RuntimeAction, RuntimeFault, RuntimeFaultStats, StorageScope, UrlMatcher, } from "./types.js";
2
+ export { faults, type FaultHelperOptions, type LifecycleHelperOptions, type RuntimeHelperOptions, } from "./faults.js";
3
+ export { buildRuntimeFaultsScript, compileRuntimeFaults, mergeRuntimeStats, runtimeFaultName, runtimeMatchesUrl, type CompiledRuntimeFault, } from "./runtime-faults.js";
4
+ export { compileLifecycleFaults, executeLifecycleAction, lifecycleFaultName, lifecycleFaultsAtStage, lifecycleMatchesUrl, lifecycleStatsFrom, PlaywrightLifecycleExecutor, shouldFireProbability, type CompiledLifecycleFault, type LifecycleActionExecutor, } from "./lifecycle-faults.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,KAAK,EACL,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,GAAG,EACH,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,MAAM,EACN,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,GAC1B,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,2BAA2B,EAC3B,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Network-level (FaultRule helpers + builders)
2
+ export { faults, } from "./faults.js";
3
+ // Runtime-level (addInitScript-based monkey patches)
4
+ export { buildRuntimeFaultsScript, compileRuntimeFaults, mergeRuntimeStats, runtimeFaultName, runtimeMatchesUrl, } from "./runtime-faults.js";
5
+ // Page lifecycle (Playwright Page / BrowserContext at named stages)
6
+ export { compileLifecycleFaults, executeLifecycleAction, lifecycleFaultName, lifecycleFaultsAtStage, lifecycleMatchesUrl, lifecycleStatsFrom, PlaywrightLifecycleExecutor, shouldFireProbability, } from "./lifecycle-faults.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,+CAA+C;AAC/C,OAAO,EACL,MAAM,GAIP,MAAM,aAAa,CAAC;AAErB,qDAAqD;AACrD,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,GAElB,MAAM,qBAAqB,CAAC;AAE7B,oEAAoE;AACpE,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,2BAA2B,EAC3B,qBAAqB,GAGtB,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Page-lifecycle fault injection.
3
+ *
4
+ * Distinct from network-side `FaultRule` (request-scoped, applied via
5
+ * Playwright `route()`). These are page-scoped client-side perturbations
6
+ * applied at well-defined stages of a page visit — CPU throttle, storage
7
+ * wipe, Service Worker cache eviction, key/value tampering. The crawler
8
+ * decides when to call into the executor; the executor decides how to
9
+ * realise each `LifecycleAction` against the browser.
10
+ *
11
+ * Pure helpers (compile / match / probability roll / name derivation) live
12
+ * here so the routing logic is unit-testable without a real browser. The
13
+ * Playwright-backed executor lives at the bottom of this file.
14
+ */
15
+ import type { BrowserContext, Page } from "playwright";
16
+ import type { LifecycleAction, LifecycleFault, LifecycleFaultStats, LifecycleStage } from "./types.js";
17
+ /** Compiled form: regex pre-compiled, name pre-derived. */
18
+ export interface CompiledLifecycleFault {
19
+ fault: LifecycleFault;
20
+ /** null when `fault.urlPattern` was omitted (matches every URL). */
21
+ pattern: RegExp | null;
22
+ name: string;
23
+ matched: number;
24
+ fired: number;
25
+ errored: number;
26
+ }
27
+ /** Auto-derive a stats label when the user didn't set `fault.name`. */
28
+ export declare function lifecycleFaultName(fault: LifecycleFault): string;
29
+ export declare function compileLifecycleFaults(faults: LifecycleFault[] | undefined): CompiledLifecycleFault[];
30
+ /** True when `compiled.pattern` matches `url` (or no pattern was set). */
31
+ export declare function lifecycleMatchesUrl(compiled: Pick<CompiledLifecycleFault, "pattern">, url: string): boolean;
32
+ /**
33
+ * Roll the seeded RNG against `probability`. Returns true when the fault
34
+ * should fire. `prob >= 1` (or undefined) always fires; `prob <= 0` never
35
+ * fires; anything in between samples one number from `rng`.
36
+ *
37
+ * RNG consumption is deliberately conditional on `prob < 1` so that adding
38
+ * a probability-1 fault to a config doesn't shift the seed sequence for
39
+ * existing chaos action selection.
40
+ */
41
+ export declare function shouldFireProbability(prob: number | undefined, rng: {
42
+ next(): number;
43
+ }): boolean;
44
+ /** Pick the compiled faults that target a given lifecycle stage. */
45
+ export declare function lifecycleFaultsAtStage(compiled: readonly CompiledLifecycleFault[], stage: LifecycleStage): CompiledLifecycleFault[];
46
+ export declare function lifecycleStatsFrom(compiled: readonly CompiledLifecycleFault[]): LifecycleFaultStats[];
47
+ /**
48
+ * Browser-side primitives needed to realise each `LifecycleAction`. One
49
+ * method per action kind so tests can fake exactly what they exercise
50
+ * without standing up Playwright.
51
+ */
52
+ export interface LifecycleActionExecutor {
53
+ cpuThrottle(rate: number): Promise<void>;
54
+ clearStorage(scopes: readonly ("localStorage" | "sessionStorage" | "cookies" | "indexedDB")[]): Promise<void>;
55
+ evictCache(cacheNames?: readonly string[]): Promise<void>;
56
+ tamperStorage(scope: "localStorage" | "sessionStorage", key: string, value: string): Promise<void>;
57
+ }
58
+ /** Dispatch a single `LifecycleAction` to the right executor method. */
59
+ export declare function executeLifecycleAction(action: LifecycleAction, executor: LifecycleActionExecutor): Promise<void>;
60
+ /**
61
+ * Real executor backed by Playwright. CPU throttle requires a CDP session;
62
+ * we attach lazily and reuse across calls on the same page.
63
+ */
64
+ export declare class PlaywrightLifecycleExecutor implements LifecycleActionExecutor {
65
+ private readonly page;
66
+ private readonly context;
67
+ private cdp;
68
+ constructor(page: Page, context: BrowserContext);
69
+ private getCdp;
70
+ cpuThrottle(rate: number): Promise<void>;
71
+ clearStorage(scopes: readonly ("localStorage" | "sessionStorage" | "cookies" | "indexedDB")[]): Promise<void>;
72
+ evictCache(cacheNames?: readonly string[]): Promise<void>;
73
+ tamperStorage(scope: "localStorage" | "sessionStorage", key: string, value: string): Promise<void>;
74
+ }
75
+ //# sourceMappingURL=lifecycle-faults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-faults.d.ts","sourceRoot":"","sources":["../src/lifecycle-faults.ts"],"names":[],"mappings":"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"}