Selenium WebDriver skill for Codex

Explicit waits, stable locators, a pinned Grid. Codex maintains Selenium suites the way they stay green: explicit waits on specific conditions with implicit waits off, locators on test ids instead of generated classes or absolute XPath, one browser session per test with screenshots and page source on failure, and page objects that expose user actions. It also stands up Selenium Grid 4 in Docker for parallel cross-browser CI.

  • Testing & QA
  • $selenium-webdriver
  • 192 lines
  • 2 files
  • ~2,050 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- selenium-webdriver

Installs to ~/.agents/skills/selenium-webdriver/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $selenium-webdriver, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Writes and maintains Selenium 4 WebDriver suites in Python, Java and JavaScript - Selenium Manager driver setup, stable locators, explicit waits with expected conditions instead of sleeps or implicit waits, page objects, per-test driver lifecycle, failure screenshots, parallel runs, and Selenium Grid 4 in Docker (standalone or hub and nodes) for cross-browser CI. Use when working on an existing Selenium suite, fixing NoSuchElement, StaleElementReference or timeout flakiness, setting up Selenium Grid, or running WebDriver tests headless in CI.

Use it when

  • Working on an existing Selenium WebDriver suite
  • Selenium tests are flaky with element or timeout exceptions
  • Setting up Selenium Grid or running WebDriver headless in CI
  • Cross-browser tests must run on a device cloud via WebDriver

Use something else when

  • Starting a new browser test suite in a JavaScript or TypeScript project: use playwright-e2e
  • You want to automate a browser for scraping or chores, not testing: use browser-automation

How to check the result

Changed tests pass five consecutive headless runs at CI parallelism, no new sleeps or implicit waits appear, and a deliberately broken locator fails fast with a screenshot artifact.

The mistake it prevents

Mixing implicit and explicit waits or adding sleeps, which makes timeouts unpredictable and hides the real synchronisation problem.

~/.agents/skills/selenium-webdriver/SKILL.md

--- name: selenium-webdriver description: Writes and maintains Selenium 4 WebDriver suites in Python, Java and JavaScript - Selenium Manager driver setup, stable locators, explicit waits with… license: MIT ---

A Selenium suite is healthy when every interaction waits for a specific condition rather than a fixed time, locators target stable attributes, each test owns its browser session and data, failures leave a screenshot and page source behind, and the same tests run against a pinned Grid in CI. Selenium is the right tool for existing suites, Java-heavy teams, real-browser vendor coverage and device clouds; for a new JavaScript project, consider playwright-e2e first.

Inspect first

  1. Read AGENTS.md, the build file (requirements*.txt/pyproject.toml, pom.xml, build.gradle, package.json) and the Selenium version. Selenium 4.6+ ships Selenium Manager, which resolves drivers and browsers automatically (it downloads them, so it needs network the first time or a pre-provisioned cache).
  2. Find the driver factory, base test class or fixtures, page objects, wait helpers and any time.sleep, Thread.sleep or implicitly_wait calls: rg -n "sleep\(|implicitly_wait|implicitlyWait|manage\(\)\.timeouts" tests src/test.
  3. Read CI config: headless flags, Grid URL, browser versions, parallelism, artifacts.
  4. Collect the failing test’s error type and screenshot before changing code.

Locators

PreferExampleAvoid
Dedicated test attributeBy.CSS_SELECTOR, "[data-testid='checkout-submit']"Generated classes (.css-1x2y3z)
Accessible id or nameBy.ID, "email", By.NAME, "password"Absolute XPath (/html/body/div[3]/...)
Link or button text, when copy is stableBy.XPATH, "//button[normalize-space()='Place order']"Index-based selection ((//button)[4])

Add data-testid attributes to the app when no stable attribute exists; it is cheaper than maintaining brittle XPath.

Waits

  • Use explicit waits (WebDriverWait + expected conditions) for every element that appears, changes or becomes clickable after page load.
  • Leave the implicit wait at 0. Mixing implicit and explicit waits makes timeouts add up unpredictably; Selenium’s own documentation warns against it.
  • Never sleep to wait for the UI. If no expected condition fits, write a custom one (a function taking the driver and returning a truthy value).
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, timeout=10)
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "[data-testid='checkout-submit']"))).click()
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, "[data-testid='order-status']"), "Confirmed"))
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("[data-testid='checkout-submit']"))).click();
wait.until(ExpectedConditions.textToBePresentInElementLocated(By.cssSelector("[data-testid='order-status']"), "Confirmed"));
const { Builder, Browser, By, until } = require("selenium-webdriver");
const el = await driver.wait(until.elementLocated(By.css("[data-testid='checkout-submit']")), 10000);
await driver.wait(until.elementIsEnabled(el), 10000);
await el.click();

Driver lifecycle (pytest)

import os
import pytest
from selenium import webdriver

