# Network Error Monitor

## Principle

Automatically detect and fail tests when HTTP 4xx/5xx errors occur during execution. Act like Sentry for tests - catch silent backend failures even when UI passes assertions.

## Rationale

Traditional Playwright tests focus on UI:

- Backend 500 errors ignored if UI looks correct
- Silent failures slip through
- No visibility into background API health
- Tests pass while features are broken

The `network-error-monitor` provides:

- **Automatic detection**: All HTTP 4xx/5xx responses tracked
- **Test failures**: Fail tests with backend errors (even if UI passes)
- **Structured artifacts**: JSON reports with error details
- **Smart opt-out**: Disable for validation tests expecting errors
- **Deduplication**: Group repeated errors by pattern
- **Domino effect prevention**: Limit test failures per error pattern
- **Respects test status**: Won't suppress actual test failures

## Quick Start

```typescript
import { test } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';

// That's it! Network monitoring is automatically enabled
test('my test', async ({ page }) => {
  await page.goto('/dashboard');
  // If any HTTP 4xx/5xx errors occur, the test will fail
});
```

## Pattern Examples

### Example 1: Basic Auto-Monitoring

**Context**: Automatically fail tests when backend errors occur.

**Implementation**:

```typescript
import { test } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';

// Monitoring automatically enabled
test('should load dashboard', async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page.locator('h1')).toContainText('Dashboard');

  // Passes if no HTTP errors
  // Fails if any 4xx/5xx errors detected with clear message:
  //    "Network errors detected: 2 request(s) failed"
  //    Failed requests:
  //      GET 500 https://api.example.com/users
  //      POST 503 https://api.example.com/metrics
});
```

**Key Points**:

- Zero setup - auto-enabled for all tests
- Fails on any 4xx/5xx response
- Structured error message with URLs and status codes
- JSON artifact attached to test report

### Example 2: Opt-Out for Validation Tests

**Context**: Some tests expect errors (validation, error handling, edge cases).

**Implementation**:

```typescript
import { test } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';

// Opt-out with annotation
test(
  'should show error on invalid input',
  { annotation: [{ type: 'skipNetworkMonitoring' }] },
  async ({ page }) => {
    await page.goto('/form');
    await page.click('#submit'); // Triggers 400 error

    // Monitoring disabled - test won't fail on 400
    await expect(page.getByText('Invalid input')).toBeVisible();
  }
);

// Or opt-out entire describe block
test.describe('error handling', { annotation: [{ type: 'skipNetworkMonitoring' }] }, () => {
  test('handles 404', async ({ page }) => {
    // All tests in this block skip monitoring
  });

  test('handles 500', async ({ page }) => {
    // Monitoring disabled
  });
});
```

**Key Points**:

- Use annotation `{ type: 'skipNetworkMonitoring' }`
- Can opt-out single test or entire describe block
- Monitoring still active for other tests
- Perfect for intentional error scenarios

### Example 3: Respects Test Status

**Context**: The monitor respects final test statuses to avoid suppressing important test outcomes.

**Behavior by test status:**

- **`failed`**: Network errors logged as additional context, not thrown
- **`timedOut`**: Network errors logged as additional context
- **`skipped`**: Network errors logged, skip status preserved
- **`interrupted`**: Network errors logged, interrupted status preserved
- **`passed`**: Network errors throw and fail the test

**Example with test.skip():**

```typescript
test('feature gated test', async ({ page }) => {
  const featureEnabled = await checkFeatureFlag();
  test.skip(!featureEnabled, 'Feature not enabled');
  // If skipped, network errors won't turn this into a failure
  await page.goto('/new-feature');
});
```

### Example 4: Excluding Legitimate Errors

**Context**: Some endpoints legitimately return 4xx/5xx responses.

**Implementation**:

```typescript
import { test as base } from '@playwright/test';
import { createNetworkErrorMonitorFixture } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';

export const test = base.extend(
  createNetworkErrorMonitorFixture({
    excludePatterns: [
      /email-cluster\/ml-app\/has-active-run/, // ML service returns 404 when no active run
      /idv\/session-templates\/list/, // IDV service returns 404 when not configured
      /sentry\.io\/api/, // External Sentry errors should not fail tests
    ],
  })
);
```

**For merged fixtures:**

```typescript
import { test as base, mergeTests } from '@playwright/test';
import { createNetworkErrorMonitorFixture } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';

const networkErrorMonitor = base.extend(
  createNetworkErrorMonitorFixture({
    excludePatterns: [/analytics\.google\.com/, /cdn\.example\.com/],
  })
);

export const test = mergeTests(authFixture, networkErrorMonitor);
```

### Example 5: Preventing Domino Effect

**Context**: One failing endpoint shouldn't fail all tests.

**Implementation**:

```typescript
import { test as base } from '@playwright/test';
import { createNetworkErrorMonitorFixture } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';

const networkErrorMonitor = base.extend(
  createNetworkErrorMonitorFixture({
    excludePatterns: [], // Required when using maxTestsPerError
    maxTestsPerError: 1, // Only first test fails per error pattern, rest just log
  })
);
```

**How it works:**

When `/api/v2/case-management/cases` returns 500:

- **First test** encountering this error: **FAILS** with clear error message
- **Subsequent tests** encountering same error: **PASSES** but logs warning

Error patterns are grouped by `method + status + base path`:

- `GET /api/v2/case-management/cases/123` -> Pattern: `GET:500:/api/v2/case-management`
- `GET /api/v2/case-management/quota` -> Pattern: `GET:500:/api/v2/case-management` (same group!)
- `POST /api/v2/case-management/cases` -> Pattern: `POST:500:/api/v2/case-management` (different group!)

