Automation, Devkit & E2E
A platform that asks teams to self-serve only works if it gives them fast feedback on whether they did it right. None of the mechanisms below gate a developer’s work — they give a “here’s what’s wrong, here’s the exact command to fix it” surface instead.
For why Playwright (and which AI-assisted tools were considered), see ADR 0007 — Adopt Playwright for E2E.
What’s built today
Section titled “What’s built today”whisker doctor
Section titled “whisker doctor”Run on demand or invoked by whisker dev. Implemented as pure functions
that return { id, status: 'ok'|'warn'|'fail', detail, remediation? }, so
the same engine can eventually drive both the CLI and a future live UI. The
check count varies per project (shared-package placement and port checks
scale with config), but covers:
| Check | Fails because |
|---|---|
| Node version | Installed Node < required floor (24+) |
| BFF URLs (declared + effective, test/prod) | Empty config, or surfaces a developer’s VITE_BFF_URL override |
Host path / host package.json |
whisker-host not resolvable (via WHISKER_HOST_PATH or a sibling directory), or the resolved directory has no package.json |
| Host script contract | whisker:prep / whisker:rebuild-loader / whisker:restore missing — the host is too old for the kit’s orchestration |
| Shared-package placement | A host-shared package (per module-federation.config.ts#shared) found in dependencies instead of devDependencies, or missing entirely |
| Port availability | A configured port is in use — prints the lsof kill command |
| BFF reachability | HEAD request fails — often “you need VPN” |
Doctor is iframe-aware: for iframe consumers, the host-script-contract checks are skipped. The worst status across all checks sets the exit code so CI can gate on it if a team chooses.
Husky hooks
Section titled “Husky hooks”Every scaffold wires pre-commit and post-commit hooks to the kit:
.husky/pre-commit → pnpm exec whisker pre-commit.husky/post-commit → pnpm exec whisker post-commitpre-commit regenerates config/module-manifest.json from
src/moduleManifest.ts and validates it — a schema violation fails the
commit before it ever reaches CI. post-commit updates provenance fields.
This makes config/module-manifest.json an artifact that’s always correct
relative to src/moduleManifest.ts on any commit, on any branch.
Nightly Playwright E2E
Section titled “Nightly Playwright E2E”The runner is fully built and live at whisker-host/Jenkinsfile.nightly,
running H 2 * * *.
Host-level suites (shipped, in whisker-host/tests/e2e/): auth,
dashboard, nav, module-routes — all backed by a shared authedPage
fixture (tests/e2e/fixtures/auth.ts) that drives the BERM login flow and
yields an authenticated page. Credentials come from Cerberus in CI, from
.env.local locally.
Pipeline phases:
1. Fetch credentials from Cerberus (one shared dummy account)2. Host-level E2E: auth, dashboard, nav, module-routes specs3. Per-module E2E: snapshot the live registry once, git clone each module's repo (from its manifest's `repository` field), run its own pnpm test:e2e against the running host4. Merge + publish: playwright merge-reports → S3 nightly/latest + nightly/{date}5. Notify: #platform-e2e aggregate + per-module Slack channel on failureThe phrase that matters: the runner never needs to change when a module is added, removed, or rewrites its tests — it iterates over whatever’s registered at run time, and each module’s manifest tells it where to clone from and which Slack channel to notify.
Iframe consumers can be targeted deterministically in Playwright via the
iframe’s title attribute (set to the module name by IframeRenderer):
const frame = page.frameLocator('iframe[title="sim-web-ui"]');Reports land at:
s3://whisker-e2e-reports/nightly/latest/{report.json, index.html}s3://whisker-e2e-reports/nightly/{date}/...same shape...Vitest as the unified test framework
Section titled “Vitest as the unified test framework”Vitest is the unit-test framework for both modules and services scaffolded
from create-whisker. The historical Japa setup in sim-bff stays as-is
until the platform concerns are carved into whisker-bff. One framework
keeps developer mobility between modules and services cheap.
What is not automated
Section titled “What is not automated”These genuinely require a human and live in docs/manual-setup-required.md:
developer-workstation setup (Playwright browsers, @playwright/mcp,
gimme-creds), per-project scaffold-time items (NPE account IDs, OSCAR
scopes, initial Jenkins approval), and platform infrastructure (the dummy
test Okta account, Jenkins org credentials, the E2E reports bucket).
Planned / proposed
Section titled “Planned / proposed”Devkit panel (port 3009)
Section titled “Devkit panel (port 3009)”A read-only, non-blocking compliance status surface, opened next to
whisker-host during whisker dev. Design is settled; no implementation
exists yet — no server, no UI, no compliance-check module in the kit.
The plan: an Express server on port 3009, started by whisker dev once the
main servers are healthy, pushing check results over SSE. Planned sections:
services status (PID/uptime/port for host, module dev server, devkit
itself), a compliance list with remediation links, the nightly E2E report
(this module’s row + platform aggregate, proxied to sidestep browser
CORS), the manual-setup checklist, and the currently-pinned contract
version.
Eight planned compliance checks (pure functions, meant to be shared by
whisker doctor and the panel — not yet coded):
1. E2E tests exist (tests/e2e/*.spec.ts present)2. playwright.config.ts present3. module-manifest.json is current (hash of src/moduleManifest vs generated file)4. whisker-contract version is up to date5. whisker.config.json passes Zod schema6. Node version satisfies engines.node7. @playwright/mcp present in ~/.cursor/mcp.json (workstation check)8. NPE account IDs are not still "TODO" placeholder stringsAll non-blocking by design — whisker.config.json#compliance may later let
a team opt a specific check into CI-blocking, but the default stays
advisory.
whiskerTest fixture + ./e2e kit export
Section titled “whiskerTest fixture + ./e2e kit export”Planned, not shipped: @nike/whisker-module-kit doesn’t yet export an
./e2e subpath. The intended shape:
import { whiskerTest as test, expect } from '@nike/whisker-module-kit/e2e';
test('inventory table loads', async ({ authedPage }) => { await authedPage.goto('/inventory'); await expect(authedPage.getByRole('table')).toBeVisible();});authedPage would read WHISKER_HOST_URL, log in with
WHISKER_TEST_USER/WHISKER_TEST_PASS, wait for the host shell, and yield
the page — mirroring the host’s own fixture so module teams don’t write
auth boilerplate. Also planned: whisker test:e2e (boots the dev stack,
runs Playwright, tears down) and whisker add-e2e (scaffolds a starter
suite into an existing repo).
See Risks & improvements for priority on both items.
