Browser view: find every Playwright, and set one up in two clicks
Build App / compute-version (pull_request) Successful in 14s
Build App / build-macos (pull_request) Successful in 2m30s
Build App / build-windows (pull_request) Successful in 5m18s
Build App / build-linux (pull_request) Successful in 6m42s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped

Detection missed the npx cache, so a Playwright installed through Claude
Code's MCP setup (`npx @playwright/mcp@latest`, which unpacks into
~/.npm/_npx/<hash>/node_modules and no node_modules at all) was invisible.
The probe now globs that cache alongside the existing roots and reports
every root it consulted.

It also read `has_bind` off whichever manifest resolved first. Verified
that npm does not hoist for global installs and that the `playwright`
wrapper ships no types/types.d.ts, so `npm i -g playwright` made the pane
call a current build "predates browser.bind()". The probe now hops from
the wrapper to its nested playwright-core.

The messages no longer offer `@playwright/mcp` as a way through setup: it
bundles a playwright-core that binds but never `@playwright/cli`, so that
route could not have worked. It is named only for what it does do.

New `install.rs` + two commands do the setup, streaming on the existing
`container-progress` event and re-probing on success:

  * playwright + @playwright/cli into /workspace as `claude`, --no-save.
    /workspace is not a bind mount (projects mount at
    /workspace/{mount_name}), so nothing of the user's is touched, no sudo
    is needed, and Node resolves it from scripts in the project.
  * A browser, as its own action with the size stated first: apt libraries
    as root, then the download, then a real headless launch to prove it
    works. The base image ships none of Chromium's shared libraries, which
    is why a download could succeed and the browser still not start.
    Chromium and the Chrome channel are both offered — @playwright/mcp
    asks for `chrome` specifically. A certificate failure is reported as a
    container trust-store problem rather than a broken install.

Installing is always user-initiated; opening the tab only probes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSP2KNPhuWKQ4DL5TZEn3k
This commit is contained in:
2026-08-10 10:15:02 -07:00
co-authored by Claude Opus 5
parent 584fcdd837
commit a5bcc462a7
10 changed files with 1795 additions and 93 deletions
+19 -1
View File
@@ -1,5 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
import type { Project, ProjectPath, ContainerInfo, SiblingContainer, AppSettings, UpdateInfo, ImageUpdateInfo, FileEntry, WebTerminalInfo, SttStatus, GatewayStatus, InstallOptions, ClaudeSession, ContainerCapabilities, ScheduledTask, ScheduledTaskInput, SchedulerNotification, AuthBridgeStatus, BrowserViewStatus, PlaywrightDetection, ContainerStaleness, MigrationOptions, MigrationReport, MigrationState, ClearTokenOutcome } from "./types";
import type { Project, ProjectPath, ContainerInfo, SiblingContainer, AppSettings, UpdateInfo, ImageUpdateInfo, FileEntry, WebTerminalInfo, SttStatus, GatewayStatus, InstallOptions, ClaudeSession, ContainerCapabilities, ScheduledTask, ScheduledTaskInput, SchedulerNotification, AuthBridgeStatus, BrowserViewStatus, PlaywrightDetection, BrowserSetupOutcome, BrowserInstallTarget, ContainerStaleness, MigrationOptions, MigrationReport, MigrationState, ClearTokenOutcome } from "./types";
// Docker
export const checkDocker = () => invoke<boolean>("check_docker");
@@ -178,6 +178,24 @@ export const getBrowserViewStatus = (projectId: string) =>
/** Probe for Playwright without starting anything — used to re-check after installing it. */
export const checkBrowserViewSupport = (projectId: string) =>
invoke<PlaywrightDetection>("check_browser_view_support", { projectId });
/**
* Install `playwright` + `@playwright/cli` into the container's `/workspace`.
*
* A container mutation, so it only ever runs from an explicit click. Progress
* streams on the existing `container-progress` event; the result carries a
* fresh probe. Browsers are a separate action — see below.
*/
export const installBrowserViewSupport = (projectId: string) =>
invoke<BrowserSetupOutcome>("install_browser_view_support", { projectId });
/**
* Install a browser and the apt libraries it needs, then verify it launches.
* `chromium` is Playwright's own build; `chrome` is the channel
* `@playwright/mcp` asks for. Hundreds of MB — never call this implicitly.
*/
export const installBrowserViewBrowser = (
projectId: string,
browser: BrowserInstallTarget,
) => invoke<BrowserSetupOutcome>("install_browser_view_browser", { projectId, browser });
// Shared Claude Code auth token — one `claude setup-token` run authenticates
// every Anthropic-backend project. The token itself is never exposed here: it
+27 -1
View File
@@ -434,14 +434,40 @@ export interface PlaywrightDetection {
node_version: string | null;
playwright_version: string | null;
playwright_path: string | null;
/** Playwright's own `cli.js`, which installs browsers and their apt libraries. */
playwright_cli: string | null;
/** Whether the resolved Playwright declares the `browser.bind()` live-dashboard API. */
has_bind: boolean;
cli_version: string | null;
cli_entry: string | null;
/** Module roots the probe searched, echoed back for the "not found" message. */
/** Browser bundles in `~/.cache/ms-playwright`, e.g. `chromium-1200`. Never `ffmpeg-*`. */
browsers: string[];
/** Path to Google Chrome when the `chrome` channel — what `@playwright/mcp`
* asks for — is installed. It is an apt package, so it is never in `browsers`. */
chrome_channel: string | null;
/** Module roots the probe searched, echoed back for the "not found" message.
* Includes the npx cache (`~/.npm/_npx/*/node_modules`), which is where a
* Playwright installed through Claude Code's MCP setup actually lives. */
searched: string[];
}
/** Result of an install action. Mirrors Rust `BrowserSetupOutcome`. */
export interface BrowserSetupOutcome {
/** Fresh probe taken after the install, so the pane can update itself. */
detection: PlaywrightDetection;
/** Tail of the real npm/apt/Playwright output — shown instead of a generic message. */
log: string;
/** Whether a browser was actually started and closed. `null` when the step
* didn't try (the package step doesn't). */
browser_launched: boolean | null;
/** Something that didn't fail the action but the user still needs to know. */
warning: string | null;
}
/** Browsers the pane can install. `chromium` is Playwright's own build;
* `chrome` is the Google Chrome channel `@playwright/mcp` asks for. */
export type BrowserInstallTarget = "chromium" | "chrome";
/** Mirrors Rust `BrowserViewState` (serde snake_case). */
export type BrowserViewState = "off" | "running" | "unavailable";