**Why include HTTP method?** A GET 404 vs POST 404 might represent different issues:

- `GET 404 /api/users/123` -> User not found (expected in some tests)
- `POST 404 /api/users` -> Endpoint doesn't exist (critical error)

**Output for subsequent tests:**

```
Warning: Network errors detected but not failing test (maxTestsPerError limit reached):
  GET 500 https://api.example.com/api/v2/case-management/cases
```

**Recommended configuration:**

```typescript
createNetworkErrorMonitorFixture({
  excludePatterns: [...], // Required - known broken endpoints (can be empty [])
  maxTestsPerError: 1     // Stop domino effect (requires excludePatterns)
})
```

**Understanding worker-level state:**

Error pattern counts are stored in worker-level global state:

```typescript
// test-file-1.spec.ts (runs in Worker 1)
test('test A', () => {
  /* triggers GET:500:/api/v2/cases */
}); // FAILS

// test-file-2.spec.ts (runs later in Worker 1)
test('test B', () => {
  /* triggers GET:500:/api/v2/cases */
}); // PASSES (limit reached)

// test-file-3.spec.ts (runs in Worker 2 - different worker)
test('test C', () => {
  /* triggers GET:500:/api/v2/cases */
}); // FAILS (fresh worker)
```

### Example 6: Integration with Merged Fixtures

**Context**: Combine network-error-monitor with other utilities.

**Implementation**:

```typescript
// playwright/support/merged-fixtures.ts
import { mergeTests } from '@playwright/test';
import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures';
import { test as networkErrorMonitorFixture } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';

export const test = mergeTests(
  authFixture,
  networkErrorMonitorFixture
  // Add other fixtures
);

// In tests
import { test, expect } from '../support/merged-fixtures';

test('authenticated with monitoring', async ({ page, authToken }) => {
  // Both auth and network monitoring active
  await page.goto('/protected');

  // Fails if backend returns errors during auth flow
});
```

**Key Points**:

- Combine with `mergeTests`
- Works alongside all other utilities
- Monitoring active automatically
- No extra setup needed

### Example 7: Artifact Structure

**Context**: Debugging failed tests with network error artifacts.

When test fails due to network errors, artifact attached:

```json
[
  {
    "url": "https://api.example.com/users",
    "status": 500,
    "method": "GET",
    "timestamp": "2025-11-10T12:34:56.789Z"
  },
  {
    "url": "https://api.example.com/metrics",
    "status": 503,
    "method": "POST",
    "timestamp": "2025-11-10T12:34:57.123Z"
  }
]
```

## Implementation Details

### How It Works

1. **Fixture Extension**: Uses Playwright's `base.extend()` with `auto: true`
2. **Response Listener**: Attaches `page.on('response')` listener at test start
3. **Multi-Page Monitoring**: Automatically monitors popups and new tabs via `context.on('page')`
4. **Error Collection**: Captures 4xx/5xx responses, checking exclusion patterns
5. **Try/Finally**: Ensures error processing runs even if test fails early
6. **Status Check**: Only throws errors if test hasn't already reached final status
7. **Artifact**: Attaches JSON file to test report for debugging

### Performance

The monitor has minimal performance impact:

- Event listener overhead: ~0.1ms per response
- Memory: ~200 bytes per unique error
- No network delay (observes responses, doesn't intercept them)

## Comparison with Alternatives

| Approach                    | Network Error Monitor | Manual afterEach      |
| --------------------------- | --------------------- | --------------------- |
| **Setup Required**          | Zero (auto-enabled)   | Every test file       |
| **Catches Silent Failures** | Yes                   | Yes (if configured)   |
| **Structured Artifacts**    | JSON attached         | Custom impl           |
| **Test Failure Safety**     | Try/finally           | afterEach may not run |
| **Opt-Out Mechanism**       | Annotation            | Custom logic          |
| **Status Aware**            | Respects skip/failed  | No                    |

## When to Use

**Auto-enabled for:**

- All E2E tests
- Integration tests
- Any test hitting real APIs

**Opt-out for:**

- Validation tests (expecting 4xx)
- Error handling tests (expecting 5xx)
- Offline tests (network-recorder playback)

## Troubleshooting

### Test fails with network errors but I don't see them in my app

The errors might be happening during page load or in background polling. Check the `network-errors.json` artifact in your test report for full details including timestamps.

### False positives from external services

Configure exclusion patterns as shown in the "Excluding Legitimate Errors" section above.

### Network errors not being caught

Ensure you're importing the test from the correct fixture:

```typescript
// Correct
import { test } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';

// Wrong - this won't have network monitoring
import { test } from '@playwright/test';
```

## Related Fragments

- `overview.md` - Installation and fixtures
- `fixtures-composition.md` - Merging with other utilities
- `error-handling.md` - Traditional error handling patterns

## Anti-Patterns

**DON'T opt out of monitoring globally:**

```typescript
// Every test skips monitoring
test.use({ annotation: [{ type: 'skipNetworkMonitoring' }] });
```

**DO opt-out only for specific error tests:**

```typescript
test.describe('error scenarios', { annotation: [{ type: 'skipNetworkMonitoring' }] }, () => {
  // Only these tests skip monitoring
});
```

**DON'T ignore network error artifacts:**

```typescript
// Test fails, artifact shows 500 errors
// Developer: "Works on my machine" ¯\_(ツ)_/¯
```

**DO check artifacts for root cause:**

```typescript
// Read network-errors.json artifact
// Identify failing endpoint: GET /api/users -> 500
// Fix backend issue before merging
```
