Large-Scale Test Framework Migration Using LLMs

airbnb · Tech · 2024 · source Agentic: yes · promotional bias: low · source-backed · extracted with prompt 0.5.6 (schema v2)

Generated from extraction.json — do not edit by hand. Every claim cites verbatim source evidence; stated means the quote directly asserts the claim, implied means it supports it indirectly.

Problem

Domain: Large-scale code migration and test modernization

Airbnb needed to migrate nearly 3.5K React component test files from Enzyme to React Testing Library without losing original test intent or code coverage. The migration could not be a simple find-and-replace because the frameworks encourage different testing styles, and the original manual estimate was 1.5 years of engineering time.

Constraints:

Architecture

Airbnb built a state-machine migration pipeline where each test file moved through Enzyme-to-RTL refactor, Jest fixes, lint/TypeScript fixes, and completion states. Failed validation triggered LLM repair attempts with updated error context and the latest file version. Prompts grew to 40K-100K tokens with component source, target tests, sibling tests, imports, RTL examples, and guidelines. The team used status comments, step-filtered reruns, and a sample-tune-sweep loop to push success from 75% to 97%, then manually finished the remaining 3% using LLM-generated baselines.

Diagram confidence: explicit (topology described by the source).

flowchart LR
  corpus["Target Enzyme test corpus"] --> state["Migration state machine"]
  state --> validate["Validation runner"]
  validate --> loop["LLM repair loop"]
  context["Context assembler"] --> loop
  selector["Related-file selector"] --> context
  loop --> output["Migrated RTL test file"]
  output --> validate
  state --> status["Migration status tracker"]
  status --> rerun["Targeted rerun tool"]
  rerun --> sweep["Sample tune sweep loop"]
  sweep --> context
  sweep --> manual["Human migration finish"]
  manual --> output
Drag to pan · scroll or use +/− to zoom · reconstructed from the source's described component topology

Components

Component Role Interacts with
Target Enzyme test corpus Nearly 3.5K React component tests that needed semantic migration to RTL. Migration state machine
Migration state machine Per-file orchestration that advances only after validation passes for each refactor stage. Target Enzyme test corpus, Validation runner, LLM repair loop, Migration status tracker
Validation runner Runs Jest, lint, TypeScript, and completion checks to decide whether a file can advance. Migration state machine, LLM repair loop
LLM repair loop Retry loop that sends updated validation errors and current file state to frontier models. Validation runner, Context assembler, Migrated RTL test file
Context assembler Builds large prompts from component source, test file, sibling tests, imports, examples, and migration guidelines. LLM repair loop, Related-file selector
Related-file selector Selects nearby files, good examples, and relevant dependencies that make the prompt useful. Context assembler
Migration status tracker Stamps files with generated status comments and pass/fail metrics for each migration step. Migration state machine, Targeted rerun tool
Targeted rerun tool Lets engineers rerun individual files or path patterns filtered by the failed step. Migration status tracker, Sample tune sweep loop
Sample tune sweep loop Human-guided iteration process that samples common failures, updates prompts/scripts, validates fixes, and sweeps remaining files. Context assembler, Targeted rerun tool, LLM repair loop, Human migration finish
Human migration finish Manual intervention for the final long-tail files after automation reached its practical ceiling. Sample tune sweep loop, Migrated RTL test file
Migrated RTL test file Output test file expected to preserve original test intent and coverage while using RTL. Validation runner, Human migration finish

Data flow

Each Enzyme test file enters a per-file state machine. The pipeline validates a migration step; if validation fails, it builds a dynamic prompt with the current file, validation errors, component source, related files, examples, and guidelines, then asks the LLM to repair the file. Status comments record step progress and test counts. Engineers inspect aggregate failures, sample 5-10 representative files, update prompts or scripts, rerun the sample, and sweep the remaining failing files. Files that hit the automation ceiling are handed to engineers with generated baselines.

4 supporting quotes
“We started by breaking down the migration into a series of automated validation and refactor steps.”article, stated
“we bring in the LLM to fix it.”article, stated
“We updated our steps to use dynamic prompts for each retry, giving the validation errors and the most recent version of the file to the LLM”article, stated
“we opted to manually fix the remaining files, working from the baseline (failing) refactors”article, stated

Why this architecture

