testdriverai 7.1.2 → 7.1.4

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 (38) hide show
  1. package/.github/workflows/test-init.yml +145 -0
  2. package/agent/lib/commander.js +2 -2
  3. package/agent/lib/commands.js +10 -5
  4. package/docs/docs.json +29 -84
  5. package/docs/v7/_drafts/migration.mdx +3 -3
  6. package/docs/v7/api/act.mdx +1 -1
  7. package/docs/v7/api/assert.mdx +1 -1
  8. package/docs/v7/api/assertions.mdx +14 -14
  9. package/docs/v7/getting-started/quickstart.mdx +11 -9
  10. package/docs/v7/getting-started/running-and-debugging.mdx +3 -2
  11. package/docs/v7/getting-started/writing-tests.mdx +4 -5
  12. package/docs/v7/overview/readme.mdx +1 -1
  13. package/docs/v7/presets/chrome.mdx +38 -41
  14. package/docs/v7/presets/electron.mdx +107 -100
  15. package/docs/v7/presets/webapp.mdx +40 -43
  16. package/interfaces/cli/commands/init.js +79 -21
  17. package/interfaces/vitest-plugin.mjs +36 -9
  18. package/lib/vitest/hooks.mjs +5 -1
  19. package/manual/test-init-command.js +223 -0
  20. package/package.json +2 -2
  21. package/schema.json +5 -5
  22. package/sdk-log-formatter.js +1 -1
  23. package/sdk.d.ts +8 -8
  24. package/sdk.js +64 -14
  25. package/test/testdriver/exec-output.test.mjs +1 -1
  26. package/test/testdriver/exec-pwsh.test.mjs +1 -1
  27. package/test/testdriver/focus-window.test.mjs +1 -1
  28. package/test/testdriver/hover-image.test.mjs +1 -1
  29. package/test/testdriver/hover-text-with-description.test.mjs +0 -3
  30. package/test/testdriver/match-image.test.mjs +1 -1
  31. package/test/testdriver/scroll-keyboard.test.mjs +1 -1
  32. package/test/testdriver/scroll-until-image.test.mjs +1 -1
  33. package/test/testdriver/scroll-until-text.test.mjs +18 -1
  34. package/test/testdriver/scroll.test.mjs +1 -1
  35. package/test/testdriver/setup/lifecycleHelpers.mjs +105 -5
  36. package/test/testdriver/setup/testHelpers.mjs +6 -2
  37. package/vitest.config.mjs +7 -2
  38. package/test/testdriver/exec-js.test.mjs +0 -43
@@ -16,7 +16,7 @@ The `chrome()` preset automatically sets up a Chrome browser with TestDriver and
16
16
  import { TestDriver } from 'testdriverai/vitest/hooks';
17
17
 
18
18
  test('my test', async (context) => {
19
- const testdriver = TestDriver(context);
19
+ const testdriver = TestDriver(context, { headless: true });
20
20
  await testdriver.provision.chrome({ url: 'https://example.com' });
21
21
  // ...
22
22
  });
@@ -29,12 +29,12 @@ The `chrome()` preset automatically sets up a Chrome browser with TestDriver and
29
29
 
30
30
  ```javascript
31
31
  import { test } from 'vitest';
32
- import { chrome } from 'testdriverai/presets';
32
+ import { TestDriver } from 'testdriverai/vitest/hooks';
33
33
 
34
34
  test('login test', async (context) => {
35
- const { testdriver } = await chrome(context, {
36
- url: 'https://myapp.com/login'
37
- });
35
+ const testdriver = TestDriver(context, { headless: true });
36
+
37
+ await testdriver.provision.chrome({ url: 'https://myapp.com/login' });
38
38
 
39
39
  await testdriver.find('email input').type('user@example.com');
40
40
  await testdriver.find('password input').type('password123');
@@ -98,12 +98,12 @@ chrome(context, options): Promise<ChromeResult>
98
98
 
99
99
  ```javascript
100
100
  import { test } from 'vitest';
101
- import { chrome } from 'testdriverai/presets';
101
+ import { TestDriver } from 'testdriverai/vitest/hooks';
102
102
 
