ScreenshotNeo

BlogHow-to

How to Get Started With Automated Browser Testing

Choose a browser testing framework, write a reliable first end-to-end test, and run it locally and in CI with practical Playwright, Selenium, and Cypress guidance.

By the ScreenshotNeo team4 October 202610 min read

To get started with automated browser testing, choose a framework that fits your language, browser targets, and team; install its runner and browser dependencies; then automate one important user journey and run it repeatedly, ideally in CI. For a JavaScript or TypeScript project, Playwright Test is a practical starting point when its integrated runner and Chromium, Firefox, and WebKit support fit your needs. Consider Selenium when language-neutral WebDriver support or an existing Selenium setup matters, and Cypress for its JavaScript-oriented end-to-end workflow. No framework is best for every team.

This guide walks through the first test with Playwright, then explains how to make it dependable and when the other options fit. It assumes you can run your web app and have a test environment available.

1. Choose a framework for your project

Before installing anything, write down the project language, browsers your users rely on, whether tests need to run against your app in CI, and whether your team already uses a framework. Those constraints are more useful than a universal ranking.

Framework Consider it when Setup and browser notes
Playwright Test Your project is JavaScript or TypeScript and an integrated runner plus multi-engine testing suits your workflow. Install the package and use its CLI to install version-matched browser binaries. The reviewed docs cover Chromium, Firefox, and WebKit.
Selenium WebDriver You need language-neutral WebDriver support, broad browser or platform reach, or already have Selenium tests. Install a language binding and browser. Selenium Manager handles driver management in supported bindings. Selenium IDE is an optional record-and-playback entry point; Selenium Grid supports distributed execution.
Cypress Your team wants its JavaScript-oriented end-to-end runner workflow. Configure the app server and run in a supported browser. The reviewed guide lists Chrome-family browsers and Firefox, with WebKit experimental; Chrome for Testing is an option for a pinned Chrome binary.

Browser support and setup details can change. Check the current framework documentation before pinning a CI image or choosing a browser matrix: Playwright browser installation, Selenium documentation, and Cypress browser support.

2. Install Playwright and its browser

For a Node.js project, install Playwright Test as a development dependency, then install the browser you want to run. The examples below use Chromium as a small first setup.

npm init playwright@latest

The setup command can create starter files. If you already have a Node project, add the package and install Chromium explicitly:

npm install --save-dev @playwright/test
npx playwright install chromium

In a Linux CI environment, install the browser’s system dependencies too:

npx playwright install --with-deps chromium

Playwright browser binaries are tied to Playwright versions. Keep the dependency lockfile, and rerun the browser installer after updating Playwright. Install only browsers your suite currently runs; add others when you intentionally expand coverage.

3. Run a first end-to-end test

Pick a journey that matters to users and has a deterministic test environment, such as signing in with a test account and seeing the account page. Use a visible name or role to find controls, perform the actions a person would, and assert the user-visible result.

Save this as tests/sign-in.spec.js. It uses the example app URL http://localhost:3000; change the URL and accessible names to match your application. The account and password should be dedicated test credentials, not production credentials.

const { test, expect } = require('@playwright/test');

test('a user can sign in', async ({ page }) => {
  await page.goto('http://localhost:3000/sign-in');

  await page.getByRole('textbox', { name: 'Email' }).fill('qa@example.test');
  await page.getByLabel('Password').fill('test-password');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page.getByRole('heading', { name: 'Your account' })).toBeVisible();
});

Start the app in one terminal, then run the test in another:

npx playwright test tests/sign-in.spec.js --project=chromium

If your project uses TypeScript, save the test as tests/sign-in.spec.ts; the test body is the same. If the generated Playwright configuration does not define a Chromium project, either add one or omit --project=chromium and run the configured project.

Configure the app server and browser project

A simple playwright.config.js can start the local app automatically and define a Chromium project:

