Refactoring Existing Test Code

9 min read

Bulk refactoring is where Claude Code saves QA engineers the most time. The work is necessary, high-volume, and largely mechanical — exactly the profile where AI assistance compresses hours into minutes. This lesson covers the most common refactoring patterns, how to keep Claude Code targeted, and the review practices that prevent a large refactor from introducing subtle regressions.

When to reach for Claude Code

The best fits for AI-assisted refactoring share a profile:

  • High-volume — the same change repeated across many files (selector strategy, import paths, API names)
  • Structural — adopting a pattern like Page Objects across an existing suite
  • Mechanical — predictable transformations like moving from cy.get('.btn') to cy.findByTestId('submit-button')

The worst fits are refactors that require business reasoning — deciding whether a test's assertion actually reflects intended behaviour, or whether an entire test should be deleted rather than changed. Those still need a human.

Selector strategy migration

The team has decided to move from CSS selectors to data-testid attributes. There are 200 test files. Manual find-and-replace is not an option.

All tests in cypress/e2e/ currently use cy.get('#submit-btn').
The new convention is cy.findByTestId('submit-button').
 
Do the following:
1. Search cypress/e2e/ for all instances of cy.get('#submit-btn')
2. Replace each with cy.findByTestId('submit-button')
3. List the files you changed
4. Do not run the suite yet — I want to review the diff first

Asking for the file list and deferring the run gives you a checkpoint. Review with git diff before approving the test run. For a mechanical change like this, reviewing 10 changed files takes 3 minutes.

Page Object adoption

Moving from inline tests to a Page Object Model:

Look at tests/checkout.spec.ts. All selectors and interactions are inline.
 
Refactor this to use a Page Object:
1. Create src/pages/CheckoutPage.ts with locators as getters and interactions as methods
2. Update tests/checkout.spec.ts to use the page object
3. Show me the structure of CheckoutPage.ts before writing anything

The "show me the structure before writing" line is valuable. You see the proposed class interface, catch anything that does not make sense for your domain, and redirect before any files change.

Framework API upgrades

Updating deprecated APIs — for example, Cypress Cypress.Cookies.preserveOncecy.session():

Find all uses of Cypress.Cookies.preserveOnce in cypress/.
For each one, explain the equivalent cy.session() pattern, then apply it.
Work file by file — show me each change before moving to the next.

The "file by file" instruction is a deliberate pacing choice. For a change this structural, reviewing each transformation is faster than approving a batch of 50 at once and finding a systematic error halfway through.

The plan-first pattern

For any refactor affecting more than five files, ask for a plan before execution:

I want to refactor our test suite to use a consistent fixture loading pattern.
Currently some tests import fixtures inline, some use beforeEach, 
some use Cypress fixtures.
 
Before making any changes:
1. Summarise the patterns you find in the codebase
2. Propose a unified approach
3. Estimate how many files are affected
 
I'll review your proposal before you start.

This prevents the most expensive kind of mistake: a session that makes 50 changes in one direction before you realise it chose the wrong approach.

Refactoring approach — manual vs Claude Code-assisted

Manual refactor

  • IDE find + replace — misses dynamic patterns

  • Files touched one at a time

  • Inconsistent application across the codebase

  • Hours for 50+ file changes

  • Easy to miss edge cases in complex transforms

Claude Code-assisted

  • Semantic search — finds patterns, not just strings

  • All files in one pass with a summary of changes

  • Consistent: applies the same transformation everywhere

  • Minutes for 50+ files — hours freed for review

  • Plan-first lets you catch the wrong approach early

Review strategy for large refactors

After Claude Code runs, before executing the test suite:

  1. git diff --stat — see which files changed and how many lines
  2. git diff — read a sample of the actual changes (20–30 files is auditable; 200 is not — sample strategically)
  3. Run the test suite and treat any new failure as a regression introduced by the refactor

If a refactor produces an unexpectedly large diff, stop and investigate before running tests. A 500-line diff for a "simple" selector rename is a signal that Claude Code did something broader than asked.

⚠️ Common Mistakes

  • Bulk refactoring on a dirty branch. Always start a refactor from a clean git state on a dedicated branch. That way git diff shows only the refactor, not a mix of unrelated changes.
  • Skipping the test run after. "The changes look right" and "the tests still pass" are different things. Always run the full affected suite after a bulk refactor.
  • Asking for too much at once. "Refactor the entire test suite to use Page Objects" is not one task — it is twenty. Break it into modules and work through incrementally.

🎯 Practice Task

Run a targeted bulk refactor. 20–30 minutes.

  1. Find a pattern in your test suite that should be changed — a deprecated selector, a repeated setup block, a hardcoded string that should be a constant.
  2. Use the plan-first approach: ask Claude Code to summarise what it finds before changing anything.
  3. Approve the plan, then ask Claude Code to apply the change. Request a summary of changed files before running the test suite.
  4. Review the diff. Run the affected tests.
  5. Note: what did Claude Code get right? What required manual adjustment?

The next lesson applies these same principles to a specific, high-value task: building Page Objects for UI that already exists.

// tip to track lessons you complete and pick up where you left off across devices.