@desplega.ai/qa-use 2.4.1 → 2.5.0

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 (173) hide show
  1. package/dist/lib/api/browser.d.ts +2 -2
  2. package/dist/lib/api/browser.d.ts.map +1 -1
  3. package/dist/lib/api/browser.js +1 -1
  4. package/dist/lib/api/browser.js.map +1 -1
  5. package/dist/lib/api/index.d.ts +9 -5
  6. package/dist/lib/api/index.d.ts.map +1 -1
  7. package/dist/lib/api/index.js +11 -2
  8. package/dist/lib/api/index.js.map +1 -1
  9. package/dist/lib/browser/index.d.ts +1 -1
  10. package/dist/lib/browser/index.d.ts.map +1 -1
  11. package/dist/lib/browser/index.js +7 -8
  12. package/dist/lib/browser/index.js.map +1 -1
  13. package/dist/lib/env/index.js +4 -4
  14. package/dist/lib/env/index.js.map +1 -1
  15. package/dist/lib/tunnel/index.d.ts.map +1 -1
  16. package/dist/lib/tunnel/index.js +6 -6
  17. package/dist/lib/tunnel/index.js.map +1 -1
  18. package/dist/package.json +10 -14
  19. package/dist/src/cli/commands/browser/back.js +1 -1
  20. package/dist/src/cli/commands/browser/back.js.map +1 -1
  21. package/dist/src/cli/commands/browser/check.js +1 -1
  22. package/dist/src/cli/commands/browser/check.js.map +1 -1
  23. package/dist/src/cli/commands/browser/click.js +1 -1
  24. package/dist/src/cli/commands/browser/click.js.map +1 -1
  25. package/dist/src/cli/commands/browser/close.js +2 -2
  26. package/dist/src/cli/commands/browser/close.js.map +1 -1
  27. package/dist/src/cli/commands/browser/create.js +5 -5
  28. package/dist/src/cli/commands/browser/create.js.map +1 -1
  29. package/dist/src/cli/commands/browser/drag.js +1 -1
  30. package/dist/src/cli/commands/browser/drag.js.map +1 -1
  31. package/dist/src/cli/commands/browser/fill.js +2 -2
  32. package/dist/src/cli/commands/browser/fill.js.map +1 -1
  33. package/dist/src/cli/commands/browser/forward.js +1 -1
  34. package/dist/src/cli/commands/browser/forward.js.map +1 -1
  35. package/dist/src/cli/commands/browser/generate-test.js +2 -2
  36. package/dist/src/cli/commands/browser/generate-test.js.map +1 -1
  37. package/dist/src/cli/commands/browser/goto.js +2 -2
  38. package/dist/src/cli/commands/browser/goto.js.map +1 -1
  39. package/dist/src/cli/commands/browser/hover.js +1 -1
  40. package/dist/src/cli/commands/browser/hover.js.map +1 -1
  41. package/dist/src/cli/commands/browser/index.d.ts.map +1 -1
  42. package/dist/src/cli/commands/browser/index.js +24 -24
  43. package/dist/src/cli/commands/browser/index.js.map +1 -1
  44. package/dist/src/cli/commands/browser/list.js +2 -2
  45. package/dist/src/cli/commands/browser/list.js.map +1 -1
  46. package/dist/src/cli/commands/browser/mfa-totp.js +1 -1
  47. package/dist/src/cli/commands/browser/mfa-totp.js.map +1 -1
  48. package/dist/src/cli/commands/browser/press.js +1 -1
  49. package/dist/src/cli/commands/browser/press.js.map +1 -1
  50. package/dist/src/cli/commands/browser/reload.js +1 -1
  51. package/dist/src/cli/commands/browser/reload.js.map +1 -1
  52. package/dist/src/cli/commands/browser/run.d.ts.map +1 -1
  53. package/dist/src/cli/commands/browser/run.js +16 -16
  54. package/dist/src/cli/commands/browser/run.js.map +1 -1
  55. package/dist/src/cli/commands/browser/screenshot.d.ts.map +1 -1
  56. package/dist/src/cli/commands/browser/screenshot.js +3 -3
  57. package/dist/src/cli/commands/browser/screenshot.js.map +1 -1
  58. package/dist/src/cli/commands/browser/scroll-into-view.js +1 -1
  59. package/dist/src/cli/commands/browser/scroll-into-view.js.map +1 -1
  60. package/dist/src/cli/commands/browser/scroll.js +2 -2
  61. package/dist/src/cli/commands/browser/scroll.js.map +1 -1
  62. package/dist/src/cli/commands/browser/select.js +2 -2
  63. package/dist/src/cli/commands/browser/select.js.map +1 -1
  64. package/dist/src/cli/commands/browser/snapshot.js +2 -2
  65. package/dist/src/cli/commands/browser/snapshot.js.map +1 -1
  66. package/dist/src/cli/commands/browser/status.js +1 -1
  67. package/dist/src/cli/commands/browser/status.js.map +1 -1
  68. package/dist/src/cli/commands/browser/stream.js +1 -1
  69. package/dist/src/cli/commands/browser/stream.js.map +1 -1
  70. package/dist/src/cli/commands/browser/type.js +2 -2
  71. package/dist/src/cli/commands/browser/type.js.map +1 -1
  72. package/dist/src/cli/commands/browser/uncheck.js +1 -1
  73. package/dist/src/cli/commands/browser/uncheck.js.map +1 -1
  74. package/dist/src/cli/commands/browser/upload.d.ts.map +1 -1
  75. package/dist/src/cli/commands/browser/upload.js +3 -3
  76. package/dist/src/cli/commands/browser/upload.js.map +1 -1
  77. package/dist/src/cli/commands/browser/wait-for-load.js +1 -1
  78. package/dist/src/cli/commands/browser/wait-for-load.js.map +1 -1
  79. package/dist/src/cli/commands/browser/wait-for-selector.js +1 -1
  80. package/dist/src/cli/commands/browser/wait-for-selector.js.map +1 -1
  81. package/dist/src/cli/commands/browser/wait.js +2 -2
  82. package/dist/src/cli/commands/browser/wait.js.map +1 -1
  83. package/dist/src/cli/commands/info.js +4 -4
  84. package/dist/src/cli/commands/info.js.map +1 -1
  85. package/dist/src/cli/commands/install-deps.js +1 -1
  86. package/dist/src/cli/commands/install-deps.js.map +1 -1
  87. package/dist/src/cli/commands/setup.d.ts.map +1 -1
  88. package/dist/src/cli/commands/setup.js +4 -4
  89. package/dist/src/cli/commands/setup.js.map +1 -1
  90. package/dist/src/cli/commands/test/diff.d.ts +6 -0
  91. package/dist/src/cli/commands/test/diff.d.ts.map +1 -0
  92. package/dist/src/cli/commands/test/diff.js +187 -0
  93. package/dist/src/cli/commands/test/diff.js.map +1 -0
  94. package/dist/src/cli/commands/test/export.d.ts.map +1 -1
  95. package/dist/src/cli/commands/test/export.js +4 -4
  96. package/dist/src/cli/commands/test/export.js.map +1 -1
  97. package/dist/src/cli/commands/test/index.d.ts.map +1 -1
  98. package/dist/src/cli/commands/test/index.js +7 -5
  99. package/dist/src/cli/commands/test/index.js.map +1 -1
  100. package/dist/src/cli/commands/test/info.d.ts.map +1 -1
  101. package/dist/src/cli/commands/test/info.js +4 -4
  102. package/dist/src/cli/commands/test/info.js.map +1 -1
  103. package/dist/src/cli/commands/test/init.d.ts.map +1 -1
  104. package/dist/src/cli/commands/test/init.js +4 -4
  105. package/dist/src/cli/commands/test/init.js.map +1 -1
  106. package/dist/src/cli/commands/test/list.d.ts.map +1 -1
  107. package/dist/src/cli/commands/test/list.js +4 -4
  108. package/dist/src/cli/commands/test/list.js.map +1 -1
  109. package/dist/src/cli/commands/test/run.js +5 -5
  110. package/dist/src/cli/commands/test/run.js.map +1 -1
  111. package/dist/src/cli/commands/test/runs.js +5 -5
  112. package/dist/src/cli/commands/test/runs.js.map +1 -1
  113. package/dist/src/cli/commands/test/sync.d.ts +5 -0
  114. package/dist/src/cli/commands/test/sync.d.ts.map +1 -1
  115. package/dist/src/cli/commands/test/sync.js +68 -10
  116. package/dist/src/cli/commands/test/sync.js.map +1 -1
  117. package/dist/src/cli/commands/test/validate.js +2 -2
  118. package/dist/src/cli/commands/test/validate.js.map +1 -1
  119. package/dist/src/cli/commands/update.d.ts.map +1 -1
  120. package/dist/src/cli/commands/update.js +5 -5
  121. package/dist/src/cli/commands/update.js.map +1 -1
  122. package/dist/src/cli/index.js +5 -5
  123. package/dist/src/cli/index.js.map +1 -1
  124. package/dist/src/cli/lib/browser-sessions.js +4 -4
  125. package/dist/src/cli/lib/browser-sessions.js.map +1 -1
  126. package/dist/src/cli/lib/browser.d.ts +1 -1
  127. package/dist/src/cli/lib/browser.d.ts.map +1 -1
  128. package/dist/src/cli/lib/browser.js +2 -2
  129. package/dist/src/cli/lib/browser.js.map +1 -1
  130. package/dist/src/cli/lib/config.js +3 -3
  131. package/dist/src/cli/lib/config.js.map +1 -1
  132. package/dist/src/cli/lib/download.js +6 -6
  133. package/dist/src/cli/lib/download.js.map +1 -1
  134. package/dist/src/cli/lib/id-injector.js +1 -1
  135. package/dist/src/cli/lib/id-injector.js.map +1 -1
  136. package/dist/src/cli/lib/loader.js +2 -2
  137. package/dist/src/cli/lib/loader.js.map +1 -1
  138. package/dist/src/cli/lib/output.js +8 -8
  139. package/dist/src/cli/lib/output.js.map +1 -1
  140. package/dist/src/cli/lib/runner.d.ts +1 -1
  141. package/dist/src/cli/lib/runner.d.ts.map +1 -1
  142. package/dist/src/cli/lib/runner.js +0 -1
  143. package/dist/src/cli/lib/runner.js.map +1 -1
  144. package/dist/src/http-server.d.ts.map +1 -1
  145. package/dist/src/http-server.js +3 -4
  146. package/dist/src/http-server.js.map +1 -1
  147. package/dist/src/server.d.ts +0 -1
  148. package/dist/src/server.d.ts.map +1 -1
  149. package/dist/src/server.js +18 -86
  150. package/dist/src/server.js.map +1 -1
  151. package/dist/src/tunnel-mode.js +2 -2
  152. package/dist/src/tunnel-mode.js.map +1 -1
  153. package/dist/src/types/test-definition.d.ts +18 -6
  154. package/dist/src/types/test-definition.d.ts.map +1 -1
  155. package/dist/src/types/test-definition.js +2 -2
  156. package/dist/src/types.d.ts.map +1 -1
  157. package/dist/src/types.js +5 -12
  158. package/dist/src/types.js.map +1 -1
  159. package/dist/src/utils/package.js +4 -4
  160. package/dist/src/utils/package.js.map +1 -1
  161. package/dist/src/utils/summary.d.ts +1 -1
  162. package/dist/src/utils/summary.d.ts.map +1 -1
  163. package/dist/src/utils/summary.js.map +1 -1
  164. package/lib/api/browser.test.ts +2 -3
  165. package/lib/api/browser.ts +22 -22
  166. package/lib/api/index.test.ts +169 -0
  167. package/lib/api/index.ts +25 -11
  168. package/lib/browser/index.ts +8 -9
  169. package/lib/env/index.ts +4 -4
  170. package/lib/tunnel/index.test.ts +1 -1
  171. package/lib/tunnel/index.ts +6 -6
  172. package/lib/tunnel/integration.test.ts +3 -3
  173. package/package.json +10 -14
