Automation Testing: A Beginner’s Tutorial
Learn what to automate, choose a browser testing framework, write your first Playwright test, and run it in CI.
Automation testing means using code or tools to check whether software behaves as expected. For a beginner, the practical path is to learn a few testing basics, choose one framework that fits your application and team, automate one stable user-visible behavior, run it repeatedly, and then put it in continuous integration (CI).
This tutorial uses Playwright for a small browser test and GitHub Actions for CI. The example checks that a page displays a heading. Replace the example URL and expected text with a page and behavior you control. Browser automation is only one layer of testing: if a unit test can adequately check a requirement, it is usually simpler to start there. Browser tests exercise more of the application from a user’s perspective, but they cost more to run and maintain. Selenium’s test automation guidance recommends keeping browser tests short and using the browser only when needed.
1. Learn the shape of a test
A useful first test has three parts:
- Setup: establish the state the test needs, such as opening a page with known content.
- Action: perform a small number of user-like steps, such as clicking a button or entering a value.
- Evaluation: assert the outcome that matters, such as a confirmation message appearing.
Start with one requirement that a user would notice if it broke. For example: “When a visitor opens the help page, the page shows the heading ‘Help center’.” Avoid building a large end-to-end journey as your first test. Short tests are easier to diagnose when they fail.
Before writing browser automation, ask whether the requirement truly needs a browser. A function’s calculation may be covered more directly by a unit test. A browser test is useful when the requirement depends on the rendered page or on interactions among browser, application, and server.
2. Choose one tool that fits your situation
There is no universal best framework. Compare the language your team already uses, the browsers and application you need to cover, how readable the tests should be, the setup your environment can support, and how you plan to run tests in CI.
| Tool | What its official documentation establishes | Consider it when |
|---|---|---|
| Selenium | WebDriver drives browsers; Selenium Manager handles browser and driver management by default; Grid supports parallel runs across machines; Selenium IDE records and plays back actions. | You need its browser and platform coverage, use a language supported by your team, or need distributed execution. Account for browser infrastructure and execution costs. |
| Robot Framework | Test cases use plain-text keyword sequences, with browser and API libraries and starter tutorials. | Readable keyword-driven organization fits your team and the available libraries cover your application. |
| Playwright | Its documentation includes browser installation, test execution, and CI workflows, including GitHub Actions. | The language and browser support fit your project and you want a documented path into CI. |
If you are learning for a current job or project, the existing team stack is a sensible starting point. Pick one framework, get a small test working, and learn its conventions before comparing more tools. The steps below assume Node.js and Playwright.
3. Install Playwright and write a first test
Use a current Node.js installation. In a new project directory, initialize a package and install Playwright Test:
npm init -y
npm install --save-dev @playwright/test
npx playwright install
Create tests/home.spec.js:
const { test, expect } = require('@playwright/test');
test('home page shows its heading', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.getByRole('heading', { name: 'Example Domain' })).toBeVisible();
});
Run it:
npx playwright test
The example uses a role and accessible name to identify a heading. For your own app, prefer locators tied to user-visible roles and labels where they are available. If those do not express the target reliably, use a stable application-owned test identifier. Avoid selectors coupled to incidental layout or styling, since redesigns can break them without changing the user behavior.
Playwright assertions such as toBeVisible() wait for the condition to become true within the assertion timeout. This is more reliable than inserting a fixed sleep as a general remedy. Add a wait for a specific state only when the test has a real synchronization need.
Adapt the example to a user action
For a form, test a small, meaningful flow: open a known page, fill a labeled field, submit, and assert the visible result. Use a test account or controlled test data; do not make a beginner test depend on production records that change outside the test.
const { test, expect } = require('@playwright/test');
test('search displays matching results', async ({ page }) => {
await page.goto('https://your-test-site.example/search');
await page.getByRole('textbox', { name: 'Search' }).fill('billing');
await page.getByRole('button', { name: 'Search' }).click();
await expect(page.getByRole('heading', { name: 'Billing help' })).toBeVisible();
});
Replace the domain, labels, and expected result with elements and content from your test application. If the page requires authentication, use a dedicated test account and follow your framework’s recommended approach for storing credentials securely in CI secrets.
4. Make the test repeatable
A passing test once is a start, not proof that it is reliable. Run it again locally and look at both passing and failing behavior. Keep the starting state predictable and the test independent from unrelated records or other tests.
- Use a controlled test environment and data that the test can create or reset.
- Keep each test focused on one behavior, with a short sequence of actions.
- Use stable, user-facing locators where possible.
- Wait for a meaningful state or assertion instead of sleeping for an arbitrary duration.
- When a test fails, inspect the error and browser output, then determine whether the app, test data, locator, or timing assumption changed.
Do not make every failure pass by increasing timeouts. A timeout may reveal that the page never reached the expected state, the test targeted the wrong element, the environment is slow, or a dependency failed. Fix the underlying cause when possible.
5. Run the test in GitHub Actions
Once the test runs predictably on a developer machine, add a workflow so it runs on pushes and pull requests. Create .github/workflows/playwright.yml:
name: Playwright tests
on:
push:
pull_request:
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
This follows the core install-and-run approach in Playwright’s CI documentation. The GitHub Actions quickstart explains workflows triggered by events such as pushes. Commit the lockfile so npm ci installs the dependency versions recorded by the project.
Playwright recommends using one worker in CI initially to prioritize stability and reproducibility. Once the suite is stable, you can explore parallel execution or sharding across jobs to reduce elapsed time. More workers can also increase machine and service load, so scale based on observed needs rather than adding concurrency first.
6. What to automate first
Choose tests by the consequence of a failure and how clearly the expected result can be checked. A small smoke test for a critical page or flow can be more useful than a broad script that is difficult to maintain.
- Start with a stable, user-critical behavior that can be checked from a clear result.
- Prefer flows that need browser rendering or interaction to verify.
- Keep calculations and isolated logic in lighter test layers when those layers cover the requirement adequately.
- Defer flows that depend on unstable third-party services, changing production data, or difficult-to-control state until you have a test environment strategy.
Automation does not repair an unclear requirement or a weak test strategy. Decide what behavior matters, what state the test needs, and what observable result counts as success before choosing selectors or adding steps.
7. Troubleshooting common failures
| Symptom | Likely cause | What to do |
|---|---|---|
npx playwright cannot find a browser |
The browser binaries have not been installed in this environment. | Run npx playwright install locally or npx playwright install --with-deps in the documented Linux CI setup. |
| Locator times out | The expected element is absent, its accessible name differs, navigation did not finish as expected, or the app is slower than the assumed condition. | Check the page and locator, verify the test data and route, and wait for the actual expected state. Increase a timeout only when the longer wait reflects a real environment requirement. |
| Test passes locally but fails in CI | Different dependencies, missing browser/system dependencies, environment configuration, test data, or resource limits. | Use the lockfile with npm ci, install browsers and dependencies in CI, inspect the failure output, and make required environment values available through CI configuration. |
| Test is flaky | Shared mutable data, order dependence, unstable selectors, arbitrary sleeps, or external service variability. | Isolate test data, remove hidden dependencies between tests, use stable locators and condition-based assertions, and control or replace unstable dependencies where practical. |
| Click or fill targets the wrong element | A selector matches more than one element or describes implementation details that changed. | Use a more specific role, label, or stable test identifier, and assert that the intended element is unique or visible before continuing. |
| Workflow cannot install dependencies | The dependency manifest and lockfile are out of sync, or the workflow is running from the wrong directory. | Regenerate and commit the lockfile with the package manifest, and set the workflow working directory to the application package when it is not at the repository root. |
8. Performance, reliability, and cost
Browser tests start real browser processes and exercise more of the stack than unit tests, so they generally require more execution time and infrastructure. Keep the browser suite focused on behavior that benefits from browser coverage and let faster test layers cover requirements they can check adequately.
For reliability, keep browser scenarios short, control their data, and run them in the same basic way locally and in CI. In CI, begin with one worker as Playwright advises; parallelization and sharding are later options when the suite and environment can support them. There is no universal runtime or cost figure for this tutorial: it depends on suite size, browser choice, CI resources, and application behavior.
Test automation also carries maintenance cost. A test that encodes visual details or unstable data can create repair work without protecting an important behavior. Review whether each test still checks a real requirement as the application changes.
Or skip the browser setup
If your task is to capture a website screenshot for documentation, visual review, or an agent workflow, ScreenshotNeo provides a screenshot API and MCP server. It does not replace interactive functional tests like the Playwright example; it handles website capture.
One GET request returns an image or PDF. See the ScreenshotNeo API documentation for options and details.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await require('node:fs/promises').writeFile('shot.webp', image);
ScreenshotNeo accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools named take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card.
FAQ
Do I need to learn programming before automation testing?
Some basic programming makes it easier to write, adapt, and debug tests. You can start with a small framework example while learning the language used by your project.
Should I automate every manual test?
No. Prioritize behaviors that matter, are repeatable, and have a clear expected result. A manual check may remain more practical for a rare or exploratory scenario.
Can a screenshot prove that a feature works?
A screenshot can show what a page looked like at capture time, but by itself it does not prove that a user interaction, server change, or business rule worked. Use a functional test for those behaviors.
What should I learn after the first test?
Learn your framework’s locator and assertion practices, how to control test data, how to debug failures, and how your team runs tests in CI. Add complexity only when a real requirement calls for it.
Further reading
- Selenium: Overview of Test Automation
- Robot Framework: Writing Your First Code
- Playwright: Continuous Integration
- GitHub Actions quickstart
- Practical Playwright Test: Next-Generation Web Testing and Automation, optional book for readers who choose Playwright


