npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chaos-maker/playwright

v0.8.0

Published

Playwright adapter for @chaos-maker/core - one-line chaos injection in E2E tests

Readme

@chaos-maker/playwright

Playwright adapter for @chaos-maker/core. One-line chaos injection in E2E tests.

Install

npm install @chaos-maker/core @chaos-maker/playwright

Both packages are required - @chaos-maker/playwright loads the core UMD bundle into the browser page.

Usage

Direct API

import { test, expect } from '@playwright/test';
import { injectChaos, removeChaos, getChaosLog } from '@chaos-maker/playwright';

test('shows error when API fails', async ({ page }) => {
  try {
    await injectChaos(page, {
      network: {
        failures: [{ urlPattern: '/api/data', statusCode: 503, probability: 1.0 }]
      }
    });

    await page.goto('/dashboard');
    await expect(page.getByText('Something went wrong')).toBeVisible();

    // Check what chaos was applied
    const log = await getChaosLog(page);
    expect(log.some(e => e.type === 'network:failure' && e.applied)).toBe(true);
  } finally {
    await removeChaos(page);
  }
});

For direct API calls, use try / finally when a test can fail before explicit cleanup. removeChaos(page) restores the current document and is safe during teardown. Playwright addInitScript() entries stay registered on the Page, so a later page.reload() or page.goto() on the same reused page can run prior chaos init scripts again. Prefer the fixture, Playwright's default fresh page per test, or a new page/context when reload isolation matters.

Test Fixture

For automatic cleanup, use the built-in fixture:

import { test, expect } from '@chaos-maker/playwright/fixture';

test('handles slow network', async ({ page, chaos }) => {
  await chaos.inject({
    network: {
      latencies: [{ urlPattern: '/api/', delayMs: 3000, probability: 1.0 }]
    }
  });

  await page.goto('/');
  await expect(page.getByText('Loading')).toBeVisible();

  const log = await chaos.getLog();
  expect(log.some(e => e.type === 'network:latency' && e.applied)).toBe(true);
});
// chaos.remove() is called automatically after each test

With Presets

Drop a built-in preset by name with the declarative presets field:

await injectChaos(page, { presets: ['slow-api'] });

Register your own bundle inline via customPresets:

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

The legacy spread style still works for migration:

import { test, expect } from '@playwright/test';
import { presets } from '@chaos-maker/core';
import { injectChaos } from '@chaos-maker/playwright';

test('app works offline', async ({ page }) => {
  await injectChaos(page, presets.offlineMode);
  await page.goto('/');
  await expect(page.getByText('No connection')).toBeVisible();
});

With Config Builder

import { test } from '@playwright/test';
import { ChaosConfigBuilder } from '@chaos-maker/core';
import { injectChaos } from '@chaos-maker/playwright';

const config = new ChaosConfigBuilder()
  .failRequests('/api/checkout', 500, 0.5)
  .addLatency('/api/', 2000, 1.0)
  .build();

test('checkout handles combined chaos', async ({ page }) => {
  await injectChaos(page, config);
  await page.goto('/checkout');
  // ...
});

Rule Groups

Use Rule Groups to toggle a set of rules during a test without reinjecting chaos.

import { test, expect } from '@playwright/test';
import { ChaosConfigBuilder } from '@chaos-maker/core';
import {
  injectChaos,
  enableGroup,
  disableGroup,
  enableSWGroup,
  disableSWGroup,
} from '@chaos-maker/playwright';

test('toggles checkout chaos', async ({ page }) => {
  const config = new ChaosConfigBuilder()
    .defineGroup('payments', { enabled: false })
    .inGroup('payments')
    .failRequests('/api/pay', 503, 1)
    .build();

  await injectChaos(page, config);
  await page.goto('/checkout');

  await enableGroup(page, 'payments');
  await expect(page.getByText('Try again')).toBeVisible();

  await disableGroup(page, 'payments');
});

With the fixture, the same helpers are available as chaos.enableGroup(name) and chaos.disableGroup(name).

For Service Worker rules, use the SW helpers after injectSWChaos:

await enableSWGroup(page, 'payments');
await disableSWGroup(page, 'payments');

Browser-side enableGroup and disableGroup affect page rules from injectChaos. enableSWGroup and disableSWGroup affect Service Worker rules from injectSWChaos.

SSE and GraphQL

await injectChaos(page, {
  seed: 42,
  sse: {
    drops: [{ urlPattern: '/events', eventType: 'token', probability: 0.1 }],
  },
  network: {
    failures: [{
      urlPattern: '/graphql',
      graphqlOperation: 'GetUser',
      statusCode: 503,
      probability: 1,
    }],
  },
});
await page.goto('/dashboard');

SSE chaos and GraphQL operation matching use the same pre-navigation injectChaos() timing as fetch, XHR, and WebSocket chaos.

API

injectChaos(page, config, opts?)

Inject chaos into a Playwright page. Call before page.goto() to ensure all network requests are intercepted from the start.

  • page - Playwright Page instance
  • config - ChaosConfig object (see @chaos-maker/core for full config reference)
  • opts - optional. InjectChaosOptions:
    • tracing?: boolean | 'auto' - emit chaos events into the Playwright trace (see Debugging with trace). Requires testInfo when true.
    • testInfo?: TestInfo - active Playwright TestInfo (supplied automatically by the fixture).
    • traceOptions?: { verbose?: boolean; attachmentName?: string } - tune trace output.

