Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b2c8c52
Expand E2E testing
alex-rawlings-yyc Jul 6, 2026
ed2c07a
Fix test failures, lint errors
alex-rawlings-yyc Jul 7, 2026
00a9cb6
Improve e2e helpers
alex-rawlings-yyc Jul 7, 2026
36c1e5e
Anchor Interlinearizer dock-tab locators in e2e helpers
alex-rawlings-yyc Jul 7, 2026
c36beb2
Fix e2e helper project-selection and unify draft dirty-state gate
alex-rawlings-yyc Jul 7, 2026
f26c659
Make CDP feature tier self-launch; fix strict-mode tab locators
alex-rawlings-yyc Jul 9, 2026
47905e5
Run full e2e suite on Windows; make CDP teardown Windows-safe
alex-rawlings-yyc Jul 9, 2026
ec525a1
Suggested changes on expand-e2e-testing (#160)
imnasnainaec Jul 10, 2026
0c3705c
Harden CDP e2e tier against shared-instance overlay cascade
alex-rawlings-yyc Jul 10, 2026
631a080
Fix e2e CI failures: DevTools viewport squeeze, Unknown-tab startup
alex-rawlings-yyc Jul 10, 2026
cd01eed
Add test:e2e:headless to reproduce the Linux CI e2e run locally
alex-rawlings-yyc Jul 10, 2026
536cb15
Reject non-positive PIDs in killProcessTree
alex-rawlings-yyc Jul 10, 2026
1616cd1
Retry Windows paranext-core npm ci to survive cache-race flake
alex-rawlings-yyc Jul 10, 2026
dc77c3f
chore: nudge CI to re-report CodeRabbit status
alex-rawlings-yyc Jul 10, 2026
c744b19
Stop leaked project-picker tabs cascading CDP e2e failures
alex-rawlings-yyc Jul 13, 2026
e2090f2
Stop e2e nav hanging on the disabled platform toolbar control
alex-rawlings-yyc Jul 13, 2026
718ac70
Stop one stray "Unknown" tab cascading the shared CDP e2e tier
alex-rawlings-yyc Jul 13, 2026
446ddcd
Fail fast when a CDP feature test's shared instance has died
alex-rawlings-yyc Jul 13, 2026
6e137cc
Gate e2e readiness on service-host registration
alex-rawlings-yyc Jul 13, 2026
92fe203
Trim version-pinned upstream detail from e2e helper comments
alex-rawlings-yyc Jul 13, 2026
4553ee5
Arm fatal-startup tripwire across both e2e readiness stages
alex-rawlings-yyc Jul 13, 2026
51f3171
Write smoke e2e startup log to e2e-tests/, not fixtures/
alex-rawlings-yyc Jul 13, 2026
ff77a3a
Route smoke e2e kill paths through killProcessTree
alex-rawlings-yyc Jul 13, 2026
fd77dfd
Raise CDP e2e readiness-budget floor to survive one poll
alex-rawlings-yyc Jul 13, 2026
99c0023
Trim redundant e2e comments and docs per review
alex-rawlings-yyc Jul 14, 2026
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
42 changes: 36 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ jobs:
working-directory: extension-repo
run: npm run test:coverage

e2e-smoke:
e2e:
runs-on: ${{ matrix.os }}
strategy:
# Don't cancel the other OS's e2e run when one fails — we want both reports.
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
Expand Down Expand Up @@ -86,12 +88,34 @@ jobs:
# transpile TypeScript on Windows.
run: npm ci --ignore-scripts

- name: Install core dependencies
- name: Install core dependencies (Linux)
if: matrix.os == 'ubuntu-latest'
working-directory: paranext-core
# Cannot --omit=optional: lightningcss native binaries are optional deps required by the
# webpack build.
run: npm ci --ignore-scripts

- name: Install core dependencies (Windows)
if: matrix.os == 'windows-latest'
working-directory: paranext-core
shell: pwsh
# Same install as Linux, but retried: this full install fetches the most packages
# through the shared C:\npm\cache and intermittently races on _cacache\tmp (EEXIST) or hits a
# locked node_modules file during npm's pre-install cleanup (EPERM rmdir). `npm cache verify`
# between attempts evicts the corrupt/partial cache entry so the retry starts clean.
run: |
$ErrorActionPreference = 'Continue'
for ($attempt = 1; $attempt -le 3; $attempt++) {
Write-Host "npm ci attempt $attempt of 3"
npm ci --ignore-scripts
if ($LASTEXITCODE -eq 0) { exit 0 }
Write-Host "npm ci failed (exit $LASTEXITCODE); verifying cache before retry"
npm cache verify
if ($LASTEXITCODE -ne 0) { Write-Host "npm cache verify failed (exit $LASTEXITCODE)" }
}
Write-Error 'npm ci failed after 3 attempts'
exit 1

- name: Install Electron binary
working-directory: paranext-core
run: node node_modules/electron/install.js
Expand All @@ -112,15 +136,16 @@ jobs:
working-directory: paranext-core
run: npm run build:dll

- name: Run e2e smoke tests (Linux)
- name: Run e2e tests (Linux)
if: matrix.os == 'ubuntu-latest'
working-directory: extension-repo
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" npm run test:e2e:smoke
# Linux needs an xvfb display
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" npm run test:e2e

- name: Run e2e smoke tests (Windows)
- name: Run e2e tests (Windows)
if: matrix.os == 'windows-latest'
working-directory: extension-repo
run: npm run test:e2e:smoke
run: npm run test:e2e

- name: Upload Playwright test results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand All @@ -129,6 +154,11 @@ jobs:
name: playwright-results-${{ matrix.os }}
retention-days: 7
path: |
extension-repo/e2e-tests/.cdp-app-startup.log
extension-repo/e2e-tests/.smoke-app-startup.log
extension-repo/e2e-tests/playwright-report/
extension-repo/e2e-tests/test-results/
if-no-files-found: warn
# upload-artifact excludes hidden files by default (since v4.4), which silently dropped
# the dotfile app-startup log from every artifact.
include-hidden-files: true
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ temp-build
# AI files
.claude

# Playwright test output
# Playwright output
e2e-tests/.cdp-*
e2e-tests/.dev-*
e2e-tests/playwright-report
e2e-tests/test-results
2 changes: 2 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"BBCCCVVV",
"believ",
"clickability",
"cmdk",
"cullable",
"deconflict",
"deconfliction",
Expand Down Expand Up @@ -65,6 +66,7 @@
"Stylesheet",
"typedefs",
"unanalyzed",
"unglossed",
"unhover",
"unobserves",
"unphrased",
Expand Down
27 changes: 26 additions & 1 deletion e2e-tests/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# e2e-tests

End-to-end tests for the interlinearizer extension using Playwright + Electron. The suite launches a real Platform.Bible instance with the extension loaded via `--extensions` and verifies the extension starts up correctly. Currently contains one smoke test confirming the extension activates and registers its PAPI command.
End-to-end tests for the interlinearizer extension using Playwright + Electron. The suite has two tiers:

- **Smoke tests** (`tests/smoke/`, `app.fixture`) launch a fresh Platform.Bible instance with the extension loaded via `--extensions` and verify the extension starts up correctly.
- **Feature tests** (`tests/features/`, `cdp.fixture`) connect over CDP to a running Platform.Bible instance and exercise interlinearizer UI flows (glossing, draft persistence, project modals).

Run everything with `npm run test:e2e` (smoke tier then CDP tier). Each tier can be run alone with `npm run test:e2e:smoke` and `npm run test:e2e:cdp`.

Both tiers are self-launching: the CDP tier's `globalSetup` launches its own Platform.Bible instance (with `--remote-debugging-port=9223`) in an isolated user-data dir and tears it down afterward, so `npm run test:e2e:cdp` needs no manual `npm run start:cdp` first. To iterate against a warm instance instead, run `npm run start:cdp` in one terminal, then run the CDP config directly with `npx playwright test --config e2e-tests/playwright-cdp.config.ts`: the setup detects the in-use CDP port, reuses that instance, and leaves it running.

In CI (`.github/workflows/test.yml`, `e2e` job) the full suite runs on both Linux and Windows.

To reproduce the Linux CI run locally before pushing, run `npm run test:e2e:headless` (requires `xvfb` — `sudo apt install xvfb`). It runs the full suite on the same virtual 1280x960 display CI uses, so nothing appears on screen and window geometry matches CI exactly. Ports 1212, 8876, and 9223 must be free (close any running Platform.Bible or renderer dev server first). A green local run does not cover the Windows leg, and CI's slower runners can still surface timing-dependent flakes, but it catches layout, selector, and logic failures before they reach CI.

**Contents:**

Expand All @@ -17,3 +28,17 @@ These tests are adapted from `paranext-core`'s e2e suite with changes to support
- **Extension launch helper** — `fixtures/helpers.ts` uses `launchElectronWithExtension()` instead of `launchElectronApp()`. It passes `--extensions <dist>` to the Electron process, resolves the Electron binary from paranext-core's `node_modules`, and polls `rpc.discover` for the extension's PAPI method to confirm activation.
- **Window finding** — `fixtures/app.fixture.ts` manually polls `electronApp.windows()` by URL instead of calling `electronApp.firstWindow()`, because the extension injects content into an existing window rather than being the sole owner of the renderer.
- **Renderer readiness** — `global-setup.ts` adds an HTTP GET probe after the TCP port check to wait for webpack compilation to finish, rather than assuming the port being open means the bundle is ready.

## Writing feature tests

Feature tests run with `npm run test:e2e:cdp`. That command launches a fresh, isolated instance, but the tests are also run against a shared, long-lived `npm run start:cdp` instance during local iteration (see above), so they must assume nothing about the instance's state and must leave nothing behind that could poison the next run. The protocol:

- **Import from `cdp.fixture`, never `app.fixture`.** The CDP config already serializes execution (`workers: 1`), so tests never race each other on the shared instance.
- **The instance is only ever used with the WEB project.** This is an operating assumption, not something tests verify: `ensureInterlinearizerOpenOnWeb()` trusts an existing Interlinearizer tab and only picks WEB when opening fresh. Don't point `start:cdp` at other projects.
- **Mutating tests operate on the dedicated "E2E Test Project", never on a developer's own projects.** `ensureE2eProjectActive()` opens it (creating it on first use) at the start of each mutating test. Because the draft is the single per-source working buffer, replacing it could destroy unsaved developer work — so when the draft is dirty and the active project is _not_ the e2e project, the helper first saves the draft into a new `e2e-rescued-work-<timestamp>` project. Rescue projects are backups, not junk: delete them manually once recovered. Dirty state left while the e2e project is active is treated as leftover test data and discarded.
- **Self-establish every precondition.** A mutating test's setup composes these steps as needed: `ensureInterlinearizerOpenOnWeb()`, `ensureE2eProjectActive()`, `navigateToScriptureRef()` (the scroll-group reference could be anywhere), and `wipeDraft()`. They stay as separate calls rather than one wrapper on purpose — tests pick the subset they need (e.g. the draft-persistence reopen deliberately runs only open + navigate, skipping the project reset and wipe that would destroy the state it is verifying).
- **Reset at the start, tidy at the end.** Correctness rests on the start-of-test sequence, which self-heals whatever a failed run left behind (leftover modals, stray project pickers, another test's dirty draft); see the JSDocs on `ensureInterlinearizerOpenOnWeb()` and `ensureE2eProjectActive()` for the mechanism. This self-healing is also what lets the CDP config safely `retries` in CI. Mutating tests additionally end with `ensureE2eProjectActive(page, { rescueDirtyDraft: false })` to discard their own leftovers — a courtesy to the next run, not something correctness depends on.
- **Use unique per-run values** (e.g. `` `e2e-gloss-${Date.now()}` ``) for anything written into the draft, so a stale leftover can never satisfy an assertion.
- **Drive only the visible UI.** No JSON-RPC/WebSocket calls to set up or assert state (the rpc.discover readiness polls in the shared helpers are the one sanctioned exception).
- **Prefer existing accessible selectors** (roles, aria-labels like `Gloss for {word}`, ModalShell title ids) over adding new `data-testid`s to production code.
- **Mutating tests must not overwrite or delete projects, and must not create any beyond what `ensureE2eProjectActive()` creates** (the e2e project itself, plus rescue projects). The current modal coverage is a read-only cancel tour; a create/delete lifecycle test needs its own self-healing cleanup (e.g. deleting leftover `e2e-*` projects at start) before it's safe on a shared instance.
8 changes: 5 additions & 3 deletions e2e-tests/fixtures/cdp.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
*
* - No app restart needed (no port 8876 conflict)
* - Tests run against the same app instance used during development
* - No teardown/shutdown of the app on completion
* - No teardown/shutdown of the app by the fixture on completion
*
* Prerequisite: Platform.Bible running with --remote-debugging-port=9223 and the interlinearizer
* extension loaded.
* The app is provided by the CDP config's `globalSetup` (which self-launches one for `npm run
* test:e2e:cdp`) or by a developer's own `npm run start:cdp`; either way it must be running with
* `--remote-debugging-port=9223` and the interlinearizer extension loaded by the time this fixture
* connects.
*/
import { test as base, chromium, Page } from '@playwright/test';

Expand Down
Loading
Loading