Loading skill
Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configure Playwright for comprehensive Electron application testing including E2E tests, visual regression, accessibility audits, and cross-platform test matrices
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 59% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 132% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 427% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 203% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 224% | 0% |
Configure Playwright for comprehensive Electron application testing. This skill sets up the complete testing infrastructure including E2E tests, visual regression testing, accessibility audits, and cross-platform test matrices with CI/CD integration.
_electron fixturejson{ "type": "object", "properties": { "projectPath": { "type": "string", "description": "Path to the Electron project root" }, "testDir": { "type": "string", "default": "tests/e2e" }, "features": { "type": "array", "items": { "enum": [ "visualRegression", "accessibility", "coverage", "performance", "ipcTesting", "multiWindow", "systemDialogMocks" ] }, "default": ["visualRegression", "accessibility", "ipcTesting"] }, "platforms": { "type": "array", "items": { "enum": ["windows", "macos", "linux"] }, "default": ["windows", "macos", "linux"] }, "pageObjects": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "selectors": { "type": "object" } } }, "description": "Page objects to generate" }, "ciIntegration": { "type": "object", "properties": { "provider": { "enum": ["github-actions", "azure-devops", "circleci", "gitlab"] }, "parallelization": { "type": "boolean", "default": true }, "sharding": { "type": "number", "description": "Number of shards" } } } }, "required": ["projectPath"] }
json{ "type": "object", "properties": { "success": { "type": "boolean" }, "files": { "type": "array", "items": { "type": "object", "properties": { "path": { "type": "string" }, "type": { "enum": ["config", "fixture", "pageObject", "test", "helper", "ci"] } } } }, "commands": { "type": "object", "properties": { "runTests": { "type": "string" }, "updateSnapshots": { "type": "string" }, "showReport": { "type": "string" } } }, "ciWorkflow": { "type": "string", "description": "Path to generated CI workflow file" } }, "required": ["success", "files"] }
tests/
e2e/
playwright.config.ts # Main Playwright config
fixtures/
electron-app.ts # Electron fixture
test-utils.ts # Test utilities
page-objects/
MainWindow.ts # Page object models
SettingsDialog.ts
specs/
app.spec.ts # Application tests
ipc.spec.ts # IPC tests
visual.spec.ts # Visual regression
a11y.spec.ts # Accessibility tests
mocks/
electron-api-mocks.ts # Electron API mocks
ipc-mocks.ts # IPC mocks
snapshots/ # Visual snapshots
reports/ # Test reportstypescript// playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests/e2e/specs', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [ ['html', { outputFolder: 'tests/e2e/reports' }], ['json', { outputFile: 'tests/e2e/reports/results.json' }], ], use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, projects: [ { name: 'electron', testMatch: '**/*.spec.ts', }, ], });
typescript// fixtures/electron-app.ts import { test as base, ElectronApplication, Page } from '@playwright/test'; import { _electron as electron } from 'playwright'; import path from 'path'; export type TestFixtures = { electronApp: ElectronApplication; mainWindow: Page; }; export const test = base.extend<TestFixtures>({ electronApp: async ({}, use) => { // Launch Electron app const electronApp = await electron.launch({ args: [path.join(__dirname, '../../dist/main/main.js')], env: { ...process.env, NODE_ENV: 'test', }, }); // Use the app in tests await use(electronApp); // Cleanup await electronApp.close(); }, mainWindow: async ({ electronApp }, use) => { // Wait for first window const window = await electronApp.firstWindow(); // Wait for app to be ready await window.waitForLoadState('domcontentloaded'); await use(window); }, }); export { expect } from '@playwright/test';
typescript// page-objects/MainWindow.ts import { Page, Locator } from '@playwright/test'; export class MainWindow { readonly page: Page; readonly titleBar: Locator; readonly sidebar: Locator; readonly mainContent: Locator; readonly statusBar: Locator; constructor(page: Page) { this.page = page; this.titleBar = page.locator('[data-testid="title-bar"]'); this.sidebar = page.locator('[data-testid="sidebar"]'); this.mainContent = page.locator('[data-testid="main-content"]'); this.statusBar = page.locator('[data-testid="status-bar"]'); } async getTitle(): Promise<string> { return this.page.title(); } async openSettings(): Promise<void> { await this.page.click('[data-testid="settings-button"]'); await this.page.waitForSelector('[data-testid="settings-dialog"]'); } async navigateTo(section: string): Promise<void> { await this.sidebar.locator(`[data-section="${section}"]`).click(); await this.page.waitForLoadState('networkidle'); } async screenshot(name: string): Promise<Buffer> { return this.page.screenshot({ path: `tests/e2e/snapshots/${name}.png` }); } }
typescript// specs/ipc.spec.ts import { test, expect } from '../fixtures/electron-app'; test.describe('IPC Communication', () => { test('should send message to main process', async ({ electronApp, mainWindow }) => { // Evaluate in main process const result = await electronApp.evaluate(async ({ ipcMain }) => { return new Promise((resolve) => { ipcMain.once('test-channel', (event, data) => { resolve(data); }); }); }); // Send from renderer await mainWindow.evaluate(() => { window.electronAPI.send('test-channel', { message: 'hello' }); }); // Verify expect(result).toEqual({ message: 'hello' }); }); test('should receive response from main process', async ({ mainWindow }) => { const response = await mainWindow.evaluate(async () => { return window.electronAPI.invoke('get-app-version'); }); expect(response).toMatch(/^\d+\.\d+\.\d+$/); }); });
typescript// specs/visual.spec.ts import { test, expect } from '../fixtures/electron-app'; import { MainWindow } from '../page-objects/MainWindow'; test.describe('Visual Regression', () => { test('main window matches snapshot', async ({ mainWindow }) => { const page = new MainWindow(mainWindow); // Wait for animations to complete await mainWindow.waitForTimeout(500); await expect(mainWindow).toHaveScreenshot('main-window.png', { maxDiffPixels: 100, }); }); test('dark mode matches snapshot', async ({ electronApp, mainWindow }) => { // Toggle dark mode via IPC await mainWindow.evaluate(() => { window.electronAPI.invoke('set-theme', 'dark'); }); await mainWindow.waitForTimeout(300); await expect(mainWindow).toHaveScreenshot('main-window-dark.png', { maxDiffPixels: 100, }); }); test('settings dialog matches snapshot', async ({ mainWindow }) => { const page = new MainWindow(mainWindow); await page.openSettings(); const dialog = mainWindow.locator('[data-testid="settings-dialog"]'); await expect(dialog).toHaveScreenshot('settings-dialog.png'); }); });
typescript// specs/a11y.spec.ts import { test, expect } from '../fixtures/electron-app'; import AxeBuilder from '@axe-core/playwright'; test.describe('Accessibility', () => { test('main window should have no accessibility violations', async ({ mainWindow }) => { const accessibilityScanResults = await new AxeBuilder({ page: mainWindow }) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) .analyze(); expect(accessibilityScanResults.violations).toEqual([]); }); test('keyboard navigation should work', async ({ mainWindow }) => { // Tab through focusable elements await mainWindow.keyboard.press('Tab'); const firstFocused = await mainWindow.evaluate(() => document.activeElement?.getAttribute('data-testid') ); expect(firstFocused).toBeTruthy(); // Verify focus is visible const focusedElement = mainWindow.locator(':focus'); await expect(focusedElement).toBeVisible(); }); test('screen reader announcements should be correct', async ({ mainWindow }) => { // Check ARIA labels const button = mainWindow.locator('[data-testid="save-button"]'); await expect(button).toHaveAttribute('aria-label'); // Check live regions const liveRegion = mainWindow.locator('[aria-live="polite"]'); await expect(liveRegion).toBeAttached(); }); });
typescript// mocks/electron-api-mocks.ts import { ElectronApplication } from '@playwright/test'; export async function mockDialog( electronApp: ElectronApplication, response: { filePaths?: string[]; canceled?: boolean } ) { await electronApp.evaluate( async ({ dialog }, response) => { dialog.showOpenDialog = async () => response; dialog.showSaveDialog = async () => ({ filePath: response.filePaths?.[0], canceled: response.canceled ?? false, }); }, response ); } export async function mockClipboard( electronApp: ElectronApplication, content: string ) { await electronApp.evaluate( async ({ clipboard }, content) => { clipboard.readText = () => content; clipboard.writeText = () => {}; }, content ); } export async function mockShell(electronApp: ElectronApplication) { const openedUrls: string[] = []; await electronApp.evaluate(async ({ shell }) => { shell.openExternal = async (url) => { // Track in test return true; }; }); return { openedUrls }; }
yaml# .github/workflows/e2e-tests.yml name: E2E Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] shardIndex: [1, 2, 3, 4] shardTotal: [4] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Build Electron app run: npm run build - name: Install Playwright run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Upload test artifacts uses: actions/upload-artifact@v4 if: always() with: name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }} path: tests/e2e/reports/ - name: Upload snapshots uses: actions/upload-artifact@v4 if: failure() with: name: snapshots-${{ matrix.os }}-${{ matrix.shardIndex }} path: tests/e2e/snapshots/ merge-reports: needs: test runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: pattern: playwright-report-* merge-multiple: true path: merged-reports - name: Merge reports run: npx playwright merge-reports merged-reports --reporter html
waitForLoadState() before interactionselectron-builder-config - Build configurationelectron-mock-factory - Mock Electron APIsvisual-regression-setup - Visual testing setupaccessibility-test-runner - Accessibility auditsdesktop-test-architect - Testing strategyui-automation-specialist - UI automation expertiseOther measured skills in the registry, with their headline benchmark lift.