Files
computer-playbook/roles/web-app-penpot/files/playwright/playwright.spec.js

96 lines
4.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);
});