mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-24 16:54:43 +00:00
Adds a full desktop Playwright E2E suite that launches the Electron app against a mock inference server, exercising the full boot chain: electron -> hermes serve -> mock provider -> renderer Includes: - Mock OpenAI-compatible inference server (mock-server.ts) - Shared fixtures with sandbox isolation (credentials, HERMES_HOME, userData, fixed window-state.json for reproducible screenshots) - Test specs: boot, boot-failure, onboarding, mock-backend-setup, chat, and packaged-app launch - Visual regression: expectVisualSnapshot() wraps toHaveScreenshot in try/catch so diffs are reported without failing the test suite - CI workflow: xvfb at 1280x1024, baseline cache from main (--update-snapshots on main, compare on PRs), step summary table with diff/actual/expected image links, dedicated visual-diffs artifact - dev:mock script for local fake-provider development - test:e2e:visual + test:e2e:update-snapshots scripts using cage - .gitignore: *-snapshots/ (baselines cached in CI, not committed)
63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
/**
|
|
* E2E smoke tests for the dev-mode desktop app.
|
|
*
|
|
* These tests launch the Electron app from the built dist/ (not the
|
|
* packaged binary) with a real `hermes serve` backend pointed at a mock
|
|
* inference server. The full chain is exercised:
|
|
*
|
|
* electron → hermes serve (python) → mock provider → renderer
|
|
*
|
|
* Prerequisite: `npm run build` must have been run so dist/ exists.
|
|
* Run from the nix devshell:
|
|
* npm exec playwright test e2e/boot.spec.ts --reporter=list
|
|
*/
|
|
import { expect, test } from '@playwright/test'
|
|
|
|
import {
|
|
type MockBackendFixture,
|
|
setupMockBackend,
|
|
waitForAppReady,
|
|
} from './fixtures'
|
|
import { expectVisualSnapshot } from './visual-snapshot'
|
|
|
|
let fixture: MockBackendFixture | null = null
|
|
|
|
test.beforeAll(async () => {
|
|
fixture = await setupMockBackend()
|
|
})
|
|
|
|
test.afterAll(async () => {
|
|
await fixture?.cleanup()
|
|
fixture = null
|
|
})
|
|
|
|
test.describe('dev-mode boot with mock backend', () => {
|
|
test('window opens with Hermes title', async () => {
|
|
const title = await fixture!.page.title()
|
|
expect(title).toContain('Hermes')
|
|
})
|
|
|
|
test('renderer mounts and shows DOM content', async () => {
|
|
const page = fixture!.page
|
|
// Wait for the React root to mount. The app renders into #root
|
|
// (see src/main.tsx), but content may arrive through portals — so
|
|
// check the body for any interactive content instead.
|
|
await page.waitForSelector('body', { state: 'attached' })
|
|
// Wait for the main app shell — the composer is always present.
|
|
await page.waitForSelector('textarea, [contenteditable="true"]', {
|
|
state: 'attached',
|
|
timeout: 30_000,
|
|
})
|
|
})
|
|
|
|
test('backend boots and app becomes ready', async () => {
|
|
// This is the big one — wait for the full boot chain to complete:
|
|
// electron starts → hermes serve is spawned → WS connects → config
|
|
// loaded → sessions loaded → boot overlay dismissed → composer visible.
|
|
await waitForAppReady(fixture!, 120_000)
|
|
})
|
|
|
|
test('screenshot after boot', async () => {
|
|
await expectVisualSnapshot(fixture!.page, { name: 'boot-ready', app: fixture!.app })
|
|
})
|
|
})
|