Testing

Leveraging AI and MCP Servers to Build AI Tests

How AI agents and MCP servers can be combined to automate test generation and execution in modern development workflows.

Building E2E Tests with AI, MCP, and Playwright

Introduction

If you are new to frontend development, writing end-to-end (E2E) tests can feel overwhelming. You have to figure out the right selectors, deal with timing issues, and keep your tests from breaking every time the UI changes. That is a lot to handle on top of everything else you are already learning.

The good news is there is a smarter way to approach this. By combining AI with something called MCP (Model Context Protocol) and a tool called Playwright, you can let AI observe how your application actually behaves and generate tests based on that. You do not have to guess - the AI watches, then writes.

What Is the Core Idea?

Most AI tools that write tests do so by reading your code. The problem is that reading code is not the same as using your app. Things can look fine on paper but break in the browser.

With MCP and Playwright, AI works differently. Instead of analyzing your code statically, it opens a real browser, clicks around your application, watches what happens, and writes tests based on what it actually sees. This is a much more reliable foundation for your tests.

What Does MCP Do?

MCP is a standardized way for AI to interact with outside tools - not just generate text, but actually do things.

Without MCP, AI can only respond with words. With MCP, AI can take actions: open a browser, fill in a form, click a button, inspect what changed on the page. It becomes a working agent rather than just a writing assistant.

What Does Playwright Add?

Playwright is the tool that gives AI the ability to control a browser. Through Playwright MCP, AI can:

  • Visit pages in your application
  • Type into input fields and submit forms
  • Click buttons and watch what the UI does in response
  • Read what content appears on screen
  • Monitor network requests as they happen

This means the tests AI generates are based on real interactions, not assumptions.

The Selector Problem (and Why It Matters to You)

When you write a test, you need a way to tell it which element to interact with. A common mistake - especially early on - is targeting elements by their CSS class names. The problem is that modern frameworks often generate class names that look like random strings:

html

These names change between builds. Your test breaks, and it is not obvious why.

The better approach is to add dedicated test identifiers to your HTML, like this:

html

These are stable, readable, and have nothing to do with styling. Keep your styling classes separate from your test identifiers - they serve different purposes.

How AI Generates a Test: A Real Example

Say you have a search feature with autocomplete. Here is how the AI-driven process works, step by step.

Step 1 - Navigate: AI goes to the relevant page, for example /search.

Step 2 - Inspect: AI looks for input fields, result containers, and test identifiers.

Step 3 - Interact: AI types into the search field and watches what the UI does.

Step 4 - Observe network activity: AI notices any API calls made in response to the input.

Step 5 - Write the test:

import { test, expect } from '@playwright/test';
test.describe('Search autocomplete', () => {
test('typing "tata" shows suggestions', async ({ page }) => {
await page.goto('http://localhost:3000');
const search = page.locator('input[placeholder="Search for Stocks"]').first();
await expect(search).toBeVisible({ timeout: 5000 });
// Click to focus and trigger fetchStocks()
await search.click();
// Wait for the stocks offline API to finish fetching before typing
await page.waitForResponse(
(response) =>
response.url().includes('/internal-api/v1/public/search-stocks-offline') &&
response.status() === 200,
{ timeout: 10000 }
);
await search.fill('tata');
// Suggestions are  >
const suggestions = page.locator('[class*="suggestedItem"]');
// Wait for debounce (300ms) + rendering
await expect(suggestions.first()).toBeVisible({ timeout: 5000 });
await expect(suggestions.first()).toContainText(/tata/i);
});
});

This test is grounded in what actually happened in the browser, which makes it far more trustworthy.

Why Use Search and Autocomplete as an Example?

Autocomplete is a great learning case because it involves several things that are commonly tricky to test:

  • Handling user input
  • Waiting for debounced API calls (requests that fire after a short delay)
  • Rendering content conditionally
  • Dealing with edge cases like empty results or slow responses
  • Keyboard navigation

If this works well, you have a solid foundation for testing almost anything else.

What Can You Use This For?

Once you understand the approach, you can apply it to:

  • Generating E2E tests for any route in your app
  • Testing form validation, including error messages and edge cases
  • Testing multi-step flows like onboarding or checkout
  • Finding gaps in your current test coverage
  • Validating that your API responses match what the UI expects

What Does This Look Like Over Time?

As your application grows, this approach scales with it. New features can have tests generated automatically. Existing tests require less manual maintenance. Regressions get caught earlier. Over time, quality assurance stops being something you do at the end and becomes part of how you build.

Conclusion

The real value here is not just that AI saves you time writing tests. It is that AI actually understands your application by interacting with it directly, the same way a user would.

For a new developer, this removes a lot of the guesswork from testing. You do not have to be an expert in selectors or async behavior to get started. You just need to build your app, add thoughtful test identifiers, and let the AI observe and verify the behavior for you.

Start small, try it on one feature, and build from there.

Resume