@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.
Files changed (138) hide show
  1. package/dist/lib/api/browser-types.d.ts +175 -0
  2. package/dist/lib/api/browser-types.d.ts.map +1 -0
  3. package/dist/lib/api/browser-types.js +5 -0
  4. package/dist/lib/api/browser-types.js.map +1 -0
  5. package/dist/lib/api/browser.d.ts +66 -0
  6. package/dist/lib/api/browser.d.ts.map +1 -0
  7. package/dist/lib/api/browser.js +223 -0
  8. package/dist/lib/api/browser.js.map +1 -0
  9. package/dist/package.json +2 -1
  10. package/dist/src/cli/commands/browser/back.d.ts +6 -0
  11. package/dist/src/cli/commands/browser/back.d.ts.map +1 -0
  12. package/dist/src/cli/commands/browser/back.js +42 -0
  13. package/dist/src/cli/commands/browser/back.js.map +1 -0
  14. package/dist/src/cli/commands/browser/check.d.ts +6 -0
  15. package/dist/src/cli/commands/browser/check.d.ts.map +1 -0
  16. package/dist/src/cli/commands/browser/check.js +62 -0
  17. package/dist/src/cli/commands/browser/check.js.map +1 -0
  18. package/dist/src/cli/commands/browser/click.d.ts +6 -0
  19. package/dist/src/cli/commands/browser/click.d.ts.map +1 -0
  20. package/dist/src/cli/commands/browser/click.js +63 -0
  21. package/dist/src/cli/commands/browser/click.js.map +1 -0
  22. package/dist/src/cli/commands/browser/close.d.ts +6 -0
  23. package/dist/src/cli/commands/browser/close.d.ts.map +1 -0
  24. package/dist/src/cli/commands/browser/close.js +44 -0
  25. package/dist/src/cli/commands/browser/close.js.map +1 -0
  26. package/dist/src/cli/commands/browser/create.d.ts +6 -0
  27. package/dist/src/cli/commands/browser/create.d.ts.map +1 -0
  28. package/dist/src/cli/commands/browser/create.js +281 -0
  29. package/dist/src/cli/commands/browser/create.js.map +1 -0
  30. package/dist/src/cli/commands/browser/fill.d.ts +6 -0
  31. package/dist/src/cli/commands/browser/fill.d.ts.map +1 -0
  32. package/dist/src/cli/commands/browser/fill.js +83 -0
  33. package/dist/src/cli/commands/browser/fill.js.map +1 -0
  34. package/dist/src/cli/commands/browser/forward.d.ts +6 -0
  35. package/dist/src/cli/commands/browser/forward.d.ts.map +1 -0
  36. package/dist/src/cli/commands/browser/forward.js +42 -0
  37. package/dist/src/cli/commands/browser/forward.js.map +1 -0
  38. package/dist/src/cli/commands/browser/get-blocks.d.ts +6 -0
  39. package/dist/src/cli/commands/browser/get-blocks.d.ts.map +1 -0
  40. package/dist/src/cli/commands/browser/get-blocks.js +35 -0
  41. package/dist/src/cli/commands/browser/get-blocks.js.map +1 -0
  42. package/dist/src/cli/commands/browser/goto.d.ts +6 -0
  43. package/dist/src/cli/commands/browser/goto.d.ts.map +1 -0
  44. package/dist/src/cli/commands/browser/goto.js +53 -0
  45. package/dist/src/cli/commands/browser/goto.js.map +1 -0
  46. package/dist/src/cli/commands/browser/hover.d.ts +6 -0
  47. package/dist/src/cli/commands/browser/hover.d.ts.map +1 -0
  48. package/dist/src/cli/commands/browser/hover.js +63 -0
  49. package/dist/src/cli/commands/browser/hover.js.map +1 -0
  50. package/dist/src/cli/commands/browser/index.d.ts +9 -0
  51. package/dist/src/cli/commands/browser/index.d.ts.map +1 -0
  52. package/dist/src/cli/commands/browser/index.js +71 -0
  53. package/dist/src/cli/commands/browser/index.js.map +1 -0
  54. package/dist/src/cli/commands/browser/list.d.ts +6 -0
  55. package/dist/src/cli/commands/browser/list.d.ts.map +1 -0
  56. package/dist/src/cli/commands/browser/list.js +85 -0
  57. package/dist/src/cli/commands/browser/list.js.map +1 -0
  58. package/dist/src/cli/commands/browser/press.d.ts +6 -0
  59. package/dist/src/cli/commands/browser/press.d.ts.map +1 -0
  60. package/dist/src/cli/commands/browser/press.js +67 -0
  61. package/dist/src/cli/commands/browser/press.js.map +1 -0
  62. package/dist/src/cli/commands/browser/reload.d.ts +6 -0
  63. package/dist/src/cli/commands/browser/reload.d.ts.map +1 -0
  64. package/dist/src/cli/commands/browser/reload.js +42 -0
  65. package/dist/src/cli/commands/browser/reload.js.map +1 -0
  66. package/dist/src/cli/commands/browser/run.d.ts +6 -0
  67. package/dist/src/cli/commands/browser/run.d.ts.map +1 -0
  68. package/dist/src/cli/commands/browser/run.js +618 -0
  69. package/dist/src/cli/commands/browser/run.js.map +1 -0
  70. package/dist/src/cli/commands/browser/screenshot.d.ts +6 -0
  71. package/dist/src/cli/commands/browser/screenshot.d.ts.map +1 -0
  72. package/dist/src/cli/commands/browser/screenshot.js +72 -0
  73. package/dist/src/cli/commands/browser/screenshot.js.map +1 -0
  74. package/dist/src/cli/commands/browser/scroll-into-view.d.ts +6 -0
  75. package/dist/src/cli/commands/browser/scroll-into-view.d.ts.map +1 -0
  76. package/dist/src/cli/commands/browser/scroll-into-view.js +64 -0
  77. package/dist/src/cli/commands/browser/scroll-into-view.js.map +1 -0
  78. package/dist/src/cli/commands/browser/scroll.d.ts +6 -0
  79. package/dist/src/cli/commands/browser/scroll.d.ts.map +1 -0
  80. package/dist/src/cli/commands/browser/scroll.js +63 -0
  81. package/dist/src/cli/commands/browser/scroll.js.map +1 -0
  82. package/dist/src/cli/commands/browser/select.d.ts +6 -0
  83. package/dist/src/cli/commands/browser/select.d.ts.map +1 -0
  84. package/dist/src/cli/commands/browser/select.js +83 -0
  85. package/dist/src/cli/commands/browser/select.js.map +1 -0
  86. package/dist/src/cli/commands/browser/snapshot.d.ts +6 -0
  87. package/dist/src/cli/commands/browser/snapshot.d.ts.map +1 -0
  88. package/dist/src/cli/commands/browser/snapshot.js +72 -0
  89. package/dist/src/cli/commands/browser/snapshot.js.map +1 -0
  90. package/dist/src/cli/commands/browser/status.d.ts +6 -0
  91. package/dist/src/cli/commands/browser/status.d.ts.map +1 -0
  92. package/dist/src/cli/commands/browser/status.js +91 -0
  93. package/dist/src/cli/commands/browser/status.js.map +1 -0
  94. package/dist/src/cli/commands/browser/stream.d.ts +6 -0
  95. package/dist/src/cli/commands/browser/stream.d.ts.map +1 -0
  96. package/dist/src/cli/commands/browser/stream.js +135 -0
  97. package/dist/src/cli/commands/browser/stream.js.map +1 -0
  98. package/dist/src/cli/commands/browser/tunnel.d.ts +13 -0
  99. package/dist/src/cli/commands/browser/tunnel.d.ts.map +1 -0
  100. package/dist/src/cli/commands/browser/tunnel.js +225 -0
  101. package/dist/src/cli/commands/browser/tunnel.js.map +1 -0
  102. package/dist/src/cli/commands/browser/type.d.ts +6 -0
  103. package/dist/src/cli/commands/browser/type.d.ts.map +1 -0
  104. package/dist/src/cli/commands/browser/type.js +61 -0
  105. package/dist/src/cli/commands/browser/type.js.map +1 -0
  106. package/dist/src/cli/commands/browser/uncheck.d.ts +6 -0
  107. package/dist/src/cli/commands/browser/uncheck.d.ts.map +1 -0
  108. package/dist/src/cli/commands/browser/uncheck.js +62 -0
  109. package/dist/src/cli/commands/browser/uncheck.js.map +1 -0
  110. package/dist/src/cli/commands/browser/url.d.ts +6 -0
  111. package/dist/src/cli/commands/browser/url.d.ts.map +1 -0
  112. package/dist/src/cli/commands/browser/url.js +40 -0
  113. package/dist/src/cli/commands/browser/url.js.map +1 -0
  114. package/dist/src/cli/commands/browser/wait-for-load.d.ts +6 -0
  115. package/dist/src/cli/commands/browser/wait-for-load.d.ts.map +1 -0
  116. package/dist/src/cli/commands/browser/wait-for-load.js +50 -0
  117. package/dist/src/cli/commands/browser/wait-for-load.js.map +1 -0
  118. package/dist/src/cli/commands/browser/wait-for-selector.d.ts +6 -0
  119. package/dist/src/cli/commands/browser/wait-for-selector.d.ts.map +1 -0
  120. package/dist/src/cli/commands/browser/wait-for-selector.js +52 -0
  121. package/dist/src/cli/commands/browser/wait-for-selector.js.map +1 -0
  122. package/dist/src/cli/commands/browser/wait.d.ts +6 -0
  123. package/dist/src/cli/commands/browser/wait.d.ts.map +1 -0
  124. package/dist/src/cli/commands/browser/wait.js +60 -0
  125. package/dist/src/cli/commands/browser/wait.js.map +1 -0
  126. package/dist/src/cli/commands/test/run.d.ts.map +1 -1
  127. package/dist/src/cli/commands/test/run.js +38 -7
  128. package/dist/src/cli/commands/test/run.js.map +1 -1
  129. package/dist/src/cli/index.js +2 -0
  130. package/dist/src/cli/index.js.map +1 -1
  131. package/dist/src/cli/lib/browser-sessions.d.ts +72 -0
  132. package/dist/src/cli/lib/browser-sessions.d.ts.map +1 -0
  133. package/dist/src/cli/lib/browser-sessions.js +184 -0
  134. package/dist/src/cli/lib/browser-sessions.js.map +1 -0
  135. package/lib/api/browser-types.ts +278 -0
  136. package/lib/api/browser.test.ts +378 -0
  137. package/lib/api/browser.ts +279 -0
  138. package/package.json +2 -1
