Files
HiveGrid-Wireframes/hivegrid-cloud/scripts/run-playwright-a11y.test.mjs
T

994 lines
41 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "vitest";
import {
hasRequiredAuthEnv,
isUrlReachable,
missingReadinessEnv,
missingReadinessModeEnv,
missingReadinessMutatingOptInEnv,
missingNoTenantAccountCreateEnv,
missingSubscriptionSuccessReadinessEnv,
missingWorkflowOutboxDrainReadinessEnv,
missingWorkflowWebhookRunReadinessEnv,
resolveTargetUrl,
runPlaywrightA11y,
} from "./run-playwright-a11y.mjs";
function recordingSpawn(handlers) {
const calls = [];
function spawn(command, args, options) {
calls.push({ command, args, options });
const handler = handlers[command];
if (!handler) {
return { status: 1, stdout: "", stderr: "", error: new Error(`unexpected spawn for ${command}`) };
}
return handler({ args, options });
}
spawn.calls = calls;
return spawn;
}
function captureLines() {
const lines = [];
function write(line) {
lines.push(line);
}
write.lines = lines;
return write;
}
function completeReadinessEnv(overrides = {}) {
return {
E2E_BASE_URL: "https://staging.example.test",
E2E_TENANT_EMAIL: "user@example.test",
E2E_TENANT_PASSWORD: "secret",
E2E_NO_TENANT_EMAIL: "no-tenant@example.test",
E2E_NO_TENANT_PASSWORD: "no-tenant-secret",
E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION: "0",
E2E_TENANT_DASHBOARD_PATH: "/app/acme/dashboard",
E2E_TENANT_SETTINGS_ACCOUNT_PATH: "/app/acme/settings/account",
E2E_TENANT_SETTINGS_PROFILE_PATH: "/app/acme/settings/profile",
E2E_TENANT_SUBSCRIPTION_PATH: "/app/acme/settings/subscription",
E2E_TENANT_MEMBERS_PATH: "/app/acme/settings/members",
E2E_TENANT_ROLES_AND_PERMISSIONS_PATH: "/app/acme/settings/roles-and-permissions",
E2E_TENANT_NOTIFICATIONS_PATH: "/app/acme/settings/notifications",
E2E_TENANT_LOGS_PATH: "/app/acme/settings/logs",
E2E_TENANT_LOG_EVENTS_PATH: "/app/acme/settings/logs/events",
E2E_TENANT_AUDIT_EXPORT_PATH: "/app/acme/settings/audit-export",
E2E_TENANT_PRIVACY_PATH: "/app/acme/settings/privacy",
E2E_TENANT_CHATBOT_PATH: "/app/acme/settings/chatbot",
E2E_TENANT_AI_USAGE_PATH: "/app/acme/settings/ai-usage",
E2E_TENANT_API_PATH: "/app/acme/settings/api",
E2E_TENANT_API_KEYS_PATH: "/app/acme/settings/api/keys",
E2E_TENANT_API_DOCS_PATH: "/app/acme/settings/api/docs",
E2E_TENANT_API_LOGS_PATH: "/app/acme/settings/api/logs",
E2E_TENANT_POLICIES_PATH: "/app/acme/settings/policies",
E2E_TENANT_PORTAL_ANALYTICS_PATH: "/app/acme/portals/readiness/analytics",
E2E_TENANT_PORTAL_SETTINGS_PATH: "/app/acme/portals/readiness/settings",
E2E_ADMIN_EMAIL_SEQUENCES_PATH: "/admin/email-sequences",
E2E_PLUGIN_SETTINGS_PATH: "/app/acme/settings/plugins/plugin-1",
E2E_PLUGIN_READONLY_EMAIL: "plugin-readonly@example.test",
E2E_PLUGIN_READONLY_PASSWORD: "readonly-secret",
E2E_WORKFLOW_ENGINE_PATH: "/app/acme/workflows",
E2E_WORKFLOW_BUILDER_PATH: "/app/acme/workflows/wf-1/builder",
E2E_WORKFLOW_RUN_API_PATH: "/app/acme/workflows/wf-1/run/api",
E2E_WORKFLOW_RUN_MANUAL_PATH: "/app/acme/workflows/wf-1/run",
E2E_WORKFLOW_WEBHOOK_BUILDER_PATH: "/app/acme/workflows/wf-webhook/builder",
E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH: "/app/acme/workflows/wf-webhook/run",
E2E_WORKFLOW_WAITING_BUILDER_PATH: "/app/acme/workflows/wf-waiting/builder",
E2E_WORKFLOW_WAITING_RUN_MANUAL_PATH: "/app/acme/workflows/wf-waiting/run",
E2E_WORKFLOW_WAITING_RUN_API_PATH: "/app/acme/workflows/wf-waiting/run/api",
E2E_WORKFLOW_WAITING_EXECUTIONS_PATH: "/app/acme/workflows/wf-waiting/executions",
E2E_WORKFLOW_WAITING_EXECUTION_ID: "wfe-waiting-1",
PLUGIN_BINARY_BASE_PATH: "/tmp/e2e-plugins",
E2E_A11Y_STRICT: "1",
E2E_BROWSER_MATRIX: "1",
E2E_ALLOW_WORKFLOW_RUN: "1",
E2E_ALLOW_REGISTRATION_CREATE: "1",
E2E_ALLOW_ACCOUNT_CREATE: "1",
E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE: "1",
E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION: "1",
E2E_ALLOW_PLUGIN_SETTINGS_WRITE: "1",
REQUIRE_E2E_READINESS: "1",
...overrides,
};
}
describe("run-playwright-a11y", () => {
it("uses E2E_BASE_URL when provided", () => {
assert.equal(resolveTargetUrl({ env: { E2E_BASE_URL: "https://staging.example.test" } }), "https://staging.example.test");
});
it("falls back to localhost when E2E_BASE_URL is missing", () => {
assert.equal(resolveTargetUrl({ env: {} }), "http://localhost:3000");
});
it("falls back to localhost when E2E_BASE_URL is blank", () => {
assert.equal(resolveTargetUrl({ env: { E2E_BASE_URL: "" } }), "http://localhost:3000");
assert.equal(resolveTargetUrl({ env: { E2E_BASE_URL: " " } }), "http://localhost:3000");
});
it("trims E2E_BASE_URL before probing the target", () => {
assert.equal(resolveTargetUrl({ env: { E2E_BASE_URL: " https://staging.example.test " } }), "https://staging.example.test");
});
it("SKIPs with exit 0 when SKIP_E2E_A11Y=1 is set", () => {
const spawn = recordingSpawn({});
const stdout = captureLines();
const outcome = runPlaywrightA11y({ env: { SKIP_E2E_A11Y: "1" }, spawn, stdout });
assert.deepEqual(outcome, { status: "skip", reason: "explicit-skip", exitCode: 0 });
assert.deepEqual(stdout.lines, ["SKIP_E2E_A11Y=1 set — skipping."]);
assert.equal(spawn.calls.length, 0);
});
it("fails instead of honoring SKIP_E2E_A11Y in required readiness lanes", () => {
const spawn = recordingSpawn({});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: {
SKIP_E2E_A11Y: "1",
REQUIRE_E2E_A11Y: "1",
REQUIRE_AUTH_E2E: "1",
REQUIRE_E2E_READINESS: "1",
E2E_REQUIRE_SUBSCRIPTION_SUCCESS: "1",
},
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "explicit-skip-in-required-lane", exitCode: 1 });
assert.deepEqual(stdout.lines, [
"FAIL: SKIP_E2E_A11Y=1 cannot be used with required E2E lanes: REQUIRE_E2E_A11Y=1, REQUIRE_AUTH_E2E=1, REQUIRE_E2E_READINESS=1, E2E_REQUIRE_SUBSCRIPTION_SUCCESS=1.",
]);
assert.equal(spawn.calls.length, 0);
});
it("SKIPs with exit 0 when the Playwright runtime is missing", () => {
const spawn = recordingSpawn({
npx: () => ({ status: 1, stdout: "", stderr: "", error: new Error("ENOENT") }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({ env: {}, spawn, stdout });
assert.deepEqual(outcome, { status: "skip", reason: "runtime-missing", exitCode: 0 });
assert.match(stdout.lines[0], /^SKIP: Playwright runtime not installed/);
assert.equal(spawn.calls.filter((call) => call.command === "curl").length, 0);
});
it("fails instead of skipping missing runtime when REQUIRE_E2E_A11Y=1", () => {
const spawn = recordingSpawn({
npx: () => ({ status: 1, stdout: "", stderr: "", error: new Error("ENOENT") }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({ env: { REQUIRE_E2E_A11Y: "1" }, spawn, stdout });
assert.deepEqual(outcome, { status: "fail", reason: "runtime-missing", exitCode: 1 });
assert.deepEqual(stdout.lines, ["FAIL: Playwright runtime not installed while REQUIRE_E2E_A11Y=1."]);
});
it("SKIPs with exit 0 when the target URL is unreachable", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
assert.deepEqual(args, ["--no-install", "playwright", "--version"]);
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
},
curl: () => ({ status: 7, stdout: "", stderr: "Connection refused" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: { E2E_BASE_URL: "http://offline.example.test" },
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "skip", reason: "target-unreachable", exitCode: 0 });
assert.ok(stdout.lines.some((line) => line === "SKIP: Target URL http://offline.example.test not reachable."));
assert.equal(spawn.calls.filter((call) => call.command === "npx" && call.args[0] === "playwright").length, 0);
});
it("fails instead of skipping unreachable targets when REQUIRE_E2E_A11Y=1", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
assert.deepEqual(args, ["--no-install", "playwright", "--version"]);
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
},
curl: () => ({ status: 7, stdout: "", stderr: "Connection refused" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: { E2E_BASE_URL: "http://offline.example.test", REQUIRE_E2E_A11Y: "1" },
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "target-unreachable", exitCode: 1 });
assert.deepEqual(stdout.lines, ["FAIL: Target URL http://offline.example.test not reachable while REQUIRE_E2E_A11Y=1."]);
});
it("fails before running Playwright when authenticated E2E credentials are required but missing", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 0, stdout: "", stderr: "" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: { E2E_BASE_URL: "https://staging.example.test", REQUIRE_AUTH_E2E: "1" },
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "auth-env-missing", exitCode: 1 });
assert.deepEqual(stdout.lines, ["FAIL: E2E_TENANT_EMAIL / E2E_TENANT_PASSWORD not set while REQUIRE_AUTH_E2E=1."]);
assert.equal(spawn.calls.filter((call) => call.command === "npx" && call.args[0] === "playwright").length, 0);
});
it("detects authenticated E2E credentials", () => {
assert.equal(hasRequiredAuthEnv({ env: {} }), false);
assert.equal(hasRequiredAuthEnv({ env: { E2E_TENANT_EMAIL: "user@example.test" } }), false);
assert.equal(hasRequiredAuthEnv({ env: { E2E_TENANT_EMAIL: "user@example.test", E2E_TENANT_PASSWORD: "secret" } }), true);
});
it("fails before running Playwright when readiness E2E inputs are incomplete", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 0, stdout: "", stderr: "" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: {
E2E_BASE_URL: "https://staging.example.test",
E2E_TENANT_EMAIL: "user@example.test",
E2E_TENANT_PASSWORD: "secret",
REQUIRE_E2E_READINESS: "1",
},
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "readiness-env-missing", exitCode: 1 });
assert.match(stdout.lines[0], /E2E readiness env missing:/);
assert.match(stdout.lines[0], /E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION/);
assert.match(stdout.lines[0], /E2E_TENANT_DASHBOARD_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_SETTINGS_ACCOUNT_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_SETTINGS_PROFILE_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_SUBSCRIPTION_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_MEMBERS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_ROLES_AND_PERMISSIONS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_NOTIFICATIONS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_LOGS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_LOG_EVENTS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_AUDIT_EXPORT_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_PRIVACY_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_CHATBOT_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_AI_USAGE_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_API_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_API_KEYS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_API_DOCS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_API_LOGS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_POLICIES_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_PORTAL_ANALYTICS_PATH/);
assert.match(stdout.lines[0], /E2E_TENANT_PORTAL_SETTINGS_PATH/);
assert.match(stdout.lines[0], /E2E_PLUGIN_SETTINGS_PATH/);
assert.match(stdout.lines[0], /E2E_PLUGIN_READONLY_EMAIL/);
assert.match(stdout.lines[0], /E2E_PLUGIN_READONLY_PASSWORD/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_ENGINE_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_BUILDER_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_RUN_API_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_RUN_MANUAL_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_WEBHOOK_BUILDER_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_WAITING_BUILDER_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_WAITING_RUN_MANUAL_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_WAITING_RUN_API_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_WAITING_EXECUTIONS_PATH/);
assert.match(stdout.lines[0], /E2E_WORKFLOW_WAITING_EXECUTION_ID/);
assert.match(stdout.lines[0], /PLUGIN_BINARY_BASE_PATH/);
assert.doesNotMatch(stdout.lines[0], /E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH/);
assert.doesNotMatch(stdout.lines[0], /E2E_ALLOW_WORKFLOW_RUN=1/);
assert.doesNotMatch(stdout.lines[0], /E2E_ALLOW_REGISTRATION_CREATE=1/);
assert.equal(spawn.calls.filter((call) => call.command === "npx" && call.args[0] === "playwright").length, 0);
});
it("accepts the complete readiness E2E env contract", () => {
assert.deepEqual(
missingReadinessEnv({
env: {
E2E_TENANT_EMAIL: "user@example.test",
E2E_TENANT_PASSWORD: "secret",
E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION: "0",
E2E_TENANT_DASHBOARD_PATH: "/app/acme/dashboard",
E2E_TENANT_SETTINGS_ACCOUNT_PATH: "/app/acme/settings/account",
E2E_TENANT_SETTINGS_PROFILE_PATH: "/app/acme/settings/profile",
E2E_TENANT_SUBSCRIPTION_PATH: "/app/acme/settings/subscription",
E2E_TENANT_MEMBERS_PATH: "/app/acme/settings/members",
E2E_TENANT_ROLES_AND_PERMISSIONS_PATH: "/app/acme/settings/roles-and-permissions",
E2E_TENANT_NOTIFICATIONS_PATH: "/app/acme/settings/notifications",
E2E_TENANT_LOGS_PATH: "/app/acme/settings/logs",
E2E_TENANT_LOG_EVENTS_PATH: "/app/acme/settings/logs/events",
E2E_TENANT_AUDIT_EXPORT_PATH: "/app/acme/settings/audit-export",
E2E_TENANT_PRIVACY_PATH: "/app/acme/settings/privacy",
E2E_TENANT_CHATBOT_PATH: "/app/acme/settings/chatbot",
E2E_TENANT_AI_USAGE_PATH: "/app/acme/settings/ai-usage",
E2E_TENANT_API_PATH: "/app/acme/settings/api",
E2E_TENANT_API_KEYS_PATH: "/app/acme/settings/api/keys",
E2E_TENANT_API_DOCS_PATH: "/app/acme/settings/api/docs",
E2E_TENANT_API_LOGS_PATH: "/app/acme/settings/api/logs",
E2E_TENANT_POLICIES_PATH: "/app/acme/settings/policies",
E2E_TENANT_PORTAL_ANALYTICS_PATH: "/app/acme/portals/readiness/analytics",
E2E_TENANT_PORTAL_SETTINGS_PATH: "/app/acme/portals/readiness/settings",
E2E_ADMIN_EMAIL_SEQUENCES_PATH: "/admin/email-sequences",
E2E_PLUGIN_SETTINGS_PATH: "/app/acme/settings/plugins/plugin-1",
E2E_PLUGIN_READONLY_EMAIL: "plugin-readonly@example.test",
E2E_PLUGIN_READONLY_PASSWORD: "readonly-secret",
E2E_WORKFLOW_ENGINE_PATH: "/app/acme/workflows",
E2E_WORKFLOW_BUILDER_PATH: "/app/acme/workflows/wf-1/builder",
E2E_WORKFLOW_RUN_API_PATH: "/app/acme/workflows/wf-1/run/api",
E2E_WORKFLOW_RUN_MANUAL_PATH: "/app/acme/workflows/wf-1/run",
E2E_WORKFLOW_WEBHOOK_BUILDER_PATH: "/app/acme/workflows/wf-webhook/builder",
E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH: "/app/acme/workflows/wf-webhook/run",
E2E_WORKFLOW_WAITING_BUILDER_PATH: "/app/acme/workflows/wf-waiting/builder",
E2E_WORKFLOW_WAITING_RUN_MANUAL_PATH: "/app/acme/workflows/wf-waiting/run",
E2E_WORKFLOW_WAITING_RUN_API_PATH: "/app/acme/workflows/wf-waiting/run/api",
E2E_WORKFLOW_WAITING_EXECUTIONS_PATH: "/app/acme/workflows/wf-waiting/executions",
E2E_WORKFLOW_WAITING_EXECUTION_ID: "wfe-waiting-1",
PLUGIN_BINARY_BASE_PATH: "/tmp/e2e-plugins",
},
}),
[]
);
});
it("rejects whitespace-only readiness values before Playwright starts", () => {
assert.deepEqual(
missingReadinessEnv({
env: completeReadinessEnv({
E2E_TENANT_DASHBOARD_PATH: " ",
E2E_PLUGIN_SETTINGS_PATH: "\n\t",
E2E_WORKFLOW_RUN_MANUAL_PATH: "",
}),
}),
["E2E_TENANT_DASHBOARD_PATH", "E2E_PLUGIN_SETTINGS_PATH", "E2E_WORKFLOW_RUN_MANUAL_PATH"]
);
});
it("keeps subscription success optional for normal readiness lanes", () => {
const env = completeReadinessEnv();
assert.deepEqual(missingReadinessEnv({ env }), []);
assert.deepEqual(missingSubscriptionSuccessReadinessEnv({ env }), []);
});
it("requires readiness and mobile dashboard follow-through env when subscription success strict mode is enabled", () => {
assert.deepEqual(
missingSubscriptionSuccessReadinessEnv({
env: { E2E_REQUIRE_SUBSCRIPTION_SUCCESS: "1" },
}),
["E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH", "E2E_TENANT_DASHBOARD_PATH", "REQUIRE_E2E_READINESS=1", "E2E_BROWSER_MATRIX=1"]
);
assert.deepEqual(
missingSubscriptionSuccessReadinessEnv({
env: completeReadinessEnv({
E2E_REQUIRE_SUBSCRIPTION_SUCCESS: "1",
E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH: "/subscribe/acme/cs_test_123/success",
}),
}),
[]
);
});
it("does not allow subscription success strict mode outside the readiness mobile matrix", () => {
assert.deepEqual(
missingSubscriptionSuccessReadinessEnv({
env: {
E2E_REQUIRE_SUBSCRIPTION_SUCCESS: "1",
E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH: "/subscribe/acme/cs_test_123/success",
E2E_TENANT_DASHBOARD_PATH: "/app/acme/dashboard",
},
}),
["REQUIRE_E2E_READINESS=1", "E2E_BROWSER_MATRIX=1"]
);
});
it("requires strict a11y, real workflow execution, and the mobile browser matrix for readiness lanes", () => {
assert.deepEqual(
missingReadinessModeEnv({
env: {
E2E_A11Y_STRICT: "1",
},
}),
["E2E_BROWSER_MATRIX=1", "E2E_ALLOW_WORKFLOW_RUN=1"]
);
assert.deepEqual(
missingReadinessModeEnv({
env: {
E2E_A11Y_STRICT: "1",
E2E_BROWSER_MATRIX: "1",
E2E_ALLOW_WORKFLOW_RUN: "1",
},
}),
[]
);
});
it("requires core mutating opt-ins for readiness lanes", () => {
assert.deepEqual(
missingReadinessMutatingOptInEnv({
env: {
E2E_ALLOW_REGISTRATION_CREATE: "1",
E2E_ALLOW_ACCOUNT_CREATE: "1",
},
}),
[
"E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE=1",
"E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION=1",
"E2E_ALLOW_PLUGIN_SETTINGS_WRITE=1",
]
);
assert.deepEqual(
missingReadinessMutatingOptInEnv({
env: {
E2E_ALLOW_REGISTRATION_CREATE: "1",
E2E_ALLOW_ACCOUNT_CREATE: "1",
E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE: "1",
E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION: "1",
E2E_ALLOW_PLUGIN_SETTINGS_WRITE: "1",
},
}),
[]
);
});
it("requires no-tenant credentials whenever no-tenant account creation is enabled", () => {
assert.deepEqual(missingNoTenantAccountCreateEnv({ env: {} }), []);
assert.deepEqual(
missingNoTenantAccountCreateEnv({
env: { E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE: "1" },
}),
["E2E_NO_TENANT_EMAIL", "E2E_NO_TENANT_PASSWORD"]
);
assert.deepEqual(
missingNoTenantAccountCreateEnv({
env: {
E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE: "1",
E2E_NO_TENANT_EMAIL: "no-tenant@example.test",
E2E_NO_TENANT_PASSWORD: "NoTenantPass123!",
},
}),
[]
);
});
it("fails before running Playwright when no-tenant account creation is enabled without credentials", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 0, stdout: "", stderr: "" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: {
E2E_BASE_URL: "https://staging.example.test",
E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE: "1",
},
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "no-tenant-account-create-env-missing", exitCode: 1 });
assert.deepEqual(stdout.lines, [
"FAIL: E2E no-tenant account creation env missing: E2E_NO_TENANT_EMAIL, E2E_NO_TENANT_PASSWORD while E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE=1.",
]);
assert.equal(spawn.calls.length, 0);
});
it("fails before running Playwright when readiness mode is not strict/mobile", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 0, stdout: "", stderr: "" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: {
E2E_BASE_URL: "https://staging.example.test",
E2E_TENANT_EMAIL: "user@example.test",
E2E_TENANT_PASSWORD: "secret",
E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION: "0",
E2E_TENANT_DASHBOARD_PATH: "/app/acme/dashboard",
E2E_TENANT_SETTINGS_ACCOUNT_PATH: "/app/acme/settings/account",
E2E_TENANT_SETTINGS_PROFILE_PATH: "/app/acme/settings/profile",
E2E_TENANT_SUBSCRIPTION_PATH: "/app/acme/settings/subscription",
E2E_TENANT_MEMBERS_PATH: "/app/acme/settings/members",
E2E_TENANT_ROLES_AND_PERMISSIONS_PATH: "/app/acme/settings/roles-and-permissions",
E2E_TENANT_NOTIFICATIONS_PATH: "/app/acme/settings/notifications",
E2E_TENANT_LOGS_PATH: "/app/acme/settings/logs",
E2E_TENANT_LOG_EVENTS_PATH: "/app/acme/settings/logs/events",
E2E_TENANT_AUDIT_EXPORT_PATH: "/app/acme/settings/audit-export",
E2E_TENANT_PRIVACY_PATH: "/app/acme/settings/privacy",
E2E_TENANT_CHATBOT_PATH: "/app/acme/settings/chatbot",
E2E_TENANT_AI_USAGE_PATH: "/app/acme/settings/ai-usage",
E2E_TENANT_API_PATH: "/app/acme/settings/api",
E2E_TENANT_API_KEYS_PATH: "/app/acme/settings/api/keys",
E2E_TENANT_API_DOCS_PATH: "/app/acme/settings/api/docs",
E2E_TENANT_API_LOGS_PATH: "/app/acme/settings/api/logs",
E2E_TENANT_POLICIES_PATH: "/app/acme/settings/policies",
E2E_TENANT_PORTAL_ANALYTICS_PATH: "/app/acme/portals/readiness/analytics",
E2E_TENANT_PORTAL_SETTINGS_PATH: "/app/acme/portals/readiness/settings",
E2E_ADMIN_EMAIL_SEQUENCES_PATH: "/admin/email-sequences",
E2E_PLUGIN_SETTINGS_PATH: "/app/acme/settings/plugins/plugin-1",
E2E_PLUGIN_READONLY_EMAIL: "plugin-readonly@example.test",
E2E_PLUGIN_READONLY_PASSWORD: "readonly-secret",
E2E_WORKFLOW_ENGINE_PATH: "/app/acme/workflows",
E2E_WORKFLOW_BUILDER_PATH: "/app/acme/workflows/wf-1/builder",
E2E_WORKFLOW_RUN_API_PATH: "/app/acme/workflows/wf-1/run/api",
E2E_WORKFLOW_RUN_MANUAL_PATH: "/app/acme/workflows/wf-1/run",
E2E_WORKFLOW_WEBHOOK_BUILDER_PATH: "/app/acme/workflows/wf-webhook/builder",
E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH: "/app/acme/workflows/wf-webhook/run",
E2E_WORKFLOW_WAITING_BUILDER_PATH: "/app/acme/workflows/wf-waiting/builder",
E2E_WORKFLOW_WAITING_RUN_MANUAL_PATH: "/app/acme/workflows/wf-waiting/run",
E2E_WORKFLOW_WAITING_RUN_API_PATH: "/app/acme/workflows/wf-waiting/run/api",
E2E_WORKFLOW_WAITING_EXECUTIONS_PATH: "/app/acme/workflows/wf-waiting/executions",
E2E_WORKFLOW_WAITING_EXECUTION_ID: "wfe-waiting-1",
PLUGIN_BINARY_BASE_PATH: "/tmp/e2e-plugins",
REQUIRE_E2E_READINESS: "1",
},
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "readiness-mode-missing", exitCode: 1 });
assert.deepEqual(stdout.lines, ["FAIL: E2E readiness mode missing: E2E_A11Y_STRICT=1, E2E_BROWSER_MATRIX=1, E2E_ALLOW_WORKFLOW_RUN=1."]);
assert.equal(spawn.calls.filter((call) => call.command === "pnpm" && call.args[0] === "exec").length, 0);
});
it("does not require mutating opt-ins for normal non-mutating readiness lanes", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 0, stdout: "", stderr: "" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: completeReadinessEnv({
E2E_ALLOW_REGISTRATION_CREATE: undefined,
E2E_ALLOW_ACCOUNT_CREATE: undefined,
E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE: undefined,
E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION: undefined,
E2E_ALLOW_PLUGIN_SETTINGS_WRITE: undefined,
}),
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "pass", reason: "ok", exitCode: 0 });
assert.equal(spawn.calls.filter((call) => call.command === "pnpm" && call.args[0] === "exec").length, 1);
});
it("fails before running Playwright when mutating readiness opt-ins are missing", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 0, stdout: "", stderr: "" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: completeReadinessEnv({
E2E_ALLOW_REGISTRATION_CREATE: undefined,
E2E_ALLOW_ACCOUNT_CREATE: undefined,
E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE: undefined,
E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION: undefined,
E2E_ALLOW_PLUGIN_SETTINGS_WRITE: undefined,
REQUIRE_E2E_MUTATING_READINESS: "1",
}),
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "readiness-mutating-opt-in-missing", exitCode: 1 });
assert.deepEqual(stdout.lines, [
"FAIL: E2E readiness mutating opt-in missing: E2E_ALLOW_REGISTRATION_CREATE=1, E2E_ALLOW_ACCOUNT_CREATE=1, E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE=1, E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION=1, E2E_ALLOW_PLUGIN_SETTINGS_WRITE=1.",
]);
assert.equal(spawn.calls.filter((call) => call.command === "pnpm" && call.args[0] === "exec").length, 0);
});
it("fails before running Playwright when subscription success strict mode is missing its path", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 0, stdout: "", stderr: "" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: completeReadinessEnv({ E2E_REQUIRE_SUBSCRIPTION_SUCCESS: "1" }),
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "subscription-success-readiness-env-missing", exitCode: 1 });
assert.deepEqual(stdout.lines, [
"FAIL: E2E subscription success readiness env missing: E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH while E2E_REQUIRE_SUBSCRIPTION_SUCCESS=1.",
]);
assert.equal(spawn.calls.filter((call) => call.command === "pnpm" && call.args[0] === "exec").length, 0);
});
it("fails before running Playwright when real workflow webhook execution lacks its target contract", () => {
const spawn = recordingSpawn({});
const stdout = captureLines();
assert.deepEqual(
missingWorkflowWebhookRunReadinessEnv({
env: { E2E_ALLOW_WORKFLOW_WEBHOOK_RUN: "1" },
}),
[
"E2E_TENANT_EMAIL",
"E2E_TENANT_PASSWORD",
"E2E_WORKFLOW_WEBHOOK_BUILDER_PATH",
"E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH",
"E2E_WORKFLOW_WEBHOOK_TARGET_URL",
]
);
assert.deepEqual(
missingWorkflowWebhookRunReadinessEnv({
env: completeReadinessEnv({
E2E_ALLOW_WORKFLOW_WEBHOOK_RUN: "1",
E2E_WORKFLOW_WEBHOOK_TARGET_URL: "https://hooks.example.test/readiness",
}),
}),
[]
);
const outcome = runPlaywrightA11y({
env: completeReadinessEnv({
E2E_ALLOW_WORKFLOW_WEBHOOK_RUN: "1",
E2E_WORKFLOW_WEBHOOK_TARGET_URL: "",
}),
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "workflow-webhook-run-readiness-env-missing", exitCode: 1 });
assert.deepEqual(stdout.lines, [
"FAIL: E2E workflow webhook run readiness env missing: E2E_WORKFLOW_WEBHOOK_TARGET_URL while E2E_ALLOW_WORKFLOW_WEBHOOK_RUN=1.",
]);
assert.equal(spawn.calls.length, 0);
});
it("fails before running Playwright when workflow outbox-drain readiness is misconfigured", () => {
const spawn = recordingSpawn({});
const stdout = captureLines();
assert.deepEqual(
missingWorkflowOutboxDrainReadinessEnv({
env: { E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN: "1", E2E_WORKFLOW_OUTBOX_DRAIN_EXPECTED_STATUS: "done" },
}),
[
"E2E_TENANT_EMAIL",
"E2E_TENANT_PASSWORD",
"E2E_WORKFLOW_RUN_MANUAL_PATH",
"E2E_WORKFLOW_OUTBOX_DRAIN_EXPECTED_STATUS=success|error",
]
);
assert.deepEqual(
missingWorkflowOutboxDrainReadinessEnv({
env: completeReadinessEnv({
E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN: "1",
E2E_WORKFLOW_OUTBOX_DRAIN_EXPECTED_STATUS: "error",
}),
}),
[]
);
const outcome = runPlaywrightA11y({
env: completeReadinessEnv({
E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN: "1",
E2E_WORKFLOW_OUTBOX_DRAIN_EXPECTED_STATUS: "done",
}),
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "workflow-outbox-drain-readiness-env-missing", exitCode: 1 });
assert.deepEqual(stdout.lines, [
"FAIL: E2E workflow outbox-drain readiness env missing: E2E_WORKFLOW_OUTBOX_DRAIN_EXPECTED_STATUS=success|error while E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1.",
]);
assert.equal(spawn.calls.length, 0);
});
it("static contract keeps subscription success strict mode explicit", () => {
const runnerSource = readFileSync("scripts/run-playwright-a11y.mjs", "utf8");
const subscriptionSource = readFileSync("tests/e2e/subscription-smoke.spec.ts", "utf8");
const readinessDocs = readFileSync("docs/testing/e2e-readiness.md", "utf8");
assert.match(runnerSource, /E2E_REQUIRE_SUBSCRIPTION_SUCCESS/u);
assert.match(runnerSource, /E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH/u);
assert.match(subscriptionSource, /E2E_REQUIRE_SUBSCRIPTION_SUCCESS/u);
assert.match(
subscriptionSource,
/Subscription checkout success smoke requires E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH while E2E_REQUIRE_SUBSCRIPTION_SUCCESS=1/u
);
assert.match(subscriptionSource, /follows checkout success return into the tenant dashboard on mobile when strict mode is enabled/u);
assert.match(subscriptionSource, /await expectTenantDashboard\(page\)/u);
assert.match(readinessDocs, /Normal\s+readiness runs stay non-mutating and do not require\s+`E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH`/u);
assert.match(readinessDocs, /`E2E_REQUIRE_SUBSCRIPTION_SUCCESS=1`/u);
});
it("requires plugin settings and workflow fixtures separately from mutating opt-ins", () => {
assert.deepEqual(
missingReadinessEnv({
env: {
E2E_TENANT_EMAIL: "user@example.test",
E2E_TENANT_PASSWORD: "secret",
E2E_TENANT_DASHBOARD_PATH: "/app/acme/dashboard",
E2E_TENANT_SUBSCRIPTION_PATH: "/app/acme/settings/subscription",
E2E_WORKFLOW_RUN_MANUAL_PATH: "/app/acme/workflows/wf-1/run",
E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH: "/app/acme/workflows/wf-webhook/run",
PLUGIN_BINARY_BASE_PATH: "/tmp/e2e-plugins",
},
}),
[
"E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION",
"E2E_TENANT_SETTINGS_ACCOUNT_PATH",
"E2E_TENANT_SETTINGS_PROFILE_PATH",
"E2E_TENANT_MEMBERS_PATH",
"E2E_TENANT_ROLES_AND_PERMISSIONS_PATH",
"E2E_TENANT_NOTIFICATIONS_PATH",
"E2E_TENANT_LOGS_PATH",
"E2E_TENANT_LOG_EVENTS_PATH",
"E2E_TENANT_AUDIT_EXPORT_PATH",
"E2E_TENANT_PRIVACY_PATH",
"E2E_TENANT_CHATBOT_PATH",
"E2E_TENANT_AI_USAGE_PATH",
"E2E_TENANT_API_PATH",
"E2E_TENANT_API_KEYS_PATH",
"E2E_TENANT_API_DOCS_PATH",
"E2E_TENANT_API_LOGS_PATH",
"E2E_TENANT_POLICIES_PATH",
"E2E_TENANT_PORTAL_ANALYTICS_PATH",
"E2E_TENANT_PORTAL_SETTINGS_PATH",
"E2E_ADMIN_EMAIL_SEQUENCES_PATH",
"E2E_PLUGIN_SETTINGS_PATH",
"E2E_PLUGIN_READONLY_EMAIL",
"E2E_PLUGIN_READONLY_PASSWORD",
"E2E_WORKFLOW_ENGINE_PATH",
"E2E_WORKFLOW_BUILDER_PATH",
"E2E_WORKFLOW_RUN_API_PATH",
"E2E_WORKFLOW_WEBHOOK_BUILDER_PATH",
"E2E_WORKFLOW_WAITING_BUILDER_PATH",
"E2E_WORKFLOW_WAITING_RUN_MANUAL_PATH",
"E2E_WORKFLOW_WAITING_RUN_API_PATH",
"E2E_WORKFLOW_WAITING_EXECUTIONS_PATH",
"E2E_WORKFLOW_WAITING_EXECUTION_ID",
]
);
});
it("rejects invalid explicit registration email verification readiness assertions", () => {
assert.deepEqual(
missingReadinessEnv({
env: completeReadinessEnv({
E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION: "yes",
}),
}),
["E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION=0|1"]
);
});
it("keeps persistent plugin lifecycle E2E flows behind explicit opt-in", () => {
const source = readFileSync(
"tests/e2e/plugin-settings.mobile-a11y.spec.ts",
"utf8"
);
assert.match(source, /skipOrFailWhenE2eOptInMissing/u);
assert.match(source, /E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION/u);
assert.match(source, /E2E_ALLOW_PLUGIN_SETTINGS_WRITE/u);
assert.match(
source,
/test\("capability grant and revoke persist for the seeded readiness plugin"[\s\S]*?skipUnlessPluginLifecycleMutationAllowed\("capability grant\/revoke persistence flow"\)/u
);
assert.match(
source,
/test\("lifecycle controls persist enable and disable state for the seeded lifecycle plugin"[\s\S]*?skipUnlessPluginLifecycleMutationAllowed\("enable\/disable persistence flow"\)/u
);
});
it("keeps mutating registration and account E2E flows readiness-aware behind explicit opt-ins", () => {
const source = readFileSync(
"tests/e2e/registration-account-dashboard.mobile-a11y.spec.ts",
"utf8"
);
assert.match(source, /skipOrFailWhenE2eOptInMissing\("E2E_ALLOW_REGISTRATION_CREATE"/u);
assert.match(source, /skipOrFailWhenE2eOptInMissing\("E2E_ALLOW_ACCOUNT_CREATE"/u);
assert.match(source, /skipOrFailWhenE2eOptInMissing\("E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE"/u);
assert.doesNotMatch(source, /test\.skip\(process\.env\.E2E_ALLOW_(?:REGISTRATION_CREATE|ACCOUNT_CREATE|NO_TENANT_ACCOUNT_CREATE)/u);
});
it("keeps the local mutating E2E readiness lane explicit and documented", () => {
const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
const script = packageJson.scripts["verify:e2e-mutating-readiness:local"];
const docs = readFileSync("docs/testing/e2e-readiness.md", "utf8");
const requiredFlags = [
"REQUIRE_E2E_MUTATING_READINESS=1",
"E2E_ALLOW_REGISTRATION_CREATE=1",
"E2E_ALLOW_ACCOUNT_CREATE=1",
"E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE=1",
"E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION=1",
"E2E_ALLOW_PLUGIN_SETTINGS_WRITE=1",
];
assert.equal(typeof script, "string");
assert.match(script, /pnpm run verify:e2e-a11y/u);
assert.match(docs, /verify:e2e-mutating-readiness:local/u);
for (const flag of requiredFlags) {
assert.match(script, new RegExp(flag, "u"));
assert.match(docs, new RegExp(flag, "u"));
}
assert.match(docs, /E2E_NO_TENANT_EMAIL/u);
assert.match(docs, /E2E_NO_TENANT_PASSWORD/u);
});
it("keeps the local worker-drain E2E readiness lane explicit and documented", () => {
const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
const script = packageJson.scripts["verify:e2e-mutating-readiness:worker-local"];
const docs = readFileSync("docs/testing/e2e-readiness.md", "utf8");
assert.equal(typeof script, "string");
assert.match(script, /E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1/u);
assert.match(script, /pnpm run verify:e2e-mutating-readiness:local/u);
assert.match(docs, /verify:e2e-mutating-readiness:worker-local/u);
assert.match(docs, /E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1/u);
assert.match(docs, /WorkflowExecution\.status/u);
});
it("keeps real workflow webhook E2E runs behind explicit opt-in and target URL", () => {
const source = readFileSync(
"tests/e2e/workflow-builder-run.mobile-a11y.spec.ts",
"utf8"
);
assert.match(source, /E2E_ALLOW_WORKFLOW_WEBHOOK_RUN/u);
assert.match(source, /E2E_WORKFLOW_WEBHOOK_TARGET_URL/u);
assert.match(source, /E2E_WORKFLOW_WEBHOOK_BUILDER_PATH/u);
assert.match(source, /E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH/u);
assert.match(source, /E2E workflow webhook run opt-in missing/u);
assert.match(
source,
/if \(allowWorkflowWebhookRun\)[\s\S]*?requireWorkflowWebhookRunReadiness\(\)[\s\S]*?else[\s\S]*?skipOrFailWhenE2eInputsMissing/u
);
assert.match(
source,
/if \(allowWorkflowWebhookRun\)[\s\S]*?workflowWebhookTargetUrl![\s\S]*?else[\s\S]*?interceptCurrentRoutePost/u
);
});
it("runs Playwright when runtime and target are reachable", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 0, stdout: "", stderr: "" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const stdout = captureLines();
const outcome = runPlaywrightA11y({
env: { E2E_BASE_URL: "https://staging.example.test" },
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "pass", reason: "ok", exitCode: 0 });
const runCall = spawn.calls.find((call) => call.command === "pnpm" && call.args[0] === "exec");
assert.ok(runCall, "expected playwright test to be invoked");
assert.deepEqual(runCall.args, ["exec", "playwright", "test", "--config=playwright.config.ts"]);
assert.deepEqual(runCall.options, { stdio: "inherit" });
});
it("returns the Playwright exit status when the suite fails", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
pnpm: () => ({ status: 3, stdout: "", stderr: "failed" }),
curl: () => ({ status: 0, stdout: "200", stderr: "" }),
});
const outcome = runPlaywrightA11y({ env: {}, spawn, stdout: captureLines() });
assert.deepEqual(outcome, { status: "fail", reason: "non-zero-exit", exitCode: 3 });
});
it("treats a valid curl status body as reachable", () => {
const spawn = recordingSpawn({
curl: () => ({ status: 0, stdout: "503", stderr: "" }),
});
assert.equal(isUrlReachable("https://degraded.example.test", { spawn }), true);
});
it("treats server errors as unreachable in strict readiness mode", () => {
const spawn = recordingSpawn({
npx: ({ args }) => {
if (args[0] === "--no-install") {
return { status: 0, stdout: "Version 1.59.1", stderr: "" };
}
return { status: 0, stdout: "", stderr: "" };
},
curl: () => ({ status: 0, stdout: "503", stderr: "" }),
});
const stdout = captureLines();
assert.equal(isUrlReachable("https://degraded.example.test", { spawn, allowServerErrors: false }), false);
const outcome = runPlaywrightA11y({
env: { E2E_BASE_URL: "https://degraded.example.test", REQUIRE_E2E_A11Y: "1" },
spawn,
stdout,
});
assert.deepEqual(outcome, { status: "fail", reason: "target-unreachable", exitCode: 1 });
assert.deepEqual(stdout.lines, ["FAIL: Target URL https://degraded.example.test not reachable while REQUIRE_E2E_A11Y=1."]);
assert.equal(spawn.calls.filter((call) => call.command === "pnpm").length, 0);
});
});