Tech

No QA Team, No Time: How We Made Test Automation Stick with AI Agents

2025.12.03 · Hyorin Lee, Sumi Byeon

Read the original in Korean
No QA Team, No Time: How We Made Test Automation Stick with AI Agents

No QA Team, No Time: How We Made Test Automation Stick with AI Agents

Author
Hyorin Lee, Sumi Byeon
Date
Dec 3, 2025
Description
What we learned automating test code writing with AI agents built on Claude Code.
Featured
Featured
KO slug
ai-test-code-automation-experience-lessons-learned
Status
Published
Tag
Tech
Translation
AI draft
Translated from the Korean original. Some screenshots show the original Korean interface.

Introduction

Hi, we're Hyorin Lee and Sumi Byeon, frontend developers on the LLM Platform Team. Writing test code always gets pushed to the back of the queue, so in this post we share how our team automated it with AI and what we learned. If you've done some development, you've probably heard of test code and TDD (Test-Driven Development), wondered about them, and maybe even tried to apply them to your own project.
 
“I really should write some tests...”
“This is exactly the kind of case that needs tests...”
Our team needed test code even more than most, because of the situation below.
  • No QA team
  • A lot of features to build in a short time
  • Developers do QA among themselves, miss a lot, and it's a waste of their time
 
Even so, we still weren't writing test code.
 
 

1. Why testing is hard to adopt in practice

If we needed it this badly, why weren't we writing tests?

1-1. Structural and organizational factors

Write perfect code and miss the deadline vs. write code that just works and hit the deadline
No developer on a real project picks the first option. Test code is less about the feature in front of you and more of an investment for later. For most projects, this week's demo or this release matters more.
 

1-2. Technical factors

  • Code that is hard to test
    • When existing code is a tangle of dependencies, adding tests is very hard. If a React component mixes API calls, state management, and DOM manipulation, you can spend a whole day just on mocking.
       
  • Fatigue from setting up a test environment
    • Vitest, Jest, Cypress, MSW, React Testing Library… There are plenty of testing tools, but even the setup is not easy. People setting up a test environment end up saying, “The config takes longer than the tests.”
       
  • Hard to maintain
    • When tests are written in too much detail, changing a single line of code breaks dozens of them. Eventually people start to see tests as “something that holds us back,” and from then on nobody writes them.
 

1-3. Cultural and perception factors

  • No visible results
    • Test code doesn't show up on screen. With no visible result like “this feature works,” writing tests is seen as work that produces nothing.
       
  • Lack of test-writing skills
    • Most developers are comfortable building features, but not with deciding “what to test, and how far.” Pick the wrong unit of testing and tests become tedious and useless.
 
This post is not going to make the textbook argument that “you should write tests for code quality.” Everyone already knows tests are needed. The problem is how to actually make it work in practice. So in this post we share how our team, on a real project, wrote test code quickly with few resources and verified it efficiently.
 
 

2. The test code rules our team set

 
Before writing any tests, we didn't have a clear sense of how they should be written. The same questions kept coming up:
 
"Do we have to test all the code?"
"Where do we start?"
"What exactly should we mock, and where do we stop?"
 
To answer them, we set the following two rules.

2-1. The test pyramid model

 
Mike Cohn introduced the concept of the “test pyramid” in his book Succeeding with Agile .
 
Test pyramid model
 
 
The goal of the pyramid is:
“Build an efficient set of tests that are fast, stable, and easy to maintain.”
 
So, following the pyramid model, we decided on this mix across all our test code:
  • Unit tests: 70% - fast and stable, always run in CI
  • Integration tests: 20% - focused on core user flows
  • E2E tests: 10% - critical paths only (login, payment, etc.)
 

2-2. When, and what, to verify?

Verification approach
Test target
Reason
Verify only what appears on screen
React components
Tests from the user's point of view, in line with the RTL philosophy
ㅤ
Pure functions / utils
No dependencies / no mocks needed
Verify code flow and interactions between internal logic
Business logic classes
Collaboration between objects must be verified explicitly
ㅤ
Complex workflows
Verify call order and conditional branches
Verify logic only at the API call layer; otherwise verify what appears on screen
API communication
Keeps the real network flow intact
 
Core principles
  • What the user sees (UI, results) → verify what appears on screen
  • Collaboration between objects (function calls, order matters) → verify code flow and interactions between internal logic
  • Keep mocks to a minimum, only where they're truly needed!
 
 

3. Building a test agent with Claude Code

Now let's get into how we automate testing with an AI agent.
EXEM officially supports Claude and Cursor as AI development tools, so we use Claude in our day-to-day work. The rest of this post is therefore based on Claude Code.
This post uses Claude Code, but the same approach works with other AI tools such as GPT-Codex and Copilot.
 
 
Overall architecture
We built our test automation system on Claude Code. Claude Code is an AI agent that runs directly from the command line and can automate everything from writing code to running tests.
 

Step 1: Build the project context

We write a CLAUDE.md file so the AI agent can understand our project's rules and structure.
Here's a quick look at the file we wrote.
  • For mocking in tests, we set it up so mocking starts at the network level.
  • For everything else, such as i18n and TanStack Query, we built a render function that uses the real provider values, and the tests use that function.
 

Step 2: Create subagents

We create a test-writing agent specialized for each test level.

Unit test subagent

 

Integration test subagent

What held us back most in integration tests was mocking. So we defined our team's own mocking boundary and set up the agent to write test code within it.
Each agent reads CLAUDE.md and writes tests that follow the project's conventions.

How routing works

 

End-to-end workflow

Claude Code reads CLAUDE.md, understands the project's rules, and then generates tests that follow them.
 
 

4. Hands-on: util tests and a login form integration test