@@ -0,0 +1,279 @@
1
+ /**
2
+ * BrowserApiClient - Client for the desplega.ai Browser API (/browsers/v1/)
3
+ */
4
+
5
+ import axios from 'axios';
6
+ import type { AxiosInstance } from 'axios';
7
+ import { getEnv } from '../env/index.js';
8
+ import type {
9
+ BrowserSession,
10
+ BrowserAction,
11
+ ActionResult,
12
+ SnapshotResult,
13
+ UrlResult,
14
+ BlocksResult,
15
+ CreateBrowserSessionOptions,
16
+ BrowserSessionStatus,
17
+ } from './browser-types.js';
18
+ import type { ExtendedStep } from '../../src/types/test-definition.js';
19
+
20
+ export class BrowserApiClient {
21
+ private readonly client: AxiosInstance;
22
+ private apiKey: string | null = null;
23
+
24
+ constructor(baseUrl?: string) {
25
+ // Use environment variable if available, otherwise use provided baseUrl, finally fall back to production
26
+ const apiUrl = getEnv('QA_USE_API_URL') || baseUrl || 'https://api.desplega.ai';
27
+
28
+ this.client = axios.create({
29
+ baseURL: `${apiUrl}/browsers/v1`,
30
+ timeout: 60000, // Longer timeout for browser operations
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ },
34
+ });
35
+
36
+ // Auto-load API key from environment or config file if available
37
+ const envApiKey = getEnv('QA_USE_API_KEY');
38
+ if (envApiKey) {
39
+ this.setApiKey(envApiKey);
40
+ }
41
+ }
42
+
43
+ setApiKey(apiKey: string): void {
44
+ this.apiKey = apiKey;
45
+ this.client.defaults.headers['Authorization'] = `Bearer ${apiKey}`;
46
+ }
47
+
48
+ getApiKey(): string | null {
49
+ return this.apiKey;
50
+ }
51
+
52
+ getBaseUrl(): string {
53
+ return this.client.defaults.baseURL || 'https://api.desplega.ai/browsers/v1';
54
+ }
55
+
56
+ // ==========================================
57
+ // Session Management
58
+ // ==========================================
59
+
60
+ /**
61
+ * Create a new browser session
62
+ */
63
+ async createSession(options: CreateBrowserSessionOptions = {}): Promise<BrowserSession> {
64
+ try {
65
+ const response = await this.client.post('/sessions', {
66
+ headless: options.headless ?? true,
67
+ viewport: options.viewport ?? 'desktop',
68
+ timeout: options.timeout ?? 300,
69
+ ...(options.ws_url && { ws_url: options.ws_url }),
70
+ });
71
+
72
+ return response.data as BrowserSession;
73
+ } catch (error) {
74
+ throw this.handleError(error, 'create session');
75
+ }
76
+ }
77
+
78
+ /**
79
+ * List all sessions for the organization
80
+ */
81
+ async listSessions(): Promise<BrowserSession[]> {
82
+ try {
83
+ const response = await this.client.get('/sessions');
84
+ // API may return { sessions: [...] } or just [...]
85
+ return Array.isArray(response.data) ? response.data : response.data.sessions || [];
86
+ } catch (error) {
87
+ throw this.handleError(error, 'list sessions');
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get a specific session by ID
93
+ */
94
+ async getSession(sessionId: string): Promise<BrowserSession> {
95
+ try {
96
+ const response = await this.client.get(`/sessions/${sessionId}`);
97
+ return response.data as BrowserSession;
98
+ } catch (error) {
99
+ throw this.handleError(error, 'get session');
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Delete/close a session
105
+ */
106
+ async deleteSession(sessionId: string): Promise<void> {
107
+ try {
108
+ await this.client.delete(`/sessions/${sessionId}`);
109
+ } catch (error) {
110
+ throw this.handleError(error, 'delete session');
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Wait for a session to reach a specific status
116
+ * @param sessionId - The session ID to wait for
117
+ * @param targetStatus - The status to wait for (default: 'active')
118
+ * @param timeoutMs - Maximum time to wait in milliseconds (default: 30000)
119
+ * @param pollIntervalMs - Polling interval in milliseconds (default: 1000)
120
+ */
121
+ async waitForStatus(
122
+ sessionId: string,
123
+ targetStatus: BrowserSessionStatus = 'active',
124
+ timeoutMs: number = 30000,
125
+ pollIntervalMs: number = 1000
126
+ ): Promise<BrowserSession> {
127
+ const startTime = Date.now();
128
+
129
+ while (Date.now() - startTime < timeoutMs) {
130
+ const session = await this.getSession(sessionId);
131
+
132
+ if (session.status === targetStatus) {
133
+ return session;
134
+ }
135
+
136
+ // If session is closed or failed, throw error
137
+ if (session.status === 'closed') {
138
+ throw new Error(`Session ${sessionId} is closed`);
139
+ }
140
+
141
+ // Wait before polling again
142
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
143
+ }
144
+
145
+ throw new Error(`Timeout waiting for session ${sessionId} to reach status "${targetStatus}"`);
146
+ }
147
+
148
+ // ==========================================
149
+ // Actions
150
+ // ==========================================
151
+
152
+ /**
153
+ * Execute a browser action
154
+ */
155
+ async executeAction(sessionId: string, action: BrowserAction): Promise<ActionResult> {
156
+ try {
157
+ const response = await this.client.post(`/sessions/${sessionId}/action`, action);
158
+ return response.data as ActionResult;
159
+ } catch (error) {
160
+ throw this.handleError(error, `execute action "${action.type}"`);
161
+ }
162
+ }
163
+
164
+ // ==========================================
165
+ // Inspection
166
+ // ==========================================
167
+
168
+ /**
169
+ * Get the ARIA accessibility tree snapshot
170
+ */
171
+ async getSnapshot(sessionId: string): Promise<SnapshotResult> {
172
+ try {
173
+ const response = await this.client.get(`/sessions/${sessionId}/snapshot`);
174
+ return response.data as SnapshotResult;
175
+ } catch (error) {
176
+ throw this.handleError(error, 'get snapshot');
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Get a screenshot of the current page
182
+ * @returns Buffer containing PNG image data
183
+ */
184
+ async getScreenshot(sessionId: string): Promise<Buffer> {
185
+ try {
186
+ const response = await this.client.get(`/sessions/${sessionId}/screenshot`, {
187
+ responseType: 'arraybuffer',
188
+ });
189
+ return Buffer.from(response.data);
190
+ } catch (error) {
191
+ throw this.handleError(error, 'get screenshot');
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Get the current page URL
197
+ */
198
+ async getUrl(sessionId: string): Promise<string> {
199
+ try {
200
+ const response = await this.client.get(`/sessions/${sessionId}/url`);
201
+ const result = response.data as UrlResult;
202
+ return result.url;
203
+ } catch (error) {
204
+ throw this.handleError(error, 'get URL');
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Get recorded blocks (test steps) from the session
210
+ * @returns Array of ExtendedStep objects
211
+ */
212
+ async getBlocks(sessionId: string): Promise<ExtendedStep[]> {
213
+ try {
214
+ const response = await this.client.get(`/sessions/${sessionId}/blocks`);
215
+ const result = response.data as BlocksResult;
216
+ return (result.blocks || []) as ExtendedStep[];
217
+ } catch (error) {
218
+ throw this.handleError(error, 'get blocks');
219
+ }
220
+ }
221
+
222
+ // ==========================================
223
+ // WebSocket Streaming
224
+ // ==========================================
225
+
226
+ /**
227
+ * Get the WebSocket URL for streaming events
228
+ */
229
+ getStreamUrl(sessionId: string): string {
230
+ // Convert HTTP URL to WebSocket URL
231
+ const baseUrl = this.getBaseUrl();
232
+ const wsUrl = baseUrl.replace(/^http/, 'ws');
233
+ return `${wsUrl}/sessions/${sessionId}/stream`;
234
+ }
235
+
236
+ // ==========================================
237
+ // Error Handling
238
+ // ==========================================
239
+
240
+ private handleError(error: unknown, operation: string): Error {
241
+ if (axios.isAxiosError(error)) {
242
+ const statusCode = error.response?.status;
243
+ const errorData = error.response?.data as { message?: string; detail?: string } | undefined;
244
+
245
+ if (statusCode === 404) {
246
+ return new Error(`Session not found`);
247
+ }
248
+
249
+ if (statusCode === 401) {
250
+ return new Error(`Unauthorized. Please check your API key.`);
251
+ }
252
+
253
+ if (statusCode === 403) {
254
+ return new Error(`Forbidden. You don't have permission for this operation.`);
255
+ }
256
+
257
+ const message =
258
+ errorData?.message || errorData?.detail || `HTTP ${statusCode}: Failed to ${operation}`;
259
+ return new Error(message);
260
+ }
261
+
262
+ if (error instanceof Error) {
263
+ return error;
264
+ }
265
+
266
+ return new Error(`Unknown error during ${operation}`);
267
+ }
268
+ }
269
+
270
+ // Re-export types for convenience
271
+ export type {
272
+ BrowserSession,
273
+ BrowserAction,
274
+ ActionResult,
275
+ SnapshotResult,
276
+ UrlResult,
277
+ CreateBrowserSessionOptions,
278
+ BrowserSessionStatus,
279
+ } from './browser-types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/qa-use",
3
- "version": "2.1.7",
3
+ "version": "2.2.2",
4
4
  "packageManager": "bun@^1.3.4",
5
5
  "description": "QA automation tool for browser testing with MCP server support",
6
6
  "type": "module",
@@ -95,6 +95,7 @@
95
95
  "express": "^4.21.2",
96
96
  "glob": "^13.0.0",
97
97
  "playwright": "1.55.0",
98
+ "ws": "^8.19.0",
98
99
  "yaml": "^2.8.2"
99
100
  }
100
101
  }