@desplega.ai/qa-use 2.2.3 → 2.2.5

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 (44) hide show
  1. package/dist/lib/api/browser-types.d.ts +80 -4
  2. package/dist/lib/api/browser-types.d.ts.map +1 -1
  3. package/dist/lib/api/browser.d.ts +26 -3
  4. package/dist/lib/api/browser.d.ts.map +1 -1
  5. package/dist/lib/api/browser.js +89 -3
  6. package/dist/lib/api/browser.js.map +1 -1
  7. package/dist/package.json +1 -1
  8. package/dist/src/cli/commands/browser/create.d.ts.map +1 -1
  9. package/dist/src/cli/commands/browser/create.js +33 -1
  10. package/dist/src/cli/commands/browser/create.js.map +1 -1
  11. package/dist/src/cli/commands/browser/generate-test.d.ts +6 -0
  12. package/dist/src/cli/commands/browser/generate-test.d.ts.map +1 -0
  13. package/dist/src/cli/commands/browser/generate-test.js +46 -0
  14. package/dist/src/cli/commands/browser/generate-test.js.map +1 -0
  15. package/dist/src/cli/commands/browser/index.d.ts.map +1 -1
  16. package/dist/src/cli/commands/browser/index.js +4 -0
  17. package/dist/src/cli/commands/browser/index.js.map +1 -1
  18. package/dist/src/cli/commands/browser/logs.d.ts +6 -0
  19. package/dist/src/cli/commands/browser/logs.d.ts.map +1 -0
  20. package/dist/src/cli/commands/browser/logs.js +95 -0
  21. package/dist/src/cli/commands/browser/logs.js.map +1 -0
  22. package/dist/src/cli/commands/browser/run.d.ts.map +1 -1
  23. package/dist/src/cli/commands/browser/run.js +130 -10
  24. package/dist/src/cli/commands/browser/run.js.map +1 -1
  25. package/dist/src/cli/commands/browser/snapshot.d.ts.map +1 -1
  26. package/dist/src/cli/commands/browser/snapshot.js +14 -2
  27. package/dist/src/cli/commands/browser/snapshot.js.map +1 -1
  28. package/dist/src/cli/commands/browser/status.d.ts.map +1 -1
  29. package/dist/src/cli/commands/browser/status.js +23 -0
  30. package/dist/src/cli/commands/browser/status.js.map +1 -1
  31. package/dist/src/cli/commands/test/export.d.ts.map +1 -1
  32. package/dist/src/cli/commands/test/export.js +47 -30
  33. package/dist/src/cli/commands/test/export.js.map +1 -1
  34. package/dist/src/cli/commands/test/sync.d.ts.map +1 -1
  35. package/dist/src/cli/commands/test/sync.js +54 -28
  36. package/dist/src/cli/commands/test/sync.js.map +1 -1
  37. package/dist/src/cli/lib/loader.d.ts +8 -0
  38. package/dist/src/cli/lib/loader.d.ts.map +1 -1
  39. package/dist/src/cli/lib/loader.js +38 -7
  40. package/dist/src/cli/lib/loader.js.map +1 -1
  41. package/lib/api/browser-types.ts +105 -1
  42. package/lib/api/browser.test.ts +213 -0
  43. package/lib/api/browser.ts +112 -3
  44. package/package.json +1 -1
@@ -286,6 +286,56 @@ describe('BrowserApiClient', () => {
286
286
  expect(snapshot.snapshot).toContain('Example');
287
287
  expect(snapshot.url).toBe('https://example.com');
288
288
  });
289
+
290
+ it('should get ARIA snapshot with filtering options', async () => {
291
+ const mockSnapshot = {
292
+ snapshot: '- button "Submit" [ref=e1]',
293
+ url: 'https://example.com',
294
+ filter_stats: {
295
+ original_lines: 450,
296
+ filtered_lines: 42,
297
+ reduction_percent: 91,
298
+ },
299
+ };
300
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockSnapshot });
301
+
302
+ const snapshot = await client.getSnapshot('session-123', {
303
+ interactive: true,
304
+ compact: true,
305
+ max_depth: 3,
306
+ });
307
+
308
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith(
309
+ '/sessions/session-123/snapshot?interactive=true&compact=true&max_depth=3'
310
+ );
311
+ expect(snapshot.filter_stats?.reduction_percent).toBe(91);
312
+ });
313
+
314
+ it('should get ARIA snapshot with scope option', async () => {
315
+ const mockSnapshot = {
316
+ snapshot: '- heading "Main Content" [ref=e1]',
317
+ url: 'https://example.com',
318
+ };
319
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockSnapshot });
320
+
321
+ await client.getSnapshot('session-123', { scope: '#main' });
322
+
323
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith(
324
+ '/sessions/session-123/snapshot?scope=%23main'
325
+ );
326
+ });
327
+
328
+ it('should get ARIA snapshot without options (backward compatible)', async () => {
329
+ const mockSnapshot = {
330
+ snapshot: '- heading "Example" [ref=e1]',
331
+ url: 'https://example.com',
332
+ };
333
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockSnapshot });
334
+
335
+ await client.getSnapshot('session-123');
336
+
337
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith('/sessions/session-123/snapshot');
338
+ });
289
339
  });
