npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

automigrate

v0.3.0

Published

Universal test migration tool — converts Selenium, Cypress, Puppeteer, Appium, WebdriverIO, and Robot Framework test suites to Playwright

Readme

automigrate

Migrate Selenium, Cypress, Puppeteer, WebdriverIO, Appium, and Robot Framework test suites to Playwright.

npm version License: MIT Build Status Node.js


automigrate demo

automigrate analyzes your existing test suites, builds a transformation plan with confidence scores, and generates idiomatic Playwright code. It runs in dry-run mode by default and never modifies your source files.

Features

  • Multi-framework -- Selenium, Cypress, Puppeteer, WebdriverIO, Appium, and Robot Framework
  • Multi-language -- Java, JavaScript, TypeScript, and Python parsers
  • Safety-first -- Dry-run by default; original files are never touched
  • Confidence scoring -- Every transformation is rated high/medium/low so you know what needs manual review
  • Smart selectors -- Converts legacy selectors to Playwright best practices (getByRole, getByTestId)
  • Page Object generation -- Detects page object patterns and produces Playwright POM equivalents
  • Fixture generation -- Creates Playwright test fixtures from setup/teardown hooks
  • Plugin system -- Custom parsers, transformation rules, and code generators via the AutomigratePlugin interface
  • Parallel processing -- Concurrent file transformation with configurable concurrency
  • Unified diffs -- Preview every change before committing with automigrate diff
  • Watch mode -- Re-migrate incrementally as source files change with --watch
  • Non-test file filtering -- Automatically excludes config files (wdio.conf, jest.config, tsconfig, etc.)

Quick Start

Analyze your test suite

npx automigrate analyze ./tests

This scans your test directory, detects frameworks and languages, and prints a complexity report with migration confidence estimates.

Run a migration (dry-run)

npx automigrate migrate ./tests --output ./playwright-tests

By default this is a dry-run. You will see a full report and preview of the generated files without anything being written to disk.

Write the output

npx automigrate migrate ./tests --output ./playwright-tests --no-dry-run

You will be prompted for confirmation before any files are created. Pass -y to skip the prompt.

Deep scan

npx automigrate scan ./tests

Performs a deep structural analysis -- detects frameworks, file roles, patterns, and generates a migration blueprint.

Installation

# Global install
npm install -g automigrate

# Or use npx (no install required)
npx automigrate --help

# Or add to your project
npm install --save-dev automigrate

Requires Node.js >= 18.0.0.

CLI Reference

scan <sourceDir>

Deep-scan project structure -- detect frameworks, file roles, patterns, and generate a migration blueprint.

automigrate scan ./tests --verbose

analyze <sourceDir>

Scan source tests and produce a migration feasibility report.

automigrate analyze ./tests --format json --output report.json

| Option | Description | Default | | ------------------- | ------------------------------------------ | ------------- | | --format <format> | Output format: table, json, markdown | table | | --output <file> | Write report to file | stdout | | --config <path> | Path to config file | auto-detected | | -v, --verbose | Verbose output | false |

migrate <sourceDir>

Run the full migration pipeline.

automigrate migrate ./tests \
  --output ./playwright-tests \
  --language typescript \
  --selector-strategy best-practice \
  --wait-strategy auto-wait \
  --page-objects \
  --fixtures \
  --no-dry-run -y

| Option | Description | Default | | --------------------- | ----------------------------------------------------------------------------- | --------------- | | --output <dir> | Output directory for Playwright tests (required) | -- | | --framework <fw> | Source framework: selenium, cypress, puppeteer, webdriverio, appium | auto-detected | | --language <lang> | Target language: typescript, javascript | typescript | | --no-dry-run | Write files to disk | dry-run enabled | | --selector-strategy | preserve, modernize, best-practice | preserve | | --wait-strategy | preserve, auto-wait, explicit | auto-wait | | --page-objects | Generate Playwright page object classes | false | | --fixtures | Generate Playwright test fixtures | false | | --include <glob> | Include patterns (repeatable) | see defaults | | --exclude <glob> | Exclude patterns (repeatable) | see defaults | | --concurrency <n> | Max concurrent file processing | 4 | | --watch | Watch source directory and re-migrate on changes | false | | --format <format> | Report format: table, json | table | | --report <file> | Write full JSON report to file | -- | | -v, --verbose | Verbose output | false | | -y, --yes | Skip confirmation prompt | false |

diff <sourceDir>

Preview changes as unified diffs without writing files.

automigrate diff ./tests --output ./playwright-tests

init

