Intro – What’s MCP & Why LLMs Need Model Context?
As AI agents evolve, so does their ability to interact with real-world web apps. Traditional automation relies on brittle selectors and hard-coded steps. The Model Context Protocol (MCP) which is an open standard that enables LLMs to interface with the web using structured, semantically rich data.
LLMs don’t “see” pages like humans. They rely on model context, an accessibility based snapshot of a page’s elements, roles, and structure. Instead of fuzzy screenshots, they get clear, machine-readable context. This is where Playwright-MCP shines.
How Playwright-MCP Provides API Tools (navigate, snapshot, click,browse type)
Playwright-MCP acts as a bridge between the browser and the LLM. It extends Playwright with a protocol that:
- Navigates to any URL
- Snapshots the page into a structured tree (based on ARIA/accessibility)
- Allows agents to click, type, hover, or drag using semantic references
For example, instead of page.click("#login-btn")
, an agent might request:
{
"action": "click",
"ref": {
"role": "button",
"name": "Sign in with Microsoft"
}
}
This creates a more robust, generalizable automation flow.
Behind the Scenes, JSON View of Snapshot -> Mapping to Test Code
When an LLM asks for a snapshot, Playwright-MCP returns a detailed JSON like this:
{
"role": "form",
"children": [
{
"role": "textbox",
"name": "Email"
},
{
"role": "textbox",
"name": "Password"
},
{
"role": "button",
"name": "Sign in"
}
]
}
From this, the agent generates code like:
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
await page.getByRole('textbox', { name: 'Password' }).fill('securepass');
await page.getByRole('button', { name: 'Sign in' }).click();
Agent Workflow, Prompt → Actions → Code Generation
The full workflow looks like this:
- Prompt The User ask: “Log in to the dashboard using Azure AD.”
- Agent uses MCP to:
- Navigate to
/login
- Request a snapshot
- Identify the Azure AD login button
- Execute a click
- Navigate to
- Optionally, the agent turns its actions into a reusable Playwright test.
Real-World Example Checkbox Testing on my practiceautomatedtesting.com
In this example I used cursor IDE with Playwright-MCP enabled. I prompted:
“Write a test that checks and unchecks checkboxes on practiceautomatedtesting.com.”
Copilot or any other llm will try to do the follwing
- Launches MCP server
- Navigates to
/webelements
- Detects the “Check Box” section via snapshot
- Clicks the link
- Identifies checkboxes using labels
- Checks/unchecks them
- Asserts expected states
This is simplified but based on your chat conversation it will auto generate your testscases and does it pretty well I must say
Checkbox Testing on practiceautomatedtesting.com
This is the output the mcp icw llm created
const { test, expect } = require('@playwright/test');
test.describe('Checkbox Section - practiceautomatedtesting.com', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://practiceautomatedtesting.com/webelements');
// Click the Check Box section
await page.getByRole('link', { name: /Check Box/i }).click();
// Wait for checkboxes to be visible
await expect(page.getByText('Checkbox 1')).toBeVisible();
await expect(page.getByText('Checkbox 2')).toBeVisible();
});
test('should check and uncheck Checkbox 1', async ({ page }) => {
const checkbox1 = page.getByLabel('Checkbox 1');
await expect(checkbox1).not.toBeChecked();
await checkbox1.check();
await expect(checkbox1).toBeChecked();
await checkbox1.uncheck();
await expect(checkbox1).not.toBeChecked();
});
test('should check and uncheck Checkbox 2', async ({ page }) => {
const checkbox2 = page.getByLabel('Checkbox 2');
await expect(checkbox2).not.toBeChecked();
await checkbox2.check();
await expect(checkbox2).toBeChecked();
await checkbox2.uncheck();
await expect(checkbox2).not.toBeChecked();
});
test('should check both checkboxes and verify', async ({ page }) => {
const checkbox1 = page.getByLabel('Checkbox 1');
const checkbox2 = page.getByLabel('Checkbox 2');
await checkbox1.check();
await checkbox2.check();
await expect(checkbox1).toBeChecked();
await expect(checkbox2).toBeChecked();
});
});
Wrap-Up, The Benefits, Caveats, Future Directions
Benefits
- More semantic, reliable test generation
- Works across different DOM layouts and styling
- Enables agent-driven workflows and autonomous test writing
Caveats
- Accessibility structure must be well-formed
- May require adjustments for dynamic/spa-heavy apps
Future Directions
- Live agent debugging + replay
- Multi-modal snapshots (DOM + image)
With Playwright-MCP, building tests isn’t just automation—it’s collaboration between you and your AI assistant.
My Final Thoughts
As someone diving deep into automated testing, integrating Playwright-MCP with tools like Cursor has completely changed how I think about writing tests. What used to take 30 minutes of trial and error now takes a simple prompt and a few seconds of validation.
Working with real examples like practiceautomatedtesting.com, I saw firsthand how model context eliminates the guesswork. The LLM knows exactly what’s on the page, what’s clickable, and what each element means because it sees the page like a screen reader would.
That kind of clarity doesn’t just make testing faster it makes it smarter. And honestly, a little more fun.
This is the future of testing. And it’s already here.
But remember, when using Cursor with Playwright-MCP, it’s crucial to define rules or constraints in your prompts. This helps guide the LLM toward deterministic behavior and prevents it from hallucinating non-existent elements or logic.
For example, specify things like:
- Only interact with visible elements
- Use exact role and name combinations
- Avoid assumptions about navigation or redirects
- use the right question with proper context
With the right guidance, the AI becomes a powerful, trustworthy partner.
For more details, check out github.com/microsoft/playwright-mcp