When you automate browser work by combining Claude Code with Playwright MCP, the recurring problem is how to run “the same login steps as always” against a test environment with as few tool round trips as possible, in a form that later operations can build on.

This article walks through how I arrived at a design that runs a real Playwright script end to end on the same page via browser_run_code_unsafe, along with the benefits, the risks, and the alternatives I rejected. It is written as a general automation pattern rather than something tied to any particular service.

What was I trying to automate?

Making a repeated login sequence routine

The target was a standard login to a staging environment under test. The content is nearly identical every time, and it repeats this sequence:

  • Overriding the user agent and headers to get past a WAF or similar front layer
  • Checking whether access has been refused with a 403
  • Logging out of any existing session
  • Entering the email and password into the login form and submitting
  • Confirming the transition to the post-login page

Doing this step by step from Claude Code through Playwright MCP produces a long run of tool calls. Every extra round trip costs both time and session budget, so I looked for a design that minimises round trips while keeping the result easy to build on.

Credentials never touch the disk

A working constraint: credentials — email, password, bypass tokens — are never saved to a file in plain text. Everything is fetched from a secure store each time and handled only in memory.

What design did I settle on?

The runtime flow

What I adopted was pouring a templated Playwright script onto the same page in a single tool call. At runtime it is four steps:

  1. Fetch the credentials from the secure store (email, password, bypass token)
  2. Load a static template JS file — real Playwright code that takes page and runs — and safely substitute the placeholders (__EMAIL__ and so on) with the fetched values, escaping via JSON.stringify. That produces a finished JS code string
  3. Call browser_run_code_unsafe exactly once, passing the finished code in the code parameter. Inside the same browser and page it runs the whole sequence: UA override, 403 check, logout, form entry, submit, transition check
  4. Read the return value (success or failure, destination URL) and move on to the steps specific to whatever you are actually doing, using ordinary browser_snapshot and click calls

The point is that the tool call count comes down to essentially two: one to fetch credentials, one for run_code_unsafe.

The template is working code, not a written procedure

The heart of this design is that the template is not prose instructions but an actual Playwright script that takes page and runs. Substituting real values for the placeholders is all it takes to make it executable.

What are the benefits?

Far fewer round trips, with no handover problem

The main advantage is getting both the reduction in round trips and the ease of carrying the browser forward.

  • A real Playwright script: the template is code that runs, not a procedure to be interpreted. There is no room for interpretation to drift
  • Round trips collapse: the sequential-tool-call approach took six or seven; this takes two, cutting both elapsed time and session consumption
  • No browser handover problem: browser_run_code_unsafe runs on the same page that later operations use. The concern with the separate Node script option — that the login happens in a different browser and cannot be carried forward — does not arise
  • Nothing extra to install: the script runs inside the Playwright MCP server’s own process, so the machine running it does not need its own Node.js or Playwright. MCP is already a prerequisite, so no new environment dependency appears
  • Secrets never reach the disk: credentials are passed in memory through the code parameter and never written to a file

What are the drawbacks and risks?

This is a high-privilege tool

There are risks that should not be brushed aside, and the most important is that browser_run_code_unsafe carries privileges equivalent to arbitrary code execution. Understand that before you put it into regular use.

  • Effectively RCE-level privilege: browser_run_code_unsafe is officially described as equivalent to remote code execution. Depending on your environment’s permission mode, an approval prompt on first use is likely, and it draws more scrutiny than ordinary clicks and typing
  • Unverified selectors: the selectors for the email field, password field and submit button may have been inferred from a written procedure rather than confirmed against the real DOM. Expect the first run to fail and to need adjustment against the actual page
  • The substitution step never goes away: replacing placeholders with real values is code generation, so a mistake there becomes a runtime error. Since the values change every time, some assignment step will always remain
  • Fetching credentials still costs something: as long as you refuse to store them in plain text, one fetch per run remains — though it is a single fetch, not repeated probing
  • Consoles requiring human approval are out of scope: an admin console behind SAML plus multi-factor authentication that requires a person to approve cannot be automated by this script. Reaching that point and asking a human still applies

How does it compare with the alternatives?

Several options were compared before landing on this one. Lining them up on round trips, handover, and machine dependency makes the differences clear.

Option What it is Round trips Handover Machine dependency
Original idea (written procedure) A Markdown procedure, executed as sequential tool calls Many (6–7) Fine None
Option A (single evaluate) Consolidated into one browser_evaluate call Few (2–3) Fine None
Option B (separate Node script) A fully independent script run through Bash Fewest (1) Different browser, cannot be carried forward Needs Node and Playwright installed
Adopted run_code_unsafe executing on the same page Few (2) Fine None (runs inside the MCP process)

The adopted option gets essentially Option B’s speed while keeping Option A’s handover behaviour and independence from the local machine.

Summary: balancing speed against handover

If you are automating a routine login with Claude Code and Playwright MCP, pouring a templated, real Playwright script onto the same page through browser_run_code_unsafe was the practical answer for getting few round trips and easy browser handover at the same time.

That said, the tool carries privileges equivalent to arbitrary code execution, so approval flows and real-world selector verification cannot be skipped. Keep secrets off the disk and pass them in memory each time, and stay careful with a high-privilege tool — those two points are what make this safe to run regularly.

Note: this describes the thinking at design time. Tool permission models and APIs may change. Handle credentials according to your own environment’s security policy.

ABOUT ME
りん
On this blog, I mainly share information about web development and programming, along with my daily thoughts and what I’ve learned. I aim to create a blog that lets readers enjoy both technology and everyday life, so I also include topics about daily experiences, books, and gourmet. I’d be delighted if you could drop by casually and find something useful or enjoyable here.