290
340
 
291
341
  describe('getScreenshot', () => {
@@ -375,4 +425,167 @@ describe('BrowserApiClient', () => {
375
425
  );
376
426
  });
377
427
  });
428
+
429
+ describe('generateTest', () => {
430
+ it('should generate test from session blocks', async () => {
431
+ const mockResult = {
432
+ yaml: 'name: Login Test\nsteps:\n - goto: https://example.com',
433
+ test_definition: { name: 'Login Test' },
434
+ block_count: 3,
435
+ };
436
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
437
+
438
+ const result = await client.generateTest('session-123', {
439
+ name: 'Login Test',
440
+ });
441
+
442
+ expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions/session-123/generate-test', {
443
+ name: 'Login Test',
444
+ });
445
+ expect(result.yaml).toContain('Login Test');
446
+ expect(result.block_count).toBe(3);
447
+ });
448
+
449
+ it('should generate test with app_config and variables', async () => {
450
+ const mockResult = {
451
+ yaml: 'name: Test with Config\nsteps: []',
452
+ test_definition: { name: 'Test with Config' },
453
+ block_count: 5,
454
+ };
455
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
456
+
457
+ const result = await client.generateTest('session-123', {
458
+ name: 'Test with Config',
459
+ app_config: 'my-app-config',
460
+ variables: { base_url: 'https://staging.example.com' },
461
+ });
462
+
463
+ expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions/session-123/generate-test', {
464
+ name: 'Test with Config',
465
+ app_config: 'my-app-config',
466
+ variables: { base_url: 'https://staging.example.com' },
467
+ });
468
+ expect(result.block_count).toBe(5);
469
+ });
470
+ });
471
+
472
+ describe('getConsoleLogs', () => {
473
+ it('should get console logs', async () => {
474
+ const mockResult = {
475
+ logs: [{ level: 'error', text: 'Test error', timestamp: '2026-01-24T10:00:00Z' }],
476
+ total: 1,
477
+ };
478
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockResult });
479
+
480
+ const result = await client.getConsoleLogs('session-123');
481
+
482
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith('/sessions/session-123/logs/console');
483
+ expect(result.logs).toHaveLength(1);
484
+ expect(result.logs[0].level).toBe('error');
485
+ });
486
+
487
+ it('should get console logs with filters', async () => {
488
+ const mockResult = { logs: [], total: 0 };
489
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockResult });
490
+
491
+ await client.getConsoleLogs('session-123', { level: 'error', limit: 50 });
492
+
493
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith(
494
+ '/sessions/session-123/logs/console?level=error&limit=50'
495
+ );
496
+ });
497
+
498
+ it('should get console logs with only level filter', async () => {
499
+ const mockResult = { logs: [], total: 0 };
500
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockResult });
501
+
502
+ await client.getConsoleLogs('session-123', { level: 'warn' });
503
+
504
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith(
505
+ '/sessions/session-123/logs/console?level=warn'
506
+ );
507
+ });
508
+ });
509
+
510
+ describe('getNetworkLogs', () => {
511
+ it('should get network logs', async () => {
512
+ const mockResult = {
513
+ requests: [
514
+ {
515
+ method: 'GET',
516
+ url: 'https://api.example.com',
517
+ status: 200,
518
+ duration_ms: 150,
519
+ timestamp: '2026-01-24T10:00:00Z',
520
+ },
521
+ ],
522
+ total: 1,
523
+ };
524
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockResult });
525
+
526
+ const result = await client.getNetworkLogs('session-123');
527
+
528
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith('/sessions/session-123/logs/network');
529
+ expect(result.requests).toHaveLength(1);
530
+ expect(result.requests[0].status).toBe(200);
531
+ });
532
+
533
+ it('should get network logs with filters', async () => {
534
+ const mockResult = { requests: [], total: 0 };
535
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockResult });
536
+
537
+ await client.getNetworkLogs('session-123', { status: '4xx,5xx', url_pattern: '*api*' });
538
+
539
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith(
540
+ '/sessions/session-123/logs/network?status=4xx%2C5xx&url_pattern=*api*'
541
+ );
542
+ });
543
+
544
+ it('should get network logs with limit only', async () => {
545
+ const mockResult = { requests: [], total: 0 };
546
+ mockAxiosInstance.get.mockResolvedValueOnce({ data: mockResult });
547
+
548
+ await client.getNetworkLogs('session-123', { limit: 100 });
549
+
550
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith(
551
+ '/sessions/session-123/logs/network?limit=100'
552
+ );
553
+ });
554
+ });
555
+
556
+ describe('createSession with record_blocks', () => {
557
+ it('should create session with record_blocks enabled', async () => {
558
+ const mockSession = {
559
+ id: 'session-rec',
560
+ status: 'starting',
561
+ created_at: '2026-01-24T10:00:00Z',
562
+ };
563
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockSession });
564
+
565
+ await client.createSession({
566
+ record_blocks: true,
567
+ });
568
+
569
+ expect(mockAxiosInstance.post).toHaveBeenCalledWith('/sessions', {
570
+ headless: true,
571
+ viewport: 'desktop',
572
+ timeout: 300,
573
+ record_blocks: true,
574
+ });
575
+ });
576
+
577
+ it('should not include record_blocks when not provided', async () => {
578
+ const mockSession = {
579
+ id: 'session-no-rec',
580
+ status: 'starting',
581
+ created_at: '2026-01-24T10:00:00Z',
582
+ };
583
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockSession });
584
+
585
+ await client.createSession({});
586
+
587
+ const callArgs = mockAxiosInstance.post.mock.calls[0];
588
+ expect(callArgs[1]).not.toHaveProperty('record_blocks');
589
+ });
590
+ });
378
591
  });
