@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.
- package/dist/lib/api/browser-types.d.ts +80 -4
- package/dist/lib/api/browser-types.d.ts.map +1 -1
- package/dist/lib/api/browser.d.ts +26 -3
- package/dist/lib/api/browser.d.ts.map +1 -1
- package/dist/lib/api/browser.js +89 -3
- package/dist/lib/api/browser.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/cli/commands/browser/create.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/create.js +33 -1
- package/dist/src/cli/commands/browser/create.js.map +1 -1
- package/dist/src/cli/commands/browser/generate-test.d.ts +6 -0
- package/dist/src/cli/commands/browser/generate-test.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/generate-test.js +46 -0
- package/dist/src/cli/commands/browser/generate-test.js.map +1 -0
- package/dist/src/cli/commands/browser/index.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/index.js +4 -0
- package/dist/src/cli/commands/browser/index.js.map +1 -1
- package/dist/src/cli/commands/browser/logs.d.ts +6 -0
- package/dist/src/cli/commands/browser/logs.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/logs.js +95 -0
- package/dist/src/cli/commands/browser/logs.js.map +1 -0
- package/dist/src/cli/commands/browser/run.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/run.js +130 -10
- package/dist/src/cli/commands/browser/run.js.map +1 -1
- package/dist/src/cli/commands/browser/snapshot.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/snapshot.js +14 -2
- package/dist/src/cli/commands/browser/snapshot.js.map +1 -1
- package/dist/src/cli/commands/browser/status.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/status.js +23 -0
- package/dist/src/cli/commands/browser/status.js.map +1 -1
- package/dist/src/cli/commands/test/export.d.ts.map +1 -1
- package/dist/src/cli/commands/test/export.js +47 -30
- package/dist/src/cli/commands/test/export.js.map +1 -1
- package/dist/src/cli/commands/test/sync.d.ts.map +1 -1
- package/dist/src/cli/commands/test/sync.js +54 -28
- package/dist/src/cli/commands/test/sync.js.map +1 -1
- package/dist/src/cli/lib/loader.d.ts +8 -0
- package/dist/src/cli/lib/loader.d.ts.map +1 -1
- package/dist/src/cli/lib/loader.js +38 -7
- package/dist/src/cli/lib/loader.js.map +1 -1
- package/lib/api/browser-types.ts +105 -1
- package/lib/api/browser.test.ts +213 -0
- package/lib/api/browser.ts +112 -3
- package/package.json +1 -1
package/lib/api/browser.test.ts
CHANGED
|
@@ -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
|
});
|
package/lib/api/browser.ts
CHANGED
|
@@ -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
|
|
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
|
|
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';
|