DEV Community

Cover image for I wasted a weekend on WSL2 browser automation so you don’t have to
Lars Winstand
Lars Winstand

Posted on • Originally published at standardcompute.com

I wasted a weekend on WSL2 browser automation so you don’t have to

If you want browser automation on Windows that actually survives contact with reality, treat WSL2 networking as the first problem.

Not Playwright.
Not OpenClaw.
Not your prompts.

The fastest demo is attaching from WSL2 to Windows Chrome over CDP on port 9222.

The setup I’d actually trust for a full week is simpler: run the agent and the browser in the same Linux environment with WSLg.

I went down this rabbit hole after seeing a thread from someone trying to run OpenClaw 2026.6.11 in Ubuntu on WSL2 and attach to an existing Windows Chrome session. That problem is way more common than people admit.

The dream is obvious:

  • Keep using your already logged-in Windows Chrome profile
  • Let a Linux-side agent inherit Gmail, Calendar, Slack, Notion, and everything else
  • Skip rebuilding auth flows from scratch

That sounds great until WSL2 reminds you it is not just “localhost with extra steps.”

The real bug is WSL2 networking

Most people assume this should work:

  1. Start Chrome on Windows with remote debugging enabled
  2. From Ubuntu in WSL2, connect to it with Playwright
  3. Profit

Sometimes it does work.

Sometimes you lose hours because WSL2 and Windows are on different networking assumptions.

By default, WSL2 uses NAT networking. That means Linux processes do not automatically see Windows services as if they were local.

So if Chrome is listening on Windows, localhost:9222 inside WSL2 may not mean what you think it means.

Find the Windows host IP from WSL2

On default NAT mode, this is the first command worth running:

ip route show | grep -i default | awk '{ print $3 }'
Enter fullscreen mode Exit fullscreen mode

That usually gives you the Windows host IP as seen from WSL2.

Then your Playwright connection looks more like this:

const { chromium } = require('playwright');

const browser = await chromium.connectOverCDP('http://172.30.96.1:9222');
Enter fullscreen mode Exit fullscreen mode

If you’re using mirrored networking instead of NAT, localhost:9222 may work directly.

That one detail changes the whole setup.

The fastest prototype: attach to Windows Chrome over CDP

If your actual goal is “reuse my existing authenticated Chrome session,” CDP is the cleanest shortcut.

Start Chrome on Windows with remote debugging enabled:

& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
Enter fullscreen mode Exit fullscreen mode

Then from WSL2:

const { chromium } = require('playwright');

const browser = await chromium.connectOverCDP('http://localhost:9222');
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();

await page.goto('https://mail.google.com');
Enter fullscreen mode Exit fullscreen mode

If localhost fails, swap it for the Windows host IP.

Why this is appealing

Because it reuses the browser profile you already care about.

No fresh Chromium install.
No signing into Google again.
No rebuilding sessions for every internal tool.

For a quick OpenClaw demo, or a one-off Playwright script, this is the shortest path.

The catch: CDP is the convenience path, not the best path

This is the part people skip when the demo starts working.

Playwright supports connectOverCDP(), but it also warns that CDP is lower fidelity than a native Playwright connection.

That matters.

You can absolutely use CDP for:

  • quick prototypes
  • local experiments
  • session reuse
  • debugging flows on your own machine

I would not choose it first for unattended automation.

Once you mix these pieces together:

  • Windows Chrome
  • Linux agent runtime
  • remote debugging
  • saved sessions
  • WSL2 networking modes

...you get a setup that feels clever right up until it breaks on Monday morning.

Should you let an agent drive your daily Chrome profile?

Honestly, no.

You can do it.
That doesn’t make it a good default.

If your Windows Chrome profile is signed into Gmail, GitHub, Slack, admin dashboards, and personal accounts, attaching an autonomous agent to it is convenient but risky.

It’s also brittle.

Extensions, saved state, local files, auth redirects, and random profile drift all become part of your automation environment.

That is not a clean test surface.

For a personal prototype, sure.

For a real workflow, I’d rather isolate the browser.

The setup I’d actually recommend: run both sides in Linux

The boring answer wins.

Run the agent and the browser in the same Linux environment inside WSL2.

If you’re on Windows 10 build 19044+ or Windows 11, WSLg makes Linux GUI apps much less painful than they used to be.

If GUI support is acting weird, refresh WSL first:

wsl --update
wsl --shutdown
Enter fullscreen mode Exit fullscreen mode