@@ -10,10 +10,17 @@ import type {
10
10
  BrowserAction,
11
11
  ActionResult,
12
12
  SnapshotResult,
13
+ SnapshotOptions,
13
14
  UrlResult,
14
15
  BlocksResult,
15
16
  CreateBrowserSessionOptions,
16
17
  BrowserSessionStatus,
18
+ GenerateTestOptions,
19
+ GenerateTestResult,
20
+ ConsoleLogsOptions,
21
+ ConsoleLogsResult,
22
+ NetworkLogsOptions,
23
+ NetworkLogsResult,
17
24
  } from './browser-types.js';
18
25
  import type { ExtendedStep } from '../../src/types/test-definition.js';
19
26
 
@@ -67,6 +74,8 @@ export class BrowserApiClient {
67
74
  viewport: options.viewport ?? 'desktop',
68
75
  timeout: options.timeout ?? 300,
69
76
  ...(options.ws_url && { ws_url: options.ws_url }),
77
+ ...(options.record_blocks !== undefined && { record_blocks: options.record_blocks }),
78
+ ...(options.after_test_id && { after_test_id: options.after_test_id }),
70
79
  });
71
80
 
72
81
  return response.data as BrowserSession;
@@ -133,11 +142,16 @@ export class BrowserApiClient {
133
142
  return session;
134
143
  }
135
144
 
136
- // If session is closed or failed, throw error
145
+ // If session is closed, throw error
137
146
  if (session.status === 'closed') {
138
147
  throw new Error(`Session ${sessionId} is closed`);
139
148
  }
140
149
 
150
+ // If session failed (e.g., after_test_id test failed), return session for error handling
151
+ if (session.status === 'failed') {
152
+ return session;
153
+ }
154
+
141
155
  // Wait before polling again
142
156
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
143
157
  }