// @ts-check
const { defineConfig, devices } = require('@playwright/test');

module.exports = defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  reporter: process.env.CI ? 'github' : 'list',
  use: {
    baseURL: 'http://127.0.0.1:3000',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
  webServer: {
    command: 'npm run start -- --host 127.0.0.1',
    url: 'http://127.0.0.1:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

Adjust the start command, URL, and device preset to your app. With baseURL configured, the test can use await page.goto('/sign-in'). The server timeout is for startup; it is not a reason to insert fixed sleeps into the test.

4. Make the test reliable

Use stable, user-facing selectors

Prefer accessible roles and names such as getByRole('button', { name: 'Save' }) or labels such as getByLabel('Email'). If an element has no accessible contract and you need a selector tied to the test, ask the app team to add an explicit test identifier. Avoid selectors that depend on incidental CSS classes, element nesting, or private implementation details. Playwright’s guidance says tests should verify behavior for end users and avoid relying on implementation details: Playwright best practices.

Wait for a meaningful condition

Playwright locators wait for relevant actionability conditions and its assertions retry. Prefer asserting that the resulting heading, confirmation, or updated state appears over sleeping for an arbitrary number of milliseconds.

// Prefer a condition that describes the expected result:
await expect(page.getByText('Changes saved')).toBeVisible();

// Avoid using a fixed delay as a substitute for knowing the result:
// await page.waitForTimeout(5000);

Use a specific navigation or response wait only when that event is part of the behavior being tested. A page can update without a full navigation, so asserting on visible application state is often the more direct check.

Isolate tests and test data

A test should not pass only because a previous test logged in, set a cookie, or created a record. Give each test the relevant storage, session, and data setup it needs. Reset or uniquely name mutable records, and avoid tests sharing an account or resource that they modify concurrently. Browser-context isolation helps separate browser state, but it does not reset server-side data for you.

Assert outcomes users can observe

Check the result that matters: a confirmation, a visible error, a changed status, or a completed flow. Avoid asserting internal function names, array structure, or CSS classes unless those are directly part of the contract under test.

5. Run it in continuous integration

Once the test is repeatable locally, run it on commits or pull requests. Keep the CI browser and dependency versions controlled with the lockfile and the matching Playwright browser install. A basic CI sequence is:

npm ci
npx playwright install --with-deps chromium
npm run build
npm run start -- --host 127.0.0.1 &
npx playwright test --project=chromium

In practice, configure the app server in Playwright’s webServer setting so the runner can manage startup and wait for the configured URL, as in the config above. Adapt build and start commands to the project. Preserve Playwright traces or other available diagnostics for failed runs; the sample config records a trace on the first retry.

Begin with one browser. Add Firefox, WebKit, or other browser profiles when user needs or known risks justify the extra runs. If suite runtime becomes a problem, first check whether tests are independent and reliable; then consider parallel workers or sharding. Playwright documents CI and sharding guidance.

6. When to consider Selenium or Cypress

Selenium WebDriver

Selenium uses language bindings to control browsers through WebDriver, with a browser and driver as parts of the setup. Selenium Manager can manage drivers for supported bindings. Selenium IDE offers a low-code record/playback route, and Selenium Grid is available when distributed execution is needed. Selenium’s project guidance is explicit that “No one approach works for all situations.” See Selenium getting started and Selenium test practices.

Cypress

Follow the Cypress E2E setup for your app server and chosen browser. Its browser guide covers the supported browser families and Chrome for Testing; WebKit is described as experimental in the reviewed guidance. See Cypress E2E testing.

For either tool, apply the same test design basics: independent setup, selectors tied to user-visible behavior, explicit outcomes, and controlled test data.

7. Screenshot a page without running a browser test

Browser tests answer whether an interaction and its outcome work. If you only need a rendered page image for a visual review or record, a screenshot API can capture it without writing and maintaining a browser script. ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its options include full-page capture, element capture, device presets, custom CSS and JavaScript, and waiting for a selector, delay, or network idle; see the ScreenshotNeo API documentation.

Or skip the browser setup:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo accepts cookie and consent banners like 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, blank pages, and failed loads are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, no card required.

8. Troubleshooting common first-test failures

Symptom Likely cause Fix
Browser executable is missing The Playwright package is installed, but its matching browser binary is not. Run npx playwright install chromium, or install all configured browsers. In Linux CI, use npx playwright install --with-deps chromium.
Browser installation and package versions drift The Playwright package was updated without installing its matching browser binaries. After updating the dependency, rerun the Playwright browser installation and keep the lockfile in sync.
Navigation fails or times out The app is not running, the URL or port is wrong, or the server has not become ready. Open the URL locally, check the start command and host binding, and configure webServer with the app’s actual URL.
Locator resolves to no element The page differs from the assumed state, the accessible name differs, or the selector targets implementation markup. Inspect the page and accessible name, then use the actual role and label. Make test setup reach the expected state.
Click is intercepted or times out An overlay is present, the control is disabled, or the page has not reached the expected state. Assert or wait for the relevant visible state and resolve the overlay or disabled condition. Avoid forcing a click to conceal a real interaction problem.
Test passes alone but fails in the suite Tests share cookies, storage, accounts, or mutable server data. Make prerequisites explicit, isolate browser state, and reset or uniquely allocate records that tests change.
CI fails but local runs pass Browser or system dependencies differ, the app startup differs, or timing and shared data make the test flaky. Pin dependencies, install the matching browser and system packages, use the same start path, and capture traces to inspect failures.
Suite is slow or resource-heavy Too many browsers are running before coverage needs them, or tests are serialized or share state. Start with the required browser set, keep tests independent, and add parallelism or sharding only when runtime warrants it.

9. Performance, reliability, and cost

  • Keep the first CI matrix small. Browser binaries and execution time both add setup and runtime cost. Start with the browser users need most, then expand intentionally.
  • Stabilize before scaling. Parallel workers and sharding can shorten elapsed time, but shared accounts or records can create contention. Isolate data first.
  • Make failures diagnosable. Keep traces, screenshots, or video where the framework provides them. A useful artifact can show whether a failure came from app behavior, environment setup, or test assumptions.
  • Control version drift. Dependency lockfiles and matching browser binaries make local and CI runs more comparable. Revisit framework and browser versions regularly.
  • Spend browser-test effort on user behavior. Use end-to-end tests for critical journeys and regressions that need a real browser. Lower-level tests can cover logic that does not need the browser.

10. A first-week checklist

  1. Choose a framework based on project language, browser needs, and existing team setup.
  2. Pin the framework dependency and install the browser binaries needed for the first run.
  3. Pick one deterministic, important user journey and document its test data prerequisites.
  4. Use accessible names or explicit test contracts for selectors.
  5. Assert a visible result and remove arbitrary waits.
  6. Run locally, then add the same command to CI with matching dependencies.
  7. Save useful failure diagnostics and fix isolation problems before adding more tests.
  8. Expand browsers and parallel execution when user coverage and runtime justify them.

Frequently asked questions

Should a first browser test cover the whole application?

No. Begin with one high-value journey whose expected result and test data are clear. Add coverage as you learn where browser-level checks catch problems that other tests miss.

Can I use a screenshot as an end-to-end test?

A screenshot can help review rendered output, but an image capture alone does not verify that a user can complete an interaction or that the application reaches the correct state. Use a browser test for that behavior.

Do I need to test every browser on every change?

Not necessarily. Start with the browser coverage your users and risks require, then add engines deliberately. Browser support and CI cost depend on the framework and environment you choose.

Is a recorded test ready for CI?

Review its selectors, assertions, data setup, and isolation first. Recording can capture actions, but the test still needs to express a meaningful outcome and run independently.