Skip to main content

sel2pw — Selenium Java → Python Playwright migration agent

Implements the agent described in selenium_java_to_playwright_migration_agent_system_prompt.md (bundled as sel2pw/prompts/system_prompt.md) three ways, all on one shared core:

Surface Command Who does the reasoning
CLI sel2pw inventory / migrate / validate Built-in agent + any LLM provider
MCP server sel2pw mcp (or sel2pw-mcp) The MCP host's LLM (Claude Code/Desktop, Copilot, Cursor…), or the built-in agent via run_migration_agent
Library MigrationAgent(get_provider(...), Workspace.create(...)).run() Any LLMProvider

Architecture

src/sel2pw/
  workspace.py   sandbox: named roots (source=ro, target=rw, --reference=ro), path-escape checks, secret redaction
  inventory.py   deterministic Java scan: tests (IDs "Class#method"), lifecycle hooks, page objects,
                 dependencies, locators, data-provider rows, constructs to flag (waits, frames, alerts, JS, ...)
  validate.py    compile (in-process), pytest --collect-only, pytest run (opt-in only)
  ledger.py      structured mappings + issues recorded by the agent
  gates.py       deterministic acceptance gates
  reports.py     code-generated migration_map.md / migration_validation.md
  manifest.py    run fingerprint + manifest under <target>/.sel2pw/runs/
  tools.py       ONE tool registry + input validation, used by both the agent and the MCP server
  agent.py       provider-agnostic loop (append-only history, gate-driven repair rounds, finalize)
  llm/           base.py (neutral messages) + anthropic/openai adapters + registry + cache.py (record/replay)
  prompts/       system_prompt.md (extracted from the spec) + static TOOL LAYER appendix
  cli.py, mcp_server.py

The spec says "prompt text alone is not a security boundary", so the tool layer enforces it: the source root is never writable; no path can leave its root; secrets are redacted before reaching any LLM; overwriting a target file that existed before the run is recorded and reported; browser execution (run_check kind=run) is refused unless the operator passes --allow-browser-tests. There is no generic shell tool.

Install

pip install -e ".[all]"        # or .[anthropic] / .[openai] / .[mcp]; .[dev] adds pytest

CLI

# Deterministic inventory (no LLM, free)
sel2pw inventory path/to/selenium-project --out inventory.md

# Full migration with the built-in agent
sel2pw migrate --source path/to/selenium-project --target path/to/innoharmony-repo \
    --task "Migrate LoginTest and its page objects" --scope "LoginTest#*"

# Static checks on migrated code (add --collect for pytest --collect-only)
sel2pw validate path/to/innoharmony-repo --collect --python path/to/repo/.venv/Scripts/python

