Skip to content

3 min read

Migration Guide

You don’t migrate a POM suite to CDAT in one big bang. You do it feature-by-feature, with old and new patterns coexisting until the last Page Object is gone.

Step 1 - Pilot one feature (week 1)

Pick a feature with 5-15 tests that the team understands well. Login is a classic choice because it’s small but exercises auth, forms, and assertions.

Create the new structure:

features/
└── login/
  ├── components.ts
  ├── data.ts
  ├── actions.ts
  └── test.ts

Run both old and new test suites in CI. The new suite proves CDAT works in your environment; the old suite stays as the safety net.

Step 2 - Lock in the smart-wait utility

Add @cdat/utils (or copy the Cdat class source directly):

pnpm add -D @cdat/utils

Replace every page.waitForTimeout() in your existing tests with the appropriate smart-wait helper. This is the highest-leverage change you can make to your suite - flake usually drops 30-50% just from this.

Step 3 - Add ESLint guardrails

{
"rules": {
  "@typescript-eslint/no-explicit-any": "warn",
  "no-restricted-syntax": [
    "warn",
    {
      "selector": "MemberExpression[property.name='waitForTimeout']",
      "message": "Use Cdat smart waits instead"
    }
  ]
}
}

Set both to warn initially so the team sees the violations during review without breaking the build. After 2 sprints of cleanup, promote to error.

Step 4 - Migrate features in priority order

Order features by flake rate descending. The fix-this-flaky-feature project doubles as your migration project.

Per-feature steps, in order:

  1. Move locators - extract page.locator() calls from the PageObject into a dedicated components.ts.
  2. Move test data - pull inline strings out of the PageObject and into data.ts with proper types.
  3. Move user flows - port the PageObject’s high-level methods into actions.ts, dropping any embedded assertions on the way.
  4. Move scenarios - relocate the actual test() blocks plus their expect() calls into test.ts.
  5. Run both suites in CI - keep the old PageObject tests and the new CDAT tests running side-by-side for one full sprint to catch regressions.
  6. Delete the old PageObject - once the new suite is stable, remove the legacy file and delete its imports.

Step 5 - Retire the pages/ folder

When the last feature has been migrated, delete pages/. Add an ESLint rule to forbid recreating it:

{
"rules": {
  "no-restricted-imports": [
    "error",
    { "patterns": ["**/pages/**"] }
  ]
}
}

Common migration pitfalls

Mixing patterns inside one feature. If you start migrating Login but leave half the locators in the old PageObject, you’ve created a worse mess than either pure pattern. Finish the feature.

Re-using the old PageObject from new Actions. Tempting, but defeats the purpose. Copy the locators into the new components.ts and delete the old PageObject when done.

Skipping data.ts because “it’s just strings”. Hardcoded strings in tests are the #2 source of test debt (after waitForTimeout). Force the discipline now.

Next steps