ScreenshotNeo

BlogHow-to

How to Write Selenium Test Scripts

Build a reliable Selenium test script from browser setup through assertions and cleanup, with runnable Python, Java, and JavaScript examples.

By the ScreenshotNeo team4 October 20269 min read

A Selenium test script starts a browser session, opens a page, finds elements, performs actions, waits for the application’s response, checks an expected result, and closes the session. The key to a reliable script is not a long sequence of clicks: use stable locators, wait for the state the next action needs, assert a meaningful outcome, and always clean up the browser.

This guide builds the same small test in Python, Java, and JavaScript. It opens Selenium’s sample web form, enters text, submits it, and verifies the response. Choose the binding that fits your project; the WebDriver workflow is the same across languages. See Selenium’s first-script guide and WebDriver documentation.

1. Choose a language, browser, and test runner

Use the language your application team already maintains. Python, Java, and JavaScript examples follow; Selenium also has bindings for other languages. Choose a browser your users or deployment environment require. For a first local script, install the Selenium binding and have the browser available. Selenium Manager is included in the standard binding flow and handles normal browser-driver management, so a basic test typically does not need driver download code.

Choice Use it when
Language binding You want the test to fit your existing application and team tooling.
Browser You need to cover a particular supported browser or reproduce a browser-specific issue.
Test runner You want repeatable execution, setup and teardown, reporting, and assertions as part of a suite.
Local or remote execution Run locally while learning; consider remote browsers or Selenium Grid when the environment or parallel capacity requires them.

Browser versions, pinned environments, containers, and remote execution policies can need additional configuration. Keep that environment setup outside the test’s core behavior where possible, so the test remains readable.

2. Install Selenium

Python

python -m pip install selenium

Save the example below as test_form.py and run python test_form.py. Selenium Manager will attempt to locate or manage the browser driver for the installed browser in the standard setup.

Java

Add the Selenium Java dependency using your project’s dependency manager. With Maven, for example, add the Selenium Java artifact to pom.xml, then compile and run the class using your project’s normal Java test or application workflow. Keep the dependency version aligned with the version approved for your project; consult the official getting-started guide for current setup instructions.

JavaScript

npm install selenium-webdriver

Save the example as test-form.js. Use a Node.js version that supports the async/await syntax shown. Install and configure a browser in the environment where the script runs.

3. Write the end-to-end script

The examples use an explicit wait for the response text. That matters because navigation reaching its configured document-ready state does not guarantee that JavaScript-driven content is ready for the next interaction.

Python: complete runnable example

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


def main():
    driver = webdriver.Chrome()
    try:
        driver.get("https://www.selenium.dev/selenium/web/web-form.html")
        wait = WebDriverWait(driver, 10)

        text_field = wait.until(
            EC.visibility_of_element_located((By.NAME, "my-text"))
        )
        text_field.send_keys("Selenium")
        driver.find_element(By.CSS_SELECTOR, "button").click()

        message = wait.until(
            EC.visibility_of_element_located((By.ID, "message"))
        )
        assert message.text == "Received!", (
            f"Expected 'Received!', got {message.text!r}"
        )
        print("Form submission passed")
    finally:
        driver.quit()


if __name__ == "__main__":
    main()

Java: complete example

Place this class in a Maven project with the Selenium Java dependency configured. The wait and assertion are explicit, and quit() runs even if an assertion or browser command fails.

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

public class SeleniumFormTest {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        try {
            driver.get("https://www.selenium.dev/selenium/web/web-form.html");
            WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

            wait.until(ExpectedConditions.visibilityOfElementLocated(
                By.name("my-text")
            )).sendKeys("Selenium");
            driver.findElement(By.cssSelector("button")).click();

            String message = wait.until(ExpectedConditions.visibilityOfElementLocated(
                By.id("message")
            )).getText();
            if (!"Received!".equals(message)) {
                throw new AssertionError("Expected 'Received!', got: " + message);
            }
            System.out.println("Form submission passed");
        } finally {
            driver.quit();
        }
    }
}

JavaScript: complete example

const { Builder, By, until } = require('selenium-webdriver');

