@arcadialdev/arcality 2.2.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.
- package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
- package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
- package/.agents/skills/playwright-best-practices/README.md +147 -0
- package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
- package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
- package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
- package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
- package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
- package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
- package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
- package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
- package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
- package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
- package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
- package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
- package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
- package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
- package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
- package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
- package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
- package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
- package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
- package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
- package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
- package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
- package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
- package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
- package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
- package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
- package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
- package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
- package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
- package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
- package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
- package/.env.example +21 -0
- package/README.md +30 -0
- package/bin/arcality.mjs +86 -0
- package/package.json +66 -0
- package/playwright.config.ts +12 -0
- package/scripts/cleanup-qmsdev.mjs +63 -0
- package/scripts/discover-view.mjs +52 -0
- package/scripts/extract-view.mjs +64 -0
- package/scripts/gen-and-run.mjs +838 -0
- package/scripts/init.mjs +290 -0
- package/scripts/migrate-to-central-out.mjs +157 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/rebrand-report.mjs +241 -0
- package/scripts/setup.mjs +166 -0
- package/src/KnowledgeService.ts +239 -0
- package/src/arcalityClient.mjs +266 -0
- package/src/configLoader.mjs +179 -0
- package/src/configManager.mjs +172 -0
- package/src/consoleBanner.ts +32 -0
- package/src/envSetup.ts +205 -0
- package/src/index.ts +25 -0
- package/src/projectInspector.ts +42 -0
- package/src/services/collectiveMemoryService.ts +178 -0
- package/src/testRunner.ts +201 -0
- package/tests/_helpers/ArcalityReporter.ts +490 -0
- package/tests/_helpers/agentic-runner.spec.ts +741 -0
- package/tests/_helpers/ai-agent-helper.ts +1573 -0
- package/tests/_helpers/discover-view.spec.ts +238 -0
- package/tests/_helpers/extract-view.spec.ts +118 -0
- package/tests/_helpers/qa-tools.ts +333 -0
- package/tests/_helpers/smart-action.spec.ts +1458 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# Electron Testing
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Setup & Configuration](#setup--configuration)
|
|
6
|
+
2. [Launching Electron Apps](#launching-electron-apps)
|
|
7
|
+
3. [Main Process Testing](#main-process-testing)
|
|
8
|
+
4. [Renderer Process Testing](#renderer-process-testing)
|
|
9
|
+
5. [IPC Communication](#ipc-communication)
|
|
10
|
+
6. [Native Features](#native-features)
|
|
11
|
+
7. [Packaging & Distribution](#packaging--distribution)
|
|
12
|
+
|
|
13
|
+
## Setup & Configuration
|
|
14
|
+
|
|
15
|
+
### Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -D @playwright/test electron
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Basic Configuration
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// playwright.config.ts
|
|
25
|
+
import { defineConfig } from "@playwright/test";
|
|
26
|
+
|
|
27
|
+
export default defineConfig({
|
|
28
|
+
testDir: "./tests",
|
|
29
|
+
timeout: 30000,
|
|
30
|
+
use: {
|
|
31
|
+
trace: "on-first-retry",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Electron Test Fixture
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// fixtures/electron.ts
|
|
40
|
+
import {
|
|
41
|
+
test as base,
|
|
42
|
+
_electron as electron,
|
|
43
|
+
ElectronApplication,
|
|
44
|
+
Page,
|
|
45
|
+
} from "@playwright/test";
|
|
46
|
+
|
|
47
|
+
type ElectronFixtures = {
|
|
48
|
+
electronApp: ElectronApplication;
|
|
49
|
+
window: Page;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const test = base.extend<ElectronFixtures>({
|
|
53
|
+
electronApp: async ({}, use) => {
|
|
54
|
+
// Launch Electron app
|
|
55
|
+
const electronApp = await electron.launch({
|
|
56
|
+
args: [".", "--no-sandbox"],
|
|
57
|
+
env: {
|
|
58
|
+
...process.env,
|
|
59
|
+
NODE_ENV: "test",
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await use(electronApp);
|
|
64
|
+
|
|
65
|
+
// Cleanup
|
|
66
|
+
await electronApp.close();
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
window: async ({ electronApp }, use) => {
|
|
70
|
+
// Wait for first window
|
|
71
|
+
const window = await electronApp.firstWindow();
|
|
72
|
+
|
|
73
|
+
// Wait for app to be ready
|
|
74
|
+
await window.waitForLoadState("domcontentloaded");
|
|
75
|
+
|
|
76
|
+
await use(window);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export { expect } from "@playwright/test";
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Launch Options
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Advanced launch configuration
|
|
87
|
+
const electronApp = await electron.launch({
|
|
88
|
+
args: ["main.js", "--custom-flag"],
|
|
89
|
+
cwd: "/path/to/app",
|
|
90
|
+
env: {
|
|
91
|
+
...process.env,
|
|
92
|
+
ELECTRON_ENABLE_LOGGING: "1",
|
|
93
|
+
NODE_ENV: "test",
|
|
94
|
+
},
|
|
95
|
+
timeout: 30000,
|
|
96
|
+
// For packaged apps
|
|
97
|
+
executablePath: "/path/to/MyApp.app/Contents/MacOS/MyApp",
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Launching Electron Apps
|
|
102
|
+
|
|
103
|
+
### Development Mode
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
test("launch in dev mode", async () => {
|
|
107
|
+
const electronApp = await electron.launch({
|
|
108
|
+
args: ["."], // Points to package.json main
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const window = await electronApp.firstWindow();
|
|
112
|
+
await expect(window.locator("h1")).toContainText("My App");
|
|
113
|
+
|
|
114
|
+
await electronApp.close();
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Packaged Application
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
test("launch packaged app", async () => {
|
|
122
|
+
const appPath =
|
|
123
|
+
process.platform === "darwin"
|
|
124
|
+
? "/Applications/MyApp.app/Contents/MacOS/MyApp"
|
|
125
|
+
: process.platform === "win32"
|
|
126
|
+
? "C:\\Program Files\\MyApp\\MyApp.exe"
|
|
127
|
+
: "/usr/bin/myapp";
|
|
128
|
+
|
|
129
|
+
const electronApp = await electron.launch({
|
|
130
|
+
executablePath: appPath,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const window = await electronApp.firstWindow();
|
|
134
|
+
await expect(window).toHaveTitle(/MyApp/);
|
|
135
|
+
|
|
136
|
+
await electronApp.close();
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Multiple Windows
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
test("handle multiple windows", async ({ electronApp }) => {
|
|
144
|
+
const mainWindow = await electronApp.firstWindow();
|
|
145
|
+
|
|
146
|
+
// Trigger new window
|
|
147
|
+
await mainWindow.getByRole("button", { name: "Open Settings" }).click();
|
|
148
|
+
|
|
149
|
+
// Wait for new window
|
|
150
|
+
const settingsWindow = await electronApp.waitForEvent("window");
|
|
151
|
+
|
|
152
|
+
// Both windows are now accessible
|
|
153
|
+
await expect(settingsWindow.locator("h1")).toHaveText("Settings");
|
|
154
|
+
await expect(mainWindow.locator("h1")).toHaveText("Main");
|
|
155
|
+
|
|
156
|
+
// Get all windows
|
|
157
|
+
const windows = electronApp.windows();
|
|
158
|
+
expect(windows.length).toBe(2);
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Main Process Testing
|
|
163
|
+
|
|
164
|
+
### Evaluate in Main Process
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
test("access main process", async ({ electronApp }) => {
|
|
168
|
+
// Evaluate in main process context
|
|
169
|
+
const appPath = await electronApp.evaluate(async ({ app }) => {
|
|
170
|
+
return app.getAppPath();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(appPath).toContain("my-electron-app");
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Access Electron APIs
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
test("electron API access", async ({ electronApp }) => {
|
|
181
|
+
// Get app version
|
|
182
|
+
const version = await electronApp.evaluate(async ({ app }) => {
|
|
183
|
+
return app.getVersion();
|
|
184
|
+
});
|
|
185
|
+
expect(version).toMatch(/^\d+\.\d+\.\d+$/);
|
|
186
|
+
|
|
187
|
+
// Get platform info
|
|
188
|
+
const platform = await electronApp.evaluate(async ({ app }) => {
|
|
189
|
+
return process.platform;
|
|
190
|
+
});
|
|
191
|
+
expect(["darwin", "win32", "linux"]).toContain(platform);
|
|
192
|
+
|
|
193
|
+
// Check if app is ready
|
|
194
|
+
const isReady = await electronApp.evaluate(async ({ app }) => {
|
|
195
|
+
return app.isReady();
|
|
196
|
+
});
|
|
197
|
+
expect(isReady).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### BrowserWindow Properties
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
test("check window properties", async ({ electronApp, window }) => {
|
|
205
|
+
// Get BrowserWindow from main process
|
|
206
|
+
const windowBounds = await electronApp.evaluate(async ({ BrowserWindow }) => {
|
|
207
|
+
const win = BrowserWindow.getAllWindows()[0];
|
|
208
|
+
return win.getBounds();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(windowBounds.width).toBeGreaterThan(0);
|
|
212
|
+
expect(windowBounds.height).toBeGreaterThan(0);
|
|
213
|
+
|
|
214
|
+
// Check window state
|
|
215
|
+
const isMaximized = await electronApp.evaluate(async ({ BrowserWindow }) => {
|
|
216
|
+
const win = BrowserWindow.getAllWindows()[0];
|
|
217
|
+
return win.isMaximized();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Check window title
|
|
221
|
+
const title = await electronApp.evaluate(async ({ BrowserWindow }) => {
|
|
222
|
+
const win = BrowserWindow.getAllWindows()[0];
|
|
223
|
+
return win.getTitle();
|
|
224
|
+
});
|
|
225
|
+
expect(title).toBeTruthy();
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Renderer Process Testing
|
|
230
|
+
|
|
231
|
+
### Standard Page Testing
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
test("renderer interactions", async ({ window }) => {
|
|
235
|
+
// Standard Playwright page interactions
|
|
236
|
+
await window.getByRole("button", { name: "Click Me" }).click();
|
|
237
|
+
await expect(window.getByText("Clicked!")).toBeVisible();
|
|
238
|
+
|
|
239
|
+
// Fill forms
|
|
240
|
+
await window.getByLabel("Username").fill("testuser");
|
|
241
|
+
await window.getByLabel("Password").fill("password123");
|
|
242
|
+
await window.getByRole("button", { name: "Login" }).click();
|
|
243
|
+
|
|
244
|
+
// Verify navigation
|
|
245
|
+
await expect(window).toHaveURL(/dashboard/);
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Access Node.js in Renderer
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
test("node integration", async ({ window }) => {
|
|
253
|
+
// If nodeIntegration is enabled
|
|
254
|
+
const nodeVersion = await window.evaluate(() => {
|
|
255
|
+
return (window as any).process?.version;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Check if Node APIs are available
|
|
259
|
+
const hasFs = await window.evaluate(() => {
|
|
260
|
+
return typeof (window as any).require === "function";
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Context Isolation Testing
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
test("context isolation", async ({ window }) => {
|
|
269
|
+
// Test preload script exposed APIs
|
|
270
|
+
const apiAvailable = await window.evaluate(() => {
|
|
271
|
+
return typeof (window as any).electronAPI !== "undefined";
|
|
272
|
+
});
|
|
273
|
+
expect(apiAvailable).toBe(true);
|
|
274
|
+
|
|
275
|
+
// Call exposed API
|
|
276
|
+
const result = await window.evaluate(async () => {
|
|
277
|
+
return await (window as any).electronAPI.getAppVersion();
|
|
278
|
+
});
|
|
279
|
+
expect(result).toMatch(/^\d+\.\d+\.\d+$/);
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## IPC Communication
|
|
284
|
+
|
|
285
|
+
### Testing IPC from Renderer
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
test("IPC invoke", async ({ window }) => {
|
|
289
|
+
// Test preload-exposed IPC call
|
|
290
|
+
const result = await window.evaluate(async () => {
|
|
291
|
+
return await (window as any).electronAPI.getData("user-settings");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
expect(result).toHaveProperty("theme");
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Testing IPC from Main Process
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
test("main to renderer IPC", async ({ electronApp, window }) => {
|
|
302
|
+
// Set up listener in renderer
|
|
303
|
+
await window.evaluate(() => {
|
|
304
|
+
(window as any).receivedMessage = null;
|
|
305
|
+
(window as any).electronAPI.onMessage((msg: string) => {
|
|
306
|
+
(window as any).receivedMessage = msg;
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Send from main process
|
|
311
|
+
await electronApp.evaluate(async ({ BrowserWindow }) => {
|
|
312
|
+
const win = BrowserWindow.getAllWindows()[0];
|
|
313
|
+
win.webContents.send("message", "Hello from main!");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Verify receipt
|
|
317
|
+
await window.waitForFunction(() => (window as any).receivedMessage !== null);
|
|
318
|
+
const message = await window.evaluate(() => (window as any).receivedMessage);
|
|
319
|
+
expect(message).toBe("Hello from main!");
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Mock IPC Handlers
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
// In test setup or fixture
|
|
327
|
+
test("mock IPC handler", async ({ electronApp, window }) => {
|
|
328
|
+
// Override IPC handler in main process
|
|
329
|
+
await electronApp.evaluate(async ({ ipcMain }) => {
|
|
330
|
+
// Remove existing handler
|
|
331
|
+
ipcMain.removeHandler("fetch-data");
|
|
332
|
+
|
|
333
|
+
// Add mock handler
|
|
334
|
+
ipcMain.handle("fetch-data", async () => {
|
|
335
|
+
return { mocked: true, data: "test-data" };
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Test with mocked handler
|
|
340
|
+
const result = await window.evaluate(async () => {
|
|
341
|
+
return await (window as any).electronAPI.fetchData();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
expect(result.mocked).toBe(true);
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Native Features
|
|
349
|
+
|
|
350
|
+
### File System Dialogs
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
test("file dialog", async ({ electronApp, window }) => {
|
|
354
|
+
// Mock dialog response
|
|
355
|
+
await electronApp.evaluate(async ({ dialog }) => {
|
|
356
|
+
dialog.showOpenDialog = async () => ({
|
|
357
|
+
canceled: false,
|
|
358
|
+
filePaths: ["/mock/path/file.txt"],
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Trigger file open
|
|
363
|
+
await window.getByRole("button", { name: "Open File" }).click();
|
|
364
|
+
|
|
365
|
+
// Verify file was "opened"
|
|
366
|
+
await expect(window.getByText("file.txt")).toBeVisible();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("save dialog", async ({ electronApp, window }) => {
|
|
370
|
+
await electronApp.evaluate(async ({ dialog }) => {
|
|
371
|
+
dialog.showSaveDialog = async () => ({
|
|
372
|
+
canceled: false,
|
|
373
|
+
filePath: "/mock/path/saved-file.txt",
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
await window.getByRole("button", { name: "Save" }).click();
|
|
378
|
+
await expect(window.getByText("Saved successfully")).toBeVisible();
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Menu Testing
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
test("application menu", async ({ electronApp }) => {
|
|
386
|
+
// Get menu structure
|
|
387
|
+
const menuLabels = await electronApp.evaluate(async ({ Menu }) => {
|
|
388
|
+
const menu = Menu.getApplicationMenu();
|
|
389
|
+
return menu?.items.map((item) => item.label) || [];
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
expect(menuLabels).toContain("File");
|
|
393
|
+
expect(menuLabels).toContain("Edit");
|
|
394
|
+
|
|
395
|
+
// Trigger menu action
|
|
396
|
+
await electronApp.evaluate(async ({ Menu }) => {
|
|
397
|
+
const menu = Menu.getApplicationMenu();
|
|
398
|
+
const fileMenu = menu?.items.find((item) => item.label === "File");
|
|
399
|
+
const newItem = fileMenu?.submenu?.items.find(
|
|
400
|
+
(item) => item.label === "New",
|
|
401
|
+
);
|
|
402
|
+
newItem?.click();
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Native Notifications
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
test("notifications", async ({ electronApp, window }) => {
|
|
411
|
+
// Mock Notification
|
|
412
|
+
let notificationShown = false;
|
|
413
|
+
await electronApp.evaluate(async ({ Notification }) => {
|
|
414
|
+
const OriginalNotification = Notification;
|
|
415
|
+
(global as any).Notification = class extends OriginalNotification {
|
|
416
|
+
constructor(options: any) {
|
|
417
|
+
super(options);
|
|
418
|
+
(global as any).lastNotification = options;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Trigger notification
|
|
424
|
+
await window.getByRole("button", { name: "Notify" }).click();
|
|
425
|
+
|
|
426
|
+
// Verify notification was created
|
|
427
|
+
const notification = await electronApp.evaluate(async () => {
|
|
428
|
+
return (global as any).lastNotification;
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(notification.title).toBe("New Message");
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Clipboard
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
test("clipboard operations", async ({ electronApp, window }) => {
|
|
439
|
+
// Write to clipboard
|
|
440
|
+
await electronApp.evaluate(async ({ clipboard }) => {
|
|
441
|
+
clipboard.writeText("Test clipboard content");
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Paste in app
|
|
445
|
+
await window.getByRole("textbox").focus();
|
|
446
|
+
await window.keyboard.press("ControlOrMeta+v");
|
|
447
|
+
|
|
448
|
+
// Read clipboard
|
|
449
|
+
const clipboardContent = await electronApp.evaluate(async ({ clipboard }) => {
|
|
450
|
+
return clipboard.readText();
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
expect(clipboardContent).toBe("Test clipboard content");
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Packaging & Distribution
|
|
458
|
+
|
|
459
|
+
### Testing Packaged Apps
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// fixtures/packaged-electron.ts
|
|
463
|
+
import { test as base, _electron as electron } from "@playwright/test";
|
|
464
|
+
import path from "path";
|
|
465
|
+
import { execSync } from "child_process";
|
|
466
|
+
|
|
467
|
+
export const test = base.extend({
|
|
468
|
+
electronApp: async ({}, use) => {
|
|
469
|
+
// Build the app first (or use pre-built)
|
|
470
|
+
const distPath = path.join(__dirname, "../dist");
|
|
471
|
+
|
|
472
|
+
let executablePath: string;
|
|
473
|
+
if (process.platform === "darwin") {
|
|
474
|
+
executablePath = path.join(
|
|
475
|
+
distPath,
|
|
476
|
+
"mac",
|
|
477
|
+
"MyApp.app",
|
|
478
|
+
"Contents",
|
|
479
|
+
"MacOS",
|
|
480
|
+
"MyApp",
|
|
481
|
+
);
|
|
482
|
+
} else if (process.platform === "win32") {
|
|
483
|
+
executablePath = path.join(distPath, "win-unpacked", "MyApp.exe");
|
|
484
|
+
} else {
|
|
485
|
+
executablePath = path.join(distPath, "linux-unpacked", "myapp");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const electronApp = await electron.launch({ executablePath });
|
|
489
|
+
await use(electronApp);
|
|
490
|
+
await electronApp.close();
|
|
491
|
+
},
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Anti-Patterns to Avoid
|
|
496
|
+
|
|
497
|
+
| Anti-Pattern | Problem | Solution |
|
|
498
|
+
| ------------------------------------- | ---------------------------- | -------------------------------------------- |
|
|
499
|
+
| Not closing ElectronApplication | Resource leaks | Always call `electronApp.close()` in cleanup |
|
|
500
|
+
| Hardcoded executable paths | Breaks cross-platform | Use platform detection |
|
|
501
|
+
| Testing packaged app without building | Outdated code | Build before testing or test dev mode |
|
|
502
|
+
| Ignoring IPC in tests | Missing coverage | Test IPC communication explicitly |
|
|
503
|
+
| Not mocking native dialogs | Tests hang waiting for input | Mock dialog responses |
|
|
504
|
+
|
|
505
|
+
## Related References
|
|
506
|
+
|
|
507
|
+
- **Fixtures**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for custom fixture patterns
|
|
508
|
+
- **Component Testing**: See [component-testing.md](component-testing.md) for renderer testing patterns
|
|
509
|
+
- **Debugging**: See [debugging.md](../debugging/debugging.md) for troubleshooting
|