`, 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:177` returns `true` on non-mobile screens, so the `div.details`
container is visible without any URL parameters.
### 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. 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.)
4. 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.
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. Run: npx playwright test --config=playwright.prober.config.ts docker.spec.ts
10. Upload Playwright HTML report as artifact (if: always()). Set
`retention-days: 30` (same as live-URL probers).
11. Stop and remove container (if: always()): docker stop
topola-prober-${{ github.run_id }} 2>/dev/null; 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/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 |
| `.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.