agentsclimarketplace

Playwright electron config

Skill a5c-ai/babysitter/library/specializations/desktop-development/skills/playwright-electron-config

Babysitter enforces obedience on agentic workforces and enables them to manage extremely complex tasks and workflows through deterministic, hallucination-free self-orchestration

Install
npx -y skills add a5c-ai/babysitter --skill playwright-electron-config

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Configure Playwright for comprehensive Electron application testing including E2E tests, visual regression, accessibility audits, and cross-platform test matrices

SKILL.md

14.1 KB, as published. Nobody here has run it

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 _electron fixture
  • 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

  1. Use data-testid attributes - Stable selectors for test automation
  2. Wait for app ready state - Use waitForLoadState() before interactions
  3. Mock external dependencies - Dialog, shell, network calls
  4. Run tests in isolation - Fresh app instance per test when needed
  5. Use visual snapshots carefully - Allow pixel tolerance for CI variations
  6. Test on actual platforms - Cross-platform matrix in CI

Community References

Related Skills

  • electron-builder-config - Build configuration
  • electron-mock-factory - Mock Electron APIs
  • visual-regression-setup - Visual testing setup
  • accessibility-test-runner - Accessibility audits

Related Agents

  • desktop-test-architect - Testing strategy
  • ui-automation-specialist - UI automation expertise

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.