mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
96 lines
4.6 KiB
JavaScript
96 lines
4.6 KiB
JavaScript
// Penpot Playwright spec — orchestration only. Shared state, login helpers and
|
||
// the lifecycle hook live in `_shared.js`; each login surface is registered
|
||
// from its own `test-login-*.js` companion so every scenario stays atomar and
|
||
// individually inspectable.
|
||
|
||
const shared = require("./_shared");
|
||
const { resolveTimeout } = require("./timeouts");
|
||
const { expectHstsWhenTls, gotoOnion } = require("./personas");
|
||
const { test, expect, skipUnlessServiceEnabled, runGuestFlow, runBiberFlow, runAdminFlow, env, penpotOidcLogin } = shared;
|
||
|
||
test.use({ ignoreHTTPSErrors: true });
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
expect(env.baseUrl, "PENPOT_BASE_URL must be set").toBeTruthy();
|
||
expect(env.canonicalDomain, "CANONICAL_DOMAIN must be set").toBeTruthy();
|
||
await page.context().clearCookies();
|
||
});
|
||
|
||
test("baseline: Penpot responds on the canonical domain with TLS", async ({ page }) => {
|
||
const r = await gotoOnion(page, `${env.baseUrl}/`);
|
||
expect(r, "Expected Penpot response").toBeTruthy();
|
||
expect(r.status(), "Expected Penpot front page status < 500").toBeLessThan(500);
|
||
expect(
|
||
r.url().includes(env.canonicalDomain),
|
||
`Expected canonical domain "${env.canonicalDomain}" to back the Penpot URL`,
|
||
).toBe(true);
|
||
expectHstsWhenTls(r.headers(), env.baseUrl, "Penpot");
|
||
});
|
||
|
||
// Login surfaces — one companion per method × persona.
|
||
require("./test-login-native").register(shared);
|
||
require("./test-login-oidc-admin").register(shared);
|
||
require("./test-login-oidc-biber").register(shared);
|
||
require("./test-login-ldap-admin").register(shared);
|
||
require("./test-login-ldap-biber").register(shared);
|
||
require("./test-seaweedfs");
|
||
|
||
test("project: administrator creates a design project", async ({ page }) => {
|
||
skipUnlessServiceEnabled("sso");
|
||
test.setTimeout(resolveTimeout(120_000)); // OIDC round-trip + dashboard project creation
|
||
await penpotOidcLogin(page, env.adminUsername, env.adminPassword);
|
||
const projectName = `pw-project-${Date.now()}`;
|
||
const addProject = page
|
||
.getByRole("button", { name: /new project|add project|create.*project/i })
|
||
.or(page.locator('[data-testid="add-project"], [data-test="add-project"]'))
|
||
.first();
|
||
await expect(addProject, "Expected a create-project control on the dashboard").toBeVisible({ timeout: resolveTimeout(60_000) });
|
||
await addProject.click();
|
||
const nameInput = page.locator('.project-name input, input[type="text"]:visible, [contenteditable="true"]:visible').first();
|
||
await nameInput.fill(projectName);
|
||
await nameInput.press("Enter");
|
||
await expect(page.getByText(projectName, { exact: false }).first()).toBeVisible({ timeout: resolveTimeout(30_000) });
|
||
});
|
||
|
||
test("asset: administrator uploads an image asset into a design file", async ({ page }) => {
|
||
skipUnlessServiceEnabled("sso");
|
||
test.setTimeout(resolveTimeout(180_000)); // OIDC + editor load + image upload
|
||
await penpotOidcLogin(page, env.adminUsername, env.adminPassword);
|
||
|
||
// Open Drafts and create a new file; Penpot navigates into the workspace editor.
|
||
await page.getByText("Drafts", { exact: true }).first().click();
|
||
const newFile = page.getByText(/\+\s*New File/i).first();
|
||
await expect(newFile, "Expected a create-file control in Drafts").toBeVisible({ timeout: resolveTimeout(60_000) });
|
||
await newFile.click({ timeout: resolveTimeout(30_000) });
|
||
await expect.poll(() => page.url(), { timeout: resolveTimeout(90_000), message: "expected to enter the Penpot workspace editor" })
|
||
.toContain("/workspace");
|
||
|
||
const validPng = shared.validImagePng();
|
||
const fileInput = page.locator("#image-upload");
|
||
await fileInput.waitFor({ state: "attached", timeout: resolveTimeout(60_000) });
|
||
await fileInput.setInputFiles({ name: "pw-asset.png", mimeType: "image/png", buffer: validPng });
|
||
|
||
await expect(
|
||
page.getByRole("tabpanel", { name: "Layers" }).getByText(/pw-asset/i).first(),
|
||
).toBeVisible({ timeout: resolveTimeout(60_000) });
|
||
});
|
||
|
||
// Persona scenarios. Bodies live in the shared persona helpers. Penpot's login
|
||
// is an in-app "OpenID" provider entry (clickable text, not a `login`/`sign-in`
|
||
// link) and its logout sits behind an SPA user menu the generic persona helper
|
||
// does not recognise, so the authenticated biber / administrator persona
|
||
// journeys are declared blocked (PERSONA_*_BLOCKED, mirrors web-app-taiga).
|
||
// Their real auth paths are exercised by the dedicated login companions above.
|
||
|
||
test("guest: public-landing → auth chain → never authenticated", async ({ page }) => {
|
||
await runGuestFlow(page);
|
||
});
|
||
|
||
test("biber: app → role interaction → universal logout", async ({ page }) => {
|
||
await runBiberFlow(page);
|
||
});
|
||
|
||
test("administrator: app → admin interaction → universal logout", async ({ page }) => {
|
||
await runAdminFlow(page);
|
||
});
|