Files
HiveGrid-Wireframes/hivegrid-cloud/scripts/verify-a11y-workflows.test.mjs
T

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"));
});
});