Claude
Skills
Sign in
Back

error-injection-tester

Included with Lifetime
$97 forever

Test application resilience by injecting failures: offline mode, blocked resources, API error responses (500/403/timeout), JS exceptions, localStorage quota exceeded, and slow network (3G). Captures app response to each failure including error UI, console errors, and graceful degradation.

Design

What this skill does


# Error Injection Tester

Systematically inject failures into a running web application to evaluate its
resilience, error handling UI, and graceful degradation behavior. Each injection
is isolated, documented with screenshots, and console output is captured.

## When to Use

- Verifying error boundaries and fallback UI render correctly.
- Testing offline/network-failure behavior before shipping a PWA.
- Ensuring API error responses produce user-friendly messages.
- Checking that third-party resource failures do not break the page.
- Validating storage quota handling for localStorage-heavy apps.
- Simulating slow networks to test loading states and timeouts.

## Prerequisites

- **Playwright MCP server** connected and responding (all `mcp__playwright__browser_*` tools available).
- **Chromium-based browser** required for CDP network emulation and blocked URLs.
- Target page must be loaded before injections begin.

## Workflow

### Step 1 -- Navigate and Establish Baseline

```
browser_navigate({ url: "<target_url>" })
```

```
browser_take_screenshot({ type: "png", filename: "error-injection-baseline.png" })
```

```
browser_console_messages({ level: "error" })
```

### Step 2 -- Test Offline Mode

Simulate a complete network disconnection.

```javascript
browser_run_code({
  code: `async (page) => {
    const client = await page.context().newCDPSession(page);
    await client.send('Network.enable');
    await client.send('Network.emulateNetworkConditions', {
      offline: true,
      latency: 0,
      downloadThroughput: 0,
      uploadThroughput: 0
    });
    return 'Network set to offline';
  }`
})
```

Trigger an action that requires network (e.g., click a button, navigate):

```javascript
browser_evaluate({
  function: `() => {
    // Attempt a fetch to trigger offline behavior
    fetch(window.location.href).catch(err => {
      window.__offlineError = err.message;
    });
    return 'Fetch attempted while offline';
  }`
})
```

```
browser_wait_for({ time: 3 })
```

```
browser_take_screenshot({ type: "png", filename: "error-injection-offline.png" })
```

```
browser_console_messages({ level: "error" })
```

Restore network:

```javascript
browser_run_code({
  code: `async (page) => {
    const client = await page.context().newCDPSession(page);
    await client.send('Network.emulateNetworkConditions', {
      offline: false,
      latency: 0,
      downloadThroughput: -1,
      uploadThroughput: -1
    });
    return 'Network restored';
  }`
})
```

### Step 3 -- Test Blocked Resources (CSS/JS/Images)

Block specific resource types and reload to see degraded rendering.

```javascript
browser_run_code({
  code: `async (page) => {
    const client = await page.context().newCDPSession(page);
    await client.send('Network.enable');
    await client.send('Network.setBlockedURLs', {
      urls: ['*.css', '*.js']
    });
    await page.reload({ waitUntil: 'domcontentloaded' });
    return 'CSS and JS blocked, page reloaded';
  }`
})
```

```
browser_take_screenshot({ type: "png", filename: "error-injection-no-css-js.png" })
```

```
browser_console_messages({ level: "error" })
```

Unblock and test image blocking:

```javascript
browser_run_code({
  code: `async (page) => {
    const client = await page.context().newCDPSession(page);
    await client.send('Network.setBlockedURLs', {
      urls: ['*.png', '*.jpg', '*.jpeg', '*.webp', '*.gif', '*.svg', '*.avif']
    });
    await page.reload({ waitUntil: 'domcontentloaded' });
    return 'Images blocked, page reloaded';
  }`
})
```

```
browser_take_screenshot({ type: "png", filename: "error-injection-no-images.png" })
```

Clear blocked URLs:

```javascript
browser_run_code({
  code: `async (page) => {
    const client = await page.context().newCDPSession(page);
    await client.send('Network.setBlockedURLs', { urls: [] });
    await page.reload({ waitUntil: 'load' });
    return 'Blocks cleared, page reloaded';
  }`
})
```

