AI-powered Selenium to Playwright Java migration — preserves TestNG, JUnit4/5, Cucumber BDD
Project description
QAMigrate
AI-powered test framework migration
A QATonic Innovations product.
QAMigrate converts Selenium Java test suites to Playwright Java automatically. It parses your code with Tree-sitter, understands your project architecture, generates equivalent Playwright code with Claude or GPT-4o, and produces a full migration report — per-file diffs, confidence scores, and a prioritised review checklist for your QA lead.
What makes QAMigrate different
It preserves your project as-is, not just your code.
- Framework preserved — If your project uses TestNG, the Playwright output
uses TestNG.
@BeforeMethod,@DataProvider,@Parameters,@Listenersall stay exactly as they are. If you use JUnit 4 or JUnit 5, that's what you get out. No forced framework migration. - Data-driven tests preserved —
@DataProviderwithObject[][],@Parameterswith XML config, external CSV/JSON data sources — all kept. - Project structure preserved — same directory layout, same package
hierarchy (with a
.playwrightsub-package added to avoid collisions), same build tool. - Context-aware — before generating a single line, QAMigrate reads your whole project: class roles, dependency graph, conventions (method chaining, logging patterns, thread-local driver), infrastructure (Selenium version, TestNG/JUnit version, Java version). Generated code matches your team's style.
Supported today (v0.5.x)
Selenium (Java) → Playwright (Java) — production-ready.
| What you have | What you get |
|---|---|
| Java 11+ / Maven or Gradle | Same build tool, Java 17+ |
| Selenium 3 or 4 | Playwright for Java 1.x |
| TestNG (any version) | TestNG — annotations preserved |
| JUnit 4 | JUnit 4 — annotations preserved |
| JUnit 5 | JUnit 5 — annotations preserved |
@DataProvider / @Parameters |
Kept verbatim on TestNG path |
@FindBy, PageFactory |
page.locator(), constructor injection |
WebDriverWait, ExpectedConditions |
Removed (Playwright auto-waits) |
| Page Object Model | Page Object Model (same pattern) |
| Base class inheritance | Preserved, getPage() injected |
| Cucumber BDD (v7+) | Cucumber kept — see below |
| LLM provider | Anthropic Claude or OpenAI GPT-4o |
Cucumber BDD projects (v0.5.15+)
Selenium + Java + JUnit5 + Cucumber 7 + Maven → Playwright + Java + JUnit5 + Cucumber 7 + Maven
What stays identical:
.featurefiles — 100% unchanged, Gherkin is framework-neutral@Given/@When/@Then/@And/@Butstep annotations — preserved verbatim- Cucumber hooks (
@Before/@After) — structure kept, only WebDriver → Page inside - JUnit Platform Suite runner (
TestRunner.java) — copied verbatim, not LLM-migrated - Cucumber version and all related deps — unchanged
- Allure, HTML, JSON reporting — unchanged
- Tags (
@smoke,@regression) — unchanged - Parallel execution config — unchanged
What changes:
DriverManager:ThreadLocal<WebDriver>→ThreadLocal<Page>+ context/browser/playwrightBrowserFactory:new ChromeDriver()→Playwright.create()+ browser launch- Page objects:
Bylocators →page.locator()stored asLocatorfields WaitUtils:WebDriverWaitremoved (Playwright auto-waits)- Screenshot in
@Afterhook:page.screenshot()instead ofTakesScreenshot
Roadmap
- v0.6.x: Python Selenium → Python Playwright (pytest)
- v0.6.x+: JavaScript/TypeScript Selenium → Playwright (including Java→JS cross-language)
- v0.6.x+: Python Selenium + Behave BDD → Python Playwright + Behave
- v0.7.x+: Cypress → Playwright, RestAssured → Karate
Why QAMigrate
You don't just get generated code. You get evidence:
- Per-file diffs — every pattern touched, why, side-by-side before/after
- Confidence score with reasoning — not just a number, but "Compiled after 2 fix attempts" or "-20 pts: 2 potential hallucinations detected"
- Colour-coded HTML report — green/amber/red confidence bars, mobile-responsive
review.mdchecklist — Priority / Verify / Solid buckets for your QA lead- Batch report — HTML, JSON, Markdown; shareable with your team
- Time-saved estimate — how many developer-hours of manual work you avoided
- Dry-run mode — preview everything without calling the LLM
Quick start
Prerequisites
- Python 3.11+
- Java 17+ (for compile validation; optional — see
compiler.skip) - One of: Anthropic API key or OpenAI API key
Install
pip install qamigrate
Set API key
# Anthropic (recommended — better at code generation)
export ANTHROPIC_API_KEY=sk-ant-your-key
# or OpenAI
export OPENAI_API_KEY=sk-your-openai-key
Or put them in a .env file in your project root.
Run (3 commands)
# 1. Initialise — detects your framework, injects correct deps into pom.xml
qamigrate init --project ./my-selenium-project --yes
# 2. Migrate — analyses, generates, compiles, fixes
qamigrate migrate --all --project ./my-selenium-project \
--provider openai --model gpt-4o \
--report ./qamigrate-report
# 3. Verify
cd my-selenium-project && mvn clean compile
The init command detects whether your project uses TestNG, JUnit4, or JUnit5
and injects the matching framework dep alongside Playwright — no manual
pom.xml editing needed.
Output
QAMigrate - Migrating 15 file(s)
-> [1/15] Environment.java PASSTHROUGH (enum, no Selenium markers — copied verbatim, 0.1s)
-> [2/15] BaseTest.java OK playwright/BaseTest.java (18.3s, 90% confidence)
-> [3/15] LoginPage.java OK playwright/LoginPage.java (14.2s, 100% confidence)
...
Files 14 / 15
Patterns handled 149
Avg confidence 92%
Time 169s
Est. hours saved ~37.2
Report written:
HTML: ./qamigrate-report/report.html ← colour-coded confidence bars
JSON: ./qamigrate-report/report.json
Markdown: ./qamigrate-report/report.md
Review: ./qamigrate-report/review.md ← priority checklist for QA lead
Generated files live next to their originals in a playwright/ subfolder,
declared in a com.your.package.playwright sub-package to avoid compile
collisions with the originals.
What gets migrated
Selenium API → Playwright API (always)
| Selenium | Playwright |
|---|---|
@FindBy(id="x") |
page.locator("#x") (stored as Locator field) |
@FindBy(css="x") |
page.locator("x") |
@FindBy(xpath="x") |
page.locator("xpath=x") |
PageFactory.initElements |
Removed — Playwright uses constructor injection |
WebDriverWait / ExpectedConditions |
Removed — Playwright auto-waits |
element.sendKeys("text") |
locator.fill("text") |
element.click() |
locator.click() |
new Select(el).selectByValue("x") |
locator.selectOption("x") |
driver.findElements(By.x) |
page.locator(x).all() |
driver.switchTo().frame(sel) |
page.frameLocator(sel).locator(...) |
JavascriptExecutor.executeScript |
page.evaluate(script) |
driver.get(url) |
page.navigate(url) |
Actions.moveToElement |
locator.hover() |
Actions.dragAndDrop |
source.dragTo(target) |
| File uploads | locator.setInputFiles(path) |
Assert.assertTrue(el.isDisplayed()) |
assertThat(locator).isVisible() |
ChromeDriver setup |
Playwright.create() + browser.newContext() |
Framework lifecycle (preserved or migrated based on your project)
TestNG project — kept as-is:
| Annotation | Output |
|---|---|
@Test |
@Test (TestNG) |
@BeforeMethod |
@BeforeMethod |
@AfterMethod |
@AfterMethod |
@BeforeClass |
@BeforeClass |
@AfterClass |
@AfterClass |
@DataProvider |
preserved verbatim |
@Parameters |
preserved verbatim |
@Listeners({X.class}) |
preserved verbatim |
JUnit5 project — lifecycle migrated:
| TestNG | JUnit5 |
|---|---|
@BeforeMethod |
@BeforeEach |
@AfterMethod |
@AfterEach |
@BeforeClass |
@BeforeAll static |
@AfterClass |
@AfterAll static |
CLI reference
| Command | Description |
|---|---|
qamigrate init |
Detect framework, inject Playwright + test-framework deps into pom.xml |
qamigrate scan |
Analyse codebase, show detected patterns and complexity |
qamigrate analyze |
Full architecture report (roles, conventions, dependency graph) — zero LLM cost |
qamigrate migrate |
Migrate files to Playwright Java |
migrate flags
<file> Migrate a single file
--all, -a Migrate all Java files in the project
--dry-run Analyse only, no LLM calls, no writes
--report <dir> Write report.html, report.json, report.md, review.md
--project, -p <path> Project root (default: current dir)
--no-batch-compile Fall back to per-file compile (debugging only)
--confidence-threshold N Fail with exit code 2 when any file < N% confidence
--confidence-mode avg|any "any" (default): fail if any file below threshold
"avg": fail if project average below threshold
LLM provider flags
--provider <name> anthropic or openai (both agents)
--model <id> e.g. gpt-4o or claude-sonnet-4-20250514
--coder-provider / --coder-model Override Coder agent only
--fixer-provider / --fixer-model Override Fixer agent only
init flags
--yes, -y Auto-inject deps without prompting
Detects and injects: Playwright + your project's test framework
(TestNG at its detected version, JUnit 4, or JUnit 5)
How it works
Phase 0 ProjectAnalyzer Read the whole project: roles, dependency graph,
(Tree-sitter) conventions, infrastructure. Zero LLM calls.
|
Phase 1 Passthrough Enums, DTOs, Cucumber runner → copied verbatim.
+ CoderAgent (LLM) All other files → Playwright Java via structured
output (tool use). Framework-aware + BDD-aware prompt.
Step def signatures FROZEN; Hooks kept as-is.
|
Phase 1.5 Import rewrite Sibling imports rewritten to .playwright package.
Cucumber imports (io.cucumber.*) never rewritten.
Postgen fixes Known API gaps fixed deterministically:
- @Listeners guard (TestNG / BDD path)
- ScreenshotOptions import
- Locator.State → WaitForSelectorState
- AssertJ → Playwright assertThat
- Leftover Selenium imports stripped
- Cucumber import corruption guard
|
Phase 2 ClasspathResolver mvn dependency:build-classpath (cached)
|
Phase 3 CompilerAgent javac batch compile — all files together
+ FixerAgent (LLM) Fix errors iteratively (up to max_retries)
+ Rollback Restore best-known state if Fixer regresses
|
Report HTML/JSON/Markdown Per-file diffs, confidence bars, review.md
Plain Python — no LangChain, no graph framework, no agent orchestration.
Providers implement a single generate_structured method; adding Gemini or
Ollama takes ~200 lines.
Configuration (qamigrate.yaml)
pipeline:
max_retries: 3 # Fix attempts per file (0-10)
coder:
provider: anthropic
model: claude-sonnet-4-20250514
max_tokens: 8192
temperature: 0.1
fixer:
provider: openai
model: gpt-4o-mini # Cheaper model fine for fixing
compiler:
build_tool: maven # or gradle
java_version: "17" # Detected from pom.xml if omitted
skip: false # true when Playwright JARs not on local classpath
timeout_seconds: 120 # javac timeout (10-600)
classpath:
timeout_seconds: 60 # mvn dep:build-classpath timeout (10-600)
use_cache: true # Cache classpath in .qamigrate/classpath.txt
scanner:
exclude_patterns:
- "**/target/**"
- "**/build/**"
- "**/playwright/**"
max_file_size_kb: 500
Environment variables override YAML with QAMIGRATE_ prefix:
export QAMIGRATE_COMPILER__SKIP=true
export QAMIGRATE_PIPELINE__MAX_RETRIES=5
Python API
from pathlib import Path
from qamigrate.agents.pipeline import run_project_migration
from qamigrate.core.config import QAMigrateConfig
from qamigrate.core.report import MigrationReport
config = QAMigrateConfig()
report = MigrationReport()
result = run_project_migration(
files=[Path("src/test/java/LoginPage.java")],
project_path=Path("."),
config=config,
)
for record in result.records:
report.add(record)
report.finish()
report.write_html(Path("./report.html"))
report.write_review_markdown(Path("./review.md"))
CI integration
# Fail the pipeline if migration quality drops below 80%
qamigrate migrate --all --project . \
--provider openai --model gpt-4o \
--confidence-threshold 80 \
--confidence-mode avg
echo $LASTEXITCODE # 0 = passed, 1 = generation failed, 2 = quality gate failed
Development
pip install -e ".[dev]"
pytest tests -v # 349 tests
ruff check src tests
mypy src
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qamigrate-0.5.18.tar.gz.
File metadata
- Download URL: qamigrate-0.5.18.tar.gz
- Upload date:
- Size: 2.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc0ffbd7fadd1dab4c24de1232777dbb9f03c1d567dc2d06dcc1cca56c62a2cf
|
|
| MD5 |
6fe6c661dee29876506a0cf5baf41eb0
|
|
| BLAKE2b-256 |
f280d5202511bc09df5d220cec272996430a383237301a35bee830d6de6e03cb
|
File details
Details for the file qamigrate-0.5.18-py3-none-any.whl.
File metadata
- Download URL: qamigrate-0.5.18-py3-none-any.whl
- Upload date:
- Size: 151.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
773b093fb02f511d8e74b294deec5290200192fa956587c2b7bd3485e11fde87
|
|
| MD5 |
54596ff3d04c3a535a5f3068fd6bceea
|
|
| BLAKE2b-256 |
59ce72860ef23f2cb6cdb79797909d65e918fb0e099886582a59149a2cf4cbf0
|