@assertrx/dino 1.0.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 (89) hide show
  1. package/README.md +293 -0
  2. package/extensions/dino.ts +70 -0
  3. package/extensions/mabl-mcp.ts +306 -0
  4. package/extensions/playwright-mcp.ts +142 -0
  5. package/package.json +47 -0
  6. package/prompts/create-pr.md +110 -0
  7. package/prompts/generate-bdd-from-url.md +65 -0
  8. package/prompts/generate-pom-from-url-cli.md +48 -0
  9. package/prompts/generate-pom-from-url.md +45 -0
  10. package/prompts/generate-spec-from-feature.md +64 -0
  11. package/prompts/heal-playwright-failures.md +106 -0
  12. package/prompts/setup-playwright.md +81 -0
  13. package/scripts/start-dino.cmd +8 -0
  14. package/scripts/start-dino.mjs +226 -0
  15. package/scripts/start-dino.ps1 +9 -0
  16. package/scripts/start-dino.sh +10 -0
  17. package/skills/acli/SKILL.md +292 -0
  18. package/skills/bdd-gherkin/SKILL.md +552 -0
  19. package/skills/build-review-interface/SKILL.md +96 -0
  20. package/skills/error-analysis/SKILL.md +164 -0
  21. package/skills/eval-audit/SKILL.md +183 -0
  22. package/skills/evaluate-rag/SKILL.md +177 -0
  23. package/skills/generate-synthetic-data/SKILL.md +131 -0
  24. package/skills/mabl-mcp/SKILL.md +79 -0
  25. package/skills/playwright-cli/SKILL.md +246 -0
  26. package/skills/playwright-cli/advanced-workflows.md +679 -0
  27. package/skills/playwright-cli/core-commands.md +375 -0
  28. package/skills/playwright-cli/device-emulation.md +475 -0
  29. package/skills/playwright-cli/request-mocking.md +373 -0
  30. package/skills/playwright-cli/running-custom-code.md +553 -0
  31. package/skills/playwright-cli/screenshots-and-media.md +457 -0
  32. package/skills/playwright-cli/session-management.md +425 -0
  33. package/skills/playwright-cli/storage-and-auth.md +487 -0
  34. package/skills/playwright-cli/test-generation.md +336 -0
  35. package/skills/playwright-cli/tracing-and-debugging.md +501 -0
  36. package/skills/playwright-core/SKILL.md +101 -0
  37. package/skills/playwright-core/accessibility.md +1517 -0
  38. package/skills/playwright-core/angular.md +1086 -0
  39. package/skills/playwright-core/api-testing.md +1623 -0
  40. package/skills/playwright-core/assertions-and-waiting.md +703 -0
  41. package/skills/playwright-core/auth-flows.md +1081 -0
  42. package/skills/playwright-core/authentication.md +1409 -0
  43. package/skills/playwright-core/browser-apis.md +673 -0
  44. package/skills/playwright-core/browser-extensions.md +319 -0
  45. package/skills/playwright-core/canvas-and-webgl.md +494 -0
  46. package/skills/playwright-core/clock-and-time-mocking.md +427 -0
  47. package/skills/playwright-core/common-pitfalls.md +1318 -0
  48. package/skills/playwright-core/component-testing.md +1179 -0
  49. package/skills/playwright-core/configuration.md +731 -0
  50. package/skills/playwright-core/crud-testing.md +945 -0
  51. package/skills/playwright-core/debugging.md +827 -0
  52. package/skills/playwright-core/drag-and-drop.md +992 -0
  53. package/skills/playwright-core/electron-testing.md +622 -0
  54. package/skills/playwright-core/error-and-edge-cases.md +1181 -0
  55. package/skills/playwright-core/error-index.md +1903 -0
  56. package/skills/playwright-core/file-operations.md +749 -0
  57. package/skills/playwright-core/file-upload-download.md +1012 -0
  58. package/skills/playwright-core/fixtures-and-hooks.md +1072 -0
  59. package/skills/playwright-core/flaky-tests.md +883 -0
  60. package/skills/playwright-core/forms-and-validation.md +1056 -0
  61. package/skills/playwright-core/i18n-and-localization.md +622 -0
  62. package/skills/playwright-core/iframes-and-shadow-dom.md +488 -0
  63. package/skills/playwright-core/locator-strategy.md +588 -0
  64. package/skills/playwright-core/locators.md +743 -0
  65. package/skills/playwright-core/mobile-and-responsive.md +1669 -0
  66. package/skills/playwright-core/multi-context-and-popups.md +581 -0
  67. package/skills/playwright-core/multi-user-and-collaboration.md +477 -0
  68. package/skills/playwright-core/network-mocking.md +1395 -0
  69. package/skills/playwright-core/nextjs.md +1013 -0
  70. package/skills/playwright-core/performance-testing.md +608 -0
  71. package/skills/playwright-core/react.md +1082 -0
  72. package/skills/playwright-core/search-and-filter.md +1366 -0
  73. package/skills/playwright-core/security-testing.md +624 -0
  74. package/skills/playwright-core/service-workers-and-pwa.md +524 -0
  75. package/skills/playwright-core/test-architecture.md +569 -0
  76. package/skills/playwright-core/test-data-management.md +1209 -0
  77. package/skills/playwright-core/test-organization.md +947 -0
  78. package/skills/playwright-core/third-party-integrations.md +754 -0
  79. package/skills/playwright-core/trace-analysis.md +161 -0
  80. package/skills/playwright-core/visual-regression.md +1027 -0
  81. package/skills/playwright-core/vue.md +1162 -0
  82. package/skills/playwright-core/websockets-and-realtime.md +623 -0
  83. package/skills/playwright-core/when-to-mock.md +827 -0
  84. package/skills/playwright-pom/SKILL.md +94 -0
  85. package/skills/playwright-pom/page-object-model.md +921 -0
  86. package/skills/playwright-pom/pom-vs-fixtures-vs-helpers.md +937 -0
  87. package/skills/validate-evaluator/SKILL.md +215 -0
  88. package/skills/write-judge-prompt/SKILL.md +144 -0
  89. package/themes/dino.json +66 -0