Generate a .automigrate.config.ts configuration template in the current directory.

automigrate init

Configuration

Run automigrate init to generate a .automigrate.config.ts file. All CLI options can be set here:

import type { MigrationConfig } from 'automigrate';

const config: MigrationConfig = {
  sourceDir: './tests',
  outputDir: './playwright-tests',
  targetLanguage: 'typescript',

  // Safety
  dryRun: true,
  preserveOriginal: true,

  // Code generation
  generatePageObjects: true,
  generateFixtures: true,

  // File selection
  includePatterns: ['**/*.spec.ts', '**/*.test.js', '**/*.java'],
  excludePatterns: ['**/node_modules/**', '**/dist/**'],

  // Transformation strategies
  selectorStrategy: 'best-practice', // "preserve" | "modernize" | "best-practice"
  waitStrategy: 'auto-wait', // "preserve" | "auto-wait" | "explicit"
  assertionStyle: 'expect', // "expect" | "test.expect"

  // Performance
  parallel: true,
  maxConcurrency: 4,

  // Custom rules (optional)
  customRules: [],
};

export default config;

Selector Strategies

| Strategy | Behavior | | --------------- | ----------------------------------------------------------------------------------------- | | preserve | Keep original selectors as-is, only change the API (By.id("x") to page.locator('#x')) | | modernize | Convert to Playwright locator syntax but preserve selector strings | | best-practice | Upgrade to role-based and test-id selectors where possible (getByRole, getByTestId) |

Wait Strategies

| Strategy | Behavior | | ----------- | ------------------------------------------------------------------- | | preserve | Translate waits 1:1 (explicit waits, sleeps) | | auto-wait | Remove unnecessary waits -- Playwright auto-waits for actionability | | explicit | Convert all waits to explicit waitFor() calls |

Supported Transformations

Selenium (Java/JS) to Playwright

| Selenium | Playwright | | ------------------------------------------- | ------------------------------------------------ | | driver.get(url) | await page.goto(url) | | driver.findElement(By.id("x")) | page.locator('#x') | | driver.findElement(By.cssSelector("s")) | page.locator('s') | | driver.findElement(By.xpath("//a")) | page.locator('xpath=//a') | | element.sendKeys("text") | await locator.fill("text") | | element.click() | await locator.click() | | element.clear() | await locator.clear() | | element.getText() | await locator.textContent() | | element.getAttribute("href") | await locator.getAttribute("href") | | element.isDisplayed() | await locator.isVisible() | | driver.executeScript(js) | await page.evaluate(js) | | driver.switchTo().frame(ref) | page.frameLocator(selector) / contentFrame() | | driver.switchTo().alert().accept() | page.on('dialog', d => d.accept()) | | driver.manage().addCookie(c) | await context.addCookies([c]) | | driver.manage().getCookieNamed("n") | (await context.cookies()).find(...) | | driver.manage().deleteAllCookies() | await context.clearCookies() | | new Actions(driver).moveToElement(el) | await el.hover() | | new Actions(driver).dragAndDrop(src, tgt) | await src.dragTo(tgt) | | new Actions(driver).contextClick(el) | await el.click({ button: 'right' }) | | new Actions(driver).doubleClick(el) | await el.dblclick() | | new Select(el).selectByVisibleText("x") | await el.selectOption({ label: 'x' }) | | driver.getTitle() | await page.title() | | driver.getCurrentUrl() | page.url() | | driver.navigate().back() | await page.goBack() | | driver.getScreenshotAs(...) | await page.screenshot({ path }) | | driver.quit() | await browser.close() | | assertTrue(el.isDisplayed()) | await expect(locator).toBeVisible() | | assertEquals(title, "x") | await expect(page).toHaveTitle("x") |

WebdriverIO to Playwright

