2026-09-15 19:30:08 +09:00
|
|
|
const assert = require("node:assert/strict");
|
|
|
|
|
const { Buffer } = require("node:buffer");
|
|
|
|
|
const { encode } = require("@msgpack/msgpack");
|
|
|
|
|
const { chromium } = require(process.env.PLAYWRIGHT_PATH || "playwright");
|
|
|
|
|
|
|
|
|
|
const base = process.env.SITE_URL || "http://127.0.0.1:18790";
|
|
|
|
|
const endpoint = "https://health.quorate.dev/health";
|
|
|
|
|
const paths = ["/", "/about/", "/blog/", "/404.html", "/blog/specimen/"];
|
|
|
|
|
|
|
|
|
|
const days = Array(30).fill(1);
|
|
|
|
|
days[1] = null;
|
|
|
|
|
days[2] = 23 / 24;
|
|
|
|
|
days[3] = 0;
|
|
|
|
|
|
|
|
|
|
const snapshot = [
|
|
|
|
|
1786838400,
|
|
|
|
|
[
|
2026-09-16 18:44:13 +09:00
|
|
|
["Nodes", ["one", "two"], [days, days]],
|
|
|
|
|
["Public services", ["Code"], [days]],
|
2026-09-15 19:30:08 +09:00
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function mockHealth(route, mode) {
|
|
|
|
|
if (mode === "connectionrefused") {
|
|
|
|
|
return route.abort("connectionrefused");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return route.fulfill({
|
|
|
|
|
status: mode === "503" ? 503 : 200,
|
|
|
|
|
contentType: "application/msgpack",
|
|
|
|
|
body: Buffer.from(encode(snapshot)),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkWidth(page) {
|
|
|
|
|
assert.equal(
|
|
|
|
|
await page.evaluate(
|
|
|
|
|
() => document.documentElement.scrollWidth <= innerWidth,
|
|
|
|
|
),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkHealth(page, width, mode) {
|
|
|
|
|
await page.route(endpoint, (route) => mockHealth(route, mode));
|
|
|
|
|
await page.goto(`${base}/up/`);
|
|
|
|
|
await page.waitForFunction(
|
|
|
|
|
() => document.documentElement.dataset.ready === "true",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const healthy = mode === "healthy";
|
|
|
|
|
assert.equal(await page.locator(".bar").count(), healthy ? 90 : 0);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await page.locator(".controls").isVisible(),
|
|
|
|
|
healthy && width <= 600,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (healthy) {
|
2026-09-16 18:44:13 +09:00
|
|
|
assert.deepEqual(await page.locator(".health-row h3").allTextContents(), [
|
|
|
|
|
"Code",
|
|
|
|
|
"one",
|
|
|
|
|
"two",
|
|
|
|
|
]);
|
2026-09-15 19:30:08 +09:00
|
|
|
assert.match(
|
|
|
|
|
await page.locator(".health-group h2").first().textContent(),
|
|
|
|
|
/Public services/,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await page.locator(".bar").nth(2).click();
|
|
|
|
|
assert.match(await page.locator("#detail").textContent(), /95\.83%/);
|
2026-09-16 18:44:13 +09:00
|
|
|
assert.match(await page.locator("#detail").textContent(), /^Code ·/);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await page.locator("#target option").first().textContent(),
|
|
|
|
|
"Public services · Code",
|
|
|
|
|
);
|
|
|
|
|
assert.match(
|
|
|
|
|
await page.locator(".bar").first().getAttribute("aria-label"),
|
|
|
|
|
/^Code ·/,
|
|
|
|
|
);
|
2026-09-15 19:30:08 +09:00
|
|
|
} else {
|
|
|
|
|
assert.equal(await page.locator("#observed").textContent(), "No data");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await checkWidth(page);
|
|
|
|
|
await page.unroute(endpoint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkLayout(page) {
|
|
|
|
|
assert.equal(await page.locator("header nav a").count(), 4);
|
|
|
|
|
assert.equal(await page.locator("h1").count(), 1);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await page
|
|
|
|
|
.locator("body")
|
|
|
|
|
.evaluate((node) => getComputedStyle(node).fontSize),
|
|
|
|
|
"16px",
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await page
|
|
|
|
|
.locator("h1")
|
|
|
|
|
.evaluate((node) => getComputedStyle(node).fontSize),
|
|
|
|
|
"24px",
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await page
|
|
|
|
|
.locator(".shell")
|
|
|
|
|
.evaluate((node) => getComputedStyle(node).maxWidth),
|
|
|
|
|
"704px",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await checkWidth(page);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkBlog(page) {
|
|
|
|
|
assert.equal(await page.locator(".posts li").count(), 2);
|
|
|
|
|
assert.equal(await page.locator(".posts h2").count(), 2);
|
|
|
|
|
assert.equal(await page.locator(".posts p").count(), 2);
|
|
|
|
|
assert.equal(await page.locator(".posts img").count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkPost(page) {
|
|
|
|
|
await page.locator(".mermaid svg").waitFor();
|
|
|
|
|
await page.locator("mjx-container").waitFor();
|
|
|
|
|
|
|
|
|
|
assert.equal(await page.locator("blockquote").count(), 1);
|
|
|
|
|
assert.equal(await page.locator("table").count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkPage(page, path) {
|
|
|
|
|
await page.goto(`${base}${path}`);
|
|
|
|
|
await checkLayout(page);
|
|
|
|
|
|
|
|
|
|
switch (path) {
|
|
|
|
|
case "/about/":
|
|
|
|
|
assert.equal(await page.locator("article p").textContent(), "WIP");
|
|
|
|
|
break;
|
|
|
|
|
case "/blog/":
|
|
|
|
|
await checkBlog(page);
|
|
|
|
|
break;
|
|
|
|
|
case "/blog/specimen/":
|
|
|
|
|
await checkPost(page);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
const browser = await chromium.launch({
|
|
|
|
|
executablePath: process.env.CHROME_PATH,
|
|
|
|
|
headless: true,
|
|
|
|
|
});
|
|
|
|
|
let cases = 0;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
for (const width of [320, 390, 768, 1280]) {
|
|
|
|
|
for (const colorScheme of ["light", "dark"]) {
|
|
|
|
|
const page = await browser.newPage({
|
|
|
|
|
viewport: { width, height: 844 },
|
|
|
|
|
colorScheme,
|
|
|
|
|
});
|
|
|
|
|
const errors = [];
|
|
|
|
|
page.on("pageerror", (error) => errors.push(error.message));
|
|
|
|
|
|
|
|
|
|
for (const mode of ["healthy", "503", "connectionrefused"]) {
|
|
|
|
|
await checkHealth(page, width, mode);
|
|
|
|
|
cases++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const path of paths) {
|
|
|
|
|
await checkPage(page, path);
|
|
|
|
|
cases++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(errors, []);
|
|
|
|
|
await page.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`PASS: ${cases} page and health cases`);
|
|
|
|
|
} finally {
|
|
|
|
|
await browser.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main().catch((error) => {
|
|
|
|
|
console.error(error);
|
|
|
|
|
process.exitCode = 1;
|
|
|
|
|
});
|