How to Automate UI Testing from Scratch
Build a dependable browser test with Playwright, then run it in CI. Learn how to choose a journey, write useful assertions, troubleshoot failures, and expand safely.
To automate UI testing from scratch, choose one important user journey, write a browser test that performs a real user action and checks the visible outcome, run it locally, then run the same test in CI. This guide uses Playwright with TypeScript and Node.js. It also explains how to choose a test, avoid brittle waits and selectors, diagnose failures, and decide what to automate next.
A useful first test might verify that a visitor can sign in and reach an account page, or complete a purchase and see a confirmation. Keep the first test focused: confidence comes from checking an outcome that matters, not from maximizing test count.
1. Choose the right first UI test
Pick a journey whose failure would matter to a user. Write down the starting state, the action, and what the user should see at the end.
| Part | Example |
|---|---|
| Starting state | The sign-in page is available and the test account is ready. |
| User action | Enter credentials and submit the form. |
| Visible outcome | The account page heading appears. |
Prefer a critical end-to-end journey when the behavior depends on several integrated parts of the application. Use a unit, API, or component test when that gives enough confidence with less setup and maintenance. Accessibility checks can complement those test types; they do not replace checking a user journey.
2. Install Playwright and create a test
For a Node.js project, follow the current Playwright installation guide for your package manager and operating system. The commands below use npm; browser installation details can vary by environment.
npm init playwright@latest
Choose TypeScript when prompted if you want to use the example as written. The setup creates a Playwright configuration and example tests. You can keep the generated structure or adapt it to the project conventions. A minimal test file, such as tests/ui.spec.ts, can look like this:
import { test, expect } from '@playwright/test';
test('visitor can open the installation guide', async ({ page }) => {
await page.goto('https://playwright.dev/');
await page.getByRole('link', { name: 'Get started' }).click();
await expect(
page.getByRole('heading', { name: 'Installation' })
).toBeVisible();
});
This is Playwright’s documented starter pattern, not a claim that it has been independently run here. Replace the example URL and expected heading with your own application and user journey. For the sign-in example, the test should use a controlled test account and assert the resulting account-page state.
The test has three useful parts:
- Navigate:
page.goto()opens the page under test. - Act: a role and accessible name identify the link a user would recognize.
- Assert: a web-first expectation checks that the resulting heading becomes visible.
3. Write selectors and assertions that survive change
Prefer locators based on accessible roles and names, labels, or text that corresponds to what users see. Playwright’s best-practices guide recommends targeting the rendered experience users interact with. If the interface has no suitable accessible locator, add a deliberate test identifier rather than relying on a long CSS path tied to layout details.
Good assertions state the outcome that matters: a confirmation message is visible, a dialog closes, or a heading appears. Avoid assertions that only prove an implementation detail, such as the existence of an internal wrapper element, unless that detail itself is the requirement.
Playwright’s locator actions perform actionability checks before interacting, and web-first assertions retry while waiting for the expected state. For example, toBeVisible() waits for visibility within the assertion timeout. That makes state-based synchronization more reliable than inserting a fixed delay after every click.
4. Run the test locally
Run the generated suite with the project’s Playwright command:
npx playwright test
To run a single test file, pass its path:
npx playwright test tests/ui.spec.ts
Use the runner’s available reporting and debugging options when diagnosing a failure. Check the current Playwright test-running guide for supported commands and flags. Fix the underlying selector, data, environment, or application behavior before treating a rerun as evidence of success.
5. Add the same test to CI
CI should use a reproducible sequence: install the project’s dependencies, install the browser binaries and any required system dependencies, then run the suite. The Playwright CI guide documents this setup and recommends starting with one worker for stability and reproducibility.
npm ci
npx playwright install --with-deps
npx playwright test
Use the package manager and lockfile for your repository. Check the official Playwright CI guide for platform-specific details, especially if the CI image or operating system differs from local development. Pinning the project dependencies through its lockfile helps keep local and CI installs aligned.
Begin with one CI worker. Consider parallel workers or sharding across CI jobs when execution time warrants it and the available machines can support the additional browser processes. Parallel runs can expose shared-state conflicts, such as tests competing for the same account or mutable records, so isolate test data before increasing concurrency.
6. Keep the suite dependable as it grows
- Add tests according to user and business risk, not page count.
- Give each test the data and state it needs; avoid order-dependent tests.
- Keep test accounts and external services controlled where possible.
- Capture useful failure diagnostics and review recurring failures instead of repeatedly rerunning until green.
- Prefer a small, trusted suite over a large suite that nobody believes.
End-to-end tests cover integrated journeys, but they also depend on application, backend, browser, and CI setup. Keep lower-level checks for logic that does not need a real browser. This makes the browser suite more focused and easier to maintain.
7. Choose between Playwright and Cypress
Playwright and Cypress are both credible choices. This research supports a practical Playwright starter and identifies Cypress as an alternative with an interactive local workflow; it does not establish a universal winner. Compare them against your project’s browser and operating-system needs, team language and skills, synchronization model, CI resources, debugging workflow, and reporting requirements.
| Decision | Questions to check |
|---|---|
| Browser and runtime | Which browsers and operating systems do local development and CI require? Check current official support for the versions you use. |
| Authoring model | Does the team prefer Playwright’s async/await style and integrated runner, or Cypress’s command-chaining and interactive local workflow? |
| Locators and waiting | Can tests target accessible, user-visible controls and wait for application state? |
| CI setup | Can your CI environment install browsers and dependencies? Will you need multiple workers or sharded jobs? |
| Debugging and reporting | Are local debugging and failure artifacts sufficient, or do you need additional team reporting capabilities? |
| Existing stack | Which framework, language, test skills, and CI constraints already exist in the project? |
Cypress describes end-to-end, component, API, and accessibility testing as serving different purposes, with accessibility checks able to complement other test types. Choose the level that gives the required confidence for each risk. Review the current Cypress overview and testing types guide alongside the relevant Playwright documentation before deciding.
Or skip the browser setup
If the task is to capture a webpage for a visual check, report, or agent workflow, ScreenshotNeo provides a website screenshot API and MCP server. One GET request can return a PNG, JPEG, WebP, or PDF. The API call does not replace interactive UI tests: it captures a page rather than exercising a full user journey.
cURL:
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}`);
See the ScreenshotNeo API documentation for request options and response details. Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.
Troubleshooting common problems
| Symptom | Likely cause | What to do |
|---|---|---|
| Browser executable is missing | The browser binaries were not installed in this environment, or the installed version does not match the project. | Run the documented Playwright browser installation command for the project and CI image. Check the official CI guide for system dependencies. |
| Test times out waiting for a locator | The expected element did not appear, the locator does not match the rendered page, or the app is in an unexpected state. | Inspect the page and failure diagnostics. Confirm the accessible role/name, test data, route, and expected state. Avoid extending timeouts before understanding the cause. |
| Click fails because the element is not actionable | The target may be covered, disabled, moving, or not yet ready for interaction. | Check the visible page state and whether a dialog, overlay, or loading state blocks the control. Wait for the meaningful state rather than forcing a click. |
| Test passes locally but fails in CI | CI may have different dependencies, browser setup, environment variables, test data, resource limits, or timing. | Use a clean install and matching browser setup, inspect CI artifacts, and reproduce the CI environment where possible. Start with one worker. |
| Tests fail only when run together | They may share mutable accounts or records, depend on execution order, or compete for limited resources. | Isolate test data and remove order dependencies. Increase parallelism only after the tests can run independently. |
| Adding a sleep seems to fix a flaky test | The delay hides a race without proving the needed condition occurred. | Replace it with a locator assertion or wait for the specific application state that makes the next action valid. |
| Selector breaks after a visual redesign | The test relies on fragile structure or styling classes. | Use a role, label, or user-visible name when appropriate. Add a deliberate test identifier if no user-facing locator suits the control. |
Performance, reliability, and cost
Browser tests are more resource-intensive than checks that do not launch a browser, so run them where they add meaningful integration coverage. A focused suite and one CI worker are a sensible starting point. More workers or sharded jobs can reduce elapsed time when the CI capacity and test isolation support them, but they also require more resources and can reveal data collisions.
Reliability depends on meaningful assertions, stable test data, controlled dependencies, and state-based waits. A test that checks the wrong outcome can be consistently green and still provide little confidence. Treat repeated failures as information about the test or environment, not as a reason to normalize reruns.
Costs include developer time to maintain tests, CI compute and browser execution, and any optional reporting or hosted services the team chooses. The research does not establish comparative prices or performance benchmarks for Playwright and Cypress, so compare current provider terms directly if those costs affect the decision.
FAQ
Do I need to automate every page?
No. Start with a few important journeys and use other test levels for behavior that does not need a browser.
Should I use fixed waits in UI tests?
Usually not. Prefer actions and assertions that wait for the state the test actually needs.
Can a screenshot API replace UI automation?
No. A screenshot API captures a rendered page; a UI test interacts with the application and verifies behavior. Use each for the job it covers.
When should I add parallel CI workers?
After the suite is stable and its tests have isolated data. Then decide whether the extra CI capacity is worth the shorter run time.


