diff --git a/web/README.md b/web/README.md index dd8322cd8372..686fdb39a1c7 100644 --- a/web/README.md +++ b/web/README.md @@ -5,24 +5,41 @@ for awhile, but at least let's get started. # Setup -Install dependencies from the repo root with `make node-install` (or `make install` for the full -Python + web + docs bootstrap). This wraps `npm ci` and explicitly rebuilds the small set of -packages whose install scripts are required for the toolchain to function — currently `esbuild`, -`chromedriver`, `tree-sitter`, and `tree-sitter-json`. +The package manager is **pnpm**, pinned by the `packageManager` field in `package.json`. We +currently require Node.js 24 or newer. -The repo-root `.npmrc` sets `ignore-scripts=true` to neutralize the dominant npm supply-chain -attack vector. As a side effect, running `npm ci` directly in this directory will install -dependencies but skip those rebuilds, leaving `esbuild` and `chromedriver` in a non-functional -state. If you bypass `make`, run the rebuild step yourself: +Install from the project root (not the `./web` folder!): ```bash -npm rebuild --ignore-scripts=false --foreground-scripts \ - esbuild chromedriver tree-sitter tree-sitter-json +make web-install # this package only +make install # node + web + the Python core ``` -New dependencies that ship install scripts must be audited and added to `TRUSTED_INSTALL_SCRIPTS` -in the repo-root `Makefile`. Each entry is arbitrary code that runs at install time, so the list -is intentionally small. +`web/` is a pnpm workspace root — it has its own `pnpm-workspace.yaml` and `pnpm-lock.yaml`, +independent of the ones at the repository root. `make node-install` installs the _root_ workspace +and does **not** include the `./web` folder (this folder); use `make web-install` (or `make +install`) for web work. Documentation dependencies are also separate; build them with `make +docs-install`. + +Running `pnpm install` in this folder will also work. The `make` targets add two things: they pass +`--frozen-lockfile`, and `make node-install` first runs `scripts/node/lint-runtime.mjs` to check +that the local Node or pnpm matches the requirements in `package.json`. (The project root `.npmrc` +also sets `engine-strict=true` and `save-exact=true` to make sure `pnpm add` writes exact versions.) + +## Install scripts + +pnpm blocks package install scripts by default, since they're the vector for most supply-chain +attacks. Packages can be allowed to run install scripts only by explicitly being included in one of +two fields in `pnpm-workspace.yaml`: + +- **`onlyBuiltDependencies`** — the allowlist. Currently `chromedriver`, `esbuild`, `tree-sitter`, + `tree-sitter-json`, and `@tree-sitter-grammars/tree-sitter-yaml`, all of which need to compile or + fetch a binary to function. +- **`allowBuilds`** — pnpm 11's explicit approval map. `true` mirrors the allowlist; `false` records + a deliberate decline, which suppresses the "pending approval" prompt without running anything. + +A new dependency that ships an install script must be audited and added to **both** fields. Think +before you add anything to the lists. # The Theory of the authentik UI diff --git a/web/test/browser/1000-snapshots.test.ts b/web/test/browser/1000-snapshots.test.ts new file mode 100644 index 000000000000..f610d33c5224 --- /dev/null +++ b/web/test/browser/1000-snapshots.test.ts @@ -0,0 +1,78 @@ +import { expect, test } from "#e2e"; + +const COLOR_SCHEMES = ["light", "dark"] as const; + +for (const scheme of COLOR_SCHEMES) { + test.describe(`Appearance - ${scheme}`, () => { + // locking the viewport size to ensure consistent snapshots + test.use({ colorScheme: scheme, viewport: { width: 1280, height: 800 } }); + + test("Dashboard renders", async ({ session, page }) => { + await test.step("Authenticate", async () => + await session.login({ to: "/if/admin/#/administration/overview" })); + + await expect( + page.locator("html"), + `Document reports the ${scheme} color scheme`, + ).toHaveAttribute("data-theme", scheme, { timeout: 10_000 }); + + await page.waitForTimeout(1000); + + await test.step("Compare the screenshot", async () => { + await expect(page, `${scheme} matches the baseline`).toHaveScreenshot( + `overview-${scheme}.webp`, + // Fairly high, but needed to handle how dates and version numbers + // can change. + { + animations: "disabled", + caret: "hide", + mask: [page.locator("ak-version")], + maxDiffPixelRatio: 0.05, + }, + ); + }); + }); + + test("Table renders", async ({ session, page }) => { + await test.step("Authenticate", async () => + await session.login({ to: "/if/admin/#/events/rules" })); + + await page.getByRole("button", { name: "Expand row" }).first().click(); + + await page.waitForTimeout(1000); + + await test.step("Compare the screenshot", async () => { + await expect(page, `${scheme} matches the baseline`).toHaveScreenshot( + `notification-table-${scheme}.webp`, + // Tighter, since there are no dates, versions, etc on the page. + { + animations: "disabled", + caret: "hide", + maxDiffPixelRatio: 0.02, + }, + ); + }); + }); + + test("Form renders", async ({ session, page }) => { + await test.step("Authenticate", async () => + await session.login({ to: "/if/admin/#/flow/stages/prompts" })); + + await page.getByRole("button", { name: "New Prompt" }).click(); + + await page.waitForTimeout(1000); + + await test.step("Compare the screenshot", async () => { + await expect(page, `${scheme} matches the baseline`).toHaveScreenshot( + `new-prompt-form-${scheme}.webp`, + // Tighter, since there are no dates, versions, etc on the page. + { + animations: "disabled", + caret: "hide", + maxDiffPixelRatio: 0.02, + }, + ); + }); + }); + }); +} diff --git a/web/test/browser/1000-snapshots.test.ts-snapshots/new-prompt-form-dark-chromium-darwin.webp b/web/test/browser/1000-snapshots.test.ts-snapshots/new-prompt-form-dark-chromium-darwin.webp new file mode 100644 index 000000000000..9821323c9b87 Binary files /dev/null and b/web/test/browser/1000-snapshots.test.ts-snapshots/new-prompt-form-dark-chromium-darwin.webp differ diff --git a/web/test/browser/1000-snapshots.test.ts-snapshots/new-prompt-form-light-chromium-darwin.webp b/web/test/browser/1000-snapshots.test.ts-snapshots/new-prompt-form-light-chromium-darwin.webp new file mode 100644 index 000000000000..0651f43dcad4 Binary files /dev/null and b/web/test/browser/1000-snapshots.test.ts-snapshots/new-prompt-form-light-chromium-darwin.webp differ diff --git a/web/test/browser/1000-snapshots.test.ts-snapshots/notification-table-dark-chromium-darwin.webp b/web/test/browser/1000-snapshots.test.ts-snapshots/notification-table-dark-chromium-darwin.webp new file mode 100644 index 000000000000..545fadf161d9 Binary files /dev/null and b/web/test/browser/1000-snapshots.test.ts-snapshots/notification-table-dark-chromium-darwin.webp differ diff --git a/web/test/browser/1000-snapshots.test.ts-snapshots/notification-table-light-chromium-darwin.webp b/web/test/browser/1000-snapshots.test.ts-snapshots/notification-table-light-chromium-darwin.webp new file mode 100644 index 000000000000..2064167a1f68 Binary files /dev/null and b/web/test/browser/1000-snapshots.test.ts-snapshots/notification-table-light-chromium-darwin.webp differ diff --git a/web/test/browser/1000-snapshots.test.ts-snapshots/overview-dark-chromium-darwin.webp b/web/test/browser/1000-snapshots.test.ts-snapshots/overview-dark-chromium-darwin.webp new file mode 100644 index 000000000000..f28391bebb15 Binary files /dev/null and b/web/test/browser/1000-snapshots.test.ts-snapshots/overview-dark-chromium-darwin.webp differ diff --git a/web/test/browser/1000-snapshots.test.ts-snapshots/overview-light-chromium-darwin.webp b/web/test/browser/1000-snapshots.test.ts-snapshots/overview-light-chromium-darwin.webp new file mode 100644 index 000000000000..d6af28e8a22f Binary files /dev/null and b/web/test/browser/1000-snapshots.test.ts-snapshots/overview-light-chromium-darwin.webp differ