Skip to content

Commit 196a501

Browse files
feat: add Playwright e2e testing setup (#1)
* feat: add Playwright e2e testing setup with CI workflow Set up end-to-end testing infrastructure using Playwright based on the wiki app patterns. Includes auth setup, Frappe API helpers, VMS-specific test helpers, an initial projects test suite, and a GitHub Actions workflow for automated UI testing. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix pre-commit formatting issues Run ruff-format and prettier on existing files to pass CI linter checks. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix implicit Optional type annotations (RUF013) Convert `str = None` to `str | None = None` to satisfy ruff RUF013 rule. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: exclude tsconfig JSONC files from check-json pre-commit hook TypeScript tsconfig files use JSONC (comments allowed) which fails strict JSON validation. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: include mandatory owner_user field when creating test projects Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
1 parent 0366edf commit 196a501

20 files changed

Lines changed: 1519 additions & 67 deletions

File tree

.github/workflows/ui-tests.yml

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
name: UI Tests
2+
3+
on:
4+
push:
5+
branches: [develop]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
concurrency:
10+
group: ui-tests-vms-${{ github.event.number || github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
ui-tests:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 60
17+
name: Playwright E2E Tests
18+
19+
services:
20+
redis-cache:
21+
image: redis:alpine
22+
ports:
23+
- 13000:6379
24+
redis-queue:
25+
image: redis:alpine
26+
ports:
27+
- 11000:6379
28+
mariadb:
29+
image: mariadb:10.6
30+
env:
31+
MYSQL_ROOT_PASSWORD: root
32+
ports:
33+
- 3306:3306
34+
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
35+
36+
steps:
37+
- name: Clone
38+
uses: actions/checkout@v4
39+
40+
- name: Setup Python
41+
uses: actions/setup-python@v5
42+
with:
43+
python-version: "3.14"
44+
45+
- name: Setup Node
46+
uses: actions/setup-node@v4
47+
with:
48+
node-version: 24
49+
check-latest: true
50+
51+
- name: Add to Hosts
52+
run: echo "127.0.0.1 vms.test" | sudo tee -a /etc/hosts
53+
54+
- name: Cache pip
55+
uses: actions/cache@v4
56+
with:
57+
path: ~/.cache/pip
58+
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
59+
60+
- name: Get yarn cache directory path
61+
id: yarn-cache-dir-path
62+
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
63+
64+
- name: Cache yarn
65+
uses: actions/cache@v4
66+
with:
67+
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
68+
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
69+
70+
- name: Cache Playwright browsers
71+
uses: actions/cache@v4
72+
with:
73+
path: ~/.cache/ms-playwright
74+
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package.json') }}
75+
76+
- name: Install MariaDB Client
77+
run: |
78+
sudo apt update
79+
sudo apt-get install mariadb-client
80+
81+
- name: Setup Bench
82+
run: |
83+
pip install frappe-bench
84+
bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
85+
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
86+
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
87+
88+
- name: Install VMS
89+
working-directory: /home/runner/frappe-bench
90+
run: |
91+
bench get-app vms $GITHUB_WORKSPACE
92+
bench setup requirements --dev
93+
bench new-site --db-root-password root --admin-password admin vms.test
94+
bench --site vms.test install-app vms
95+
bench build
96+
env:
97+
CI: "Yes"
98+
99+
- name: Configure Site
100+
working-directory: /home/runner/frappe-bench
101+
run: |
102+
bench --site vms.test set-config allow_tests true
103+
bench --site vms.test set-config host_name "http://vms.test:8000"
104+
105+
- name: Start Frappe Server
106+
working-directory: /home/runner/frappe-bench
107+
run: |
108+
sed -i 's/^watch:/# watch:/g' Procfile
109+
sed -i 's/^schedule:/# schedule:/g' Procfile
110+
bench start &> bench_start.log &
111+
echo "Waiting for Frappe server to start..."
112+
timeout 60 bash -c 'until curl -s http://vms.test:8000 > /dev/null; do sleep 2; done'
113+
echo "Frappe server is ready!"
114+
115+
- name: Install Playwright
116+
run: |
117+
cd $GITHUB_WORKSPACE
118+
npm install
119+
npx playwright install --with-deps chromium
120+
121+
- name: Run Playwright Tests
122+
working-directory: ${{ github.workspace }}
123+
run: npx playwright test
124+
env:
125+
BASE_URL: http://vms.test:8000
126+
FRAPPE_USER: Administrator
127+
FRAPPE_PASSWORD: admin
128+
129+
- name: Upload Playwright Report
130+
uses: actions/upload-artifact@v4
131+
if: always()
132+
with:
133+
name: playwright-report
134+
path: playwright-report/
135+
retention-days: 7
136+
137+
- name: Upload Test Results
138+
uses: actions/upload-artifact@v4
139+
if: failure()
140+
with:
141+
name: test-results
142+
path: test-results/
143+
retention-days: 7
144+
145+
- name: Show Bench Logs on Failure
146+
if: failure()
147+
working-directory: /home/runner/frappe-bench
148+
run: |
149+
echo "=== Bench Start Log ==="
150+
cat bench_start.log || true
151+
echo ""
152+
echo "=== Frappe Logs ==="
153+
cat logs/*.log || true

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,9 @@ jspm_packages/
5353

5454
# Aider AI Chat
5555
.aider*
56-
vms/public/frontend/assets
56+
vms/public/frontend/assets
57+
58+
# Playwright
59+
e2e/.auth/
60+
test-results/
61+
playwright-report/

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ repos:
1313
- id: check-merge-conflict
1414
- id: check-ast
1515
- id: check-json
16+
exclude: "tsconfig.*\\.json$"
1617
- id: check-toml
1718
- id: check-yaml
1819
- id: debug-statements

e2e/helpers/auth.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { APIRequestContext, Page } from "@playwright/test";
2+
3+
/**
4+
* Login via Frappe API (faster than UI login).
5+
* Sets cookies on the request context for subsequent API calls.
6+
*/
7+
export async function loginViaAPI(
8+
request: APIRequestContext,
9+
email = "Administrator",
10+
password = "admin",
11+
): Promise<void> {
12+
const response = await request.post("/api/method/login", {
13+
form: {
14+
usr: email,
15+
pwd: password,
16+
},
17+
});
18+
19+
if (!response.ok()) {
20+
throw new Error(`Login failed: ${response.status()} ${await response.text()}`);
21+
}
22+
}
23+
24+
/**
25+
* Login via UI (for testing the login flow itself).
26+
*/
27+
export async function loginViaUI(
28+
page: Page,
29+
email = "Administrator",
30+
password = "admin",
31+
): Promise<void> {
32+
await page.goto("/login");
33+
await page.waitForLoadState("networkidle");
34+
35+
await page.fill('input[data-fieldname="email"]', email);
36+
await page.fill('input[data-fieldname="password"]', password);
37+
await page.click('button[type="submit"]');
38+
39+
await page.waitForURL(/\/(app|desk)/, { timeout: 30000 });
40+
}
41+
42+
/**
43+
* Logout the current user.
44+
*/
45+
export async function logout(page: Page): Promise<void> {
46+
await page.goto("/api/method/logout");
47+
await page.waitForLoadState("networkidle");
48+
}
49+
50+
/**
51+
* Check if user is logged in by verifying session.
52+
*/
53+
export async function isLoggedIn(request: APIRequestContext): Promise<boolean> {
54+
try {
55+
const response = await request.get("/api/method/frappe.auth.get_logged_user");
56+
if (!response.ok()) return false;
57+
58+
const data = await response.json();
59+
return data.message && data.message !== "Guest";
60+
} catch {
61+
return false;
62+
}
63+
}

0 commit comments

Comments
 (0)