who_need_help/e2e/tests/notifications-data.spec.ts

155 lines
6.7 KiB
TypeScript

import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
import {
captureBrowserFailures,
gotoLiveView,
gotoWithTransientRetry,
projectEmail,
projectText,
registerAndConfirm,
selectOptionContaining,
setRequestLocation,
waitForMapIdle,
} from "./helpers";
test("nearby alerts, private notification inbox, preferences, and data export work end to end", async ({
browser,
request,
}, testInfo) => {
const scope = `${testInfo.project.name}-${Date.now()}-${process.pid}`;
const subscriber = await registerAndConfirm(
browser,
request,
projectEmail(`notification-subscriber-${scope}`, testInfo.project.name),
"E2E Notification Subscriber",
);
const requester = await registerAndConfirm(
browser,
request,
projectEmail(`notification-requester-${scope}`, testInfo.project.name),
"E2E Notification Requester",
);
const assertSubscriberClean = captureBrowserFailures(subscriber.page);
const assertRequesterClean = captureBrowserFailures(requester.page);
const alertName = projectText("Urgent medicine nearby", scope);
const privateArea = projectText("Private subscriber center", scope);
const requestTitle = projectText("Nearby notification request", scope);
await gotoLiveView(subscriber.page, "/notifications");
await subscriber.page.getByRole("link", { name: "Delivery settings" }).click();
const preferenceForm = subscriber.page.locator("#notification-preferences-form");
await preferenceForm.getByLabel("Allow email notifications").check();
await preferenceForm.getByLabel("Nearby requests by email").check();
await preferenceForm.getByLabel("Use quiet hours").check();
await preferenceForm.getByLabel("From").fill("22:00");
await preferenceForm.getByLabel("Until").fill("07:00");
await preferenceForm.getByRole("button", { name: "Save preferences" }).click();
await expect(subscriber.page.getByText("Notification preferences saved.")).toBeVisible();
await subscriber.page
.getByRole("link", { name: "Nearby alerts", exact: true })
.click();
const subscriptionForm = subscriber.page.locator("#nearby-subscription-form");
await subscriptionForm.getByLabel("Alert name").fill(alertName);
await subscriptionForm.getByLabel("Private area label").fill(privateArea);
await subscriptionForm
.getByText("Enter coordinates manually", { exact: true })
.click();
await subscriptionForm.getByLabel("Latitude").fill("50.4501");
await subscriptionForm.getByLabel("Longitude").fill("30.5234");
await subscriptionForm
.locator('input[name="nearby_subscription[radius_meters]"][value="3000"]')
.check();
await expect
.poll(
async () => {
const map = subscriber.page.locator("#nearby-subscription-map");
if ((await map.getAttribute("data-map-idle")) === "true") {
return "idle";
}
if ((await map.locator("[data-map-fallback=true]").count()) === 1) {
return "fallback";
}
return null;
},
{ timeout: 15_000 },
)
.not.toBeNull();
await subscriptionForm.getByLabel("Email notification").check();
await subscriptionForm.getByRole("button", { name: "Create nearby alert" }).click();
await expect(subscriber.page.getByText("Nearby alert created.")).toBeVisible();
const savedAlert = subscriber.page
.getByRole("heading", { name: "Your nearby alerts" })
.locator("..");
await expect(savedAlert.getByRole("heading", { name: alertName })).toBeVisible();
await expect(savedAlert.getByText(`${privateArea} · 3 km`)).toBeVisible();
await expect(savedAlert.getByText("Push", { exact: true })).toBeVisible();
await expect(savedAlert.getByText("Email", { exact: true })).toBeVisible();
const accessibility = await new AxeBuilder({ page: subscriber.page }).analyze();
expect(
accessibility.violations,
accessibility.violations
.map((violation) => `${violation.id}: ${violation.help}`)
.join("\n"),
).toEqual([]);
await gotoLiveView(requester.page, "/requests/new");
await selectOptionContaining(requester.page, "Category", "Medicine pickup");
await requester.page.getByLabel("Medicine pickup status").selectOption("reserved");
await requester.page.getByLabel("Short title").fill(requestTitle);
await requester.page
.getByLabel("What help do you need?")
.fill("Please collect the legal medicine that is already reserved.");
await requester.page.getByLabel("Urgency").selectOption("now");
await requester.page.getByRole("button", { name: "In 3 hours" }).click();
await setRequestLocation(requester.page, {
label: "Public notification test area",
mode: "approximate_public",
latitude: "50.4501",
longitude: "30.5234",
radiusMeters: 1000,
});
await requester.page.locator("#request-form input[type=checkbox]").check();
await requester.page.getByRole("button", { name: "Publish request" }).click();
await expect(requester.page.getByRole("heading", { name: requestTitle })).toBeVisible();
const requestURL = requester.page.url();
await subscriber.page.getByRole("link", { name: "Inbox" }).click();
const inbox = subscriber.page
.getByRole("heading", { name: "Inbox" })
.locator("xpath=ancestor::section");
const notification = inbox.getByRole("button", { name: /New help request nearby/ });
await expect(notification).toBeVisible({ timeout: 20_000 });
await expect(inbox.getByText(privateArea)).toHaveCount(0);
await notification.click();
await expect(subscriber.page).toHaveURL(requestURL);
await expect(subscriber.page.getByRole("heading", { name: requestTitle })).toBeVisible();
// WebKit reports cancelled image decoding as a page error when a document
// destroys a raster map before its worker reaches idle. Waiting here tests
// the real rendered state and avoids navigating away mid-decode.
await waitForMapIdle(subscriber.page);
await gotoWithTransientRetry(subscriber.page, "/users/settings");
await expect(subscriber.page.getByRole("heading", { name: "Account Settings" })).toBeVisible();
assertSubscriberClean();
assertRequesterClean();
const exportLink = subscriber.page.getByRole("link", { name: "Download my data" });
await expect(exportLink).toHaveAttribute("href", "/users/data-export");
const exportURL = new URL("/users/data-export", subscriber.page.url()).toString();
const exportResponse = await subscriber.context.request.get(exportURL);
expect(exportResponse.status()).toBe(200);
expect(exportResponse.headers()["content-disposition"]).toMatch(
/^attachment; filename="who-need-help-account-export-\d{4}-\d{2}-\d{2}\.json"$/,
);
expect((await exportResponse.json()).format).toBe("who-need-help-account-export");
await subscriber.context.close();
await requester.context.close();
});