@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
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
/** Auto-derive a stats label when the user didn't set `fault.name`. */
|
|
16
|
+
export function lifecycleFaultName(fault) {
|
|
17
|
+
if (fault.name)
|
|
18
|
+
return fault.name;
|
|
19
|
+
const a = fault.action;
|
|
20
|
+
switch (a.kind) {
|
|
21
|
+
case "cpu-throttle":
|
|
22
|
+
return `cpu-throttle:${a.rate}x`;
|
|
23
|
+
case "clear-storage":
|
|
24
|
+
return `clear-storage:${a.scopes.join("+")}`;
|
|
25
|
+
case "evict-cache":
|
|
26
|
+
return a.cacheNames && a.cacheNames.length > 0
|
|
27
|
+
? `evict-cache:${a.cacheNames.join("+")}`
|
|
28
|
+
: "evict-cache";
|
|
29
|
+
case "tamper-storage":
|
|
30
|
+
return `tamper-storage:${a.scope}.${a.key}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function compilePattern(matcher) {
|
|
34
|
+
if (matcher === undefined)
|
|
35
|
+
return null;
|
|
36
|
+
return matcher instanceof RegExp ? matcher : new RegExp(matcher);
|
|
37
|
+
}
|
|
38
|
+
export function compileLifecycleFaults(faults) {
|
|
39
|
+
if (!faults || faults.length === 0)
|
|
40
|
+
return [];
|
|
41
|
+
return faults.map((fault) => ({
|
|
42
|
+
fault,
|
|
43
|
+
pattern: compilePattern(fault.urlPattern),
|
|
44
|
+
name: lifecycleFaultName(fault),
|
|
45
|
+
matched: 0,
|
|
46
|
+
fired: 0,
|
|
47
|
+
errored: 0,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
/** True when `compiled.pattern` matches `url` (or no pattern was set). */
|
|
51
|
+
export function lifecycleMatchesUrl(compiled, url) {
|
|
52
|
+
return compiled.pattern === null || compiled.pattern.test(url);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Roll the seeded RNG against `probability`. Returns true when the fault
|
|
56
|
+
* should fire. `prob >= 1` (or undefined) always fires; `prob <= 0` never
|
|
57
|
+
* fires; anything in between samples one number from `rng`.
|
|
58
|
+
*
|
|
59
|
+
* RNG consumption is deliberately conditional on `prob < 1` so that adding
|
|
60
|
+
* a probability-1 fault to a config doesn't shift the seed sequence for
|
|
61
|
+
* existing chaos action selection.
|
|
62
|
+
*/
|
|
63
|
+
export function shouldFireProbability(prob, rng) {
|
|
64
|
+
if (prob === undefined || prob >= 1)
|
|
65
|
+
return true;
|
|
66
|
+
if (prob <= 0)
|
|
67
|
+
return false;
|
|
68
|
+
return rng.next() < prob;
|
|
69
|
+
}
|
|
70
|
+
/** Pick the compiled faults that target a given lifecycle stage. */
|
|
71
|
+
export function lifecycleFaultsAtStage(compiled, stage) {
|
|
72
|
+
return compiled.filter((c) => c.fault.when === stage);
|
|
73
|
+
}
|
|
74
|
+
export function lifecycleStatsFrom(compiled) {
|
|
75
|
+
return compiled.map((c) => ({
|
|
76
|
+
name: c.name,
|
|
77
|
+
matched: c.matched,
|
|
78
|
+
fired: c.fired,
|
|
79
|
+
errored: c.errored,
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
/** Dispatch a single `LifecycleAction` to the right executor method. */
|
|
83
|
+
export async function executeLifecycleAction(action, executor) {
|
|
84
|
+
switch (action.kind) {
|
|
85
|
+
case "cpu-throttle":
|
|
86
|
+
await executor.cpuThrottle(action.rate);
|
|
87
|
+
return;
|
|
88
|
+
case "clear-storage":
|
|
89
|
+
await executor.clearStorage(action.scopes);
|
|
90
|
+
return;
|
|
91
|
+
case "evict-cache":
|
|
92
|
+
await executor.evictCache(action.cacheNames);
|
|
93
|
+
return;
|
|
94
|
+
case "tamper-storage":
|
|
95
|
+
await executor.tamperStorage(action.scope, action.key, action.value);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Real executor backed by Playwright. CPU throttle requires a CDP session;
|
|
101
|
+
* we attach lazily and reuse across calls on the same page.
|
|
102
|
+
*/
|
|
103
|
+
export class PlaywrightLifecycleExecutor {
|
|
104
|
+
page;
|
|
105
|
+
context;
|
|
106
|
+
cdp = null;
|
|
107
|
+
constructor(page, context) {
|
|
108
|
+
this.page = page;
|
|
109
|
+
this.context = context;
|
|
110
|
+
}
|
|
111
|
+
getCdp() {
|
|
112
|
+
if (this.cdp === null) {
|
|
113
|
+
this.cdp = this.context.newCDPSession(this.page);
|
|
114
|
+
}
|
|
115
|
+
return this.cdp;
|
|
116
|
+
}
|
|
117
|
+
async cpuThrottle(rate) {
|
|
118
|
+
const client = await this.getCdp();
|
|
119
|
+
await client.send("Emulation.setCPUThrottlingRate", { rate });
|
|
120
|
+
}
|
|
121
|
+
async clearStorage(scopes) {
|
|
122
|
+
const inPage = scopes.filter((s) => s !== "cookies");
|
|
123
|
+
if (inPage.length > 0) {
|
|
124
|
+
// page.evaluate runs in the page context; we pass the scope set so the
|
|
125
|
+
// browser side decides which storages to wipe without us injecting JS.
|
|
126
|
+
await this.page.evaluate(async (scopeList) => {
|
|
127
|
+
const s = new Set(scopeList);
|
|
128
|
+
if (s.has("localStorage")) {
|
|
129
|
+
try {
|
|
130
|
+
window.localStorage.clear();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
/* SecurityError on opaque origins */
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (s.has("sessionStorage")) {
|
|
137
|
+
try {
|
|
138
|
+
window.sessionStorage.clear();
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* SecurityError on opaque origins */
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (s.has("indexedDB") && "indexedDB" in window) {
|
|
145
|
+
// databases() is not in every browser; guard.
|
|
146
|
+
// @ts-ignore - older lib.dom typings omit databases()
|
|
147
|
+
const dbs = (await indexedDB.databases?.()) ?? [];
|
|
148
|
+
await Promise.all(dbs
|
|
149
|
+
.map((d) => d.name)
|
|
150
|
+
.filter((n) => typeof n === "string")
|
|
151
|
+
.map((name) => new Promise((resolve) => {
|
|
152
|
+
const req = indexedDB.deleteDatabase(name);
|
|
153
|
+
req.onsuccess = () => resolve();
|
|
154
|
+
req.onerror = () => resolve();
|
|
155
|
+
req.onblocked = () => resolve();
|
|
156
|
+
})));
|
|
157
|
+
}
|
|
158
|
+
}, inPage);
|
|
159
|
+
}
|
|
160
|
+
if (scopes.includes("cookies")) {
|
|
161
|
+
// Context-level: drops every cookie across every page in the context.
|
|
162
|
+
await this.context.clearCookies();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async evictCache(cacheNames) {
|
|
166
|
+
await this.page.evaluate(async (names) => {
|
|
167
|
+
if (!("caches" in self))
|
|
168
|
+
return;
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
|
+
const c = self.caches;
|
|
171
|
+
const all = await c.keys();
|
|
172
|
+
const target = names && names.length > 0 ? all.filter((k) => names.includes(k)) : all;
|
|
173
|
+
await Promise.all(target.map((k) => c.delete(k)));
|
|
174
|
+
}, cacheNames);
|
|
175
|
+
}
|
|
176
|
+
async tamperStorage(scope, key, value) {
|
|
177
|
+
await this.page.evaluate(({ scope, key, value }) => {
|
|
178
|
+
try {
|
|
179
|
+
(scope === "localStorage" ? window.localStorage : window.sessionStorage).setItem(key, value);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
/* SecurityError on opaque origins */
|
|
183
|
+
}
|
|
184
|
+
}, { scope, key, value });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=lifecycle-faults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle-faults.js","sourceRoot":"","sources":["../src/lifecycle-faults.ts"],"names":[],"mappings":"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"}
|
package/dist/random.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny seeded RNG used by the package's own unit tests so they don't have
|
|
3
|
+
* to depend on chaosbringer. Not part of the public surface — only re-export
|
|
4
|
+
* from `index.ts` if a downstream legitimately needs the same generator
|
|
5
|
+
* function (most won't; they'll bring their own RNG).
|
|
6
|
+
*/
|
|
7
|
+
import type { Rng } from "./types.js";
|
|
8
|
+
export interface SeededRng extends Rng {
|
|
9
|
+
readonly seed: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function createRng(seed: number): SeededRng;
|
|
12
|
+
//# sourceMappingURL=random.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"random.d.ts","sourceRoot":"","sources":["../src/random.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,WAAW,SAAU,SAAQ,GAAG;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAYjD"}
|
package/dist/random.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny seeded RNG used by the package's own unit tests so they don't have
|
|
3
|
+
* to depend on chaosbringer. Not part of the public surface — only re-export
|
|
4
|
+
* from `index.ts` if a downstream legitimately needs the same generator
|
|
5
|
+
* function (most won't; they'll bring their own RNG).
|
|
6
|
+
*/
|
|
7
|
+
export function createRng(seed) {
|
|
8
|
+
let state = seed >>> 0;
|
|
9
|
+
return {
|
|
10
|
+
seed,
|
|
11
|
+
next() {
|
|
12
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
13
|
+
let t = state;
|
|
14
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
15
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
16
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=random.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"random.js","sourceRoot":"","sources":["../src/random.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC;IACvB,OAAO;QACL,IAAI;QACJ,IAAI;YACF,KAAK,GAAG,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;YACnC,IAAI,CAAC,GAAG,KAAK,CAAC;YACd,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACrC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;YAC1C,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC;QAC/C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS-runtime fault injection.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from `FaultRule` (request-scoped, applied via Playwright `route()`)
|
|
5
|
+
* and `LifecycleFault` (one-shot at named stages of a page visit). These are
|
|
6
|
+
* persistent monkey-patches injected into every page via `addInitScript`,
|
|
7
|
+
* subverting in-page JS APIs (fetch / Date / storage / addEventListener) so
|
|
8
|
+
* the app sees client-side failures that no network mock would expose.
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* - `flaky-fetch`: `window.fetch` rejects with a TypeError before any
|
|
12
|
+
* network round-trip — simulates "Failed to fetch" / DNS down / Service
|
|
13
|
+
* Worker reject. Different from `faults.status(500)`, which still
|
|
14
|
+
* resolves the promise.
|
|
15
|
+
* - `clock-skew`: `Date.now` / `performance.now` are shifted forward by N
|
|
16
|
+
* ms — exposes token-expiry / cache-bust bugs on long sessions.
|
|
17
|
+
*
|
|
18
|
+
* Pure helpers (`buildRuntimeFaultsScript`, `runtimeFaultName`,
|
|
19
|
+
* `compileRuntimeFaults`) generate / serialize the init script and roll
|
|
20
|
+
* probability — unit-testable without a browser. Stats are reported by the
|
|
21
|
+
* in-page script via a known `window.__chaosbringerRuntimeStats` global; the
|
|
22
|
+
* crawler reads it after each page visit.
|
|
23
|
+
*/
|
|
24
|
+
import type { Rng, RuntimeFault, RuntimeFaultStats } from "./types.js";
|
|
25
|
+
/** Compiled form: regex pre-compiled, name pre-derived. */
|
|
26
|
+
export interface CompiledRuntimeFault {
|
|
27
|
+
fault: RuntimeFault;
|
|
28
|
+
/** null when `fault.urlPattern` was omitted. */
|
|
29
|
+
pattern: RegExp | null;
|
|
30
|
+
name: string;
|
|
31
|
+
matched: number;
|
|
32
|
+
fired: number;
|
|
33
|
+
}
|
|
34
|
+
/** Auto-derive a stats label when the user didn't set `fault.name`. */
|
|
35
|
+
export declare function runtimeFaultName(fault: RuntimeFault): string;
|
|
36
|
+
export declare function compileRuntimeFaults(faults: RuntimeFault[] | undefined): CompiledRuntimeFault[];
|
|
37
|
+
/** True when `compiled.pattern` matches `url` (or no pattern was set). */
|
|
38
|
+
export declare function runtimeMatchesUrl(compiled: Pick<CompiledRuntimeFault, "pattern">, url: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Decide whether a probabilistic fault fires this time. Mirrors the
|
|
41
|
+
* lifecycle / network helpers so all three layers share a deterministic
|
|
42
|
+
* roll behaviour given the same RNG.
|
|
43
|
+
*/
|
|
44
|
+
export declare function shouldFireProbability(probability: number | undefined, rng: Rng): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Build the init script body. Self-contained IIFE — no closure over the
|
|
47
|
+
* caller's scope, no external imports — because Playwright serializes init
|
|
48
|
+
* scripts as plain text and runs them in a fresh frame on every navigation.
|
|
49
|
+
*
|
|
50
|
+
* `seed` lets each page roll deterministic probabilities. Pass the
|
|
51
|
+
* crawler's seed so a `(seed, runtimeFaults)` pair always produces the same
|
|
52
|
+
* pattern of injections.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildRuntimeFaultsScript(faults: ReadonlyArray<RuntimeFault>, seed: number): string;
|
|
55
|
+
/**
|
|
56
|
+
* Read the in-page stats counter and merge into the compiled-fault counters
|
|
57
|
+
* (`matched` and `fired`). Returns the merged stats; the compiled-fault
|
|
58
|
+
* objects are mutated in place so the next page picks up where this one
|
|
59
|
+
* left off.
|
|
60
|
+
*
|
|
61
|
+
* Stats keys are array indices so two faults with the same name don't
|
|
62
|
+
* collide their counters. Both legacy name-keyed snapshots and the
|
|
63
|
+
* current index-keyed shape are accepted: name-keyed entries are applied
|
|
64
|
+
* to the first compiled fault with that name (the safe, non-collapsing
|
|
65
|
+
* fallback used when reading older traces).
|
|
66
|
+
*/
|
|
67
|
+
export declare function mergeRuntimeStats(compiled: CompiledRuntimeFault[], pageStats: Record<string, {
|
|
68
|
+
matched: number;
|
|
69
|
+
fired: number;
|
|
70
|
+
}>): RuntimeFaultStats[];
|
|
71
|
+
//# sourceMappingURL=runtime-faults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-faults.d.ts","sourceRoot":"","sources":["../src/runtime-faults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,iBAAiB,EAAc,MAAM,YAAY,CAAC;AAEnF,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,gDAAgD;IAChD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAS5D;AAOD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EAAE,GAAG,SAAS,GACjC,oBAAoB,EAAE,CASxB;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC/C,GAAG,EAAE,MAAM,GACV,OAAO,CAET;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAKxF;AAYD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,EACnC,IAAI,EAAE,MAAM,GACX,MAAM,CA0GR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,oBAAoB,EAAE,EAChC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5D,iBAAiB,EAAE,CAuBrB"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS-runtime fault injection.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from `FaultRule` (request-scoped, applied via Playwright `route()`)
|
|
5
|
+
* and `LifecycleFault` (one-shot at named stages of a page visit). These are
|
|
6
|
+
* persistent monkey-patches injected into every page via `addInitScript`,
|
|
7
|
+
* subverting in-page JS APIs (fetch / Date / storage / addEventListener) so
|
|
8
|
+
* the app sees client-side failures that no network mock would expose.
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* - `flaky-fetch`: `window.fetch` rejects with a TypeError before any
|
|
12
|
+
* network round-trip — simulates "Failed to fetch" / DNS down / Service
|
|
13
|
+
* Worker reject. Different from `faults.status(500)`, which still
|
|
14
|
+
* resolves the promise.
|
|
15
|
+
* - `clock-skew`: `Date.now` / `performance.now` are shifted forward by N
|
|
16
|
+
* ms — exposes token-expiry / cache-bust bugs on long sessions.
|
|
17
|
+
*
|
|
18
|
+
* Pure helpers (`buildRuntimeFaultsScript`, `runtimeFaultName`,
|
|
19
|
+
* `compileRuntimeFaults`) generate / serialize the init script and roll
|
|
20
|
+
* probability — unit-testable without a browser. Stats are reported by the
|
|
21
|
+
* in-page script via a known `window.__chaosbringerRuntimeStats` global; the
|
|
22
|
+
* crawler reads it after each page visit.
|
|
23
|
+
*/
|
|
24
|
+
/** Auto-derive a stats label when the user didn't set `fault.name`. */
|
|
25
|
+
export function runtimeFaultName(fault) {
|
|
26
|
+
if (fault.name)
|
|
27
|
+
return fault.name;
|
|
28
|
+
const a = fault.action;
|
|
29
|
+
switch (a.kind) {
|
|
30
|
+
case "flaky-fetch":
|
|
31
|
+
return "flaky-fetch";
|
|
32
|
+
case "clock-skew":
|
|
33
|
+
return `clock-skew:${a.skewMs}ms`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function compilePattern(matcher) {
|
|
37
|
+
if (matcher === undefined)
|
|
38
|
+
return null;
|
|
39
|
+
return matcher instanceof RegExp ? matcher : new RegExp(matcher);
|
|
40
|
+
}
|
|
41
|
+
export function compileRuntimeFaults(faults) {
|
|
42
|
+
if (!faults || faults.length === 0)
|
|
43
|
+
return [];
|
|
44
|
+
return faults.map((fault) => ({
|
|
45
|
+
fault,
|
|
46
|
+
pattern: compilePattern(fault.urlPattern),
|
|
47
|
+
name: runtimeFaultName(fault),
|
|
48
|
+
matched: 0,
|
|
49
|
+
fired: 0,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
/** True when `compiled.pattern` matches `url` (or no pattern was set). */
|
|
53
|
+
export function runtimeMatchesUrl(compiled, url) {
|
|
54
|
+
return compiled.pattern === null || compiled.pattern.test(url);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Decide whether a probabilistic fault fires this time. Mirrors the
|
|
58
|
+
* lifecycle / network helpers so all three layers share a deterministic
|
|
59
|
+
* roll behaviour given the same RNG.
|
|
60
|
+
*/
|
|
61
|
+
export function shouldFireProbability(probability, rng) {
|
|
62
|
+
const p = probability ?? 1;
|
|
63
|
+
if (p >= 1)
|
|
64
|
+
return true;
|
|
65
|
+
if (p <= 0)
|
|
66
|
+
return false;
|
|
67
|
+
return rng.next() < p;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Serialize a UrlMatcher into a structure the in-page script can rebuild
|
|
71
|
+
* without `eval`. Strings stay strings; RegExp becomes `{ source, flags }`.
|
|
72
|
+
*/
|
|
73
|
+
function serializeMatcher(m) {
|
|
74
|
+
if (m === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
if (m instanceof RegExp)
|
|
77
|
+
return { source: m.source, flags: m.flags };
|
|
78
|
+
return { source: m, flags: "" };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build the init script body. Self-contained IIFE — no closure over the
|
|
82
|
+
* caller's scope, no external imports — because Playwright serializes init
|
|
83
|
+
* scripts as plain text and runs them in a fresh frame on every navigation.
|
|
84
|
+
*
|
|
85
|
+
* `seed` lets each page roll deterministic probabilities. Pass the
|
|
86
|
+
* crawler's seed so a `(seed, runtimeFaults)` pair always produces the same
|
|
87
|
+
* pattern of injections.
|
|
88
|
+
*/
|
|
89
|
+
export function buildRuntimeFaultsScript(faults, seed) {
|
|
90
|
+
// Stats keys are indices, not names — two faults can legitimately share
|
|
91
|
+
// a name (`flaky-fetch` x2 with different urlPatterns) and we mustn't
|
|
92
|
+
// collapse their counters.
|
|
93
|
+
const serialized = faults.map((f, i) => ({
|
|
94
|
+
id: i,
|
|
95
|
+
name: runtimeFaultName(f),
|
|
96
|
+
pattern: serializeMatcher(f.urlPattern),
|
|
97
|
+
probability: typeof f.probability === "number" ? f.probability : 1,
|
|
98
|
+
action: f.action,
|
|
99
|
+
}));
|
|
100
|
+
// Body of the init script. Indented for readability; whitespace is fine
|
|
101
|
+
// because Playwright won't minify it.
|
|
102
|
+
return `(() => {
|
|
103
|
+
if (typeof window === "undefined") return;
|
|
104
|
+
if (window.__chaosbringerRuntimeFaultsInstalled) return;
|
|
105
|
+
window.__chaosbringerRuntimeFaultsInstalled = true;
|
|
106
|
+
window.__chaosbringerRuntimeStats = {};
|
|
107
|
+
|
|
108
|
+
// Park-Miller LCG — small, deterministic, good enough for fault rolls.
|
|
109
|
+
let __rng = ${seed >>> 0} || 1;
|
|
110
|
+
const __nextRoll = () => {
|
|
111
|
+
__rng = ((__rng * 16807) % 2147483647) | 0;
|
|
112
|
+
if (__rng <= 0) __rng += 2147483647;
|
|
113
|
+
return (__rng - 1) / 2147483646;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const faults = ${JSON.stringify(serialized)};
|
|
117
|
+
const stats = window.__chaosbringerRuntimeStats;
|
|
118
|
+
for (const f of faults) stats[String(f.id)] = { matched: 0, fired: 0 };
|
|
119
|
+
|
|
120
|
+
const matchUrl = (pattern, url) => {
|
|
121
|
+
if (!pattern) return true;
|
|
122
|
+
try {
|
|
123
|
+
return new RegExp(pattern.source, pattern.flags).test(url);
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const roll = (f) => {
|
|
130
|
+
const slot = stats[String(f.id)];
|
|
131
|
+
slot.matched++;
|
|
132
|
+
if (f.probability >= 1) {
|
|
133
|
+
slot.fired++;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (f.probability <= 0) return false;
|
|
137
|
+
const fired = __nextRoll() < f.probability;
|
|
138
|
+
if (fired) slot.fired++;
|
|
139
|
+
return fired;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// --- flaky-fetch ---
|
|
143
|
+
const fetchFaults = faults.filter((f) => f.action.kind === "flaky-fetch");
|
|
144
|
+
if (fetchFaults.length > 0 && typeof window.fetch === "function") {
|
|
145
|
+
const realFetch = window.fetch.bind(window);
|
|
146
|
+
window.fetch = function chaosFetch(input, init) {
|
|
147
|
+
const url =
|
|
148
|
+
typeof input === "string" ? input :
|
|
149
|
+
input instanceof URL ? input.toString() :
|
|
150
|
+
(input && typeof input.url === "string") ? input.url :
|
|
151
|
+
"";
|
|
152
|
+
for (const f of fetchFaults) {
|
|
153
|
+
if (matchUrl(f.pattern, url) && roll(f)) {
|
|
154
|
+
const msg = f.action.rejectionMessage || "chaosbringer: simulated fetch failure";
|
|
155
|
+
return Promise.reject(new TypeError(msg));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return realFetch(input, init);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- clock-skew ---
|
|
163
|
+
const skewFaults = faults.filter((f) => f.action.kind === "clock-skew");
|
|
164
|
+
if (skewFaults.length > 0) {
|
|
165
|
+
// Use a plain accumulator. Multi-day skews (e.g. 30 days ~ 2.6e9 ms)
|
|
166
|
+
// exceed int32 max, so any '| 0' truncation here would flip them negative.
|
|
167
|
+
let totalSkew = 0;
|
|
168
|
+
for (const f of skewFaults) {
|
|
169
|
+
if (matchUrl(f.pattern, location.href) && roll(f)) {
|
|
170
|
+
totalSkew += Number(f.action.skewMs);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (totalSkew !== 0) {
|
|
174
|
+
const realDateNow = Date.now.bind(Date);
|
|
175
|
+
Date.now = () => realDateNow() + totalSkew;
|
|
176
|
+
const realPerfNow = performance.now.bind(performance);
|
|
177
|
+
performance.now = () => realPerfNow() + totalSkew;
|
|
178
|
+
// Patch the Date constructor so \`new Date()\` (no args) also skews.
|
|
179
|
+
const RealDate = Date;
|
|
180
|
+
const SkewedDate = function (...args) {
|
|
181
|
+
if (args.length === 0) return new RealDate(realDateNow() + totalSkew);
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
return new RealDate(...args);
|
|
184
|
+
};
|
|
185
|
+
SkewedDate.now = Date.now;
|
|
186
|
+
SkewedDate.UTC = RealDate.UTC;
|
|
187
|
+
SkewedDate.parse = RealDate.parse;
|
|
188
|
+
SkewedDate.prototype = RealDate.prototype;
|
|
189
|
+
// @ts-ignore
|
|
190
|
+
window.Date = SkewedDate;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
})();`;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Read the in-page stats counter and merge into the compiled-fault counters
|
|
197
|
+
* (`matched` and `fired`). Returns the merged stats; the compiled-fault
|
|
198
|
+
* objects are mutated in place so the next page picks up where this one
|
|
199
|
+
* left off.
|
|
200
|
+
*
|
|
201
|
+
* Stats keys are array indices so two faults with the same name don't
|
|
202
|
+
* collide their counters. Both legacy name-keyed snapshots and the
|
|
203
|
+
* current index-keyed shape are accepted: name-keyed entries are applied
|
|
204
|
+
* to the first compiled fault with that name (the safe, non-collapsing
|
|
205
|
+
* fallback used when reading older traces).
|
|
206
|
+
*/
|
|
207
|
+
export function mergeRuntimeStats(compiled, pageStats) {
|
|
208
|
+
for (let i = 0; i < compiled.length; i++) {
|
|
209
|
+
const c = compiled[i];
|
|
210
|
+
// Index-keyed (current shape).
|
|
211
|
+
const ps = pageStats[String(i)];
|
|
212
|
+
if (ps) {
|
|
213
|
+
c.matched += ps.matched;
|
|
214
|
+
c.fired += ps.fired;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
// Backwards-compat: name-keyed (one slot per distinct name only;
|
|
218
|
+
// applied to the first compiled fault that wears that name).
|
|
219
|
+
const byName = pageStats[c.name];
|
|
220
|
+
if (byName && !compiled.slice(0, i).some((c2) => c2.name === c.name)) {
|
|
221
|
+
c.matched += byName.matched;
|
|
222
|
+
c.fired += byName.fired;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return compiled.map((c) => ({
|
|
226
|
+
rule: c.name,
|
|
227
|
+
matched: c.matched,
|
|
228
|
+
fired: c.fired,
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=runtime-faults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-faults.js","sourceRoot":"","sources":["../src/runtime-faults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAcH,uEAAuE;AACvE,MAAM,UAAU,gBAAgB,CAAC,KAAmB;IAClD,IAAI,KAAK,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAClC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IACvB,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,aAAa;YAChB,OAAO,aAAa,CAAC;QACvB,KAAK,YAAY;YACf,OAAO,cAAc,CAAC,CAAC,MAAM,IAAI,CAAC;IACtC,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,OAA+B;IACrD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,MAAkC;IAElC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,KAAK;QACL,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC;QACzC,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC;QAC7B,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC;KACT,CAAC,CAAC,CAAC;AACN,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAC/B,QAA+C,EAC/C,GAAW;IAEX,OAAO,QAAQ,CAAC,OAAO,KAAK,IAAI,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,WAA+B,EAAE,GAAQ;IAC7E,MAAM,CAAC,GAAG,WAAW,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,SAAS,gBAAgB,CAAC,CAAyB;IACjD,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,CAAC,YAAY,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAmC,EACnC,IAAY;IAEZ,wEAAwE;IACxE,sEAAsE;IACtE,2BAA2B;IAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACvC,EAAE,EAAE,CAAC;QACL,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;QACzB,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC;QACvC,WAAW,EAAE,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,EAAE,CAAC,CAAC,MAAM;KACjB,CAAC,CAAC,CAAC;IAEJ,wEAAwE;IACxE,sCAAsC;IACtC,OAAO;;;;;;;gBAOO,IAAI,KAAK,CAAC;;;;;;;mBAOP,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6EvC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAgC,EAChC,SAA6D;IAE7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;QACvB,+BAA+B;QAC/B,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,EAAE,EAAE,CAAC;YACP,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,CAAC;YACxB,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC;YACpB,SAAS;QACX,CAAC;QACD,iEAAiE;QACjE,6DAA6D;QAC7D,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC;YAC5B,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,KAAK,EAAE,CAAC,CAAC,KAAK;KACf,CAAC,CAAC,CAAC;AACN,CAAC"}
|