Skip to content

Commit 8582397

Browse files
authored
feat: add Playwright E2E testing and PR build artifacts (#43)
* feat: scaffold Playwright e2e test directory * fix: add @types/node to e2e devDependencies * feat: add data-testid to app shell for e2e testing * feat: add shared Playwright app fixture * feat: add app shell smoke test * feat: add make e2e, e2e-ui, e2e-report targets * feat: add data-testid attributes to core layout components for e2e testing * fix: remove duplicate sidebar render from CoreLayout CoreLayout.tsx rendered <Layout.Sidebar /> directly, but Container already renders it. Since the sidebar is position:fixed, both instances overlapped identically — the one in CoreLayout was redundant DOM. * feat: add initial navigation and bottom drawer e2e tests * feat: add PR validation workflow with e2e tests and build artifacts * fix: use npx for Playwright commands in CI to avoid pnpm workspace resolution * fix: add e2e to pnpm workspace so pnpm exec resolves in CI * fix: update lockfile for e2e workspace and pin Wails CLI to v2.11.0 * fix: use Playwright webServer for CI instead of manual background process Playwright's built-in webServer config handles starting wails dev, health-checking the URL, and tearing down. Increased timeout to 5min for CI runners where compilation is slower. * fix: pass -tags webkit2_41 to wails dev in CI for webkit2gtk-4.1 compat * fix: add xvfb for headless GTK init in CI wails dev panics with 'failed to init GTK' on headless Linux because it needs a display server to initialize the WebView. xvfb-run provides a virtual framebuffer so GTK can initialize without a real display. * fix: start wails dev as background process in CI to avoid shutdown hang Playwright's webServer can't cleanly terminate the xvfb-run + wails dev process tree, causing the job to hang after tests pass. Starting wails dev as a background step lets the job teardown handle cleanup. * fix: add -nosyncgomod, -noreload, -skipbindings to wails dev in CI Skips go mod tidy, file watching, and binding generation in CI. Bindings staleness is already caught by the bindings-check PR job. * fix: add -m flag to skip go mod tidy in CI
1 parent f201ed8 commit 8582397

18 files changed

Lines changed: 419 additions & 3 deletions

File tree

.github/workflows/pr.yml

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
name: PR Validation
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
7+
concurrency:
8+
group: pr-${{ github.event.pull_request.number }}
9+
cancel-in-progress: true
10+
11+
env:
12+
NODE_OPTIONS: "--max-old-space-size=4096"
13+
GO_VERSION: "1.26"
14+
NODE_VERSION: "20"
15+
PNPM_VERSION: "10"
16+
17+
jobs:
18+
# ─────────────────────────────────────────────────────────────────────────────
19+
# E2E Tests — Playwright against wails dev
20+
# ─────────────────────────────────────────────────────────────────────────────
21+
e2e-tests:
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 20
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
with:
28+
submodules: recursive
29+
30+
- name: Install system dependencies
31+
run: |
32+
sudo apt-get update
33+
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev xvfb
34+
35+
- name: Setup Node
36+
uses: actions/setup-node@v4
37+
with:
38+
node-version: ${{ env.NODE_VERSION }}
39+
40+
- name: Setup pnpm
41+
uses: pnpm/action-setup@v4
42+
with:
43+
version: ${{ env.PNPM_VERSION }}
44+
run_install: |
45+
- args: []
46+
- args: [--global, typescript]
47+
48+
- name: Setup Go
49+
uses: actions/setup-go@v5
50+
with:
51+
go-version: ${{ env.GO_VERSION }}
52+
cache: true
53+
cache-dependency-path: "go.sum"
54+
55+
- name: Install Wails
56+
run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
57+
58+
- name: Build workspace packages
59+
run: make packages
60+
61+
- name: Install Playwright browsers
62+
working-directory: e2e
63+
run: pnpm exec playwright install --with-deps chromium
64+
65+
- name: Start wails dev
66+
run: |
67+
xvfb-run wails dev -loglevel Error -tags webkit2_41 -nosyncgomod -m -noreload -skipbindings &
68+
echo "Waiting for Wails dev server..."
69+
for i in $(seq 1 150); do
70+
if curl -sf http://localhost:34115 > /dev/null 2>&1; then
71+
echo "Wails dev server is ready (${i}s)"
72+
exit 0
73+
fi
74+
sleep 2
75+
done
76+
echo "Timed out waiting for Wails dev server"
77+
exit 1
78+
79+
- name: Run Playwright tests
80+
working-directory: e2e
81+
run: pnpm test
82+
83+
- name: Upload test report
84+
if: always()
85+
uses: actions/upload-artifact@v4
86+
with:
87+
name: playwright-report
88+
path: |
89+
e2e/playwright-report/
90+
e2e/test-results/
91+
retention-days: 7
92+
93+
# ─────────────────────────────────────────────────────────────────────────────
94+
# Build Artifacts — downloadable binaries for manual testing
95+
# ─────────────────────────────────────────────────────────────────────────────
96+
build-artifacts:
97+
strategy:
98+
fail-fast: false
99+
matrix:
100+
include:
101+
- os: macos-latest
102+
platform: darwin/universal
103+
artifact-name: pr-build-macos
104+
extra-flags: ""
105+
- os: ubuntu-latest
106+
platform: linux/amd64
107+
artifact-name: pr-build-linux
108+
extra-flags: "-tags webkit2_41"
109+
- os: windows-latest
110+
platform: windows/amd64
111+
artifact-name: pr-build-windows
112+
extra-flags: ""
113+
runs-on: ${{ matrix.os }}
114+
timeout-minutes: 30
115+
steps:
116+
- name: Checkout
117+
uses: actions/checkout@v4
118+
with:
119+
submodules: recursive
120+
121+
- name: Install system dependencies (Linux)
122+
if: runner.os == 'Linux'
123+
run: |
124+
sudo apt-get update
125+
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
126+
127+
- name: Setup Node
128+
uses: actions/setup-node@v4
129+
with:
130+
node-version: ${{ env.NODE_VERSION }}
131+
132+
- name: Setup pnpm
133+
uses: pnpm/action-setup@v4
134+
with:
135+
version: ${{ env.PNPM_VERSION }}
136+
run_install: |
137+
- args: []
138+
- args: [--global, typescript]
139+
140+
- name: Setup Go
141+
uses: actions/setup-go@v5
142+
with:
143+
go-version: ${{ env.GO_VERSION }}
144+
cache: true
145+
cache-dependency-path: "go.sum"
146+
147+
- name: Install Wails
148+
shell: bash
149+
run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
150+
151+
- name: Build workspace packages
152+
shell: bash
153+
run: make packages
154+
155+
- name: Build Omniview
156+
shell: bash
157+
run: |
158+
wails build -platform ${{ matrix.platform }} \
159+
${{ matrix.extra-flags }} \
160+
-webview2 download \
161+
-ldflags "\
162+
-X github.com/omniviewdev/omniview/internal/version.Version=0.0.0-pr.${{ github.event.pull_request.number }} \
163+
-X github.com/omniviewdev/omniview/internal/version.GitCommit=${{ github.sha }} \
164+
-X github.com/omniviewdev/omniview/internal/version.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
165+
-X github.com/omniviewdev/omniview/internal/version.Development=true"
166+
167+
- name: Prepare artifact (macOS)
168+
if: runner.os == 'macOS'
169+
run: |
170+
cd build/bin
171+
zip -r Omniview-pr${{ github.event.pull_request.number }}-macos.zip Omniview.app
172+
173+
- name: Prepare artifact (Linux)
174+
if: runner.os == 'Linux'
175+
working-directory: build/bin
176+
run: mv Omniview "Omniview-pr${{ github.event.pull_request.number }}-linux-amd64"
177+
178+
- name: Prepare artifact (Windows)
179+
if: runner.os == 'Windows'
180+
working-directory: build/bin
181+
run: Rename-Item -Path "Omniview.exe" -NewName "Omniview-pr${{ github.event.pull_request.number }}-windows-amd64.exe"
182+
183+
- name: Upload artifact
184+
uses: actions/upload-artifact@v4
185+
with:
186+
name: ${{ matrix.artifact-name }}
187+
path: |
188+
build/bin/*.zip
189+
build/bin/Omniview-pr*
190+
if-no-files-found: error
191+
retention-days: 5

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,7 @@ benchmarks/
5454

5555
# Superpowers brainstorm sessions
5656
.superpowers/
57+
58+
# E2E tests
59+
e2e/test-results/
60+
e2e/playwright-report/

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
.PHONY: docs prepare sync packages dev dev-plugin runtime build
2+
.PHONY: docs prepare sync packages dev dev-plugin runtime build e2e e2e-ui e2e-report
33
.PHONY: check go-build go-vet go-test go-lint bindings bindings-check
44
.PHONY: ui-install ui-build ui-lint ui-typecheck fmt fmt-check
55

@@ -151,3 +151,12 @@ sign:
151151
.PHONY: build-debug
152152
build-debug:
153153
wails build -clean -debug
154+
155+
e2e:
156+
cd e2e && pnpm install && pnpm exec playwright install chromium && pnpm exec playwright test
157+
158+
e2e-ui:
159+
cd e2e && pnpm exec playwright test --ui
160+
161+
e2e-report:
162+
cd e2e && pnpm exec playwright show-report

e2e/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "omniview-e2e",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"test": "playwright test",
7+
"test:ui": "playwright test --ui",
8+
"report": "playwright show-report"
9+
},
10+
"devDependencies": {
11+
"@playwright/test": "1.50.1",
12+
"@types/node": "^20.11.30"
13+
}
14+
}

e2e/playwright.config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { defineConfig } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
timeout: 30_000,
6+
retries: process.env.CI ? 2 : 0,
7+
workers: 1,
8+
reporter: [
9+
['html', { open: 'never' }],
10+
['list'],
11+
],
12+
use: {
13+
baseURL: 'http://localhost:34115',
14+
screenshot: 'only-on-failure',
15+
video: 'on-first-retry',
16+
trace: 'on-first-retry',
17+
},
18+
// Locally, Playwright manages wails dev lifecycle.
19+
// In CI, wails dev is started as a background step because xvfb-run +
20+
// wails dev creates a process tree that doesn't shut down cleanly.
21+
webServer: process.env.CI ? undefined : {
22+
command: 'cd .. && wails dev -loglevel Error',
23+
url: 'http://localhost:34115',
24+
timeout: 120_000,
25+
reuseExistingServer: true,
26+
},
27+
});

e2e/pnpm-lock.yaml

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/tests/fixtures/app.fixture.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { test as base, type Page } from '@playwright/test';
2+
3+
type AppFixtures = {
4+
appPage: Page;
5+
};
6+
7+
export const test = base.extend<AppFixtures>({
8+
appPage: async ({ page }, use) => {
9+
await page.goto('/');
10+
await page.waitForSelector('[data-testid="app-shell"]', { timeout: 15_000 });
11+
await use(page);
12+
},
13+
});
14+
15+
export { expect } from '@playwright/test';

e2e/tests/layout/app-shell.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { test, expect } from '../fixtures/app.fixture';
2+
3+
test.describe('App Shell', () => {
4+
test('loads without errors', async ({ appPage }) => {
5+
await expect(appPage.locator('[data-testid="app-shell"]')).toBeVisible();
6+
});
7+
8+
test('header is visible', async ({ appPage }) => {
9+
await expect(appPage.locator('[data-testid="app-header"]')).toBeVisible();
10+
});
11+
12+
test('sidebar is visible', async ({ appPage }) => {
13+
await expect(appPage.locator('[data-testid="app-sidebar"]')).toBeVisible();
14+
});
15+
16+
test('main content area is visible', async ({ appPage }) => {
17+
await expect(appPage.locator('[data-testid="app-main-content"]')).toBeVisible();
18+
});
19+
20+
test('footer is visible', async ({ appPage }) => {
21+
await expect(appPage.locator('[data-testid="app-footer"]')).toBeVisible();
22+
});
23+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test, expect } from '../fixtures/app.fixture';
2+
3+
test.describe('Navigation', () => {
4+
test('sidebar is visible on load', async ({ appPage }) => {
5+
await expect(appPage.locator('[data-testid="app-sidebar"]')).toBeVisible();
6+
});
7+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { test, expect } from '../fixtures/app.fixture';
2+
3+
test.describe('Bottom Drawer', () => {
4+
test('drag handle is present', async ({ appPage }) => {
5+
// This data-testid already exists in the codebase
6+
const handle = appPage.locator('[data-testid="bottom-drawer-drag-handle"]');
7+
// The drawer may be collapsed by default — just verify the handle exists in DOM
8+
await expect(handle).toBeAttached();
9+
});
10+
});

0 commit comments

Comments
 (0)