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 .

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.

The test code and files were created.

Here is part of the generated test code.
When we run the tests, they all pass.

Integration tests
We asked for test code for the
LoginForm.tsx component. 
The test code is done.

When we run it, we can see that a few tests failed.

Side note: when tests fail
A few tests failed! Now we're at a fork in the road.
- The test code is wrong.
- 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.

It identified the causes of the failures as follows.

- Input component
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.
- Form Validation
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.


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.

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.

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.

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.
Step 2: Claude writes the test code
It generates test code that respects the mocking boundary defined in the
CLAUDE.md file.
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.

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,

The GitHub Issue that was actually created:
We could see the failure filed automatically as a GitHub Issue.

6. The 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

