How to Get Started with Automated Web Testing in CI
Start browser testing in CI with one reliable user journey. Choose a framework, wait for your app, run the test, and keep failure reports easy to find.
To get started with automated web testing in continuous integration (CI), choose a browser framework that fits your project, write one test for an important user journey, then run it on pushes or pull requests after the app and browser are ready. Keep the first run small and make its logs and report available when it fails.
CI runs automated checks as code changes are integrated. A browser test can make the workflow fail when a user journey breaks. You do not need to automate the whole site before getting value from the first test.
1. Choose a framework that fits your project
Use the language and test setup your team already knows where possible. There is no universal framework winner in the available documentation; compare setup, browser coverage, CI environment, diagnostics, and whether remote execution matters.
| Framework | Consider it when | Setup points |
|---|---|---|
| Playwright | You want its JavaScript test runner and a documented CI workflow. | The CI job installs project dependencies and browser dependencies before running tests. Its CI guide recommends starting with one worker for stability and reproducibility. |
| Cypress | You want Cypress’s maintained GitHub Action and its build/start/wait workflow options. | The action can install Cypress and can run configured build and start commands. Its documentation warns that starting a server and immediately launching tests can race. |
| Selenium | Your team’s language bindings and WebDriver approach are a good fit, or remote execution across machines is important. | Plan for language bindings, a browser, and a driver. Selenium Grid provides a route to remote browser execution. |
Check the current framework documentation before implementing: runner images, action versions, browser builds, and commands can change. GitHub Actions is only an example; Playwright and Cypress tests can run in other CI providers too.
2. Write one useful browser test
Choose a high-value, observable journey: for example, submitting a form and checking that a confirmation appears. Keep the first test focused on what a user can see or do, rather than internal implementation details.
Use an approved test environment and test data. Avoid depending on production credentials; store any required secrets in your CI provider’s secret management facility and follow your team’s access policies.
Example test with Playwright
The following JavaScript test assumes the test environment serves a page with a link named “Sign in,” a heading named “Sign in,” and a button named “Continue.” Adjust the URL and accessible names to match your application. It checks navigation and a visible result; it does not assume a particular authentication backend.
import { test, expect } from '@playwright/test';
test('sign-in page shows its primary action', async ({ page }) => {
await page.goto('http://127.0.0.1:3000');
await page.getByRole('link', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Sign in' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible();
});
Install the project’s Playwright package and configure its test runner in the repository. The commands below show the CI sequence for a JavaScript project with a lockfile and a configured Playwright test suite.
3. Run the test in GitHub Actions
This workflow runs on pushes and pull requests, installs dependencies and Playwright’s browser dependencies, builds the app, starts it as a managed web server, runs the test with one worker, and uploads the HTML report even when the test fails. Replace the build command and app URL if your project differs. Playwright’s webServer setting waits for the readiness URL instead of relying on a fixed sleep.
# .github/workflows/e2e.yml
name: browser-tests
on:
push:
pull_request:
jobs:
playwright:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build
- run: npx playwright test --workers=1
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
if-no-files-found: ignore
retention-days: 14
For the workflow’s webServer wait to work, configure the project’s Playwright settings, for example:
// playwright.config.js
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
reporter: [['html', { open: 'never' }]],
workers: process.env.CI ? 1 : undefined,
use: {
baseURL: 'http://127.0.0.1:3000',
trace: 'on-first-retry',
},
webServer: {
command: 'npm run start -- --host 127.0.0.1',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
Use a start command that binds to the address reachable by the runner and a readiness URL that returns successfully only when the app is usable. If your app requires a separate database or service, provision it before the browser test. Playwright’s official [CI guide](https://playwright.dev/docs/ci) documents its GitHub Actions sequence and report artifact example.
Cypress alternative
Cypress documents a maintained GitHub Action with build, start, and wait-on options. A minimal job has the following shape; set the action inputs and application commands to match the current Cypress documentation and your repository.
name: cypress
on:
push:
pull_request:
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- uses: cypress-io/github-action@v7
with:
build: npm run build
start: npm run start
wait-on: 'http://127.0.0.1:3000
Verify the action version and supported inputs when you add the workflow. See [Cypress’s GitHub Actions guide](https://docs.cypress.io/app/continuous-integration/github-actions) and [CI overview](https://docs.cypress.io/app/continuous-integration/overview) for current setup, caching, runner, and readiness guidance.
Selenium route
Selenium’s setup depends on the language binding, browser, and driver you select, so there is no single cross-language runnable workflow to copy here. Follow the official [Selenium getting started guide](https://www.selenium.dev/documentation/en/selenium_installation/) for those prerequisites. If the browser should run on another machine or in a distributed environment, see [Selenium Grid getting started](https://www.selenium.dev/documentation/grid/getting_started/).
4. Make the CI run reliable and diagnosable
- Wait for readiness: Starting a server process does not prove the app can serve requests. Use a URL check or framework readiness option, not an arbitrary sleep.
- Start with one worker: Playwright recommends one worker in CI as a stability and reproducibility starting point. Increase concurrency only after the run is dependable and the runner has capacity.
- Keep the first suite small: One important journey makes failures easier to understand. Add additional critical flows once the first test is repeatable.
- Retain useful output: Upload an HTML report or preserve framework logs and failure artifacts so developers can inspect the failed step. Set retention according to team policy.
- Control the browser environment when needed: Cypress documents Docker images as a way to constrain browser versions; Playwright documents browser installation and a Linux image. These can improve repeatability when environment drift is a problem.
- Expand coverage deliberately: Add browser or operating-system combinations and parallel runs when they answer a real coverage need. Selenium Grid is an option for remote execution across machines.
5. Know what affects runtime and cost
CI runtime depends on the app build, browser and operating-system setup, test count, and available runner capacity. The reviewed framework documentation does not establish comparable speed, flakiness, or cost figures, so choose based on your project and measure your own workflow.
Browser installation and application startup add work to a job; dependency caching and a stable runner image may help, but cache correctness matters. Begin with a serial run that is easy to diagnose. If it becomes too slow, consider splitting independent tests or adding workers after confirming that tests do not share mutable state and that the runner can support the extra browsers.
CI billing depends on your CI provider and runner configuration, which are outside the cited framework setup guidance. Check your provider’s current usage and retention policies rather than assuming browser tests have a fixed cost.
Or skip the browser setup
If you need a visual capture of a page as part of review or an AI-assisted workflow, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF; see the API documentation for options. For example, this cURL request saves a WebP screenshot:
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,
)
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 request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));
ScreenshotNeo accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Every feature is on every plan.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
Troubleshooting CI browser tests
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable or shared library missing | The job installed the test package but not its browser or operating-system dependencies. | For Playwright, run npx playwright install --with-deps on the Linux runner. Follow Cypress or Selenium’s runner-specific setup for their environments. |
| Connection refused or navigation times out at test start | The app has not become ready, is bound to another interface or port, or failed during startup. | Check startup logs and the configured URL. Bind the server so the job can reach it and use a readiness check such as Playwright’s webServer or Cypress’s wait-on. |
| Test passes locally but fails in CI | The CI environment may have different browser dependencies, timing, data, environment variables, or resource limits. | Inspect the report and logs, make test data deterministic, install the expected browser dependencies, and use the same target environment assumptions. Do not hide a race with a longer fixed delay. |
| Report is missing after failure | The upload step did not run on a failed job, or the report path does not match the reporter output. | Run the upload step with an always condition, confirm the configured reporter and path, and allow missing files while diagnosing setup failures. |
| Runs are flaky or compete with each other | Parallel tests may share data or exceed runner capacity. | Start with one worker, isolate test data, and raise concurrency only after checking resource use and state sharing. |
| Browser or runner updates change behavior | The environment is not pinned or has changed since the last successful run. | Use documented installation or container options to control versions where needed, and update deliberately after checking current framework guidance. |
Frequently asked questions
Do I need to test every browser on the first CI run?
No. Begin with the browser and journey that matter most to the project. Add combinations when they address a defined compatibility need.
Can browser tests run against a deployed test environment?
Yes. The application can be started in the job or already deployed. Configure the test base URL and make sure the target environment is available and contains suitable test data.
Should I use browser tests for every check?
Use them for user-visible journeys that need a real browser. Keep the first CI browser suite focused; the framework setup sources do not prescribe a universal testing mix.


