201 lines
9.4 KiB
TypeScript
201 lines
9.4 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import {
|
|
captureBrowserFailures,
|
|
loginWithMagicLink,
|
|
registerAndConfirm,
|
|
selectOptionContaining,
|
|
} from "./helpers";
|
|
|
|
test("activity approval, privacy controls, reporting, and moderation work end to end", async ({
|
|
browser,
|
|
request,
|
|
}) => {
|
|
const organizer = await registerAndConfirm(
|
|
browser,
|
|
request,
|
|
"activity-organizer@example.invalid",
|
|
"E2E Activity Organizer",
|
|
);
|
|
const participant = await registerAndConfirm(
|
|
browser,
|
|
request,
|
|
"activity-participant@example.invalid",
|
|
"E2E Activity Participant",
|
|
);
|
|
const assertOrganizerClean = captureBrowserFailures(organizer.page);
|
|
const assertParticipantClean = captureBrowserFailures(participant.page);
|
|
|
|
await participant.page.goto("/moderation");
|
|
await expect(participant.page).toHaveURL(/\/requests$/);
|
|
await expect(participant.page.getByText("Moderator access is required.")).toBeVisible();
|
|
|
|
await organizer.page.goto("/activities/new");
|
|
await selectOptionContaining(organizer.page, "Activity type", "Coffee or tea");
|
|
await organizer.page.getByLabel("Preferred setting").selectOption("cafe");
|
|
await organizer.page.getByLabel("Short title").fill("E2E public coffee meetup");
|
|
await organizer.page
|
|
.getByLabel("Plan and expectations")
|
|
.fill("Meet at a public café for an hour of safe conversation.");
|
|
|
|
const startsAt = new Date(Date.now() + 4 * 60 * 60 * 1000).toISOString().slice(0, 16);
|
|
const joinDeadline = new Date(Date.now() + 2 * 60 * 60 * 1000)
|
|
.toISOString()
|
|
.slice(0, 16);
|
|
await organizer.page.getByLabel("Starts at (UTC)").fill(startsAt);
|
|
await organizer.page.getByLabel("Join requests close (UTC)").fill(joinDeadline);
|
|
await organizer.page.getByLabel("Total group capacity").fill("3");
|
|
await organizer.page
|
|
.getByLabel("Approximate public meeting area")
|
|
.fill("E2E central public café");
|
|
await organizer.page.getByLabel("Public map visibility").selectOption("approximate_public");
|
|
await organizer.page.getByLabel("Latitude").fill("50.4501");
|
|
await organizer.page.getByLabel("Longitude").fill("30.5234");
|
|
await organizer.page.locator("#activity-form input[type=checkbox]").check();
|
|
await organizer.page.getByRole("button", { name: "Publish activity" }).click();
|
|
await expect(organizer.page).toHaveURL(/\/activities\/[0-9a-f-]+$/);
|
|
const activityURL = organizer.page.url();
|
|
|
|
await participant.page.goto(activityURL);
|
|
await expect(participant.page.getByRole("heading", { name: "Approved group chat" })).toHaveCount(
|
|
0,
|
|
);
|
|
await participant.page.getByRole("button", { name: "Request to join" }).click();
|
|
await expect(participant.page.getByText("Approval pending")).toBeVisible();
|
|
await expect(organizer.page.getByText("E2E Activity Participant")).toBeVisible();
|
|
|
|
const organizerControls = organizer.page
|
|
.getByRole("heading", { name: "Organizer controls" })
|
|
.locator("..");
|
|
await organizerControls.getByRole("button", { name: "Approve" }).click();
|
|
await expect(
|
|
participant.page.getByRole("heading", { name: "Approved group chat" }),
|
|
).toBeVisible();
|
|
|
|
await participant.page
|
|
.getByPlaceholder("Message the approved group")
|
|
.fill("I will meet you by the public entrance.");
|
|
await participant.page.getByRole("button", { name: "Send", exact: true }).click();
|
|
await expect(organizer.page.getByText("I will meet you by the public entrance.")).toBeVisible();
|
|
|
|
await organizer.page
|
|
.getByPlaceholder("Message the approved group")
|
|
.fill("Welcome to the E2E activity group.");
|
|
await organizer.page.getByRole("button", { name: "Send", exact: true }).click();
|
|
await expect(participant.page.getByText("Welcome to the E2E activity group.")).toBeVisible();
|
|
|
|
const organizerMessage = participant.page
|
|
.locator("#activity-messages article")
|
|
.filter({ hasText: "Welcome to the E2E activity group." });
|
|
await organizerMessage.getByRole("button", { name: "Report" }).click();
|
|
const safetyControls = participant.page
|
|
.getByRole("heading", { name: "Safety controls" })
|
|
.locator("..");
|
|
await expect(
|
|
safetyControls.getByText("The report is scoped to the selected group message."),
|
|
).toBeVisible();
|
|
await safetyControls.getByLabel("Reason").selectOption("harassment");
|
|
await safetyControls
|
|
.getByLabel("What happened?")
|
|
.fill("E2E moderation evidence for the selected activity message.");
|
|
await safetyControls.getByRole("button", { name: "Send report" }).click();
|
|
await expect(participant.page.getByText("Report sent to moderators.")).toBeVisible();
|
|
|
|
await participant.page.goto("/categories/proposals");
|
|
await participant.page.getByLabel("Proposed category").fill("E2E quiet board games");
|
|
await participant.page.getByLabel("Mode").selectOption("activity");
|
|
await participant.page
|
|
.getByLabel("Why is this useful?")
|
|
.fill("A moderated public activity category for quiet board-game meetups.");
|
|
await participant.page.getByRole("button", { name: "Publish proposal" }).click();
|
|
await expect(
|
|
participant.page.getByText("Proposal published for community voting."),
|
|
).toBeVisible();
|
|
|
|
await participant.page.goto(activityURL);
|
|
await participant.page.getByRole("button", { name: "Block organizer" }).click();
|
|
await expect(participant.page).toHaveURL(/\/activities$/);
|
|
await participant.page.goto("/profile");
|
|
const blockedUsers = participant.page.getByRole("heading", { name: "Blocked users" }).locator("..");
|
|
await expect(blockedUsers.getByText("E2E Activity Organizer")).toBeVisible();
|
|
await blockedUsers.getByRole("button", { name: "Unblock" }).click();
|
|
await expect(participant.page.getByText("User unblocked.")).toBeVisible();
|
|
|
|
await participant.page.getByLabel("Default location visibility").selectOption(
|
|
"exact_for_active_match",
|
|
);
|
|
await participant.page
|
|
.getByLabel("Optional external thank-you link")
|
|
.fill("https://example.invalid/e2e-thanks");
|
|
await participant.page.getByRole("button", { name: "Save profile" }).click();
|
|
await expect(participant.page.getByText("Profile updated.")).toBeVisible();
|
|
|
|
await participant.page.getByLabel("Network").selectOption("telegram");
|
|
await participant.page.getByLabel("Public profile URL").fill("https://t.me/e2e_participant");
|
|
await participant.page.getByLabel("Handle (optional)").fill("@e2e_participant");
|
|
await participant.page.getByRole("button", { name: "Add unverified link" }).click();
|
|
await expect(participant.page.getByText("Social link added as unverified.")).toBeVisible();
|
|
const socialSection = participant.page
|
|
.getByRole("heading", { name: "Social links" })
|
|
.locator("xpath=ancestor::section");
|
|
await expect(socialSection.getByText("telegram", { exact: true })).toBeVisible();
|
|
await socialSection.getByRole("button", { name: "Remove" }).click();
|
|
await expect(socialSection.getByText("No social links added.")).toBeVisible();
|
|
|
|
await participant.page.goto("/requests/new");
|
|
await expect(participant.page.getByLabel("Location visibility")).toHaveValue(
|
|
"exact_for_active_match",
|
|
);
|
|
|
|
const admin = await loginWithMagicLink(browser, request, "e2e-admin@example.invalid");
|
|
const assertAdminClean = captureBrowserFailures(admin.page);
|
|
await admin.page.goto("/moderation");
|
|
await expect(admin.page.getByRole("heading", { name: "Moderation" })).toBeVisible();
|
|
|
|
const reportCard = admin.page
|
|
.locator("article")
|
|
.filter({ hasText: "E2E moderation evidence for the selected activity message." });
|
|
await reportCard.getByRole("button", { name: "View scoped evidence" }).click();
|
|
const evidence = admin.page
|
|
.getByRole("heading", { name: "Reported evidence" })
|
|
.locator("xpath=ancestor::section");
|
|
await expect(evidence.getByText("Welcome to the E2E activity group.")).toBeVisible();
|
|
await evidence.getByRole("button", { name: "Close" }).click();
|
|
await reportCard.getByRole("combobox").selectOption("resolved");
|
|
await reportCard.getByPlaceholder("Resolution note").fill("Reviewed in isolated browser E2E.");
|
|
await reportCard.getByRole("button", { name: "Save" }).click();
|
|
await expect(admin.page.getByText("Report updated.")).toBeVisible();
|
|
|
|
const proposalCard = admin.page
|
|
.locator("article")
|
|
.filter({ hasText: "E2E quiet board games" });
|
|
await proposalCard.getByPlaceholder("Rejection note").fill("Covered by an existing category.");
|
|
await proposalCard.getByRole("button", { name: "Reject" }).click();
|
|
await expect(admin.page.getByText("Proposal rejected.")).toBeVisible();
|
|
|
|
const participantRow = admin.page
|
|
.locator("tbody tr")
|
|
.filter({ hasText: "activity-participant@example.invalid" });
|
|
await participantRow.getByRole("combobox").first().selectOption("restricted");
|
|
await participantRow.getByPlaceholder("Internal note").fill("E2E restriction boundary.");
|
|
await participantRow.getByRole("button", { name: "Save" }).click();
|
|
await expect(admin.page.getByText("User status updated.")).toBeVisible();
|
|
|
|
await participant.page.goto("/categories/proposals");
|
|
await participant.page.getByLabel("Proposed category").fill("E2E restricted proposal");
|
|
await participant.page.getByLabel("Mode").selectOption("help");
|
|
await participant.page
|
|
.getByLabel("Why is this useful?")
|
|
.fill("This must be rejected because the account is restricted.");
|
|
await participant.page.getByRole("button", { name: "Publish proposal" }).click();
|
|
await expect(
|
|
participant.page.getByText("Confirm your account and ensure it is active first."),
|
|
).toBeVisible();
|
|
|
|
assertOrganizerClean();
|
|
assertParticipantClean();
|
|
assertAdminClean();
|
|
await organizer.context.close();
|
|
await participant.context.close();
|
|
await admin.context.close();
|
|
});
|