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.


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)
--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 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           # 349 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.6.1.tar.gz (930.9 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.6.1-py3-none-any.whl (163.0 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for qamigrate-0.6.1.tar.gz
Algorithm Hash digest
SHA256 eff582545d372de1cfbc8291f8ec34cbc3938f7ae13465e7daef0f2e47cf6e8a
MD5 5aaba47fea8843b4a460921023b0a7db
BLAKE2b-256 b9e05d30cf16552dddea0618d266f2c7cc59c31d337f272cd0c1cd31d06bd263

See more details on using hashes here.

File details

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

File metadata

  • Download URL: qamigrate-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 163.0 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.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 746d27633e2ce1737a32f0d8951dd1f3ae60ec609f2c6d6852339b934f1b2832
MD5 daf04e8b0e563b05e299906f9dd8ba15
BLAKE2b-256 0aad71634277551c19373ae629b87a8f0d401e1df3eaba1853d066e24fb559cc

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