@desplega.ai/qa-use 2.1.7 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/api/browser-types.d.ts +175 -0
- package/dist/lib/api/browser-types.d.ts.map +1 -0
- package/dist/lib/api/browser-types.js +5 -0
- package/dist/lib/api/browser-types.js.map +1 -0
- package/dist/lib/api/browser.d.ts +66 -0
- package/dist/lib/api/browser.d.ts.map +1 -0
- package/dist/lib/api/browser.js +223 -0
- package/dist/lib/api/browser.js.map +1 -0
- package/dist/package.json +2 -1
- package/dist/src/cli/commands/browser/back.d.ts +6 -0
- package/dist/src/cli/commands/browser/back.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/back.js +42 -0
- package/dist/src/cli/commands/browser/back.js.map +1 -0
- package/dist/src/cli/commands/browser/check.d.ts +6 -0
- package/dist/src/cli/commands/browser/check.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/check.js +62 -0
- package/dist/src/cli/commands/browser/check.js.map +1 -0
- package/dist/src/cli/commands/browser/click.d.ts +6 -0
- package/dist/src/cli/commands/browser/click.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/click.js +63 -0
- package/dist/src/cli/commands/browser/click.js.map +1 -0
- package/dist/src/cli/commands/browser/close.d.ts +6 -0
- package/dist/src/cli/commands/browser/close.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/close.js +44 -0
- package/dist/src/cli/commands/browser/close.js.map +1 -0
- package/dist/src/cli/commands/browser/create.d.ts +6 -0
- package/dist/src/cli/commands/browser/create.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/create.js +281 -0
- package/dist/src/cli/commands/browser/create.js.map +1 -0
- package/dist/src/cli/commands/browser/fill.d.ts +6 -0
- package/dist/src/cli/commands/browser/fill.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/fill.js +83 -0
- package/dist/src/cli/commands/browser/fill.js.map +1 -0
- package/dist/src/cli/commands/browser/forward.d.ts +6 -0
- package/dist/src/cli/commands/browser/forward.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/forward.js +42 -0
- package/dist/src/cli/commands/browser/forward.js.map +1 -0
- package/dist/src/cli/commands/browser/get-blocks.d.ts +6 -0
- package/dist/src/cli/commands/browser/get-blocks.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/get-blocks.js +35 -0
- package/dist/src/cli/commands/browser/get-blocks.js.map +1 -0
- package/dist/src/cli/commands/browser/goto.d.ts +6 -0
- package/dist/src/cli/commands/browser/goto.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/goto.js +53 -0
- package/dist/src/cli/commands/browser/goto.js.map +1 -0
- package/dist/src/cli/commands/browser/hover.d.ts +6 -0
- package/dist/src/cli/commands/browser/hover.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/hover.js +63 -0
- package/dist/src/cli/commands/browser/hover.js.map +1 -0
- package/dist/src/cli/commands/browser/index.d.ts +9 -0
- package/dist/src/cli/commands/browser/index.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/index.js +71 -0
- package/dist/src/cli/commands/browser/index.js.map +1 -0
- package/dist/src/cli/commands/browser/list.d.ts +6 -0
- package/dist/src/cli/commands/browser/list.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/list.js +85 -0
- package/dist/src/cli/commands/browser/list.js.map +1 -0
- package/dist/src/cli/commands/browser/press.d.ts +6 -0
- package/dist/src/cli/commands/browser/press.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/press.js +67 -0
- package/dist/src/cli/commands/browser/press.js.map +1 -0
- package/dist/src/cli/commands/browser/reload.d.ts +6 -0
- package/dist/src/cli/commands/browser/reload.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/reload.js +42 -0
- package/dist/src/cli/commands/browser/reload.js.map +1 -0
- package/dist/src/cli/commands/browser/run.d.ts +6 -0
- package/dist/src/cli/commands/browser/run.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/run.js +618 -0
- package/dist/src/cli/commands/browser/run.js.map +1 -0
- package/dist/src/cli/commands/browser/screenshot.d.ts +6 -0
- package/dist/src/cli/commands/browser/screenshot.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/screenshot.js +72 -0
- package/dist/src/cli/commands/browser/screenshot.js.map +1 -0
- package/dist/src/cli/commands/browser/scroll-into-view.d.ts +6 -0
- package/dist/src/cli/commands/browser/scroll-into-view.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/scroll-into-view.js +64 -0
- package/dist/src/cli/commands/browser/scroll-into-view.js.map +1 -0
- package/dist/src/cli/commands/browser/scroll.d.ts +6 -0
- package/dist/src/cli/commands/browser/scroll.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/scroll.js +63 -0
- package/dist/src/cli/commands/browser/scroll.js.map +1 -0
- package/dist/src/cli/commands/browser/select.d.ts +6 -0
- package/dist/src/cli/commands/browser/select.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/select.js +83 -0
- package/dist/src/cli/commands/browser/select.js.map +1 -0
- package/dist/src/cli/commands/browser/snapshot.d.ts +6 -0
- package/dist/src/cli/commands/browser/snapshot.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/snapshot.js +72 -0
- package/dist/src/cli/commands/browser/snapshot.js.map +1 -0
- package/dist/src/cli/commands/browser/status.d.ts +6 -0
- package/dist/src/cli/commands/browser/status.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/status.js +91 -0
- package/dist/src/cli/commands/browser/status.js.map +1 -0
- package/dist/src/cli/commands/browser/stream.d.ts +6 -0
- package/dist/src/cli/commands/browser/stream.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/stream.js +135 -0
- package/dist/src/cli/commands/browser/stream.js.map +1 -0
- package/dist/src/cli/commands/browser/tunnel.d.ts +13 -0
- package/dist/src/cli/commands/browser/tunnel.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/tunnel.js +225 -0
- package/dist/src/cli/commands/browser/tunnel.js.map +1 -0
- package/dist/src/cli/commands/browser/type.d.ts +6 -0
- package/dist/src/cli/commands/browser/type.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/type.js +61 -0
- package/dist/src/cli/commands/browser/type.js.map +1 -0
- package/dist/src/cli/commands/browser/uncheck.d.ts +6 -0
- package/dist/src/cli/commands/browser/uncheck.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/uncheck.js +62 -0
- package/dist/src/cli/commands/browser/uncheck.js.map +1 -0
- package/dist/src/cli/commands/browser/url.d.ts +6 -0
- package/dist/src/cli/commands/browser/url.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/url.js +40 -0
- package/dist/src/cli/commands/browser/url.js.map +1 -0
- package/dist/src/cli/commands/browser/wait-for-load.d.ts +6 -0
- package/dist/src/cli/commands/browser/wait-for-load.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/wait-for-load.js +50 -0
- package/dist/src/cli/commands/browser/wait-for-load.js.map +1 -0
- package/dist/src/cli/commands/browser/wait-for-selector.d.ts +6 -0
- package/dist/src/cli/commands/browser/wait-for-selector.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/wait-for-selector.js +52 -0
- package/dist/src/cli/commands/browser/wait-for-selector.js.map +1 -0
- package/dist/src/cli/commands/browser/wait.d.ts +6 -0
- package/dist/src/cli/commands/browser/wait.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/wait.js +60 -0
- package/dist/src/cli/commands/browser/wait.js.map +1 -0
- package/dist/src/cli/commands/test/run.d.ts.map +1 -1
- package/dist/src/cli/commands/test/run.js +38 -7
- package/dist/src/cli/commands/test/run.js.map +1 -1
- package/dist/src/cli/index.js +2 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/lib/browser-sessions.d.ts +72 -0
- package/dist/src/cli/lib/browser-sessions.d.ts.map +1 -0
- package/dist/src/cli/lib/browser-sessions.js +184 -0
- package/dist/src/cli/lib/browser-sessions.js.map +1 -0
- package/lib/api/browser-types.ts +278 -0
- package/lib/api/browser.test.ts +378 -0
- package/lib/api/browser.ts +279 -0
- package/package.json +2 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Browser API (/browsers/v1/)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ==========================================
|
|
6
|
+
// Session Types
|
|
7
|
+
// ==========================================
|
|
8
|
+
|
|
9
|
+
export type BrowserSessionStatus = 'starting' | 'active' | 'closing' | 'closed';
|
|
10
|
+
|
|
11
|
+
export type ViewportType = 'desktop' | 'mobile' | 'tablet';
|
|
12
|
+
|
|
13
|
+
export interface CreateBrowserSessionOptions {
|
|
14
|
+
headless?: boolean;
|
|
15
|
+
viewport?: ViewportType;
|
|
16
|
+
timeout?: number; // Session timeout in seconds (60-3600)
|
|
17
|
+
ws_url?: string; // WebSocket URL for remote/tunneled browser
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface BrowserSession {
|
|
21
|
+
id: string;
|
|
22
|
+
status: BrowserSessionStatus;
|
|
23
|
+
created_at: string;
|
|
24
|
+
updated_at?: string;
|
|
25
|
+
current_url?: string;
|
|
26
|
+
viewport?: ViewportType;
|
|
27
|
+
headless?: boolean;
|
|
28
|
+
timeout?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ==========================================
|
|
32
|
+
// Action Types
|
|
33
|
+
// ==========================================
|
|
34
|
+
|
|
35
|
+
export type BrowserActionType =
|
|
36
|
+
| 'goto'
|
|
37
|
+
| 'back'
|
|
38
|
+
| 'forward'
|
|
39
|
+
| 'reload'
|
|
40
|
+
| 'click'
|
|
41
|
+
| 'fill'
|
|
42
|
+
| 'type'
|
|
43
|
+
| 'press'
|
|
44
|
+
| 'hover'
|
|
45
|
+
| 'scroll'
|
|
46
|
+
| 'scroll_into_view'
|
|
47
|
+
| 'select'
|
|
48
|
+
| 'check'
|
|
49
|
+
| 'uncheck'
|
|
50
|
+
| 'wait'
|
|
51
|
+
| 'wait_for_selector'
|
|
52
|
+
| 'wait_for_load'
|
|
53
|
+
| 'snapshot'
|
|
54
|
+
| 'screenshot';
|
|
55
|
+
|
|
56
|
+
export type ScrollDirection = 'up' | 'down' | 'left' | 'right';
|
|
57
|
+
|
|
58
|
+
export interface GotoAction {
|
|
59
|
+
type: 'goto';
|
|
60
|
+
url: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface BackAction {
|
|
64
|
+
type: 'back';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ForwardAction {
|
|
68
|
+
type: 'forward';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ReloadAction {
|
|
72
|
+
type: 'reload';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ClickAction {
|
|
76
|
+
type: 'click';
|
|
77
|
+
ref?: string;
|
|
78
|
+
text?: string; // AI-based semantic element selection (alternative to ref)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface FillAction {
|
|
82
|
+
type: 'fill';
|
|
83
|
+
ref?: string;
|
|
84
|
+
text?: string; // AI-based semantic element selection (alternative to ref)
|
|
85
|
+
value: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface TypeAction {
|
|
89
|
+
type: 'type';
|
|
90
|
+
ref: string;
|
|
91
|
+
text: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface PressAction {
|
|
95
|
+
type: 'press';
|
|
96
|
+
key: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface HoverAction {
|
|
100
|
+
type: 'hover';
|
|
101
|
+
ref?: string;
|
|
102
|
+
text?: string; // AI-based semantic element selection (alternative to ref)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ScrollAction {
|
|
106
|
+
type: 'scroll';
|
|
107
|
+
direction: ScrollDirection;
|
|
108
|
+
amount?: number; // pixels, default 500
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface SelectAction {
|
|
112
|
+
type: 'select';
|
|
113
|
+
ref?: string;
|
|
114
|
+
text?: string; // AI-based semantic element selection (alternative to ref)
|
|
115
|
+
value: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface CheckAction {
|
|
119
|
+
type: 'check';
|
|
120
|
+
ref?: string;
|
|
121
|
+
text?: string; // AI-based semantic element selection (alternative to ref)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface UncheckAction {
|
|
125
|
+
type: 'uncheck';
|
|
126
|
+
ref?: string;
|
|
127
|
+
text?: string; // AI-based semantic element selection (alternative to ref)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ScrollIntoViewAction {
|
|
131
|
+
type: 'scroll_into_view';
|
|
132
|
+
ref?: string;
|
|
133
|
+
text?: string; // AI-based semantic element selection (alternative to ref)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface WaitAction {
|
|
137
|
+
type: 'wait';
|
|
138
|
+
duration_ms: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface WaitForSelectorAction {
|
|
142
|
+
type: 'wait_for_selector';
|
|
143
|
+
selector: string;
|
|
144
|
+
state?: 'visible' | 'hidden' | 'attached' | 'detached';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface WaitForLoadAction {
|
|
148
|
+
type: 'wait_for_load';
|
|
149
|
+
state?: 'load' | 'domcontentloaded' | 'networkidle';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface SnapshotAction {
|
|
153
|
+
type: 'snapshot';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface ScreenshotAction {
|
|
157
|
+
type: 'screenshot';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type BrowserAction =
|
|
161
|
+
| GotoAction
|
|
162
|
+
| BackAction
|
|
163
|
+
| ForwardAction
|
|
164
|
+
| ReloadAction
|
|
165
|
+
| ClickAction
|
|
166
|
+
| FillAction
|
|
167
|
+
| TypeAction
|
|
168
|
+
| PressAction
|
|
169
|
+
| HoverAction
|
|
170
|
+
| ScrollAction
|
|
171
|
+
| ScrollIntoViewAction
|
|
172
|
+
| SelectAction
|
|
173
|
+
| CheckAction
|
|
174
|
+
| UncheckAction
|
|
175
|
+
| WaitAction
|
|
176
|
+
| WaitForSelectorAction
|
|
177
|
+
| WaitForLoadAction
|
|
178
|
+
| SnapshotAction
|
|
179
|
+
| ScreenshotAction;
|
|
180
|
+
|
|
181
|
+
// ==========================================
|
|
182
|
+
// API Response Types
|
|
183
|
+
// ==========================================
|
|
184
|
+
|
|
185
|
+
export interface ActionResult {
|
|
186
|
+
success: boolean;
|
|
187
|
+
error?: string;
|
|
188
|
+
data?: unknown;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface SnapshotResult {
|
|
192
|
+
snapshot: string;
|
|
193
|
+
url?: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface UrlResult {
|
|
197
|
+
url: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface BlocksResult {
|
|
201
|
+
blocks: unknown[]; // ExtendedStep[] - typed in BrowserApiClient
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ==========================================
|
|
205
|
+
// WebSocket Event Types
|
|
206
|
+
// ==========================================
|
|
207
|
+
|
|
208
|
+
export type WebSocketEventType =
|
|
209
|
+
| 'action_started'
|
|
210
|
+
| 'action_completed'
|
|
211
|
+
| 'status_changed'
|
|
212
|
+
| 'error'
|
|
213
|
+
| 'closed';
|
|
214
|
+
|
|
215
|
+
export interface WebSocketEvent {
|
|
216
|
+
type: WebSocketEventType;
|
|
217
|
+
data?: unknown;
|
|
218
|
+
timestamp?: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface ActionStartedEvent extends WebSocketEvent {
|
|
222
|
+
type: 'action_started';
|
|
223
|
+
data: {
|
|
224
|
+
action_type: BrowserActionType;
|
|
225
|
+
action_id?: string;
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface ActionCompletedEvent extends WebSocketEvent {
|
|
230
|
+
type: 'action_completed';
|
|
231
|
+
data: {
|
|
232
|
+
action_id?: string;
|
|
233
|
+
success: boolean;
|
|
234
|
+
error?: string;
|
|
235
|
+
result?: unknown;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface StatusChangedEvent extends WebSocketEvent {
|
|
240
|
+
type: 'status_changed';
|
|
241
|
+
data: {
|
|
242
|
+
status: BrowserSessionStatus;
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface ErrorEvent extends WebSocketEvent {
|
|
247
|
+
type: 'error';
|
|
248
|
+
data: {
|
|
249
|
+
message: string;
|
|
250
|
+
code?: string;
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface ClosedEvent extends WebSocketEvent {
|
|
255
|
+
type: 'closed';
|
|
256
|
+
data?: {
|
|
257
|
+
reason?: string;
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ==========================================
|
|
262
|
+
// Client Messages (WebSocket)
|
|
263
|
+
// ==========================================
|
|
264
|
+
|
|
265
|
+
export interface PingMessage {
|
|
266
|
+
type: 'ping';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface ActionMessage {
|
|
270
|
+
type: 'action';
|
|
271
|
+
data: BrowserAction;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface GetStatusMessage {
|
|
275
|
+
type: 'get_status';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export type ClientMessage = PingMessage | ActionMessage | GetStatusMessage;
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrowserApiClient Unit Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, mock, spyOn } from 'bun:test';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import { BrowserApiClient } from './browser.js';
|
|
8
|
+
|
|
9
|
+
// Mock axios
|
|
10
|
+
const mockAxiosInstance = {
|
|
11
|
+
get: mock(() => Promise.resolve({ data: {} })),
|
|
12
|
+
post: mock(() => Promise.resolve({ data: {} })),
|
|
13
|
+
delete: mock(() => Promise.resolve({ data: {} })),
|
|
14
|
+
defaults: {
|
|
15
|
+
baseURL: 'https://api.desplega.ai/browsers/v1',
|
|
16
|
+
headers: {},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
mock.module('axios', () => ({
|
|
21
|
+
default: {
|
|
22
|
+
create: () => mockAxiosInstance,
|
|
23
|
+
isAxiosError: (err: unknown) =>
|
|
24
|
+
typeof err === 'object' && err !== null && 'response' in err && 'isAxiosError' in err,
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
describe('BrowserApiClient', () => {
|
|
29
|
+
let client: BrowserApiClient;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
// Reset mocks
|
|
33
|
+
mockAxiosInstance.get.mockReset();
|
|
34
|
+
mockAxiosInstance.post.mockReset();
|
|
35
|
+
mockAxiosInstance.delete.mockReset();
|
|
36
|
+
|
|
37
|
+
// Clear environment variables
|
|
38
|
+
delete process.env.QA_USE_API_KEY;
|
|
39
|
+
delete process.env.QA_USE_API_URL;
|
|
40
|
+
|
|
41
|
+
client = new BrowserApiClient();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('constructor', () => {
|
|
45
|
+
it('should use default API URL', () => {
|
|
46
|
+
const c = new BrowserApiClient();
|
|
47
|
+
expect(c.getBaseUrl()).toContain('api.desplega.ai');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should use custom API URL', () => {
|
|
51
|
+
const c = new BrowserApiClient('https://custom.api.com');
|
|
52
|
+
// The URL should be set via axios.create
|
|
53
|
+
expect(mockAxiosInstance.defaults.baseURL).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('setApiKey', () => {
|
|
58
|
+
it('should set API key', () => {
|
|
59
|
+
client.setApiKey('test-key-123');
|
|
60
|
+
expect(client.getApiKey()).toBe('test-key-123');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('createSession', () => {
|
|
65
|
+
it('should create session with default options', async () => {
|
|
66
|
+
const mockSession = {
|
|
67
|
+
id: 'session-123',
|
|
68
|
+
status: 'starting',
|
|
69
|
+
created_at: '2026-01-23T10:00:00Z',
|
|
70
|
+
};
|
|
71
|
+
mockAxiosInstance.post.mockResolvedValueOnce({ data: mockSession });
|
|
72
|
+
|
|
73
|
+
const session = await client.createSession();
|
|
74
|
+
|
|
75
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions', {
|
|
76
|
+
headless: true,
|
|
77
|
+
viewport: 'desktop',
|
|
78
|
+
timeout: 300,
|
|
79
|
+
});
|
|
80
|
+
expect(session.id).toBe('session-123');
|
|
81
|
+
expect(session.status).toBe('starting');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should create session with custom options', async () => {
|
|
85
|
+
const mockSession = {
|
|
86
|
+
id: 'session-456',
|
|
87
|
+
status: 'starting',
|
|
88
|
+
created_at: '2026-01-23T10:00:00Z',
|
|
89
|
+
};
|
|
90
|
+
mockAxiosInstance.post.mockResolvedValueOnce({ data: mockSession });
|
|
91
|
+
|
|
92
|
+
const session = await client.createSession({
|
|
93
|
+
headless: false,
|
|
94
|
+
viewport: 'mobile',
|
|
95
|
+
timeout: 600,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions', {
|
|
99
|
+
headless: false,
|
|
100
|
+
viewport: 'mobile',
|
|
101
|
+
timeout: 600,
|
|
102
|
+
});
|
|
103
|
+
expect(session.id).toBe('session-456');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should create session with ws_url for remote browser', async () => {
|
|
107
|
+
const mockSession = {
|
|
108
|
+
id: 'session-789',
|
|
109
|
+
status: 'starting',
|
|
110
|
+
created_at: '2026-01-23T10:00:00Z',
|
|
111
|
+
};
|
|
112
|
+
mockAxiosInstance.post.mockResolvedValueOnce({ data: mockSession });
|
|
113
|
+
|
|
114
|
+
const session = await client.createSession({
|
|
115
|
+
headless: true,
|
|
116
|
+
viewport: 'desktop',
|
|
117
|
+
timeout: 300,
|
|
118
|
+
ws_url: 'wss://tunnel.example.com/devtools/browser/abc123',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions', {
|
|
122
|
+
headless: true,
|
|
123
|
+
viewport: 'desktop',
|
|
124
|
+
timeout: 300,
|
|
125
|
+
ws_url: 'wss://tunnel.example.com/devtools/browser/abc123',
|
|
126
|
+
});
|
|
127
|
+
expect(session.id).toBe('session-789');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should not include ws_url when not provided', async () => {
|
|
131
|
+
const mockSession = {
|
|
132
|
+
id: 'session-abc',
|
|
133
|
+
status: 'starting',
|
|
134
|
+
created_at: '2026-01-23T10:00:00Z',
|
|
135
|
+
};
|
|
136
|
+
mockAxiosInstance.post.mockResolvedValueOnce({ data: mockSession });
|
|
137
|
+
|
|
138
|
+
await client.createSession({
|
|
139
|
+
headless: true,
|
|
140
|
+
viewport: 'desktop',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const callArgs = mockAxiosInstance.post.mock.calls[0];
|
|
144
|
+
expect(callArgs[1]).not.toHaveProperty('ws_url');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('listSessions', () => {
|
|
149
|
+
it('should list sessions from array response', async () => {
|
|
150
|
+
const mockSessions = [
|
|
151
|
+
{ id: 'session-1', status: 'active' },
|
|
152
|
+
{ id: 'session-2', status: 'starting' },
|
|
153
|
+
];
|
|
154
|
+
mockAxiosInstance.get.mockResolvedValueOnce({ data: mockSessions });
|
|
155
|
+
|
|
156
|
+
const sessions = await client.listSessions();
|
|
157
|
+
|
|
158
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/sessions');
|
|
159
|
+
expect(sessions).toHaveLength(2);
|
|
160
|
+
expect(sessions[0].id).toBe('session-1');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should list sessions from object response', async () => {
|
|
164
|
+
const mockResponse = {
|
|
165
|
+
sessions: [
|
|
166
|
+
{ id: 'session-1', status: 'active' },
|
|
167
|
+
{ id: 'session-2', status: 'starting' },
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
mockAxiosInstance.get.mockResolvedValueOnce({ data: mockResponse });
|
|
171
|
+
|
|
172
|
+
const sessions = await client.listSessions();
|
|
173
|
+
|
|
174
|
+
expect(sessions).toHaveLength(2);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('getSession', () => {
|
|
179
|
+
it('should get session by ID', async () => {
|
|
180
|
+
const mockSession = {
|
|
181
|
+
id: 'session-123',
|
|
182
|
+
status: 'active',
|
|
183
|
+
url: 'https://example.com',
|
|
184
|
+
};
|
|
185
|
+
mockAxiosInstance.get.mockResolvedValueOnce({ data: mockSession });
|
|
186
|
+
|
|
187
|
+
const session = await client.getSession('session-123');
|
|
188
|
+
|
|
189
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/sessions/session-123');
|
|
190
|
+
expect(session.id).toBe('session-123');
|
|
191
|
+
expect(session.status).toBe('active');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('deleteSession', () => {
|
|
196
|
+
it('should delete session by ID', async () => {
|
|
197
|
+
mockAxiosInstance.delete.mockResolvedValueOnce({ data: {} });
|
|
198
|
+
|
|
199
|
+
await client.deleteSession('session-123');
|
|
200
|
+
|
|
201
|
+
expect(mockAxiosInstance.delete).toHaveBeenCalledWith('/sessions/session-123');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('executeAction', () => {
|
|
206
|
+
it('should execute goto action', async () => {
|
|
207
|
+
const mockResult = { success: true };
|
|
208
|
+
mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
|
|
209
|
+
|
|
210
|
+
const result = await client.executeAction('session-123', {
|
|
211
|
+
type: 'goto',
|
|
212
|
+
url: 'https://example.com',
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions/session-123/action', {
|
|
216
|
+
type: 'goto',
|
|
217
|
+
url: 'https://example.com',
|
|
218
|
+
});
|
|
219
|
+
expect(result.success).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should execute click action', async () => {
|
|
223
|
+
const mockResult = { success: true };
|
|
224
|
+
mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
|
|
225
|
+
|
|
226
|
+
const result = await client.executeAction('session-123', {
|
|
227
|
+
type: 'click',
|
|
228
|
+
ref: 'e3',
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions/session-123/action', {
|
|
232
|
+
type: 'click',
|
|
233
|
+
ref: 'e3',
|
|
234
|
+
});
|
|
235
|
+
expect(result.success).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should execute fill action', async () => {
|
|
239
|
+
const mockResult = { success: true };
|
|
240
|
+
mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
|
|
241
|
+
|
|
242
|
+
const result = await client.executeAction('session-123', {
|
|
243
|
+
type: 'fill',
|
|
244
|
+
ref: 'e4',
|
|
245
|
+
value: 'test@example.com',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions/session-123/action', {
|
|
249
|
+
type: 'fill',
|
|
250
|
+
ref: 'e4',
|
|
251
|
+
value: 'test@example.com',
|
|
252
|
+
});
|
|
253
|
+
expect(result.success).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should execute scroll action', async () => {
|
|
257
|
+
const mockResult = { success: true };
|
|
258
|
+
mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
|
|
259
|
+
|
|
260
|
+
const result = await client.executeAction('session-123', {
|
|
261
|
+
type: 'scroll',
|
|
262
|
+
direction: 'down',
|
|
263
|
+
amount: 500,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions/session-123/action', {
|
|
267
|
+
type: 'scroll',
|
|
268
|
+
direction: 'down',
|
|
269
|
+
amount: 500,
|
|
270
|
+
});
|
|
271
|
+
expect(result.success).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('getSnapshot', () => {
|
|
276
|
+
it('should get ARIA snapshot', async () => {
|
|
277
|
+
const mockSnapshot = {
|
|
278
|
+
snapshot: '- heading "Example" [ref=e1]',
|
|
279
|
+
url: 'https://example.com',
|
|
280
|
+
};
|
|
281
|
+
mockAxiosInstance.get.mockResolvedValueOnce({ data: mockSnapshot });
|
|
282
|
+
|
|
283
|
+
const snapshot = await client.getSnapshot('session-123');
|
|
284
|
+
|
|
285
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/sessions/session-123/snapshot');
|
|
286
|
+
expect(snapshot.snapshot).toContain('Example');
|
|
287
|
+
expect(snapshot.url).toBe('https://example.com');
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe('getScreenshot', () => {
|
|
292
|
+
it('should get screenshot as buffer', async () => {
|
|
293
|
+
const mockData = new Uint8Array([0x89, 0x50, 0x4e, 0x47]); // PNG header
|
|
294
|
+
mockAxiosInstance.get.mockResolvedValueOnce({ data: mockData });
|
|
295
|
+
|
|
296
|
+
const buffer = await client.getScreenshot('session-123');
|
|
297
|
+
|
|
298
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/sessions/session-123/screenshot', {
|
|
299
|
+
responseType: 'arraybuffer',
|
|
300
|
+
});
|
|
301
|
+
expect(Buffer.isBuffer(buffer)).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('getUrl', () => {
|
|
306
|
+
it('should get current URL', async () => {
|
|
307
|
+
const mockResult = { url: 'https://example.com/page' };
|
|
308
|
+
mockAxiosInstance.get.mockResolvedValueOnce({ data: mockResult });
|
|
309
|
+
|
|
310
|
+
const url = await client.getUrl('session-123');
|
|
311
|
+
|
|
312
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/sessions/session-123/url');
|
|
313
|
+
expect(url).toBe('https://example.com/page');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('getStreamUrl', () => {
|
|
318
|
+
it('should return WebSocket URL', () => {
|
|
319
|
+
const url = client.getStreamUrl('session-123');
|
|
320
|
+
|
|
321
|
+
expect(url).toContain('ws');
|
|
322
|
+
expect(url).toContain('session-123');
|
|
323
|
+
expect(url).toContain('/stream');
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('waitForStatus', () => {
|
|
328
|
+
it('should return immediately if session is already active', async () => {
|
|
329
|
+
const mockSession = {
|
|
330
|
+
id: 'session-123',
|
|
331
|
+
status: 'active',
|
|
332
|
+
created_at: '2026-01-23T10:00:00Z',
|
|
333
|
+
};
|
|
334
|
+
mockAxiosInstance.get.mockResolvedValueOnce({ data: mockSession });
|
|
335
|
+
|
|
336
|
+
const session = await client.waitForStatus('session-123', 'active', 5000, 100);
|
|
337
|
+
|
|
338
|
+
expect(session.status).toBe('active');
|
|
339
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledTimes(1);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should poll until session becomes active', async () => {
|
|
343
|
+
const startingSession = {
|
|
344
|
+
id: 'session-123',
|
|
345
|
+
status: 'starting',
|
|
346
|
+
created_at: '2026-01-23T10:00:00Z',
|
|
347
|
+
};
|
|
348
|
+
const activeSession = {
|
|
349
|
+
id: 'session-123',
|
|
350
|
+
status: 'active',
|
|
351
|
+
created_at: '2026-01-23T10:00:00Z',
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
mockAxiosInstance.get
|
|
355
|
+
.mockResolvedValueOnce({ data: startingSession })
|
|
356
|
+
.mockResolvedValueOnce({ data: startingSession })
|
|
357
|
+
.mockResolvedValueOnce({ data: activeSession });
|
|
358
|
+
|
|
359
|
+
const session = await client.waitForStatus('session-123', 'active', 5000, 50);
|
|
360
|
+
|
|
361
|
+
expect(session.status).toBe('active');
|
|
362
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledTimes(3);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should throw error if session is closed', async () => {
|
|
366
|
+
const closedSession = {
|
|
367
|
+
id: 'session-123',
|
|
368
|
+
status: 'closed',
|
|
369
|
+
created_at: '2026-01-23T10:00:00Z',
|
|
370
|
+
};
|
|
371
|
+
mockAxiosInstance.get.mockResolvedValueOnce({ data: closedSession });
|
|
372
|
+
|
|
373
|
+
await expect(client.waitForStatus('session-123', 'active', 5000, 100)).rejects.toThrow(
|
|
374
|
+
'closed'
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
});
|