
Open Testing Convert Selenium
- 1 installs
- 13 repo stars
- Updated May 29, 2026
- agentictesting/opentestingai
open-testing-convert-selenium is a Claude Code skill that converts Selenium WebDriver tests to and from frameworks like Playwright, Cypress, Eggplant and OpenTest.AI.
About
open-testing-convert-selenium is a Claude Code skill that converts Selenium WebDriver tests to and from other test frameworks including Playwright, Cypress, Eggplant, OpenTest.AI and Testers.AI. It handles Selenium written in Java, Python, JavaScript, C# and Ruby, extracts a canonical test intent, and defers orchestration to an open-testing-convert master skill. A developer uses it to migrate a Selenium suite or translate a single script.
- Converts Selenium WebDriver tests to and from Playwright, Cypress, Eggplant and OpenTest.AI
- Handles Selenium in Java, Python, JavaScript, C# and Ruby
- Extracts canonical test intent and flattens Page Object Model tests during migration
Open Testing Convert Selenium by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,750 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Jul 26, 2026 (Skillselion catalog sync)
open-testing-convert-selenium capabilities & compatibility
- Capabilities
- test conversion · test migration · selenium to playwright · selenium to cypress
- Works with
- selenium · playwright
- Use cases
- testing · refactoring
- Pricing
- Free
What open-testing-convert-selenium says it does
Convert Selenium WebDriver tests to and from Eggplant (SenseTalk), OpenTest.AI, Testers.AI Recorder, Testers.AI Dynamic, Playwright, and Cypress.
This skill converts tests **from** Selenium to any supported target, or **to** Selenium from another framework.
Page Object Model tests are supported — flatten the page-object calls into inline actions during extraction.
npx skills add https://github.com/agentictesting/opentestingai --skill open-testing-convert-seleniumAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 13 |
| Last updated | May 29, 2026 |
| Repository | agentictesting/opentestingai ↗ |
What it does
Convert Selenium WebDriver tests to and from Playwright, Cypress, Eggplant and other test frameworks.
Who is it for?
Migrating a Selenium test suite to Playwright or Cypress, or translating a single WebDriver script.
Skip if: Appium mobile WebDriver or Cucumber-JVM tests, which it delegates to sibling convert skills.
When should I use this skill?
The user wants to migrate a Selenium test suite, translate a Selenium script, or round-trip it through another framework.
What you get
The skill emits converted tests in the target framework with a preamble noting language choice and TODOs.
- converted test files in the target framework
- conversion preamble with detected language and TODOs
By the numbers
- 6 conversion targets: eggplant, opentestai, testersai-recorder, testersai-dynamic, playwright, cypress
- supports 5 source languages: Java, Python, JavaScript, C#, Ruby
Files
Convert — Selenium WebDriver
This skill converts tests from Selenium to any supported target, or to Selenium from another framework. It defers orchestration, target semantics, and cross-framework rules to the open-testing-convert master skill.
When to use
Trigger when:
- The user references a Selenium codebase, file, or snippet and asks to migrate or translate it.
- The user asks to convert to Selenium from a recording (Testers.AI Recorder) or a natural-language format (OpenTest.AI, Testers.AI Dynamic).
- The user uploads a
.java,.py,.js,.ts,.cs, or.rbfile containingWebDriver,ChromeDriver,RemoteWebDriver,By.,@Test,driver.get, etc.
How to use
1. Read the master orchestrator first. Open ../open-testing-convert/SKILL.md and follow its step order (detect source → detect target → resolve language → delegate to this skill → read target primer → validate output). 2. Confirm target and language. The master's language-policy.md governs language choice (default: preserve source language unless the user requests otherwise). For Selenium the source language is one of Java, Python, JavaScript/TypeScript, C#, or Ruby. 3. Read the relevant target primer:
../open-testing-convert/reference/targets/eggplant.md../open-testing-convert/reference/targets/opentestai.md../open-testing-convert/reference/targets/testersai-recorder.md../open-testing-convert/reference/targets/testersai-dynamic.md../open-testing-convert/reference/targets/playwright.md- (Cypress: refer to
shared/wait-strategies.md§Cypress andshared/assertion-mapping.mdfor the Cypress column)
4. Read this skill's reference files:
reference/source-profile.md— what Selenium tests look like across languages.reference/extraction.md— how to extract a canonical test intent from Selenium code.reference/mappings.md— per-target translation tables and worked examples.
5. Read shared references as needed: open-testing-convert/reference/shared/{locator-mapping,wait-strategies,assertion-mapping,lifecycle-mapping}.md. 6. Emit the converted test(s) according to the target primer's Emission Checklist. Produce a preamble summarising: source file, detected language, target, language choice, any TODOs.
Scope
- Selenium 3.x and 4.x. Prefer 4.x locator idioms when generating Selenium.
- Selenium Grid, Appium 1.x that reuses WebDriver — Appium is covered by
open-testing-convert-appium; this skill stops at pure Selenium. - Page Object Model tests are supported — flatten the page-object calls into inline actions during extraction.
Out of scope (delegate to sibling skills)
- Appium (mobile WebDriver): use
open-testing-convert-appium. - Cucumber-Java / Cucumber-JVM that wraps Selenium: use
open-testing-convert-cucumber(it will invoke this skill as a sub-call for the step-definition layer).
Quick reference: Selenium verbs and their canonical conversions
| Selenium (Java) | Intent | Conversion hub |
|---|---|---|
driver.get(url) | navigate | shared/wait-strategies.md §Navigation |
driver.findElement(By.id("x")).click() | click | shared/locator-mapping.md |
driver.findElement(By.id("x")).sendKeys("y") | text input | shared/locator-mapping.md |
new WebDriverWait(driver, ...).until(ExpectedConditions.visibilityOfElementLocated(by)) | explicit wait | shared/wait-strategies.md §Selenium→Playwright cookbook |
assertTrue(el.isDisplayed()) | assertion | shared/assertion-mapping.md |
@BeforeEach driver = new ChromeDriver() | lifecycle | shared/lifecycle-mapping.md |
See reference/mappings.md for the full per-target cookbook.
Selenium — extraction rules
How to turn a Selenium test into the converter's canonical-intent representation before emitting to a target.
Canonical intent shape
Every source test decomposes into a sequence of typed actions:
{
test_name: string,
setup_url: string | null,
lifecycle: { beforeEach, afterEach, beforeAll, afterAll },
steps: [
{ kind: "navigate", url },
{ kind: "click", locator, visible_label? },
{ kind: "input", locator, value, field_label? },
{ kind: "select", locator, option, field_label? },
{ kind: "check" | "uncheck", locator, label? },
{ kind: "hover", locator },
{ kind: "keypress", key },
{ kind: "wait_for", locator_or_url_or_response },
{ kind: "assert", assertion_kind, locator?, value?, pattern? },
{ kind: "js_exec", script },
{ kind: "screenshot", name? }
],
metadata: { comments, docstring_tags, priority, owner }
}Every Selenium statement maps to one of these steps.
Line-by-line extraction rules
Navigation
| Selenium | Canonical |
|---|---|
driver.get(url) | {kind:"navigate", url} |
driver.navigate().to(url) | same |
driver.navigate().back() | {kind:"navigate", direction:"back"} |
driver.navigate().forward() | {kind:"navigate", direction:"forward"} |
driver.navigate().refresh() | {kind:"refresh"} |
Locate + act pairs
findElement(BY).ACTION(args) always decomposes to {kind: ACTION, locator: BY, ...args}.
| Action call | Canonical kind |
|---|---|
.click() | click |
.sendKeys(x) / .send_keys(x) / .SendKeys(x) | input with value=x |
.clear() | clear (often paired with subsequent input — fold clear()+sendKeys() into single input) |
.submit() | submit (rare; often interchangeable with click on a submit button) |
When you see new Select(el).selectByVisibleText("X"), emit {kind:"select", option:"X"}.
Label extraction
Convert a locator to a human label for natural-language targets:
1. If the source has a sibling <label for="id">X</label> comment or context → field_label = "X". 2. If By.linkText("X") or By.partialLinkText("X") → visible_label = "X". 3. If By.cssSelector("button[type=submit]") and the test file names it submitBtn → visible_label = "Submit" (heuristic from variable name). 4. If locator is By.id("email") → field_label = "email" as a fallback. 5. If nothing derivable → visible_label = null; leave the natural-language step generic (Click the button with id 'x').
Flag low-confidence labels in the preamble.
Waits
Selenium wait idioms map to {kind:"wait_for", ...}:
| ExpectedConditions | Canonical |
|---|---|
visibilityOfElementLocated(by) | wait_for: {kind:"element_visible", locator: by} |
elementToBeClickable(by) | wait_for: {kind:"element_clickable", locator: by} |
textToBePresentInElement(by, "x") | wait_for: {kind:"text_in_element", locator: by, text:"x"} |
urlContains("x") | wait_for: {kind:"url_contains", pattern:"x"} |
urlMatches("^.*x.*$") | wait_for: {kind:"url_matches", pattern:"^.*x.*$"} |
invisibilityOfElementLocated(by) | wait_for: {kind:"element_hidden", locator: by} |
alertIsPresent() | wait_for: {kind:"alert_present"} |
numberOfElementsToBe(by, n) | wait_for: {kind:"count_equals", locator: by, count:n} |
Targets with auto-wait (Playwright, Cypress) drop these as synchronisation and rely on assertion retry. Targets without (Eggplant, Selenium itself) preserve them.
Assertions
Assertions come from the host test framework, not Selenium itself. Detect and decompose:
| Source pattern | Canonical assertion |
|---|---|
assertTrue(el.isDisplayed()) | {kind:"assert", assertion_kind:"visible", locator: el's by} |
assertEquals("X", el.getText()) | {kind:"assert", assertion_kind:"text_equals", locator, value:"X"} |
assertTrue(el.getText().contains("X")) | {kind:"assert", assertion_kind:"text_contains", locator, value:"X"} |
assertEquals("X", el.getAttribute("value")) | {kind:"assert", assertion_kind:"value_equals", locator, value:"X"} |
assertTrue(driver.getCurrentUrl().contains("X")) | {kind:"assert", assertion_kind:"url_contains", pattern:"X"} |
assertEquals(N, driver.findElements(by).size()) | {kind:"assert", assertion_kind:"count_equals", locator: by, count: N} |
assertTrue(el.isSelected()) | {kind:"assert", assertion_kind:"checked", locator} |
assertFalse(...) | invert assertion_kind (visible → not_visible, etc.) |
Python assert expr, JS expect(x).to.equal(y) / Chai / Jest expect(x).toBe(y), C# Assert.AreEqual, Ruby expect(x).to eq(y) — same decomposition.
Lifecycle
Extract setUp/tearDown body as a sub-sequence:
lifecycle.beforeEach = [
{kind:"navigate", url:"https://..."},
...
]When the target has no lifecycle primitive (OpenTest.AI, Dynamic, Recorder, Eggplant), inline beforeEach at the top of each test's steps and mark in preamble.
Comments → metadata
/** @priority critical */JSDoc or Javadoc →metadata.priority = "critical".# Owner: frontend-teamPython comment →metadata.owner = "frontend-team".// TODO: flaky on CI→ preserve as preamble note; do not embed in target.
JavaScript executor and Actions
JavascriptExecutor.executeScript(js)→{kind:"js_exec", script: js}. Targets that support JS: emit a passthrough. Targets that don't (OpenTest.AI, Dynamic): emit a natural-language stepExecute the JavaScript: <summary>and flag for review.new Actions(driver).moveToElement(el).perform()→{kind:"hover", locator}.new Actions(driver).contextClick(el).perform()→{kind:"right_click", locator}.new Actions(driver).dragAndDrop(src, dest).perform()→{kind:"drag", from: src, to: dest}.
Page Object Model flattening
When a test calls loginPage.loginAs("u", "p"):
1. Find the LoginPage class definition. 2. Read the loginAs method body. 3. Substitute each statement inline using the POM's field locators (e.g., @FindBy(id = "email") → By.id("email")). 4. Treat the flattened sequence as the test body.
If the POM is in another file that isn't available, annotate each POM call as {kind:"unresolved_call", method:"loginPage.loginAs", args:[...]} and ask the user to provide the POM definition, or emit a TODO comment in the output.
Data-driven tests
- JUnit `@ParameterizedTest` + `@CsvSource` → expand each CSV row into one canonical test. Name the test
<base>_<row>. - pytest `@pytest.mark.parametrize` → same expansion.
- TestNG `@DataProvider` → same.
For code targets (Playwright, Cypress, Selenium reverse), preserve the data-driven idiom with the target's equivalent. For natural-language targets (OpenTest.AI, Dynamic) and Recorder, expand to individual test cases.
Output ordering
Canonical intent preserves source order. Do NOT:
- Merge consecutive clear+sendKeys into a single step (emit
clear, theninput; fold only if the target needs it). - Drop wait_for steps before the source intended them to block.
- Reorder assertions to the end.
The canonical sequence is what gets reshaped by the target primer.
Preamble metadata
At the end of extraction, build the preamble:
source: Selenium <language>
source_file: <path>
tests_extracted: <count>
page_objects_resolved: <count or TODO list>
lifecycle_inlined: yes|no
todos: [...]
low_confidence_labels: [...]
js_exec_calls: <count>
data_driven_expansion: <count>This preamble travels with the conversion output so downstream reviewers can target their attention.
Selenium — per-target mappings
Translation tables and worked examples. The canonical source example used throughout:
@Test void userCanLogIn() {
driver.get("https://app.example.com/login");
driver.findElement(By.id("email")).sendKeys("user@example.com");
driver.findElement(By.id("password")).sendKeys("secret");
driver.findElement(By.cssSelector("button[type=submit]")).click();
wait.until(ExpectedConditions.urlContains("/dashboard"));
assertTrue(driver.findElement(By.cssSelector(".welcome")).isDisplayed());
}→ Playwright (TypeScript)
See ../../open-testing-convert/reference/targets/playwright.md and shared/wait-strategies.md §Selenium→Playwright.
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.locator('#email').fill('user@example.com');
await page.locator('#password').fill('secret');
await page.locator('button[type=submit]').click();
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.locator('.welcome')).toBeVisible();
});Rules applied:
By.id("x")→page.locator('#x'). Preferpage.getByLabel(...)when the source has a visible label.sendKeys→fill(nottype—fillis atomic;typesimulates keystrokes).WebDriverWait.until(urlContains)→expect(page).toHaveURL(regex). Wait is implicit in the assertion.assertTrue(isDisplayed())→expect(locator).toBeVisible().@BeforeEachwithdriver = new ChromeDriver(); driver.get(URL)→ collapsed into test body first line (Playwright's{ page }fixture handles browser lifecycle).
Language matrix for Playwright
| Source Selenium lang | Default Playwright lang | Override available |
|---|---|---|
| Java | TypeScript | java, python, csharp |
| Python | Python | TypeScript, Java, C# |
| JS/TS | TypeScript | Python, Java, C# |
| C# | C# | TypeScript, Python, Java |
| Ruby | TypeScript | no Ruby binding for Playwright |
Ruby → TypeScript default because Playwright has no official Ruby binding.
→ Cypress (JS/TS)
describe('Login', () => {
it('user can log in', () => {
cy.visit('https://app.example.com/login');
cy.get('#email').type('user@example.com');
cy.get('#password').type('secret');
cy.get('button[type=submit]').click();
cy.url().should('include', '/dashboard');
cy.get('.welcome').should('be.visible');
});
});Rules applied:
driver.get(url)→cy.visit(url).findElement(By.x)→cy.get('<css>'). Cypress has no first-class id/xpath wrappers; collapse to CSS.sendKeys(x)→.type(x)(or.clear().type(x)if source had a clear).WebDriverWait→ drop; letcy.url().should(...)retry.assertTrue(isDisplayed())→.should('be.visible').- Cypress cannot run multiple browser tabs; if source uses
driver.switchTo().window(...), flag as incompatible.
Cypress locator limitations
Selenium By.xpath doesn't have a native Cypress equivalent. Options:
- Install
cypress-xpathplugin →cy.xpath('//...'). Note plugin dependency in preamble. - Convert xpath → CSS where possible.
- Flag with TODO if neither.
By.linkText("X") → cy.contains('a', 'X').
→ Selenium (cross-language)
When the user requests Selenium Java → Selenium Python or similar, map language idioms while preserving the WebDriver structure.
| Java | Python |
|---|---|
new ChromeDriver() | webdriver.Chrome() |
driver.findElement(By.id("x")) | driver.find_element(By.ID, "x") |
el.sendKeys("x") | el.send_keys("x") |
el.getText() | el.text |
el.getAttribute("v") | el.get_attribute("v") |
new WebDriverWait(driver, Duration.ofSeconds(10)) | WebDriverWait(driver, 10) |
ExpectedConditions.visibilityOfElementLocated(by) | EC.visibility_of_element_located((By.ID, "x")) |
assertTrue(expr) (JUnit) | assert expr (pytest) |
Test runner translates with the language: JUnit → pytest or unittest; TestNG → pytest; NUnit → xUnit / NUnit / MSTest (preserve on C# → C#), etc.
→ Eggplant (SenseTalk)
See ../../open-testing-convert/reference/targets/eggplant.md. Mode C (WebDriver) is the natural target for Selenium.
// Source: Selenium Java, LoginTest.java#userCanLogIn
// Setup
WebConnect host: "localhost", browser: "chrome", url: "https://app.example.com/login"
// Test body
SendKeys webID: "email", "user@example.com"
SendKeys webID: "password", "secret"
Click webCssSelector: "button[type=submit]"
WaitFor 10, the URL contains "/dashboard"
Assert that (findElement(webCssSelector: ".welcome")).isDisplayed is true
// Teardown
WebDisconnect allRules applied:
By.id("x")→webID: "x".By.cssSelector("x")→webCssSelector: "x".By.xpath("x")→webXPath: "x".By.linkText("X")→webLinkText: "X".By.className("x")→webClassName: "x".sendKeys→SendKeys <locator>, "<value>".click()→Click <locator>.WebDriverWait.until(ExpectedConditions.urlContains("x"))→WaitFor 10, the URL contains "x".assertTrue(el.isDisplayed())→Assert that <locator>'s isDisplayed is true.driver.get(url)at setup →WebConnect ... url: "<url>".driver.quit()at teardown →WebDisconnect all.
If the Selenium source uses By.xpath("//td[text()='" + variable + "']") with string interpolation, emit SenseTalk interpolated string: webXPath: ("//td[text()='" & variable & "']").
→ OpenTest.AI
See ../../open-testing-convert/reference/targets/opentestai.md.
{
"test_cases": [
{
"test_case_id": "user_can_log_in",
"test_case_name": "user can log in",
"url": "https://app.example.com/login",
"overall_description": "Verify a user can sign in with valid credentials and reach the dashboard.",
"validation_conditions": "The dashboard URL loads and the welcome banner is visible.",
"test_steps": [
"Type 'user@example.com' into the 'email' field.",
"Type 'secret' into the 'password' field.",
"Click the submit button.",
"Verify the URL matches '/dashboard'.",
"Verify the welcome banner is visible."
],
"priority_reason": "",
"if_fails_why_fix": "",
"probable_impact": "",
"probable_cause": "",
"route_to_engineer": "",
"data": {
"expected_test_input_parameters": ["user@example.com", "secret"],
"expected_results": [
{ "name": "URL", "value": "/dashboard", "reason": "Successful login navigates to dashboard." },
{ "name": "Welcome banner", "value": "visible", "reason": "Post-login affordance." }
]
}
}
]
}Rules applied:
- Setup URL (
driver.get(...)in beforeEach) →urlfield at test level. Does NOT also become a step (to avoid duplication — seeopentestai.md§12). - Locator → visible label heuristic:
By.id("email")withsendKeys→ "Type '...' into the 'email' field." (id used as label fallback).By.cssSelector("button[type=submit]")→ "Click the submit button." (inferred from the css selector[type=submit]).By.cssSelector(".welcome")→ "Verify the welcome banner is visible." (inferred from class namewelcome).WebDriverWait.until(urlContains(x))→ NOT a step (implicit in next assertion). Alternative: if source uses this without a subsequent URL assertion, emit as a step: "Wait for the URL to include '/dashboard'."assertTrue(isDisplayed())→ "Verify the <label> is visible."- Input values extracted into
expected_test_input_parameters. - Assertion-constants extracted into
expected_results. - Priority/impact fields empty (source had no JSDoc/Javadoc hints).
→ Testers.AI Dynamic
Same as OpenTest.AI plus envelope change plus optional hints.
{
"format": "testers-ai-dynamic",
"version": "1.0",
"exportDate": "2026-04-20T12:34:56.789Z",
"test_cases": [
{ /* as OpenTest.AI above */ }
]
}Hints derivable from Selenium:
By.id("email")+sendKeys→hints.push({ stepIndex: 0, expectedElementType: "input", anchor: "email" }).By.cssSelector("button[type=submit]")→{ stepIndex: 2, role: "button" }(inferred frombutton[type=submit]).urlContains("/dashboard")assertion →{ stepIndex: 3, assertType: "url_matches" }.isDisplayed()assertion →{ stepIndex: 4, assertType: "element_exists" }.
Emit hints only when derivable; never fabricate.
→ Testers.AI Recorder
Highest-synthesis path — Selenium has selectors but no screenshots. Skeleton-grade output is expected.
{
"format": "testers-ai",
"version": "1.0",
"exportDate": "2026-04-20T12:34:56.789Z",
"origin": "https://app.example.com",
"url": "https://app.example.com/login",
"tests": [
{
"test_case_name": "user can log in",
"test_steps": [
"Type \"user@example.com\" into \"email\"",
"Type \"secret\" into \"password\"",
"Click on \"submit button\"",
"Verify text \"/dashboard\" is visible on the page",
"Verify \"welcome\" exists on the page"
],
"overall_description": "Test converted from Selenium Java LoginTest#userCanLogIn",
"validation_conditions": "",
"origin": "https://app.example.com",
"url": "https://app.example.com/login",
"recordedActions": [
{
"action": "input",
"element": "input \"email\"",
"elementId": "email",
"value": "user@example.com",
"selectors": {
"tag": "input", "id": "email", "cssSelector": "#email",
"xpath": "", "name": "", "className": "", "type": "", "role": "",
"ariaLabel": "", "placeholder": "", "href": "", "value": "",
"title": "", "testId": "", "visibleText": "", "innerText": "",
"labelText": "", "tagIndex": 0,
"rect": { "x": 0, "y": 0, "w": 0, "h": 0 },
"isVisible": true, "parentTag": "", "siblingCount": 0
},
"naturalStep": "Type \"user@example.com\" into \"email\"",
"url": "https://app.example.com/login",
"timestamp": 1745155200000
}
/* ...next action, same shape... */
],
"selectorHints": [
{ /* mirror of recordedActions[0].selectors */ }
]
}
]
}Rules applied:
- Every
By.locator becomesselectors.id/selectors.cssSelector/selectors.xpathas appropriate. Other selectorHints fields: empty string, 0, or false. - No screenshots (source has none). Omit
elementScreenshot/elementCapture. - Timestamps: monotonically increasing; use
Date.now()at emit time or equal-interval 1000ms gaps. rectall zeros because Selenium doesn't expose DOM geometry at authoring time.isVisible: trueby default (the test assumes so; if the test has aisDisplayedassert, that's stronger evidence).- Flag in preamble: "Skeleton-grade output; rect, tagIndex, siblingCount, and screenshots require re-recording against live application."
Assertion mapping cheat-sheet
| Selenium assertion | Playwright | Cypress | OpenTest.AI step | Recorder assertType | Eggplant |
|---|---|---|---|---|---|
assertTrue(el.isDisplayed()) | toBeVisible() | should('be.visible') | Verify '<label>' is visible. | element_exists | Assert that <loc>'s isDisplayed is true |
assertEquals("X", el.getText()) | toHaveText("X") | should('have.text','X') | Verify '<label>' displays 'X'. | text_visible (with text: "X") | Assert that <loc>'s text is "X" |
assertTrue(el.getText().contains("X")) | toContainText("X") | should('contain','X') | Verify '<label>' contains 'X'. | text_visible | Assert that "X" is in <loc>'s text |
assertTrue(url.contains("X")) | toHaveURL(/X/) | url().should('include','X') | Verify URL contains '/X'. | closest: text_visible + note | Assert that the URL contains "X" |
assertEquals(N, driver.findElements(by).size()) | toHaveCount(N) | should('have.length', N) | Verify there are N '<label>' items. | no v1 form; flag | n/a |
assertTrue(el.isSelected()) | toBeChecked() | should('be.checked') | Verify '<label>' is checked. | element_exists + note | Assert that <loc>'s isSelected is true |
See ../../open-testing-convert/reference/shared/assertion-mapping.md for the full cross-framework assertion table.
Preamble format
Output every conversion with this preamble (as a comment for code targets, as a sibling .readme.md for JSON targets):
--- Selenium conversion preamble ---
Source: Selenium Java (JUnit 5)
Source file: src/test/java/com/example/LoginTest.java
Tests extracted: 1
Target: Playwright TypeScript
Language preservation: override (Java → TypeScript, default for Playwright from Java)
Lifecycle inlined: yes (driver.get in @BeforeEach → page.goto at top of test body)
Page objects resolved: 0 (test had inline locators)
TODOs: none
Low-confidence labels: none
JS exec calls: 0
Data-driven expansion: 0 → 1This preamble is required by the master orchestrator's emission checklist.
Selenium — source profile
What a Selenium test looks like across languages. Use these shapes as detection signals and as templates when converting to Selenium.
Detection signals
Filename extension alone is not sufficient — Selenium coexists with TestNG, JUnit, pytest, unittest, Mocha, Jest, NUnit, MSTest, xUnit, and RSpec. Look for:
- Import / using:
org.openqa.selenium.*(Java),from selenium import webdriver(Python),selenium-webdriver(JS),OpenQA.Selenium(C#),require 'selenium-webdriver'(Ruby). - Driver instantiation:
new ChromeDriver(),webdriver.Chrome(),new Builder().forBrowser('chrome').build(),new ChromeDriver()in C#. By.locator factory.WebDriverWait,ExpectedConditions/expected_conditions as EC.
Canonical shapes by language
Java (JUnit 5)
import org.junit.jupiter.api.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
class LoginTest {
WebDriver driver;
WebDriverWait wait;
@BeforeEach
void setUp() {
driver = new ChromeDriver();
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
driver.get("https://app.example.com/login");
}
@AfterEach
void tearDown() { driver.quit(); }
@Test
void userCanLogIn() {
driver.findElement(By.id("email")).sendKeys("user@example.com");
driver.findElement(By.id("password")).sendKeys("secret");
driver.findElement(By.cssSelector("button[type=submit]")).click();
wait.until(ExpectedConditions.urlContains("/dashboard"));
assertTrue(driver.findElement(By.cssSelector(".welcome")).isDisplayed());
}
}Java (TestNG)
Same body; annotations change:
@BeforeMethod // vs @BeforeEach
@AfterMethod // vs @AfterEach
@Test // same name, different package: org.testng.annotations.TestPython (pytest)
import pytest
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
@pytest.fixture
def driver():
d = webdriver.Chrome()
yield d
d.quit()
def test_user_can_log_in(driver):
driver.get("https://app.example.com/login")
driver.find_element(By.ID, "email").send_keys("user@example.com")
driver.find_element(By.ID, "password").send_keys("secret")
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
WebDriverWait(driver, 10).until(EC.url_contains("/dashboard"))
assert driver.find_element(By.CSS_SELECTOR, ".welcome").is_displayed()Python (unittest)
class LoginTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def tearDown(self):
self.driver.quit()
def test_login(self):
# ... same body ...JavaScript / TypeScript (Mocha + selenium-webdriver)
const { Builder, By, until } = require('selenium-webdriver');
const { expect } = require('chai');
describe('Login', function() {
let driver;
beforeEach(async () => {
driver = await new Builder().forBrowser('chrome').build();
await driver.get('https://app.example.com/login');
});
afterEach(async () => driver.quit());
it('user can log in', async () => {
await driver.findElement(By.id('email')).sendKeys('user@example.com');
await driver.findElement(By.id('password')).sendKeys('secret');
await driver.findElement(By.css('button[type=submit]')).click();
await driver.wait(until.urlContains('/dashboard'), 10000);
expect(await driver.findElement(By.css('.welcome')).isDisplayed()).to.be.true;
});
});C# (NUnit)
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
[TestFixture]
public class LoginTests {
private IWebDriver driver;
[SetUp]
public void SetUp() {
driver = new ChromeDriver();
driver.Navigate().GoToUrl("https://app.example.com/login");
}
[TearDown]
public void TearDown() => driver.Quit();
[Test]
public void UserCanLogIn() {
driver.FindElement(By.Id("email")).SendKeys("user@example.com");
driver.FindElement(By.Id("password")).SendKeys("secret");
driver.FindElement(By.CssSelector("button[type=submit]")).Click();
new WebDriverWait(driver, TimeSpan.FromSeconds(10))
.Until(d => d.Url.Contains("/dashboard"));
Assert.IsTrue(driver.FindElement(By.CssSelector(".welcome")).Displayed);
}
}Ruby (RSpec)
require 'selenium-webdriver'
RSpec.describe 'Login' do
before(:each) do
@driver = Selenium::WebDriver.for :chrome
@driver.get 'https://app.example.com/login'
end
after(:each) { @driver.quit }
it 'user can log in' do
@driver.find_element(id: 'email').send_keys 'user@example.com'
@driver.find_element(id: 'password').send_keys 'secret'
@driver.find_element(css: 'button[type=submit]').click
wait = Selenium::WebDriver::Wait.new(timeout: 10)
wait.until { @driver.current_url.include?('/dashboard') }
expect(@driver.find_element(css: '.welcome').displayed?).to be true
end
endBy locator vocabulary
Selenium 4's By exposes the same eight strategies across every binding:
| Strategy | Java | Python | JS | C# | Ruby |
|---|---|---|---|---|---|
| id | By.id("x") | By.ID, "x" | By.id('x') | By.Id("x") | id: 'x' |
| name | By.name("x") | By.NAME | By.name | By.Name | name: 'x' |
| class name | By.className("x") | By.CLASS_NAME | By.className | By.ClassName | class_name: 'x' |
| tag name | By.tagName("x") | By.TAG_NAME | By.tagName | By.TagName | tag_name: 'x' |
| link text | By.linkText("X") | By.LINK_TEXT | By.linkText | By.LinkText | link_text: 'X' |
| partial link text | By.partialLinkText("X") | By.PARTIAL_LINK_TEXT | By.partialLinkText | By.PartialLinkText | partial_link_text: 'X' |
| css selector | By.cssSelector("x") | By.CSS_SELECTOR | By.css | By.CssSelector | css: 'x' |
| xpath | By.xpath("//x") | By.XPATH | By.xpath | By.XPath | xpath: '//x' |
Selenium 4 adds By.RelativeLocator (near / above / below / toLeftOf / toRightOf) — rarely seen in production, map to the target's equivalent (Playwright page.locator(...).locator(...) chains, or xpath).
Common Selenium patterns
Page Object Model
Tests reference page objects rather than inline locators:
class LoginPage {
@FindBy(id = "email") WebElement email;
@FindBy(id = "password") WebElement password;
@FindBy(css = "button[type=submit]") WebElement submit;
void loginAs(String u, String p) { email.sendKeys(u); password.sendKeys(p); submit.click(); }
}When extracting, inline each page-object action. Do not emit page objects in the target unless the user requests them.
Waits — four forms
1. Implicit wait — driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)). Deprecated in style; replace with explicit. 2. Explicit wait — new WebDriverWait(driver, ...).until(ExpectedConditions.X). Standard. 3. Fluent wait — new FluentWait<>(driver).withTimeout(...).pollingEvery(...).ignoring(...). Rare; map to explicit wait with the target's tuning knobs. 4. Thread.sleep — anti-pattern; drop unless the source deliberately modelled a delay.
Actions API
new Actions(driver).moveToElement(el).click().perform() — for hover, drag, keyboard chords. Map to page.locator(...).hover() (Playwright), cy.get(...).trigger('mouseover') (Cypress), or equivalent.
JavaScript executor
((JavascriptExecutor) driver).executeScript("arguments[0].click();", el) — escape hatch when native click doesn't work. Map to page.evaluate() (Playwright), cy.then(win => win.document.querySelector(...).click()) (Cypress), ExecuteJavaScript "..." (Eggplant Mode C).
Anti-patterns to detect and flatten
- Nested try/catch around `findElement` for visibility polling. → explicit
WebDriverWaitin the intermediate canonical form. - `Thread.sleep` as a wait. → drop in conversion; flag in preamble.
- `if (el.isDisplayed()) { ... }` as a pseudo-assert. →
assert el.isDisplayed()in canonical form; emit as the target's retrying assertion. - Locators that embed live data (
By.xpath("//td[text()='" + name + "']")) → preserve the interpolation pattern when possible; degrade to text match in natural-language targets.
Language-specific notes
- Java / C#: static typing; driver type is
WebDriver/IWebDriver. Generic fixtures possible. - Python: no type hints by default; snake_case method names (
send_keys,is_displayed). - JavaScript / TypeScript: always async;
awaiton every driver call. Chained promises are idiomatic. - Ruby:
find_element(id: ...)hash form is preferred overBy.id(...).
Non-code output always preserves the URL context
Every Selenium test has at least one driver.get(...) call. Extract the URL and populate:
- OpenTest.AI / Dynamic:
urlat test level. - Testers.AI Recorder:
urlat envelope and first-action level. - Eggplant:
WebConnect url: "<url>"as the first statement.
If the test navigates multiple times (driver.get(a); ...; driver.get(b)), preserve each navigation as a step — do not collapse.