Files
kandev/apps/web/e2e/tests/auth/mobile-system-data-storage-member-gating.spec.ts
T
2026-09-04 00:37:41 +00:00

142 lines
5.1 KiB
TypeScript

import { devices, expect } from "@playwright/test";
import path from "node:path";
import { backendFixture as test } from "../../fixtures/backend";
import { login, setupAdmin } from "../../helpers/auth";
import {
createMember,
createSnapshotAsAdmin,
DATA_STORAGE_ROUTE,
expectMemberApiGating,
GATING_ADMIN,
GATING_MEMBER,
STORAGE_ROUTE,
} from "./data-storage-gating-helpers";
/**
* Mobile parity for the Data & Logs and Storage member gates. The split keeps
* backups on Data & Logs and disables storage actions on Storage. This spec
* proves both owning pages are complete, inert, and contained at Pixel 5 width.
*
* Runs in the `mobile-chrome` project (routed away from the desktop `auth`
* project via its testIgnore) and restarts the worker backend with auth on
* and its own database, mirroring system-data-storage-member-gating.spec.ts.
* afterAll restarts to the fixture baseline.
*/
test.describe.serial("Data & Logs and Storage member gating (mobile)", () => {
let snapshotName = "";
test.beforeAll(async ({ backend, browser }) => {
await backend.restart({
KANDEV_FEATURES_AUTH: "true",
KANDEV_DATABASE_PATH: path.join(backend.tmpDir, "kandev-mobile-data-storage.db"),
});
const adminContext = await browser.newContext({
...devices["Pixel 5"],
baseURL: backend.frontendUrl,
});
await setupAdmin(adminContext, backend.baseUrl, GATING_ADMIN);
await login(adminContext, backend.baseUrl, GATING_ADMIN);
await createMember(adminContext, backend.baseUrl);
snapshotName = await createSnapshotAsAdmin(adminContext, backend.baseUrl);
await adminContext.close();
});
test.afterAll(async ({ backend }) => {
await backend.restart();
});
test("a member gets the read-only view, inert and contained, on a phone", async ({
browser,
backend,
}) => {
// Manual contexts do not inherit the project's device options; spread
// them explicitly so this spec actually exercises the mobile viewport.
const ctx = await browser.newContext({
...devices["Pixel 5"],
baseURL: backend.frontendUrl,
});
await login(ctx, backend.baseUrl, GATING_MEMBER);
await expectMemberApiGating(ctx, backend.baseUrl, snapshotName);
const page = await ctx.newPage();
expect((await page.viewportSize())?.width).toBe(393);
await page.goto(DATA_STORAGE_ROUTE);
await expect(page.getByTestId("system-backups-name").first()).toHaveText(snapshotName, {
timeout: 15_000,
});
await expect(page.getByTestId("system-backups-admin-only")).toBeVisible();
await expect(page.getByTestId("system-backups-create")).toHaveCount(0);
await expect(page.getByTestId("system-backups-download")).toHaveCount(0);
await expect(page.getByTestId("system-backups-restore")).toHaveCount(0);
await expect(page.getByTestId("system-backups-delete")).toHaveCount(0);
await page.goto(STORAGE_ROUTE);
const analyze = page.getByTestId("storage-analyze");
await expect(analyze).toBeDisabled();
await expect(page.getByTestId("storage-run-now")).toBeDisabled();
// The reason must stay reachable by touch rather than hover-only: the
// disabled control keeps a touch-sized target so its tooltip trigger is
// usable on a phone.
const analyzeBox = await analyze.boundingBox();
expect(analyzeBox).not.toBeNull();
expect(analyzeBox!.height).toBeGreaterThanOrEqual(44);
// The narrower table must not push the document into horizontal scroll.
await expect
.poll(() =>
page.evaluate(
() => document.documentElement.scrollWidth <= document.documentElement.clientWidth,
),
)
.toBe(true);
await ctx.close();
});
test("an admin still gets every control across both pages on a phone", async ({
browser,
backend,
}) => {
const ctx = await browser.newContext({
...devices["Pixel 5"],
baseURL: backend.frontendUrl,
});
await login(ctx, backend.baseUrl, GATING_ADMIN);
const page = await ctx.newPage();
expect((await page.viewportSize())?.width).toBe(393);
await page.goto(DATA_STORAGE_ROUTE);
await expect(page.getByTestId("system-backups-create")).toBeVisible({ timeout: 15_000 });
const row = page.getByTestId("system-backups-row").first();
await expect(row.getByTestId("system-backups-delete")).toBeVisible();
for (const testId of [
"system-backups-download",
"system-backups-restore",
"system-backups-delete",
]) {
const action = row.getByTestId(testId);
await expect(action).toBeVisible();
const box = await action.boundingBox();
expect(box).not.toBeNull();
expect(box!.width).toBeGreaterThanOrEqual(44);
expect(box!.height).toBeGreaterThanOrEqual(44);
}
await expect(page.getByTestId("system-backups-admin-only")).toHaveCount(0);
await page.goto(STORAGE_ROUTE);
await expect(page.getByTestId("storage-analyze")).toBeEnabled();
await expect
.poll(() =>
page.evaluate(
() => document.documentElement.scrollWidth <= document.documentElement.clientWidth,
),
)
.toBe(true);
await ctx.close();
});
});