Metadata-Version: 2.4
Name: qamigrate
Version: 0.9.1
Summary: AI-powered Selenium to Playwright Java migration — preserves TestNG, JUnit4/5, Cucumber BDD
Project-URL: Homepage, https://github.com/QATonic/QAMigrate
Project-URL: Repository, https://github.com/QATonic/QAMigrate
Project-URL: Issues, https://github.com/QATonic/QAMigrate/issues
Project-URL: Changelog, https://github.com/QATonic/QAMigrate/blob/main/CHANGELOG.md
Author: QATonic Innovations
License: MIT
Keywords: ai,automation,bdd,cucumber,java,junit5,llm,migration,playwright,selenium,test-automation,testing,testng
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Code Generators
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.11
Requires-Dist: anthropic>=0.40.0
Requires-Dist: openai>=1.50.0
Requires-Dist: pydantic-settings>=2.5.0
Requires-Dist: pydantic>=2.9.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: pyyaml>=6.0.0
Requires-Dist: rich>=13.9.0
Requires-Dist: structlog>=24.4.0
Requires-Dist: tree-sitter-java>=0.23.0
Requires-Dist: tree-sitter>=0.23.0
Requires-Dist: typer[all]>=0.12.0
Provides-Extra: all
Provides-Extra: dev
Requires-Dist: mypy>=1.12.0; extra == 'dev'
Requires-Dist: pre-commit>=4.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest>=8.3.0; extra == 'dev'
Requires-Dist: ruff>=0.7.0; extra == 'dev'
Provides-Extra: openai
Description-Content-Type: text/markdown

# QAMigrate

> AI-powered test framework migration

[![PyPI version](https://img.shields.io/pypi/v/qamigrate.svg)](https://pypi.org/project/qamigrate/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A [QATonic Innovations](https://github.com/QATonic) product.

**[→ Step-by-step User Guide](USERGUIDE.md)** — 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 + Maven` → `Playwright + 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

- Python 3.11+
- Java 17+ (for compile validation; optional — see `compiler.skip`)
- One of: [Anthropic API key](https://console.anthropic.com/settings/keys)
  or [OpenAI API key](https://platform.openai.com/api-keys)

### Install

```bash
pip install qamigrate
```

### Set API key

```bash
# 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)

```bash
# 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`)

```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:

```bash
export QAMIGRATE_COMPILER__SKIP=true
export QAMIGRATE_PIPELINE__MAX_RETRIES=5
```

## Python API

```python
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

```bash
# 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

```bash
pip install -e ".[dev]"
pytest tests -v           # 671 tests
ruff check src tests
mypy src
```

## License

MIT — see [LICENSE](LICENSE).
