Files
RRS/tests/site.spec.js
T
2026-10-08 18:38:15 -04:00

303 lines
10 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."],
["/products", "Knitted for"],
["/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
.locator(".contact-banner")
.getByRole("link", { name: "Text us on WhatsApp", exact: true })
.click();
await expect(page).toHaveURL("/contact#whatsapp");
await expect(page.locator("#whatsapp")).toBeInViewport();
await expect(
page
.locator(".whatsapp")
.getByRole("link", { name: "Text us on WhatsApp", exact: true }),
).toHaveAttribute("href", /wa\.me/);
await expect(page.locator("#whatsapp-status")).toContainText(
"Opens WhatsApp",
);
await page.goBack();
await expect(page).toHaveURL("/fabrics");
await page.goto("/contact#whatsapp");
await page
.locator(".contact-banner")
.getByRole("link", { name: "Text 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: "Products", exact: true })
.click();
await expect(page).toHaveURL("/products");
await expect(toggle).toHaveAttribute("aria-expanded", "false");
await expect(page.getByRole("heading", { level: 1 })).toContainText(
"Knitted for",
);
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 shows the four stat facts and nothing else", async ({
page,
}) => {
await page.goto("/");
await expect(page.locator(".stats__item")).toHaveCount(4);
await expect(
page.getByText("The starting point for a world of possibilities."),
).toHaveCount(0);
await expect(page.locator(".contact-banner")).toHaveCount(0);
await expect(page.locator(".contact-strip")).toHaveCount(0);
await expect(page.locator(".site-nav__contact")).toHaveCount(0);
await expect(
page.getByRole("link", { name: "Manufacturing", exact: true }),
).toHaveCount(0);
});
test("home and products heroes cycle through four product images", async ({
page,
}) => {
for (const path of ["/", "/products"]) {
await page.goto(path);
const slides = page
.locator(".image-carousel")
.first()
.locator(".image-carousel__image");
await expect(slides).toHaveCount(4);
expect(
await slides.first().evaluate((image) => image.getAnimations().length),
).toBeGreaterThan(0);
}
await page.goto("/products");
await expect(page.locator(".product")).toHaveCount(4);
await expect(
page.getByText("Daily-wear T-shirts", { exact: true }),
).toBeVisible();
await expect(page.getByText("Women’s wear", { exact: true })).toBeVisible();
});
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("/");
});