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.
@@ -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! 🚀**