103
103
  test('search functionality', async (context) => {
104
- const { testdriver } = await chrome(context, {
105
- url: 'https://example.com'
106
- });
104
+ const testdriver = TestDriver(context, { headless: true });
105
+
106
+ await testdriver.provision.chrome({ url: 'https://example.com' });
107
107
 
108
108
  await testdriver.find('search input').type('TestDriver');
109
109
  await testdriver.find('search button').click();
@@ -115,13 +115,12 @@ test('search functionality', async (context) => {
115
115
 
116
116
  ```javascript
117
117
  import { test, expect } from 'vitest';
118
- import { chrome } from 'testdriverai/presets';
118
+ import { TestDriver } from 'testdriverai/vitest/hooks';
119
119
 
120
120
  test('checkout flow', async (context) => {
121
- const { testdriver, dashcam } = await chrome(context, {
122
- url: 'https://shop.example.com',
123
- maximized: true
124
- });
121
+ const testdriver = TestDriver(context, { headless: true });
122
+
123
+ await testdriver.provision.chrome({ url: 'https://shop.example.com' });
125
124
 
126
125
  // Add items to cart
127
126
  await testdriver.find('Add to Cart button').click();
@@ -149,22 +148,20 @@ test('checkout flow', async (context) => {
149
148
 
150
149
  ```javascript
151
150
  import { test } from 'vitest';
152
- import { chrome } from 'testdriverai/presets';
151
+ import { TestDriver } from 'testdriverai/vitest/hooks';
153
152
 
154
153
  test('windows chrome test', async (context) => {
155
- const { testdriver } = await chrome(context, {
156
- url: 'https://myapp.com',
157
- os: 'windows'
158
- });
154
+ const testdriver = TestDriver(context, { headless: true, os: 'windows' });
155
+
156
+ await testdriver.provision.chrome({ url: 'https://myapp.com' });
159
157
 
160
158
  await testdriver.find('Start').click();
161
159
  });
162
160
 
163
161
  test('mac chrome test', async (context) => {
164
- const { testdriver } = await chrome(context, {
165
- url: 'https://myapp.com',
166
- os: 'mac'
167
- });
162
+ const testdriver = TestDriver(context, { headless: true, os: 'mac' });
163
+
164
+ await testdriver.provision.chrome({ url: 'https://myapp.com' });
168
165
 
169
166
  await testdriver.find('Start').click();
170
167
  });
@@ -174,13 +171,12 @@ test('mac chrome test', async (context) => {
174
171
 
175
172
  ```javascript
176
173
  import { test } from 'vitest';
177
- import { chrome } from 'testdriverai/presets';
174
+ import { TestDriver } from 'testdriverai/vitest/hooks';
178
175
 
179
176
  test('quick test without recording', async (context) => {
180
- const { testdriver } = await chrome(context, {
181
- url: 'https://example.com',
182
- dashcam: false // Disable Dashcam for faster execution
183
- });
177
+ const testdriver = TestDriver(context, { headless: true, dashcam: false });
178
+
179
+ await testdriver.provision.chrome({ url: 'https://example.com' });
184
180
 
185
181
  await testdriver.find('button').click();
186
182
  });
@@ -190,13 +186,14 @@ test('quick test without recording', async (context) => {
190
186
 
191
187
  ```javascript
192
188
  import { test } from 'vitest';
193
- import { chrome } from 'testdriverai/presets';
189
+ import { TestDriver } from 'testdriverai/vitest/hooks';
194
190
 
195
191
  test('chrome extension', async (context) => {
196
- const { testdriver } = await chrome(context, {
192
+ const testdriver = TestDriver(context, { headless: true });
193
+
194
+ await testdriver.provision.chrome({
197
195
  url: 'chrome://extensions',
198
- guest: false, // Need profile for extensions
199
- maximized: true
196
+ guest: false // Need profile for extensions
200
197
  });
201
198
 
202
199
  await testdriver.find('Developer mode toggle').click();
@@ -226,9 +223,9 @@ At test end:
226
223
 
227
224
  ```javascript
228
225
  test('user registration', async (context) => {
229
- const { testdriver } = await chrome(context, {
230
- url: 'https://myapp.com/register'
231
- });
226
+ const testdriver = TestDriver(context, { headless: true });
227
+
228
+ await testdriver.provision.chrome({ url: 'https://myapp.com/register' });
232
229
 
233
230
  await testdriver.find('email field').type('user@example.com');
234
231
  await testdriver.find('password field').type('SecurePass123!');
@@ -244,9 +241,9 @@ test('user registration', async (context) => {
244
241
 
245
242
  ```javascript
246
243
  test('multi-page navigation', async (context) => {
247
- const { testdriver } = await chrome(context, {
248
- url: 'https://myapp.com'
249
- });
244
+ const testdriver = TestDriver(context, { headless: true });
245
+
246
+ await testdriver.provision.chrome({ url: 'https://myapp.com' });
250
247
 
251
248
  // Navigate through pages
252
249
  await testdriver.find('About link').click();
@@ -265,9 +262,9 @@ test('multi-page navigation', async (context) => {
265
262
  ```javascript
266
263
  test('handles errors gracefully', async (context) => {
267
264
  try {
268
- const { testdriver } = await chrome(context, {
269
- url: 'https://myapp.com'
270
- });
265
+ const testdriver = TestDriver(context, { headless: true });
266
+
267
+ await testdriver.provision.chrome({ url: 'https://myapp.com' });
271
268
 
272
269
  await testdriver.find('non-existent element').click();
273
270
  } catch (error) {
@@ -16,7 +16,7 @@ The `electron()` preset automatically sets up an Electron application with TestD
16
16
  import { TestDriver } from 'testdriverai/vitest/hooks';
17
17
 
18
18
  test('my test', async (context) => {
19
- const testdriver = TestDriver(context);
19
+ const testdriver = TestDriver(context, { headless: true });
20
20
  await testdriver.provision.electron({
21
21
  appPath: './dist/my-app'
22
22
  });
@@ -31,16 +31,16 @@ The `electron()` preset automatically sets up an Electron application with TestD
31
31
 
32
32
  ```javascript
33
33
  import { test } from 'vitest';
34
- import { electron } from 'testdriverai/presets';
34
+ import { TestDriver } from 'testdriverai/vitest/hooks';
35
35
 
36
36
  test('electron app test', async (context) => {
37
- const { app } = await electron(context, {
38
- appPath: './dist/my-app'
39
- });
37
+ const testdriver = TestDriver(context, { headless: true });
40
38
 
41
- await app.find('main window').click();
42
- await app.find('File menu').click();
43
- await app.find('New Document').click();
39
+ await testdriver.provision.electron({ appPath: './dist/my-app' });
40
+
41
+ await testdriver.find('main window').click();
42
+ await testdriver.find('File menu').click();
43
+ await testdriver.find('New Document').click();
44
44
  });
45
45
  ```
46
46
 
@@ -98,15 +98,15 @@ electron(context, options): Promise<ElectronResult>
98
98
 
99
99
  ```javascript
100
100
  import { test } from 'vitest';
101
- import { electron } from 'testdriverai/presets';
101
+ import { TestDriver } from 'testdriverai/vitest/hooks';
102
102
 
103
103
  test('opens main window', async (context) => {
104
- const { app } = await electron(context, {
105
- appPath: './dist/my-electron-app'
106
- });
104
+ const testdriver = TestDriver(context, { headless: true });
107
105
 
108
- await app.assert('Main window is visible');
109
- await app.find('Welcome message').click();
106
+ await testdriver.provision.electron({ appPath: './dist/my-electron-app' });
107
+
108
+ await testdriver.assert('Main window is visible');
109
+ await testdriver.find('Welcome message').click();
110
110
  });
111
111
  ```
112
112
 
@@ -114,10 +114,12 @@ test('opens main window', async (context) => {
114
114
 
115
115
  ```javascript
116
116
  import { test } from 'vitest';
117
- import { electron } from 'testdriverai/presets';
117
+ import { TestDriver } from 'testdriverai/vitest/hooks';
118
118
 
119
119
  test('app with debug mode', async (context) => {
120
- const { app } = await electron(context, {
120
+ const testdriver = TestDriver(context, { headless: true });
121
+
122
+ await testdriver.provision.electron({
121
123
  appPath: './dist/app',
122
124
  args: [
123
125
  '--enable-logging',
@@ -126,8 +128,8 @@ test('app with debug mode', async (context) => {
126
128
  ]
127
129
  });
128
130
 
129
- await app.find('Debug panel').click();
130
- await app.assert('Debug information is visible');
131
+ await testdriver.find('Debug panel').click();
132
+ await testdriver.assert('Debug information is visible');
131
133
  });
132
134
  ```
133
135
 
@@ -135,27 +137,27 @@ test('app with debug mode', async (context) => {
135
137
 
136
138
  ```javascript
137
139
  import { test } from 'vitest';
138
- import { electron } from 'testdriverai/presets';
140
+ import { TestDriver } from 'testdriverai/vitest/hooks';
139
141
 
140
142
  test('file menu operations', async (context) => {
141
- const { app } = await electron(context, {
142
- appPath: './dist/editor-app'
143
- });
143
+ const testdriver = TestDriver(context, { headless: true });
144
+
145
+ await testdriver.provision.electron({ appPath: './dist/editor-app' });
144
146
 
145
147
  // Open File menu
146
- await app.find('File').click();
147
- await app.find('New File').click();
148
+ await testdriver.find('File').click();
149
+ await testdriver.find('New File').click();
148
150
 
149
151
  // Verify new file created
150
- await app.assert('Untitled document is open');
152
+ await testdriver.assert('Untitled document is open');
151
153
 
152
154
  // Save file
153
- await app.find('File').click();
154
- await app.find('Save As').click();
155
- await app.type('test-document.txt');
156
- await app.pressKeys(['enter']);
155
+ await testdriver.find('File').click();
156
+ await testdriver.find('Save As').click();
157
+ await testdriver.type('test-document.txt');
158
+ await testdriver.pressKeys(['enter']);
157
159
 
158
- await app.assert('File saved successfully');
160
+ await testdriver.assert('File saved successfully');
159
161
  });
160
162
  ```
161
163
 
@@ -163,24 +165,26 @@ test('file menu operations', async (context) => {
163
165
 
164
166
  ```javascript
165
167
  import { test } from 'vitest';
166
- import { electron } from 'testdriverai/presets';
168
+ import { TestDriver } from 'testdriverai/vitest/hooks';
167
169
 
168
170
  test('windows electron app', async (context) => {
169
- const { app } = await electron(context, {
170
- appPath: 'C:\\Program Files\\MyApp\\MyApp.exe',
171
- os: 'windows'
171
+ const testdriver = TestDriver(context, { headless: true, os: 'windows' });
172
+
173
+ await testdriver.provision.electron({
174
+ appPath: 'C:\\Program Files\\MyApp\\MyApp.exe'
172
175
  });
173
176
 
174
- await app.find('Start button').click();
177
+ await testdriver.find('Start button').click();
175
178
  });
176
179
 
177
180
  test('mac electron app', async (context) => {
178
- const { app } = await electron(context, {
179
- appPath: '/Applications/MyApp.app/Contents/MacOS/MyApp',
180
- os: 'mac'
181
+ const testdriver = TestDriver(context, { headless: true, os: 'mac' });
182
+
183
+ await testdriver.provision.electron({
184
+ appPath: '/Applications/MyApp.app/Contents/MacOS/MyApp'
181
185
  });
182
186
 
183
- await app.find('Start button').click();
187
+ await testdriver.find('Start button').click();
184
188
  });
185
189
  ```
186
190
 
@@ -188,27 +192,27 @@ test('mac electron app', async (context) => {
188
192
 
189
193
  ```javascript
190
194
  import { test } from 'vitest';
191
- import { electron } from 'testdriverai/presets';
195
+ import { TestDriver } from 'testdriverai/vitest/hooks';
192
196
 
193
197
  test('configure app settings', async (context) => {
194
- const { app } = await electron(context, {
195
- appPath: './dist/my-app'
196
- });
198
+ const testdriver = TestDriver(context, { headless: true });
199
+
200
+ await testdriver.provision.electron({ appPath: './dist/my-app' });
197
201
 
198
202
  // Open preferences
199
- await app.find('Settings').click();
203
+ await testdriver.find('Settings').click();
200
204
 
201
205
  // Change theme
202
- await app.find('Appearance').click();
203
- await app.find('Dark mode toggle').click();
206
+ await testdriver.find('Appearance').click();
207
+ await testdriver.find('Dark mode toggle').click();
204
208
 
205
209
  // Verify change
206
- await app.assert('Dark mode is enabled');
210
+ await testdriver.assert('Dark mode is enabled');
207
211
 
208
212
  // Save settings
209
- await app.find('Save button').click();
213
+ await testdriver.find('Save button').click();
210
214
 
211
- await app.assert('Settings saved');
215
+ await testdriver.assert('Settings saved');
212
216
  });
213
217
  ```
214
218
 
@@ -216,25 +220,25 @@ test('configure app settings', async (context) => {
216
220
 
217
221
  ```javascript
218
222
  import { test } from 'vitest';
219
- import { electron } from 'testdriverai/presets';
223
+ import { TestDriver } from 'testdriverai/vitest/hooks';
220
224
 
221
225
  test('multiple windows', async (context) => {
222
- const { app } = await electron(context, {
223
- appPath: './dist/multi-window-app'
224
- });
226
+ const testdriver = TestDriver(context, { headless: true });
227
+
228
+ await testdriver.provision.electron({ appPath: './dist/multi-window-app' });
225
229
 
226
230
  // Open new window
227
- await app.find('File').click();
228
- await app.find('New Window').click();
231
+ await testdriver.find('File').click();
232
+ await testdriver.find('New Window').click();
229
233
 
230
234
  // Switch between windows
231
- await app.find('second window').click();
232
- await app.assert('Second window is focused');
235
+ await testdriver.find('second window').click();
236
+ await testdriver.assert('Second window is focused');
233
237
 
234
238
  // Close window
235
- await app.find('Close button').click();
239
+ await testdriver.find('Close button').click();
236
240
 
237
- await app.assert('Window closed');
241
+ await testdriver.assert('Window closed');
238
242
  });
239
243
  ```
240
244
 
@@ -242,29 +246,29 @@ test('multiple windows', async (context) => {
242
246
 
243
247
  ```javascript
244
248
  import { test } from 'vitest';
245
- import { electron } from 'testdriverai/presets';
249
+ import { TestDriver } from 'testdriverai/vitest/hooks';
246
250
 
247
251
  test('form submission', async (context) => {
248
- const { app, dashcam } = await electron(context, {
249
- appPath: './dist/form-app'
250
- });
252
+ const testdriver = TestDriver(context, { headless: true });
253
+
254
+ await testdriver.provision.electron({ appPath: './dist/form-app' });
251
255
 
252
256
  // Fill form
253
- await app.find('Name input').type('John Doe');
254
- await app.find('Email input').type('john@example.com');
255
- await app.find('Phone input').type('555-1234');
257
+ await testdriver.find('Name input').type('John Doe');
258
+ await testdriver.find('Email input').type('john@example.com');
259
+ await testdriver.find('Phone input').type('555-1234');
256
260
 
257
261
  // Select dropdown
258
- await app.find('Country dropdown').click();
259
- await app.find('United States').click();
262
+ await testdriver.find('Country dropdown').click();
263
+ await testdriver.find('United States').click();
260
264
 
261
265
  // Check checkbox
262
- await app.find('Terms and conditions').click();
266
+ await testdriver.find('Terms and conditions').click();
263
267
 
264
268
  // Submit
265
- await app.find('Submit button').click();
269
+ await testdriver.find('Submit button').click();
266
270
 
267
- await app.assert('Form submitted successfully');
271
+ await testdriver.assert('Form submitted successfully');
268
272
 
269
273
  // Dashcam captures entire interaction
270
274
  });
@@ -291,15 +295,15 @@ At test end:
291
295
 
292
296
  ```javascript
293
297
  test('ipc events', async (context) => {
294
- const { app } = await electron(context, {
295
- appPath: './dist/ipc-app'
296
- });
298
+ const testdriver = TestDriver(context, { headless: true });
299
+
300
+ await testdriver.provision.electron({ appPath: './dist/ipc-app' });
297
301
 
298
302
  // Trigger IPC event from renderer
299
- await app.find('Send Message button').click();
303
+ await testdriver.find('Send Message button').click();
300
304
 
301
305
  // Verify main process response
302
- await app.assert('Response received from main process');
306
+ await testdriver.assert('Response received from main process');
303
307
  });
304
308
  ```
305
309
 
@@ -307,12 +311,12 @@ test('ipc events', async (context) => {
307
311
 
308
312
  ```javascript
309
313
  test('system notifications', async (context) => {
310
- const { app } = await electron(context, {
311
- appPath: './dist/notification-app'
312
- });
314
+ const testdriver = TestDriver(context, { headless: true });
315
+
316
+ await testdriver.provision.electron({ appPath: './dist/notification-app' });
313
317
 
314
- await app.find('Show Notification').click();
315
- await app.assert('System notification appears');
318
+ await testdriver.find('Show Notification').click();
319
+ await testdriver.assert('System notification appears');
316
320
  });
317
321
  ```
318
322
 
@@ -320,17 +324,17 @@ test('system notifications', async (context) => {
320
324
 
321
325
  ```javascript
322
326
  test('system tray', async (context) => {
323
- const { app } = await electron(context, {
324
- appPath: './dist/tray-app'
325
- });
327
+ const testdriver = TestDriver(context, { headless: true });
328
+
329
+ await testdriver.provision.electron({ appPath: './dist/tray-app' });
326
330
 
327
331
  // Click tray icon
328
- await app.find('app tray icon').click();
332
+ await testdriver.find('app tray icon').click();
329
333
 
330
334
  // Interact with tray menu
331
- await app.find('Show Window').click();
335
+ await testdriver.find('Show Window').click();
332
336
 
333
- await app.assert('Main window appears');
337
+ await testdriver.assert('Main window appears');
334
338
  });
335
339
  ```
336
340
 
@@ -338,17 +342,19 @@ test('system tray', async (context) => {
338
342
 
339
343
  ```javascript
340
344
  test('app update', async (context) => {
341
- const { app } = await electron(context, {
345
+ const testdriver = TestDriver(context, { headless: true });
346
+
347
+ await testdriver.provision.electron({
342
348
  appPath: './dist/updatable-app',
343
349
  args: ['--check-updates']
344
350
  });
345
351
 
346
- await app.find('Check for Updates').click();
347
- await app.assert('Checking for updates message');
352
+ await testdriver.find('Check for Updates').click();
353
+ await testdriver.assert('Checking for updates message');
348
354
 
349
355
  // Assuming update is available
350
- await app.find('Download Update').click();
351
- await app.assert('Update downloaded');
356
+ await testdriver.find('Download Update').click();
357
+ await testdriver.assert('Update downloaded');
352
358
  });
353
359
  ```
354
360
 
@@ -398,9 +404,8 @@ test('using provision', async (context) => {
398
404
  ```javascript
399
405
  test('handles missing app path', async (context) => {
400
406
  try {
401
- const { app } = await electron(context, {
402
- appPath: '/nonexistent/path'
403
- });
407
+ const testdriver = TestDriver(context, { headless: true });
408
+ await testdriver.provision.electron({ appPath: '/nonexistent/path' });
404
409
  } catch (error) {
405
410
  // Cleanup still happens automatically
406
411
  expect(error.message).toContain('appPath');
@@ -408,13 +413,13 @@ test('handles missing app path', async (context) => {
408
413
  });
409
414
 
410
415
  test('handles app crashes', async (context) => {
411
- const { app } = await electron(context, {
412
- appPath: './dist/my-app'
413
- });
416
+ const testdriver = TestDriver(context, { headless: true });
417
+
418
+ await testdriver.provision.electron({ appPath: './dist/my-app' });
414
419
 
415
420
  try {
416
421
  // Trigger something that might crash
417
- await app.find('Crash button').click();
422
+ await testdriver.find('Crash button').click();
418
423
  } catch (error) {
419
424
  // Dashcam still saves replay of crash
420
425
  console.error('App crashed:', error);
@@ -431,7 +436,9 @@ test('handles app crashes', async (context) => {
431
436
 
432
437
  ```javascript
433
438
  test('with debugging enabled', async (context) => {
434
- const { app } = await electron(context, {
439
+ const testdriver = TestDriver(context, { headless: true });
440
+
441
+ await testdriver.provision.electron({
435
442
  appPath: './dist/my-app',
436
443
  args: [
437
444
  '--enable-logging',