Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
- Add isRetryableError() helper (retry on TimeoutError, Protocol error, Target closed, Session closed, Navigation failed, net::ERR_*) - Wrap browser screenshot in retry loop (max 2 retries, exponential backoff) - Add retryCount to ScreenshotResult, X-Retry-Count response header - Validation/SSRF/auth errors are NOT retried - 28 new tests (12 retry classification + 6 screenshot retry + route tests)
56 lines
2.1 KiB
TypeScript
56 lines
2.1 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { isRetryableError } from '../retry.js'
|
|
|
|
describe('isRetryableError', () => {
|
|
it('returns true for TimeoutError', () => {
|
|
const err = new Error('TimeoutError: Navigation timeout of 20000ms exceeded')
|
|
err.name = 'TimeoutError'
|
|
expect(isRetryableError(err)).toBe(true)
|
|
})
|
|
|
|
it('returns true for Protocol error', () => {
|
|
expect(isRetryableError(new Error('Protocol error (Runtime.callFunctionOn): Session closed.'))).toBe(true)
|
|
})
|
|
|
|
it('returns true for Target closed', () => {
|
|
expect(isRetryableError(new Error('Target closed'))).toBe(true)
|
|
})
|
|
|
|
it('returns true for Session closed', () => {
|
|
expect(isRetryableError(new Error('Session closed. Most likely the page has been closed.'))).toBe(true)
|
|
})
|
|
|
|
it('returns true for Navigation failed', () => {
|
|
expect(isRetryableError(new Error('Navigation failed because browser has disconnected!'))).toBe(true)
|
|
})
|
|
|
|
it('returns true for net::ERR_ errors', () => {
|
|
expect(isRetryableError(new Error('net::ERR_CONNECTION_RESET'))).toBe(true)
|
|
expect(isRetryableError(new Error('net::ERR_CONNECTION_REFUSED'))).toBe(true)
|
|
})
|
|
|
|
it('returns false for SCREENSHOT_TIMEOUT (overall timeout, not transient)', () => {
|
|
expect(isRetryableError(new Error('SCREENSHOT_TIMEOUT'))).toBe(false)
|
|
})
|
|
|
|
it('returns false for SSRF_BLOCKED', () => {
|
|
expect(isRetryableError(new Error('SSRF_BLOCKED'))).toBe(false)
|
|
})
|
|
|
|
it('returns false for validation errors', () => {
|
|
expect(isRetryableError(new Error('hideSelector contains dangerous characters'))).toBe(false)
|
|
expect(isRetryableError(new Error('selector and fullPage are mutually exclusive'))).toBe(false)
|
|
})
|
|
|
|
it('returns false for SELECTOR_NOT_FOUND', () => {
|
|
expect(isRetryableError(new Error('SELECTOR_NOT_FOUND'))).toBe(false)
|
|
})
|
|
|
|
it('returns false for JS_EXECUTION_ERROR', () => {
|
|
expect(isRetryableError(new Error('JS_EXECUTION_ERROR: foo is not defined'))).toBe(false)
|
|
})
|
|
|
|
it('returns false for generic errors', () => {
|
|
expect(isRetryableError(new Error('Something went wrong'))).toBe(false)
|
|
})
|
|
})
|