| WebdriverIO | Playwright | | ------------------------------------------- | ----------------------------------------------------- | | browser.url(path) | await page.goto(path) | | $('selector') | page.locator('selector') | | $$('selector') | page.locator('selector') | | $('sel').setValue('text') | await page.locator('sel').fill('text') | | $('sel').click() | await page.locator('sel').click() | | $('sel').doubleClick() | await page.locator('sel').dblclick() | | $('sel').clearValue() | await page.locator('sel').clear() | | $('sel').getText() | await page.locator('sel').textContent() | | $('sel').getValue() | await page.locator('sel').inputValue() | | $('sel').isDisplayed() | await page.locator('sel').isVisible() | | $('sel').waitForDisplayed() | await page.locator('sel').waitFor() | | $('sel').waitForDisplayed({reverse:true}) | await page.locator('sel').waitFor({state:'hidden'}) | | $('sel').moveTo() | await page.locator('sel').hover() | | $('sel').scrollIntoView() | await page.locator('sel').scrollIntoViewIfNeeded() | | $('sel').selectByVisibleText('x') | await page.locator('sel').selectOption({label:'x'}) | | browser.pause(ms) | await page.waitForTimeout(ms) | | browser.execute(fn) | await page.evaluate(fn) | | browser.keys('Enter') | await page.keyboard.press('Enter') | | browser.saveScreenshot(path) | await page.screenshot({ path }) | | browser.acceptAlert() | page.on('dialog', d => d.accept()) | | browser.dismissAlert() | page.on('dialog', d => d.dismiss()) | | browser.setCookies(cookies) | await context.addCookies(cookies) | | browser.deleteCookies() | await context.clearCookies() | | expect(el).toBeDisplayed() | await expect(locator).toBeVisible() | | expect(el).toHaveText(t) | await expect(locator).toHaveText(t) | | expect(el).toHaveTextContaining(t) | await expect(locator).toContainText(t) | | expect(el).toBeSelected() | await expect(locator).toBeChecked() |

Cypress to Playwright

| Cypress | Playwright | | ----------------------------- | ------------------------------------- | | cy.visit(url) | await page.goto(url) | | cy.get('selector') | page.locator('selector') | | cy.get('[data-testid="x"]') | page.getByTestId('x') | | cy.contains('text') | page.getByText('text') | | .click() | await locator.click() | | .type('text') | await locator.fill('text') | | .type('{enter}') | await locator.press('Enter') | | .clear() | await locator.clear() | | .check() / .uncheck() | await locator.check() / uncheck() | | .select(value) | await locator.selectOption(value) | | .should('be.visible') | await expect(locator).toBeVisible() | | .should('have.text', t) | await expect(locator).toHaveText(t) | | cy.intercept(method, url) | await page.route(url, handler) | | cy.screenshot(name) | await page.screenshot({ path }) |

Puppeteer to Playwright

| Puppeteer | Playwright | | --------------------------- | ------------------------------------------- | | puppeteer.launch(opts) | chromium.launch(opts) | | browser.newPage() | context.newPage() | | page.$(selector) | page.locator(selector) | | page.$$(selector) | page.locator(selector).all() | | page.$eval(sel, fn) | page.locator(sel).evaluate(fn) | | page.click(selector) | await page.locator(selector).click() | | page.type(sel, text) | await page.locator(sel).fill(text) | | page.waitForSelector(sel) | await page.locator(sel).waitFor() | | page.waitForXPath(xpath) | await page.locator('xpath=...').waitFor() | | page.waitForNavigation() | await page.waitForURL(pattern) | | page.setViewport(v) | await page.setViewportSize(v) | | page.evaluate(fn) | await page.evaluate(fn) | | page.screenshot(opts) | await page.screenshot(opts) |

Appium to Playwright

| Appium | Playwright | | ------------------------------------ | ---------------------------------------------- | | MobileBy.AccessibilityId("x") | page.getByLabel('x') | | AppiumBy.accessibilityId("x") | page.getByLabel('x') | | AppiumBy.id("com.app:id/btn") | page.locator('#com.app:id/btn') | | driver.findElementById("x") | page.locator('#x') | | driver.findElementByXPath("//x") | page.locator('xpath=//x') | | driver.findElementByClassName("x") | page.locator('.x') | | new TouchAction(driver).tap(el) | // [automigrate] Use page.touchscreen | | driver.context("WEBVIEW") | // [automigrate] Playwright handles natively | | driver.setLocation(lat, lon) | await context.setGeolocation({...}) |

200+ transformation rules are included across all frameworks. Each rule includes confidence scoring and flags low-confidence transforms for manual review.

Plugin API

Create custom plugins to extend automigrate with framework-specific parsers, additional transformation rules, or custom code generators.

import type { AutomigratePlugin } from 'automigrate';