@@ -2,27 +2,27 @@
2
2
  * BrowserApiClient - Client for the desplega.ai Browser API (/browsers/v1/)
3
3
  */
4
4
 
5
- import axios from 'axios';
6
5
  import type { AxiosInstance } from 'axios';
6
+ import axios from 'axios';
7
+ import type { ExtendedStep } from '../../src/types/test-definition.js';
7
8
  import { getEnv } from '../env/index.js';
8
9
  import type {
9
- BrowserSession,
10
- BrowserAction,
11
10
  ActionResult,
12
- SnapshotResult,
13
- SnapshotOptions,
14
- UrlResult,
15
11
  BlocksResult,
16
- CreateBrowserSessionOptions,
12
+ BrowserAction,
13
+ BrowserSession,
17
14
  BrowserSessionStatus,
18
- GenerateTestOptions,
19
- GenerateTestResult,
20
15
  ConsoleLogsOptions,
21
16
  ConsoleLogsResult,
17
+ CreateBrowserSessionOptions,
18
+ GenerateTestOptions,
19
+ GenerateTestResult,
22
20
  NetworkLogsOptions,
23
21
  NetworkLogsResult,
22
+ SnapshotOptions,
23
+ SnapshotResult,
24
+ UrlResult,
24
25
  } from './browser-types.js';
