This document describes how to run and write tests for the AI Browser project.
The project uses Playwright for E2E (End-to-End) testing of the Electron application.
┌─────────────────┐
│ Playwright │ ← Test runner
└────────┬────────┘
│ Electron API
┌────────▼────────┐
│ ai-browser │ ← Your Electron app
│ (dev mode) │
└─────────────────┘
-
Build Electron code:
pnpm run build:deps
-
Start Next.js dev server (in a separate terminal):
pnpm run next
pnpm run test:e2eOutput example:
Running 4 tests using 1 worker
✓ 1 e2e/basic.test.ts:44:7 › Electron App › app launches successfully (5ms)
✓ 2 e2e/basic.test.ts:50:7 › Electron App › can take screenshot (460ms)
✓ 3 e2e/basic.test.ts:56:7 › Electron App › main window is visible (6ms)
✓ 4 e2e/basic.test.ts:64:7 › Electron App › can get window count (2ms)
4 passed
pnpm run test:e2e:uiOpens an interactive interface where you can:
- Watch tests execute in real-time
- View screenshots at each step
- Debug failed tests
- Re-run specific tests
After running tests, view the detailed HTML report:
pnpm exec playwright show-reportThen open http://localhost:9323 in your browser.
e2e/
├── basic.test.ts # Basic app launch tests
├── screenshots/ # Test screenshots
│ └── app.png
└── (future tests...)
import { test, expect, _electron as electron } from '@playwright/test';
import path from 'path';
let electronApp;
let window;
test.beforeAll(async () => {
// Launch Electron app
electronApp = await electron.launch({
args: [path.join(process.cwd(), 'dist/electron/main/index.mjs')],
env: { ...process.env, NODE_ENV: 'development' },
});
// Get the first window
window = await electronApp.firstWindow();
await window.waitForTimeout(5000); // Wait for app to load
});
test.afterAll(async () => {
await electronApp?.close();
});
test('app launches successfully', async () => {
expect(window).toBeTruthy();
const title = await window.title();
console.log('Window title:', title);
});test('can take screenshot', async () => {
const screenshot = await window.screenshot({
path: 'e2e/screenshots/my-test.png'
});
expect(screenshot).toBeTruthy();
});test('can click button', async () => {
await window.click('[data-testid="my-button"]');
await expect(window.locator('.result')).toBeVisible();
});
test('can type text', async () => {
await window.fill('[data-testid="input"]', 'Hello World');
await window.press('[data-testid="input"]', 'Enter');
});test('can access BrowserWindow', async () => {
const windowCount = await electronApp.evaluate(({ BrowserWindow }) => {
return BrowserWindow.getAllWindows().length;
});
expect(windowCount).toBe(1);
});test('can send IPC message', async () => {
const result = await electronApp.evaluate(async ({ ipcMain }) => {
return new Promise((resolve) => {
ipcMain.once('test-response', (event, data) => {
resolve(data);
});
});
});
expect(result).toBeDefined();
});For reliable element selection, add data-testid to components:
// In React component
<button data-testid="submit-btn">Submit</button>
// In test
await window.click('[data-testid="submit-btn"]');// Wait for element to appear
await window.waitForSelector('.loading', { state: 'hidden' });
// Wait for navigation
await window.waitForURL('**/dashboard');
// Custom wait
await window.waitForFunction(() => {
return document.querySelector('.content')?.textContent?.includes('Ready');
});// e2e/pages/home-page.ts
export class HomePage {
constructor(private page: Page) {}
async clickNewTask() {
await this.page.click('[data-testid="new-task-btn"]');
}
async getTaskCount() {
return await this.page.locator('.task-item').count();
}
}
// In test
const homePage = new HomePage(window);
await homePage.clickNewTask();
expect(await homePage.getTaskCount()).toBe(1);In playwright.config.ts:
export default defineConfig({
use: {
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
});import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 60000, // Test timeout
retries: 0, // Retry failed tests
workers: 1, // Run tests sequentially for Electron
use: {
trace: 'on-first-retry',
},
reporter: [
['list'], // Console output
['html', { open: 'never' }], // HTML report
],
});- Ensure Next.js server is running:
pnpm run next - Ensure Electron is built:
pnpm run build:deps - Check if port 5173 is available
- Increase timeout in config or test:
test('slow test', async () => { test.setTimeout(120000); // 2 minutes // ... });
- Use
window.pause()to debug:test('debug test', async () => { await window.pause(); // Opens inspector });
- Add wait time after navigation:
await window.waitForTimeout(2000); await window.screenshot({ path: 'screenshot.png' });
# .github/workflows/test.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm run build:deps
- run: pnpm run next &
- run: sleep 10 && pnpm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/e2e/- Test filesplaywright.config.ts- Playwright configuratione2e/screenshots/- Test screenshotsplaywright-report/- HTML reports (generated)