Files
template-app/scripts/verify-shell.mjs
T
2026-11-14 09:56:45 +00:00

78 lines
3.5 KiB
JavaScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// Read-only browser smoke for a built image or a live deployment. No account is needed.
// pnpm exec playwright install chromium
// node scripts/verify-shell.mjs https://your-app.example.test
import { strict as assert } from "node:assert"
import { mkdir, writeFile } from "node:fs/promises"
import { join } from "node:path"
import { chromium, expect } from "@playwright/test"
const base = new URL(process.argv[2] ?? "http://127.0.0.1:3000")
assert(["http:", "https:"].includes(base.protocol), "expected an HTTP(S) app URL")
// Evidence is opt-in, local and bounded; never collect URLs, headers, cookies or bodies.
const evidence = process.env.CONUSAI_BROWSER_EVIDENCE_DIR
if (evidence) {
process.umask(0o077)
await mkdir(evidence, { mode: 0o700 })
}
const errors = []
const consoleErrors = []
const network = []
let passed = false
const browser = await chromium.launch()
try {
const context = await browser.newContext({ locale: "en-US" })
const page = await context.newPage()
page.setDefaultTimeout(15_000)
page.on("pageerror", (error) => errors.push(error.message.slice(0, 300)))
page.on("console", (message) => {
if (message.type() === "error" && consoleErrors.length < 100) consoleErrors.push(message.text().slice(0, 300))
})
page.on("response", (response) => {
if (!evidence || network.length >= 1000) return
const url = new URL(response.url())
network.push({
method: response.request().method(),
sameOrigin: url.origin === base.origin,
route: url.pathname.startsWith("/assets/") ? "asset" : url.pathname === "/" ? "shell" : "other",
status: response.status(),
})
})
const response = await page.goto(new URL("/", base).href, { waitUntil: "networkidle" })
assert.equal(response?.status(), 200)
if (evidence) await page.screenshot({ path: join(evidence, "initial.png"), fullPage: true })
const root = page.locator("html")
await page.getByRole("button", { name: "Switch to light theme", exact: true }).click()
await expect(root).toHaveClass("light")
await page.reload()
await expect(root).toHaveClass("light")
await page.getByRole("button", { name: "Lietuvių", exact: true }).click()
await expect(root).toHaveAttribute("lang", "lt")
await expect(page.getByRole("button", { name: "Perjungti į tamsią temą", exact: true })).toBeVisible()
await page.reload()
await expect(root).toHaveAttribute("lang", "lt")
await expect(root).toHaveClass("light")
if (evidence) await page.screenshot({ path: join(evidence, "lithuanian-light.png"), fullPage: true })
await page.getByRole("button", { name: "Perjungti į tamsią temą", exact: true }).click()
await expect(root).not.toHaveClass("light")
await page.getByRole("button", { name: "English", exact: true }).click()
await expect(root).toHaveAttribute("lang", "en")
await expect(page.getByRole("button", { name: "Switch to light theme", exact: true })).toBeVisible()
if (evidence) await page.screenshot({ path: join(evidence, "english-dark.png"), fullPage: true })
assert.deepEqual(errors, [], "browser runtime errors")
assert.deepEqual(consoleErrors, [], "browser console errors")
passed = true
console.log("verify-shell: theme and language switch both ways and persist across reloads")
} finally {
await browser.close()
if (evidence)
await writeFile(
join(evidence, "result.json"),
`${JSON.stringify({ passed, errors, consoleErrors, network }, null, 2)}\n`,
{
flag: "wx",
mode: 0o600,
},
)
}