removeChaos(page)

Stop chaos and restore original fetch/XHR/DOM behavior.

This restores the active document. It does not remove Playwright addInitScript() registrations from a reused Page, because Playwright does not expose a removal API for them.

getChaosLog(page)

Retrieve the chaos event log from the page. Returns ChaosEvent[] - every chaos check emitted since injection, with applied: true/false.

enableGroup(page, name) / disableGroup(page, name)

Toggle a browser-side Rule Group at runtime.

enableSWGroup(page, name, opts?) / disableSWGroup(page, name, opts?)

Toggle a Service Worker Rule Group at runtime. Pass opts.timeoutMs to override the Service Worker acknowledgement timeout.

Fixture: chaos

Available when importing test from @chaos-maker/playwright/fixture:

  • chaos.inject(config) - same as injectChaos(page, config)
  • chaos.remove() - same as removeChaos(page) (also called automatically after each test)
  • chaos.getLog() - same as getChaosLog(page)
  • chaos.enableGroup(name) - same as enableGroup(page, name)
  • chaos.disableGroup(name) - same as disableGroup(page, name)
  • chaos.enableSWGroup(name, opts?) - same as enableSWGroup(page, name, opts)
  • chaos.disableSWGroup(name, opts?) - same as disableSWGroup(page, name, opts)

Validation

injectChaos validates the config from Node BEFORE any page touch. A malformed config throws ChaosConfigError synchronously from the test runner - your test fails before navigation, not in the browser console. ChaosConfigError.issues is a structured ValidationIssue[] with path, code, ruleType, and optional expected / received. See the Rule Validation concept page.

import { injectChaos, ChaosConfigError } from '@chaos-maker/playwright';

try {
  await injectChaos(page, config, {
    validation: { unknownFields: 'warn' },
  });
} catch (e) {
  if (e instanceof ChaosConfigError) {
    for (const issue of e.issues) console.error(issue.path, issue.code, issue.message);
  }
  throw e;
}

Debugging with trace

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.

// playwright.config.ts
export default defineConfig({
  use: {
    trace: 'on-first-retry', // or 'on' / 'retain-on-failure'
  },
});
import { test, expect } from '@chaos-maker/playwright/fixture';

test('flaky checkout', async ({ page, chaos }) => {
  await chaos.inject({
    network: {
      failures: [{ urlPattern: '/api/pay', statusCode: 503, probability: 1.0 }],
    },
  });
  await page.goto('/checkout');
  await page.click('#pay');
  await expect(page.getByText('Order placed')).toBeVisible(); // fails
});

On failure, open the trace (bunx playwright show-trace ...). You'll see a step like:

chaos:network:failure /api/pay → 503

…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.

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 }).

Direct API users must supply testInfo explicitly:

import { injectChaos } from '@chaos-maker/playwright';
import { test } from '@playwright/test';

test('with direct API', async ({ page }, testInfo) => {
  await injectChaos(page, config, { tracing: true, testInfo });
  // ...
});

Leak diagnostics

Enable debug: true on the chaos config to surface leaked-runtime diagnostics in the event log. Filter getChaosLog(page) for type === 'debug' events with detail.reason covering double-patched globals, stale wrapper handles, orphaned observers, or active-instance conflicts. See @chaos-maker/core for the full reason list.

await injectChaos(page, { debug: true, network: { /* ... */ } });
await page.goto('/');
const issues = (await getChaosLog(page)).filter(
  (e) => e.type === 'debug' && /already-patched|stale|orphaned|active-instance-conflict/.test(String(e.detail.reason ?? '')),
);

Service Worker chaos

Intercept SW-originated fetches. Requires one line in your service-worker script.

// user's sw.js (classic)
importScripts('/chaos-maker-sw.js');
import {
  injectSWChaos,
  removeSWChaos,
  getSWChaosLog,
  getSWChaosLogFromSW,
  enableSWGroup,
  disableSWGroup,
} from '@chaos-maker/playwright';

test('SW-fetched /api returns 503', async ({ page }) => {
  await page.goto('/app-with-sw/');
  // wait for controller after your app's SW registration
  await injectSWChaos(page, {
    groups: [{ name: 'payments', enabled: false }],
    network: {
      failures: [{ urlPattern: '/api/data', statusCode: 503, probability: 1, group: 'payments' }],
    },
    seed: 1,
  });
  await enableSWGroup(page, 'payments');
  await page.click('#trigger');
  const log = await getSWChaosLog(page);
  expect(log.some(e => e.type === 'network:failure' && e.applied)).toBe(true);
  await disableSWGroup(page, 'payments');
  await removeSWChaos(page);
});

Use getSWChaosLog(page) for the page-buffered event log. This is the default assertion surface because it reflects events broadcast from the Service Worker to the page. Use getSWChaosLogFromSW(page) when you need a direct pull from the Service Worker's in-memory log, such as debugging a missed page-side broadcast.

removeSWChaos(page) stops the worker engine and clears both the page-buffered and worker-side logs. For full browser isolation between tests, unregister the app's Service Worker or use a fresh browser context.

Two artifacts ship in @chaos-maker/core:

  • dist/sw.js - IIFE bundle for classic SWs (importScripts('/chaos-maker-sw.js')).
  • dist/sw.mjs - ESM bundle for module SWs (import { installChaosSW } from '/chaos-maker-sw.mjs').

Serve whichever your SW type uses at a URL reachable from the service-worker scope.

License

MIT