224 lines
9.9 KiB
TypeScript
224 lines
9.9 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import {
|
|
captureBrowserFailures,
|
|
clickUntilVisible,
|
|
gotoLiveView,
|
|
loginWithPassword,
|
|
markerKinds,
|
|
projectEmail,
|
|
projectText,
|
|
registerAndConfirm,
|
|
selectOptionContaining,
|
|
setRequestLocation,
|
|
waitForMapReady,
|
|
} from "./helpers";
|
|
|
|
test("two users complete medicine tracking, handover, realtime chat, and blind reviews", async ({
|
|
browser,
|
|
request,
|
|
}, testInfo) => {
|
|
const runID = process.env.E2E_RUN_ID;
|
|
const fixturePassword = process.env.E2E_FIXTURE_PASSWORD;
|
|
const requestTitle = projectText(
|
|
runID ? `Staging E2E medicine pickup ${runID}` : "E2E medicine pickup",
|
|
testInfo.project.name,
|
|
);
|
|
const requesterEmail = projectEmail("requester", testInfo.project.name);
|
|
const helperEmail = projectEmail("helper", testInfo.project.name);
|
|
const replacementEmail = projectEmail("replacement-helper", testInfo.project.name);
|
|
const requester =
|
|
runID && fixturePassword
|
|
? await loginWithPassword(browser, requesterEmail, fixturePassword)
|
|
: await registerAndConfirm(browser, request, requesterEmail, "E2E Requester");
|
|
const helper =
|
|
runID && fixturePassword
|
|
? await loginWithPassword(browser, helperEmail, fixturePassword)
|
|
: await registerAndConfirm(browser, request, helperEmail, "E2E Helper");
|
|
const replacement =
|
|
runID && fixturePassword
|
|
? await loginWithPassword(browser, replacementEmail, fixturePassword)
|
|
: await registerAndConfirm(
|
|
browser,
|
|
request,
|
|
replacementEmail,
|
|
"E2E Replacement Helper",
|
|
);
|
|
const assertRequesterClean = captureBrowserFailures(requester.page);
|
|
const assertHelperClean = captureBrowserFailures(helper.page);
|
|
const assertReplacementClean = captureBrowserFailures(replacement.page);
|
|
|
|
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: "E2E central district",
|
|
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).toHaveURL(/\/requests\/[0-9a-f-]+$/);
|
|
const requestURL = requester.page.url();
|
|
await expect(requester.page.getByRole("heading", { name: requestTitle })).toBeVisible();
|
|
|
|
await gotoLiveView(helper.page, requestURL);
|
|
await helper.page.getByRole("button", { name: "I can help" }).click();
|
|
await expect(helper.page.getByRole("heading", { name: "Private match chat" })).toBeVisible();
|
|
await expect(requester.page.getByRole("heading", { name: "Private match chat" })).toBeVisible();
|
|
|
|
await helper.page
|
|
.getByPlaceholder("Write a safe coordination message…")
|
|
.fill("I am on my way to the pharmacy.");
|
|
await helper.page.getByRole("button", { name: "Send", exact: true }).click();
|
|
await expect(requester.page.getByText("I am on my way to the pharmacy.")).toBeVisible();
|
|
|
|
await requester.page
|
|
.getByPlaceholder("Write a safe coordination message…")
|
|
.fill("Thank you. The order is waiting at the counter.");
|
|
await requester.page.getByRole("button", { name: "Send", exact: true }).click();
|
|
await expect(helper.page.getByText("Thank you. The order is waiting at the counter.")).toBeVisible();
|
|
|
|
const origin = new URL(process.env.BASE_URL!).origin;
|
|
await helper.context.grantPermissions(["geolocation"], { origin });
|
|
await helper.context.setGeolocation({
|
|
latitude: 50.4504,
|
|
longitude: 30.5237,
|
|
accuracy: 5,
|
|
});
|
|
await helper.page.getByRole("button", { name: "Share location" }).click();
|
|
await expect(helper.page.getByRole("button", { name: "Stop and delete position" })).toBeVisible();
|
|
await expect.poll(() => markerKinds(requester.page)).toContain("Shared location");
|
|
|
|
await helper.page.getByRole("button", { name: "Stop and delete position" }).click();
|
|
await expect(helper.page.getByRole("button", { name: "Share location" })).toBeVisible();
|
|
await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared location");
|
|
|
|
await helper.page.getByRole("button", { name: "Start helping" }).click();
|
|
const handoverCode = (
|
|
await requester.page.locator("text=HANDOVER CODE FOR THE HELPER").locator("..").locator(".font-mono").innerText()
|
|
).replace(/\s/g, "");
|
|
expect(handoverCode).toMatch(/^\d{6}$/);
|
|
|
|
await helper.page.getByLabel("Enter handover code").fill(handoverCode);
|
|
await helper.page.getByRole("button", { name: "Verify handover" }).click();
|
|
await expect(helper.page.getByText("Handover code verified.")).toBeVisible();
|
|
|
|
await helper.page.getByRole("button", { name: "Confirm my part is complete" }).click();
|
|
await requester.page.getByRole("button", { name: "Confirm my part is complete" }).click();
|
|
await expect(requester.page.getByRole("heading", { name: "Double-blind review" })).toBeVisible();
|
|
await expect(helper.page.getByRole("heading", { name: "Double-blind review" })).toBeVisible();
|
|
|
|
await helper.page.getByLabel("Rating").selectOption("5");
|
|
await helper.page.getByLabel("Comment (optional)").fill("Clear and safe coordination.");
|
|
await helper.page.getByRole("button", { name: "Submit review" }).click();
|
|
await expect(
|
|
helper.page
|
|
.locator("#main-content")
|
|
.getByText("Review saved. It appears after both participants review."),
|
|
).toBeVisible();
|
|
|
|
await waitForMapReady(requester.page);
|
|
await gotoLiveView(requester.page, "/profile");
|
|
await expect(requester.page.getByText("None revealed yet.")).toBeVisible();
|
|
await expect(requester.page.getByText("Clear and safe coordination.")).toHaveCount(0);
|
|
await gotoLiveView(requester.page, requestURL);
|
|
|
|
await requester.page.getByLabel("Rating").selectOption("5");
|
|
await requester.page.getByLabel("Comment (optional)").fill("Reliable volunteer.");
|
|
await requester.page.getByRole("button", { name: "Submit review" }).click();
|
|
await expect(
|
|
requester.page
|
|
.locator("#main-content")
|
|
.getByText("Review saved. It appears after both participants review."),
|
|
).toBeVisible();
|
|
|
|
await waitForMapReady(requester.page);
|
|
await gotoLiveView(requester.page, "/profile");
|
|
await expect(requester.page.getByText("Clear and safe coordination.")).toBeVisible();
|
|
await waitForMapReady(helper.page);
|
|
await gotoLiveView(helper.page, "/profile");
|
|
await expect(helper.page.getByText("Reliable volunteer.")).toBeVisible();
|
|
|
|
const roadsideTitle = projectText(
|
|
runID ? `Staging E2E motorcycle chain ${runID}` : "E2E motorcycle chain",
|
|
testInfo.project.name,
|
|
);
|
|
await gotoLiveView(requester.page, "/requests/new");
|
|
await selectOptionContaining(
|
|
requester.page,
|
|
"Category",
|
|
"Motorcycle roadside help",
|
|
);
|
|
await requester.page.getByLabel("Motorcycle problem").selectOption("broken_chain");
|
|
await requester.page
|
|
.getByLabel("Motorcycle is away from active traffic")
|
|
.selectOption("true");
|
|
await requester.page.getByLabel("Short title").fill(roadsideTitle);
|
|
await requester.page
|
|
.getByLabel("What help do you need?")
|
|
.fill("The motorcycle is secured and needs help with a broken chain.");
|
|
await requester.page.getByLabel("Urgency").selectOption("today");
|
|
await requester.page.getByRole("button", { name: "In 3 hours" }).click();
|
|
await setRequestLocation(requester.page, {
|
|
label: "E2E secured roadside area",
|
|
mode: "hidden",
|
|
});
|
|
await requester.page.locator("#request-form input[type=checkbox]").last().check();
|
|
await requester.page.getByRole("button", { name: "Publish request" }).click();
|
|
await expect(requester.page.getByRole("heading", { name: roadsideTitle })).toBeVisible();
|
|
|
|
const roadsideURL = requester.page.url();
|
|
await gotoLiveView(helper.page, roadsideURL);
|
|
const withdrawalSummary = helper.page
|
|
.locator("summary")
|
|
.filter({ hasText: "Withdraw from this request" });
|
|
await clickUntilVisible(
|
|
helper.page.getByRole("button", { name: "I can help" }),
|
|
withdrawalSummary,
|
|
);
|
|
await withdrawalSummary.click();
|
|
const withdrawalForm = helper.page.locator("#assignment-withdrawal-form");
|
|
await withdrawalForm.getByLabel("Reason").selectOption("requester_unreachable");
|
|
await withdrawalForm
|
|
.getByLabel("Note (optional)")
|
|
.fill("I could not reach the requester during the E2E workflow.");
|
|
await withdrawalForm.getByRole("button", { name: "Confirm withdrawal" }).click();
|
|
|
|
await expect(
|
|
helper.page.getByText(
|
|
"You left this match. The request is open for another helper if time remains.",
|
|
),
|
|
).toBeVisible();
|
|
await expect(helper.page.getByRole("heading", { name: "Private match chat" })).toHaveCount(0);
|
|
await expect(helper.page.getByRole("button", { name: "I can help" })).toBeVisible();
|
|
await expect(requester.page.getByText("Open", { exact: true })).toBeVisible();
|
|
await expect(
|
|
requester.page.getByText(
|
|
"The previous helper left. This request is open for a new helper again.",
|
|
),
|
|
).toBeVisible();
|
|
|
|
await gotoLiveView(replacement.page, roadsideURL);
|
|
await replacement.page.getByRole("button", { name: "I can help" }).click();
|
|
await expect(
|
|
replacement.page.getByRole("heading", { name: "Private match chat" }),
|
|
).toBeVisible();
|
|
await expect(requester.page.getByText("Helper: E2E Replacement Helper")).toBeVisible();
|
|
await expect(helper.page.getByRole("heading", { name: "Private match chat" })).toHaveCount(0);
|
|
|
|
assertRequesterClean();
|
|
assertHelperClean();
|
|
assertReplacementClean();
|
|
await requester.context.close();
|
|
await helper.context.close();
|
|
await replacement.context.close();
|
|
});
|