Agent skill
playwright-electron-config
Configure Playwright for comprehensive Electron application testing including E2E tests, visual regression, accessibility audits, and cross-platform test matrices
Install this agent skill to your Project
npx add-skill https://github.com/a5c-ai/babysitter/tree/main/library/specializations/desktop-development/skills/playwright-electron-config
SKILL.md
playwright-electron-config
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.
Capabilities
- Configure Playwright for Electron with
_electronfixture - Generate page object models for Electron windows
- Set up visual regression testing with snapshots
- Configure accessibility testing with axe-core
- Create cross-platform test matrices for CI
- Mock Electron APIs (dialog, shell, clipboard)
- Test IPC communication between main and renderer
- Generate test coverage reports
Input Schema
{
"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"]
}
Output Schema
{
"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"]
}
Generated File Structure
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 reports
Code Templates
Playwright Configuration for Electron
// 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',
},
],
});
Electron Test Fixture
// 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';
Page Object Model
// 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` });
}
}
IPC Testing
// 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+$/);
});
});
Visual Regression Testing
// 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');
});
});
Accessibility Testing
// 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();
});
});
Electron API Mocks
// 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 };
}
GitHub Actions CI Workflow
# .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
Best Practices
- Use data-testid attributes - Stable selectors for test automation
- Wait for app ready state - Use
waitForLoadState()before interactions - Mock external dependencies - Dialog, shell, network calls
- Run tests in isolation - Fresh app instance per test when needed
- Use visual snapshots carefully - Allow pixel tolerance for CI variations
- Test on actual platforms - Cross-platform matrix in CI
Community References
Related Skills
electron-builder-config- Build configurationelectron-mock-factory- Mock Electron APIsvisual-regression-setup- Visual testing setupaccessibility-test-runner- Accessibility audits
Related Agents
desktop-test-architect- Testing strategyui-automation-specialist- UI automation expertise
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
gsd-tools
Central utility skill for GSD operations. Provides config parsing, slug generation, timestamps, path operations, and orchestrates calls to other specialized skills. Acts as the unified entry point that the original gsd-tools.cjs provided via its lib/ modules (commands, config, core, init).
model-profile-resolution
Resolve model profile (quality/balanced/budget) at orchestration start and map agents to specific models. Enables cost/quality tradeoffs by selecting appropriate AI models for each agent role.
verification-suite
Plan structure validation, phase completeness checks, reference integrity verification, and artifact existence confirmation. Provides the structured verification layer ensuring GSD artifacts are well-formed and complete.
state-management
STATE.md reading, writing, and field-level updates. Provides cross-session state persistence via .planning/STATE.md with structured fields for current task, completed phases, blockers, decisions, and quick tasks.
git-integration
Git commit patterns, formats, and conventions for GSD methodology. Provides atomic commits per task, structured commit messages, planning file commits, branch management, and milestone tag operations.
frontmatter-parsing
YAML frontmatter parsing and manipulation for .planning/ documents. Provides read, write, update, query, and validation operations on frontmatter blocks in GSD markdown artifacts.
Didn't find tool you were looking for?