const myPlugin: AutomigratePlugin = {
  name: 'my-custom-plugin',
  version: '1.0.0',

  // Scope to a specific source framework (optional)
  sourceFramework: 'selenium',

  // Add custom transformation rules
  transformationRules: [
    {
      id: 'custom-login',
      name: 'Login Helper',
      description: 'Convert custom login utility to Playwright fixture',
      sourceFramework: 'selenium',
      sourcePattern: /LoginHelper\.login\((\w+),\s*(\w+)\)/,
      targetTemplate: 'await login($1, $2)',
      confidence: 'high',
      category: 'custom',
      requiresManualReview: false,
      examples: [
        {
          input: 'LoginHelper.login(user, pass)',
          output: 'await login(user, pass)',
          language: 'java',
        },
      ],
    },
  ],

  // Lifecycle hooks
  async beforeMigration(config) {
    console.log(`Starting migration of ${config.sourceDir}`);
  },

  async afterMigration(report) {
    console.log(`Migrated ${report.summary.totalFiles} files`);
  },
};

export default myPlugin;

Examples

Selenium (Java) to Playwright (TypeScript)

Before:

@Test
public void testLogin() {
    driver.get("https://example.com/login");
    driver.findElement(By.id("username")).sendKeys("admin");
    driver.findElement(By.id("password")).sendKeys("secret");
    driver.findElement(By.cssSelector("button[type='submit']")).click();

    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("dashboard")));

    String title = driver.getTitle();
    assertEquals("Dashboard", title);
}

After:

import { test, expect } from '@playwright/test';

test('testLogin', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.locator('#username').fill('admin');
  await page.locator('#password').fill('secret');
  await page.locator("button[type='submit']").click();

  await page.locator('#dashboard').waitFor({ state: 'visible' });

  await expect(page).toHaveTitle('Dashboard');
});

WebdriverIO to Playwright

Before:

describe('Login', () => {
  it('should login with valid credentials', async () => {
    await browser.url('/login');
    await $('#username').setValue('admin');
    await $('#password').setValue('secret');
    await $('button[type="submit"]').click();
    await expect($('.welcome')).toBeDisplayed();
    await expect($('.welcome')).toHaveTextContaining('Welcome');
  });
});

After:

import { test, expect } from '@playwright/test';

test.describe('Login', () => {
  test('should login with valid credentials', async ({ page }) => {
    await page.goto('/login');
    await page.locator('#username').fill('admin');
    await page.locator('#password').fill('secret');
    await page.locator('button[type="submit"]').click();
    await expect(page.locator('.welcome')).toBeVisible();
    await expect(page.locator('.welcome')).toContainText('Welcome');
  });
});

Cypress to Playwright

Before:

describe('Search', () => {
  it('should return results', () => {
    cy.visit('/search');
    cy.get('[data-testid="search-input"]').type('playwright');
    cy.get('[data-testid="search-button"]').click();
    cy.get('.results-list').should('be.visible');
    cy.get('.results-list li').should('have.length.greaterThan', 0);
  });
});

After:

import { test, expect } from '@playwright/test';

test.describe('Search', () => {
  test('should return results', async ({ page }) => {
    await page.goto('/search');
    await page.getByTestId('search-input').fill('playwright');
    await page.getByTestId('search-button').click();
    await expect(page.locator('.results-list')).toBeVisible();
    await expect(page.locator('.results-list li')).not.toHaveCount(0);
  });
});

Roadmap

  • [x] Selenium (Java/JS) to Playwright
  • [x] Cypress to Playwright
  • [x] Puppeteer to Playwright
  • [x] WebdriverIO to Playwright
  • [x] Appium mobile test transformations
  • [x] Robot Framework support
  • [x] Watch mode for incremental migration
  • [ ] Interactive mode with per-transformation approval
  • [ ] VS Code extension for inline migration suggestions
  • [ ] Migration progress dashboard
  • [ ] C# parser (NUnit/xUnit with Selenium WebDriver)

Contributing

# Clone the repository
git clone https://github.com/vazidmansuri005/automigrate.git
cd automigrate

# Install dependencies
npm install

# Run the dev build (watch mode)
npm run dev

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Lint
npm run lint
npm run lint:fix

# Type check
npm run typecheck

# Format
npm run format

# Run the CLI locally
npm run cli -- analyze ./path/to/tests

Project Structure

src/
  cli/              CLI commands (commander)
  config/           Configuration loading and defaults
  core/
    transformers/   Transformation engine (200+ regex rules + smart patterns)
    parsers/        Language-specific parsers (Java, JS/TS, Gherkin, Python)
    generators/     Playwright code generators
    analyzers/      Framework detection and structure analysis
    reporters/      Migration report formatters
  mappings/         Framework-to-Playwright API mapping tables
  types/            TypeScript type definitions
  utils/            Shared utilities (diff, logging)
tests/
  unit/             386 unit tests (vitest)
  fixtures/         Test fixtures for all supported frameworks
  e2e/              End-to-end integration tests

License

MIT