@pytest.fixture
def driver(request):
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")
    options.add_argument("--window-size=1366,900")
    grid = os.environ.get("SELENIUM_REMOTE_URL")
    drv = webdriver.Remote(command_executor=grid, options=options) if grid else webdriver.Chrome(options=options)
    yield drv
    if getattr(request.node, "rep_call", None) and request.node.rep_call.failed:
        os.makedirs("artifacts", exist_ok=True)
        drv.save_screenshot(f"artifacts/{request.node.name}.png")
        with open(f"artifacts/{request.node.name}.html", "w", encoding="utf-8") as f:
            f.write(drv.page_source)
    drv.quit()

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()
    setattr(item, f"rep_{rep.when}", rep)

One driver per test (or per worker with a fresh profile per test) keeps cookies and storage from leaking. Always quit() in teardown, or Grid slots and local processes leak.

Page objects

class CheckoutPage:
    SUBMIT = (By.CSS_SELECTOR, "[data-testid='checkout-submit']")
    STATUS = (By.CSS_SELECTOR, "[data-testid='order-status']")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def place_order(self) -> "CheckoutPage":
        self.wait.until(EC.element_to_be_clickable(self.SUBMIT)).click()
        self.wait.until(EC.text_to_be_present_in_element(self.STATUS, "Confirmed"))
        return self

    def status(self) -> str:
        return self.driver.find_element(*self.STATUS).text

Page objects expose user actions and queries, not WebElements; assertions stay in tests.

Selenium Grid 4

NeedSetup
One browser in CIdocker run -d -p 4444:4444 --shm-size=2g selenium/standalone-chrome:<pinned tag>
Several browsers, parallel sessionsHub + nodes with Docker Compose
Elastic, per-session containersDynamic Grid (node-docker) or Kubernetes with the Selenium Helm chart
Real Safari, mobile devices, many OS versionsA device cloud that speaks WebDriver; credentials as CI secrets
# docker-compose.grid.yml
services:
  selenium-hub:
    image: selenium/hub:4.35.0        # pin to the version you test with
    ports: ["4442:4442", "4443:4443", "4444:4444"]
  chrome:
    image: selenium/node-chrome:4.35.0
    shm_size: 2gb
    depends_on: [selenium-hub]
    environment:
      SE_EVENT_BUS_HOST: selenium-hub
      SE_EVENT_BUS_PUBLISH_PORT: "4442"
      SE_EVENT_BUS_SUBSCRIBE_PORT: "4443"
      SE_NODE_MAX_SESSIONS: "2"
  firefox:
    image: selenium/node-firefox:4.35.0
    shm_size: 2gb
    depends_on: [selenium-hub]
    environment:
      SE_EVENT_BUS_HOST: selenium-hub
      SE_EVENT_BUS_PUBLISH_PORT: "4442"
      SE_EVENT_BUS_SUBSCRIBE_PORT: "4443"

Check curl -s http://localhost:4444/status reports "ready": true before starting tests, and keep client library and Grid on the same minor version. Image tags above are examples; use the current release.

Common errors

ErrorCauseFix
NoSuchElementExceptionLooked before it rendered, wrong frame, wrong locatorExplicit wait; switch_to.frame; check locator in DevTools
StaleElementReferenceExceptionDOM re-rendered after you found the elementRe-locate inside the action; wait for the re-render to finish
ElementClickInterceptedExceptionOverlay, sticky header, animation, cookie bannerWait for overlay invisibility; scroll into view; dismiss banner in setup
TimeoutException on a waitCondition never met: real bug, or the wrong conditionRead screenshot and page source before raising the timeout
SessionNotCreatedExceptionDriver/browser mismatch, Grid has no matching nodeLet Selenium Manager resolve; check Grid capabilities
Chrome crashes in Docker/dev/shm too small--shm-size=2g or --disable-dev-shm-usage

Verification

  • Run the changed tests five times headless locally or against the Grid, and once with the CI worker count; all green.
  • Break the feature on purpose (rename the test id) and confirm a fast, clear failure with a screenshot artifact.
  • rg "sleep\(|implicitly_wait" in the suite returns nothing new.

Gotchas

  • --headless=new is Chrome’s current headless mode; the old headless mode behaved differently and has been removed from Chrome.
  • Grid sessions that are never quit hold slots until SE_NODE_SESSION_TIMEOUT; parallel runs then queue and time out.
  • Tests that log in through the UI every time are slow; set the session cookie via add_cookie after an API login where the app allows it.
  • Running against shared staging with real accounts can send real emails or payments; use dedicated test accounts and sandboxed integrations.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Selenium WebDriver"
  short_description: "Explicit waits, stable locators, a pinned Grid"
  default_prompt: "$selenium-webdriver Our Selenium tests fail with StaleElementReferenceException in CI, fix them"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem