Core Browser Tools — Navigate, Click, Type, Wait

9 min read

You don't call Playwright MCP tools directly — Claude does, in response to your prompts. But every assistant action maps to a specific tool with specific arguments, and reading those calls in the chat panel is your single best debugging surface. This lesson covers the tools that make up the bulk of every session: navigate, click, type, key press, select, hover, wait, and snapshot. Knowing them by name turns the tool-call panel from noise into evidence.

The tool names below are the public, stable interface as of early 2026. Exact argument shapes have evolved a few times since launch — the README in the @playwright/mcp package is authoritative if you see a name that no longer matches.

The eight tools you'll see in nearly every session

browser_navigate — go to a URL.

{ "url": "https://example.com" }

The first call in almost every session. Returns once the page reaches its load state — Playwright handles waiting under the hood, so the next tool runs against a stable page.

browser_navigate_back — the browser back button. Useful when the assistant goes one step too far and needs to retreat without rebuilding the navigation chain.

browser_click — click an element by its accessibility ref.

{ "ref": "e3" }

The ref comes from the most recent browser_snapshot. Re-snapshot first if the page state has changed since the last one.

browser_type — type text into an input.

{ "ref": "e5", "text": "alice@test.com" }

By default the text is appended to whatever is already in the field; some server versions support a clear flag to wipe first. If a field looks dirty between tests, ask the assistant to clear it explicitly.

browser_press_key — press a keyboard key.

{ "key": "Enter" }

Enter is the most common — committing a search, submitting a form. Tab, Escape, ArrowDown, and modifier combinations like Meta+K (the command palette) all work the same way.

browser_select_option — choose from a native <select> dropdown.

{ "ref": "e7", "values": ["United Kingdom"] }

values is an array because some selects allow multiple. For custom dropdown components built with <div>s, the assistant uses a click + click pattern instead.

browser_hover — hover the cursor over an element. Required for tooltips, dropdown menus that appear on hover, and any UI gated on :hover.

browser_wait_for — wait for a condition.

{ "text": "Order confirmed", "time": 5 }

The condition can be visible text appearing, text disappearing, or a timeout in seconds. This replaces the "sleep for two seconds and hope" anti-pattern with explicit synchronisation. When the assistant uses this voluntarily, your tests are usually in good shape.

browser_snapshot — refresh the accessibility tree. The assistant calls this after every navigation and most state changes; the result is what subsequent click and type calls reference.

A real flow, tool call by tool call

Your prompt:

Log in as admin@test.com with password Pass123, then verify the welcome message.

What the tool-call panel actually shows:

Seven calls, no code, no selectors. The assistant resolved every target by role and accessible name from the live tree. When this flow is later promoted into a real Playwright test, the generated code mirrors the same structure — getByLabel("Email"), getByLabel("Password"), getByRole("button", { name: "Login" }), expect(page.getByText("Welcome")).toBeVisible(). The MCP session is essentially a live demonstration the assistant can transcribe.

Reading the panel like a pro

Open Claude Desktop's tool-call panel for any session and three things are worth noticing:

  • The args of each call. Are the refs aimed at the elements you'd expect? A click failing on the wrong ref is the most common cause of "it did the wrong thing."
  • The result of each browser_snapshot. This is the model's actual view of the page. If a target element doesn't appear here, the click after will fail — and the bug is often in the page (missing role/name) rather than in the assistant.
  • The presence or absence of browser_wait_for. Sessions that race past async UI and fail intermittently are missing waits. Adding "wait for the success message before continuing" in the prompt usually fixes it.

The panel is your debugging IDE for AI sessions. Get fluent in it early and the rest of the course is easier.

When generated tests come out brittle

A common pattern: the assistant runs a flow successfully, you ask for the equivalent Playwright code, and the generated test fails 1 in 5 runs. Almost always the cause is one of:

  • Hard-coded waitForTimeout in place of browser_wait_for-style synchronisation. Replace with await expect(...).toBeVisible() or await page.waitForResponse(...).
  • Brittle selectors — class names with framework-generated suffixes, deep CSS chains. Replace with getByRole, getByLabel, getByText. The accessibility-first locator is what the model originally targeted; the generated code should reflect that.
  • Missing assertions on side effects. "Click submit" without asserting "order confirmation appears" leaves the test green even when the click did nothing. Be explicit in the prompt about the success oracle, and make sure the generated test asserts it.

Think of the AI session as a correct-by-construction demonstration and the generated test as a transcription. Transcriptions need editing.

⚠️ Common mistakes

  • Asking the assistant to "wait a bit" instead of for a specific signal. Vague waits become hard-coded timeouts in the generated code, which is the single most common source of flake. Always specify what to wait for — text, URL change, network response — never how long.
  • Skipping browser_snapshot between actions. When a click opens a modal and the next prompt step targets a button inside the modal, the assistant must re-snapshot first. Strong sessions are explicit: "after each navigation or modal open, re-snapshot before clicking." Weak sessions race ahead and fail on stale refs.
  • Treating success in the chat as success in the system. The assistant says "login complete." Did it actually log in? The only way to know is to assert against a post-login signal — a URL change, a header avatar, a server-side cookie. Build that oracle into the prompt; don't trust the prose.

🎯 Practice task

Run a flow and read every tool call. 25 minutes.

  1. Pick a real flow on your staging app — login, add to cart, file an issue. Write a precise prompt: starting URL, exact inputs, what to verify at the end (with a specific oracle, e.g. "assert the URL contains /dashboard" or "confirm the heading reads 'Welcome, X'").
  2. Run the prompt. While it runs, expand every tool call in the panel and note the arguments. Sketch the call graph — it should look like the flowchart above. If the assistant skipped a snapshot or used a fixed wait, note it.
  3. Ask the assistant: "Emit the equivalent Playwright TypeScript test for this flow. Use getByRole, getByLabel, and getByText. No hard-coded timeouts. Assert the success oracle explicitly." Save the output to tests/ai-generated-login.spec.ts.
  4. Run the generated test with npx playwright test --headed. If it passes first try, go through it line by line and look for assumptions that won't hold across runs (e.g., a unique ID that won't exist on a fresh database). If it fails, diagnose the failure against the tool-call log — usually it's a missing wait or a too-aggressive selector.
  5. Stretch: introduce a small flake into the app (slow down a network call by 1.5 seconds). Re-run the generated test. If it goes flaky, refactor the wait logic until it's stable. This loop — AI seed → generated test → harden by hand — is the workflow chapter 3 covers in depth.

The tools above account for ~90% of every session. The next lesson covers the more specialised tools — network, JavaScript eval, file upload — that handle the last 10% but unlock workflows nothing else can.

// tip to track lessons you complete and pick up where you left off across devices.