Skip to content

Commit dfb680f

Browse files
author
SentienceDEV
committed
timeout param + passive captcha handling
1 parent 196b87e commit dfb680f

File tree

3 files changed

+103
-5
lines changed

3 files changed

+103
-5
lines changed

src/backends/snapshot.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ export interface SnapshotOptions {
104104
gridId?: number | null;
105105
/** Use server-side API (Pro/Enterprise tier) */
106106
useApi?: boolean;
107+
/** Gateway snapshot timeout (milliseconds) */
108+
gatewayTimeoutMs?: number;
107109
/** API key for server-side processing */
108110
sentienceApiKey?: string;
109111
/** Goal/task description for ordinal support and gateway reranking */

src/snapshot.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface SnapshotOptions {
5858
min_z_index?: number;
5959
};
6060
use_api?: boolean; // Force use of server-side API if True, local extension if False
61+
gatewayTimeoutMs?: number; // Gateway snapshot timeout in milliseconds
6162
save_trace?: boolean; // Save raw_elements to JSON for benchmarking/training
6263
trace_path?: string; // Path to save trace file (default: "trace_{timestamp}.json")
6364
goal?: string; // Optional goal/task description for the snapshot
@@ -317,11 +318,20 @@ async function snapshotViaApi(
317318
};
318319

319320
try {
320-
const response = await fetch(gatewayUrl, {
321-
method: 'POST',
322-
headers,
323-
body: payloadJson,
324-
});
321+
const timeoutMs = options.gatewayTimeoutMs ?? 30000;
322+
const controller = new AbortController();
323+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
324+
let response: Response;
325+
try {
326+
response = await fetch(gatewayUrl, {
327+
method: 'POST',
328+
headers,
329+
body: payloadJson,
330+
signal: controller.signal,
331+
});
332+
} finally {
333+
clearTimeout(timeoutId);
334+
}
325335

326336
if (!response.ok) {
327337
let errorText: string | undefined = undefined;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { snapshot } from '../src';
2+
import { BrowserEvaluator } from '../src/utils/browser-evaluator';
3+
4+
jest.mock('../src/utils/browser-evaluator', () => ({
5+
BrowserEvaluator: {
6+
waitForCondition: jest.fn().mockResolvedValue(true),
7+
evaluate: jest.fn(),
8+
},
9+
}));
10+
11+
const mockedBrowserEvaluator = BrowserEvaluator as jest.Mocked<typeof BrowserEvaluator>;
12+
13+
function makeBrowser() {
14+
return {
15+
getApiKey: () => 'sk_test',
16+
getApiUrl: () => 'https://api.sentienceapi.com',
17+
getPage: () => ({}),
18+
} as any;
19+
}
20+
21+
describe('Snapshot gateway timeout', () => {
22+
const rawResult = {
23+
raw_elements: [],
24+
url: 'https://example.com',
25+
viewport: { width: 800, height: 600 },
26+
diagnostics: {},
27+
};
28+
29+
beforeEach(() => {
30+
mockedBrowserEvaluator.evaluate.mockResolvedValue(rawResult as any);
31+
});
32+
33+
afterEach(() => {
34+
jest.restoreAllMocks();
35+
});
36+
37+
it('uses default gateway timeout when not provided', async () => {
38+
const fetchMock = jest.fn().mockResolvedValue({
39+
ok: true,
40+
json: async () => ({ status: 'success', elements: [], url: 'https://example.com' }),
41+
headers: new Headers(),
42+
});
43+
(global as any).fetch = fetchMock;
44+
45+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
46+
fn: (...args: any[]) => void,
47+
_ms?: number
48+
) => {
49+
return 123 as any;
50+
}) as any);
51+
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout').mockImplementation(() => {});
52+
53+
await snapshot(makeBrowser(), { screenshot: false, limit: 10 });
54+
55+
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 30000);
56+
expect(clearTimeoutSpy).toHaveBeenCalledWith(123);
57+
expect(fetchMock).toHaveBeenCalledWith(
58+
'https://api.sentienceapi.com/v1/snapshot',
59+
expect.objectContaining({
60+
signal: expect.any(AbortSignal),
61+
})
62+
);
63+
});
64+
65+
it('uses custom gateway timeout when provided', async () => {
66+
const fetchMock = jest.fn().mockResolvedValue({
67+
ok: true,
68+
json: async () => ({ status: 'success', elements: [], url: 'https://example.com' }),
69+
headers: new Headers(),
70+
});
71+
(global as any).fetch = fetchMock;
72+
73+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
74+
fn: (...args: any[]) => void,
75+
_ms?: number
76+
) => {
77+
return 456 as any;
78+
}) as any);
79+
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout').mockImplementation(() => {});
80+
81+
await snapshot(makeBrowser(), { screenshot: false, limit: 10, gatewayTimeoutMs: 12345 });
82+
83+
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 12345);
84+
expect(clearTimeoutSpy).toHaveBeenCalledWith(456);
85+
});
86+
});

0 commit comments

Comments
 (0)