Skip to main content

AI-powered Selenium to Playwright Java migration — preserves TestNG, JUnit4/5, Cucumber BDD

Project description

QAMigrate

AI-powered test framework migration

PyPI version Python 3.11+ License: MIT

A QATonic Innovations product.

→ Step-by-step User Guide — new to QAMigrate? Start here.

What QAMigrate is responsible for

QAMigrate is a translation tool, not a debugger. The contract:

  • Input must compile. Your Selenium project must mvn compile cleanly before QAMigrate runs. From v0.7 onward, the pre-flight gate enforces this — the tool refuses to run on broken source unless you pass --ignore-source-errors.
  • Output preserves your source's correctness. QAMigrate translates valid Selenium Java patterns to valid Playwright Java patterns. It does NOT fix logic bugs in your source.
  • Errors are attributed. When you do override the gate, the report splits compile errors into "caused by QAMigrate" (our responsibility, hallucinations / dropped overloads / etc.) and "inherited from source" (a bug already in your Selenium code that we faithfully copied forward).

If your project doesn't currently compile as Selenium, fix the errors first — then run QAMigrate. Migrating broken source produces broken output, and QAMigrate would be wrongly blamed for translating bugs it never introduced.


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, @Listeners all 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@DataProvider with Object[][], @Parameters with XML config, external CSV/JSON data sources — all kept.
  • Project structure preserved — same directory layout, same package hierarchy (with a .playwright sub-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.6.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 + MavenPlaywright + Java + JUnit5 + Cucumber 7 + Maven

What stays identical:

  • .feature files — 100% unchanged, Gherkin is framework-neutral
  • @Given/@When/@Then/@And/@But step 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/playwright
  • BrowserFactory: new ChromeDriver()Playwright.create() + browser launch
  • Page objects: By locators → page.locator() stored as Locator fields
  • WaitUtils: WebDriverWait removed (Playwright auto-waits)
  • Screenshot in @After hook: page.screenshot() instead of TakesScreenshot

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.md checklist — 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

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)
--exclude <pattern>       Exclude files matching this glob (relative to --project).
                          Repeatable. Useful for monorepos.
--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       19 deterministic rules + 3 synthesis phases —
                               zero LLM cost:
                               - @Listeners guard (TestNG / BDD path)
                               - ScreenshotOptions import fix
                               - Locator.State → WaitForSelectorState
                               - AssertJ → Playwright assertThat
                               - Leftover Selenium imports stripped
                               - Cucumber import corruption guard
                               - N/A prose injection stripped
                               - Enum constant separators fixed
                               - Abstract method missing semicolons
                               - Doubled method headers collapsed
                               - Inline method body missing braces
                               - Javadoc placed after signature → moved above
                               - HTML entity escapes (&lt; → <) decoded
                               - Orphan Selenium identifiers commented out
                               - Constructor arity guard (drop invented args)
                               - Hallucinated method calls rewritten (edit-dist ≤ 2 OR same accessor prefix)
                               - Playwright API misuse: setIgnoreHTTPSErrors moved to NewContextOptions
            |
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 a new provider (Gemini, local model, etc.) takes ~200 lines. Anthropic and OpenAI are bundled today.

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           # 671 tests
ruff check src tests
mypy src

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qamigrate-0.8.0.tar.gz (458.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qamigrate-0.8.0-py3-none-any.whl (199.6 kB view details)

Uploaded Python 3

File details

Details for the file qamigrate-0.8.0.tar.gz.

File metadata

  • Download URL: qamigrate-0.8.0.tar.gz
  • Upload date:
  • Size: 458.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for qamigrate-0.8.0.tar.gz
Algorithm Hash digest
SHA256 35e810733290eb0c7195ac536605781cc0e0ab588daba5708e625f9ca0ecab71
MD5 fde0f5e834fb2c5eaaedce46237d3ab3
BLAKE2b-256 837c128cd7deb4afcf6bf6b7fb3b5be2a3265c7ddf7624900e71cef22b3574b0

See more details on using hashes here.

File details

Details for the file qamigrate-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: qamigrate-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 199.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for qamigrate-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 26ed40de4d1b62db65b59e6a0a901967e37d867add892158ded6cd14517f27fa
MD5 f1aceae0806653ecac29fbd0c5bcbecd
BLAKE2b-256 7cfe78b3759337a9d6524df78b9a6f9596d08bd29d7fb5c4759a1bce6c4ec4f8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page