testdriverai 7.2.21 → 7.2.23
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/docs/v7/_drafts/plugin-migration.mdx +3 -5
- package/lib/vitest/hooks.mjs +26 -14
- package/package.json +1 -1
- package/test/testdriver/hover-image.test.mjs +19 -1
- package/test/testdriver/hover-text-with-description.test.mjs +19 -1
- package/test/testdriver/match-image.test.mjs +19 -1
- package/test/testdriver/scroll-until-text.test.mjs +19 -1
- package/docs/v7/_drafts/implementation-plan.mdx +0 -994
- package/docs/v7/_drafts/optimal-sdk-design.mdx +0 -1348
- package/docs/v7/_drafts/performance.mdx +0 -517
- package/docs/v7/_drafts/platforms/linux.mdx +0 -308
- package/docs/v7/_drafts/platforms/macos.mdx +0 -433
- package/docs/v7/_drafts/platforms/windows.mdx +0 -430
- package/docs/v7/_drafts/sdk-logging.mdx +0 -222
- package/test/testdriver/setup/globalTeardown.mjs +0 -11
- package/test/testdriver/setup/lifecycleHelpers.mjs +0 -357
- package/test/testdriver/setup/testHelpers.mjs +0 -541
- package/test/testdriver/setup/vitestSetup.mjs +0 -40
|
@@ -1,994 +0,0 @@
|
|
|
1
|
-
# TestDriver SDK Redesign - Implementation Plan
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This document breaks down the implementation of the optimal SDK design into actionable tasks. Work is organized into 4 phases over ~8 weeks, with each phase building on the previous one while maintaining backward compatibility.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Phase 1: Core Foundation (Week 1-2)
|
|
10
|
-
|
|
11
|
-
### Goal
|
|
12
|
-
Extract core components into clean, composable modules without breaking existing functionality.
|
|
13
|
-
|
|
14
|
-
### Tasks
|
|
15
|
-
|
|
16
|
-
#### 1.1 Create Dashcam Class (Priority: HIGH)
|
|
17
|
-
**File**: `lib/core/Dashcam.js` (new)
|
|
18
|
-
|
|
19
|
-
**What to build:**
|
|
20
|
-
```javascript
|
|
21
|
-
class Dashcam {
|
|
22
|
-
constructor(client, options = {}) {
|
|
23
|
-
this.client = client;
|
|
24
|
-
this.apiKey = options.apiKey;
|
|
25
|
-
this.autoStart = options.autoStart ?? false;
|
|
26
|
-
this.logs = options.logs || [];
|
|
27
|
-
this.recording = false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async auth(apiKey) { /* ... */ }
|
|
31
|
-
async addLog(config) { /* ... */ }
|
|
32
|
-
async addFileLog(path, name) { /* ... */ }
|
|
33
|
-
async addApplicationLog(app, name) { /* ... */ }
|
|
34
|
-
async start() { /* ... */ }
|
|
35
|
-
async stop() { /* returns replay URL */ }
|
|
36
|
-
async isRecording() { /* ... */ }
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
**Migration steps:**
|
|
41
|
-
1. Extract logic from `lifecycleHelpers.mjs`:
|
|
42
|
-
- `authDashcam()` → `Dashcam.auth()`
|
|
43
|
-
- `addDashcamLog()` → `Dashcam.addLog()`
|
|
44
|
-
- `startDashcam()` → `Dashcam.start()`
|
|
45
|
-
- `stopDashcam()` → `Dashcam.stop()`
|
|
46
|
-
|
|
47
|
-
2. Keep old helpers as thin wrappers:
|
|
48
|
-
```javascript
|
|
49
|
-
// lifecycleHelpers.mjs
|
|
50
|
-
export async function authDashcam(client, apiKey) {
|
|
51
|
-
const dashcam = new Dashcam(client);
|
|
52
|
-
return dashcam.auth(apiKey);
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
3. Update tests to use new class (optional, gradual)
|
|
57
|
-
|
|
58
|
-
**Validation:**
|
|
59
|
-
- [ ] All existing tests pass with old helpers
|
|
60
|
-
- [ ] New Dashcam class works standalone
|
|
61
|
-
- [ ] Windows and Linux support maintained
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
#### 1.2 Create Helper Functions Module (Priority: LOW)
|
|
66
|
-
**File**: `src/helpers/index.js` (new)
|
|
67
|
-
|
|
68
|
-
**What to build:**
|
|
69
|
-
Start with most useful helpers:
|
|
70
|
-
|
|
71
|
-
```javascript
|
|
72
|
-
// src/helpers/wait.js
|
|
73
|
-
export async function waitForElement(client, description, timeout = 30000) {
|
|
74
|
-
const startTime = Date.now();
|
|
75
|
-
while (Date.now() - startTime < timeout) {
|
|
76
|
-
const el = await client.find(description);
|
|
77
|
-
if (el.found()) return el;
|
|
78
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
79
|
-
}
|
|
80
|
-
throw new Error(`Element not found: ${description}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// src/helpers/retry.js
|
|
84
|
-
export async function retryUntilSuccess(fn, options = {}) {
|
|
85
|
-
const { maxAttempts = 3, delay = 1000 } = options;
|
|
86
|
-
let lastError;
|
|
87
|
-
|
|
88
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
89
|
-
try {
|
|
90
|
-
return await fn();
|
|
91
|
-
} catch (error) {
|
|
92
|
-
lastError = error;
|
|
93
|
-
if (i < maxAttempts - 1) {
|
|
94
|
-
await new Promise(r => setTimeout(r, delay));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
throw lastError;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// src/helpers/forms.js
|
|
102
|
-
export async function fillForm(client, formData) {
|
|
103
|
-
for (const [fieldName, value] of Object.entries(formData)) {
|
|
104
|
-
const field = await client.find(fieldName);
|
|
105
|
-
await field.click();
|
|
106
|
-
await client.type(value);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**Validation:**
|
|
112
|
-
- [ ] Helpers work with TestDriver instance
|
|
113
|
-
- [ ] Can be imported individually
|
|
114
|
-
- [ ] Documentation with examples
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
#### 1.3 Update Package Exports (Priority: HIGH)
|
|
119
|
-
**File**: `package.json`
|
|
120
|
-
|
|
121
|
-
**What to change:**
|
|
122
|
-
```json
|
|
123
|
-
{
|
|
124
|
-
"exports": {
|
|
125
|
-
".": "./sdk.js",
|
|
126
|
-
"./core": "./lib/core/index.js",
|
|
127
|
-
"./helpers": "./src/helpers/index.js",
|
|
128
|
-
"./vitest": "./interfaces/vitest-plugin.mjs"
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
**Create**: `lib/core/index.js`
|
|
134
|
-
```javascript
|
|
135
|
-
export { default as TestDriver } from '../../sdk.js';
|
|
136
|
-
export { Dashcam } from './Dashcam.js';
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**Validation:**
|
|
140
|
-
- [ ] `import TestDriver from 'testdriverai'` works
|
|
141
|
-
- [ ] `import { Dashcam } from 'testdriverai/core'` works
|
|
142
|
-
- [ ] `import { waitForElement } from 'testdriverai/helpers'` works
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## Phase 2: Vitest Plugin Enhancement (Week 3-4)
|
|
147
|
-
|
|
148
|
-
### Goal
|
|
149
|
-
Implement auto-lifecycle mode and useTestDriver hook for cleaner test code.
|
|
150
|
-
|
|
151
|
-
### Tasks
|
|
152
|
-
|
|
153
|
-
#### 2.1 Create useTestDriver Hook (Priority: HIGH)
|
|
154
|
-
**File**: `interfaces/vitest/hooks.mjs` (new)
|
|
155
|
-
|
|
156
|
-
**What to build:**
|
|
157
|
-
```javascript
|
|
158
|
-
import { beforeAll, afterAll } from 'vitest';
|
|
159
|
-
import TestDriver from '../../sdk.js';
|
|
160
|
-
|
|
161
|
-
let clientInstance = null;
|
|
162
|
-
|
|
163
|
-
export function useTestDriver(options = {}) {
|
|
164
|
-
// Return existing instance if already created
|
|
165
|
-
if (clientInstance) return clientInstance;
|
|
166
|
-
|
|
167
|
-
const {
|
|
168
|
-
autoConnect = true,
|
|
169
|
-
autoLaunch = true,
|
|
170
|
-
...clientOptions
|
|
171
|
-
} = options;
|
|
172
|
-
|
|
173
|
-
if (autoConnect) {
|
|
174
|
-
beforeAll(async () => {
|
|
175
|
-
clientInstance = new TestDriver(
|
|
176
|
-
process.env.TD_API_KEY,
|
|
177
|
-
{
|
|
178
|
-
os: process.env.TEST_PLATFORM || 'linux',
|
|
179
|
-
...clientOptions
|
|
180
|
-
}
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
await clientInstance.auth();
|
|
184
|
-
await clientInstance.connect();
|
|
185
|
-
|
|
186
|
-
// Auto-launch if configured and preset available
|
|
187
|
-
if (autoLaunch && globalThis.__testdriverPreset) {
|
|
188
|
-
await globalThis.__testdriverPreset.afterConnect(clientInstance);
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
afterAll(async () => {
|
|
193
|
-
if (clientInstance) {
|
|
194
|
-
await clientInstance.disconnect();
|
|
195
|
-
clientInstance = null;
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return new Proxy({}, {
|
|
201
|
-
get(target, prop) {
|
|
202
|
-
// Return client methods lazily
|
|
203
|
-
if (!clientInstance) {
|
|
204
|
-
throw new Error('TestDriver not connected. Did beforeAll run?');
|
|
205
|
-
}
|
|
206
|
-
return clientInstance[prop];
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**Validation:**
|
|
213
|
-
- [ ] Works in test files with minimal setup
|
|
214
|
-
- [ ] Handles parallel tests correctly
|
|
215
|
-
- [ ] Error messages are helpful
|
|
216
|
-
- [ ] Cleanup happens properly
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
#### 2.2 Create useDashcam Hook (Priority: HIGH)
|
|
221
|
-
**File**: `interfaces/vitest/hooks.mjs` (modify)
|
|
222
|
-
|
|
223
|
-
**What to build:**
|
|
224
|
-
```javascript
|
|
225
|
-
import { beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
|
|
226
|
-
import { Dashcam } from '../../lib/core/Dashcam.js';
|
|
227
|
-
|
|
228
|
-
let dashcamInstance = null;
|
|
229
|
-
|
|
230
|
-
export function useDashcam(options = {}) {
|
|
231
|
-
const {
|
|
232
|
-
scope = 'test', // 'test' | 'file' | 'suite'
|
|
233
|
-
logs = [],
|
|
234
|
-
autoStart = true,
|
|
235
|
-
...dashcamOptions
|
|
236
|
-
} = options;
|
|
237
|
-
|
|
238
|
-
const client = useTestDriver();
|
|
239
|
-
|
|
240
|
-
// Setup based on scope
|
|
241
|
-
if (scope === 'test') {
|
|
242
|
-
beforeEach(async () => {
|
|
243
|
-
if (!dashcamInstance) {
|
|
244
|
-
dashcamInstance = new Dashcam(client, { logs, ...dashcamOptions });
|
|
245
|
-
await dashcamInstance.auth();
|
|
246
|
-
for (const log of logs) {
|
|
247
|
-
await dashcamInstance.addLog(log);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
if (autoStart) {
|
|
251
|
-
await dashcamInstance.start();
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
afterEach(async (context) => {
|
|
256
|
-
const url = await dashcamInstance.stop();
|
|
257
|
-
if (context.task) {
|
|
258
|
-
context.task.meta.testdriverDashcamUrl = url;
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
} else if (scope === 'file') {
|
|
262
|
-
beforeAll(async () => {
|
|
263
|
-
dashcamInstance = new Dashcam(client, { logs, ...dashcamOptions });
|
|
264
|
-
await dashcamInstance.auth();
|
|
265
|
-
for (const log of logs) {
|
|
266
|
-
await dashcamInstance.addLog(log);
|
|
267
|
-
}
|
|
268
|
-
if (autoStart) {
|
|
269
|
-
await dashcamInstance.start();
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
afterAll(async () => {
|
|
274
|
-
const url = await dashcamInstance.stop();
|
|
275
|
-
console.log('📹 Recording:', url);
|
|
276
|
-
dashcamInstance = null;
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return dashcamInstance;
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
**Validation:**
|
|
285
|
-
- [ ] Per-test recording works
|
|
286
|
-
- [ ] Per-file recording works
|
|
287
|
-
- [ ] URLs captured correctly
|
|
288
|
-
- [ ] Cleanup happens at right time
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
#### 2.3 Simplify Plugin Configuration (Priority: MEDIUM)
|
|
293
|
-
**File**: `interfaces/vitest-plugin.mjs` (modify)
|
|
294
|
-
|
|
295
|
-
**What to change:**
|
|
296
|
-
Make plugin accept simplified config:
|
|
297
|
-
|
|
298
|
-
```javascript
|
|
299
|
-
export default function testDriverPlugin(options = {}) {
|
|
300
|
-
const {
|
|
301
|
-
mode = 'auto', // NEW: auto | manual | hybrid
|
|
302
|
-
preset = null, // NEW: preset configuration
|
|
303
|
-
launch = null, // NEW: simple launch config
|
|
304
|
-
dashcam = {},
|
|
305
|
-
...restOptions
|
|
306
|
-
} = options;
|
|
307
|
-
|
|
308
|
-
// Store preset globally for useTestDriver
|
|
309
|
-
if (preset) {
|
|
310
|
-
globalThis.__testdriverPreset = typeof preset === 'string'
|
|
311
|
-
? getBuiltinPreset(preset, launch)
|
|
312
|
-
: preset;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Store dashcam config globally
|
|
316
|
-
globalThis.__testdriverDashcamConfig = dashcam;
|
|
317
|
-
|
|
318
|
-
// Rest of existing plugin logic...
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**Validation:**
|
|
323
|
-
- [ ] Backward compatible with existing config
|
|
324
|
-
- [ ] New options work as expected
|
|
325
|
-
- [ ] Error messages for invalid config
|
|
326
|
-
|
|
327
|
-
---
|
|
328
|
-
|
|
329
|
-
#### 2.4 Update Test Helpers to Use Hooks (Priority: LOW)
|
|
330
|
-
**File**: Create new example files showing migration
|
|
331
|
-
|
|
332
|
-
**What to create:**
|
|
333
|
-
```javascript
|
|
334
|
-
// examples/vitest-migration.md
|
|
335
|
-
# Migration to useTestDriver Hook
|
|
336
|
-
|
|
337
|
-
## Old Way
|
|
338
|
-
```javascript
|
|
339
|
-
import { createTestClient, setupTest, teardownTest } from './testHelpers';
|
|
340
|
-
|
|
341
|
-
beforeEach(async (ctx) => {
|
|
342
|
-
testdriver = createTestClient({ task: ctx.task });
|
|
343
|
-
await setupTest(testdriver);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
afterEach(async (ctx) => {
|
|
347
|
-
await teardownTest(testdriver, { task: ctx.task });
|
|
348
|
-
});
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
## New Way
|
|
352
|
-
```javascript
|
|
353
|
-
import { useTestDriver } from 'testdriverai/vitest';
|
|
354
|
-
|
|
355
|
-
const td = useTestDriver(); // That's it!
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
**Validation:**
|
|
359
|
-
- [ ] Migration guide written
|
|
360
|
-
- [ ] Example tests converted
|
|
361
|
-
- [ ] Both old and new ways work
|
|
362
|
-
|
|
363
|
-
---
|
|
364
|
-
|
|
365
|
-
## Phase 3: Presets System (Week 5-6)
|
|
366
|
-
|
|
367
|
-
### Goal
|
|
368
|
-
Build preset system with at least 2 working presets (Chrome, VSCode).
|
|
369
|
-
|
|
370
|
-
### Tasks
|
|
371
|
-
|
|
372
|
-
#### 3.1 Define Preset Interface (Priority: HIGH)
|
|
373
|
-
**File**: `lib/presets/types.js` (new)
|
|
374
|
-
|
|
375
|
-
**What to build:**
|
|
376
|
-
```javascript
|
|
377
|
-
/**
|
|
378
|
-
* @typedef {Object} ApplicationPreset
|
|
379
|
-
* @property {string} name - Preset name
|
|
380
|
-
* @property {Function} [beforeConnect] - Called before sandbox connects
|
|
381
|
-
* @property {Function} [afterConnect] - Called after sandbox connects
|
|
382
|
-
* @property {Function} [beforeTest] - Called before each test
|
|
383
|
-
* @property {Function} [afterTest] - Called after each test
|
|
384
|
-
* @property {Object} [dashcam] - Dashcam configuration
|
|
385
|
-
* @property {Object} [launch] - Launch configuration
|
|
386
|
-
*/
|
|
387
|
-
|
|
388
|
-
export class PresetBuilder {
|
|
389
|
-
constructor(name) {
|
|
390
|
-
this.preset = { name };
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
beforeConnect(fn) {
|
|
394
|
-
this.preset.beforeConnect = fn;
|
|
395
|
-
return this;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
afterConnect(fn) {
|
|
399
|
-
this.preset.afterConnect = fn;
|
|
400
|
-
return this;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
withDashcam(config) {
|
|
404
|
-
this.preset.dashcam = config;
|
|
405
|
-
return this;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
build() {
|
|
409
|
-
return this.preset;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
export function definePreset(config) {
|
|
414
|
-
return config;
|
|
415
|
-
}
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
**Validation:**
|
|
419
|
-
- [ ] TypeScript types generated
|
|
420
|
-
- [ ] JSDoc comments complete
|
|
421
|
-
- [ ] Interface makes sense for various apps
|
|
422
|
-
|
|
423
|
-
---
|
|
424
|
-
|
|
425
|
-
#### 3.2 Build Chrome Preset (Priority: HIGH)
|
|
426
|
-
**File**: `lib/presets/chrome.js` (new)
|
|
427
|
-
|
|
428
|
-
**What to build:**
|
|
429
|
-
```javascript
|
|
430
|
-
import { definePreset } from './types.js';
|
|
431
|
-
|
|
432
|
-
export function chromePreset(options = {}) {
|
|
433
|
-
const {
|
|
434
|
-
url = 'http://localhost:3000',
|
|
435
|
-
waitFor = null,
|
|
436
|
-
profile = 'guest',
|
|
437
|
-
windowSize = 'maximized',
|
|
438
|
-
logs = false
|
|
439
|
-
} = options;
|
|
440
|
-
|
|
441
|
-
return definePreset({
|
|
442
|
-
name: 'Chrome',
|
|
443
|
-
|
|
444
|
-
async afterConnect(client) {
|
|
445
|
-
const shell = client.os === 'windows' ? 'pwsh' : 'sh';
|
|
446
|
-
|
|
447
|
-
let chromeCmd;
|
|
448
|
-
if (client.os === 'windows') {
|
|
449
|
-
chromeCmd = `Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--${profile}", "${url}"`;
|
|
450
|
-
} else {
|
|
451
|
-
chromeCmd = `google-chrome --start-maximized --${profile} "${url}" >/dev/null 2>&1 &`;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
await client.exec(shell, chromeCmd, 30000);
|
|
455
|
-
await client.wait(3000);
|
|
456
|
-
|
|
457
|
-
if (waitFor) {
|
|
458
|
-
const startTime = Date.now();
|
|
459
|
-
while (Date.now() - startTime < 60000) {
|
|
460
|
-
const el = await client.find(waitFor);
|
|
461
|
-
if (el.found()) break;
|
|
462
|
-
await client.wait(2000);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
|
|
467
|
-
dashcam: logs ? {
|
|
468
|
-
scope: 'test',
|
|
469
|
-
logs: [
|
|
470
|
-
// TODO: How to capture browser console logs?
|
|
471
|
-
// This might need browser extension or special setup
|
|
472
|
-
]
|
|
473
|
-
} : undefined
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
**Validation:**
|
|
479
|
-
- [ ] Works on Linux
|
|
480
|
-
- [ ] Works on Windows
|
|
481
|
-
- [ ] Handles various Chrome options
|
|
482
|
-
- [ ] waitFor works correctly
|
|
483
|
-
|
|
484
|
-
---
|
|
485
|
-
|
|
486
|
-
#### 3.3 Build VSCode Preset (Priority: MEDIUM)
|
|
487
|
-
**File**: `lib/presets/vscode.js` (new)
|
|
488
|
-
|
|
489
|
-
**What to build:**
|
|
490
|
-
```javascript
|
|
491
|
-
import { definePreset } from './types.js';
|
|
492
|
-
|
|
493
|
-
export function vscodePreset(options = {}) {
|
|
494
|
-
const {
|
|
495
|
-
workspace = null,
|
|
496
|
-
extensions = [],
|
|
497
|
-
settings = {},
|
|
498
|
-
logs = { extension: true, console: true }
|
|
499
|
-
} = options;
|
|
500
|
-
|
|
501
|
-
return definePreset({
|
|
502
|
-
name: 'VSCode',
|
|
503
|
-
|
|
504
|
-
async afterConnect(client) {
|
|
505
|
-
// Install extensions
|
|
506
|
-
for (const ext of extensions) {
|
|
507
|
-
await client.exec('sh', `code --install-extension ${ext}`, 60000);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Apply settings
|
|
511
|
-
if (Object.keys(settings).length > 0) {
|
|
512
|
-
const settingsJson = JSON.stringify(settings, null, 2);
|
|
513
|
-
await client.exec('sh', `echo '${settingsJson}' > ~/.config/Code/User/settings.json`);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Launch VSCode
|
|
517
|
-
const launchCmd = workspace
|
|
518
|
-
? `code ${workspace}`
|
|
519
|
-
: 'code';
|
|
520
|
-
await client.exec('sh', launchCmd, 30000);
|
|
521
|
-
await client.wait(5000);
|
|
522
|
-
},
|
|
523
|
-
|
|
524
|
-
dashcam: {
|
|
525
|
-
scope: 'test',
|
|
526
|
-
logs: [
|
|
527
|
-
logs.extension ? {
|
|
528
|
-
type: 'file',
|
|
529
|
-
path: '~/.config/Code/logs/extension.log',
|
|
530
|
-
name: 'Extension Log'
|
|
531
|
-
} : null,
|
|
532
|
-
logs.console ? {
|
|
533
|
-
type: 'file',
|
|
534
|
-
path: '~/.config/Code/logs/console.log',
|
|
535
|
-
name: 'Console Log'
|
|
536
|
-
} : null
|
|
537
|
-
].filter(Boolean)
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
**Validation:**
|
|
544
|
-
- [ ] Installs extensions correctly
|
|
545
|
-
- [ ] Applies settings
|
|
546
|
-
- [ ] Launches workspace
|
|
547
|
-
- [ ] Logs captured properly
|
|
548
|
-
|
|
549
|
-
---
|
|
550
|
-
|
|
551
|
-
#### 3.4 Create Preset Registry (Priority: LOW)
|
|
552
|
-
**File**: `lib/presets/index.js` (new)
|
|
553
|
-
|
|
554
|
-
**What to build:**
|
|
555
|
-
```javascript
|
|
556
|
-
import { chromePreset } from './chrome.js';
|
|
557
|
-
import { vscodePreset } from './vscode.js';
|
|
558
|
-
|
|
559
|
-
const BUILTIN_PRESETS = {
|
|
560
|
-
chrome: chromePreset,
|
|
561
|
-
vscode: vscodePreset
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
export function getBuiltinPreset(name, options = {}) {
|
|
565
|
-
const presetFn = BUILTIN_PRESETS[name];
|
|
566
|
-
if (!presetFn) {
|
|
567
|
-
throw new Error(`Unknown preset: ${name}. Available: ${Object.keys(BUILTIN_PRESETS).join(', ')}`);
|
|
568
|
-
}
|
|
569
|
-
return presetFn(options);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
export { chromePreset, vscodePreset };
|
|
573
|
-
export { definePreset, PresetBuilder } from './types.js';
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
**Validation:**
|
|
577
|
-
- [ ] Presets can be imported individually
|
|
578
|
-
- [ ] Registry lookup works
|
|
579
|
-
- [ ] Good error messages
|
|
580
|
-
|
|
581
|
-
---
|
|
582
|
-
|
|
583
|
-
## Phase 4: DX Polish & Documentation (Week 7-8)
|
|
584
|
-
|
|
585
|
-
### Goal
|
|
586
|
-
Polish developer experience, add TypeScript definitions, write comprehensive docs.
|
|
587
|
-
|
|
588
|
-
### Tasks
|
|
589
|
-
|
|
590
|
-
#### 4.1 TypeScript Definitions (Priority: HIGH)
|
|
591
|
-
**File**: `types/index.d.ts` (new)
|
|
592
|
-
|
|
593
|
-
**What to build:**
|
|
594
|
-
```typescript
|
|
595
|
-
// Core SDK
|
|
596
|
-
export class TestDriver {
|
|
597
|
-
constructor(apiKey?: string, options?: TestDriverOptions);
|
|
598
|
-
auth(): Promise<void>;
|
|
599
|
-
connect(options?: ConnectOptions): Promise<SandboxInstance>;
|
|
600
|
-
disconnect(): Promise<void>;
|
|
601
|
-
find(description: string): Promise<Element>;
|
|
602
|
-
findAll(description: string): Promise<Element[]>;
|
|
603
|
-
// ... all other methods
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
export interface TestDriverOptions {
|
|
607
|
-
apiKey?: string;
|
|
608
|
-
apiRoot?: string;
|
|
609
|
-
os?: 'linux' | 'windows' | 'mac';
|
|
610
|
-
resolution?: string;
|
|
611
|
-
cache?: boolean;
|
|
612
|
-
analytics?: boolean;
|
|
613
|
-
sandbox?: SandboxOptions;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Dashcam
|
|
617
|
-
export class Dashcam {
|
|
618
|
-
constructor(client: TestDriver, options?: DashcamOptions);
|
|
619
|
-
auth(apiKey?: string): Promise<void>;
|
|
620
|
-
addLog(config: LogConfig): Promise<void>;
|
|
621
|
-
start(): Promise<void>;
|
|
622
|
-
stop(): Promise<string | null>;
|
|
623
|
-
isRecording(): Promise<boolean>;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
export interface DashcamOptions {
|
|
627
|
-
apiKey?: string;
|
|
628
|
-
autoStart?: boolean;
|
|
629
|
-
logs?: LogConfig[];
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
export interface LogConfig {
|
|
633
|
-
name: string;
|
|
634
|
-
type: 'file' | 'stdout' | 'application';
|
|
635
|
-
path?: string;
|
|
636
|
-
application?: string;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Vitest Plugin
|
|
640
|
-
export interface VitestPluginOptions {
|
|
641
|
-
apiKey?: string;
|
|
642
|
-
apiRoot?: string;
|
|
643
|
-
mode?: 'auto' | 'manual' | 'hybrid';
|
|
644
|
-
preset?: string | ApplicationPreset;
|
|
645
|
-
launch?: LaunchConfig;
|
|
646
|
-
dashcam?: DashcamConfig;
|
|
647
|
-
os?: 'linux' | 'windows' | 'mac';
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
export function useTestDriver(options?: UseTestDriverOptions): TestDriver;
|
|
651
|
-
export function useDashcam(options?: UseDashcamOptions): Dashcam;
|
|
652
|
-
|
|
653
|
-
// Presets
|
|
654
|
-
export interface ApplicationPreset {
|
|
655
|
-
name: string;
|
|
656
|
-
beforeConnect?: (client: TestDriver) => Promise<void>;
|
|
657
|
-
afterConnect?: (client: TestDriver) => Promise<void>;
|
|
658
|
-
beforeTest?: (client: TestDriver) => Promise<void>;
|
|
659
|
-
afterTest?: (client: TestDriver) => Promise<void>;
|
|
660
|
-
dashcam?: DashcamConfig;
|
|
661
|
-
launch?: LaunchConfig;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
export function chromePreset(options?: ChromePresetOptions): ApplicationPreset;
|
|
665
|
-
export function vscodePreset(options?: VSCodePresetOptions): ApplicationPreset;
|
|
666
|
-
export function definePreset(config: ApplicationPreset): ApplicationPreset;
|
|
667
|
-
|
|
668
|
-
// Helpers
|
|
669
|
-
export function waitForElement(
|
|
670
|
-
client: TestDriver,
|
|
671
|
-
description: string,
|
|
672
|
-
timeout?: number
|
|
673
|
-
): Promise<Element>;
|
|
674
|
-
|
|
675
|
-
export function retryUntilSuccess<T>(
|
|
676
|
-
fn: () => Promise<T>,
|
|
677
|
-
options?: RetryOptions
|
|
678
|
-
): Promise<T>;
|
|
679
|
-
|
|
680
|
-
export function fillForm(
|
|
681
|
-
client: TestDriver,
|
|
682
|
-
formData: Record<string, string>
|
|
683
|
-
): Promise<void>;
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
**Validation:**
|
|
687
|
-
- [ ] All exports have types
|
|
688
|
-
- [ ] JSDoc and TypeScript match
|
|
689
|
-
- [ ] IntelliSense works in VSCode
|
|
690
|
-
- [ ] No `any` types unless necessary
|
|
691
|
-
|
|
692
|
-
---
|
|
693
|
-
|
|
694
|
-
#### 4.2 Create Examples Repository (Priority: MEDIUM)
|
|
695
|
-
**Directory**: `examples/` (new structure)
|
|
696
|
-
|
|
697
|
-
**What to create:**
|
|
698
|
-
```
|
|
699
|
-
examples/
|
|
700
|
-
├── quickstart/
|
|
701
|
-
│ ├── basic-web-test.test.js
|
|
702
|
-
│ ├── with-dashcam.test.js
|
|
703
|
-
│ └── README.md
|
|
704
|
-
├── advanced/
|
|
705
|
-
│ ├── custom-app.test.js
|
|
706
|
-
│ ├── multi-app-integration.test.js
|
|
707
|
-
│ ├── manual-lifecycle.test.js
|
|
708
|
-
│ └── README.md
|
|
709
|
-
├── custom-preset/
|
|
710
|
-
│ ├── salesforce-preset.js
|
|
711
|
-
│ ├── slack-preset.js
|
|
712
|
-
│ ├── using-custom-preset.test.js
|
|
713
|
-
│ └── README.md
|
|
714
|
-
└── migration/
|
|
715
|
-
├── before-helpers.test.js
|
|
716
|
-
├── after-hooks.test.js
|
|
717
|
-
└── README.md
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
**Each example should:**
|
|
721
|
-
- [ ] Work out of the box
|
|
722
|
-
- [ ] Have clear comments
|
|
723
|
-
- [ ] Show common patterns
|
|
724
|
-
- [ ] Include README with context
|
|
725
|
-
|
|
726
|
-
---
|
|
727
|
-
|
|
728
|
-
#### 4.3 Write Comprehensive Documentation (Priority: HIGH)
|
|
729
|
-
**Files**: Multiple new docs
|
|
730
|
-
|
|
731
|
-
**What to create:**
|
|
732
|
-
|
|
733
|
-
1. **Getting Started Guide** (`docs/getting-started.md`)
|
|
734
|
-
- Installation
|
|
735
|
-
- First test in 5 minutes
|
|
736
|
-
- Quick wins with auto mode
|
|
737
|
-
|
|
738
|
-
2. **Vitest Plugin Guide** (`docs/vitest-plugin.md`)
|
|
739
|
-
- Configuration options
|
|
740
|
-
- Lifecycle modes explained
|
|
741
|
-
- useTestDriver and useDashcam hooks
|
|
742
|
-
- Best practices
|
|
743
|
-
|
|
744
|
-
3. **Presets Guide** (`docs/presets.md`)
|
|
745
|
-
- Using built-in presets
|
|
746
|
-
- Creating custom presets
|
|
747
|
-
- Preset API reference
|
|
748
|
-
- Common patterns
|
|
749
|
-
|
|
750
|
-
4. **API Reference** (`docs/api-reference.md`)
|
|
751
|
-
- TestDriver class
|
|
752
|
-
- Dashcam class
|
|
753
|
-
- Element class
|
|
754
|
-
- Helper functions
|
|
755
|
-
- All methods documented
|
|
756
|
-
|
|
757
|
-
5. **Migration Guide** (`docs/migration-guide.md`)
|
|
758
|
-
- From testHelpers to hooks
|
|
759
|
-
- Timeline and deprecation plan
|
|
760
|
-
- Side-by-side examples
|
|
761
|
-
- Troubleshooting
|
|
762
|
-
|
|
763
|
-
**Validation:**
|
|
764
|
-
- [ ] All docs reviewed for clarity
|
|
765
|
-
- [ ] Code examples tested
|
|
766
|
-
- [ ] Links work
|
|
767
|
-
- [ ] Table of contents generated
|
|
768
|
-
|
|
769
|
-
---
|
|
770
|
-
|
|
771
|
-
#### 4.4 Improve Error Messages (Priority: MEDIUM)
|
|
772
|
-
**Files**: Various SDK files
|
|
773
|
-
|
|
774
|
-
**What to improve:**
|
|
775
|
-
|
|
776
|
-
1. **Better context in errors:**
|
|
777
|
-
```javascript
|
|
778
|
-
// Before
|
|
779
|
-
throw new Error('Not connected');
|
|
780
|
-
|
|
781
|
-
// After
|
|
782
|
-
throw new Error(
|
|
783
|
-
'TestDriver client not connected. ' +
|
|
784
|
-
'Did you call await client.auth() and await client.connect()? ' +
|
|
785
|
-
'Or if using useTestDriver(), did the beforeAll hook run?'
|
|
786
|
-
);
|
|
787
|
-
```
|
|
788
|
-
|
|
789
|
-
2. **Validation errors:**
|
|
790
|
-
```javascript
|
|
791
|
-
// In plugin config
|
|
792
|
-
if (options.mode && !['auto', 'manual', 'hybrid'].includes(options.mode)) {
|
|
793
|
-
throw new Error(
|
|
794
|
-
`Invalid mode: "${options.mode}". ` +
|
|
795
|
-
`Expected one of: auto, manual, hybrid`
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
3. **Helpful preset errors:**
|
|
801
|
-
```javascript
|
|
802
|
-
if (!preset && options.launch) {
|
|
803
|
-
console.warn(
|
|
804
|
-
'Warning: launch configuration provided but no preset specified. ' +
|
|
805
|
-
'Launch config will be ignored. Did you mean to add preset: "chrome"?'
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
**Validation:**
|
|
811
|
-
- [ ] Common mistakes have helpful errors
|
|
812
|
-
- [ ] Error messages tested manually
|
|
813
|
-
- [ ] Include suggestions for fixes
|
|
814
|
-
|
|
815
|
-
---
|
|
816
|
-
|
|
817
|
-
#### 4.5 Create CLI for Preset Generation (Priority: LOW)
|
|
818
|
-
**File**: `bin/create-preset.js` (new)
|
|
819
|
-
|
|
820
|
-
**What to build:**
|
|
821
|
-
```javascript
|
|
822
|
-
#!/usr/bin/env node
|
|
823
|
-
|
|
824
|
-
// Interactive CLI to scaffold a new preset
|
|
825
|
-
|
|
826
|
-
import inquirer from 'inquirer';
|
|
827
|
-
import fs from 'fs';
|
|
828
|
-
|
|
829
|
-
const answers = await inquirer.prompt([
|
|
830
|
-
{
|
|
831
|
-
type: 'input',
|
|
832
|
-
name: 'name',
|
|
833
|
-
message: 'Preset name (e.g., "slack", "figma"):',
|
|
834
|
-
},
|
|
835
|
-
{
|
|
836
|
-
type: 'input',
|
|
837
|
-
name: 'application',
|
|
838
|
-
message: 'Application to launch:',
|
|
839
|
-
},
|
|
840
|
-
{
|
|
841
|
-
type: 'confirm',
|
|
842
|
-
name: 'needsLogs',
|
|
843
|
-
message: 'Does this app need log tracking?',
|
|
844
|
-
},
|
|
845
|
-
// ... more questions
|
|
846
|
-
]);
|
|
847
|
-
|
|
848
|
-
// Generate preset file from template
|
|
849
|
-
const presetCode = generatePresetTemplate(answers);
|
|
850
|
-
fs.writeFileSync(`./presets/${answers.name}.js`, presetCode);
|
|
851
|
-
|
|
852
|
-
console.log(`✅ Preset created at ./presets/${answers.name}.js`);
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
**Validation:**
|
|
856
|
-
- [ ] Generates working preset
|
|
857
|
-
- [ ] Includes helpful comments
|
|
858
|
-
- [ ] Interactive and user-friendly
|
|
859
|
-
|
|
860
|
-
---
|
|
861
|
-
|
|
862
|
-
## Testing Strategy
|
|
863
|
-
|
|
864
|
-
### For Each Phase
|
|
865
|
-
|
|
866
|
-
1. **Unit Tests**
|
|
867
|
-
- Test each class/function in isolation
|
|
868
|
-
- Mock TestDriver instance where needed
|
|
869
|
-
- Cover edge cases
|
|
870
|
-
|
|
871
|
-
2. **Integration Tests**
|
|
872
|
-
- Test components working together
|
|
873
|
-
- Use actual TestDriver sandbox (on CI)
|
|
874
|
-
- Cover common workflows
|
|
875
|
-
|
|
876
|
-
3. **Backward Compatibility**
|
|
877
|
-
- Keep all existing tests passing
|
|
878
|
-
- Old helpers still work
|
|
879
|
-
- Gradual migration possible
|
|
880
|
-
|
|
881
|
-
### Test Coverage Goals
|
|
882
|
-
|
|
883
|
-
- Core SDK classes: 80%+
|
|
884
|
-
- Helpers: 90%+ (they're simple)
|
|
885
|
-
- Presets: Manual testing + smoke tests
|
|
886
|
-
- Plugin hooks: Integration tests
|
|
887
|
-
|
|
888
|
-
---
|
|
889
|
-
|
|
890
|
-
## Migration & Rollout Strategy
|
|
891
|
-
|
|
892
|
-
### Phase 1-2: Soft Launch
|
|
893
|
-
- New features available but not documented prominently
|
|
894
|
-
- Internal testing on acceptance-sdk tests
|
|
895
|
-
- Gather feedback
|
|
896
|
-
|
|
897
|
-
### Phase 3: Beta Release
|
|
898
|
-
- Announce new APIs in Discord
|
|
899
|
-
- Update main docs to show new way
|
|
900
|
-
- Mark old helpers as "legacy" in docs
|
|
901
|
-
- Both ways fully supported
|
|
902
|
-
|
|
903
|
-
### Phase 4: v8.0 Release
|
|
904
|
-
- New APIs are default in docs
|
|
905
|
-
- Old APIs show deprecation warnings
|
|
906
|
-
- Migration guide complete
|
|
907
|
-
- Example repository published
|
|
908
|
-
|
|
909
|
-
### Post-Launch (3-6 months)
|
|
910
|
-
- Collect feedback and iterate
|
|
911
|
-
- Build more presets based on demand
|
|
912
|
-
- Add more helpers based on patterns
|
|
913
|
-
- Consider removing old APIs in v9.0
|
|
914
|
-
|
|
915
|
-
---
|
|
916
|
-
|
|
917
|
-
## Success Metrics
|
|
918
|
-
|
|
919
|
-
### Developer Experience
|
|
920
|
-
- [ ] Time to first test: < 5 minutes (down from ~15)
|
|
921
|
-
- [ ] Lines of boilerplate code: < 5 (down from ~50)
|
|
922
|
-
- [ ] Support questions about setup: -50%
|
|
923
|
-
|
|
924
|
-
### Code Quality
|
|
925
|
-
- [ ] Test coverage maintained at 70%+
|
|
926
|
-
- [ ] All phases completed on schedule
|
|
927
|
-
- [ ] No regressions in existing functionality
|
|
928
|
-
|
|
929
|
-
### Adoption
|
|
930
|
-
- [ ] 50% of new tests use new APIs within 3 months
|
|
931
|
-
- [ ] 3+ community presets created
|
|
932
|
-
- [ ] Positive feedback from early adopters
|
|
933
|
-
|
|
934
|
-
---
|
|
935
|
-
|
|
936
|
-
## Risk Mitigation
|
|
937
|
-
|
|
938
|
-
### Risk: Breaking Changes
|
|
939
|
-
- **Mitigation**: Maintain old APIs, extensive testing, gradual rollout
|
|
940
|
-
|
|
941
|
-
### Risk: Over-Abstraction
|
|
942
|
-
- **Mitigation**: Always expose core SDK, thin wrappers only, escape hatches
|
|
943
|
-
|
|
944
|
-
### Risk: Preset Complexity
|
|
945
|
-
- **Mitigation**: Start with 2 simple presets, iterate based on feedback
|
|
946
|
-
|
|
947
|
-
### Risk: Timeline Slip
|
|
948
|
-
- **Mitigation**: Phase 1-2 are MVP, Phase 3-4 can extend if needed
|
|
949
|
-
|
|
950
|
-
---
|
|
951
|
-
|
|
952
|
-
## Open Questions
|
|
953
|
-
|
|
954
|
-
1. **Dashcam browser console logs**: How do we capture Chrome console logs for the Chrome preset?
|
|
955
|
-
- Option A: Browser extension
|
|
956
|
-
- Option B: DevTools protocol
|
|
957
|
-
- Option C: File-based logging only
|
|
958
|
-
|
|
959
|
-
2. **Preset discovery**: Should we have a preset registry/marketplace?
|
|
960
|
-
- Option A: Just document how to create and share
|
|
961
|
-
- Option B: NPM packages (e.g., `@testdriver/preset-salesforce`)
|
|
962
|
-
- Option C: Built-in registry with community submissions
|
|
963
|
-
|
|
964
|
-
3. **TypeScript migration**: Should we migrate entire codebase to TypeScript?
|
|
965
|
-
- Option A: Just type definitions (.d.ts)
|
|
966
|
-
- Option B: Gradual migration, new code in TS
|
|
967
|
-
- Option C: Full rewrite in TS (v9.0)
|
|
968
|
-
|
|
969
|
-
4. **Other test frameworks**: Should we support Jest, Mocha, etc.?
|
|
970
|
-
- Option A: Vitest only (current plan)
|
|
971
|
-
- Option B: Framework-agnostic helpers
|
|
972
|
-
- Option C: Plugins for each framework
|
|
973
|
-
|
|
974
|
-
---
|
|
975
|
-
|
|
976
|
-
## Next Steps
|
|
977
|
-
|
|
978
|
-
1. **Immediate (This Week)**
|
|
979
|
-
- [ ] Review this plan with team
|
|
980
|
-
- [ ] Decide on open questions
|
|
981
|
-
- [ ] Set up project board with tasks
|
|
982
|
-
- [ ] Create Phase 1 branch
|
|
983
|
-
|
|
984
|
-
2. **Week 1**
|
|
985
|
-
- [ ] Start Phase 1.1 (Dashcam class)
|
|
986
|
-
- [ ] Draft TypeScript types alongside implementation
|
|
987
|
-
- [ ] Write tests for Dashcam class
|
|
988
|
-
|
|
989
|
-
3. **Week 2**
|
|
990
|
-
- [ ] Complete Phase 1
|
|
991
|
-
- [ ] Start Phase 2.1 (useTestDriver hook)
|
|
992
|
-
- [ ] Begin migration of one test file as proof of concept
|
|
993
|
-
|
|
994
|
-
**Let's build this! 🚀**
|