name: E2E Readiness # Runs the deployed staging/readiness browser suite in fail-closed mode. # Main CI intentionally remains browser-free; this lane proves the authenticated # Playwright/mobile/a11y contract against a live target on schedule or demand. # # Docs: docs/testing/e2e-readiness.md on: workflow_dispatch: inputs: plugin_lifecycle_mutation: description: "Operator-approved disposable run: persist plugin grant/revoke and enable/disable state" required: false default: "false" type: boolean plugin_settings_write: description: "Operator-approved disposable run: persist plugin settings iframe writes" required: false default: "false" type: boolean workflow_outbox_drain: description: "Operator-approved run: require worker/outbox drain to terminal WorkflowExecution status" required: false default: "false" type: boolean workflow_webhook_run: description: "Operator-approved run: execute the webhook workflow against workflow_webhook_target_url" required: false default: "false" type: boolean workflow_webhook_target_url: description: "Operator-owned disposable webhook target URL, required when workflow_webhook_run is true" required: false default: "" type: string subscription_success: description: "Operator-approved run: require Stripe checkout success return smoke" required: false default: "false" type: boolean subscription_success_path: description: "Externally acquired Stripe checkout success return path, required when subscription_success is true" required: false default: "" type: string schedule: # 04:00 UTC nightly, after deploy and Lighthouse windows have settled. - cron: "0 4 * * *" permissions: contents: read env: E2E_BASE_URL: https://boiler.saas-ctrl.com E2E_A11Y_STRICT: "1" E2E_BROWSER_MATRIX: "1" E2E_ALLOW_WORKFLOW_RUN: "1" REQUIRE_E2E_A11Y: "1" REQUIRE_AUTH_E2E: "1" REQUIRE_E2E_READINESS: "1" E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION: "0" E2E_TENANT_ACCOUNT_NAME: E2E Readiness E2E_TENANT_ACCOUNT_SLUG: e2e-readiness E2E_TENANT_DASHBOARD_PATH: /e2e-readiness/dashboard E2E_TENANT_SETTINGS_ACCOUNT_PATH: /e2e-readiness/settings/account E2E_TENANT_SETTINGS_PROFILE_PATH: /e2e-readiness/settings/profile E2E_TENANT_SUBSCRIPTION_PATH: /e2e-readiness/settings/subscription E2E_TENANT_MEMBERS_PATH: /e2e-readiness/settings/members E2E_TENANT_ROLES_AND_PERMISSIONS_PATH: /e2e-readiness/settings/roles-and-permissions E2E_TENANT_NOTIFICATIONS_PATH: /e2e-readiness/settings/notifications E2E_TENANT_LOGS_PATH: /e2e-readiness/settings/logs E2E_TENANT_LOG_EVENTS_PATH: /e2e-readiness/settings/logs/events E2E_TENANT_AUDIT_EXPORT_PATH: /e2e-readiness/settings/audit-export E2E_TENANT_PRIVACY_PATH: /e2e-readiness/settings/privacy E2E_TENANT_CHATBOT_PATH: /e2e-readiness/settings/chatbot E2E_TENANT_AI_USAGE_PATH: /e2e-readiness/settings/ai-usage E2E_TENANT_API_PATH: /e2e-readiness/settings/api E2E_TENANT_API_KEYS_PATH: /e2e-readiness/settings/api/keys E2E_TENANT_API_DOCS_PATH: /e2e-readiness/settings/api/docs E2E_TENANT_API_LOGS_PATH: /e2e-readiness/settings/api/logs E2E_TENANT_POLICIES_PATH: /e2e-readiness/settings/policies E2E_TENANT_PORTAL_ANALYTICS_PATH: /e2e-readiness/portals/e2e-readiness/analytics E2E_TENANT_PORTAL_SETTINGS_PATH: /e2e-readiness/portals/e2e-readiness/settings E2E_ADMIN_EMAIL_SEQUENCES_PATH: /email-sequences E2E_PLUGIN_SETTINGS_PATH: /e2e-readiness/settings/plugins E2E_WORKFLOW_ENGINE_PATH: /e2e-readiness/workflow-engine/workflows E2E_WORKFLOW_BUILDER_PATH: /e2e-readiness/workflow-engine/workflows/wf_123 E2E_WORKFLOW_RUN_API_PATH: /e2e-readiness/workflow-engine/workflows/wf_123/run/api E2E_WORKFLOW_RUN_MANUAL_PATH: /e2e-readiness/workflow-engine/workflows/wf_123/run/manual 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 E2E_WORKFLOW_WAITING_BUILDER_PATH: /e2e-readiness/workflow-engine/workflows/wf_waiting_123 E2E_WORKFLOW_WAITING_RUN_MANUAL_PATH: /e2e-readiness/workflow-engine/workflows/wf_waiting_123/run/manual E2E_WORKFLOW_WAITING_RUN_API_PATH: /e2e-readiness/workflow-engine/workflows/wf_waiting_123/run/api E2E_WORKFLOW_WAITING_EXECUTIONS_PATH: /e2e-readiness/workflow-engine/workflows/wf_waiting_123/executions E2E_WORKFLOW_WAITING_EXECUTION_ID: wfe_waiting_123 PLUGIN_BINARY_BASE_PATH: /tmp/e2e-plugins PLAYWRIGHT_BROWSERS_PATH: .cache/ms-playwright jobs: readiness: name: Strict Playwright readiness runs-on: ubuntu-latest timeout-minutes: 45 env: 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 }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Enable Corepack run: corepack enable - name: Prepare pnpm 10.33.0 run: corepack prepare pnpm@10.33.0 --activate - name: Configure pnpm store run: pnpm config set store-dir .pnpm-store - name: Cache pnpm store uses: actions/cache@v4 with: path: .pnpm-store key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm- - name: Cache Playwright browsers uses: actions/cache@v4 with: path: .cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-playwright- - name: Install dependencies run: pnpm install --frozen-lockfile --prefer-offline - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium webkit - name: Apply manual operator E2E opt-ins if: ${{ github.event_name == 'workflow_dispatch' }} # Bind dispatch inputs to env vars instead of interpolating ${{ inputs.* }} # straight into the shell, so a crafted value cannot inject commands or # extra lines into $GITHUB_ENV (CI script injection). Free-form URL/path # inputs are additionally allowlist-validated before being written. env: OPT_PLUGIN_LIFECYCLE_MUTATION: ${{ inputs.plugin_lifecycle_mutation }} OPT_PLUGIN_SETTINGS_WRITE: ${{ inputs.plugin_settings_write }} OPT_WORKFLOW_OUTBOX_DRAIN: ${{ inputs.workflow_outbox_drain }} OPT_WORKFLOW_WEBHOOK_RUN: ${{ inputs.workflow_webhook_run }} OPT_WORKFLOW_WEBHOOK_TARGET_URL: ${{ inputs.workflow_webhook_target_url }} OPT_SUBSCRIPTION_SUCCESS: ${{ inputs.subscription_success }} OPT_SUBSCRIPTION_SUCCESS_PATH: ${{ inputs.subscription_success_path }} run: | if [ "$OPT_PLUGIN_LIFECYCLE_MUTATION" = "true" ]; then echo "E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION=1" >> "$GITHUB_ENV" fi if [ "$OPT_PLUGIN_SETTINGS_WRITE" = "true" ]; then echo "E2E_ALLOW_PLUGIN_SETTINGS_WRITE=1" >> "$GITHUB_ENV" fi if [ "$OPT_WORKFLOW_OUTBOX_DRAIN" = "true" ]; then echo "E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1" >> "$GITHUB_ENV" fi if [ "$OPT_WORKFLOW_WEBHOOK_RUN" = "true" ]; then echo "E2E_ALLOW_WORKFLOW_WEBHOOK_RUN=1" >> "$GITHUB_ENV" case "$OPT_WORKFLOW_WEBHOOK_TARGET_URL" in "") ;; *[!a-zA-Z0-9:/?._~=%\&-]*) echo "Invalid workflow_webhook_target_url" >&2; exit 1 ;; *) printf 'E2E_WORKFLOW_WEBHOOK_TARGET_URL=%s\n' "$OPT_WORKFLOW_WEBHOOK_TARGET_URL" >> "$GITHUB_ENV" ;; esac fi if [ "$OPT_SUBSCRIPTION_SUCCESS" = "true" ]; then echo "E2E_REQUIRE_SUBSCRIPTION_SUCCESS=1" >> "$GITHUB_ENV" case "$OPT_SUBSCRIPTION_SUCCESS_PATH" in "") ;; *[!a-zA-Z0-9:/?._~=%\&-]*) echo "Invalid subscription_success_path" >&2; exit 1 ;; *) printf 'E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH=%s\n' "$OPT_SUBSCRIPTION_SUCCESS_PATH" >> "$GITHUB_ENV" ;; esac fi - name: Run strict E2E readiness suite run: pnpm run verify:e2e-a11y - name: Upload Playwright traces if: always() uses: actions/upload-artifact@v4 with: name: e2e-readiness-results path: test-results/ if-no-files-found: ignore retention-days: 14