From 29c26c43b79b75b3d96e99b156cb535ea2888764 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 29 Jul 2026 11:13:10 +0000 Subject: [PATCH] test: validate packaged Pages application subpaths --- .github/workflows/deploy-pages.yml | 3 + package.json | 1 + test/browser-apps/README.md | 9 ++ test/browser-apps/pages/browser-smoke.test.ts | 83 +++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 test/browser-apps/pages/browser-smoke.test.ts diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 26997197..9e97b8f3 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -81,6 +81,9 @@ jobs: test -s _site/webpeer/index.html touch _site/.nojekyll + - name: Validate packaged Pages site + run: npm run test:browser-apps:pages + - name: Configure GitHub Pages uses: actions/configure-pages@v5 diff --git a/package.json b/package.json index db9e7bbd..ac3db65f 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "test:unit": "vitest run --config vitest.config.unit.ts", "build:browser-apps": "npm run build --workspace livesync-webapp --workspace webpeer", "test:browser-apps": "npm run test:browser --workspace livesync-webapp && npm run test:browser --workspace webpeer", + "test:browser-apps:pages": "deno test -A --no-check --frozen --config test/browser-apps/deno.json --lock test/browser-apps/deno.lock test/browser-apps/pages/browser-smoke.test.ts", "test:e2e:browser-apps": "npm run test:browser-apps", "pretest:e2e:browser-apps:interop": "npm run build:browser-apps && npm run build --workspace self-hosted-livesync-cli", "test:e2e:browser-apps:interop": "deno run -A --no-check --frozen --config test/browser-apps/deno.json --lock test/browser-apps/deno.lock test/browser-apps/run-compose-interop.ts", diff --git a/test/browser-apps/README.md b/test/browser-apps/README.md index 91b24d03..2c758d71 100644 --- a/test/browser-apps/README.md +++ b/test/browser-apps/README.md @@ -8,6 +8,7 @@ Each application has its own production-bundle smoke-test directory outside `src - `test/browser-apps/webapp/` covers Vault selection, OPFS start-up, and isolation between optional P2P settings and the main remote. - `test/browser-apps/webpeer/` covers start-up, settings persistence, and reload. +- `test/browser-apps/pages/` covers the final GitHub Pages layout, application subpaths, and relative assets. Run both app-owned tests with: @@ -23,4 +24,12 @@ Run the isolated relay and TURN scenario in Compose with: npm run test:e2e:browser-apps:interop ``` +After assembling the GitHub Pages files in `_site`, run the package smoke test with: + +```bash +npm run test:browser-apps:pages +``` + +Set `PAGES_SITE_ROOT` to test an assembled site in another directory. + The Deno dependency lock is shared only by these browser application tests. diff --git a/test/browser-apps/pages/browser-smoke.test.ts b/test/browser-apps/pages/browser-smoke.test.ts new file mode 100644 index 00000000..2c300667 --- /dev/null +++ b/test/browser-apps/pages/browser-smoke.test.ts @@ -0,0 +1,83 @@ +import { assertEquals } from "@std/assert"; +import { resolve } from "@std/path"; +import { chromium, type Page } from "playwright"; + +import { observePageFailures, startStaticServer } from "../helpers/browser.ts"; + +const pagesSiteRoot = resolve(Deno.env.get("PAGES_SITE_ROOT") ?? resolve(import.meta.dirname!, "../../../_site")); + +function observeNetworkFailures(page: Page): () => void { + const failures: string[] = []; + page.on("requestfailed", (request) => { + failures.push(`${request.method()} ${request.url()}: ${request.failure()?.errorText ?? "request failed"}`); + }); + page.on("response", (response) => { + if (response.status() >= 400) { + failures.push(`${response.request().method()} ${response.url()}: HTTP ${response.status()}`); + } + }); + return () => assertEquals(failures, [], failures.join("\n")); +} + +Deno.test({ + name: "GitHub Pages package serves browser applications from their final subpaths", + sanitizeOps: false, + sanitizeResources: false, + async fn() { + const server = await startStaticServer(pagesSiteRoot); + const browser = await chromium.launch({ headless: true }); + try { + const aggregator = await browser.newPage(); + const assertNoAggregatorFailures = observePageFailures(aggregator); + const assertNoAggregatorNetworkFailures = observeNetworkFailures(aggregator); + await aggregator.goto(new URL("aggregator.html#id=pages-smoke&n=2&i=0&d=first-", server.baseUrl).href); + await aggregator.getByText("1 / 2 Loaded", { exact: true }).waitFor(); + await aggregator.goto(new URL("aggregator.html#id=pages-smoke&n=2&i=1&d=second", server.baseUrl).href); + assertEquals( + await aggregator.getByRole("link", { name: "Open Obsidian to complete setup" }).getAttribute("href"), + "obsidian://setuplivesync?settingsQR=first-second" + ); + assertNoAggregatorFailures(); + assertNoAggregatorNetworkFailures(); + await aggregator.close(); + + const webApp = await browser.newPage(); + const assertNoWebAppFailures = observePageFailures(webApp); + const assertNoWebAppNetworkFailures = observeNetworkFailures(webApp); + await webApp.addInitScript(` + Object.defineProperty(globalThis, "showDirectoryPicker", { + configurable: true, + value: async () => { + throw new DOMException("Cancelled by Pages browser test", "AbortError"); + }, + }); + `); + await webApp.goto(new URL("webapp/", server.baseUrl).href); + await webApp.waitForURL(/\/webapp\/webapp\.html$/); + const vaultPicker = webApp.getByRole("button", { name: "Choose new vault folder" }); + await vaultPicker.click(); + await webApp.locator("#status.warning").filter({ hasText: "Vault selection was cancelled" }).waitFor(); + assertEquals(await vaultPicker.isEnabled(), true); + assertNoWebAppFailures(); + assertNoWebAppNetworkFailures(); + await webApp.close(); + + const webPeer = await browser.newPage(); + const assertNoWebPeerFailures = observePageFailures(webPeer); + const assertNoWebPeerNetworkFailures = observeNetworkFailures(webPeer); + await webPeer.goto(new URL("webpeer/", server.baseUrl).href); + await webPeer.getByRole("heading", { name: "Peer to Peer Replicator", exact: true }).waitFor({ + timeout: 30_000, + }); + await webPeer.getByText("No Connection", { exact: true }).waitFor(); + assertEquals(new URL(webPeer.url()).pathname, "/webpeer/"); + const manifest = await webPeer.request.get(new URL("webpeer/manifest.json", server.baseUrl).href); + assertEquals(manifest.status(), 200); + assertNoWebPeerFailures(); + assertNoWebPeerNetworkFailures(); + } finally { + await browser.close(); + await server.close(); + } + }, +});