Plain-language Playwright steps, saved as code you can review.
Write Playwright steps in plain language. An agent turns each one into TypeScript once, and every later run replays that code like any other Playwright test.
import { test } from "playwright-intent";
test("checkout", async ({ page, ai }) => {
await page.goto("/shop");
await ai.do("add the cheapest shirt to the cart");
await ai.do("check out as {email}", { email: "ada@example.com" });
});- Intent: the text you pass to
ai.do. Use{name}for anything that varies, and pass the values as the second argument. - Step: the TypeScript generated for an intent, saved as a file you can read and review. Changing an intent generates a new step.
- Agent: explores the live page and writes the step. It sees your test file and the page it's working on. Values you pass to
ai.doaren't included in its instructions, but they can reach it through page content once they've been typed or displayed. - Mode: decides when the agent may run.
- Store: where steps live, and what happens when they change.
Requires Node 24 and Playwright 1.60 or later.
npm install -D playwright-intent// playwright.config.ts
import {
allow,
defineConfig,
fileStore,
type IntentUseOptions,
refuse,
} from "playwright-intent";
export default defineConfig<IntentUseOptions>({
use: {
intent: {
model: "openai/gpt-6-luna",
store: fileStore({
changes: { local: allow(), ci: refuse() },
}),
},
},
});Run your tests as usual, with AI_GATEWAY_API_KEY set or a provider's model in model. Then commit the new .intent folder.
Everything lives under use.intent in playwright.config.ts.
| Option | Description |
|---|---|
store |
Required. Where steps live, and what happens when they change |
model |
The AI SDK model used to write steps |
mode |
When the agent may run. Defaults to missing |
prompts |
Changes what the built-in agent is told |
context |
Adds your own text to a failure diagnosis |
limits |
The agent's maxTurns and timeoutMs |
model takes any AI SDK model that supports tool calling. A string goes through Vercel AI Gateway. To use anything else, install its provider and pass the model:
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
// In use.intent:
model: openrouter.chat("openai/gpt-6-luna"),For LiteLLM, Ollama, or a company proxy, use an OpenAI-compatible provider:
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
const proxy = createOpenAICompatible({
name: "proxy",
baseURL: "http://localhost:4000/v1",
apiKey: process.env.PROXY_API_KEY,
});
// In use.intent:
model: proxy("gpt-6-luna"),Providers that wrap a CLI, like Codex CLI or Claude Code, run their own tools instead of ours, so they won't work.
So far, generated steps have only been tried with openai/gpt-6-luna.
The PW_INTENT environment variable overrides mode for one run.
| Mode | Missing step | Saved step fails |
|---|---|---|
none |
Fails | Fails |
missing |
Generates | Fails |
changed |
Generates | Agent diagnoses |
all |
Regenerates | Agent diagnoses |
fileStore keeps steps in .intent beside your Playwright config.
A store always needs changes, because a change that nobody keeps is lost. A step generated on a CI machine disappears with it, unless you've decided what should happen. changes is a policy checked before the agent starts:
allow()lets the change happen, and leaves it on disk for you to review and commit.refuse()fails the test instead, without calling the model.
Pass one policy to use it everywhere, or { local, ci } to choose by whether the CI environment variable is set:
fileStore({ changes: { local: allow(), ci: refuse() } });
fileStore({ changes: allow() });A policy can also be your own function, which throws to stop the change.
The built-in agent has two prompts: generate writes new steps, and diagnose decides what to do after a failure. You can read them here, or import them as defaultPrompts.
Build on a prompt to teach the agent about your app, and you'll keep getting improvements to the default:
import { defaultPrompts } from "playwright-intent";
// In use.intent
prompts: {
generate: `${defaultPrompts.generate}
Dropdowns in this app only open after their label is clicked.`,
},Or pass your own text to replace a prompt completely. The agent's tools stay the same and explain how to use them, so a prompt only needs to say what good work looks like.
Changing a prompt doesn't change steps you've already saved. PW_INTENT=all regenerates them, so keep them in Git first.
This is early, and the API may change. See docs/design.md for how it works.
See CONTRIBUTING.md for development setup and releasing.
Playwright is a trademark of its respective owner. This project is not affiliated with or endorsed by the Playwright project.
Built by