`, so the selector `.ui.error.message`
matches it.
- `.ui.errorPopup.message` — dismissable popup error (see
`src/components/error_display.tsx`). As above, `ui` and `message` come from
Semantic UI React's `
`, and `errorPopup` comes from the custom
`className="errorPopup"` prop. Note: `ErrorPopup` uses Semantic UI React's
``, which renders its content at `document.body` level, not inside
`#content` in the DOM. When the popup is closed (`open={false}`), the Portal
renders nothing, so this assertion verifies absence rather than visibility.
The side panel is expanded by default on desktop viewports (the prober project
uses `devices['Desktop Chrome']`). The `getShowSidePanel` function in
`src/util/url_args.ts` returns `true` on non-mobile screens, so the
`div.details` container is visible without any URL parameters.
All prober selectors use `data-testid` attributes (e.g.,
`data-testid="content"`, `data-testid="chart"`, `data-testid="details"`,
`data-testid="error-message"`, `data-testid="error-popup"`) rather than CSS
classes or element IDs. This makes selectors resilient to CSS class refactors
and Semantic UI React internal changes. The `data-testid` attributes are added
to the source components alongside existing IDs and classes. A shared helper
(`tests/probers/helpers.ts`) encapsulates the prober flow and selector logic,
eliminating duplication across spec files.
### Step 3: Prober GitHub Actions workflows
All prober workflows should declare minimal permissions for security:
```yaml
permissions:
contents: read
actions: write
```
All prober workflows should use `actions/checkout@v4` (not v2, which is used by
some older deploy workflows).
All prober workflows should set `timeout-minutes: 15` on each job to prevent
hanging runs from consuming runner minutes (default GitHub Actions timeout is 6
hours).
All prober workflows should define a `concurrency` group to prevent overlapping
runs (e.g., a deploy-triggered run overlapping with a schedule-triggered run):
```yaml
concurrency:
group: prober-${{ github.workflow }}
cancel-in-progress: false
```
`cancel-in-progress: false` ensures a deploy-triggered run is not cancelled by a
scheduled run — both complete independently.
Four reusable workflow files, one per prober. The three live-URL probers are
identical in structure — only the name and artifact name differ. The Docker
prober has a different structure (it builds and runs the container before
testing).
**Create:** `.github/workflows/prober-wikitree.yml`
- **Triggers:** `workflow_call` (with `wait_for_propagation` input),
`workflow_dispatch` (with `wait_for_propagation` input), `schedule` (daily at
`0 5 * * *` UTC = ~6:00/7:00 CET).
- **Depends on (when called from deploy):** `deploy-wikitree-apps.yml` only.
- **Artifact name:** `prober-report-wikitree`.
**Create:** `.github/workflows/prober-gh-pages.yml`
- Same structure.
- **Depends on (when called from deploy):** `deploy-gh-pages.yml` only.
- **Artifact name:** `prober-report-gh-pages`.
**Create:** `.github/workflows/prober-wikitree-cors.yml`
- Same structure.
- **Depends on (when called from deploy):** `deploy-wikitree-apps.yml` only.
- **Artifact name:** `prober-report-wikitree-cors`.
**Create:** `.github/workflows/prober-docker.yml`
- **Triggers:** `workflow_call`, `workflow_dispatch`, `schedule` (daily at
`0 5 * * *` UTC).
- **Depends on (when called from deploy):** `deploy-docker.yml` only.
- **Artifact name:** `prober-report-docker`.
- **No `wait_for_propagation` input** — The Docker container is available
immediately after startup; no propagation delay is needed.
**Shared workflow structure** (live-URL probers):
```
1. Checkout repository (actions/checkout@v4).
2. Setup Node.js 24.x with npm cache.
3. Run npm ci.
4. If wait_for_propagation is true, sleep 180 seconds.
5. Get Playwright version (same pattern as node.js.yml: extract version
from @playwright/test/package.json into a cache key).
6. Cache Chromium browser binaries (keyed by Playwright version). If cache
misses, install Playwright with system dependencies
(npx playwright install-deps chromium && npx playwright install
chromium). With daily + post-deploy runs, caching avoids re-downloading
~150MB on every run.
7. Run: npx playwright test --config=playwright.prober.config.ts "${SPEC}"
Each workflow sets a SPEC environment variable (e.g.,
SPEC=wikitree.spec.ts) to select only the relevant spec file. Without
this filter, Playwright would run all specs in the testDir for every
prober workflow.
8. Upload Playwright HTML report as artifact (if: always()). Set
PLAYWRIGHT_HTML_REPORT=playwright-report/prober to avoid path
conflicts with other report artifacts. Set `retention-days: 30` to
limit storage consumption — prober runs (daily + post-deploy) generate
traces, screenshots, and videos that can accumulate quickly.
```
**Docker prober workflow structure** (different from live-URL probers):
````
1. Checkout repository (actions/checkout@v4).
2. Pull Docker image: docker pull ghcr.io/pewu/topola-viewer:latest
(Pull the image published by deploy-docker.yml. This tests the actual
published artifact, not a local build. The GHCR package is public, so
no `docker login` authentication step is required.)
3. Record image digest: docker inspect --format='{{index .RepoDigests 0}}'
ghcr.io/pewu/topola-viewer:latest. Prints the full digest (e.g.,
ghcr.io/pewu/topola-viewer@sha256:abc123...) to the workflow log and
as a GitHub Actions notice. This provides traceability — if the prober
fails, you can verify which exact image was tested.
4. Run container: docker run -d -p 8080:8080 -e STATIC_URL=test.ged
-v $(pwd)/src/datasource/testdata/test.ged:/app/public/test.ged
--name topola-prober-${{ github.run_id }}
ghcr.io/pewu/topola-viewer:latest
(Use a unique container name with github.run_id to prevent name
conflicts if a previous run didn't clean up or if runs overlap.)
5. Setup Node.js 24.x with npm cache.
6. Run npm ci.
7. Get Playwright version (same pattern as node.js.yml).
8. Cache and install Playwright (same as live-URL probers).
9. Wait for container to be ready: use a bash retry loop with `curl` to
poll `http://localhost:8080/` until it responds with HTTP 200
(timeout 30s, 1s interval):
```bash
for i in $(seq 1 30); do
if curl -sf -o /dev/null http://localhost:8080/; then break; fi
sleep 1
done
curl -sf -o /dev/null http://localhost:8080/
````
The final `curl` ensures the workflow fails with a clear error if the container
never became ready. This prevents a race condition where the test runs before
Caddy is ready to serve requests. This step runs after Node/Playwright setup so
the container doesn't sit idle during dependency installation. The Docker spec
also includes a guard that checks if localhost:8080 is reachable before running
the prober. If the container is not running (e.g., when running probers locally
without Docker), the test is skipped with a helpful message instead of failing
with a confusing ECONNREFUSED error. 10. Run: npx playwright test
--config=playwright.prober.config.ts docker.spec.ts 11. Upload Playwright HTML
report as artifact (if: always()). Set `retention-days: 30` (same as live-URL
probers). 12. Stop and remove container (if: always()): docker stop
topola-prober-${{ github.run_id }} 2>/dev/null || true; docker rm
topola-prober-${{ github.run_id }}
2>/dev/null || true (The if: always() ensures cleanup runs even on failure. The
2>/dev/null and trailing true prevent errors if the container was never started,
e.g., pull failed at step 2.)
````
**`wait_for_propagation` input flag** (live-URL probers only):
* Defined under both `workflow_call` and `workflow_dispatch` triggers.
* Type: `boolean`, default: `false`.
* When invoked from `deploy-everywhere.yml`, passed as `true`.
* When triggered by schedule or manual (unchecked), defaults to `false`.
* The sleep step uses `if: inputs.wait_for_propagation` to conditionally
execute.
* The Docker prober workflow does not define this input — the container is
available immediately after `docker run`, so no propagation wait is needed.
### Step 4: Modify deploy-everywhere workflow
**Modify:** `.github/workflows/deploy-everywhere.yml`
Add four prober jobs that call the reusable prober workflows. Each prober
depends only on its relevant deploy job, not on all deploys.
Current state (before changes):
```yaml
jobs:
deploy-gh-pages:
uses: ./.github/workflows/deploy-gh-pages.yml
secrets: inherit
deploy-wikitree-apps:
uses: ./.github/workflows/deploy-wikitree-apps.yml
secrets: inherit
deploy-docker:
uses: ./.github/workflows/deploy-docker.yml
secrets: inherit
````
After changes:
```yaml
jobs:
deploy-gh-pages:
uses: ./.github/workflows/deploy-gh-pages.yml
secrets: inherit
deploy-wikitree-apps:
uses: ./.github/workflows/deploy-wikitree-apps.yml
secrets: inherit
deploy-docker:
uses: ./.github/workflows/deploy-docker.yml
secrets: inherit
prober-wikitree:
needs: deploy-wikitree-apps
uses: ./.github/workflows/prober-wikitree.yml
secrets: inherit
with:
wait_for_propagation: true
prober-gh-pages:
needs: deploy-gh-pages
uses: ./.github/workflows/prober-gh-pages.yml
secrets: inherit
with:
wait_for_propagation: true
prober-wikitree-cors:
needs: deploy-wikitree-apps
uses: ./.github/workflows/prober-wikitree-cors.yml
secrets: inherit
with:
wait_for_propagation: true
prober-docker:
needs: deploy-docker
uses: ./.github/workflows/prober-docker.yml
secrets: inherit
```
Rationale for dependency mapping:
- `prober-wikitree` needs `deploy-wikitree-apps` — it tests the WikiTree
deployment.
- `prober-gh-pages` needs `deploy-gh-pages` — it tests the GitHub Pages
deployment.
- `prober-wikitree-cors` needs `deploy-wikitree-apps` — it tests the WikiTree
deployment (with CORS proxy).
- `prober-docker` needs `deploy-docker` — it tests the Docker image published to
GHCR by `deploy-docker.yml` (Dockerfile, Caddy config, app startup). It does
not pass `wait_for_propagation` because the container is available immediately
after `docker run`.
- If a prober fails, the `deploy-everywhere` workflow is marked as failed (red
X), triggering an email notification (if GitHub email notifications are
enabled — see note in Section 2).
- Note: Individual deploy workflows (`deploy-gh-pages.yml`,
`deploy-wikitree-apps.yml`, `deploy-docker.yml`) also support
`workflow_dispatch`. If a deploy is triggered directly (instead of through
`deploy-everywhere.yml`), no probers run because probers are only called from
`deploy-everywhere.yml`. To ensure probers always run after a deploy, always
trigger deploys through `deploy-everywhere.yml`.
### Step 5: Update supporting files
**Modify:** `playwright.config.ts`
Add `testIgnore: ['*_visual.spec.ts', 'probers/**']` to the e2e project to
prevent prober specs in `tests/probers/` from being discovered by the regular CI
e2e test run. Without this, `npm run test:e2e` would try to execute prober specs
against the local dev server, causing failures.
**Modify:** `package.json`
Add a `test:probers` script for running probers locally during development:
```json
"test:probers": "playwright test --config=playwright.prober.config.ts"
```
**Modify:** `tests/tsconfig.json`
Add `probers/` to the `include` array so prober specs are type-checked by
`tsc -p tests/tsconfig.json --noEmit` (which runs in CI via `node.js.yml`).
Current state:
```json
{
"compilerOptions": { ... },
"include": ["./**/*.ts", "./**/*.d.ts"]
}
```
The existing `./**/*.ts` glob already includes `tests/probers/` — no
modification needed. The `./**/*.d.ts` glob covers type declaration files and
does not affect prober spec discovery.
**Modify:** `.github/workflows/README.md`
Add entries for the four new prober workflows to the file registry, e.g.:
```markdown
- [prober-wikitree.yml](prober-wikitree.yml): Reusable prober that smoke-tests
the WikiTree direct API path on the live WikiTree deployment. Runs daily and
after deploy.
- [prober-gh-pages.yml](prober-gh-pages.yml): Reusable prober that smoke-tests
the GitHub Pages deployment with GEDCOM-from-URL through the CORS proxy. Runs
daily and after deploy.
- [prober-wikitree-cors.yml](prober-wikitree-cors.yml): Reusable prober that
smoke-tests the CORS proxy from the WikiTree deployment with GEDCOM-from-URL.
Runs daily and after deploy.
- [prober-docker.yml](prober-docker.yml): Reusable prober that smoke-tests the
published Docker image from GHCR (Dockerfile, Caddy config, app startup) by
pulling and running it locally. Runs daily and after deploy.
```
**Create:** `tests/probers/README.md`
Document the prober test directory, explaining that these are live smoke tests
(not hermetic), how to run them locally (`npm run test:probers`), and that they
require network access to external services (WikiTree API, CORS proxy, GitHub
raw URLs).
**Modify:** `PROJECT_STRUCTURE.md`
Add entries for the new `tests/probers/` directory and
`playwright.prober.config.ts` file.
**Modify:** `docs/README.md`
Add an entry for this design document to the registry:
```markdown
- **[PROBERS_DESIGN.md](PROBERS_DESIGN.md)**: Live prober smoke tests against
deployed GitHub Pages, WikiTree URLs, and local Docker container, covering
WikiTree API, CORS proxy, GEDCOM-from-URL, and Docker build paths.
```
### Summary of all files
| File | Action | Purpose |
| -------------------------------------------- | ------ | ------------------------------------------------------------------- |
| `playwright.prober.config.ts` | Create | Separate Playwright config for probers (no local server, live URLs) |
| `playwright.config.ts` | Modify | Add `testIgnore` for `probers/**` to e2e project |
| `package.json` | Modify | Add `test:probers` script |
| `tests/probers/helpers.ts` | Create | Shared prober flow, diagnostics capture, and selector logic |
| `tests/probers/wikitree.spec.ts` | Create | WikiTree direct API smoke test |
| `tests/probers/gh-pages-gedcom.spec.ts` | Create | GitHub Pages + CORS proxy smoke test |
| `tests/probers/wikitree-cors-gedcom.spec.ts` | Create | WikiTree + CORS proxy smoke test |
| `tests/probers/docker.spec.ts` | Create | Docker container smoke test (with reachability guard) |
| `src/pages/view_page.tsx` | Modify | Add `data-testid="content"` |
| `src/chart.tsx` | Modify | Add `data-testid="chart"` |
| `src/sidepanel/details/details.tsx` | Modify | Add `data-testid="details"` |
| `src/components/error_display.tsx` | Modify | Add `data-testid` for error message and popup |
| `.github/workflows/prober-wikitree.yml` | Create | Reusable workflow: WikiTree prober |
| `.github/workflows/prober-gh-pages.yml` | Create | Reusable workflow: GH Pages prober |
| `.github/workflows/prober-wikitree-cors.yml` | Create | Reusable workflow: WikiTree CORS prober |
| `.github/workflows/prober-docker.yml` | Create | Reusable workflow: Docker prober (pulls GHCR image) |
| `.github/workflows/deploy-everywhere.yml` | Modify | Add prober jobs with targeted deploy dependencies |
| `tests/tsconfig.json` | Modify | Ensure prober specs are type-checked |
| `tests/probers/README.md` | Create | Document prober directory and usage |
| `.github/workflows/README.md` | Modify | Document new prober workflows |
| `docs/README.md` | Modify | Add prober design doc to registry |
| `PROJECT_STRUCTURE.md` | Modify | Add prober directory and config file |
## 5. Future Considerations
### WikiTree Login Flow Prober
The current WikiTree prober tests the unauthenticated API path (loading a public
profile without an authcode). A future prober could test the authenticated login
flow — logging in with an authcode and verifying that private profiles are
accessible. This would require obtaining a dedicated test account on
wikitree.com and storing the authcode as a GitHub Actions secret. This is
deferred because it adds complexity (secret management, authcode expiry, test
account maintenance) and the unauthenticated path already covers the most common
deployment scenario.
### Google Drive Integration Prober
A prober for the Google Drive integration (loading a GEDCOM file from Google
Drive) is not included. Google's OAuth flow is designed for human interaction
and includes bot detection (CAPTCHA, device verification) that would likely
prevent automated login. Additionally, the Google Drive integration requires
`VITE_GOOGLE_CLIENT_ID` and `VITE_GOOGLE_API_KEY` secrets, which are not
available in the prober environment. A possible workaround would be to use a
pre-authorized service account or a long-lived refresh token stored as a secret,
but this is complex and fragile. This is deferred until a reliable automation
approach is identified.