Tell it to click a button, it clicks the button. Tell it to fill an input, it fills the input. Give it a test with 40 steps and, assuming the page behaves, it'll run all 40.
Add two items to a shopping cart, apply the discount code, and check that the total is correct.
The browser would need to inspect the page, work out what to click, notice when something changes, and decide what to do next. That's a different kind of automation. And it's a fun engineering problem, provided you don't confuse the agent completed its plan with the application passed a test.
Let's build a small version. No giant agent framework. Just Playwright, an LLM API, and a loop we can inspect.
Our agent will do something closer to this: Observe the current page using an accessibility snapshot. Ask a language model for exactly one next action. Validate that the action belongs to a small set of permitted operations. Execute it with Playwright. Observe the new state and repeat. Separately assert the result in code.
The important word is separately. An agent is useful for finding a path through the interface. It shouldn't get the final vote on whether the system works.
We'll use a local checkout page so this tutorial doesn't depend on somebody else's website or an account with real payment details.
You'll also need an API key for an OpenAI-compatible chat completions endpoint. The example below uses OpenAI's endpoint and defaults to gpt-4.1-mini. You can select a different compatible model with an environment variable.
This is deliberately boring. Boring fixtures are good. If the experiment goes sideways, we want to know whether the problem is in the agent, not the store.
On a successful run, the model will choose actions corresponding to adding a notebook, adding a pen, entering SAVE5, applying it, checking out, and finishing. The exact order and number of model calls can vary. At the end, the script checks three concrete facts: two cart items, a $5 discount, and a $10 receipt.
If it fails, you'll still get agent-history.json and trace.zip; failures also attempt a screenshot. Open the trace with:
A note about the API: responseformat: { type: 'jsonobject' } asks the model for JSON, not for a guaranteed valid browser command. That's why validateAction() still exists. The code uses the raw Playwright library rather than Playwright Test, so the trace contains browser activity but not Playwright Test's assertion metadata.
It can click a named button or fill a named textbox. That's it. The model can't execute JavaScript, visit another website, delete files, call internal APIs, or decide that a failing assertion should be ignored.
It also doesn't receive the entire DOM. We use Playwright's locator.ariaSnapshot() to send a readable view of the accessible interface. This API is available in Playwright 1.49 and later. It's often enough for buttons, links, forms, and page text. It isn't magic: poorly labeled controls and visual-only interfaces remain difficult.
One action at a time. If the model proposes a 12-step plan in advance, step four may be wrong because step two opened a dialog. Observe again after every action.
A fixed budget. Autonomous doesn't mean infinite. Twelve actions is plenty for our checkout. For a production app, give each scenario a time limit, a step limit, and a clear failure state.
An independent oracle. We don't ask, "Did you test checkout successfully?" and accept "Yes". We inspect application state and compare it with expected values. In a real system you might check an order API, a database record, or a known fixture instead of trusting the page alone.
That last point is where a lot of impressive autonomous testing demos quietly stop being convincing.