Now let's use the subagents we created to write tests, using a real login page as the example.
 
Generating unit tests automatically
Request: lib/utils.ts : write test code for this file.
Claude Code took the request, made the following plan for writing the tests, and carried it out.
 
Claude Code planning unit test generation
 
The test code and files were created.
Generated test code and files
 
Here is part of the generated test code.
 
When we run the tests, they all pass.
Test run results
 
Integration tests
We asked for test code for the LoginForm.tsx component.
Requesting an integration test
 
The test code is done.
Test code writing completed
 
When we run it, we can see that a few tests failed.
List of failed tests
 
 
Side note: when tests fail
A few tests failed! Now we're at a fork in the road.
  1. The test code is wrong.
  1. Our code is wrong.
 
When we simply told the AI, “fix the failing tests so they pass,” we ran into a problem: it accepted the buggy existing code as correct and changed only the tests to make them pass.
To prevent this, we gave the agent an explicit rule: “When a test fails, first analyze the cause, verify whether the existing code is correct, and then propose how to fix it.”
 
Let's ask the agent to find out why the tests failed.
Asking the agent to find the cause of test failures
 
It identified the causes of the failures as follows.
Identified causes of test failures
  1. Input component
    1. In this case, the test failed because the error style and type were used in a way that broke accessibility. → The component needs to be fixed.
  1. Form Validation
    1. The empty-input test failed because the required attribute made the browser block empty input. → This is correct browser behavior, so the test code needs to change.
Let's fix things based on the causes it found.
Fixes based on the identified causes, part 1
Fixes based on the identified causes, part 2
 
Based on Claude's analysis, we split the work into what needed fixing in the component and what needed fixing in the test code, made the changes, and now all tests pass.
All tests passing
 
 

5. Automating E2E tests with Playwright MCP

So far we've shown how we use agents to automatically write and run unit test and integration test code.
But to guarantee the quality of a real service, E2E (End-to-End) tests are also essential.
 
Yet writing E2E test code is harder than it looks. You have to:
  • define test scenarios,
  • find the selector for each element on screen and write the click, input, and assertion logic by hand,
  • collect screenshots or videos when a test fails,
  • and write up bug reports.
All of this takes a long time and is a pain to maintain.
💡 The tool we brought in to solve this problem is Playwright MCP.
 

Introducing Playwright MCP

Playwright MCP is a tool that generates and runs E2E tests from natural language, and it can even file GitHub Issues automatically when tests fail.
In other words, you can automate the whole cycle: write tests → run → report results → manage issues.
 
Our automation system is built on Microsoft's @playwright/mcp library. Once you register this library as an MCP server, agents like ChatGPT or Claude Code can run Playwright directly and create, modify, and re-run tests.
 
Key features:
  • Write test scenarios in natural language
  • Run automatically and check results
  • Capture screenshots/videos automatically on failure
  • File GitHub Issues automatically
 
Since we'll be using it with Claude Code, we can connect the MCP with the command below.
A single command is all it takes to register the MCP server.
Registering the MCP server
 
We confirmed that the MCP is connected.
Now if you type “Go to Google and search for Naver” in Claude Code, it runs Playwright itself and searches for Naver.
Confirming the MCP connection
 
 

Hands-on: E2E tests for the login page

Step 1: Request tests in natural language
Let's use it to write E2E test code.
Request: Write E2E code for the login page. Be sure to include the scenario where a successful login routes to the home screen.
Requesting tests in natural language
 
Step 2: Claude writes the test code
It generates test code that respects the mocking boundary defined in the CLAUDE.md file.
Claude writing the test code
 
It generated about 7 scenarios like the one below.
 
Step 3: Automatic execution
Once the test code is written, Playwright MCP runs the tests automatically.
 
7 tests failed: 5 because of issue 1 and 2 because of issue 2. Issues 1 and 2 were both mistakes in the test code, so Claude started fixing them.
Claude starting to fix the tests
 
Step 4: File a GitHub Issue automatically on failure
But what if the problem is in our code, not the test code?
A mistake in the test code can be fixed quickly, but a mistake in our own code can take a long time to fix.
 
For these cases, we built a system that files GitHub Issues automatically.
When a test fails, the following process runs automatically.
 
Failed E2E tests:
When the E2E run finishes, a test report is generated. These were the failures:
So,
Failed E2E tests in the test report
 
The GitHub Issue that was actually created: We could see the failure filed automatically as a GitHub Issue.
Automatically created GitHub Issue
 
 

6. The full E2E automation flow

Full E2E automation flow
 
 

7. Wrapping up

Before we built the test agents, we thought of test code as a luxury. There was too much to prepare before we could write any tests, and the high maintenance cost of updating existing tests by hand every time requirements changed felt like a heavy burden.
 
But while experimenting with AI agents, we saw that some of this repetitive fix-up work can be automated. That took a lot of the mental weight off writing tests.
 
Some parts still need a human. Even if an AI agent generates and fixes code on its own, understanding the intent and context of a test and deciding what needs to be verified is still the developer's job.
 
In the end, AI agents are not a silver bullet, but they are clearly a tool that noticeably lowers the barrier to writing test code.
 
"Do we really have to write test code?"
Here's our answer now:
"Yes. But why not trust AI with it?"
 
 
References
Takefumi Yoshii, "Introduction to Testing for Frontend Development" (Korean edition), 2024 Vladimir Khorikov , "Unit Testing: Principles, Practices, and Patterns" (Korean edition), 2021 Roy Osherove, Vladimir Khorikov, "The Art of Unit Testing" (Korean edition), 2024 Playwright MCP - Microsoft Claude Code Documentation
 
 

 
 

 
 
Related articles