/ src / browser / errors.test.ts
errors.test.ts
 1  import { describe, expect, it } from 'vitest';
 2  
 3  import { classifyBrowserError, isTransientBrowserError } from './errors.js';
 4  
 5  describe('classifyBrowserError', () => {
 6    it('classifies extension transient errors with 1500ms delay', () => {
 7      for (const msg of [
 8        'Extension disconnected',
 9        'Extension not connected',
10        'attach failed',
11        'no longer exists',
12        'CDP connection reset',
13        'Daemon command failed',
14        'No window with id: 123',
15      ]) {
16        const advice = classifyBrowserError(new Error(msg));
17        expect(advice.kind, `expected "${msg}" → extension-transient`).toBe('extension-transient');
18        expect(advice.retryable).toBe(true);
19        expect(advice.delayMs).toBe(1500);
20      }
21    });
22  
23    it('classifies CDP target navigation errors with 200ms delay', () => {
24      const advice = classifyBrowserError(new Error('Inspected target navigated or closed'));
25      expect(advice.kind).toBe('target-navigation');
26      expect(advice.retryable).toBe(true);
27      expect(advice.delayMs).toBe(200);
28    });
29  
30    it('classifies CDP -32000 target errors with 200ms delay', () => {
31      const advice = classifyBrowserError(new Error('{"code":-32000,"message":"Target closed"}'));
32      expect(advice.kind).toBe('target-navigation');
33      expect(advice.retryable).toBe(true);
34      expect(advice.delayMs).toBe(200);
35    });
36  
37    it('returns non-retryable for unrelated errors', () => {
38      for (const msg of ['Permission denied', 'malformed exec payload', 'SyntaxError']) {
39        const advice = classifyBrowserError(new Error(msg));
40        expect(advice.kind).toBe('non-retryable');
41        expect(advice.retryable).toBe(false);
42      }
43    });
44  
45    it('handles non-Error values', () => {
46      expect(classifyBrowserError('Extension disconnected').kind).toBe('extension-transient');
47      expect(classifyBrowserError(42).kind).toBe('non-retryable');
48    });
49  });
50  
51  describe('isTransientBrowserError (convenience wrapper)', () => {
52    it('returns true for transient errors', () => {
53      expect(isTransientBrowserError(new Error('No window with id: 123'))).toBe(true);
54    });
55  
56    it('returns false for non-transient errors', () => {
57      expect(isTransientBrowserError(new Error('Permission denied'))).toBe(false);
58    });
59  });