(async function main() {
  const driver = await new Builder().forBrowser('chrome').build();
  try {
    await driver.get('https://www.selenium.dev/selenium/web/web-form.html');

    const textField = await driver.wait(
      until.elementIsVisible(driver.findElement(By.name('my-text'))),
      10000
    );
    await textField.sendKeys('Selenium');
    await driver.findElement(By.css('button')).click();

    const message = await driver.wait(
      until.elementIsVisible(driver.findElement(By.id('message'))),
      10000
    );
    const actual = await message.getText();
    if (actual !== 'Received!') {
      throw new Error(`Expected 'Received!', got: ${actual}`);
    }
    console.log('Form submission passed');
  } finally {
    await driver.quit();
  }
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

The exact element lookup and wait APIs vary by binding version. If your project uses a different test runner or binding version, use its current Selenium documentation while preserving the workflow: start, navigate, locate, act, wait, assert, and quit.

4. Pick locators that survive page changes

A locator identifies an element in the page DOM. Selenium supports IDs, names, CSS selectors, class names, link text, partial link text, tag names, and XPath. Prefer a unique, predictable ID when the application provides one. A stable name or CSS selector can also be clear. Avoid selectors coupled to incidental nesting or broad tags that match multiple elements.

Strategy Example Good fit
ID By.id("message") A unique, stable ID exists.
Name By.name("my-text") A form field has a meaningful stable name.
CSS selector By.cssSelector("button[type='submit']") A stable attribute or concise selector identifies the target.
XPath By.xpath("//button[@type='submit']") The relationship or text condition is hard to express clearly with another locator.

Before interacting, consider whether the locator is unique and whether a future markup change would break it for a meaningful reason. Selenium’s locator guidance describes the available strategies.

5. Wait for the condition the next step needs

Use an explicit wait for a concrete condition such as an element becoming visible, clickable, or containing expected text. This makes the script describe what “ready” means for that action. The examples use a ten-second timeout as a per-wait limit; adjust it to the application and execution environment rather than increasing it to conceal a broken flow.

Selenium’s implicit wait defaults to zero and applies globally to element lookups. Do not mix implicit and explicit waits: Selenium warns the resulting timeout behavior can be unpredictable. Avoid arbitrary fixed sleeps as the main synchronization method; they are either too short under load or waste time when the page is ready sooner. See the official waits guide.

6. Turn a script into a maintainable test

  1. Put browser creation in setup. Create a fresh driver for a test or a clearly defined group of tests.
  2. Assert an expected result. A click without checking the resulting state is an automation script, but it does not tell you whether the behavior passed.
  3. Always close the session. Use a test runner’s teardown hook or a finally block so a failed assertion does not leave a browser process running.
  4. Keep unrelated tests isolated. Avoid sharing mutable browser state across tests that should be independently repeatable.
  5. Add parallel or distributed execution when needed. Selenium Grid is an option when tests need to run across machines or in parallel; it adds environment and session-management configuration.

Selenium’s organizing code guide covers test setup and organization, and its Grid documentation explains distributed execution.

7. Troubleshooting common failures

Symptom Likely cause Fix
Driver or browser cannot be found The browser is absent, the environment blocks driver management, or a pinned browser and driver setup is needed. Install the intended browser, check environment access and version policy, and follow the Selenium Manager or remote-browser setup for that environment.
NoSuchElementException The locator is wrong, the element has not appeared, or the script is in the wrong page or frame. Confirm the current URL and DOM, verify the locator matches one element, wait for its required condition, and switch to the correct frame if applicable.
TimeoutException The expected condition never became true within the limit. Check whether the action succeeded, the locator is correct, the page is still loading, or the application returned a different state. Increase the timeout only when normal environment latency justifies it.
Click is intercepted or element is not interactable An overlay covers the control, the element is hidden, or it is not yet in an interactable state. Wait for visibility or clickability, dismiss the relevant overlay through the application flow, and confirm the target is in the viewport and enabled.
Test passes locally but fails in CI The CI browser, display mode, network, permissions, or timing differs from the local environment. Make browser and dependency setup explicit, use condition-based waits, preserve failure output, and reproduce with the same browser and execution mode.
Browser remains running after failure Cleanup did not run after an exception or assertion. Put quit() in a finally block or runner teardown and ensure teardown is registered even when setup partially fails.
Flaky result after clicking submit The test reads the page before the application’s response is ready or asserts the wrong state. Wait for the specific confirmation or state change and assert its value, rather than sleeping for a guessed duration.

8. Performance, reliability, and cost

A browser test starts a real browser session and performs browser interactions, so avoid doing setup that every test does not need. Keep each test focused, reuse stable setup only where state remains isolated, and run independent tests in parallel only when the browser infrastructure and application can handle it. Parallel execution can reduce suite wall time but increases concurrent browser and machine demand.

Reliability comes from deterministic test data, stable locators, explicit waits, meaningful assertions, and cleanup. A slow test is not automatically more reliable; broad timeouts can make genuine failures take longer to diagnose. Capture useful failure context in the runner or CI environment, and keep browser and binding versions deliberate when reproducing issues.

Cost depends on where browsers run: local machines use local compute, while hosted or Grid environments consume their own capacity. The Selenium documentation does not set a universal per-test price or benchmark. Estimate from your browser infrastructure, concurrency, run frequency, and suite duration.

Or skip the browser setup

If you need a page image for documentation, previews, or visual review rather than an interactive assertion, ScreenshotNeo is a website screenshot API and MCP server. A single request returns an image or PDF; its API documentation lists the parameters and options.

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}`);
  • Cookie and consent banners are accepted like a visitor, and 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing. Response headers report the page verdict and billing status.
  • An MCP server lets AI agents, including Claude and Cursor, use screenshot, page-info, and PDF-capture tools.
  • The free plan includes 1,000 shots a month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan.

Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.

FAQ

Does Selenium test the page’s visual appearance?

Selenium can inspect and interact with the rendered page, but this example asserts text. A visual screenshot comparison is a separate check from verifying an application state or response.

Should every test use a new browser session?

Fresh sessions help keep tests isolated. A suite can share a session when its design explicitly controls state and cleanup, but unrelated tests should not depend on mutable browser state left by earlier tests.

Can I run the same test against multiple browsers?

Yes. Choose the browser when creating the WebDriver session and provide the corresponding browser environment. For distributed or parallel runs, Selenium Grid is the documented option.

What should I learn after the first script?

Practice locator design, waits, test-runner lifecycle hooks, and isolation on your own application’s critical flows. Then add browsers or distributed execution to match the coverage your project needs.