From 6963c8d6e15e0ddf00589a467d2e4988c8357305 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 2 Sep 2026 19:43:37 -0700 Subject: [PATCH] ci: give the Website preview verification a Vercel automation bypass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Verify Website preview runtime embedding policy" step added in #963 cannot pass. Vercel deployment protection answers every preview path with 302 -> vercel.com/sso-api, so Playwright drives the browser to an SSO page and times out waiting for the app: locator.click: Test timeout of 30000ms exceeded. waiting for [data-cockpit-desktop-navigation] button "Settings" It has never succeeded: skipped on 738b2ed8 as stale, failed on its first real execution. Because the step sits mid-deploy, its failure skips Website promotion, the cockpit redirect build and cockpit promotion — production has not advanced since the arc landed, which is why cockpit.threadplane.ai still answers 200 where the smoke suite expects 308. Send the project's automation bypass when CI supplies it. The header is added only when VERCEL_AUTOMATION_BYPASS_SECRET is set, so local and production runs are unchanged, and every URL this suite visits is a first-party origin. The step now fails with an actionable message rather than a silent 90s timeout when the secret is missing. Requires the repository secret VERCEL_AUTOMATION_BYPASS_SECRET, from "Protection Bypass for Automation" on the Vercel website project. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 10 ++++++- apps/website/playwright.config.ts | 14 ++++++++++ apps/website/src/playwright-config.spec.ts | 31 ++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e99e039f..a31895118 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1005,9 +1005,17 @@ jobs: npx vercel deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }} - name: Verify Website preview runtime embedding policy if: steps.freshness.outputs.stale != 'true' && steps.affected.outputs.website == 'true' - run: npx playwright test apps/website/e2e/platform-production-smoke.spec.ts --config apps/website/playwright.config.ts --grep "unified runtime embedding policy" --reporter=list + run: | + # Without the bypass the preview answers 302 -> vercel.com/sso-api and + # this check times out on an SSO page instead of the app. Say so. + if [ -z "${VERCEL_AUTOMATION_BYPASS_SECRET}" ]; then + echo "::error::VERCEL_AUTOMATION_BYPASS_SECRET is unset — the protected Website preview cannot be verified. Enable 'Protection Bypass for Automation' on the Vercel website project and store the value as this repository secret." + exit 1 + fi + npx playwright test apps/website/e2e/platform-production-smoke.spec.ts --config apps/website/playwright.config.ts --grep "unified runtime embedding policy" --reporter=list env: PRODUCTION_SMOKE: 'true' + VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} BASE_URL: ${{ steps.deploy_website.outputs.preview_origin }} WEBSITE_URL: ${{ steps.deploy_website.outputs.preview_origin }} EXAMPLES_URL: https://examples.threadplane.ai diff --git a/apps/website/playwright.config.ts b/apps/website/playwright.config.ts index e3f0b2b11..b1567ddfe 100644 --- a/apps/website/playwright.config.ts +++ b/apps/website/playwright.config.ts @@ -37,6 +37,20 @@ export const createWebsitePlaywrightConfig = ( retries: environment['CI'] ? 2 : 0, use: { baseURL, + // Vercel deployment protection answers 302 -> vercel.com/sso-api for every + // path on a preview, so a browser-driven check lands on an SSO page and + // times out. When CI supplies the project's automation bypass, send it so + // the preview is reachable. Every URL this suite touches is a first-party + // Threadplane origin. Unset locally and in production runs. + ...(environment['VERCEL_AUTOMATION_BYPASS_SECRET'] + ? { + extraHTTPHeaders: { + 'x-vercel-protection-bypass': + environment['VERCEL_AUTOMATION_BYPASS_SECRET'], + 'x-vercel-set-bypass-cookie': 'true', + }, + } + : {}), // Custom-target coverage carries an obvious fixture key. Keep browser // artifacts disabled so request headers and page state are never retained. trace: 'off', diff --git a/apps/website/src/playwright-config.spec.ts b/apps/website/src/playwright-config.spec.ts index 67b0f71f8..76b1b06a0 100644 --- a/apps/website/src/playwright-config.spec.ts +++ b/apps/website/src/playwright-config.spec.ts @@ -4,6 +4,37 @@ import { resolve } from 'node:path'; import { createWebsitePlaywrightConfig } from '../playwright.config'; describe('Website Playwright configuration', () => { + it('sends the Vercel automation bypass only when CI supplies it', () => { + const withoutSecret = createWebsitePlaywrightConfig({}); + expect(withoutSecret.use?.extraHTTPHeaders).toBeUndefined(); + + const withSecret = createWebsitePlaywrightConfig({ + VERCEL_AUTOMATION_BYPASS_SECRET: 'sentinel-value', + }); + expect(withSecret.use?.extraHTTPHeaders).toEqual({ + 'x-vercel-protection-bypass': 'sentinel-value', + 'x-vercel-set-bypass-cookie': 'true', + }); + }); + + it('keeps the production-smoke spec loadable under Playwright CJS transpilation', () => { + const smoke = readFileSync( + resolve(__dirname, '../e2e/platform-production-smoke.spec.ts'), + 'utf8' + ); + + // Playwright transpiles specs to CJS, so the ESM-only meta object compiles + // to a `require` the loaded module cannot resolve and the file silently + // fails to collect — the job then reports "No tests found" rather than + // failing. Strip comments first: the spec names the trap in prose so it is + // not reintroduced, and that mention must not trip this guard. + const code = smoke + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/(^|[^:])\/\/.*$/gm, '$1'); + + expect(code).not.toContain('import.meta'); + }); + it('derives the production embedding assertion from the authoritative origin source', () => { const smoke = readFileSync( resolve(__dirname, '../e2e/platform-production-smoke.spec.ts'),