142 lines
5.1 KiB
TypeScript
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();
|
|
});
|
|
});
|