Useful migrate flags: --reference innoharmony=path/to/framework (extra read-only root), --allow-browser-tests (approved non-prod env only), --python (target repo's interpreter for pytest), --scope, --max-repairs, --gate-collect, --gate-run, --min-migrated N|N%, --allow-exclude PATTERN, --report-dir, --cache, --max-turns, --max-total-tokens N (stop cleanly with stop=budget), --system-prompt FILE. Exit code is 0 only when the acceptance gates pass.

Large suites: --batch-by class (or package) runs one agent session per unit, each scoped to exactly that unit's in-scope tests, with one shared ledger. Progress is saved to <target>/.sel2pw/batch/state.json after every unit, and --resume continues a batch, skipping units already accepted. When all units have run, the gates run across the whole scope and the combined reports are written.

Determinism

LLM output can't be made bit-for-bit deterministic. Claude Opus 5.5 rejects temperature, and OpenAI's seed is only best effort. sel2pw makes every outcome deterministic and every run reproducible:

Layer Mechanism
What counts as "done" Deterministic acceptance gates: scope_coverage (every in-scope test mapped), mapping_integrity (target file + symbol exist; blocked/excluded have reasons; a partial test has a MEDIUM-or-higher issue and a blocked test a BLOCKER/HIGH one; a test is excluded only if it matches --allow-exclude), migration_yield (at least --min-migrated in-scope tests, default 1, are migrated or partial), assertion_parity, parametrize_parity (data-provider rows = parametrize cases), disabled_preserved (stays skipped), compile, lint (no unjustified sleeps/networkidle, no secrets or redacted placeholders, no assertions that always pass, no expect() without a matcher), optional collect, and optional run (--gate-run with --allow-browser-tests: the mapped tests are executed once and each test's passed / failed / skipped result goes into both reports). Failures are sent back to the model for up to --max-repairs rounds, and the run fails if they persist.
Reports The agent records structured data (record_mapping, record_issue). It cannot write migration_map.md or migration_validation.md. Code renders both, with no timestamps, so identical inputs give byte-identical reports. Text the model wrote is shown in labelled "agent" columns or sections.
Reproducing a run Record/replay cache (--cache auto by default) in <target>/.sel2pw/cache. It is keyed by a SHA-256 of the provider settings, system prompt, tool schemas and the whole conversation, including tool results. Re-running with the same inputs and the same starting target replays the run exactly without calling the model. --cache replay makes any deviation an error, which suits CI. --cache off forces a fresh attempt. Tool output is normalized (sorted listings, pytest timings stripped) so keys stay stable.
Traceability Every run writes <target>/.sel2pw/runs/<run_id>/: manifest.json (model + settings, prompt/tools/task/source-tree/inventory hashes, target tree hash before/after, cache hits, usage, SDK versions), gates.json, ledger.json, events.jsonl. The stable part of the fingerprint is also in the validation report.

Escape hatches the gates recognise (all appear in reports for reviewers): a justification on a mapping (intentional assertion, case or skip difference), and line comments # sel2pw: allow-sleep: <reason>, # sel2pw: allow-networkidle: <reason>, # sel2pw: allow-secret (test data, not credentials).

Add .sel2pw/ to the target's .gitignore, or commit .sel2pw/cache if you want others to be able to replay your runs.

LLM providers (sel2pw providers)

--provider Notes
anthropic (default) Default model claude-opus-5-5. Auth: ANTHROPIC_API_KEY or ant auth login.
openai OPENAI_API_KEY, --model required
azure-openai AZURE_OPENAI_BASE_URL (v1 endpoint) + AZURE_OPENAI_API_KEY; --model = deployment
gemini GEMINI_API_KEY, OpenAI-compatible endpoint, --model required
ollama Local, OLLAMA_BASE_URL (default http://localhost:11434/v1); use a tool-capable model
openai-compatible vLLM, LM Studio, OpenRouter, LiteLLM… via SEL2PW_BASE_URL / SEL2PW_API_KEY
my_pkg.module:MyProvider Plugin: any sel2pw.llm.base.LLMProvider subclass

Defaults can come from env: SEL2PW_PROVIDER, SEL2PW_MODEL, SEL2PW_EFFORT.

Anthropic adapter specifics: streaming with eager_input_streaming tools (whole-file writes stream as they are generated; inputs are re-validated before running), adaptive thinking with --effort (default high), automatic prompt caching, and server-side refusal fallbacks enabled by default (fallbacks="default"). Pass --no-fallbacks to turn them off; you must do that on Bedrock/Vertex/Foundry gateways, where the parameter is rejected. Long runs use server-side compaction (compact-2026-01-12, at 150K input tokens, with a summary prompt that keeps the migration state); the history stays append-only, so thinking blocks remain valid. --no-compaction turns it off.

OpenAI-compatible providers get client-side clearing instead: once tool output in the history passes 400K characters, older large results are sent as a short stub (the three newest tool-result messages always go in full).

Adding a provider

from sel2pw.llm.base import LLMProvider, AssistantTurn, ToolCall

class MyProvider(LLMProvider):
    name = "mine"
    def __init__(self, model=None, **kw):
        super().__init__(model or "my-model")
    def complete(self, system, messages, tools) -> AssistantTurn:
        ...  # convert UserMessage / AssistantTurn(raw) / ToolResultsMessage to your API, call it,
             # return AssistantTurn(text, [ToolCall(id, name, args)], stop_reason, raw=<native turn>)
sel2pw migrate --provider my_pkg.providers:MyProvider ...

MCP server

Tools: java_inventory, list_files, read_file, search, write_file, edit_file, run_check, record_mapping, record_issue, check_acceptance, finalize_migration (gates + generated reports + run manifest), reset_session (start a new migration on the same server), run_migration_agent (hide with --no-agent-tool). Prompt: migrate_selenium_to_playwright. Resource: sel2pw://system-prompt. The roots, --scope and gate options are fixed at server start, so a host can't widen the scope. Host-driven runs get the same gates and generated reports through finalize_migration.

Claude Code

claude mcp add sel2pw -- sel2pw mcp --source C:/work/selenium-tests --target C:/work/innoharmony-tests

VS Code / Cursor / Claude Desktop (mcp.json / claude_desktop_config.json)

{
  "mcpServers": {
    "sel2pw": {
      "command": "C:/myprojects/selenium2playwright/.venv/Scripts/sel2pw.exe",
      "args": ["mcp", "--source", "C:/work/selenium-tests", "--target", "C:/work/innoharmony-tests"]
    }
  }
}

HTTP transport: sel2pw mcp ... --transport streamable-http [--host 127.0.0.1] [--port 8000]. The server exposes write tools, so it binds to 127.0.0.1 by default and refuses any other address unless SEL2PW_MCP_TOKEN is set; with a token, every request needs Authorization: Bearer <token>. One server instance serves one migration at a time: its recorded mappings and written files are shared by every client that connects, so run a separate server per concurrent migration, and call reset_session before starting a new one. run_migration_agent keeps its own record of written files.

Security audit

security-audit.bat            # Windows
./security-audit.sh           # Linux, macOS, Git Bash

Creates and activates .venv with the project and its dev extras, installs the pinned tools in requirements-security.txt into a separate .venv-audit (Semgrep pins an older mcp than this project needs), downloads checksum-verified gitleaks, Trivy and CodeQL into .tools/ on first use (CodeQL is a one-time ~665 MB download), then runs gitleaks, Trivy, Bandit, Semgrep, pip-audit and CodeQL (Python and GitHub Actions). Open security-reports/security-report.html for all results, with a page per tool. Options: -SkipCodeQL / --skip-codeql, -SkipInstall / --skip-install, -Out DIR / --out DIR.

Tests

pytest -q

No test calls a real LLM. The provider adapters are tested against fake clients, and the MCP tests run the server in-process.

Metadata

Release files for sel2pw 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sel2pw 0.2.0
File Size Uploaded Provenance Actions
sel2pw-0.2.0.tar.gz 89.5 kB With provenance Details for sel2pw-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sel2pw 0.2.0
File Interpreter ABI Platform Actions
sel2pw-0.2.0-py3-none-any.whl Python 3 none any Details for sel2pw-0.2.0-py3-none-any.whl

Total release size: 166.5 kB

Release files / sel2pw-0.2.0.tar.gz

Download URL sel2pw-0.2.0.tar.gz
Size 89.5 kB
Tags Source
SHA-256 checksum
How to use checksums
7add9a3db846df8d6f4b4bb2bd88a47eb220d47cc96afec608f2594f279ef16c
BLAKE2b-256 checksum
How to use checksums
c914dc88ce5da9622ac2325224ae827a93bf9c7c06b234db60404cc4967323a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / sel2pw-0.2.0-py3-none-any.whl

Download URL sel2pw-0.2.0-py3-none-any.whl
Size 77.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
296e9dce3a3215b3f073f9231ffec50f157786c6bab296991e74fcfaa0607295
BLAKE2b-256 checksum
How to use checksums
082f6b3fbd46f63396990d3b787571f29b51f9b7e86cca107b141e81cfb0bbe8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for sel2pw 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sel2pw-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page