---
title: "Test a Page's Agent Tools the Way You Test Its Buttons"
canonical: "https://current.tinyfish.ai/issue/54/foundations/article/61611/test-a-pages-agent-tools-the-way-you"
publication: "TinyFish"
publisher: "TinyFish"
language: "en-US"
guide: "https://current.tinyfish.ai/llms.txt"
format: "Practical Walkthrough"
author: "Sevda Polat"
author_url: "https://current.tinyfish.ai/team/200"
issue: 54
section: "foundations"
section_name: "Foundations"
published: 2026-11-05
description: "Playwright 1.64's WebMCP methods let teams list, call and schema-check agent tools, revealing that neither browser nor harness validates arguments against a tool's schema."
word_count: 881
source_links: 9
---

# Test a Page's Agent Tools the Way You Test Its Buttons

*By [Sevda Polat](<https://current.tinyfish.ai/team/200/markdown>)*

*November 5, 2026*

[Read on the website](<https://current.tinyfish.ai/issue/54/foundations/article/61611/test-a-pages-agent-tools-the-way-you>)

Anyone who writes Playwright tests has the reflex: load the page, find the button, click it, check what changed. WebMCP, a browser API still in draft, gives pages a second surface. It consists of named tools an agent can call directly instead of clicking. Each tool is a JavaScript function that comes with a plain-language description and an input schema, which is a JSON Schema listing the arguments the function expects. Playwright 1.64 brought these tools into the same test harness, and the reflex carries over almost unchanged. While you run it, keep an eye on the two layers that sit between your test and your tool, the browser and Playwright itself, and on what each of them actually checks for you.

## List the tools

With Chromium launched under `--enable-features=WebMCP`, one line returns the page's tools:

```ts
const tools = await page.webmcp.tools();
```

Each entry has a name, a description, an optional `inputSchema`, and annotations such as `readOnly`. Assert on them as you would on a button's label. The tool you depend on should exist, under the name you expect.

## Call one

Using the adder from [SitePoint's walkthrough](https://www.sitepoint.com/playwright-1-64-webmcp-testing/):

```ts
const result = await page.webmcp.callTool('add_numbers', { a: 3, b: 7 });
```

What comes back is a content array, the standard shape for tool output, holding the text `10`.

Those arguments travel a path where nothing is required to compare them with the input schema. The draft's [call algorithm](https://webmachinelearning.github.io/webmcp/) parses them as JSON, rejects anything that isn't an object, and runs your function. Playwright adds no schema check of its own. That means `{ a: "3", b: 7 }`, or a call with no `b` at all, can reach your code as well. A button never prompts this kind of test, because a click carries no arguments, but a tool call does. So send a malformed call and assert that your tool refuses it.

You also have to decide what refusal looks like. A tool that fails politely returns a result flagged `isError: true`. `callTool()` throws when no tool by that name exists or when its function throws, but a returned `isError: true` arrives as an ordinary value. **A test that asserts nothing beyond "didn't throw" will pass while the tool reports its own failure.**

## Check what comes back

On November 2 the draft gained an optional [`outputSchema`](https://github.com/webmachinelearning/webmcp/pull/254) describing what a tool returns. The draft says only that agents "may reject outputs that do not validate," which leaves the check optional for every agent. If you want it to happen reliably, your suite is where it will happen. Playwright 1.64 shipped on October 7, so its descriptors don't carry the field yet. You can copy the schema from your page's tool registration into a contract file. If the tool declares no schema, and the adder doesn't, write one from what you expect it to return. The schema covers the whole returned value, so validate `result` itself, content array and all, with a library such as Ajv:

```ts
const validate = ajv.compile(outputSchema);
expect(validate(result), JSON.stringify(validate.errors)).toBe(true);
```

## Who else is checking

{{component:tool-validation-layers.html}}

The Community Group's [validation thread](https://github.com/webmachinelearning/webmcp/issues/92) records browser-side validation as the intended direction. For now, enforcing [what tools declare about themselves](https://current.tinyfish.ai/issue/51/foundations/article/45945) falls to the people who wrote them. That division of labor follows an old principle that network engineers call the end-to-end argument: only the endpoint that knows what a message is supposed to mean can verify it. Your site knows what `add_numbers` should return; the browser in the middle only knows it handled valid JSON.

## Testing against a moving draft

Playwright's docs place the API at `navigator.modelContext`, but in May the draft [moved it to `document`](https://github.com/webmachinelearning/webmcp/pull/184). Playwright looks on `document` first and falls back to `navigator`, because Chromium and Firefox put it in different places. So the docs, the spec and the browsers disagree, and the harness reconciles them without telling you it has done so.

Pin your Playwright and browser versions, then, and keep schemas in files your suite reads. The draft will shift again, and when it does, you would rather find out from a red test in CI than from an agent in production that accepted a malformed result and acted on it.

<follow_up>

## Things to follow up on...

- **Who owns validation:** The WebMCP Community Group's open [issue #92](https://github.com/webmachinelearning/webmcp/issues/92) records a March resolution that favors browser validation of schemas, inputs and outputs, though the research and spec text are still unwritten, so watch it for the moment your suite stops being the only check.

- **What the harness changes:** Playwright 1.64's [injected WebMCP adapter](https://raw.githubusercontent.com/microsoft/playwright/v1.64.0/packages/injected/src/webMCP.ts) tries to parse a string result as JSON on the Chromium path before returning it, so the value your test validates may differ from what the tool's function literally returned.

- **Matching your schema draft:** Ajv's default export handles draft-07, while draft-2020-12 schemas need a separate class, as its [supported schema versions page](https://ajv.js.org/json-schema.html) explains, and that matters for which validator you import before you copy an `outputSchema` into a contract file.

- **Poisoned tool metadata:** The peer-reviewed [MCPTox benchmark](https://ojs.aaai.org/index.php/AAAI/article/view/40895) built 1,348 malicious test cases from 45 live MCP servers and showed that descriptors themselves can carry attacks, which is a reason to treat the descriptor list your tests assert on as something to scrutinize as well as confirm.

</follow_up>

## More

- [Foundations — Issue 54](<https://current.tinyfish.ai/issue/54/foundations/markdown>)
- [Issue 54](<https://current.tinyfish.ai/issue/54/markdown>)
- [Publication guide](<https://current.tinyfish.ai/llms.txt>)