Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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 frontend/app/components/ui/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function ComboboxInput({
<InputGroupAddon align="inline-end">
{showTrigger && (
<InputGroupButton
size="icon-xs"
size="icon-sm"
variant="ghost"
render={<ComboboxTrigger aria-label="Show options" />}
data-slot="input-group-button"
Expand Down
13 changes: 8 additions & 5 deletions frontend/e2e/accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ async function expectNoViolations(page: Page, loaded: Locator) {
await expect(page.getByRole("heading", { level: 1 })).toHaveCount(1);

const results = await new AxeBuilder({ page })
// Open dialogs and menus add hidden elements that keep keyboard focus inside them.
// Axe reports these as errors, but they are needed so keyboard navigation works correctly,
// so we exclude them.
.exclude("[data-base-ui-focus-guard]")
.withTags([
"wcag2a",
"wcag2aa",
Expand All @@ -28,11 +32,10 @@ for (const colorScheme of ["light", "dark"] as const) {

test("home page", async ({ page }) => {
await page.goto("./");
// Wait until "Surprise Me" button is ready
await expect(page.getByRole("button", { name: "Surprise me" })).toHaveCSS(
"opacity",
"1",
);
// Wait until the "Surprise me" button is ready
await expect(
page.getByRole("button", { name: "Surprise me" }),
).toBeEnabled();
await expectNoViolations(page, bookCards(page).first());
});

Expand Down
20 changes: 12 additions & 8 deletions frontend/e2e/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { randomUUID } from "node:crypto";
import { expect, logIn, test } from "./fixtures";
import {
expect,
expectLoggedInAs,
expectLoggedOut,
logIn,
logOut,
test,
} from "./fixtures";

test("a new user can sign up", async ({ page }) => {
await page.goto("signup");
Expand All @@ -11,9 +18,7 @@ test("a new user can sign up", async ({ page }) => {
await page.getByRole("button", { name: "Sign up", exact: true }).click();

await expect(page).toHaveURL("./");
await expect(
page.getByRole("banner").getByRole("link", { name: "Someone" }),
).toBeVisible();
await expectLoggedInAs(page, "Someone");
});

test("signing up with a used email shows an error", async ({ page, user }) => {
Expand All @@ -33,15 +38,14 @@ test("signing up with a used email shows an error", async ({ page, user }) => {

test("a user can log out and log in again", async ({ page, user }) => {
await page.goto("./");
const header = page.getByRole("banner");

await header.getByRole("button", { name: "Log out" }).click();
await expect(header.getByRole("link", { name: "Log in" })).toBeVisible();
await logOut(page);
await expectLoggedOut(page);

await logIn(page, user.email, user.password);

await expect(page).toHaveURL("./");
await expect(header.getByRole("link", { name: user.name })).toBeVisible();
await expectLoggedInAs(page, user.name);
});

test("logging in with a wrong password shows an error", async ({
Expand Down
4 changes: 2 additions & 2 deletions frontend/e2e/favorites.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { bookCards, expect, test } from "./fixtures";
import { bookCards, expect, goToProfile, test } from "./fixtures";

test("logged out users are asked to log in to save favorites", async ({
page,
Expand Down Expand Up @@ -37,7 +37,7 @@ test("a favorite shows on the profile and can be removed", async ({
page.getByRole("button", { name: `Favorite ${title}`, pressed: true }),
).toBeVisible();

await page.getByRole("banner").getByRole("link", { name: user.name }).click();
await goToProfile(page, user.name);
const favorites = page.getByRole("region", { name: "Favorite books" });
await favorites.getByRole("link", { name: title }).click();

Expand Down
71 changes: 70 additions & 1 deletion frontend/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,73 @@ async function logIn(page: Page, email: string, password: string) {
await form.getByRole("button", { name: "Log in", exact: true }).click();
}

export { test, bookCards, logIn, expect };
// Below Tailwind's md breakpoint the header shows a menu button instead of its links and buttons.
// Opens that menu on narrow screens and returns true, or returns false on wide screens.
async function openMobileMenu(page: Page) {
if (page.viewportSize()!.width >= 768) return false;
await page.getByRole("button", { name: "Open menu" }).click();
return true;
}

async function expectLoggedInAs(page: Page, name: string) {
if (await openMobileMenu(page)) {
await expect(page.getByText(`Signed in as ${name}`)).toBeVisible();
await page.keyboard.press("Escape");
} else {
await expect(
page.getByRole("banner").getByRole("link", { name }),
).toBeVisible();
}
}

async function expectLoggedOut(page: Page) {
if (await openMobileMenu(page)) {
await expect(page.getByRole("menuitem", { name: "Log in" })).toBeVisible();
await page.keyboard.press("Escape");
} else {
await expect(
page.getByRole("banner").getByRole("link", { name: "Log in" }),
).toBeVisible();
}
}

async function goToProfile(page: Page, name: string) {
if (await openMobileMenu(page)) {
await page.getByRole("menuitem", { name: "Profile" }).click();
} else {
await page.getByRole("banner").getByRole("link", { name }).click();
}
}

async function logOut(page: Page) {
if (await openMobileMenu(page)) {
await page.getByRole("menuitem", { name: "Log out" }).click();
} else {
await page
.getByRole("banner")
.getByRole("button", { name: "Log out" })
.click();
}
}

async function pickTheme(page: Page, theme: "Light" | "Dark" | "System") {
if (await openMobileMenu(page)) {
await page.getByRole("menuitem", { name: "Theme" }).click();
} else {
await page.getByRole("button", { name: "Toggle theme" }).click();
}
await page.getByRole("menuitem", { name: theme }).click();
}

export {
test,
bookCards,
logIn,
openMobileMenu,
expectLoggedInAs,
expectLoggedOut,
goToProfile,
logOut,
pickTheme,
expect,
};
6 changes: 2 additions & 4 deletions frontend/e2e/settings.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, logIn, test } from "./fixtures";
import { expect, expectLoggedInAs, logIn, test } from "./fixtures";

test("profile changes show on the profile page", async ({ page, user }) => {
await page.goto("settings");
Expand Down Expand Up @@ -29,9 +29,7 @@ test("a changed password works for the next log in", async ({ page, user }) => {
await page.context().clearCookies();
await logIn(page, user.email, "new-password");

await expect(
page.getByRole("banner").getByRole("link", { name: user.name }),
).toBeVisible();
await expectLoggedInAs(page, user.name);
});

test("a wrong current password is rejected", async ({ page, user }) => {
Expand Down
5 changes: 2 additions & 3 deletions frontend/e2e/theme.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { expect, test } from "@playwright/test";
import { expect, pickTheme, test } from "./fixtures";

test("dark mode is kept after a reload", async ({ page }) => {
await page.goto("./");
await expect(
page.getByRole("heading", { name: "Discover books worth reading" }),
).toBeVisible();

await page.getByRole("button", { name: "Toggle theme" }).click();
await page.getByRole("menuitem", { name: "Dark" }).click();
await pickTheme(page, "Dark");
await expect(page.locator("html")).toHaveClass("dark");

await page.reload();
Expand Down
2 changes: 1 addition & 1 deletion frontend/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default defineConfig({
// Retry on CI only
retries: process.env.CI ? 2 : 0,
// Opt out of parallel tests on CI
workers: process.env.CI ? 1 : 8,
workers: process.env.CI ? 1 : 4,
// Reporter to use. See https://playwright.dev/docs/test-reporters
reporter: "html",
// Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions.
Expand Down