25
- import type { ExtendedStep } from '../../src/types/test-definition.js';
26
26
 
27
27
  export class BrowserApiClient {
28
28
  private readonly client: AxiosInstance;
@@ -49,7 +49,7 @@ export class BrowserApiClient {
49
49
 
50
50
  setApiKey(apiKey: string): void {
51
51
  this.apiKey = apiKey;
52
- this.client.defaults.headers['Authorization'] = `Bearer ${apiKey}`;
52
+ this.client.defaults.headers.Authorization = `Bearer ${apiKey}`;
53
53
  }
54
54
 
55
55
  getApiKey(): string | null {
@@ -382,21 +382,21 @@ export class BrowserApiClient {
382
382
 
383
383
  // Re-export types for convenience
384
384
  export type {
385
- BrowserSession,
386
- BrowserAction,
387
385
  ActionResult,
388
- SnapshotResult,
389
- SnapshotOptions,
390
- SnapshotFilterStats,
391
- UrlResult,
392
- CreateBrowserSessionOptions,
386
+ BrowserAction,
387
+ BrowserSession,
393
388
  BrowserSessionStatus,
394
- GenerateTestOptions,
395
- GenerateTestResult,
389
+ ConsoleLogEntry,
396
390
  ConsoleLogsOptions,
397
391
  ConsoleLogsResult,
398
- ConsoleLogEntry,
392
+ CreateBrowserSessionOptions,
393
+ GenerateTestOptions,
394
+ GenerateTestResult,
395
+ NetworkLogEntry,
399
396
  NetworkLogsOptions,
400
397
  NetworkLogsResult,
401
- NetworkLogEntry,
398
+ SnapshotFilterStats,
399
+ SnapshotOptions,
400
+ SnapshotResult,
401
+ UrlResult,
402
402
  } from './browser-types.js';
@@ -0,0 +1,169 @@
1
+ /**
2
+ * ApiClient Unit Tests for importTestDefinition
3
+ */
4
+
5
+ import { beforeEach, describe, expect, it, mock } from 'bun:test';
6
+ import { ApiClient } from './index.js';
7
+
8
+ // Mock axios
9
+ const mockAxiosInstance = {
10
+ get: mock(() => Promise.resolve({ data: {} })),
11
+ post: mock(() => Promise.resolve({ data: {} })),
12
+ delete: mock(() => Promise.resolve({ data: {} })),
13
+ defaults: {
14
+ baseURL: 'https://api.desplega.ai',
15
+ headers: {} as Record<string, string>,
16
+ },
17
+ };
18
+
19
+ mock.module('axios', () => ({
20
+ default: {
21
+ create: () => mockAxiosInstance,
22
+ isAxiosError: (err: unknown) =>
23
+ typeof err === 'object' && err !== null && 'response' in err && 'isAxiosError' in err,
24
+ },
25
+ }));
26
+
27
+ describe('ApiClient', () => {
28
+ let client: ApiClient;
29
+
30
+ beforeEach(() => {
31
+ // Reset mocks
32
+ mockAxiosInstance.get.mockReset();
33
+ mockAxiosInstance.post.mockReset();
34
+ mockAxiosInstance.delete.mockReset();
35
+
36
+ // Clear environment variables
37
+ delete process.env.QA_USE_API_KEY;
38
+ delete process.env.QA_USE_API_URL;
39
+
40
+ client = new ApiClient();
41
+ client.setApiKey('test-key');
42
+ });
43
+
44
+ describe('importTestDefinition', () => {
45
+ const testDef = {
46
+ name: 'Test Definition',
47
+ steps: [{ goto: 'https://example.com' }],
48
+ };
49
+
50
+ it('should pass force option when provided', async () => {
51
+ const mockResult = {
52
+ success: true,
53
+ imported: [{ name: 'Test', id: '123', action: 'created', version_hash: 'abc123' }],
54
+ errors: [],
55
+ };
56
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
57
+
58
+ await client.importTestDefinition([testDef], { upsert: true, force: true });
59
+
60
+ expect(mockAxiosInstance.post).toHaveBeenCalledWith('/vibe-qa/cli/import', {
61
+ test_definitions: [testDef],
62
+ upsert: true,
63
+ dry_run: false,
64
+ force: true,
65
+ });
66
+ });
67
+
68
+ it('should default force to false', async () => {
69
+ const mockResult = {
70
+ success: true,
71
+ imported: [{ name: 'Test', id: '123', action: 'created' }],
72
+ errors: [],
73
+ };
74
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
75
+
76
+ await client.importTestDefinition([testDef], { upsert: true });
77
+
78
+ expect(mockAxiosInstance.post).toHaveBeenCalledWith('/vibe-qa/cli/import', {
79
+ test_definitions: [testDef],
80
+ upsert: true,
81
+ dry_run: false,
82
+ force: false,
83
+ });
84
+ });
85
+
86
+ it('should handle conflict action in response', async () => {
87
+ const mockResult = {
88
+ success: true,
89
+ imported: [
90
+ {
91
+ name: 'Test',
92
+ id: '123',
93
+ action: 'conflict',
94
+ message: 'Remote modified since last sync',
95
+ prev_version_hash: 'old123',
96
+ version_hash: 'new456',
97
+ },
98
+ ],
99
+ errors: [],
100
+ };
101
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
102
+
103
+ const result = await client.importTestDefinition([testDef], { upsert: true });
104
+
105
+ expect(result.success).toBe(true);
106
+ expect(result.imported[0].action).toBe('conflict');
107
+ expect(result.imported[0].message).toBe('Remote modified since last sync');
108
+ expect(result.imported[0].prev_version_hash).toBe('old123');
109
+ expect(result.imported[0].version_hash).toBe('new456');
110
+ });
111
+
112
+ it('should handle unchanged action in response', async () => {
113
+ const mockResult = {
114
+ success: true,
115
+ imported: [
116
+ {
117
+ name: 'Test',
118
+ id: '123',
119
+ action: 'unchanged',
120
+ version_hash: 'same123',
121
+ },
122
+ ],
123
+ errors: [],
124
+ };
125
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
126
+
127
+ const result = await client.importTestDefinition([testDef], { upsert: true });
128
+
129
+ expect(result.success).toBe(true);
130
+ expect(result.imported[0].action).toBe('unchanged');
131
+ });
132
+
133
+ it('should pass dry_run option', async () => {
134
+ const mockResult = {
135
+ success: true,
136
+ imported: [{ name: 'Test', id: '123', action: 'created' }],
137
+ errors: [],
138
+ };
139
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
140
+
141
+ await client.importTestDefinition([testDef], { dry_run: true });
142
+
143
+ expect(mockAxiosInstance.post).toHaveBeenCalledWith('/vibe-qa/cli/import', {
144
+ test_definitions: [testDef],
145
+ upsert: true,
146
+ dry_run: true,
147
+ force: false,
148
+ });
149
+ });
150
+
151
+ it('should use default options when none provided', async () => {
152
+ const mockResult = {
153
+ success: true,
154
+ imported: [],
155
+ errors: [],
156
+ };
157
+ mockAxiosInstance.post.mockResolvedValueOnce({ data: mockResult });
158
+
159
+ await client.importTestDefinition([testDef]);
160
+
161
+ expect(mockAxiosInstance.post).toHaveBeenCalledWith('/vibe-qa/cli/import', {
162
+ test_definitions: [testDef],
163
+ upsert: true,
164
+ dry_run: false,
165
+ force: false,
166
+ });
167
+ });
168
+ });
169
+ });
package/lib/api/index.ts CHANGED
@@ -1,23 +1,23 @@
1
- import axios from 'axios';
2
1
  import type { AxiosInstance, AxiosResponse } from 'axios';
2
+ import axios from 'axios';
3
3
  import 'dotenv/config';
4
- import { getEnv } from '../env/index.js';
4
+ import type { TestDefinition } from '../../src/types/test-definition.js';
5
5
  import type {
6
- TestAgentV2Session,
6
+ IssueReport,
7
7
  IssueType,
8
8
  Severity,
9
- IssueReport,
9
+ TestAgentV2Session,
10
10
  TestCreatorDoneIntent,
11
11
  } from '../../src/types.js';
12
+ import type { BlockSummary, EnhancedTestSummary } from '../../src/utils/summary.js';
12
13
  import {
13
- generateEnhancedTestSummary,
14
+ categorizeIssues,
14
15
  formatEnhancedTestReport,
16
+ generateEnhancedTestSummary,
15
17
  generateIssueStatistics,
16
- categorizeIssues,
17
18
  } from '../../src/utils/summary.js';
18
- import type { EnhancedTestSummary, BlockSummary } from '../../src/utils/summary.js';
19
- import { streamSSE, type SSEEvent } from './sse.js';
20
- import type { TestDefinition } from '../../src/types/test-definition.js';
19
+ import { getEnv } from '../env/index.js';
20
+ import { type SSEEvent, streamSSE } from './sse.js';
21
21
 
22
22
  // Re-export new types for external consumers
23
23
  export type { IssueType, Severity, IssueReport, TestCreatorDoneIntent };
@@ -285,12 +285,16 @@ export interface ValidationResult {
285
285
  export interface ImportOptions {
286
286
  upsert?: boolean;
287
287
  dry_run?: boolean;
288
+ force?: boolean; // Override version conflicts
288
289
  }
289
290
 
290
291
  export interface ImportedTest {
291
292
  name: string;
292
293
  id: string;
293
- action: 'created' | 'updated' | 'skipped';
294
+ action: 'created' | 'updated' | 'skipped' | 'unchanged' | 'conflict';
295
+ message?: string; // Warning/conflict details
296
+ prev_version_hash?: string; // Hash before operation
297
+ version_hash?: string; // Hash after operation
294
298
  }
295
299
 
296
300
  export interface ImportResult {
@@ -331,7 +335,7 @@ export class ApiClient {
331
335
 
332
336
  setApiKey(apiKey: string): void {
333
337
  this.apiKey = apiKey;
334
- this.client.defaults.headers['Authorization'] = `Bearer ${apiKey}`;
338
+ this.client.defaults.headers.Authorization = `Bearer ${apiKey}`;
335
339
  }
336
340
 
337
341
  getApiKey(): string | null {
@@ -936,6 +940,7 @@ export class ApiClient {
936
940
  test_definitions: definitions,
937
941
  upsert: options.upsert ?? true,
938
942
  dry_run: options.dry_run ?? false,
943
+ force: options.force ?? false,
939
944
  });
940
945
 
941
946
  return response.data as ImportResult;
@@ -944,6 +949,15 @@ export class ApiClient {
944
949
  const statusCode = error.response?.status;
945
950
  const errorData = error.response?.data;
946
951
 
952
+ // Handle validation errors (detail is an array of error objects)
953
+ if (Array.isArray(errorData?.detail)) {
954
+ const messages = errorData.detail.map((e: { msg?: string; loc?: string[] }) => {
955
+ const field = e.loc?.slice(2).join('.') || 'unknown';
956
+ return `${field}: ${e.msg || 'validation error'}`;
957
+ });
958
+ throw new Error(`Validation failed:\n ${messages.join('\n ')}`);
959
+ }
960
+
947
961
  throw new Error(
948
962
  errorData?.message ||
949
963
  errorData?.detail ||
@@ -1,10 +1,9 @@
1
+ import { fork } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+ import type { Browser, BrowserServer } from 'playwright';
1
6
  import { chromium } from 'playwright';
2
- import type { BrowserServer, Browser } from 'playwright';
3
- import { fork } from 'child_process';
4
- import path from 'path';
5
- import fs from 'fs';
6
- import { createRequire } from 'module';
7
- import { fileURLToPath } from 'url';
8
7
 
9
8
  export interface BrowserInstallationStatus {
10
9
  installed: boolean;
@@ -90,7 +89,7 @@ export class BrowserManager {
90
89
 
91
90
  try {
92
91
  await session.browserServer.close();
93
- } catch (error) {
92
+ } catch {
94
93
  // Silently handle cleanup errors
95
94
  }
96
95
  }
@@ -102,7 +101,7 @@ export class BrowserManager {
102
101
 
103
102
  try {
104
103
  return await chromium.connect(this.session.wsEndpoint);
105
- } catch (error) {
104
+ } catch {
106
105
  return null;
107
106
  }
108
107
  }
@@ -127,7 +126,7 @@ export class BrowserManager {
127
126
  });
128
127
  await browser.close();
129
128
  return true;
130
- } catch (error) {
129
+ } catch {
131
130
  this.session.isActive = false;
132
131
  return false;
133
132
  }
package/lib/env/index.ts CHANGED
@@ -6,9 +6,9 @@
6
6
  * 2. Config file (~/.qa-use.json) with structure: { "env": { "VAR_NAME": "value" } }
7
7
  */
8
8
 
9
- import { readFileSync, existsSync } from 'fs';
10
- import { join } from 'path';
11
- import { homedir } from 'os';
9
+ import { existsSync, readFileSync } from 'node:fs';
10
+ import { homedir } from 'node:os';
11
+ import { join } from 'node:path';
12
12
 
13
13
  interface QaUseConfig {
14
14
  env?: Record<string, string>;
@@ -133,7 +133,7 @@ export function logConfigSources(): void {
133
133
  const sources: string[] = [];
134
134
 
135
135
  if (apiKey.value) {
136
- const maskedKey = apiKey.value.slice(0, 8) + '...' + apiKey.value.slice(-4);
136
+ const maskedKey = `${apiKey.value.slice(0, 8)}...${apiKey.value.slice(-4)}`;
137
137
  sources.push(` API Key: ${maskedKey} (from ${getSourceDescription(apiKey.source)})`);
138
138
  }
139
139
 
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, beforeEach, mock } from 'bun:test';
1
+ import { beforeEach, describe, expect, it, mock } from 'bun:test';
2
2
  import { TunnelManager } from './index';
3
3
 
4
4
  // Mock the localtunnel module
@@ -1,7 +1,7 @@
1
+ import crypto from 'node:crypto';
2
+ import https from 'node:https';
3
+ import { URL } from 'node:url';
1
4
  import localtunnel from '@desplega.ai/localtunnel';
2
- import { URL } from 'url';
3
- import https from 'https';
4
- import crypto from 'crypto';
5
5
  import { getEnv } from '../env/index.js';
6
6
 
7
7
  export interface TunnelSession {
@@ -124,7 +124,7 @@ export class TunnelManager {
124
124
 
125
125
  try {
126
126
  await session.tunnel.close();
127
- } catch (error) {
127
+ } catch {
128
128
  // Silently handle cleanup errors
129
129
  }
130
130
  }
@@ -155,7 +155,7 @@ export class TunnelManager {
155
155
  clearTimeout(timeout);
156
156
  // 426 = Upgrade Required (expected for WebSocket endpoint)
157
157
  return response.ok || response.status === 426;
158
- } catch (error) {
158
+ } catch {
159
159
  this.session.isActive = false;
160
160
  return false;
161
161
  }
@@ -190,7 +190,7 @@ export class TunnelManager {
190
190
  return (
191
191
  this.session.publicUrl.replace('https://', 'wss://').replace('http://', 'ws://') + wsPath
192
192
  );
193
- } catch (error) {
193
+ } catch {
194
194
  return null;
195
195
  }
196
196
  }
@@ -1,6 +1,6 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
1
+ import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
2
+ import http from 'node:http';
2
3
  import { TunnelManager } from './index';
3
- import http from 'http';
4
4
 
5
5
  describe('TunnelManager Integration Tests', () => {
6
6
  let tunnelManager: TunnelManager;
@@ -12,7 +12,7 @@ describe('TunnelManager Integration Tests', () => {
12
12
 
13
13
  // Create a simple test HTTP server
14
14
  return new Promise<void>((resolve) => {
15
- testServer = http.createServer((req, res) => {
15
+ testServer = http.createServer((_req, res) => {
16
16
  res.writeHead(200, { 'Content-Type': 'text/plain' });
17
17
  res.end('Hello from test server');
18
18
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/qa-use",
3
- "version": "2.4.1",
3
+ "version": "2.5.0",
4
4
  "packageManager": "bun@^1.3.4",
5
5
  "description": "QA automation tool for browser testing with MCP server support",
6
6
  "type": "module",
@@ -26,10 +26,12 @@
26
26
  "start": "bun run build && node dist/src/index.js",
27
27
  "inspector": "bun run build && npx -y @modelcontextprotocol/inspector node dist/src/index.js",
28
28
  "inspector:env": "bun run build && npx dotenv-cli -e .env -- npx -y @modelcontextprotocol/inspector node dist/src/index.js",
29
- "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json}\" \"lib/**/*.{ts,tsx,js,jsx,json}\"",
30
- "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json}\" \"lib/**/*.{ts,tsx,js,jsx,json}\"",
31
- "lint": "eslint \"src/**/*.{ts,tsx,js,jsx}\" \"lib/**/*.{ts,tsx,js,jsx}\"",
32
- "lint:fix": "eslint \"src/**/*.{ts,tsx,js,jsx}\" \"lib/**/*.{ts,tsx,js,jsx}\" --fix",
29
+ "format": "biome format --write src lib",
30
+ "format:check": "biome format src lib",
31
+ "lint": "biome lint src lib && oxlint src lib",
32
+ "lint:fix": "biome lint --write src lib && oxlint src lib --fix",
33
+ "check": "biome check src lib",
34
+ "check:fix": "biome check --write src lib",
33
35
  "typecheck": "tsc --noEmit",
34
36
  "test": "bun test --preload ./test-setup.ts",
35
37
  "test:unit": "node scripts/test-unit.js",
@@ -66,25 +68,19 @@
66
68
  "access": "public"
67
69
  },
68
70
  "devDependencies": {
69
- "@eslint/js": "^9.36.0",
71
+ "@biomejs/biome": "^2.3.13",
70
72
  "@types/express": "^5.0.0",
71
73
  "@types/node": "^24.5.2",
72
74
  "@types/react": "^19.1.14",
73
75
  "@types/ws": "^8.18.1",
74
- "@typescript-eslint/eslint-plugin": "^8.44.1",
75
- "@typescript-eslint/parser": "^8.44.1",
76
76
  "@vercel/node": "^3.2.23",
77
77
  "dotenv-cli": "^7.4.2",
78
- "eslint": "^9.36.0",
79
- "eslint-config-prettier": "^10.1.8",
80
- "eslint-plugin-prettier": "^5.5.4",
81
78
  "ignore": "^7.0.5",
82
79
  "json-schema-to-typescript": "^15.0.4",
83
- "prettier": "^3.6.2",
80
+ "oxlint": "^1.42.0",
84
81
  "ts-node": "^10.9.2",
85
82
  "tsx": "^4.20.6",
86
- "typescript": "^5.9.2",
87
- "typescript-eslint": "^8.44.1"
83
+ "typescript": "^5.9.2"
88
84
  },
89
85
  "dependencies": {
90
86
  "@desplega.ai/localtunnel": "^2.2.0",