@@ -0,0 +1,622 @@
1
+ # Electron Testing
2
+
3
+ > **When to use**: When your application is an Electron desktop app and you need end-to-end tests covering the renderer process, main process, IPC communication, native dialogs, system tray, and multi-window workflows.
4
+ > **Prerequisites**: [core/configuration.md](configuration.md), [core/fixtures-and-hooks.md](fixtures-and-hooks.md)
5
+
6
+ ## Quick Reference
7
+
8
+ ```typescript
9
+ import { _electron as electron } from 'playwright';
10
+
11
+ // Launch the Electron app
12
+ const app = await electron.launch({ args: ['./main.js'] });
13
+
14
+ // Get the first window (renderer process)
15
+ const window = await app.firstWindow();
16
+
17
+ // Access the main process for evaluation
18
+ const appPath = await app.evaluate(async ({ app }) => {
19
+ return app.getPath('userData');
20
+ });
21
+
22
+ // Close the app
23
+ await app.close();
24
+ ```
25
+
26
+ ## Patterns
27
+
28
+ ### Basic Electron App Setup
29
+
30
+ **Use when**: Starting to test an Electron app with Playwright for the first time.
31
+ **Avoid when**: Your app is a web app, not an Electron app.
32
+
33
+ **TypeScript**
34
+ ```typescript
35
+ import { test, expect, _electron as electron, ElectronApplication, Page } from '@playwright/test';
36
+
37
+ let app: ElectronApplication;
38
+ let window: Page;
39
+
40
+ test.beforeAll(async () => {
41
+ // Launch the Electron app from your project directory
42
+ app = await electron.launch({
43
+ args: ['./dist/main.js'],
44
+ env: {
45
+ ...process.env,
46
+ NODE_ENV: 'test',
47
+ },
48
+ });
49
+
50
+ // Wait for the first BrowserWindow to open
51
+ window = await app.firstWindow();
52
+
53
+ // Optional: wait for the app to be fully loaded
54
+ await window.waitForLoadState('domcontentloaded');
55
+ });
56
+
57
+ test.afterAll(async () => {
58
+ await app.close();
59
+ });
60
+
61
+ test('app window has correct title', async () => {
62
+ const title = await window.title();
63
+ expect(title).toBe('My Electron App');
64
+ });
65
+
66
+ test('main page renders', async () => {
67
+ await expect(window.getByRole('heading', { name: 'Welcome' })).toBeVisible();
68
+ });
69
+ ```
70
+
71
+ **JavaScript**
72
+ ```javascript
73
+ const { test, expect, _electron: electron } = require('@playwright/test');
74
+
75
+ let app;
76
+ let window;
77
+
78
+ test.beforeAll(async () => {
79
+ app = await electron.launch({
80
+ args: ['./dist/main.js'],
81
+ env: {
82
+ ...process.env,
83
+ NODE_ENV: 'test',
84
+ },
85
+ });
86
+ window = await app.firstWindow();
87
+ await window.waitForLoadState('domcontentloaded');
88
+ });
89
+
90
+ test.afterAll(async () => {
91
+ await app.close();
92
+ });
93
+
94
+ test('app window has correct title', async () => {
95
+ const title = await window.title();
96
+ expect(title).toBe('My Electron App');
97
+ });
98
+
99
+ test('main page renders', async () => {
100
+ await expect(window.getByRole('heading', { name: 'Welcome' })).toBeVisible();
101
+ });
102
+ ```
103
+
104
+ ### Electron App Fixture (Recommended)
105
+
106
+ **Use when**: You want isolated, reusable Electron app instances across test files.
107
+ **Avoid when**: All tests can share a single app instance (rare in practice).
108
+
109
+ **TypeScript**
110
+ ```typescript
111
+ // fixtures.ts
112
+ import { test as base, expect, _electron as electron, ElectronApplication, Page } from '@playwright/test';
113
+
114
+ type ElectronFixtures = {
115
+ electronApp: ElectronApplication;
116
+ window: Page;
117
+ };
118
+
119
+ export const test = base.extend<ElectronFixtures>({
120
+ electronApp: async ({}, use) => {
121
+ const app = await electron.launch({
122
+ args: ['./dist/main.js'],
123
+ env: { ...process.env, NODE_ENV: 'test' },
124
+ });
125
+ await use(app);
126
+ await app.close();
127
+ },
128
+
129
+ window: async ({ electronApp }, use) => {
130
+ const window = await electronApp.firstWindow();
131
+ await window.waitForLoadState('domcontentloaded');
132
+ await use(window);
133
+ },
134
+ });
135
+
136
+ export { expect };
137
+ ```
138
+
139
+ ```typescript
140
+ // app.spec.ts
141
+ import { test, expect } from './fixtures';
142
+
143
+ test('navigate to settings', async ({ window }) => {
144
+ await window.getByRole('link', { name: 'Settings' }).click();
145
+ await expect(window.getByRole('heading', { name: 'Settings' })).toBeVisible();
146
+ });
147
+ ```
148
+
149
+ **JavaScript**
150
+ ```javascript
151
+ // fixtures.js
152
+ const { test: base, expect, _electron: electron } = require('@playwright/test');
153
+
154
+ const test = base.extend({
155
+ electronApp: async ({}, use) => {
156
+ const app = await electron.launch({
157
+ args: ['./dist/main.js'],
158
+ env: { ...process.env, NODE_ENV: 'test' },
159
+ });
160
+ await use(app);
161
+ await app.close();
162
+ },
163
+
164
+ window: async ({ electronApp }, use) => {
165
+ const window = await electronApp.firstWindow();
166
+ await window.waitForLoadState('domcontentloaded');
167
+ await use(window);
168
+ },
169
+ });
170
+
171
+ module.exports = { test, expect };
172
+ ```
173
+
174
+ ### Accessing the Main Process
175
+
176
+ **Use when**: You need to read Electron app state, check paths, get app version, or verify main process behavior.
177
+ **Avoid when**: Everything you need is in the renderer (UI). Prefer testing through the UI.
178
+
179
+ `app.evaluate()` runs code in the main process with access to all Electron APIs.
180
+
181
+ **TypeScript**
182
+ ```typescript
183
+ import { test, expect } from './fixtures';
184
+
185
+ test('verify app version and paths', async ({ electronApp }) => {
186
+ // Evaluate in the main process — receives the Electron module
187
+ const appInfo = await electronApp.evaluate(async ({ app }) => {
188
+ return {
189
+ version: app.getVersion(),
190
+ name: app.getName(),
191
+ userData: app.getPath('userData'),
192
+ locale: app.getLocale(),
193
+ isPackaged: app.isPackaged,
194
+ };
195
+ });
196
+
197
+ expect(appInfo.version).toMatch(/^\d+\.\d+\.\d+$/);
198
+ expect(appInfo.name).toBe('my-electron-app');
199
+ expect(appInfo.userData).toBeTruthy();
200
+ expect(appInfo.isPackaged).toBe(false); // false during development
201
+ });
202
+
203
+ test('main process environment variables are set', async ({ electronApp }) => {
204
+ const nodeEnv = await electronApp.evaluate(async () => {
205
+ return process.env.NODE_ENV;
206
+ });
207
+
208
+ expect(nodeEnv).toBe('test');
209
+ });
210
+ ```
211
+
212
+ **JavaScript**
213
+ ```javascript
214
+ const { test, expect } = require('./fixtures');
215
+
216
+ test('verify app version and paths', async ({ electronApp }) => {
217
+ const appInfo = await electronApp.evaluate(async ({ app }) => {
218
+ return {
219
+ version: app.getVersion(),
220
+ name: app.getName(),
221
+ userData: app.getPath('userData'),
222
+ isPackaged: app.isPackaged,
223
+ };
224
+ });
225
+
226
+ expect(appInfo.version).toMatch(/^\d+\.\d+\.\d+$/);
227
+ expect(appInfo.name).toBe('my-electron-app');
228
+ });
229
+ ```
230
+
231
+ ### Testing IPC Communication
232
+
233
+ **Use when**: Your app uses `ipcMain` / `ipcRenderer` for communication between the main and renderer processes.
234
+ **Avoid when**: IPC is an implementation detail and the behavior is fully testable through the UI.
235
+
236
+ **TypeScript**
237
+ ```typescript
238
+ import { test, expect } from './fixtures';
239
+
240
+ test('renderer sends IPC message and gets response', async ({ electronApp, window }) => {
241
+ // Trigger an IPC call from the renderer
242
+ const result = await window.evaluate(async () => {
243
+ // Assumes your preload script exposes ipcRenderer via contextBridge
244
+ return await (window as any).electronAPI.getSystemInfo();
245
+ });
246
+
247
+ expect(result).toHaveProperty('platform');
248
+ expect(result).toHaveProperty('arch');
249
+ expect(result.platform).toBeTruthy();
250
+ });
251
+
252
+ test('main process handles IPC file-read request', async ({ electronApp, window }) => {
253
+ // Set up a listener in the main process first
254
+ await electronApp.evaluate(async ({ ipcMain }) => {
255
+ ipcMain.handle('test-ping', async () => {
256
+ return { pong: true, timestamp: Date.now() };
257
+ });
258
+ });
259
+
260
+ // Send from renderer
261
+ const response = await window.evaluate(async () => {
262
+ return await (window as any).electronAPI.invoke('test-ping');
263
+ });
264
+
265
+ expect(response.pong).toBe(true);
266
+ expect(response.timestamp).toBeGreaterThan(0);
267
+ });
268
+
269
+ test('IPC event triggers UI update', async ({ window }) => {
270
+ // Simulate the main process sending an event to the renderer
271
+ await window.evaluate(() => {
272
+ // Trigger a custom event that the app listens for
273
+ window.dispatchEvent(new CustomEvent('app:notification', {
274
+ detail: { message: 'Update available', version: '2.0.0' },
275
+ }));
276
+ });
277
+
278
+ await expect(window.getByText('Update available')).toBeVisible();
279
+ await expect(window.getByText('Version 2.0.0')).toBeVisible();
280
+ });
281
+ ```
282
+
283
+ **JavaScript**
284
+ ```javascript
285
+ const { test, expect } = require('./fixtures');
286
+
287
+ test('renderer sends IPC message and gets response', async ({ electronApp, window }) => {
288
+ const result = await window.evaluate(async () => {
289
+ return await window.electronAPI.getSystemInfo();
290
+ });
291
+
292
+ expect(result).toHaveProperty('platform');
293
+ expect(result).toHaveProperty('arch');
294
+ });
295
+
296
+ test('IPC event triggers UI update', async ({ window }) => {
297
+ await window.evaluate(() => {
298
+ window.dispatchEvent(new CustomEvent('app:notification', {
299
+ detail: { message: 'Update available', version: '2.0.0' },
300
+ }));
301
+ });
302
+
303
+ await expect(window.getByText('Update available')).toBeVisible();
304
+ });
305
+ ```
306
+
307
+ ### File System Dialogs
308
+
309
+ **Use when**: Your app uses Electron's `dialog.showOpenDialog`, `dialog.showSaveDialog`, or similar native file dialogs.
310
+ **Avoid when**: File selection is handled by a web input (`<input type="file">`). Use standard Playwright file chooser for that.
311
+
312
+ Native dialogs cannot be interacted with directly. Mock them in the main process.
313
+
314
+ **TypeScript**
315
+ ```typescript
316
+ import { test, expect } from './fixtures';
317
+
318
+ test('open file dialog and load a document', async ({ electronApp, window }) => {
319
+ // Mock the dialog to return a specific file path
320
+ await electronApp.evaluate(async ({ dialog }) => {
321
+ dialog.showOpenDialog = async () => ({
322
+ canceled: false,
323
+ filePaths: ['/tmp/test-document.txt'],
324
+ });
325
+ });
326
+
327
+ // Click the "Open File" button in the renderer
328
+ await window.getByRole('button', { name: 'Open File' }).click();
329
+
330
+ // Verify the app loaded the file
331
+ await expect(window.getByTestId('file-name')).toHaveText('test-document.txt');
332
+ });
333
+
334
+ test('save file dialog returns selected path', async ({ electronApp, window }) => {
335
+ await electronApp.evaluate(async ({ dialog }) => {
336
+ dialog.showSaveDialog = async () => ({
337
+ canceled: false,
338
+ filePath: '/tmp/exported-report.pdf',
339
+ });
340
+ });
341
+
342
+ await window.getByRole('button', { name: 'Export PDF' }).click();
343
+ await expect(window.getByText('Saved to /tmp/exported-report.pdf')).toBeVisible();
344
+ });
345
+
346
+ test('handle canceled file dialog', async ({ electronApp, window }) => {
347
+ await electronApp.evaluate(async ({ dialog }) => {
348
+ dialog.showOpenDialog = async () => ({
349
+ canceled: true,
350
+ filePaths: [],
351
+ });
352
+ });
353
+
354
+ await window.getByRole('button', { name: 'Open File' }).click();
355
+
356
+ // App should not crash or change state
357
+ await expect(window.getByTestId('file-name')).toHaveText('No file selected');
358
+ });
359
+ ```
360
+
361
+ **JavaScript**
362
+ ```javascript
363
+ const { test, expect } = require('./fixtures');
364
+
365
+ test('open file dialog and load a document', async ({ electronApp, window }) => {
366
+ await electronApp.evaluate(async ({ dialog }) => {
367
+ dialog.showOpenDialog = async () => ({
368
+ canceled: false,
369
+ filePaths: ['/tmp/test-document.txt'],
370
+ });
371
+ });
372
+
373
+ await window.getByRole('button', { name: 'Open File' }).click();
374
+ await expect(window.getByTestId('file-name')).toHaveText('test-document.txt');
375
+ });
376
+
377
+ test('handle canceled file dialog', async ({ electronApp, window }) => {
378
+ await electronApp.evaluate(async ({ dialog }) => {
379
+ dialog.showOpenDialog = async () => ({
380
+ canceled: true,
381
+ filePaths: [],
382
+ });
383
+ });
384
+
385
+ await window.getByRole('button', { name: 'Open File' }).click();
386
+ await expect(window.getByTestId('file-name')).toHaveText('No file selected');
387
+ });
388
+ ```
389
+
390
+ ### System Tray Testing
391
+
392
+ **Use when**: Your app has a system tray icon with context menus or status indicators.
393
+ **Avoid when**: Your app has no tray functionality.
394
+
395
+ Playwright cannot directly click system tray icons. Test the tray logic by evaluating in the main process.
396
+
397
+ **TypeScript**
398
+ ```typescript
399
+ import { test, expect } from './fixtures';
400
+
401
+ test('tray icon is created on app launch', async ({ electronApp }) => {
402
+ const hasTray = await electronApp.evaluate(async ({ BrowserWindow }) => {
403
+ // Access the tray via a reference your app stores
404
+ const { tray } = require('./tray-manager');
405
+ return tray !== null && !tray.isDestroyed();
406
+ });
407
+
408
+ expect(hasTray).toBe(true);
409
+ });
410
+
411
+ test('tray tooltip shows unread count', async ({ electronApp }) => {
412
+ const tooltip = await electronApp.evaluate(async () => {
413
+ const { tray } = require('./tray-manager');
414
+ return tray.getToolTip();
415
+ });
416
+
417
+ expect(tooltip).toMatch(/\d+ unread messages?/);
418
+ });
419
+
420
+ test('clicking tray "Show" menu item opens the window', async ({ electronApp }) => {
421
+ // Simulate clicking a tray menu item by invoking its callback
422
+ await electronApp.evaluate(async ({ BrowserWindow }) => {
423
+ const { trayMenu } = require('./tray-manager');
424
+ // Find the "Show" menu item and invoke its click handler
425
+ const showItem = trayMenu.items.find((item: any) => item.label === 'Show');
426
+ if (showItem && showItem.click) {
427
+ showItem.click();
428
+ }
429
+ });
430
+
431
+ // The main window should now be visible
432
+ const window = await electronApp.firstWindow();
433
+ const isVisible = await window.evaluate(() => {
434
+ return document.visibilityState === 'visible';
435
+ });
436
+ expect(isVisible).toBe(true);
437
+ });
438
+ ```
439
+
440
+ **JavaScript**
441
+ ```javascript
442
+ const { test, expect } = require('./fixtures');
443
+
444
+ test('tray icon is created on app launch', async ({ electronApp }) => {
445
+ const hasTray = await electronApp.evaluate(async () => {
446
+ const { tray } = require('./tray-manager');
447
+ return tray !== null && !tray.isDestroyed();
448
+ });
449
+
450
+ expect(hasTray).toBe(true);
451
+ });
452
+ ```
453
+
454
+ ### Multiple Windows
455
+
456
+ **Use when**: Your Electron app opens multiple windows (preferences, about, detached panels).
457
+ **Avoid when**: Your app uses a single window.
458
+
459
+ **TypeScript**
460
+ ```typescript
461
+ import { test, expect } from './fixtures';
462
+
463
+ test('open and interact with preferences window', async ({ electronApp, window }) => {
464
+ // Click the menu item or button that opens the preferences window
465
+ await window.getByRole('menuitem', { name: 'Preferences' }).click();
466
+
467
+ // Wait for the new window to appear
468
+ const prefsWindow = await electronApp.waitForEvent('window');
469
+ await prefsWindow.waitForLoadState('domcontentloaded');
470
+
471
+ // Interact with the preferences window
472
+ await prefsWindow.getByLabel('Theme').selectOption('dark');
473
+ await prefsWindow.getByRole('button', { name: 'Save' }).click();
474
+
475
+ // Verify the main window reflects the change
476
+ await expect(window.locator('html')).toHaveAttribute('data-theme', 'dark');
477
+
478
+ // Close the preferences window
479
+ await prefsWindow.close();
480
+ });
481
+
482
+ test('get all open windows', async ({ electronApp, window }) => {
483
+ // Open a second window
484
+ await window.getByRole('button', { name: 'New Window' }).click();
485
+
486
+ // Get all windows
487
+ const allWindows = electronApp.windows();
488
+ expect(allWindows.length).toBe(2);
489
+
490
+ // Find the new window (not the main one)
491
+ const newWindow = allWindows.find((w) => w !== window)!;
492
+ await expect(newWindow.getByRole('heading')).toBeVisible();
493
+ });
494
+ ```
495
+
496
+ **JavaScript**
497
+ ```javascript
498
+ const { test, expect } = require('./fixtures');
499
+
500
+ test('open and interact with preferences window', async ({ electronApp, window }) => {
501
+ await window.getByRole('menuitem', { name: 'Preferences' }).click();
502
+
503
+ const prefsWindow = await electronApp.waitForEvent('window');
504
+ await prefsWindow.waitForLoadState('domcontentloaded');
505
+
506
+ await prefsWindow.getByLabel('Theme').selectOption('dark');
507
+ await prefsWindow.getByRole('button', { name: 'Save' }).click();
508
+
509
+ await expect(window.locator('html')).toHaveAttribute('data-theme', 'dark');
510
+ await prefsWindow.close();
511
+ });
512
+ ```
513
+
514
+ ### Testing Packaged/Built Apps
515
+
516
+ **Use when**: You want to test the production build of your Electron app (after `electron-builder`, `electron-forge`, etc.).
517
+ **Avoid when**: Development mode testing is sufficient for your CI pipeline.
518
+
519
+ **TypeScript**
520
+ ```typescript
521
+ import { test, expect, _electron as electron } from '@playwright/test';
522
+ import path from 'path';
523
+
524
+ test('packaged app launches and works', async () => {
525
+ // Path to the packaged app executable
526
+ const appPath = process.platform === 'darwin'
527
+ ? path.join(__dirname, '../dist/mac/MyApp.app/Contents/MacOS/MyApp')
528
+ : process.platform === 'win32'
529
+ ? path.join(__dirname, '../dist/win-unpacked/MyApp.exe')
530
+ : path.join(__dirname, '../dist/linux-unpacked/my-app');
531
+
532
+ const app = await electron.launch({
533
+ executablePath: appPath,
534
+ });
535
+
536
+ const window = await app.firstWindow();
537
+ await window.waitForLoadState('domcontentloaded');
538
+
539
+ // Verify the packaged app works correctly
540
+ const title = await window.title();
541
+ expect(title).toBe('My Electron App');
542
+
543
+ await expect(window.getByRole('heading', { name: 'Welcome' })).toBeVisible();
544
+
545
+ // Verify it reports as packaged
546
+ const isPackaged = await app.evaluate(async ({ app }) => app.isPackaged);
547
+ expect(isPackaged).toBe(true);
548
+
549
+ await app.close();
550
+ });
551
+ ```
552
+
553
+ **JavaScript**
554
+ ```javascript
555
+ const { test, expect, _electron: electron } = require('@playwright/test');
556
+ const path = require('path');
557
+
558
+ test('packaged app launches and works', async () => {
559
+ const appPath = process.platform === 'darwin'
560
+ ? path.join(__dirname, '../dist/mac/MyApp.app/Contents/MacOS/MyApp')
561
+ : process.platform === 'win32'
562
+ ? path.join(__dirname, '../dist/win-unpacked/MyApp.exe')
563
+ : path.join(__dirname, '../dist/linux-unpacked/my-app');
564
+
565
+ const app = await electron.launch({
566
+ executablePath: appPath,
567
+ });
568
+
569
+ const window = await app.firstWindow();
570
+ await window.waitForLoadState('domcontentloaded');
571
+
572
+ const title = await window.title();
573
+ expect(title).toBe('My Electron App');
574
+
575
+ await expect(window.getByRole('heading', { name: 'Welcome' })).toBeVisible();
576
+ await app.close();
577
+ });
578
+ ```
579
+
580
+ ## Decision Guide
581
+
582
+ | Scenario | Approach | Why |
583
+ |---|---|---|
584
+ | Launch Electron app for testing | `_electron.launch({ args: ['./main.js'] })` | Playwright's built-in Electron support |
585
+ | Get the main window | `app.firstWindow()` | Returns the first `BrowserWindow` as a Playwright `Page` |
586
+ | Read main process state | `app.evaluate(({ app }) => ...)` | Runs code in the main process with Electron APIs |
587
+ | Test IPC round-trips | `window.evaluate` (renderer) + `app.evaluate` (main) | Cover both sides of the IPC bridge |
588
+ | Mock native file dialogs | Override `dialog.showOpenDialog` via `app.evaluate` | Native dialogs cannot be automated directly |
589
+ | Test system tray | `app.evaluate` to invoke tray callbacks | Tray icons are OS-native; not clickable via Playwright |
590
+ | Multiple windows | `app.waitForEvent('window')` | Captures new `BrowserWindow` instances as they open |
591
+ | Test packaged builds | `electron.launch({ executablePath })` | Points to the built binary instead of source |
592
+
593
+ ## Anti-Patterns
594
+
595
+ | Don't Do This | Problem | Do This Instead |
596
+ |---|---|---|
597
+ | `const { app } = require('electron')` in test files | Electron APIs are not available in Playwright's Node process | Use `electronApp.evaluate(({ app }) => ...)` |
598
+ | Directly import renderer code into tests | Bypasses the actual app lifecycle and IPC | Test through the UI via the `window` (Page) object |
599
+ | Skip `waitForLoadState` after `firstWindow()` | Window may not be fully rendered | Always `await window.waitForLoadState('domcontentloaded')` |
600
+ | Test tray by clicking system-level UI | Playwright cannot interact with native OS chrome | Mock tray menu callbacks via `app.evaluate` |
601
+ | Share a single `ElectronApplication` across all tests without cleanup | State leaks between tests | Use fixtures with `app.close()` in teardown |
602
+ | Forget to close the app in `afterAll` | Leaves Electron processes running, eating CI resources | Always `await app.close()` in teardown |
603
+
604
+ ## Troubleshooting
605
+
606
+ | Symptom | Cause | Fix |
607
+ |---|---|---|
608
+ | `electron.launch()` throws "cannot find module" | `args` path does not point to your main entry file | Verify the path: `args: ['./dist/main.js']` relative to the working directory |
609
+ | `firstWindow()` times out | App does not open a `BrowserWindow` in time | Check that the app creates a window on startup; increase timeout |
610
+ | `app.evaluate` cannot access Electron modules | Destructuring the wrong parameter | Destructure correctly: `evaluate(async ({ app, dialog, BrowserWindow }) => ...)` |
611
+ | Dialog mock does not take effect | Mock applied after the dialog was already called | Set up mocks before triggering the UI action that opens the dialog |
612
+ | Second window not captured | `waitForEvent('window')` registered after the window opened | Register the event listener before triggering the action that opens the window |
613
+ | Tests hang after `app.close()` | Child processes spawned by the app are still running | Ensure your Electron app cleans up child processes on quit |
614
+ | Packaged app test fails with path error | Executable path varies by OS and build tool | Use `process.platform` to compute the correct path |
615
+ | `window.evaluate` throws context destroyed | Window was closed or navigated during evaluation | Ensure the window is stable before evaluating |
616
+
617
+ ## Related
618
+
619
+ - [core/fixtures-and-hooks.md](fixtures-and-hooks.md) -- wrapping Electron app launch in fixtures
620
+ - [core/assertions-and-waiting.md](assertions-and-waiting.md) -- all standard assertions work on Electron windows
621
+ - [core/iframes-and-shadow-dom.md](iframes-and-shadow-dom.md) -- Electron apps often embed web components or iframes
622
+ - [core/browser-apis.md](browser-apis.md) -- localStorage, IndexedDB, and other APIs work the same in Electron