Airbnb used a state machine because code migration could be decomposed into discrete, validated stages with clear pass/fail signals. Retry loops were useful because validation errors and the current file gave the LLM concrete feedback. Rich context was needed for complex test setups and team-specific conventions. The breadth-first sample-tune-sweep loop prevented the team from overfitting to one hard file while steadily reducing common failure modes across the remaining corpus.

4 supporting quotes
“We modeled this flow like a state machine, moving the file to the next state only after validation on the previous state passed”article, stated
“we found the most effective route to improve outcomes was simply brute force: retry steps multiple times until they passed or we reached a limit.”article, stated
“the main success driver we saw was choosing the right related files”article, stated
“We also wanted to do this breadth first to aggressively chip away at our remaining files without getting stuck on the most difficult migration cases.”article, stated

Models used: frontier models · LLM-driven code migration

Scale

Alternatives considered (roads not taken)

Manual migration

Rejected because: Airbnb estimated manual work would take 1.5 years, whereas the LLM-assisted pipeline finished in six weeks.

“We’d originally estimated this would take 1.5 years of engineering time to do by hand”article, stated
“we finished the entire migration in just 6 weeks.”article, stated

Delete legacy Enzyme files

Rejected because: Analysis showed deletion would create significant code-coverage gaps.

“We also couldn’t just delete the Enzyme files, as analysis showed this would create significant gaps in our code coverage.”article, stated

Simple framework swap or find-and-replace

Rejected because: The frameworks differ fundamentally, requiring preservation of test intent and coverage rather than syntax substitution.

“because of the fundamental differences between these frameworks, we couldn’t easily swap out one for the other”article, stated
“we needed an automated way to refactor test files from Enzyme to RTL while preserving the intent of the original tests and their code coverage.”article, stated

Keep tuning automation after the 97% mark

Rejected because: The remaining files had hit a practical automation ceiling, so engineers manually completed them from generated baselines.

“it seemed we were pushing into a ceiling of what we could fix via automation. Rather than invest in more tuning, we opted to manually fix the remaining files”article, stated

Patterns

tool-use-orchestration (agentic-code)

The system repeatedly invoked an LLM as a repair agent only when concrete validation steps failed, passing errors and current code as tool feedback.

“each file moves through stages of validation, and when a check fails, we bring in the LLM to fix it.”article, stated
“giving the validation errors and the most recent version of the file to the LLM”article, stated

durable-execution (workflow)

The migration was modeled as a resumable state machine with per-file status, step tracking, and targeted reruns.

“We modeled this flow like a state machine, moving the file to the next state only after validation on the previous state passed”article, stated
“It enabled us to track progress, improve failure rates for specific steps, and rerun files or steps when needed.”article, stated

context-engineering (proposed-context)

Airbnb found that selecting the right related files, examples, imports, and component context mattered more than prompt wording for hard migrations.

“This rich context approach proved highly effective for these more complex files”article, stated
“the main success driver we saw was choosing the right related files”article, stated

eval-driven-development (quality)

Automated Jest, lint, TypeScript, and migration-step validations drove each LLM retry and improvement loop.

“validation and refactor steps”article, stated
“when a check fails, we bring in the LLM to fix it.”article, stated

human-in-the-loop-gate (workflow)

Humans sampled failure modes, tuned prompts/scripts, and manually completed the final 3% once automation reached diminishing returns.

“Select a sample of files (5 to 10) that exemplify a common issue”article, stated
“we opted to manually fix the remaining files, working from the baseline (failing) refactors”article, stated

observability-tracing (operations)

Generated migration-status comments exposed per-step pass/fail counts and helped engineers find common failure patterns.

“stamping files with an automatically-generated comment to record the status of each migration step.”article, stated
“This provided visibility into common issues the scripts were facing”summary, stated

Tradeoffs

Migration workflow structure

Chose state-machine pipeline with step validation over single-pass LLM conversion of all files.

Rationale: A step-based state machine allowed parallelism, progress tracking, targeted retries, and per-step failure-rate improvement.

“We modeled this flow like a state machine, moving the file to the next state only after validation on the previous state passed”article, stated
“It enabled us to track progress, improve failure rates for specific steps, and rerun files or steps when needed.”article, stated

Retry strategy

Chose brute-force retry loops with dynamic validation feedback over more elaborate prompt engineering alone.

