Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1009,9 +1009,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
Expand Down
14 changes: 14 additions & 0 deletions apps/website/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
31 changes: 31 additions & 0 deletions apps/website/src/playwright-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Loading