From f28dfbf21d6e0fc29e6cc140dfcfe287fee9262b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemek=20Wi=C4=99ch?= Date: Fri, 3 Jul 2026 23:39:16 +0200 Subject: [PATCH] Add GitHub Pages prober --- .github/workflows/README.md | 1 + .github/workflows/deploy-everywhere.yml | 7 +++ .github/workflows/prober-gh-pages.yml | 77 +++++++++++++++++++++++++ PROJECT_STRUCTURE.md | 2 + docs/PROBERS_DESIGN.md | 6 +- docs/README.md | 1 + package.json | 1 + playwright.config.ts | 2 +- playwright.prober.config.ts | 33 +++++++++++ tests/probers/README.md | 30 ++++++++++ tests/probers/gh-pages-gedcom.spec.ts | 35 +++++++++++ 11 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/prober-gh-pages.yml create mode 100644 playwright.prober.config.ts create mode 100644 tests/probers/README.md create mode 100644 tests/probers/gh-pages-gedcom.spec.ts diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 513da19..3d82b2d 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -10,3 +10,4 @@ This directory contains GitHub Actions workflow files that automate various task - [deploy-gh-pages.yml](deploy-gh-pages.yml): Handles the deployment of the application to GitHub Pages. - [deploy-wikitree-apps.yml](deploy-wikitree-apps.yml): Manages the deployment of the application to WikiTree Apps using SFTP. - [node.js.yml](node.js.yml): The main Continuous Integration (CI) workflow. It installs dependencies, checks formatting, lints, builds, and runs tests across multiple Node.js versions. +- [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. diff --git a/.github/workflows/deploy-everywhere.yml b/.github/workflows/deploy-everywhere.yml index 871b8c2..f22d31c 100644 --- a/.github/workflows/deploy-everywhere.yml +++ b/.github/workflows/deploy-everywhere.yml @@ -14,3 +14,10 @@ jobs: deploy-docker: uses: ./.github/workflows/deploy-docker.yml secrets: inherit + + prober-gh-pages: + needs: deploy-gh-pages + uses: ./.github/workflows/prober-gh-pages.yml + secrets: inherit + with: + wait_for_propagation: true diff --git a/.github/workflows/prober-gh-pages.yml b/.github/workflows/prober-gh-pages.yml new file mode 100644 index 0000000..346d369 --- /dev/null +++ b/.github/workflows/prober-gh-pages.yml @@ -0,0 +1,77 @@ +name: Prober - GitHub Pages + +on: + workflow_call: + inputs: + wait_for_propagation: + type: boolean + default: false + workflow_dispatch: + inputs: + wait_for_propagation: + type: boolean + default: false + schedule: + - cron: '0 5 * * *' + +permissions: + contents: read + actions: write + +concurrency: + group: prober-${{ github.workflow }} + cancel-in-progress: false + +jobs: + prober: + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + SPEC: gh-pages-gedcom.spec.ts + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 24.x + cache: 'npm' + + - run: npm ci + + - name: Wait for deployment propagation + if: inputs.wait_for_propagation + run: sleep 180 + + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT + + - name: Cache Playwright Browsers + id: cache-playwright + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + + - name: Install Playwright System Dependencies + run: npx playwright install-deps chromium + + - name: Install Playwright Browsers (If Not Cached) + if: steps.cache-playwright.outputs.cache-hit != 'true' + run: npx playwright install chromium + + - name: Run prober + env: + PLAYWRIGHT_HTML_REPORT: playwright-report/prober + run: npx playwright test --config=playwright.prober.config.ts "${SPEC}" + + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v4 + with: + name: prober-report-gh-pages + path: playwright-report/ + retention-days: 30 diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 10a360c..f30d329 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -30,6 +30,7 @@ Here is an enumeration of the files in this directory, categorized by their role ### Testing * **[playwright.config.ts](playwright.config.ts)**: Configuration file for Playwright end-to-end testing framework. +* **[playwright.prober.config.ts](playwright.prober.config.ts)**: Separate Playwright config for prober (live smoke) tests against deployed URLs. * **[jest.config.ts](jest.config.ts)**: Configuration file for the Jest testing framework used for unit tests. ### Documentation & Assets @@ -52,3 +53,4 @@ Based on the documentation in the subdirectories, here is a high-level overview * **[util](src/util)**: Common utilities for dates, analytics, and data processing. * **[public](public)**: Contains static assets (like the favicon) served directly at the root path. * **[tests](tests)**: Contains end-to-end tests. + * **[tests/probers](tests/probers)**: Live smoke tests (probers) that run against deployed URLs and external services. diff --git a/docs/PROBERS_DESIGN.md b/docs/PROBERS_DESIGN.md index f9ba4d9..bcce035 100644 --- a/docs/PROBERS_DESIGN.md +++ b/docs/PROBERS_DESIGN.md @@ -351,7 +351,7 @@ targets a different URL and asserts a different expected name. subsequent `#chart` text assertion relies on Playwright's auto-wait to bridge this gap. 3. Assert expected name appears in #chart (chart SVG text). -4. Assert expected name appears in .details (side panel). +4. Assert expected name appears in div.details (side panel). 5. Assert .ui.error.message is not visible (no fatal error). 6. Assert .ui.errorPopup.message is not visible (no popup error). ``` @@ -366,7 +366,7 @@ Selectors are derived from the source code: * `#content` — main container, visible when chart state is `SHOWING_CHART` (see `src/pages/view_page.tsx:202`). * `#chart` — SVG group inside the chart (see `src/chart.tsx:599`). -* `.details` — side panel Details tab content (see `src/sidepanel/details/details.tsx:357`). +* `div.details` — side panel Details tab content (see `src/sidepanel/details/details.tsx:357`). * `.ui.error.message` — fatal error replacing the chart (see `src/components/error_display.tsx`, rendered when state is `ERROR`). The `ui` and `message` classes are added by Semantic UI React's `` @@ -385,7 +385,7 @@ Selectors are derived from the source code: 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 `.details` +`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 diff --git a/docs/README.md b/docs/README.md index b0baae7..8c06a5a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,3 +13,4 @@ While new features *may* follow this methodology to ensure robust design validat * **[PLAYWRIGHT_DESIGN.md](PLAYWRIGHT_DESIGN.md)**: Playwright E2E testing architecture, Vite development/preview server lifecycle integration, tracking blocker interceptors, and embedded iframe communication. * **[SCREENSHOT_TESTS_DESIGN.md](SCREENSHOT_TESTS_DESIGN.md)**: Pixel-perfect visual regression testing infrastructure, animation stabilization, sandbox environment/DOM sanitization, and isolated Playwright projects. * **[WEBMCP_DESIGN.md](WEBMCP_DESIGN.md)**: Model Context Protocol (MCP) bridge and TS tool registration for AI agent interaction. +* **[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. diff --git a/package.json b/package.json index c974569..7705c5f 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "test:visual": "playwright test --project=visual", "test:visual:update": "playwright test --project=visual --update-snapshots", "test:visual:ui": "playwright test --project=visual --ui", + "test:probers": "playwright test --config=playwright.prober.config.ts", "check:all": "prettier --check \"{src,tests}/**/*.{ts,tsx,json}\" && npm run lint && npm run build && npm test && tsc -p tests/tsconfig.json --noEmit && npm run test:e2e && npm run test:visual" }, "homepage": ".", diff --git a/playwright.config.ts b/playwright.config.ts index 4157ae9..66888bc 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -24,7 +24,7 @@ export default defineConfig({ projects: [ { name: 'e2e', - testIgnore: '*_visual.spec.ts', + testIgnore: ['*_visual.spec.ts', 'probers/**'], use: { ...devices['Desktop Chrome'], }, diff --git a/playwright.prober.config.ts b/playwright.prober.config.ts new file mode 100644 index 0000000..213ad64 --- /dev/null +++ b/playwright.prober.config.ts @@ -0,0 +1,33 @@ +import {defineConfig, devices} from '@playwright/test'; + +/** + * Playwright configuration for prober (live smoke) tests. + * + * Unlike the main playwright.config.ts, this config: + * - Does not start a local dev server (specs navigate to absolute URLs). + * - Runs tests sequentially to avoid rate-limiting external APIs. + * - Enforces forbidOnly unconditionally (probers always run in CI). + * - Captures screenshots, video, and traces for debugging live failures. + */ +export default defineConfig({ + testDir: './tests/probers', + fullyParallel: false, + forbidOnly: true, + retries: 2, + timeout: 120000, + reporter: [['html', {open: 'never'}], ['list']], + use: { + locale: 'en-US', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'on-first-retry', + }, + projects: [ + { + name: 'prober', + use: { + ...devices['Desktop Chrome'], + }, + }, + ], +}); diff --git a/tests/probers/README.md b/tests/probers/README.md new file mode 100644 index 0000000..6d31061 --- /dev/null +++ b/tests/probers/README.md @@ -0,0 +1,30 @@ +# Prober Tests + +These are **live smoke tests** that run against deployed URLs and external +services. They are NOT hermetic — they make real network requests to the +WikiTree API, CORS proxy, and GitHub raw URLs. + +## Running + +```bash +npm run test:probers +``` + +This uses the separate `playwright.prober.config.ts` configuration, which +does not start a local dev server. Each spec navigates to a full absolute +URL (or `localhost:8080` for the Docker prober). + +## What They Test + +| Spec | Target | Exercises | +| --- | --- | --- | +| `gh-pages-gedcom.spec.ts` | `pewu.github.io` | GitHub Pages deployment, CORS proxy, GEDCOM-from-URL | +| `wikitree.spec.ts` | `apps.wikitree.com` | WikiTree direct API, WikiTree deployment | +| `wikitree-cors-gedcom.spec.ts` | `apps.wikitree.com` | WikiTree deployment, CORS proxy via GEDCOM-from-URL | +| `docker.spec.ts` | `localhost:8080` | Published Docker image from GHCR | + +## Notes + +- Probers run sequentially (`fullyParallel: false`) to avoid rate-limiting. +- Each prober has its own GitHub Actions workflow for independent triggering. +- Probers do not block Google Analytics, so live runs generate real analytics events. diff --git a/tests/probers/gh-pages-gedcom.spec.ts b/tests/probers/gh-pages-gedcom.spec.ts new file mode 100644 index 0000000..9e892e4 --- /dev/null +++ b/tests/probers/gh-pages-gedcom.spec.ts @@ -0,0 +1,35 @@ +import {expect, test} from '@playwright/test'; + +/** + * Prober: GitHub Pages GEDCOM via CORS proxy + * + * Loads a GEDCOM file from a raw GitHub URL through the app deployed on + * pewu.github.io. Because the app is not on the apps.wikitree.com domain, + * GEDCOM URL requests are routed through the CORS proxy + * (topolaproxy.bieda.it) by default. This exercises the GitHub Pages + * deployment, the CORS proxy, and GEDCOM-from-URL loading. + */ +test('GitHub Pages GEDCOM prober', async ({page}) => { + await page.goto( + 'https://pewu.github.io/topola-viewer/#/view?url=https://raw.githubusercontent.com/PeWu/topola-viewer/master/src/datasource/testdata/test.ged&indi=I1', + ); + + // Wait for the app to reach SHOWING_CHART state. + await expect(page.locator('#content')).toBeVisible(); + + // Assert the expected person's name appears in the chart SVG. + await expect(page.locator('#chart')).toContainText('Bonifacy'); + + // Assert the expected person's name appears in the side panel. + // Scoped to div.details to avoid matching SVG + // elements in the chart that also carry the "details" class. + await expect(page.locator('div.details')).toContainText('Bonifacy'); + + // Assert no fatal error is displayed (replaces chart when state is ERROR). + await expect(page.locator('.ui.error.message')).not.toBeVisible(); + + // Assert no popup error is displayed. + // ErrorPopup uses Semantic UI React's , which renders at + // document.body level, not inside #content. + await expect(page.locator('.ui.errorPopup.message')).not.toBeVisible(); +});