Skip to content
Latest stable: v0.9.0.

Presets

Presets are the recommended quick-start path. They wrap realistic scenarios - flaky backend, mobile-3g, degraded checkout - into named bundles you drop into a config:

import { injectChaos } from '@chaos-maker/playwright';
await injectChaos(page, {
presets: ['mobile-3g', 'checkout-degraded'],
seed: 1234,
});

Layer presets for compound scenarios (mobile network + checkout instability), or mix a preset with explicit rules when a single endpoint needs tighter control:

await injectChaos(page, {
presets: ['mobile-3g'],
network: {
failures: [{ urlPattern: '/api/checkout', statusCode: 500, probability: 1 }],
},
});

Lead with presets for onboarding, smoke coverage, and exploratory resilience tests. Drop down to explicit configs for product-critical flows where the exact status code, selector, message direction, and probability matter.

Pick presets the way engineers describe scenarios - flaky backend, bad mobile network, checkout degradation, lossy realtime. Layer multiple presets when a scenario spans transports.

camelCase nameKebab aliasesTransportsIntent
slowNetworkslow-apinetworkEvery request is delayed by 2000ms. Smoke-test loading states.
flakyConnectionflaky-api, api-flakynetworkRare aborts (5%) and latency (10% @ 3000ms) on every request.
offlineModeoffline-modenetworkBlock all requests like an offline browser.
unstableApihigh-latencynetwork10% 500s and 20% latency (1000ms) scoped to /api/.
mobileThreeGmobile-3gnetworkAll requests get 1500ms latency plus 2% aborts. Approximates 3G round-trip; does not throttle bytes.
checkoutDegradedcheckout-degradednetworkPayment instability scoped to /checkout and /api/payments - 30% latency at 800ms, occasional 503/500.
degradedUiuiDisable buttons (20%) and hide links (10%).
unreliableWebSocketwebsocket-instabilitywebsocket10% drops, 500ms inbound delays, 5% truncations.
unreliableEventStreamrealtime-lagsse5% drops, 200ms delays, 2% close-after-2s on every stream.
aiSlowFirstChunkai-slow-first-chunkfetch-stream, sse, websocketFirst streamed chunk held for 3000ms. Smoke-test typing indicators and skeleton states.
aiStreamPausedai-stream-pausedfetch-stream, sse, websocketStream stalls for 5000ms after chunk 10, then resumes.
aiStreamTruncatedai-stream-truncatedfetch-stream, sse, websocketStream cut after chunk 20. Assert partial-response handling.
aiToolCallFailsai-tool-call-failsfetch-streamChunks carrying tool/function-call wire markers (tool_calls, tool_use, function_call) arrive as malformed JSON, tagged ai:tool-call-failed.
aiRetryLoopai-retry-loopnetwork429 on the first two matching requests, success from the third. Exercises backoff and retry UI.
aiReconnectAfterDropai-reconnect-after-dropfetch-stream, sseStream closes early (after chunk 5, or 3s for SSE). Assert the app reconnects or resumes.
aiMobileInterruptai-mobile-interruptfetch-stream, sse, uiUser backgrounds the app mid-generation: tab reports hidden for 3s (from 1s) while the stream drops (after chunk 10, or 4s for SSE). Assert visibility pause logic plus stream recovery.

The ai* presets reproduce the failure modes chat and assistant interfaces hit in production. The delay, pause, and truncate presets are generated from the same compiler the ai shorthand uses, so presets: ['ai-slow-first-chunk'] and ai: { firstChunkDelayMs: 3000 } produce identical rules; the preset form composes with other presets and needs no extra config. All of them apply to every URL, so scope your test to the chat call or pair the preset with app-side assertions that tolerate chaos on secondary requests.

await injectChaos(page, {
presets: ['ai-slow-first-chunk'],
seed: 42,
});
  • aiToolCallFails targets chunk CONTENT: only chunks whose decoded text matches the tool-call marker pattern are corrupted, so prose renders normally while the structured payload breaks mid-stream. The corruption event carries phase: 'ai:tool-call-failed' for report tooling.
  • aiRetryLoop uses firstN: 2 counting, so exactly the first two matching requests fail regardless of how many retries the app makes.
  • aiMobileInterrupt is the first preset to combine transport rules with a human-interaction trigger: the tabHidden trigger flips document.visibilityState while close rules drop the stream. Trigger merging is per trigger, so setting your own userInteraction.tabHidden alongside the preset overrides the preset’s timing.
  • For markdown/code-fence breakage, use a stream replay fixture with a split mutation instead of a preset; splitting needs a captured fixture.