Rationale: The team found repeated attempts with fresh error context were the most effective way to improve per-file outcomes.

“we found the most effective route to improve outcomes was simply brute force: retry steps multiple times until they passed or we reached a limit.”article, stated
“With this simple retry loop, we found we could successfully migrate a large number of our simple-to-medium complexity test files”article, stated

Long-tail improvement strategy

Chose sample-tune-sweep across common failures over manual deep-dives into each failed file.

Rationale: Sampling representative failures helped improve prompts/scripts in ways that affected many remaining files at once.

“Run all remaining failing files to find common issues the LLM is getting stuck on”article, stated
“Repeat by running against all remaining files again”article, stated

Automation cutoff

Chose manual intervention for the final 3% over indefinite prompt/script tuning.

Rationale: After 50-100 retries on long-tail cases, manual completion from generated baselines was more efficient.

“By this point, we had retried many of these long tail files anywhere between 50 to 100 times”article, stated
“Rather than invest in more tuning, we opted to manually fix the remaining files”article, stated

Gotchas (what bit them)

Passing automated checks is necessary but may not prove semantic test equivalence.

Root cause: The migration validates Jest, lint, TypeScript, and completion, but preserving test intent is subtler than syntax and pass/fail status.

Resolution: Keep human review for the long tail and explicitly target preservation of original intent and coverage as an outcome.

“preserving the intent of the original tests and their code coverage.”article, stated
“Most importantly, we were able to replace Enzyme while maintaining original test intent and our overall code coverage.”article, stated

Prompt structure was less important than selecting the right context.

Root cause: Complex tests depended on team-specific patterns and related files that the model could not infer from the target file alone.

Resolution: Pull in component source, sibling tests, examples, imports, and relevant nearby files, with context up to 40K-100K tokens.

“For test files up to a certain complexity, just increasing our retry attempts worked well.”article, stated
“to handle files with intricate test state setups or excessive indirection, we found the best approach was to push as much relevant context as possible”article, stated

Long-tail files can consume huge numbers of retries without resolving.

Root cause: The hardest migration cases remained stuck even after many dynamic retry attempts.

Resolution: Stop after the automation ceiling and manually finish from LLM-generated baselines.

“we had retried many of these long tail files anywhere between 50 to 100 times”article, stated
“Rather than invest in more tuning, we opted to manually fix the remaining files”article, stated

Optimizing one hard case at a time would stall progress.

Root cause: The remaining failures contained common patterns plus difficult outliers, so depth-first debugging could overfocus on worst cases.

Resolution: Use a breadth-first sample-tune-sweep loop to address common failures across all remaining files.

“We also wanted to do this breadth first to aggressively chip away at our remaining files without getting stuck on the most difficult migration cases.”article, stated
“Select a sample of files (5 to 10) that exemplify a common issue”article, stated

Evaluation

Evaluation relied on staged automated validation for each file, status comments with test counts and step state, bulk success-rate tracking, sample-based prompt/script validation, and final human completion to preserve intent and coverage.

Metrics: Jest tests passed/failed/skipped · lint status · TypeScript compilation status · per-step migration status · bulk file completion rate · manual remaining-file count · overall test intent and code coverage preservation

3 supporting quotes
“Here’s what that code comment looked like: // MIGRATION STATUS: {"enyzme":"done","jest":{"passed":8,"failed":2,"total":10,"skipped":0,"successRate":80}”article, stated
“After four days of prompt and script refinement using the “sample, tune, and sweep” strategy, we reached 97% of the 3.5K original Enzyme files.”article, stated
“Most importantly, we were able to replace Enzyme while maintaining original test intent and our overall code coverage.”article, stated

Outcomes

The migration covered nearly 3.5K files, prompts of 40K-100K tokens, hundreds of concurrent files, and long-tail retries of 50-100 attempts.

3 supporting quotes
“With the validation and refactor pipeline, retry loops, and expanded context in place, we were able to automatically migrate 75% of our target files in 4 hours.”article, stated
“After four days of prompt and script refinement using the “sample, tune, and sweep” strategy, we reached 97% of the 3.5K original Enzyme files.”article, stated
“the total cost — including LLM API usage and six weeks of engineering time — proved far more efficient than our original manual migration estimate.”article, stated