How to Deploy Playwright on Vercel for End-to-End Testing
Run Playwright against the exact Vercel Preview Deployment URL from CI, with working GitHub Actions configuration, secrets, protection bypasses, and fixes.

Direct answer: you normally do not deploy Playwright to Vercel. Vercel deploys your application, while a CI runner installs Playwright and its browsers, waits for Vercel to report a successful Preview Deployment, then runs end-to-end tests against that deployment’s URL. This separation lets the tests validate the same artifact that reviewers will open.
The reliable flow is:
- Connect the repository to a Vercel project and enable Preview Deployments.
- Trigger CI only after deployment success.
- Check out the deployed commit.
- Install dependencies and Playwright browser dependencies.
- Pass the deployment URL to Playwright as
baseURL. - Run the suite, save traces and reports, and keep test credentials in CI secrets.
This guide uses GitHub Actions examples, then explains Vercel repository dispatch, deployment-status events, webhooks, protected previews, URL selection, reliability, cost, and common failures.
1. Prepare the Vercel Preview environment
Link your Git repository to a Vercel project and confirm that the branch or pull request you intend to test creates a Preview Deployment. Preview environments are designed for testing changes without changing production, and every deployment receives a generated URL. Vercel documents the environment model in its deployment environments documentation.
Before writing CI, open a Preview URL manually and verify the complete application path that tests will use:
- The frontend can reach its Preview database and API.
- Required Preview environment variables are configured in Vercel.
- Seed data or test accounts exist.
- External services accept requests from the Preview deployment.
- Authentication works with a dedicated test user.
Vercel keeps Local, Preview, and Production environment values separately. A variable present in Production is not automatically a valid test value in Preview. Configure application variables in Vercel, and configure runner-only credentials such as test usernames in your CI provider.
2. Add Playwright to the project
For an existing Node.js project, install Playwright Test and commit the lockfile:
npm install -D @playwright/test
npx playwright install
On a Linux CI runner, install browser operating-system dependencies too:
npx playwright install --with-deps
Playwright’s CI documentation uses the same sequence: npm ci, browser installation, and npx playwright test. A compatible Playwright container is another option when you want browser dependencies preinstalled; keep the container version aligned with the version in your lockfile.
A minimal test
import { test, expect } from '@playwright/test';
test('home page loads', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My application/i);
await expect(page.getByRole('main')).toBeVisible();
});
3. Configure Playwright for a deployed URL
Do not hard-code a temporary Preview URL in test files. Read it from the CI environment and use relative paths in tests.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }], ['list']],
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});
The local fallback is useful when running a development server on your machine. In CI that tests an existing Vercel deployment, set PLAYWRIGHT_BASE_URL to the event’s deployment URL. Playwright’s webServer option serves a different purpose: it starts a local server and waits for it to become ready. Do not add webServer to a job whose purpose is to test the already-deployed Vercel build.
4. Trigger GitHub Actions after deployment success
There are two practical GitHub patterns. Playwright’s CI guide shows the deployment_status event and filters for a successful state. Vercel’s current guide shows a repository_dispatch event of type vercel.deployment.success. Both prevent tests from racing a deployment that is still building.

