Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: E2E Tests

on:
push:
branches: ['19.0']
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
spec:
- tests/01-spp-starter-spmis.spec.ts
- tests/02-spp-starter-farmer-registry.spec.ts

steps:
- uses: actions/checkout@v4

- name: Start OpenSPP stack
run: docker compose --profile ui up -d --build

- name: Wait for OpenSPP to be healthy
run: |
echo "Waiting for http://localhost:8069/web/health ..."
for i in $(seq 1 30); do
if curl -sf http://localhost:8069/web/health; then
echo "OpenSPP is ready"
exit 0
fi
echo "Attempt $i/30 — sleeping 10s"
sleep 10
done
echo "OpenSPP did not become healthy in time"
docker compose --profile ui logs
exit 1

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: e2e/package.json

- name: Install Playwright
working-directory: e2e
run: |
npm install
npx playwright install chromium --with-deps

- name: Run E2E tests
working-directory: e2e
env:
ODOO_URL: http://localhost:8069
run: npx playwright test ${{ matrix.spec }}

- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ strategy.job-index }}
path: e2e/reports/html
retention-days: 7

- name: Record spec result
if: always()
run: |
case "${{ matrix.spec }}" in
*spmis*) NAME="SP-MIS" ;;
*farmer*) NAME="Farmer Registry" ;;
*) NAME="${{ matrix.spec }}" ;;
esac
mkdir -p status
echo "{\"name\": \"$NAME\", \"result\": \"${{ job.status }}\"}" > status/result.json

- name: Upload spec result
if: always()
uses: actions/upload-artifact@v4
with:
name: test-status-${{ strategy.job-index }}
path: status/result.json

notify:
needs: e2e
if: always()
runs-on: ubuntu-latest
steps:
- name: Download spec results
uses: actions/download-artifact@v4
with:
pattern: test-status-*
path: statuses
merge-multiple: true

- name: Post result to Discord
run: |
COLOR=3066993
if [ "${{ needs.e2e.result }}" != "success" ]; then COLOR=15158332; fi

FIELDS="[]"
for f in statuses/*.json; do
NAME=$(jq -r .name "$f")
RESULT=$(jq -r .result "$f")
if [ "$RESULT" == "success" ]; then EMOJI="✅"; else EMOJI="❌"; fi
FIELDS=$(echo "$FIELDS" | jq --arg n "$NAME" --arg v "$RESULT $EMOJI" '. + [{"name": $n, "value": $v, "inline": false}]')
done

PAYLOAD=$(jq -n \
--arg title "OpenSPP E2E Tests" \
--arg url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--argjson color "$COLOR" \
--argjson fields "$FIELDS" \
'{embeds: [{title: $title, url: $url, color: $color, fields: $fields}]}')

curl -sf -H "Content-Type: application/json" -X POST -d "$PAYLOAD" "${{ secrets.DISCORD_WEBHOOK }}"
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,9 @@ docker/fluentd/log/
.actrc
scripts/run_ci_local.sh
.github/workflows/ci-local.yml

# E2E tests (Playwright)
e2e/node_modules/
e2e/test-results/
e2e/playwright-report/
e2e/blob-report/
75 changes: 75 additions & 0 deletions e2e/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# E2E Tests

Playwright-based end-to-end tests for OpenSPP.

## Prerequisites

- Docker running with WSL Ubuntu
- Node.js 20+ installed
- Playwright and Chromium installed

## First-time setup

```bash
cd e2e
npm install
npx playwright install chromium --with-deps
```

## Running tests

```bash
cd e2e
npx playwright test --headed
```

Tests run sequentially (1 worker). Each test file resets the database before running.

## Test files

| File | Module | Description |
|------|--------|-------------|
| `01-spp-starter-spmis.spec.ts` | `spp_starter_sp_mis` | Installs OpenSPP Starter SP-MIS and verifies nav menus |
| `02-spp-starter-farmer-registry.spec.ts` | `spp_starter_farmer_registry` | Installs OpenSPP Starter Farmer Registry and verifies nav menus |

## How each test works

1. `beforeAll` — tears down and rebuilds a fresh Docker stack via `docker compose --profile ui down -v && docker compose --profile ui up -d`
2. Waits for Odoo to be healthy at `http://localhost:8069/web/health`
3. Logs in as `admin` / `admin`
4. Goes to Apps, removes the preset filter, searches by module technical name
5. Clicks Activate and waits for installation to complete
6. Waits 1 minute for nav menus to settle
7. Verifies nav menus are present and takes a screenshot

## Why `docker compose down -v && up` instead of `spp resetdb`

The `spp` CLI has a `resetdb` command that resets only the database (faster, keeps containers running). However, this workflow is designed to run automatically on every merge to `branch 19.0` via GitHub Actions. In that context, a full `docker compose down -v && up` is intentional because:

- It pulls and uses the latest image built from the merged code
- It guarantees no leftover state from previous runs (volumes, cached data)
- It ensures every test run reflects exactly what was merged

Use `spp resetdb` for local dev iteration only. Do not replace the `docker compose` reset in `helpers.ts` for CI purposes.

## Adding new tests

When adding a new test to any spec file:

1. Use `test.describe.serial` and share a single `page` across all tests via `let page: Page` at the describe scope
2. Number tests sequentially (`01`, `02`, `03` …) so the report shows them in order
3. **Always update the comment block at the top of the spec file** to include the new test — one line per test in the format:
```
// NN - Plain-language description of what the test does
```
4. Use codegen to record actions (`npx playwright codegen http://localhost:8069`), then polish the output — remove redundant clicks, replace CSS locators with role/label/placeholder locators, add `waitForLoadState`, and add `expect` assertions to verify the result was saved

## Reports

After a run, open the HTML report:
```bash
cd e2e
npm run report
```

Screenshots on failure and post-install screenshots are saved in `e2e/reports/`.
10 changes: 10 additions & 0 deletions e2e/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM mcr.microsoft.com/playwright:v1.44.0-jammy

WORKDIR /app

COPY package.json ./
RUN npm install

COPY . .

CMD ["npx", "playwright", "test"]
Binary file added e2e/fixtures/sample_BIR_2316.pdf
Binary file not shown.
Binary file added e2e/fixtures/sample_academic_calendar.pdf
Binary file not shown.
Binary file added e2e/fixtures/sample_authorization_letter.pdf
Binary file not shown.
Binary file added e2e/fixtures/sample_cert_of_enrolment.pdf
Binary file not shown.
Binary file added e2e/fixtures/sample_valid_ID_parent.pdf
Binary file not shown.
96 changes: 96 additions & 0 deletions e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "openspp-e2e",
"version": "1.0.0",
"private": true,
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"report": "playwright show-report reports/html"
},
"devDependencies": {
"@playwright/test": "^1.44.0",
"@types/node": "^25.7.0"
}
}
25 changes: 25 additions & 0 deletions e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './tests',
timeout: 600_000, // 10 min — covers 2x app installs + 3 min wait
workers: 1,
retries: 0,
reporter: [['list'], ['html', { outputFolder: 'reports/html', open: 'never' }]],

use: {
baseURL: process.env.ODOO_URL ?? 'http://localhost:8069',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
launchOptions: {
slowMo: 500, // 0.5s delay between actions
},
},

projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
Loading
Loading