117 lines
7.0 KiB
JavaScript
117 lines
7.0 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "vitest";
|
|
import { missingReadinessEnv } from "./run-playwright-a11y.mjs";
|
|
|
|
const repoRoot = process.cwd();
|
|
const readinessWorkflowEnvKeys = missingReadinessEnv({ env: {} });
|
|
|
|
function readWorkflow(relativePath) {
|
|
return fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
|
|
}
|
|
|
|
describe("a11y workflow install cache", () => {
|
|
for (const workflow of [".github/workflows/a11y.yml", ".gitea/workflows/a11y.yml"]) {
|
|
it(`${workflow} uses the pnpm store cache before installing`, () => {
|
|
const source = readWorkflow(workflow);
|
|
const configureStoreIndex = source.indexOf("pnpm config set store-dir .pnpm-store");
|
|
const cacheIndex = source.indexOf("uses: actions/cache@v4");
|
|
const installIndex = source.indexOf("pnpm install --frozen-lockfile --prefer-offline");
|
|
|
|
assert.ok(configureStoreIndex > 0, `${workflow} must configure .pnpm-store`);
|
|
assert.ok(cacheIndex > configureStoreIndex, `${workflow} must cache .pnpm-store after configuring it`);
|
|
assert.ok(source.includes("path: .pnpm-store"), `${workflow} must cache the pnpm store path`);
|
|
assert.ok(source.includes("key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}"), `${workflow} must key cache by pnpm lockfile`);
|
|
assert.ok(installIndex > cacheIndex, `${workflow} must install with --prefer-offline after cache restore`);
|
|
});
|
|
}
|
|
|
|
it(".github/workflows/a11y.yml cancels superseded PR runs without path-filtering required checks", () => {
|
|
const source = readWorkflow(".github/workflows/a11y.yml");
|
|
|
|
assert.match(source, /^concurrency:\s*$/m, "GitHub a11y workflow must cancel superseded runs");
|
|
assert.ok(
|
|
source.includes("group: a11y-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"),
|
|
"GitHub a11y workflow must scope concurrency by workflow and PR/ref",
|
|
);
|
|
assert.match(source, /^\s+cancel-in-progress:\s*true\s*$/m, "GitHub a11y workflow must cancel in-progress duplicates");
|
|
assert.doesNotMatch(source, /^\s+paths:\s*$/m, "GitHub required a11y checks must not be skipped by path filters");
|
|
assert.doesNotMatch(source, /^\s+paths-ignore:\s*$/m, "GitHub required a11y checks must not be skipped by path-ignore filters");
|
|
});
|
|
});
|
|
|
|
describe("e2e readiness workflows", () => {
|
|
for (const workflow of [".github/workflows/e2e-readiness.yml", ".gitea/workflows/e2e-readiness.yml"]) {
|
|
it(`${workflow} runs the browser readiness suite in fail-closed mode`, () => {
|
|
const source = readWorkflow(workflow);
|
|
|
|
assert.match(source, /^ workflow_dispatch:\s*$/m, `${workflow} must support manual operator dispatch`);
|
|
assert.match(source, /^ schedule:\s*$/m, `${workflow} must run on a schedule`);
|
|
assert.ok(source.includes("pnpm config set store-dir .pnpm-store"), `${workflow} must configure the pnpm store`);
|
|
assert.ok(source.includes("path: .cache/ms-playwright"), `${workflow} must cache Playwright browsers`);
|
|
assert.ok(source.includes("pnpm install --frozen-lockfile --prefer-offline"), `${workflow} must install deterministically`);
|
|
assert.ok(source.includes("pnpm exec playwright install --with-deps chromium webkit"), `${workflow} must install every browser used by E2E_BROWSER_MATRIX=1`);
|
|
assert.ok(source.includes("pnpm run verify:e2e-a11y"), `${workflow} must run the strict Playwright wrapper`);
|
|
assert.ok(source.includes("plugin_lifecycle_mutation:"), `${workflow} must expose the plugin lifecycle mutation manual dispatch opt-in`);
|
|
assert.ok(source.includes("plugin_settings_write:"), `${workflow} must expose the plugin settings write manual dispatch opt-in`);
|
|
assert.ok(source.includes("workflow_outbox_drain:"), `${workflow} must expose the workflow outbox-drain manual dispatch opt-in`);
|
|
assert.ok(source.includes("workflow_webhook_run:"), `${workflow} must expose the real workflow webhook manual dispatch opt-in`);
|
|
assert.ok(source.includes("workflow_webhook_target_url:"), `${workflow} must expose the real workflow webhook target URL input`);
|
|
assert.ok(source.includes("subscription_success:"), `${workflow} must expose the Stripe checkout success manual dispatch opt-in`);
|
|
assert.ok(source.includes("subscription_success_path:"), `${workflow} must expose the Stripe checkout success return path input`);
|
|
assert.ok(source.includes("Apply manual operator E2E opt-ins"), `${workflow} must apply manual operator opt-ins before running Playwright`);
|
|
|
|
for (const requiredEnv of [
|
|
'REQUIRE_E2E_A11Y: "1"',
|
|
'REQUIRE_AUTH_E2E: "1"',
|
|
'REQUIRE_E2E_READINESS: "1"',
|
|
'E2E_A11Y_STRICT: "1"',
|
|
'E2E_BROWSER_MATRIX: "1"',
|
|
'E2E_ALLOW_WORKFLOW_RUN: "1"',
|
|
"E2E_TENANT_EMAIL: ${{ secrets.E2E_TENANT_EMAIL }}",
|
|
"E2E_TENANT_PASSWORD: ${{ secrets.E2E_TENANT_PASSWORD }}",
|
|
"E2E_PLUGIN_READONLY_EMAIL: ${{ secrets.E2E_PLUGIN_READONLY_EMAIL }}",
|
|
"E2E_PLUGIN_READONLY_PASSWORD: ${{ secrets.E2E_PLUGIN_READONLY_PASSWORD }}",
|
|
"E2E_TENANT_API_DOCS_PATH: /e2e-readiness/settings/api/docs",
|
|
"E2E_TENANT_API_LOGS_PATH: /e2e-readiness/settings/api/logs",
|
|
"E2E_PLUGIN_SETTINGS_PATH: /e2e-readiness/settings/plugins",
|
|
"E2E_WORKFLOW_ENGINE_PATH: /e2e-readiness/workflow-engine/workflows",
|
|
"E2E_WORKFLOW_RUN_API_PATH: /e2e-readiness/workflow-engine/workflows/wf_123/run/api",
|
|
"E2E_WORKFLOW_WEBHOOK_BUILDER_PATH: /e2e-readiness/workflow-engine/workflows/wf_webhook_123",
|
|
"E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH: /e2e-readiness/workflow-engine/workflows/wf_webhook_123/run/manual",
|
|
"PLUGIN_BINARY_BASE_PATH: /tmp/e2e-plugins",
|
|
]) {
|
|
assert.ok(source.includes(requiredEnv), `${workflow} missing ${requiredEnv}`);
|
|
}
|
|
|
|
for (const optInExport of [
|
|
"E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION=1",
|
|
"E2E_ALLOW_PLUGIN_SETTINGS_WRITE=1",
|
|
"E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1",
|
|
"E2E_ALLOW_WORKFLOW_WEBHOOK_RUN=1",
|
|
"E2E_WORKFLOW_WEBHOOK_TARGET_URL=%s",
|
|
"E2E_REQUIRE_SUBSCRIPTION_SUCCESS=1",
|
|
"E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH=%s",
|
|
]) {
|
|
assert.ok(source.includes(optInExport), `${workflow} must export ${optInExport} for manual dispatch`);
|
|
}
|
|
|
|
for (const requiredKey of readinessWorkflowEnvKeys) {
|
|
assert.match(source, new RegExp(`^\\s+${requiredKey}:\\s+`, "m"), `${workflow} missing strict readiness env key ${requiredKey}`);
|
|
}
|
|
|
|
assert.doesNotMatch(source, /SKIP_E2E_A11Y:\s*"?1"?/, `${workflow} must not silently skip the browser suite`);
|
|
assert.ok(source.includes("path: test-results/"), `${workflow} must upload Playwright traces`);
|
|
});
|
|
}
|
|
|
|
it("the Gitea readiness workflow uses the self-hosted runner and host resolver", () => {
|
|
const source = readWorkflow(".gitea/workflows/e2e-readiness.yml");
|
|
|
|
assert.ok(source.includes("runs-on: toolctrl-ci"));
|
|
assert.ok(source.includes("TOOLCTRL_GITEA_HOST_IP"));
|
|
assert.ok(source.includes("git.tool-ctrl.de"));
|
|
});
|
|
});
|