Option A: GitHub deployment status
Use this when your Vercel integration creates GitHub deployment-status events:
name: Playwright against Vercel Preview
on:
deployment_status:
jobs:
e2e:
if: ${{ github.event.deployment_status.state == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out deployed commit
uses: actions/checkout@v4
with:
ref: ${{ github.event.deployment.sha }}
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run end-to-end tests
env:
PLAYWRIGHT_BASE_URL: ${{ github.event.deployment_status.target_url }}
E2E_USERNAME: ${{ secrets.E2E_USERNAME }}
E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }}
run: npx playwright test
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-results
path: test-results/
The important values are github.event.deployment.sha, which makes the checkout match the deployed commit, and github.event.deployment_status.target_url, which points the browser at that deployment. Check the event payload in your repository if your Vercel integration uses a different field.
Option B: Vercel repository dispatch
Vercel’s documented GitHub approach sends a repository_dispatch event with type vercel.deployment.success. The payload includes the deployed Git SHA and URL:
name: Playwright after Vercel success
on:
repository_dispatch:
types: [vercel.deployment.success]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.client_payload.gitSha }}
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
PLAYWRIGHT_BASE_URL: ${{ github.event.client_payload.url }}
E2E_USERNAME: ${{ secrets.E2E_USERNAME }}
E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }}
Use the exact payload field names supplied by your Vercel integration. Vercel’s guide uses the deployment URL from github.event.client_payload.url. If your payload calls the SHA sha rather than gitSha, update the checkout expression accordingly.
Other CI providers
Vercel documents a deployment.succeeded webhook for other CI systems. Configure the webhook to start your pipeline, extract the deployment URL and commit SHA, then run the same install and Playwright commands. The key invariant is that the job starts from a success event and receives the URL for that exact deployment.
5. Choose the correct Vercel URL
Vercel offers generated URLs that behave differently:
| URL choice | Behavior | Best use |
|---|---|---|
| Commit-specific deployment URL | Points to the artifact produced for one commit | Pull request and release validation |
| Branch URL | Follows the newest deployment on that branch | Long-lived integration environments |
| Production URL | Points to the live site | Post-release smoke checks |
For end-to-end tests associated with a pull request, prefer the commit-specific URL from the deployment event. A branch URL can move after another push, making a failed test difficult to associate with the source change. Vercel explains the distinction between deployment URLs and branch aliases in its generated URL documentation.
6. Authentication, secrets, and protected previews
Keep passwords, tokens, and API keys out of test source and repository files. Add them as encrypted CI secrets and expose them only to the test step:
import { test, expect } from '@playwright/test';
test('authenticated dashboard', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.E2E_USERNAME!);
await page.getByLabel('Password').fill(process.env.E2E_PASSWORD!);
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page).toHaveURL(/dashboard/);
});
Preview environment variables are consumed by the deployed application; CI secrets are consumed by the runner. You may need both. Never assume that adding a secret to GitHub makes it available inside Vercel’s Preview runtime.
Deployment Protection can require authentication before a Preview page is reachable. In that case, configure Vercel Protection Bypass for Automation and store the bypass credential as a CI secret. Add the required header or cookie in Playwright’s use.extraHTTPHeaders or context setup, following Vercel’s automation bypass documentation. Treat the bypass value like a password and rotate it when access changes.
7. Reliability and speed controls
Start with one worker in CI. Playwright recommends this starting point because it favors stable, reproducible runs. Once the suite is isolated and the runner has capacity, increase workers or use sharding across jobs. More workers reduce wall-clock time only when tests and dependent services tolerate concurrency.
- Retries: enable a small CI-only retry count and retain a trace on the first retry.
- Isolation: create independent test data or reset state between tests; shared accounts create order-dependent failures.
- Waiting: assert visible application state instead of adding arbitrary sleeps. Use a short delay only for a known asynchronous transition.
- Artifacts: upload the HTML report, screenshots, videos, and traces with
if: always(). - Timeouts: set job, navigation, and assertion limits so a broken deployment fails promptly.
- Browser versions: install browsers from the lockfile-compatible Playwright version on every run, or pin a matching container.
For large suites, shard with a matrix:
strategy:
fail-fast: false
matrix:
shard: [1/4, 2/4, 3/4, 4/4]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}
Use sharding only after each shard has predictable data and cleanup. Otherwise parallel jobs can produce intermittent failures that look like browser problems.
8. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Tests run before the site exists | Workflow starts on push or deployment-created instead of success | Use deployment_status with state success, repository dispatch, or deployment.succeeded. |
ERR_NAME_NOT_RESOLVED or connection refused |
Wrong URL field, deleted deployment, or a local URL in CI | Print the event URL safely, verify it opens from the runner, and set PLAYWRIGHT_BASE_URL from the success payload. |
| Tests validate another commit | Checkout defaults to the workflow’s triggering ref | Checkout the SHA in the deployment event. |
| Browser executable missing | Playwright package installed without browsers | Run npx playwright install --with-deps or use a compatible Playwright container. |
| Preview redirects to a login screen | Deployment Protection blocks automation | Configure Protection Bypass for Automation and pass the protected secret to the browser context. |
| Login fails only in CI | Missing CI secrets, wrong Preview backend, or test user restrictions | Check secret names, Preview variables, allowed origins, and account state. Do not print credentials. |
| Assets or API calls return 404 | Preview-specific environment variable or rewrite is missing | Compare Preview configuration with the expected deployment and inspect the failed request in a trace. |
| Flaky timeout failures | Shared state, excessive parallelism, or fixed sleeps | Use isolated data, one worker, locator assertions, and traces before increasing timeouts. |
| Workflow never starts | Event type or payload does not match the integration | Inspect a real webhook or Actions event and update the trigger and field expressions to match it. |
9. Cost and operational considerations
Playwright itself is open source, but CI consumes runner minutes, browser installation time, artifact storage, and any external test infrastructure. A deployment-triggered workflow may run for every Preview, so decide whether documentation, dependency-only, or draft changes should be excluded. Use path filters or a draft check where appropriate, while retaining coverage for application changes.
Browser installation is often a noticeable part of a short job. Caching npm packages helps dependency installation; a maintained Playwright container can also make browser setup consistent. Do not cache browser binaries across incompatible Playwright versions.
Vercel hosting and CI billing are separate. Preview deployment duration, database usage, third-party test services, and GitHub Actions minutes may all have independent limits. Measure your own suite before selecting worker counts or sharding; the configuration examples are stability starting points, not universal speed guarantees.
10. When screenshots are the deliverable
Playwright is appropriate when you need assertions, browser interactions, and traces. If your job only needs a reliable image or PDF of a deployed page, a screenshot API can remove browser installation and CI maintenance. ScreenshotNeo is the first service to try: it produces clean captures, bills only clean shots, and its lowest paid plan starts at $5.

