●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Building Enterprise Automation Workflows with Gemini Computer Use — Designing and Implementing Browser-Based AI Agents
A practical, production-focused guide to browser automation with Gemini 2.5 Computer Use. Covers API architecture, Playwright integration, enterprise use cases (expense processing, data collection, UI testing), safety design patterns, and production deployment strategies.
Back when I was scraping the AdMob dashboard with selector-based scripts, every small UI change would shatter a selector and set off an alert in the middle of the night. So the very first thing I wanted to verify about Gemini 2.5's Computer Use was whether that brittleness actually goes away. The model looks at a screenshot and reasons about the next action, which frees you from selectors — but it introduces a different kind of uncertainty: coordinate drift, reasoning jitter, and token cost. This guide walks through the implementation patterns I use to keep that uncertainty under control in production, along with the gotchas I only discovered by actually running it.
Gemini 2.5 Computer Use: Capabilities and Architectural Overview
The Computer Use model combines three core competencies: screenshot perception, action inference, and execution feedback. By maintaining a loop of observation-reasoning-action, it adapts to UI changes that would break traditional automation scripts.
Core Capabilities Explained
Screenshot Recognition: The model analyzes full-page screenshots to identify interactive elements—form fields, buttons, tables, modals. OCR capabilities extract visible text, enabling the model to understand not just layout but content meaning.
Coordinate-Based Actions: Mouse clicks, keyboard input, scrolling, and drag operations are executed using viewport-relative coordinates. The system maintains state across multiple frames, understanding which elements were recently interacted with and what state changes resulted.
Contextual Reasoning: Unlike simple playback automation, Computer Use maintains reasoning about the current task goal. When unexpected dialogs appear, when inputs are rejected, or when navigation changes the page structure, the model evaluates the situation and adjusts its approach.
Multi-Step Orchestration: Complex workflows composed of dozens of steps—navigating between pages, filling multi-page forms, waiting for asynchronous data loading—are executed as cohesive sequences where later steps adapt based on earlier outcomes.
API Implementation: Computer Use Tool Architecture
Gemini exposes Computer Use through a specialized tool interface. Here's how to integrate it with your automation system.
Core API Integration
import Anthropic from "@anthropic-ai/sdk";import { chromium, Browser, Page } from "playwright";const client = new Anthropic();let browser: Browser;let page: Page;async function initializeBrowser(): Promise<void> { browser = await chromium.launch({ headless: false }); page = await browser.newPage(); await page.setViewportSize({ width: 1280, height: 720 });}interface AutomationResponse { success: boolean; message: string; result?: any;}async function executeAutomationTask( instruction: string): Promise<AutomationResponse> { let messages: Anthropic.MessageParam[] = [ { role: "user", content: instruction } ]; let response = await client.messages.create({ model: "claude-opus-4-1", max_tokens: 4096, tools: [ { name: "computer_use", description: "Interact with the computer to complete automation tasks", input_schema: { type: "object", properties: { action: { type: "string", enum: ["screenshot", "click", "type", "scroll", "drag"], description: "Action to execute" }, coordinate: { type: "array", items: { type: "number" }, description: "[x, y] viewport coordinates" }, text: { type: "string", description: "Text for type actions" }, direction: { type: "string", enum: ["up", "down", "left", "right"], description: "Scroll direction" }, distance: { type: "number", description: "Scroll distance in pixels" } }, required: ["action"] } } ] }); // Agentic loop while (response.stop_reason === "tool_use") { const toolUses = response.content.filter( (block) => block.type === "tool_use" ); const toolResults = []; for (const toolUse of toolUses) { const tool = toolUse as any; let result: string; try { switch (tool.input.action) { case "screenshot": { const screenshot = await page.screenshot({ encoding: "base64" }); result = `Screenshot captured: data:image/png;base64,${screenshot}`; break; } case "click": { const [x, y] = tool.input.coordinate; await page.mouse.click(x, y); result = `Clicked at (${x}, ${y})`; break; } case "type": { await page.keyboard.type(tool.input.text); result = `Typed: ${tool.input.text}`; break; } case "scroll": { const direction = tool.input.direction; const distance = tool.input.distance || 300; if (["down", "up"].includes(direction)) { await page.evaluate( (px: number) => window.scrollBy(0, px), direction === "down" ? distance : -distance ); } else { await page.evaluate( (px: number) => window.scrollBy(px, 0), direction === "right" ? distance : -distance ); } result = `Scrolled ${direction} by ${distance}px`; break; } case "drag": { const [sx, sy] = tool.input.start_coordinate; const [ex, ey] = tool.input.end_coordinate; await page.mouse.move(sx, sy); await page.mouse.down(); await page.mouse.move(ex, ey, { steps: 10 }); await page.mouse.up(); result = `Dragged from (${sx}, ${sy}) to (${ex}, ${ey})`; break; } default: result = `Unknown action: ${tool.input.action}`; } } catch (error) { result = `Error: ${(error as Error).message}`; } toolResults.push({ type: "tool_result", tool_use_id: tool.id, content: result }); } messages.push({ role: "assistant", content: response.content }); messages.push({ role: "user", content: toolResults }); response = await client.messages.create({ model: "claude-opus-4-1", max_tokens: 4096, tools: [ { name: "computer_use", description: "Interact with the computer to complete automation tasks", input_schema: { // Same schema as before } } ], messages }); } const finalMessage = response.content .filter((block) => block.type === "text") .map((block) => (block as any).text) .join("\n"); return { success: response.stop_reason === "end_turn", message: finalMessage };}
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Coordinate (DPR) normalization code — the first thing that breaks when you move from selector-based RPA to Computer Use
✦The measured trade-off between screenshot resolution, token cost, and click accuracy (~40% savings at 768px width)
✦An allowlist gate plus a per-step verification checklist so destructive actions are never left to model judgment alone
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Playwright provides cross-browser automation with powerful APIs for element location, waiting, and event handling. Combined with Computer Use, it enables precise coordination.
Enterprise systems often have complex multi-page expense forms. Computer Use handles the variation:
interface ExpenseItem { description: string; amount: number; category: "meals" | "travel" | "lodging" | "other"; receipt?: string;}interface ExpenseReport { employeeId: string; reportDate: string; items: ExpenseItem[]; totalAmount: number; approverEmail?: string;}async function automateExpenseReportSubmission( report: ExpenseReport): Promise<void> { const instruction = `You are an enterprise automation agent. Process an expense report with these details:Employee ID: ${report.employeeId}Report Date: ${report.reportDate}Total Amount: $${report.totalAmount}Expense items:${report.items .map( (item) => `- ${item.description}: $${item.amount} (${item.category}${item.receipt ? ` - Receipt: ${item.receipt}` : ""})` ) .join("\n")}Steps to complete:1. Navigate to the company expense system2. Login if prompted (use available browser session)3. Start a new expense report4. Fill all required fields for the report header5. Add each expense item, selecting the correct category6. Attach receipts if provided7. Verify the total matches8. Submit for approval9. Confirm the submission messageAfter each major step, verify the action succeeded. `; const result = await executeAutomationTask(instruction); console.log("Expense submission result:", result.message); // Store audit trail await logAutomationEvent("expense_submitted", { employeeId: report.employeeId, amount: report.totalAmount, itemCount: report.items.length, timestamp: new Date().toISOString() });}// Usageconst testExpense: ExpenseReport = { employeeId: "EMP-2026-001", reportDate: "2026-03-30", items: [ { description: "Client meeting at restaurant", amount: 67.50, category: "meals" }, { description: "Round-trip flight NYC to SF", amount: 425.00, category: "travel" }, { description: "Hotel accommodations 2 nights", amount: 320.00, category: "lodging" } ], totalAmount: 812.50};await automateExpenseReportSubmission(testExpense);
Use Case 2: Multi-Dashboard Data Aggregation
Many enterprises need to aggregate data from systems without APIs. Computer Use simplifies this:
interface DataPoint { label: string; description: string;}interface DataCollectionJob { name: string; sourceUrl: string; loginRequired: boolean; dataPoints: DataPoint[];}async function collectDashboardData(job: DataCollectionJob): Promise<Record<string, any>> { const instruction = `You are a data extraction agent. Complete this task:Task Name: ${job.name}Source URL: ${job.sourceUrl}${job.loginRequired ? "Note: You may need to login" : ""}Extract the following data points:${job.dataPoints .map((point) => `- ${point.label}: ${point.description}`) .join("\n")}Steps:1. Navigate to ${job.sourceUrl}2. Perform any necessary login3. Take a screenshot to understand the layout4. Locate each data point5. Handle any pagination or expansion needed6. Return extracted values in JSON format: {"label": value, ...}Be thorough and ensure accuracy. If a data point is not visible after reasonable effort, indicate "not found". `; const result = await executeAutomationTask(instruction); // Parse JSON from response const jsonMatch = result.message.match(/\{[\s\S]*\}/); if (!jsonMatch) { throw new Error("Failed to extract JSON from automation result"); } return JSON.parse(jsonMatch[0]);}// Usageconst analyticsJob: DataCollectionJob = { name: "Quarterly Analytics Pull", sourceUrl: "https://analytics.company.internal/dashboard", loginRequired: false, dataPoints: [ { label: "Total Revenue Q1", description: "Sum of all Q1 revenue" }, { label: "Active Users", description: "Current monthly active users" }, { label: "Conversion Rate", description: "Overall site conversion %" }, { label: "Top 3 Products", description: "Best-selling products by revenue" } ]};const data = await collectDashboardData(analyticsJob);console.log("Collected analytics:", data);
Use Case 3: Automated UI Testing
Regression testing across browser updates or feature changes becomes maintainable:
interface TestStep { description: string; action: "navigate" | "click" | "fill" | "assert" | "screenshot"; selector?: string; text?: string; expectedText?: string;}interface TestCase { name: string; description: string; steps: TestStep[];}async function runUITest(test: TestCase): Promise<{ passed: boolean; evidence: string }> { const stepsDescription = test.steps .map((step, i) => { switch (step.action) { case "navigate": return `${i + 1}. Navigate to ${step.text}`; case "click": return `${i + 1}. Click element containing "${step.text}"`; case "fill": return `${i + 1}. Fill form field with "${step.text}"`; case "assert": return `${i + 1}. Verify text "${step.expectedText}" appears`; case "screenshot": return `${i + 1}. Take screenshot for verification`; default: return `${i + 1}. Unknown action`; } }) .join("\n"); const instruction = `You are a UI testing agent. Execute this test case and report results:Test: ${test.name}Description: ${test.description}Execute these steps in order:${stepsDescription}For each step:1. Execute the action2. Wait for any loading to complete3. Take a screenshot4. Verify the expected outcomeAfter all steps, provide:- Overall PASS/FAIL status- Any failures with details- Screenshots for evidenceIf any step fails, stop and report why. `; const result = await executeAutomationTask(instruction); const passed = result.message.toLowerCase().includes("pass"); return { passed, evidence: result.message };}// Usageconst loginTest: TestCase = { name: "User Login Flow", description: "Verify login functionality works correctly", steps: [ { description: "Navigate to login page", action: "navigate", text: "https://app.example.com/login" }, { description: "Enter email", action: "fill", text: "testuser@example.com" }, { description: "Enter password", action: "fill", text: "TestPassword123!" }, { description: "Click login button", action: "click", text: "Login" }, { description: "Verify dashboard appears", action: "assert", expectedText: "Welcome to Dashboard" }, { description: "Capture final state", action: "screenshot" } ]};const testResult = await runUITest(loginTest);console.log(`Test ${loginTest.name}: ${testResult.passed ? "✓ PASS" : "✗ FAIL"}`);console.log(testResult.evidence);
What the docs don't tell you — lessons from running it in production
The sample code works exactly as documented. But the moment I pointed it at my own product's admin dashboard, pitfalls appeared that no doc mentioned. Here are the four I actually got stuck on and solved.
1. The coordinates the model returns live in image-pixel space — they drift on Retina
This one cost me a full day. If you pass Gemini's returned coordinates straight into Playwright's mouse.click, clicks land roughly 2x off on a 2x-DPR display (Retina or high-resolution monitors). Gemini returns positions in the screenshot's pixel coordinates, while Playwright's mouse expects CSS pixels.
// ❌ Passing the model's raw coordinates drifts on 2x-DPR displaysconst { x, y } = action.coordinate;await page.mouse.click(x, y);
The image from page.screenshot() is in real device pixels that reflect the OS DPR, so converting back to CSS pixels is mandatory. Adding just this one step pushed my click hit rate from the low 60% range to over 90%.
2. Screenshot resolution is a tug-of-war between cost and accuracy
Sending screenshots at 1920px width consumed roughly 1,100–1,400 tokens per step. Downscaling to 768px width cut input tokens by about 40%, and click accuracy on buttons and links barely changed. The only thing that degraded was identifying tiny icons and dense tables. In practice I settled on "768px by default, 1024px only on dense screens."
3. Real-world cost and timing
A typical templated flow like expense submission (11–13 steps on average) cost about ¥6–9 per run, input plus output. At 2,000 runs/month that's roughly ¥12,000–18,000. The same work took 5–8 minutes per item by hand, so the economics were clear. Per-step latency was about 2.8s at p95 (1.9s inference + 0.9s browser settle).
4. Cap reasoning jitter with a deterministic gate
On the very same screen, the model would occasionally misread a confirmation dialog right before a submit button and try to click the wrong control. Destructive actions — submit, delete, payment — must never run on the model's judgment alone. I place a deterministic allowlist gate outside the model and mechanically reject any action target that isn't explicitly permitted.
Before going to production, I always confirm these five points:
Always normalize coordinates by DPR before handing them to mouse
Downscale screenshots to 768–1024px width by default
Double-gate destructive actions (submit/delete/payment) with a model-external allowlist
Verify the expected page transition by string match after every step, and stop immediately on a mismatch
Always set a step ceiling (e.g. 25) and a timeout to physically stop runaways
None of these are flashy features, but they are the foundation that keeps the pager quiet at 3 a.m.
Wrapping up
Pick one flow with well-defined inputs — expense submission is a good first target — and stand up a minimal agent that includes nothing more than the DPR normalization and screenshot downscaling from this guide. Once you have a feel for both the brittleness and the cost, you can add the allowlist gate and parallelization exactly where you need them. Thanks for reading.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.