The presets fire deterministically given a fixed seed. Existence-check, not rate, keeps tests stable across browsers:

  • mobile-3g - observed elapsed time on a fetch should exceed 1000ms (well below the 1500ms target).
  • checkout-degraded - every rule on /checkout and /api/payments is probabilistic (30% latency, 5% / 10% failure), so a single request is not enough to assert against. Issue a small fixed batch (e.g. 20 sequential requests across /checkout and /api/payments) under a fixed seed, then call getChaosLog() and assert at least one event whose detail.url matches /checkout or /api/payments (any of network:latency, network:failure).
  • unreliableWebSocket - chaos log should contain at least one websocket:delay event with applied: true.
  • unreliableEventStream - chaos log should contain at least one sse:delay or sse:close event.
  • aiSlowFirstChunk - first-chunk latency measured from before the fetch call should exceed 3000ms, and the log should contain an applied fetch-stream:chunk-delayed event.
  • aiToolCallFails - the log should contain an applied fetch-stream:chunk-corrupted event with detail.phase === 'ai:tool-call-failed'.
  • aiRetryLoop - exactly two applied network:failure events with statusCode: 429, then a clean response.
  • aiMobileInterrupt - the log should contain an applied ui:visibility event with detail.phase === 'user:tab-hidden' plus an applied fetch-stream:truncated (or sse:close) event.

Kebab-case aliases (slow-api, flaky-api, api-flaky, offline-mode, high-latency, mobile-3g, checkout-degraded, websocket-instability, realtime-lag, and the ai-* names) are registry-only. They resolve via presets: ['mobile-3g'] and new PresetRegistry().get('mobile-3g'). They are NOT keys on the legacy presets record export - presets['mobile-3g'] is undefined by design. Use the camelCase key (presets.mobileThreeG) when reading from the record.

Register your own bundle inline via customPresets. Names collide fail-fast against built-ins and against each other.

await injectChaos(page, {
customPresets: {
'team-flow': {
network: {
failures: [{ urlPattern: '/checkout', statusCode: 503, probability: 1 }],
},
},
},
presets: ['team-flow'],
});

Custom preset values may carry only rule arrays plus the optional groups field. presets, customPresets, seed, and debug are rejected at validation. Dependency chains are out of scope.

import { ChaosConfigBuilder } from '@chaos-maker/core';
const config = new ChaosConfigBuilder()
.usePreset('slow-api')
.failRequests('/api/checkout', 500, 1)
.build();

.usePreset() silently dedupes within the builder while preserving insertion order. Empty / whitespace-only names throw, matching the schema and registry rules.

Every failure mode surfaces as a ChaosConfigError at construction time, never at runtime:

  • Unknown preset name in presets[] - message names the offending name and lists the registered ones.
  • Custom preset value carrying presets, customPresets, seed, or debug - strict-key rejection.
  • Empty or whitespace-only preset name (in either presets[] or customPresets keys).
  • Custom preset whose name collides with a built-in (slowNetwork, slow-api, etc.) or with another custom preset.
  • Group name collision between a preset’s groups and the user’s groups. Workaround: copy the preset locally via customPresets and rename the colliding group.

Built-in preset configs are deep-frozen. Attempts to mutate them via presets.slowNetwork or registry.get(...) throw in strict mode (and are no-ops in sloppy mode). Custom presets passed via customPresets are NOT frozen on input - your own preset literals stay mutable in your code. The engine takes a deep clone at expansion time, so any post-construction tweaks you make are not observed.

Presets are the primitive resilience bundles. Scenario profiles are the layer above - one named entry that composes presets, seed, debug, and rule slices into a single identifier you reach for from many tests. See the scenario profiles concept for the full surface, including the runtime override block applied at the inject site.

import { presets } from '@chaos-maker/core';
await injectChaos(page, {
...presets.unstableApi,
seed: 42,
});

Still supported for migration. The seed belongs at the root config level, so it can be added to any preset before injection. Prefer the declarative presets: field for new code.

When a preset’s rules all hit the same API surface, register the shared targeting on the top-level matchers registry and reference it from each rule with matcher: 'name'. Named matcher resolution runs after preset expansion, so rules brought in by a preset can reference top-level matchers without duplicating matcher fields.