@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 +21 -0
- package/README.md +82 -0
- package/dist/faults.d.ts +98 -0
- package/dist/faults.d.ts.map +1 -0
- package/dist/faults.js +165 -0
- package/dist/faults.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle-faults.d.ts +75 -0
- package/dist/lifecycle-faults.d.ts.map +1 -0
- package/dist/lifecycle-faults.js +187 -0
- package/dist/lifecycle-faults.js.map +1 -0
- package/dist/random.d.ts +12 -0
- package/dist/random.d.ts.map +1 -0
- package/dist/random.js +20 -0
- package/dist/random.js.map +1 -0
- package/dist/runtime-faults.d.ts +71 -0
- package/dist/runtime-faults.d.ts.map +1 -0
- package/dist/runtime-faults.js +231 -0
- package/dist/runtime-faults.js.map +1 -0
- package/dist/types.d.ts +191 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
- package/src/faults.test.ts +127 -0
- package/src/faults.ts +225 -0
- package/src/index.ts +48 -0
- package/src/lifecycle-faults.test.ts +252 -0
- package/src/lifecycle-faults.ts +252 -0
- package/src/random.ts +26 -0
- package/src/runtime-faults.test.ts +282 -0
- package/src/runtime-faults.ts +255 -0
- package/src/types.ts +196 -0
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
|
package/dist/faults.d.ts
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|