402 lines
12 KiB
Markdown
402 lines
12 KiB
Markdown
|
|
# 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
|
||
|
|
```
|