Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e/accessibility-focus.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async function expectNoSeriousAccessibilityViolations(page: Page) {

async function openStableDemo(page: Page) {
await expectLanding(page);
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await expect(page.getByRole("heading", { name: "Demo timetable" })).toBeVisible();
await expect(page.getByRole("button", { name: "Update timetable" })).toBeEnabled();
}
Expand Down
2 changes: 1 addition & 1 deletion e2e/accessibility.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test("core release journey has no serious or critical automatic a11y violations"
await expectLanding(page);
await expectNoSeriousAccessibilityViolations(page);

await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await expect(page.getByRole("heading", { name: "Demo timetable" })).toBeVisible();
// The timetable shell becomes visible before demo initialization finishes.
// Scan the stable interactive state rather than the brief disabled/loading transition.
Expand Down
68 changes: 30 additions & 38 deletions e2e/adversarial-smoke-routes.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,39 @@
import { expect, test } from "@playwright/test";
import { watchForAppFailures } from "./helpers";

test("Adversarially smoke-test live routes across UTM, Guelph, McMaster, York, and Brock", async ({
browser,
baseURL,
}) => {
test.skip(test.info().project.name !== "chromium");
if (!baseURL) throw new Error("Playwright baseURL is required");

const targets = [
{ id: "uoft", query: "", name: "University of Toronto", sampleCourse: "DEM101H5" },
{
id: "guelph",
query: "?university=guelph",
name: "University of Guelph",
sampleCourse: "CIS 1300",
},
{
id: "mcmaster",
query: "?university=mcmaster",
name: "McMaster University",
sampleCourse: "COMPSCI 1MD3",
},
{ id: "york", query: "?university=york", name: "York University", sampleCourse: "EECS 1022" },
{
id: "brock",
query: "?university=brock",
name: "Brock University",
sampleCourse: "COSC 1P02",
},
];

for (const target of targets) {
console.log(`Smoke testing UI route for ${target.name} (${target.id})...`);
const context = await browser.newContext({ baseURL });
const page = await context.newPage();
const targets = [
{ id: "uoft", query: "", name: "University of Toronto", sampleCourse: "DEM101H5" },
{
id: "guelph",
query: "?university=guelph",
name: "University of Guelph",
sampleCourse: "CIS 1300",
},
{
id: "mcmaster",
query: "?university=mcmaster",
name: "McMaster University",
sampleCourse: "COMPSCI 1MD3",
},
{ id: "york", query: "?university=york", name: "York University", sampleCourse: "EECS 1022" },
{
id: "brock",
query: "?university=brock",
name: "Brock University",
sampleCourse: "COSC 1P02",
},
] as const;

for (const target of targets) {
test(`adversarial route smoke test for ${target.name}`, async ({ page, baseURL }) => {
test.skip(test.info().project.name !== "chromium");
if (!baseURL) throw new Error("Playwright baseURL is required");
const failures = watchForAppFailures(page, baseURL);

await page.goto(`/${target.query}`);

// Click demo button to load campus schedule
const demoButton = page.getByRole("button", { name: "Try a demo" });
const demoButton = page.getByRole("button", { name: /Try the .* demo/ });
await expect(demoButton).toBeVisible();
await demoButton.click();

Expand All @@ -59,6 +52,5 @@ test("Adversarially smoke-test live routes across UTM, Guelph, McMaster, York, a
).toBeVisible();

failures.assertClean();
await context.close();
}
});
});
}
6 changes: 2 additions & 4 deletions e2e/and66.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,13 @@ test("AND-66 first-run landing keeps activation clear on a narrow phone", async
await page.setViewportSize({ width: 360, height: 740 });
await expectLanding(page);

await expect(
page.getByRole("heading", { name: "Make every gap on campus count." }),
).toBeVisible();
await expect(page.getByRole("heading", { name: "Make every gap at UTM count." })).toBeVisible();
await expect(
page.getByText(
"Plan classes, buildings, routes, and time between classes across the Mississauga campus.",
),
).toBeVisible();
await expect(page.getByRole("button", { name: "Try a demo" })).toBeVisible();
await expect(page.getByRole("button", { name: /Try the .* demo/ })).toBeVisible();
await expect(page.getByRole("link", { name: "Need help importing?" })).toHaveAttribute(
"href",
"/acorn-import",
Expand Down
2 changes: 1 addition & 1 deletion e2e/clickable-gaps.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ test("a timetable gap opens Gap Plan with that exact interval selected", async (
const mobile = isMobileProject(testInfo.project.name);

await page.goto("/");
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();

if (mobile) {
const nav = page.getByRole("navigation", { name: "Main" });
Expand Down
48 changes: 21 additions & 27 deletions e2e/core.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ const twoTermFixturePath = path.join(
test("landing page is usable without an account", async ({ page }, testInfo) => {
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));
await expectLanding(page);
await expect(page).toHaveTitle(
"Gapwise for University of Toronto Mississauga — Timetable & Campus Navigation",
);
await expect(page).toHaveTitle("Gapwise for University of Toronto Mississauga");
await expect(
page.getByText("For University of Toronto Mississauga", { exact: true }),
page.getByText("Gapwise for University of Toronto Mississauga", { exact: true }),
).toBeVisible();
await expect(page.getByRole("button", { name: "Import ACORN" })).toBeVisible();
guard.assertClean();
Expand All @@ -28,9 +26,7 @@ test("bare fragments are removed without enabling hash routing", async ({ page }
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));

await page.goto("/#");
await expect(
page.getByRole("heading", { name: "Make every gap on campus count." }),
).toBeVisible();
await expect(page.getByRole("heading", { name: "Make every gap at UTM count." })).toBeVisible();
await expect.poll(() => page.url().endsWith("#")).toBe(false);

await page.evaluate(() => {
Expand All @@ -54,18 +50,14 @@ test("path navigation, refresh, and browser history use the SPA fallback", async
await expect(page.getByRole("heading", { name: "Page not found" })).toBeVisible();

await page.getByRole("link", { name: "Go home" }).click();
await expect(
page.getByRole("heading", { name: "Make every gap on campus count." }),
).toBeVisible();
await expect(page.getByRole("heading", { name: "Make every gap at UTM count." })).toBeVisible();
expect(new URL(page.url()).pathname).toBe("/");
expect(new URL(page.url()).hash).toBe("");

await page.goBack();
await expect(page.getByRole("heading", { name: "Page not found" })).toBeVisible();
await page.goForward();
await expect(
page.getByRole("heading", { name: "Make every gap on campus count." }),
).toBeVisible();
await expect(page.getByRole("heading", { name: "Make every gap at UTM count." })).toBeVisible();
guard.assertClean();
});

Expand Down Expand Up @@ -118,7 +110,7 @@ test("route-driven navigation preserves a loaded timetable through history", asy
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));
await expectLanding(page);

await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await expect(page).toHaveURL(/\/timetable$/);
await expect(page.getByRole("heading", { name: "Demo timetable" })).toBeVisible();

Expand Down Expand Up @@ -151,9 +143,7 @@ test("route-driven navigation preserves a loaded timetable through history", asy

await page.getByRole("link", { name: "Gapwise home" }).click();
await expect(page).toHaveURL(/\/$/);
await expect(
page.getByRole("heading", { name: "Make every gap on campus count." }),
).toBeVisible();
await expect(page.getByRole("heading", { name: "Make every gap at UTM count." })).toBeVisible();

await page.goBack();
await expect(page).toHaveURL(/\/route\/?$/);
Expand All @@ -167,7 +157,7 @@ test("timetable export offers available terms and downloads light and dark PNGs"
test.skip(testInfo.project.name !== "chromium", "PNG export coverage runs once in Chromium");
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));
await expectLanding(page);
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await expect(page).toHaveURL(/\/timetable$/);

await page.getByRole("button", { name: "Account settings" }).click();
Expand Down Expand Up @@ -276,7 +266,7 @@ test("campus-day arrival settings route transit and parking through the map", as
test.skip(testInfo.project.name !== "chromium", "commuter route coverage runs once");
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));
await expectLanding(page);
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await page
.getByRole("group", { name: "View mode" })
.getByRole("button", { name: "Day route" })
Expand Down Expand Up @@ -546,7 +536,10 @@ test("mobile term switching selects a scheduled weekday", async ({ page }, testI
].join("\r\n"),
),
});
await page.getByRole("link", { name: "Timetable" }).click();
await page
.getByRole("navigation", { name: "Main" })
.getByRole("link", { name: "Timetable", exact: true })
.click();
const weekdays = page.getByRole("group", { name: "Weekday" });
await weekdays.getByRole("button", { name: /^Mon/ }).click();
await expect(page.getByRole("heading", { name: "Monday", exact: true })).toBeVisible();
Expand All @@ -565,8 +558,11 @@ test("mobile weekday buttons expose native keyboard selection state", async ({
test.skip(testInfo.project.name !== "mobile-chromium", "mobile keyboard regression runs once");
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));
await expectLanding(page);
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("link", { name: "Timetable" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await page
.getByRole("navigation", { name: "Main" })
.getByRole("link", { name: "Timetable", exact: true })
.click();

const weekdays = page.getByRole("group", { name: "Weekday" });
const monday = weekdays.getByRole("button", { name: /^Mon/ });
Expand All @@ -591,9 +587,7 @@ test("malformed calendar fails safely with a useful error", async ({ page }, tes
});

await expect(page.getByRole("alert")).toContainText("doesn't look like a calendar export");
const landingHeading = isMobileProject(testInfo.project.name)
? "Start with your timetable."
: "Make every gap on campus count.";
const landingHeading = "Make every gap at UTM count.";
await expect(page.getByRole("heading", { name: landingHeading })).toBeVisible();
guard.assertClean();
});
Expand All @@ -603,7 +597,7 @@ test("desktop demo moves between timetable, gaps, and route", async ({ page }, t
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));
await expectLanding(page);

await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await expect(page.getByRole("heading", { name: "Demo timetable" })).toBeVisible();
const visibleGapWindows = page.getByTestId("gap-window");
await expect(visibleGapWindows.first()).toBeVisible();
Expand All @@ -624,7 +618,7 @@ test("mobile demo exposes the primary timetable and map navigation", async ({ pa
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));
await expectLanding(page);

await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
const nav = page.getByRole("navigation", { name: "Main" });
await expect(nav).toBeVisible();
await expect(nav.getByRole("link", { name: "Today" })).toBeVisible();
Expand Down
2 changes: 1 addition & 1 deletion e2e/day-route-map.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test.describe("map-first Day Route", () => {
});

await page.goto("/");
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();

if (test.info().project.name === "mobile-chromium") {
const nav = page.getByRole("navigation", { name: "Main" });
Expand Down
2 changes: 1 addition & 1 deletion e2e/destination-checker.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test("Today checks canonical destinations inside a live gap on mobile Safari", a
await page.clock.setFixedTime(new Date("2026-09-07T15:30:00.000Z"));

await page.goto("/today");
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: "Try a demo", exact: true }).click();
await expect(page).toHaveURL(/\/today$/);
await expect(page.getByRole("heading", { name: "Can I go there?" })).toBeVisible();
await expect(
Expand Down
2 changes: 1 addition & 1 deletion e2e/entrance-marker-anchor-contract.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test("entrance geographic anchor stays dimensionless and MapLibre-owned", async
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));

await expectLanding(page);
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await page
.getByRole("group", { name: "View mode" })
.getByRole("button", { name: "Day route" })
Expand Down
4 changes: 2 additions & 2 deletions e2e/entrance-marker-stability.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ test("entrance markers keep MapLibre projection isolated from interactive stylin
const projectionUrl = new URL(page.url());
projectionUrl.searchParams.set("e2eMapProjection", "1");
await page.goto(projectionUrl.toString());
await expect(page.getByRole("button", { name: "Try a demo" })).toBeVisible();
await expect(page.getByRole("button", { name: /Try the .* demo/ })).toBeVisible();

await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await page
.getByRole("group", { name: "View mode" })
.getByRole("button", { name: "Day route" })
Expand Down
2 changes: 1 addition & 1 deletion e2e/gap-plan-mobile-scroll.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test("mobile gap tool sheets keep their content vertically scrollable", async ({
const guard = watchForAppFailures(page, String(testInfo.project.use.baseURL));

await expectLanding(page);
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
const nav = page.getByRole("navigation", { name: "Main" });
await expect(nav).toBeVisible();
await nav.getByRole("link", { name: "Timetable" }).click();
Expand Down
9 changes: 2 additions & 7 deletions e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,7 @@ export function watchForAppFailures(page: Page, baseURL: string) {

export async function expectLanding(page: Page) {
await page.goto("/");
const mobile = (page.viewportSize()?.width ?? 1280) < 768;
await expect(
page.getByRole("heading", {
name: mobile ? "Start with your timetable." : "Make every gap on campus count.",
}),
).toBeVisible();
await expect(page.getByRole("button", { name: "Try a demo" })).toBeVisible();
await expect(page.locator(".university-home h1, .global-home h1").first()).toBeVisible();
await expect(page.locator(".university-home-primary").first()).toBeVisible();
await expect(page.locator("#ics-file")).toHaveCount(1);
}
4 changes: 2 additions & 2 deletions e2e/mcgill-university.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ test("McGill calendar import preserves downtown campus search and routing", asyn
const failures = watchForAppFailures(page, baseURL);

await page.goto(editionUrl(baseURL, "mcgill"));
await expect(page.getByText("For McGill University", { exact: true })).toBeVisible();
await expect(page.getByText("Gapwise for McGill University", { exact: true })).toBeVisible();
await expect(page.locator(".brand-scope-pill").first()).toHaveText("McGill");

await page
Expand Down Expand Up @@ -49,7 +49,7 @@ test("McGill demo remains usable on the mobile campus map", async ({ page, baseU
const failures = watchForAppFailures(page, baseURL);

await page.goto(editionUrl(baseURL, "mcgill"));
await page.getByRole("button", { name: "Try a demo" }).click();
await page.getByRole("button", { name: /Try the .* demo/ }).click();
await expect(page).toHaveURL(/\/timetable/);
await page.getByRole("group", { name: "Weekday" }).getByRole("button", { name: /Mon/ }).click();
await expect(page.getByText("COMP 202").first()).toBeVisible();
Expand Down
Loading
Loading