165 lines
5.1 KiB
TypeScript
165 lines
5.1 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
|
|
import { mockCoreApis } from "@e2e/fixtures/api-mocks";
|
|
import { apiEnvelope } from "@e2e/fixtures/data/common";
|
|
import {
|
|
clearBrowserState,
|
|
seedEmailSession,
|
|
} from "@e2e/fixtures/test-helpers";
|
|
|
|
const postId = "11111111-1111-1111-1111-111111111111";
|
|
let unlocked = false;
|
|
|
|
test.beforeEach(async ({ baseURL, context, page }) => {
|
|
unlocked = false;
|
|
await clearBrowserState(context, page, baseURL);
|
|
await mockCoreApis(page);
|
|
await registerPrivateZoneVideoMocks(page);
|
|
});
|
|
|
|
test("Private Zone defaults to Albums and reveals video after switching to Moments", async ({
|
|
page,
|
|
}) => {
|
|
await seedEmailSession(page);
|
|
|
|
const listRequest = page.waitForRequest((request) => {
|
|
const url = new URL(request.url());
|
|
return (
|
|
url.pathname === "/api/private-zone/posts" &&
|
|
url.searchParams.get("characterId") === "maya-tan"
|
|
);
|
|
});
|
|
await page.goto("/characters/maya/private-zone");
|
|
await listRequest;
|
|
|
|
await expect(
|
|
page.getByRole("heading", { name: "Private albums" }),
|
|
).toBeVisible();
|
|
await expect(page.getByText("Maya archive")).toBeVisible();
|
|
await page.getByRole("tab", { name: "Moments" }).click();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Private moments" }),
|
|
).toBeVisible();
|
|
await expect(page.getByText("Only for you.")).toBeVisible();
|
|
await expect(page.locator("video")).toHaveCount(0);
|
|
|
|
await page.getByRole("button", { name: "Unlock for 100 credits" }).click();
|
|
const dialog = page.getByRole("alertdialog");
|
|
await expect(dialog).toContainText("100 credits");
|
|
const unlockRequest = page.waitForRequest(
|
|
`**/api/private-zone/posts/${postId}/unlock`,
|
|
);
|
|
await dialog.getByRole("button", { name: "Unlock", exact: true }).click();
|
|
expect((await unlockRequest).postDataJSON()).toEqual({ expectedCost: 100 });
|
|
|
|
const video = page.locator("video");
|
|
await expect(video).toHaveCount(1);
|
|
await expect(video.locator("source")).toHaveAttribute("src", /video\.mp4/);
|
|
await expect(page.getByRole("alertdialog")).toHaveCount(0);
|
|
});
|
|
|
|
test("Albums render first and Moments remain available behind the second tab", async ({ page }) => {
|
|
await seedEmailSession(page);
|
|
await page.goto("/characters/maya/private-zone");
|
|
|
|
const tabs = page.getByRole("tab");
|
|
await expect(tabs).toHaveText(["Albums", "Moments"]);
|
|
await expect(page.getByRole("tab", { name: "Albums" })).toHaveAttribute(
|
|
"aria-selected",
|
|
"true",
|
|
);
|
|
await expect(
|
|
page.getByRole("heading", { name: "Private albums" }),
|
|
).toBeVisible();
|
|
await expect(page.getByText("Maya archive")).toBeVisible();
|
|
|
|
await page.getByRole("tab", { name: "Moments" }).click();
|
|
await expect(page.getByRole("tab", { name: "Moments" })).toHaveAttribute(
|
|
"aria-selected",
|
|
"true",
|
|
);
|
|
await expect(
|
|
page.getByRole("heading", { name: "Private moments" }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
async function registerPrivateZoneVideoMocks(page: Page): Promise<void> {
|
|
await page.route("**/api/private-zone/posts**", async (route) => {
|
|
const url = new URL(route.request().url());
|
|
await route.fulfill({
|
|
json: apiEnvelope({
|
|
characterId: url.searchParams.get("characterId") ?? "maya-tan",
|
|
items: [videoPost(unlocked)],
|
|
nextCursor: null,
|
|
hasMore: false,
|
|
creditBalance: unlocked ? 150 : 250,
|
|
currency: "credits",
|
|
}),
|
|
});
|
|
});
|
|
|
|
// Playwright evaluates page routes in reverse registration order. Register
|
|
// the specific mutation last so the broader list route cannot intercept it.
|
|
await page.route("**/api/private-zone/posts/*/unlock", async (route) => {
|
|
unlocked = true;
|
|
await route.fulfill({
|
|
json: apiEnvelope({
|
|
postId,
|
|
reason: "ok",
|
|
unlocked: true,
|
|
creditsCharged: 100,
|
|
requiredCredits: 100,
|
|
currentCredits: 150,
|
|
shortfallCredits: 0,
|
|
creditBalance: 150,
|
|
post: videoPost(true),
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/private-zone/albums**", async (route) => {
|
|
await route.fulfill({
|
|
json: apiEnvelope({
|
|
items: [
|
|
{
|
|
albumId: "album-maya",
|
|
title: "Maya archive",
|
|
content: null,
|
|
previewText: "Private photos",
|
|
imageCount: 1,
|
|
images: [{ url: "/images/private-zone/banner/maya.png", locked: true, index: 0 }],
|
|
locked: true,
|
|
unlocked: false,
|
|
unlockCost: 80,
|
|
publishedAt: "2026-07-24T10:00:00+00:00",
|
|
lockDetail: { locked: true },
|
|
},
|
|
],
|
|
creditBalance: 250,
|
|
pendingImageCount: 0,
|
|
hasIncompleteContent: false,
|
|
}),
|
|
});
|
|
});
|
|
}
|
|
|
|
function videoPost(isUnlocked: boolean) {
|
|
return {
|
|
postId,
|
|
characterId: "maya-tan",
|
|
caption: "Only for you.",
|
|
posterUrl: "/images/private-zone/banner/maya.png",
|
|
mediaType: "video",
|
|
videoUrl: isUnlocked ? "/test-video.mp4?token=short" : null,
|
|
videoMimeType: "video/mp4",
|
|
videoSizeBytes: 1024,
|
|
durationSeconds: 18,
|
|
unlockCostCredits: 100,
|
|
currency: "credits",
|
|
locked: !isUnlocked,
|
|
unlocked: isUnlocked,
|
|
availableForNewUnlock: true,
|
|
publishedAt: "2026-07-24T10:00:00+00:00",
|
|
};
|
|
}
|