### Step 4 -- Inject API Error Responses (500, 403, Timeout)

Intercept API calls and return error responses.

**500 Internal Server Error:**

```javascript
browser_run_code({
  code: `async (page) => {
    await page.route('**/api/**', route => {
      route.fulfill({
        status: 500,
        contentType: 'application/json',
        body: JSON.stringify({ error: 'Internal Server Error', message: 'Injected 500 by error-injection-tester' })
      });
    });
    return 'API routes intercepted with 500 responses';
  }`
})
```

Trigger an API call (click a button or interact with the page using
`browser_snapshot` + `browser_click`), then capture the result:

```
browser_wait_for({ time: 3 })
```

```
browser_take_screenshot({ type: "png", filename: "error-injection-api-500.png" })
```

```
browser_console_messages({ level: "error" })
```

**403 Forbidden:**

```javascript
browser_run_code({
  code: `async (page) => {
    await page.unrouteAll();
    await page.route('**/api/**', route => {
      route.fulfill({
        status: 403,
        contentType: 'application/json',
        body: JSON.stringify({ error: 'Forbidden', message: 'Injected 403 by error-injection-tester' })
      });
    });
    return 'API routes intercepted with 403 responses';
  }`
})
```

```
browser_wait_for({ time: 3 })
```

```
browser_take_screenshot({ type: "png", filename: "error-injection-api-403.png" })
```

**Timeout (30-second hang):**

```javascript
browser_run_code({
  code: `async (page) => {
    await page.unrouteAll();
    await page.route('**/api/**', route => {
      // Never respond -- simulates a timeout
      // The route will hang until navigation or unroute
    });
    return 'API routes intercepted with infinite hang (timeout simulation)';
  }`
})
```

```
browser_wait_for({ time: 10 })
```

```
browser_take_screenshot({ type: "png", filename: "error-injection-api-timeout.png" })
```

Clean up routes:

```javascript
browser_run_code({
  code: `async (page) => {
    await page.unrouteAll();
    return 'All route intercepts removed';
  }`
})
```

### Step 5 -- Inject JavaScript Exceptions

Test that error boundaries catch unhandled errors.

```javascript
browser_evaluate({
  function: `() => {
    // Inject unhandled error
    window.__errorsCaught = [];
    const origHandler = window.onerror;
    window.onerror = (msg, src, line, col, err) => {
      window.__errorsCaught.push({ msg, src, line, col });
      if (origHandler) origHandler(msg, src, line, col, err);
    };

    // Throw in a timeout to simulate async error
    setTimeout(() => {
      throw new Error('Injected error: Component render failure simulation');
    }, 100);

    // Throw in a promise to test unhandled rejection
    Promise.reject(new Error('Injected unhandled promise rejection'));

    return 'JS exceptions injected';
  }`
})
```

```
browser_wait_for({ time: 2 })
```

```
browser_take_screenshot({ type: "png", filename: "error-injection-js-error.png" })
```

```
browser_console_messages({ level: "error" })
```

```javascript
browser_evaluate({
  function: `() => {
    return {
      errorsCaught: window.__errorsCaught || [],
      errorBoundaryVisible: !!document.querySelector('[class*="error"], [class*="fallback"], [role="alert"]')
    };
  }`
})
```

### Step 6 -- Inject localStorage Quota Exceeded

Simulate storage quota being exceeded.

```javascript
browser_evaluate({
  function: `() => {
    const results = { filled: false, error: null };
    try {
      // Fill localStorage with large data
      const chunk = 'x'.repeat(1024 * 1024); // 1MB chunks
      for (let i = 0; i < 20; i++) {
        try {
          localStorage.setItem('__quota_test_' + i, chunk);
        } catch (e) {
          results.filled = true;
          results.error = e.message;
          results.itemsBeforeQuota = i;
          break;
        }
      }

      // If we didn't hit quota naturally, override setItem
      if (!results.filled) {
        const origSetItem = localStorage.setItem.bind(localStorage);
        localStorage.set

Related in Design