@@ -167,10 +181,21 @@ export class BrowserApiClient {
167
181
 
168
182
  /**
169
183
  * Get the ARIA accessibility tree snapshot
184
+ * @param sessionId - The session ID
185
+ * @param options - Filtering options (interactive, compact, max_depth, scope)
170
186
  */
171
- async getSnapshot(sessionId: string): Promise<SnapshotResult> {
187
+ async getSnapshot(sessionId: string, options: SnapshotOptions = {}): Promise<SnapshotResult> {
172
188
  try {
173
- const response = await this.client.get(`/sessions/${sessionId}/snapshot`);
189
+ const params = new URLSearchParams();
190
+ if (options.interactive) params.append('interactive', 'true');
191
+ if (options.compact) params.append('compact', 'true');
192
+ if (options.max_depth !== undefined) params.append('max_depth', options.max_depth.toString());
193
+ if (options.scope) params.append('scope', options.scope);
194
+
195
+ const queryString = params.toString();
196
+ const url = `/sessions/${sessionId}/snapshot${queryString ? `?${queryString}` : ''}`;
197
+
198
+ const response = await this.client.get(url);
174
199
  return response.data as SnapshotResult;
175
200
  } catch (error) {
176
201
  throw this.handleError(error, 'get snapshot');
@@ -219,6 +244,80 @@ export class BrowserApiClient {
219
244
  }
220
245
  }
221
246
 
247
+ // ==========================================
248
+ // Test Generation
249
+ // ==========================================
250
+
251
+ /**
252
+ * Generate a test definition from recorded session blocks
253
+ * @param sessionId - The session ID
254
+ * @param options - Test generation options (name, app_config, variables)
255
+ * @returns Generated test YAML and metadata
256
+ */
257
+ async generateTest(sessionId: string, options: GenerateTestOptions): Promise<GenerateTestResult> {
258
+ try {
259
+ const response = await this.client.post(`/sessions/${sessionId}/generate-test`, options);
260
+ return response.data as GenerateTestResult;
261
+ } catch (error) {
262
+ throw this.handleError(error, 'generate test');
263
+ }
264
+ }
265
+
266
+ // ==========================================
267
+ // Logs
268
+ // ==========================================
269
+
270
+ /**
271
+ * Get console logs from a session
272
+ * @param sessionId - The session ID
273
+ * @param options - Filter options (level, limit)
274
+ * @returns Console log entries
275
+ */
276
+ async getConsoleLogs(
277
+ sessionId: string,
278
+ options: ConsoleLogsOptions = {}
279
+ ): Promise<ConsoleLogsResult> {
280
+ try {
281
+ const params = new URLSearchParams();
282
+ if (options.level) params.append('level', options.level);
283
+ if (options.limit !== undefined) params.append('limit', options.limit.toString());
284
+
285
+ const queryString = params.toString();
286
+ const url = `/sessions/${sessionId}/logs/console${queryString ? `?${queryString}` : ''}`;
287
+
288
+ const response = await this.client.get(url);
289
+ return response.data as ConsoleLogsResult;
290
+ } catch (error) {
291
+ throw this.handleError(error, 'get console logs');
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Get network request logs from a session
297
+ * @param sessionId - The session ID
298
+ * @param options - Filter options (status, url_pattern, limit)
299
+ * @returns Network log entries
300
+ */
301
+ async getNetworkLogs(
302
+ sessionId: string,
303
+ options: NetworkLogsOptions = {}
304
+ ): Promise<NetworkLogsResult> {
305
+ try {
306
+ const params = new URLSearchParams();
307
+ if (options.status) params.append('status', options.status);
308
+ if (options.url_pattern) params.append('url_pattern', options.url_pattern);
309
+ if (options.limit !== undefined) params.append('limit', options.limit.toString());
310
+
311
+ const queryString = params.toString();
312
+ const url = `/sessions/${sessionId}/logs/network${queryString ? `?${queryString}` : ''}`;
313
+
314
+ const response = await this.client.get(url);
315
+ return response.data as NetworkLogsResult;
316
+ } catch (error) {
317
+ throw this.handleError(error, 'get network logs');
318
+ }
319
+ }
320
+
222
321
  // ==========================================
223
322
  // WebSocket Streaming
224
323
  // ==========================================
@@ -273,7 +372,17 @@ export type {
273
372
  BrowserAction,
274
373
  ActionResult,
275
374
  SnapshotResult,
375
+ SnapshotOptions,
376
+ SnapshotFilterStats,
276
377
  UrlResult,
277
378
  CreateBrowserSessionOptions,
278
379
  BrowserSessionStatus,
380
+ GenerateTestOptions,
381
+ GenerateTestResult,
382
+ ConsoleLogsOptions,
383
+ ConsoleLogsResult,
384
+ ConsoleLogEntry,
385
+ NetworkLogsOptions,
386
+ NetworkLogsResult,
387
+ NetworkLogEntry,
279
388
  } from './browser-types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/qa-use",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "packageManager": "bun@^1.3.4",
5
5
  "description": "QA automation tool for browser testing with MCP server support",
6
6
  "type": "module",