Power Your Software Testing with AI Agents and Cloud
The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering. Test Intelligently and Ship Faster.
- TestMu AI (Formerly LambdaTest)
- /
- Blog
- /
- Using Playwright With Cursor: Generate End-to-End (E2E) Tests From a User Story
Using Playwright With Cursor: Generate End-to-End (E2E) Tests From a User Story
Generate E2E tests from a user story in Cursor with Playwright MCP: add a .mdc rule, map each acceptance criterion to a test, and run the spec in the cloud.
Published on:
In an industrial acceptance test generation study, where GPT-4 Turbo turned user stories into Gherkin scenarios and then into Cypress scripts, 60% of the generated test cases were usable as generated and 24% had to be regenerated with additional inputs. The authors found test quality "more closely linked to the clarity and context provided in the user stories and Gherkin scenarios" than to the pages under test.
That leaves two inputs you control: the story you write and the live page the agent reads. In Cursor, Playwright MCP supplies the page and a project rule supplies the conventions. This guide takes one story from acceptance criteria to a reviewed Playwright spec that runs on TestMu AI, one cloud session per criterion.
If you are still deciding how AI fits into a Playwright suite, the Playwright AI guide compares the main options first.
TL;DR
Generating E2E tests from a user story in Cursor is a workflow where Cursor's agent reads a story's acceptance criteria, drives a real browser through the Playwright MCP server, and writes one Playwright test per criterion. It exists because generated tests are only as reliable as the story and page context the agent receives.
- Playwright MCP: Cursor starts the Playwright MCP server from .cursor/mcp.json with npx @playwright/mcp@latest. Each tool call returns the Playwright code it ran and an accessibility snapshot, so locators come from the real page instead of the model's guess.
- Acceptance criteria: Each Given/When/Then criterion maps to one Playwright test whose title carries the criterion ID, and every Then names a value the test can assert, such as a heading, an alert message or a price.
- Cursor support for Playwright Test Agents: No. In Playwright 1.64.1, npx playwright init-agents has no Cursor loop option, so in Cursor you get the planner and generator behavior by driving Playwright MCP directly with a project rule.
- Merge-ready as generated: No. Playwright MCP 0.0.83 ships no assertion tools by default and records clicks with positional locators such as nth(1), so a reviewer replaces waits with expect() checks and pins each locator to the product or row the criterion names.
- Agent Skills in Cursor: Yes. Cursor loads SKILL.md skills from .agents/skills/, and TestMu AI's Playwright skill installs there with one command, so every spec the agent writes follows the same selector order, web-first assertions and cloud fixture.
- Cloud run: The reviewed Cursor-generated Playwright spec runs on TestMu AI with one cloud session per test, named after its acceptance criterion, so a failed session in the dashboard points straight at the criterion it covers.
What Is the Cursor and Playwright MCP Workflow?
The Cursor and Playwright MCP workflow is a loop in which Cursor's agent reads a user story, opens the app in a real browser through the Playwright MCP server, and writes Playwright Test specs from what it finds.
The Playwright MCP README says the server lets LLMs interact with web pages "through structured accessibility snapshots, bypassing the need for screenshots or visually-tuned models."
Each part of the loop lives in a file you commit or a mode you switch to:
| Cursor surface | What it holds | Job in this workflow |
|---|---|---|
| .cursor/mcp.json | Project-level MCP servers, committed with the repo | Starts Playwright MCP for everyone who opens the project, with the same flags |
| .cursor/rules/*.mdc | Version-controlled project rules with frontmatter | Encodes how a story becomes a spec: one test per criterion, locator order, no sleeps |
| AGENTS.md | Plain markdown instructions at the repo root or in subfolders | Shared conventions any agent reads, including the Kane CLI instructions covered below |
| Plan mode | A reviewable plan built before any code, with clarifying questions | Turns the criteria into a test list you approve before the spec is written |
| Agent mode | Full access to tools, files and the terminal | Calls Playwright MCP, writes the spec file and runs npx playwright test |
For the server's full tool set and how it behaves outside Cursor, see AI and Playwright MCP.
How to Connect Playwright MCP to Cursor
The Cursor MCP docs use .cursor/mcp.json in a project for project-specific tools and ~/.cursor/mcp.json in your home directory for tools available everywhere.
Create the project file at the repo root and commit it, so every teammate who opens the repo gets the same browser tools with the same flags:
// .cursor/mcp.json
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--isolated"]
}
}
}The flags that matter for test generation, as npx @playwright/mcp --help describes them in version 0.0.83:
- --isolated - keeps the browser profile in memory, so each Cursor session starts with an empty cart and no saved login, the same state a fresh CI run sees.
- --test-id-attribute - set it when your app marks elements with something other than data-testid, so getByTestId() in the generated code matches your markup.
- --codegen - the language of the code each tool call reports; typescript is the default, and python, java and csharp are the alternatives.
- --headless - the browser opens headed by default. Keep it headed while you watch the agent work, and switch to headless for unattended sessions.
- Version pin - @latest pulls the newest release on every start. Once the workflow is stable, pin a version such as @playwright/mcp@0.0.83, because tool parameters change between releases.
The server needs Node.js 18 or newer. Cursor asks for approval before it runs an MCP tool by default, so the first browser_navigate call shows a prompt; if the server never starts, open the Output panel and pick MCP Logs.
Version 0.0.83 exposes 25 tools by default, from browser_navigate and browser_snapshot to browser_click, browser_type and browser_fill_form. None of them asserts anything. The expected values in your spec have to come from the acceptance criteria, which is why the story is the next file to write.
How to Write a User Story Cursor Can Turn Into Tests
Write each Then so it names something a test can read off the page: a heading, a message, a count or a price. In the same case study, 12 of the 50 generated test cases missed because the user story and Gherkin scenarios lacked context the model needed, and the authors fixed them by enriching that input.
Agreeing on criteria before any code exists is the core of ATDD. Here is the story this guide automates, written against the TestMu AI ecommerce playground and saved in the repo so Cursor can reference it:
# stories/SHOP-142.md
As a shopper, I want to find a product by name and add it to my cart,
so that I can buy it at checkout.
Scenario: AC-1 Search lists matching products
Given I am on the store home page
When I search for "iPhone"
Then the results heading reads "Search - iPhone"
And at least one product named "iPhone" is listed
Scenario: AC-2 Add to Cart confirms the product
Given I am on the iPhone product page (product_id=40)
When I click "Add to Cart"
Then a success alert names "iPhone"
Scenario: AC-3 Cart shows the product, quantity and price
Given I have added the iPhone to my cart
When I open the shopping cart
Then the "iPhone" row shows quantity 1
And its unit price and total are both $123.20
Scenario: AC-4 Search with no matches
Given I am on the store home page
When I search for "zzqx-no-such-product"
Then the page says "There is no product that matches the search criteria."Before you hand the story to Cursor, check that every criterion translates into one observable value and one Playwright assertion:
| Criterion | Observable value | Playwright assertion |
|---|---|---|
| AC-1 | Heading "Search - iPhone" and at least one product link named iPhone | toBeVisible() on the level-1 heading; not.toHaveCount(0) on getByRole('link', { name: 'iPhone', exact: true }) |
| AC-2 | Success alert that names the product | toContainText('iPhone') on getByText('Success: You have added') |
| AC-3 | Cart row with quantity 1, unit price and total of $123.20 | toHaveValue('1') on the row's textbox; toHaveCount(2) on cells named $123.20 |
| AC-4 | The no-results message | toBeVisible() on getByText('There is no product that matches the search criteria.') |
A criterion that fails this check, such as "the cart works", produces a test that asserts nothing specific. Rewrite it before generation, because the test can only assert values the story names.
How to Encode the Conversion in a Cursor Rule
Per Cursor's docs on project rules, project rules live in .cursor/rules as .mdc files, and a plain .md file in that folder is ignored by the rules system.
A rule turns your review checklist into input, so the agent gets the conventions before it writes the first line instead of after you reject it.
Save this as .cursor/rules/e2e-from-story.mdc:
---
description: Turn a user story with acceptance criteria into Playwright Test specs
globs: tests/e2e/**/*.spec.ts
alwaysApply: false
---
# E2E tests from user stories
- Read the story file the user names (stories/*.md). Write one test per acceptance criterion.
- Name the describe block after the story ID and title every test "AC-<n>: <outcome>".
- Before writing a locator, open the page with Playwright MCP and read the snapshot. Never guess a selector.
- Locator order: getByRole with the exact accessible name, then getByLabel, getByText, getByTestId.
Use CSS only to scope a region, never as the target.
- Never use nth() or first() to choose between products, rows or cards.
Open the item by its URL or filter the row with { has: ... }.
- Every Then becomes an expect() assertion on the value the criterion names.
No waitForTimeout(), and no waitFor() in place of an assertion.
- Each test builds its own state and never depends on another test.
- Import test and expect from tests/testmu.fixture.ts.
- Run npx playwright test on the new file and fix failures before you finish.
Never weaken an assertion to make a test pass.- globs - attaches the rule automatically whenever a file under tests/e2e is in the agent's context, which is Cursor's "Apply to Specific Files" behavior.
- description - lets the agent pull the rule in on its own when you mention a story, the "Apply Intelligently" behavior, even before any spec file exists.
- alwaysApply: false - keeps the rule out of unrelated chats; "Always Apply" would add it to every request.
- Length - Cursor's docs recommend keeping each rule under 500 lines and splitting large ones into smaller rules, so keep locator, assertion and data conventions in separate files as the list grows.
Rule types, precedence and the cases where a rule silently stops applying are covered in detail in Cursor rules.
How to Generate the Spec in Cursor
Start in Plan mode by pressing Shift+Tab in the chat input. Cursor's docs say the agent researches your codebase, asks clarifying questions and generates a reviewable plan you can edit before building.
That plan is where you check the criteria-to-test mapping, before any spec exists. Reference the story file in the prompt:
@stories/SHOP-142.md Use Playwright MCP to open https://ecommerce-playground.lambdatest.io/
and walk each acceptance criterion in the browser. Propose one test per AC, with the
value each test asserts. After I approve the plan, write tests/e2e/shop-142.spec.ts
following the e2e-from-story rule, then run it.Approve the plan only when it lists one test per criterion and names the value each one asserts. A plan item that says "verify the cart" has already dropped the quantity and price from AC-3.
After approval, the agent works through Playwright MCP, and every tool call reports the Playwright code it ran. Walking AC-1 to AC-3 against the playground returns these lines, with the tool name added above each one:
// browser_type: search for the product
await page.getByRole('textbox', { name: 'Search For Products' }).fill('iPhone');
await page.getByRole('textbox', { name: 'Search For Products' }).press('Enter');
// browser_click: open the first iPhone result
await page.getByRole('link', { name: 'iPhone' }).nth(1).click();
// browser_click: add it to the cart
await page.getByRole('button', { name: 'Add to Cart' }).click();
// browser_wait_for: wait for the confirmation
await page.getByText("Success: You have added").first().waitFor({ state: 'visible' });Behind each line is the accessibility snapshot the agent reads. On the search results page, the first product shows up twice, once as an image link and once as the name link:
- heading "Search - iPhone" [level=1] [ref=f1e430]
# ...
- link [ref=f1e473] [cursor=pointer]:
- /url: https://ecommerce-playground.lambdatest.io/index.php?route=product/product&product_id=40&search=iPhone
- img "iPhone" [ref=f1e476]
# ...
- heading [level=4] [ref=f1e494]:
- link "iPhone" [ref=f1e495] [cursor=pointer]:
- /url: https://ecommerce-playground.lambdatest.io/index.php?route=product/product&product_id=40&search=iPhone
- generic [ref=f1e496]: $123.20The search locator is exactly what you want in a spec, because it comes from the textbox's accessible name. The other lines are raw material: an agent that copies them into the spec as-is carries their weaknesses with them, which is what the review step is for.
How to Review an AI-Generated Playwright Spec
Read the generated file against the criteria table, not against the app. Playwright locators are strict, so a locator that matches several elements throws on the first action. Running the bare iPhone link locator against the results page shows why the MCP added nth(1):
Error: locator.click: Error: strict mode violation:
getByRole('link', { name: 'iPhone' }) resolved to 8 elements:
1) <a data-ride="false" class="carousel d-block slide" id="mz-product-grid-image-40-212469" ...>…</a>
2) <a class="text-ellipsis-2" href="...product_id=40&search=iPhone">iPhone</a>
aka getByRole('link', { name: 'iPhone' }).nth(1)These are the patterns to look for, each tied to a line from the session above or a gap in the criteria:
| Generated pattern | What goes wrong | Reviewed version |
|---|---|---|
| getByRole('link', { name: 'iPhone' }).nth(1) | The bare locator matches 8 elements, image links included, and nth(1) clicks whatever sits second, so a reordered grid adds a different product | Open the product by URL (product_id=40), or use exact: true and filter by the product the criterion names |
| getByText(...).first().waitFor() | Waits for the alert but checks nothing, so adding any product passes | expect(page.getByText('Success: You have added')).toContainText('iPhone') |
| Unnamed quantity field | The cart's quantity textbox has no accessible name in the snapshot, and the page also renders a cart drawer with the same product | Scope to the main table: locator('#content').getByRole('row').filter({ has: ... }).getByRole('textbox') |
| Shared cart state | A cart test that relies on an earlier test's click fails when tests run in parallel or alone | Each test adds its own product; fullyParallel: true in the config proves the independence |
| Happy path only | A walk that follows only the main flow, like the lines above, produces no negative test for AC-4 | One test per criterion, including the no-results search |
The reviewed spec keeps the generated search locator, opens the product by URL, and turns every Then into a web-first assertion, the auto-retrying kind described in Playwright assertions:
// tests/e2e/shop-142.spec.ts
// SHOP-142: As a shopper, I want to find a product by name and add it to my cart,
// so that I can buy it at checkout.
import { test, expect } from '../testmu.fixture';
const IPHONE_PAGE = '/index.php?route=product/product&product_id=40';
test.describe('SHOP-142: add a searched product to the cart', () => {
test('AC-1: searching "iPhone" lists matching products', async ({ page }) => {
await page.goto('/');
const search = page.getByRole('textbox', { name: 'Search For Products' });
await search.fill('iPhone');
await search.press('Enter');
await expect(page.getByRole('heading', { level: 1, name: 'Search - iPhone' })).toBeVisible();
await expect(page.getByRole('link', { name: 'iPhone', exact: true })).not.toHaveCount(0);
});
test('AC-2: Add to Cart confirms the product by name', async ({ page }) => {
await page.goto(IPHONE_PAGE);
await page.getByRole('button', { name: 'Add to Cart' }).click();
await expect(page.getByText('Success: You have added')).toContainText('iPhone');
});
test('AC-3: the cart lists iPhone with quantity 1 at $123.20', async ({ page }) => {
await page.goto(IPHONE_PAGE);
await page.getByRole('button', { name: 'Add to Cart' }).click();
await expect(page.getByText('Success: You have added')).toBeVisible();
await page.goto('/index.php?route=checkout/cart');
const row = page.locator('#content').getByRole('row')
.filter({ has: page.getByRole('link', { name: 'iPhone', exact: true }) });
await expect(row.getByRole('textbox')).toHaveValue('1');
// Unit Price and Total cells: both $123.20 when the quantity is 1.
await expect(row.getByRole('cell', { name: '$123.20', exact: true })).toHaveCount(2);
});
test('AC-4: a search with no matches says so', async ({ page }) => {
await page.goto('/');
const search = page.getByRole('textbox', { name: 'Search For Products' });
await search.fill('zzqx-no-such-product');
await search.press('Enter');
await expect(page.getByText('There is no product that matches the search criteria.')).toBeVisible();
});
});Break one expectation on purpose before you trust the suite. Lowering AC-3's expected price by one dollar fails with a message that names the exact locator, the expected count and the count it found:
x 1 [local] › tests\e2e\shop-142.spec.ts:25:7 › SHOP-142: add a searched product to the cart › AC-3: the cart lists iPhone with quantity 1 at $123.20 (14.5s)
Error: expect(locator).toHaveCount(expected) failed
Locator: locator('#content').getByRole('row').filter({ has: getByRole('link', { name: 'iPhone', exact: true }) }).getByRole('cell', { name: '$122.20', exact: true })
Expected: 2
Received: 0
Timeout: 5000ms
33 | await expect(row.getByRole('textbox')).toHaveValue('1');
34 | // Unit Price and Total cells: both $123.20 when the quantity is 1.
> 35 | await expect(row.getByRole('cell', { name: '$122.20', exact: true })).toHaveCount(2);
| ^If the edited test still passes, the assertion is checking the wrong element. Restore the value and add the check to the Cursor rule so the next generated spec gets it right.
Playwright Test Agents vs Playwright MCP in Cursor
In the Playwright Test Agents docs, the planner explores the app and produces a Markdown test plan, the generator transforms that plan into Playwright Test files, and the healer executes the test suite and automatically repairs failing tests.
Cursor is not one of the tools the agents are generated for. The --loop flag of npx playwright init-agents in Playwright 1.64.1 has no cursor value:
$ npx playwright init-agents --help
Usage: npx playwright init-agents [options]
Initialize repository agents
Options:
--loop <loop> Agentic loop provider (choices: "claude", "codex",
"copilot", "opencode", "vscode", "vscode-legacy")A Cursor forum report from October 31, 2025 shows the agents' planner_setup_page, generator_setup_page and test_list tools failing inside Cursor with "Playwright Test did not expect test() to be called here." The thread closed with no fix recorded. The options compare like this:
| Option | What you get in Cursor | Trade-off | Pick it when |
|---|---|---|---|
| Playwright MCP + project rule | Live accessibility snapshots and per-action Playwright code, configured in .cursor/mcp.json | Spec structure and assertions come from your rule and your review | You want committed Playwright specs generated from Cursor today |
| Playwright Test Agents | Planner, generator and healer with a seed test and a specs/ folder for plans | No Cursor loop, and the generator tools failed inside Cursor in the forum report | Your team also works in VS Code, Claude Code, Codex, Copilot or OpenCode |
| Playwright CLI with skills | The Playwright MCP README says a coding agent "might benefit from using the CLI+SKILLS instead" | A separate tool for the agent to call from the terminal | You prefer shell commands the agent runs over MCP tool calls |
| Kane CLI | Runs a criterion as a plain-English objective in a real browser and returns pass or fail with an evidence pack; Playwright code on export | Runs use KaneAI credits from your TestMu AI account | You want a criterion verified before anyone writes or reviews selectors |
Kane CLI fits Cursor through AGENTS.md. The Kane CLI with AI coding agents docs say its skill installer does not set up Cursor, so you append its instructions to AGENTS.md once, and Cursor's agent can then check a criterion with no selectors at all:
# Once per repo: give Cursor's agent the Kane CLI instructions (Cursor reads AGENTS.md)
curl -fsSL https://www.testmuai.com/kane-cli-agent/AGENTS.md >> AGENTS.md
# Verify one criterion as an objective, no selectors; the exit code is the verdict
kane-cli run "Open the iPhone product page, add it to the cart, open the cart and verify the iPhone row shows quantity 1" --agent --headlessKane CLI exits with 0 on pass, 1 on fail, 2 on a setup or auth error and 3 on a timeout, so a CI step can gate on a criterion even before its Playwright test exists.
How to Run the Generated Tests on TestMu AI
The TestMu AI docs for Playwright testing connect a test to the cloud grid through a wss://cdp.lambdatest.com/playwright endpoint that carries your capabilities, and mark each session passed or failed with a setTestStatus call. The fixture below wraps that pattern so every test opens its own session, named after its acceptance criterion:
// tests/testmu.fixture.ts
// One TestMu AI cloud session per test, named after the test, with its pass/fail status set.
import { test as base, expect, chromium } from '@playwright/test';
export const test = base.extend({
page: async ({ page, baseURL }, use, testInfo) => {
if (testInfo.project.name !== 'testmu-cloud') return use(page);
const capabilities = {
browserName: 'Chrome',
browserVersion: 'latest',
'LT:Options': {
platform: 'Windows 11',
build: 'SHOP-142 add to cart',
name: testInfo.title,
user: process.env.LT_USERNAME,
accessKey: process.env.LT_ACCESS_KEY,
network: true,
video: true,
console: true,
},
};
const browser = await chromium.connect(
`wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`
);
const cloudPage = await browser.newPage({ baseURL });
await use(cloudPage);
const status = testInfo.status === testInfo.expectedStatus ? 'passed' : 'failed';
await cloudPage.evaluate(() => {}, `lambdatest_action: ${JSON.stringify({
action: 'setTestStatus', arguments: { status, remark: testInfo.error?.message ?? 'ok' },
})}`);
await browser.close();
},
});
export { expect };The config keeps a local project for quick runs in your own Chrome and adds the cloud project; fullyParallel: true lets each criterion run in its own worker:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
reporter: 'list',
use: {
baseURL: 'https://ecommerce-playground.lambdatest.io',
trace: 'retain-on-failure',
},
projects: [
{ name: 'local', use: { channel: 'chrome' } },
{ name: 'testmu-cloud' },
],
});Export LT_USERNAME and LT_ACCESS_KEY from your TestMu AI account, then run the cloud project:
$ npx playwright test --project=testmu-cloud --workers=4
Running 4 tests using 4 workers
ok 1 [testmu-cloud] › tests\e2e\shop-142.spec.ts:25:7 › SHOP-142: add a searched product to the cart › AC-3: the cart lists iPhone with quantity 1 at $123.20 (25.3s)
ok 2 [testmu-cloud] › tests\e2e\shop-142.spec.ts:38:7 › SHOP-142: add a searched product to the cart › AC-4: a search with no matches says so (26.0s)
ok 4 [testmu-cloud] › tests\e2e\shop-142.spec.ts:18:7 › SHOP-142: add a searched product to the cart › AC-2: Add to Cart confirms the product by name (27.9s)
ok 3 [testmu-cloud] › tests\e2e\shop-142.spec.ts:8:7 › SHOP-142: add a searched product to the cart › AC-1: searching "iPhone" lists matching products (30.6s)
4 passed (32.3s)- One session per criterion - the dashboard lists AC-1 to AC-4 as separate sessions with their pass or fail status set, so a red build names the failing criterion before you open a log.
- Parallel sessions - with four workers the suite above finishes in 32.3 seconds; the same four tests take 1.6 minutes in a single worker.
- Session artifacts - Automation Cloud runs Playwright suites across 3,000+ browser and OS combinations and records network logs, console logs, video, screenshots and command logs for every session, with no grid to maintain.
- Browser coverage - browserName also accepts MicrosoftEdge, pw-chromium, pw-firefox and pw-webkit, so add one cloud project per browser instead of editing the fixture.
- Auto Healing - autoHeal: true works for Playwright on the grid, but it rebuilds broken locators heuristically and can settle on a similar element. Leave it off for acceptance suites whose job is to fail when the UI changes.
Note: Each acceptance criterion gets its own named session on TestMu AI, with video, console and network logs attached, so a failing criterion arrives with its replay. Try TestMu AI free!
How Agent Skills Ensure Consistent Playwright Tests in Cursor
Cursor's Agent Skills docs describe a skill as a folder containing a SKILL.md file, loaded automatically from .cursor/skills/ or .agents/skills/ in a project and applied when the agent determines it is relevant. You can also invoke a skill by typing / in Agent chat and searching for its name.
The e2e-from-story rule holds the conventions for turning a story into a spec. A skill carries a broader playbook that the agent pulls in when a task involves Playwright code, so a spec generated from a story and one generated from a bug report come out with the same locators, assertions and cloud setup.
TestMu AI's Playwright skill generates Playwright tests in TypeScript, JavaScript, Python, Java or C#, and supports local execution as well as execution on the TestMu AI cloud.
Its installer has a cursor target, which writes the skill to .agents/skills/playwright-skill/, one of the folders Cursor scans, and records the source in skills-lock.json:
$ npx agentskillsforall add LambdaTest/agent-skills --skill playwright-skill --agent cursor -y
# What lands in the repo
.agents/skills/playwright-skill/SKILL.md
.agents/skills/playwright-skill/reference/ # cloud-integration.md, debugging-flaky.md, page-object-model.md, ...
.agents/skills/playwright-skill/templates/ # lambdatest-setup.ts, playwright.config.ts
skills-lock.jsonThese are the parts of the skill that keep every generated spec in the same shape:
- Selector priority - getByRole first, then getByLabel, getByPlaceholder, getByText and getByTestId, with raw CSS or XPath only for a third-party widget that offers nothing else. The e2e-from-story rule uses the same order, so the rule and the skill never pull the agent in different directions.
- Web-first assertions - checks such as expect(locator).toBeVisible() and toHaveText() retry until their timeout, in place of reading textContent once and comparing it.
- Anti-pattern table - waitForTimeout() becomes an expect() check, page.$() becomes getByRole(), and state shared between tests moves into test.beforeEach.
- Validation workflow - after generating a test, the agent validates the config, runs the test locally on chromium, runs it on the cloud project, and reads debugging-flaky.md when something fails.
- Cloud status - the skill notes that a TestMu AI session shows Completed by default until the test reports pass or fail, and its lambdatest-setup.ts fixture sets that status after every test.
The skill's cloud pattern names each cloud project browserName:version:platform@lambdatest, and its fixture parses that name into capabilities, so adding a browser means adding a project instead of editing code. The fixture follows the same connect-and-report pattern as the one in the previous section, and also sends a playwrightClientVersion read from npx playwright --version:
// playwright.config.ts (projects excerpt, from the skill's template)
// Format: browserName:version:platform@lambdatest, parsed by lambdatest-setup.ts
{
name: 'chrome:latest:Windows 11@lambdatest',
use: { viewport: { width: 1920, height: 1080 } },
},With the skill installed, ask for the cloud run in Agent mode and name the skill, so it attaches to that message:
/playwright-skill Run tests/e2e/shop-142.spec.ts on the TestMu AI cloud on Chrome and
Windows 11. Use the skill's lambdatest-setup.ts fixture and keep every assertion unchanged.Before the agent points the skill at the story's suite, run a one-test smoke spec through the cloud project. It confirms the credentials, the capabilities parsed from the project name and the status reporting in one session:
// tests/smoke/skill-smoke.spec.ts
import { test, expect } from '../../lambdatest-setup';
test('smoke: cloud session opens and reports status', async ({ page }) => {
await page.goto('https://www.testmuai.com/selenium-playground/simple-form-demo/');
await page.getByPlaceholder('Please enter your Message').fill('Hello from the skill');
await page.getByRole('button', { name: 'Get Checked Value' }).click();
await expect(page.getByText('Hello from the skill', { exact: true })).toBeVisible();
});$ npx playwright test tests/smoke/skill-smoke.spec.ts --project="chrome:latest:Windows 11@lambdatest"
Running 1 test using 1 worker
ok 1 [chrome:latest:Windows 11@lambdatest] › tests\smoke\skill-smoke.spec.ts:4:5 › smoke: cloud session opens and reports status (19.5s)
1 passed (24.4s)The session lands in a build named PW Build followed by the date, with its status set to passed. To move the SHOP-142 spec onto the skill's fixture, change its import to lambdatest-setup and update the import line in the e2e-from-story rule to match, so the rule and the skill keep generating specs against the same fixture.
Run one test on every cloud project before you commit the list the skill's template ships. Its pw-firefox project targets macOS Ventura, which the grid rejects for Playwright 1.62 and later, so keep only the projects that pass on your Playwright version.
Let Claude Code write Playwright tests that actually pass.
Conclusion
Commit .cursor/mcp.json and the e2e-from-story rule first, then take one real story with ID-numbered criteria through Plan mode and review the spec against its criteria table before you scale up. Each review finding that repeats belongs in the rule.
When the reviewed spec passes locally, add the fixture and run it on TestMu AI, then follow the guide to parallel testing with Playwright to spread each story's criteria across the browsers your users run.
Author
Vipul Gupta is a Sr. Lead SDET at Zupee with over 9 years of experience in functional and automation testing. He has built 10+ automation projects from scratch covering web, API, and mobile applications. Vipul is skilled in Selenium, Appium, Rest Assured, Playwright, Java, Python, Pytest, BDD, TDD, Maven, Jenkins, TestNG, and JUnit. He has successfully led end-to-end QA efforts, including setting up teams from scratch and managing a 15-member QA team to ensure manual and automation testing run in parallel from day one. Vipul graduated in B.Tech CSE from CGC College of Engineering and is followed by 3,000+ QA and SDET professionals on LinkedIn, reflecting his strong influence in the testing community.
Reviewer
Himanshu Sheth is the Director of Marketing (Technical Content) at TestMu AI, with over 8 years of hands-on experience in Selenium, Cypress, and other test automation frameworks. He has authored more than 130 technical blogs for TestMu AI, covering software testing, automation strategy, and CI/CD. At TestMu AI, he leads the technical content efforts across blogs, YouTube, and social media, while closely collaborating with contributors to enhance content quality and product feedback loops. He has done his graduation with a B.E. in Computer Engineering from Mumbai University. Before TestMu AI, Himanshu led engineering teams in embedded software domains at companies like Samsung Research, Motorola, and NXP Semiconductors. He is a core member of DZone and has been a speaker at several unconferences focused on technical writing and software quality.
Playwright With Cursor FAQs
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance
- Advanced access controls
- Advanced data retention rules
- Advanced Local Testing
- Premium Support options
- Early access to beta features
- Private Slack Channel
- Unlimited Manual Accessibility DevTools Tests