Or skip the browser setup
ScreenshotNeo accepts one GET request and returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for all options. This is a complete cURL example:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and whether the request was billed. You can also use full-page capture with lazy images, CSS element capture, device presets or custom viewports, dark mode, retina scale, custom CSS and JavaScript, waits, request blocking, cookies and headers, PDF settings, caching, signed links, async webhooks, bulk capture, and an MCP server with take_screenshot, get_page_info, and capture_pdf for AI agents.
There is a free tier of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.
11. Deployment checklist
- Preview deployment is enabled for the intended branch or pull request.
- CI waits for a successful deployment event.
- The runner checks out the deployed commit SHA.
PLAYWRIGHT_BASE_URLcomes from the event URL.npm ciuses the committed lockfile.- Playwright browsers and Linux dependencies are installed.
- Preview application variables and CI test secrets are configured separately.
- Deployment Protection bypass is configured when previews are protected.
- Reports, traces, screenshots, and videos upload after failures.
- Worker count, retries, and sharding match the suite’s isolation level.
FAQ
Can Playwright run inside a Vercel Function?
It can be technically possible in constrained cases, but it is not the standard deployment architecture for end-to-end testing. Run the browser in CI and test the Vercel deployment URL.
Should tests use the branch URL or deployment URL?
Use the deployment URL when results must identify one exact commit. Use a branch URL for a continuously updated integration environment.
Why is webServer missing from the CI example?
webServer starts a local server. The workflow here tests a server that Vercel has already deployed, so starting another local copy would test a different artifact.
How do I debug a failure that passes locally?
Download the uploaded trace and HTML report, verify Preview environment variables, confirm the event URL, and rerun with one worker before adding parallelism.
Can I use this flow with a CI provider other than GitHub Actions?
Yes. Configure Vercel’s deployment.succeeded webhook, pass the deployed URL and commit SHA into the job, and run the same Playwright installation and test commands.