Then install Chromium or Chrome inside Ubuntu and run Playwright natively there.

Why this setup is better

Because a bunch of nonsense disappears:

  • no cross-OS browser attachment weirdness
  • no guessing whether localhost means Windows or Linux
  • no CDP fidelity tradeoffs when native Playwright will do
  • no accidental dependence on your personal Windows browser profile

This is the least painful local setup if you care about reproducibility.

Example: native Playwright inside WSL2

Install Playwright and browser deps:

npm init -y
npm install playwright
npx playwright install --with-deps chromium
Enter fullscreen mode Exit fullscreen mode

Then run a normal script:

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png' });
})();
Enter fullscreen mode Exit fullscreen mode

That’s it.

No Windows bridge.
No CDP attach.
No host IP lookup.

Just one runtime, one browser, one mental model.

Three setups, one clear ranking

Here’s the tradeoff table I wish I had before losing a weekend.

Option What you’re really signing up for
Windows Chrome over CDP from WSL2 Fastest prototype. Reuses your existing Windows sessions. Also depends on WSL2 networking, remote debugging, and Playwright’s lower-fidelity CDP path.
Linux Chrome or Chromium inside WSL2 with WSLg Best local reliability. Agent and browser share one environment. Cleaner Playwright support story. Costs you a separate Linux browser profile and fresh sign-ins.
Hosted browser runtime like Browserbase Best when local setup keeps wasting your time. Good for unattended jobs and team workflows. Adds an external dependency but removes most machine-specific browser pain.

My ranking:

  1. Linux Chrome or Chromium inside WSL2 with WSLg
  2. Windows Chrome over CDP for quick demos
  3. Hosted browser runtime once you’re done pretending local browser infrastructure is fun

When hosted browser infrastructure starts making sense

A hosted browser runtime sounds excessive right up until your third “works on my machine” failure.

If the browser needs to run unattended, or multiple people need the same setup, local WSL2 browser plumbing gets old fast.

That’s where something like Browserbase starts to look reasonable.

Not because local is impossible.
Because local becomes operationally expensive.

If your stack already includes:

  • OpenClaw
  • n8n
  • Make
  • Zapier
  • custom Playwright workers
  • long-running agents on an OpenAI-compatible API

...then browser reliability matters as much as model quality.

A flaky browser layer means retries, stuck jobs, broken sessions, and humans getting dragged back into the loop.

If there’s a real API, use the API

This is the other lesson people forget.

If all you need is Gmail or Google Calendar, use googleapis and OAuth2.

Don’t reach for browser automation just because agent demos make it look cool.

Browser control is for:

  • arbitrary websites
  • weird multi-step flows
  • internal tools with no API
  • dashboards that were never meant to be automated
  • sites where the browser is the interface

If the target already has a stable API, using browser automation is often self-inflicted pain.

What I’d do today

If I were setting up OpenClaw or any Playwright-based browser agent on Windows today, I’d pick one of these on purpose.

Option 1: I need a demo today

Use Windows Chrome with remote debugging and connect from WSL2 over CDP.

Best if:

  • you’re solo
  • you need existing authenticated sessions
  • you know this is a prototype shortcut

Option 2: I need local reliability

Run the agent and Chrome or Chromium together inside Linux on WSL2 with WSLg.

Best if:

  • you want fewer moving parts
  • you care about reproducibility
  • you don’t want networking weirdness in the middle of debugging

Option 3: I need this to work unattended

Use a hosted browser runtime.

Best if:

  • scheduled jobs matter
  • multiple people need the same environment
  • you’re tired of machine-specific failures

Why this matters for agent teams

If you’re building always-on agents, browser reliability and model economics are linked.

A cheap model doesn’t help much if the browser layer keeps falling over.

And if you’re paying per token while debugging flaky browser infrastructure, you get the worst of both worlds:

  • infrastructure anxiety
  • token anxiety

That’s why predictable model access matters.

If you’re running agents through n8n, Make, Zapier, OpenClaw, or custom automations, flat-rate OpenAI-compatible infrastructure like Standard Compute makes a lot more sense than watching token spend while your browser jobs retry themselves into the ground.

The practical combo is simple:

  • reliable browser execution
  • predictable model costs

That’s what makes 24/7 agent workflows viable.

My actual takeaway is boring, but useful:

Put the agent and the browser in the same environment unless you have a very specific reason not to.

Attaching to your normal Windows Chrome profile is a smart hack.

It’s just usually the first chapter of a much longer debugging story.

Top comments (0)