193 lines
7.0 KiB
TypeScript
193 lines
7.0 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 push notifications").check();
|
|
await preferenceForm.getByLabel("Nearby requests").check();
|
|
await expect(
|
|
preferenceForm.getByText(
|
|
"Nearby updates use the in-app inbox and optional push. Email digests are not enabled yet.",
|
|
),
|
|
).toBeVisible();
|
|
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("Push 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 })).toHaveCount(0);
|
|
|
|
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("link", {
|
|
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();
|
|
});
|