mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-07-23 20:21:49 +00:00
310 lines
16 KiB
Markdown
310 lines
16 KiB
Markdown
# Screenshot Testing Design Document
|
|
|
|
## 1. Problem Statement
|
|
|
|
Topola Viewer is a highly interactive, visual genealogy exploration tool that
|
|
renders family trees using complex SVG layouts and D3 configurations. As the
|
|
codebase evolves, minor updates to CSS styles, React components, or underlying
|
|
layout algorithms can easily introduce subtle visual regressions—such as
|
|
overlapping text labels, misaligned parent-child connector lines, or broken
|
|
formatting in the side panels—that standard text-based DOM tests cannot detect.
|
|
To prevent these visual bugs from reaching production, we are introducing an
|
|
automated screenshot (visual regression) testing suite using Playwright. This
|
|
testing suite will capture pixel-perfect snapshots of critical interface states,
|
|
automatically flag unintended visual changes, and guarantee a consistently
|
|
polished, premium user experience across all releases.
|
|
|
|
## 2. The Technical Plan
|
|
|
|
To consistently verify the user interface without introducing complex setups,
|
|
the screenshot testing framework is built on a local-first, self-contained
|
|
execution model. It operates by launching a virtual web browser, running the
|
|
Topola Viewer application inside it, and checking it against stored master
|
|
images (baselines).
|
|
|
|
This setup consists of four major parts working in harmony:
|
|
|
|
1. **The Test Orchestrator (Playwright):** This acts as the central manager. It
|
|
starts our local web server, launches virtual browser instances, automates
|
|
user actions (such as clicking buttons or navigation links), captures the
|
|
screenshots, and does the pixel-by-pixel comparison against our baseline
|
|
master images.
|
|
2. **The Local Web Server:** A background web server hosting the Topola Viewer
|
|
application code. It serves the frontend interface directly to the virtual
|
|
browser so that the test context runs identically to our actual user
|
|
deployments.
|
|
3. **The Network Traffic Controller (Route Interceptor):** An in-memory network
|
|
router managed by the orchestrator. When the browser attempts to download a
|
|
genealogy file (e.g., `family.ged`) or load a person's photo, the router
|
|
intercepts that request and immediately answers it with tiny, predefined test
|
|
datasets (fixtures). This guarantees that the test runs completely offline,
|
|
remains blazingly fast, and has absolute visual predictability.
|
|
4. **The Environment Sanitizer:** A tiny automated script executed directly
|
|
inside the browser window right before a screenshot is snapped. Its only job
|
|
is to locate and overwrite dynamic or shifting text elements (like Git commit
|
|
hashes or changelog dates) with fixed placeholders, ensuring they do not
|
|
trigger false test failures.
|
|
|
|
## 3. Alternatives Considered & Rejected
|
|
|
|
To prevent future developer friction, avoid redundant debugging cycles, and
|
|
establish firm design guardrails, the following technical alternatives were
|
|
evaluated and rejected:
|
|
|
|
### Alternative A: Global Animation Freezing (`freeze=true` Query Parameter)
|
|
|
|
- **Considered:** Forcing Topola's SVG engine to completely freeze all
|
|
animations globally in E2E tests to prevent visual capturing mismatches.
|
|
- **Why Rejected:** Topola Viewer's initial chart mounting is entirely static;
|
|
D3 transitions are only triggered during interactive navigation (e.g.,
|
|
clicking to shift focus to a child node). Since the target snapshots capture
|
|
the initial mount of a chart or isolated panel element, introducing a complex
|
|
global animation freezing hook is redundant. Instead, utilizing Playwright's
|
|
standard auto-waiting mechanism (which pauses until the SVG is fully loaded
|
|
and stationary) provides flawless, stable captures naturally.
|
|
|
|
### Alternative B: Monolithic Reference GEDCOM File (`rich_details.ged`)
|
|
|
|
- **Considered:** Maintaining a single, massive master `.ged` file containing a
|
|
wide collection of custom individuals (with complex names, nested attributes,
|
|
attached photos, and custom events) to serve all tests.
|
|
- **Why Rejected:** Monolithic test fixtures introduce severe coupling and high
|
|
maintenance overhead. If a developer tweaks a birth record to debug an
|
|
event-layout test, it can unintentionally shift elements in unrelated parts of
|
|
the tree, failing baselines for name-formatting or photo rendering. Instead,
|
|
creating microscopic, ad-hoc GEDCOM strings inline within each test case
|
|
guarantees complete visual isolation, makes test intents instantly readable,
|
|
and speeds up parsing.
|
|
|
|
### Alternative C: Build-Time Environment Variable Overrides (Git SHA/Time)
|
|
|
|
- **Considered:** Overriding `VITE_GIT_TIME` and `VITE_GIT_SHA` at build time
|
|
specifically for E2E testing.
|
|
- **Why Rejected:** In production gating pipelines (such as GitHub Actions), the
|
|
application is built and packaged into production-ready assets before the E2E
|
|
job begins execution. Re-compiling Vite assets solely to inject static E2E
|
|
values is slow, resource-intensive, and violates the rule of testing the exact
|
|
binary that will be deployed. Instead, executing an in-browser DOM override
|
|
(`page.evaluate`) right before screenshot execution is lightweight,
|
|
self-contained, and requires zero alterations to the build flow or production
|
|
bundle.
|
|
|
|
### Alternative D: Strict Pixel-Perfect Matching (Zero-Tolerance)
|
|
|
|
- **Considered:** Requiring absolute, 100% visual equivalence with zero pixel
|
|
mismatch allowed.
|
|
- **Why Rejected:** Slight discrepancies in font rendering, subpixel
|
|
antialiasing, and color blending are unavoidable across different operating
|
|
systems (macOS developers vs. Linux CI agents). Enforcing zero-tolerance leads
|
|
to extremely brittle tests that fail constantly due to harmless system-level
|
|
rendering differences. Instead, setting relaxed thresholds
|
|
(`maxDiffPixelRatio: 0.05` and `threshold: 0.2`) filters out system noise
|
|
while aggressively catching genuine layout bugs, overlapping elements, and
|
|
formatting failures.
|
|
|
|
## 4. Detailed Implementation Plan
|
|
|
|
This section defines the granular, step-by-step implementation steps and
|
|
enumerates every file that will be created or modified to complete this visual
|
|
regression framework.
|
|
|
|
### A. Enumeration of Files
|
|
|
|
#### 1. Files to [MODIFY]
|
|
|
|
- **[playwright.config.ts](../playwright.config.ts)**
|
|
- _Rationale:_ Isolate visual regression tests into a separate Playwright
|
|
project (separate from standard functional E2E tests). This allows applying
|
|
dedicated visual settings (like viewport locking, automatic scrollbar
|
|
hiding, and custom screenshot mismatch thresholds) exclusively to visual
|
|
tests without polluting standard E2E runs. Threshold settings are configured
|
|
globally under `expect.toHaveScreenshot`.
|
|
- **[package.json](../package.json)**
|
|
- _Rationale:_ Add dedicated npm script commands to target the standard E2E
|
|
project (`--project=e2e`) and the isolated visual testing project
|
|
(`--project=visual`), preventing slow screenshot tests from bloating
|
|
standard developer verification cycles.
|
|
|
|
#### 2. Files to [NEW]
|
|
|
|
- **`tests/helpers.ts`**
|
|
- _Rationale:_ Provide shared E2E/visual testing helper utilities. Features
|
|
`setupHermeticEnvironment()` to abort external tracking requests and embed
|
|
local fonts (ensuring offline hermetic execution) and `setupGedcomRoute()`
|
|
to serve a standard mock `.ged` dataset.
|
|
- **`tests/intro_visual.spec.ts`**
|
|
- _Rationale:_ Verify the landing page layout, copy block positions, and logo
|
|
alignments. Employs an in-browser DOM script to overwrite dynamic footer
|
|
versioning and dynamic changelog blocks prior to capture, ensuring baseline
|
|
immunity.
|
|
- **`tests/charts_visual.spec.ts`**
|
|
- _Rationale:_ Verify chart canvas boundaries, nodes, colors, and connections.
|
|
Iterates over three of the supported layouts (`Hourglass`, `Relatives`,
|
|
`Donatso`) using a simple tree, and captures screenshots of the stabilized
|
|
D3 canvas.
|
|
- **`tests/details_visual.spec.ts`**
|
|
- _Rationale:_ Verify details panel formats, image margins, fact headers, and
|
|
sources. Defines tiny, ad-hoc mock GEDCOM inline strings for individual edge
|
|
cases (long multi-part names, attached images, nested events) and serves
|
|
pre-existing photo assets (e.g. `docker/examples/photos/photos/I1.jpg`) to
|
|
render photos without broken image layouts.
|
|
- **`tests/config_visual.spec.ts`**
|
|
- _Rationale:_ Verify the visual synchronization between Side Panel settings
|
|
checkboxes/radio inputs and the SVG canvas. Captures full-viewport
|
|
screenshots (at 1280x720) across the three curated configuration
|
|
combinations.
|
|
|
|
### B. Step-by-Step Execution Plan
|
|
|
|
#### Step 1: Visual Project Isolation & Script Provisioning
|
|
|
|
1. Open `playwright.config.ts` and configure separate projects within the
|
|
projects array:
|
|
- Define an `e2e` project using desktop Chrome settings that matches all
|
|
`.spec.ts` files (`testMatch`) but excludes `*_visual.spec.ts` files
|
|
(`testIgnore`).
|
|
- Define a dedicated `visual` project that matches only `*_visual.spec.ts`
|
|
files (`testMatch`), and locks the browser viewport to a width of `1280`
|
|
and height of `720` pixels in the `use` configuration.
|
|
2. Configure custom visual expectation thresholds globally under
|
|
`expect.toHaveScreenshot` (specifically setting `maxDiffPixelRatio` to
|
|
`0.05`, `threshold` to `0.2`, and `animations` to `'disabled'`).
|
|
3. Open `package.json` and update the scripts to target standard and visual
|
|
projects respectively:
|
|
- `"test:e2e": "playwright test --project=e2e"` to run functional E2E tests
|
|
exclusively.
|
|
- `"test:visual": "playwright test --project=visual"` to run visual
|
|
regression tests exclusively.
|
|
- `"test:visual:update": "playwright test --project=visual --update-snapshots"`
|
|
to automatically regenerate baseline reference files.
|
|
|
|
#### Step 2: Landing Page Visual Validation Spec (`tests/intro_visual.spec.ts`)
|
|
|
|
1. Define a test block marked with the `@visual` tag, utilizing
|
|
`setupHermeticEnvironment` helper in `beforeEach`.
|
|
2. Instruct the browser to navigate to the root path `/`.
|
|
3. Right before assertion, trigger `page.evaluate` to clean dynamic elements:
|
|
- Target the `.version` class element and set
|
|
`.innerText = "version: 2026-01-01 00:00 (testcommit)"`.
|
|
- Target the changelog element (the container immediately following the
|
|
"What's new" heading) and replace its HTML with a static placeholder change
|
|
entry.
|
|
4. Snap the screenshot using `expect(page).toHaveScreenshot('intro-page.png')`.
|
|
|
|
#### Step 3: Core SVG Canvas Layouts Spec (`tests/charts_visual.spec.ts`)
|
|
|
|
1. Set up a `beforeEach` block that initializes `setupGedcomRoute(context)` from
|
|
`helpers.ts` to intercept `**/family.ged` requests and fulfill them with raw
|
|
GEDCOM test data.
|
|
2. Write tests with the `@visual` tag iterating through the 3 supported layouts
|
|
(`hourglass`, `relatives`, `donatso`):
|
|
- Set browser route to
|
|
`/#/view?url=https://example.org/family.ged&view=[hourglass|relatives|donatso]`.
|
|
- Determine the appropriate container selector: `#dotatsoSvgContainer` if the
|
|
view is `donatso`, otherwise `#svgContainer`.
|
|
- Locate the container element, and call `locator.waitFor()` to ensure the
|
|
element is fully attached and visible.
|
|
- Wait for D3 rendering and layout stabilization using a brief
|
|
layout-specific timeout (`waitTime`: `500ms` for hourglass/relatives,
|
|
`1500ms` for donatso).
|
|
- Capture the isolated canvas screenshot:
|
|
`expect(container).toHaveScreenshot('chart-[type].png')`.
|
|
|
|
#### Step 4: Details Panel Layouts Spec (`tests/details_visual.spec.ts`)
|
|
|
|
1. Set up a `beforeEach` block to establish hermetic routes via
|
|
`setupHermeticEnvironment(context)`.
|
|
2. Define isolated test blocks with the `@visual` tag, each loading its own
|
|
dedicated inline micro-GEDCOM dataset:
|
|
- **Complex Names Test:**
|
|
- Mock `**/family.ged` with a GEDCOM string containing
|
|
prefix/suffix/rufname tags.
|
|
- Navigate to the view route with `sidePanel=true`, locate the side panel
|
|
container `#sidebar`.
|
|
- Assert sidebar visual representation:
|
|
`expect(page.locator('#sidebar')).toHaveScreenshot('details-complex-name.png')`.
|
|
- **Image / Photo Rendering Test:**
|
|
- Mock `**/family.ged` containing an `OBJE` tag pointing to a photo path
|
|
(e.g. `photos/I1.jpg`).
|
|
- Intercept requests for `**/photos/I1.jpg` and fulfill the request by
|
|
serving the project asset `docker/examples/photos/photos/I1.jpg`.
|
|
- Navigate, wait for the image load handler to complete
|
|
(`img.waitFor({state: 'visible'})` and checking `image.complete` status).
|
|
- Assert sidebar visual representation:
|
|
`expect(page.locator('#sidebar')).toHaveScreenshot('details-photo-render.png')`.
|
|
- **Custom Facts & Citations Test:**
|
|
- Mock `**/family.ged` containing complex nested fact (`FACT`), source
|
|
(`SOUR`), and note (`NOTE`) trees.
|
|
- Select the individual and wait for `#sidebar` to load.
|
|
- Assert sidebar visual representation:
|
|
`expect(page.locator('#sidebar')).toHaveScreenshot('details-events-sources.png')`.
|
|
- **Immediate Family Rendering Test:**
|
|
- Mock `**/family.ged` containing an individual with explicit parental
|
|
links (`FAMC`) and multi-partner spousal families (`FAMS`) to display
|
|
biological parents, spouses, and chronologically sorted children blocks.
|
|
- Select the individual and wait for `#sidebar` to load.
|
|
- Assert sidebar visual representation:
|
|
`expect(page.locator('#sidebar')).toHaveScreenshot('details-immediate-family.png')`.
|
|
|
|
#### Step 5: Configurations Integration Spec (`tests/config_visual.spec.ts`)
|
|
|
|
1. Define a test block tagged `@visual` with a locked browser window viewport
|
|
size of `1280x720` via `playwright.config.ts`.
|
|
2. In `beforeEach`, mock `**/family.ged` using `setupGedcomRoute(context)`, load
|
|
`/view?sidePanel=true`, wait for `#sidebar` and `#content` to be visible, and
|
|
click the "Settings" tab
|
|
(`await page.getByText('Settings', {exact: true}).click();`) to expose config
|
|
fields.
|
|
3. Assert the **Default Configuration (State 1)**:
|
|
- Verify that both the checkbox states and the corresponding
|
|
generation-colored SVG boxes are in alignment.
|
|
- Assert the entire integrated screen:
|
|
`expect(page).toHaveScreenshot('config-state-default.png')`.
|
|
4. Automate panel clicks: Scope locators using
|
|
`page.locator('form.details .item')` to target the "Colors" and "IDs" section
|
|
items. Select the "by sex" color radio button and select the "hide" IDs
|
|
option.
|
|
5. Wait for updates (`page.waitForTimeout(300)`) and assert the **Sex Colors &
|
|
No IDs Configuration (State 2)**:
|
|
- Assert the entire integrated screen:
|
|
`expect(page).toHaveScreenshot('config-state-gender-no-ids.png')`.
|
|
6. Automate panel clicks: Scope locators using
|
|
`page.locator('form.details .item')` to target the "Colors" and "Sex" section
|
|
items. Select the "none" color radio button and select the "hide" sex option.
|
|
7. Wait for updates (`page.waitForTimeout(300)`) and assert the **Minimalist
|
|
Configuration (State 3)**:
|
|
- Assert the entire integrated screen:
|
|
`expect(page).toHaveScreenshot('config-state-minimalist.png')`.
|
|
|
|
## 5. CI/CD Pipeline Integration
|
|
|
|
To ensure that no visual regressions are introduced into the master branch, the
|
|
visual testing suite is integrated into the GitHub Actions CI/CD workflow
|
|
([node.js.yml](../.github/workflows/node.js.yml)) alongside existing tests.
|
|
|
|
### Pipeline Configuration
|
|
|
|
Visual tests run sequentially after standard E2E tests. The workflow executes
|
|
the following steps:
|
|
|
|
1. **Install Dependencies:** Resolves Node.js package dependencies and
|
|
installs/caches Playwright browser binaries (specifically `chromium`).
|
|
2. **Run E2E Tests:** Runs standard functional tests using `npm run test:e2e`.
|
|
3. **Run Visual Tests:** Runs visual regression tests using
|
|
`npm run test:visual`.
|
|
|
|
### Playwright HTML Reports in CI
|
|
|
|
To prevent test reports from overwriting each other, the HTML reports for the
|
|
different testing suites are output to distinct subdirectories within the
|
|
`playwright-report` folder:
|
|
|
|
- **E2E Tests:** Saved to `playwright-report/e2e` by setting the
|
|
`PLAYWRIGHT_HTML_REPORT` environment variable.
|
|
- **Visual Tests:** Saved to `playwright-report/visual` by setting the
|
|
`PLAYWRIGHT_HTML_REPORT` environment variable.
|
|
|
|
Both reports are bundled and uploaded as a single workflow artifact
|
|
(`playwright-report-${{ matrix.node-version }}`) on completion, allowing easy
|
|
review of failures.
|