Files
RRS/tests/site.spec.js
T
2026-10-05 13:52:45 -04:00

282 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from "@playwright/test";
import { whatsappUrl } from "../src/data.js";
const pages = [
["/", "Built to develop."],
["/fabrics", "Different yarns."],
["/about", "Built around"],
["/contact", "Bring us your"],
];
for (const width of [1440, 768, 390, 320]) {
for (const [path, heading] of pages) {
test(`${path} renders without errors at ${width}px`, async ({
page,
}, testInfo) => {
const errors = [];
page.on("pageerror", (error) => errors.push(error.message));
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
});
await page.setViewportSize({ width, height: 900 });
await page.goto(path);
await expect(page.getByRole("heading", { level: 1 })).toContainText(
heading,
);
await page.evaluate(() => document.fonts.ready);
await expect(page.locator(".loader")).toHaveCount(0, { timeout: 3500 });
for (const image of await page.locator("img").all()) {
await image.scrollIntoViewIfNeeded();
await expect(image).toHaveJSProperty("complete", true);
expect(
await image.evaluate((element) => element.naturalWidth),
).toBeGreaterThan(0);
}
await page.evaluate(() => window.scrollTo(0, 0));
await expect(page.locator("h1")).toHaveCount(1);
await expect(page.locator("[style]")).toHaveCount(0);
await expect(page.locator("form")).toHaveCount(0);
const metrics = await page.evaluate(() => ({
overflow: document.documentElement.scrollWidth > window.innerWidth,
unloadedImages: [...document.images].filter(
(image) => !image.complete || !image.naturalWidth,
).length,
fontsLoaded:
document.fonts.check('16px "Roboto Condensed"') &&
document.fonts.check('16px "Noto Sans"'),
contentWidthRatio:
document.querySelector("#main-content").getBoundingClientRect()
.width / window.innerWidth,
}));
expect(metrics).toMatchObject({
overflow: false,
unloadedImages: 0,
fontsLoaded: true,
});
expect(metrics.contentWidthRatio).toBeGreaterThan(0.85);
expect(errors).toEqual([]);
if (width === 1440 || width === 390) {
await page.screenshot({
path: testInfo.outputPath("page.png"),
fullPage: true,
});
}
});
}
}
test("desktop navigation, anchored CTA and browser back work", async ({
page,
}) => {
await page.goto("/");
await page
.getByRole("link", { name: "Explore our fabrics", exact: true })
.click();
await expect(page).toHaveURL("/fabrics");
await expect(
page
.getByRole("navigation", { name: "Main navigation" })
.getByRole("link", { name: "Fabrics", exact: true }),
).toHaveAttribute("aria-current", "page");
await page
.getByRole("link", { name: "Talk to us on WhatsApp", exact: true })
.click();
await expect(page).toHaveURL("/contact#whatsapp");
await expect(page.locator("#whatsapp")).toBeInViewport();
await expect(
page.getByRole("button", { name: "Talk to us on WhatsApp", exact: true }),
).toBeDisabled();
await expect(page.locator("#whatsapp-status")).toContainText(
"number to be supplied",
);
await page.goBack();
await expect(page).toHaveURL("/fabrics");
await page.goto("/contact#whatsapp");
await page
.getByRole("link", { name: "Talk to us on WhatsApp", exact: true })
.click();
await expect(page.locator("#whatsapp")).toBeInViewport();
});
test("mobile navigation closes on selection and Escape", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/");
const toggle = page.locator("#menu-toggle");
await toggle.click();
await expect(toggle).toHaveAttribute("aria-expanded", "true");
await page.keyboard.press("Escape");
await expect(toggle).toHaveAttribute("aria-expanded", "false");
await expect(toggle).toBeFocused();
await toggle.click();
await page
.getByRole("navigation", { name: "Main navigation" })
.getByRole("link", { name: "Manufacturing", exact: true })
.click();
await expect(page).toHaveURL("/about");
await expect(toggle).toHaveAttribute("aria-expanded", "false");
await expect(page.locator(".owner")).toHaveCount(2);
await toggle.click();
await page.getByRole("link", { name: "RRS home", exact: true }).click();
await expect(page).toHaveURL("/");
await expect(toggle).toHaveAttribute("aria-expanded", "false");
});
test("application disclosures work from the keyboard", async ({ page }) => {
await page.goto("/fabrics");
const summary = page.locator("summary").filter({ hasText: "Textured knits" });
await summary.focus();
await page.keyboard.press("Enter");
await expect(
page.getByText(
"Knitted constructions where yarn and machine configuration are used to create distinctive surface character.",
),
).toBeVisible();
await page.keyboard.press("Enter");
await expect(
page.getByText(
"Knitted constructions where yarn and machine configuration are used to create distinctive surface character.",
),
).not.toBeVisible();
await expect(page.locator(".sample")).toHaveCount(7);
});
test("conversation starter copies with accessible feedback", async ({
page,
context,
}) => {
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
await page.goto("/contact");
await page.getByRole("button", { name: "Copy conversation starter" }).click();
await expect(page.getByRole("status")).toHaveText("Message copied.");
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(
"Hello RRS, I'd like to discuss a fabric requirement.",
);
});
test("clipboard denial has a useful fallback", async ({ page }) => {
await page.goto("/contact");
await page.evaluate(() => {
Object.defineProperty(navigator, "clipboard", {
value: { writeText: () => Promise.reject(new Error("Denied")) },
configurable: true,
});
});
await page.getByRole("button", { name: "Copy conversation starter" }).click();
await expect(page.getByRole("status")).toContainText(
"Please select and copy",
);
});
test("WhatsApp requires a real international number and encodes the message", () => {
for (const value of ["", "[Number]", "123", "+12345678901", "not-a-number"])
expect(whatsappUrl(value)).toBeNull();
const url = new URL(whatsappUrl("12345678901"));
expect(url.origin).toBe("https://wa.me");
expect(url.searchParams.get("text")).toBe(
"Hello RRS, I'd like to discuss a fabric requirement.",
);
});
test("every page section unveils once when scrolled into view", async ({
page,
}) => {
for (const [path] of pages) {
await page.goto(path);
const sections = page.locator("#main-content > section");
expect(await sections.count()).toBeGreaterThan(0);
for (const section of await sections.all()) {
await section.scrollIntoViewIfNeeded();
await expect(section).toHaveClass(/reveal--visible/);
await expect(section).toHaveCSS("opacity", "1");
}
}
});
test("homepage presents proof, fabrics and process in that order", async ({
page,
}) => {
await page.goto("/");
const fabricsHeading = page.getByRole("heading", {
level: 2,
name: "Greige is where it begins.",
});
const developmentsHeading = page.getByRole("heading", {
level: 2,
name: "What we make. What we can develop.",
});
await expect(fabricsHeading).toHaveCSS("text-align", "center");
await expect(developmentsHeading.locator("..").locator("..")).toHaveClass(
/section-heading--centered/,
);
const sections = await page
.locator("#main-content > section")
.evaluateAll((nodes) => nodes.map((node) => node.classList[0]));
expect(sections.indexOf("stats")).toBeLessThan(sections.indexOf("gallery"));
expect(sections.indexOf("gallery")).toBeLessThan(sections.indexOf("process"));
});
test("capacity values roll in opposite directions and settle at 60–70", async ({
page,
}) => {
for (const path of ["/", "/fabrics"]) {
await page.goto(path);
const capacity = page.getByLabel("60 to 70");
await expect(capacity).toHaveCount(1);
await capacity.scrollIntoViewIfNeeded();
await expect
.poll(() =>
capacity
.locator(".capacity-value__reel--up")
.evaluate((element) => element.getAnimations().length),
)
.toBeGreaterThan(0);
await expect(capacity.locator(".capacity-value__reel--up")).toHaveCSS(
"transform",
/matrix/,
);
await expect(capacity.locator(".capacity-value__reel--down")).toHaveCSS(
"transform",
/matrix/,
);
}
});
test("section unveiling respects reduced-motion preferences", async ({
page,
}) => {
await page.emulateMedia({ reducedMotion: "reduce" });
await page.goto("/");
const sections = page.locator("#main-content > section");
await expect(sections.first()).toHaveClass(/reveal--visible/);
for (const section of await sections.all()) {
await expect(section).toHaveCSS("opacity", "1");
await expect(section).toHaveCSS("transform", "none");
}
const capacity = page.getByLabel("60 to 70");
await capacity.scrollIntoViewIfNeeded();
expect(
await capacity
.locator(".capacity-value__reel--up")
.evaluate((element) => element.getAnimations().length),
).toBe(0);
});
test("the woven loading page introduces the site and clears", async ({
page,
}) => {
await page.goto("/");
const loader = page.locator(".loader");
await expect(loader).toContainText("Loading Radhe Radhe Synthetics");
await expect(page.locator(".loader__machine-image")).toHaveCount(1);
await expect(loader).toHaveCount(0, { timeout: 3500 });
});
test("unknown route provides a way home", async ({ page }) => {
await page.goto("/missing-page");
await expect(page.getByRole("heading", { level: 1 })).toHaveText(
"A loose thread.",
);
await page.getByRole("link", { name: "Back to home", exact: true }).click();
await expect(page).toHaveURL("/");
});