Browser automation for GTM workflows — Chrome extension (user's authenticated session) as primary, Playwright as fallback. Two engines: (1) Claude in Chrome extension for acting inside the user's logged-in browser (SaaS apps, cookie-based workflows, user's extensions/state), (2) Playwright for headless scripted automation, testing, screenshots, CI/CD. Triggers on browser automation, web page interaction, form filling, scraping, testing web UIs, frontend debugging (console/network/DOM), automating browser-based workflows, tasks on behalf of user in browser, web services without APIs, playwright, browser test, screenshot, test the page.
From gtmnpx claudepluginhub inkeep/team-skills --plugin gtmThis skill uses the workspace's default tool permissions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Configures VPN and dedicated connections like Direct Connect, ExpressRoute, Interconnect for secure on-premises to AWS, Azure, GCP, OCI hybrid networking.
Browser automation with two engines: Chrome extension (primary) for authenticated session work, Playwright (fallback) for headless/scripted automation.
| Chrome extension (primary) | Playwright (fallback) | |
|---|---|---|
| Engine | Claude in Chrome extension (user's Chrome) | Playwright (own browser instance) |
| Best for | Acting inside the user's authenticated browser session — logged-in SaaS apps, cookie-based workflows, tasks that need the user's extensions/state | Testing, QA, screenshots, CI/CD, scripted multi-step flows, performance audits, video recording |
| Auth model | Inherits the user's live Chrome session (cookies, localStorage, extensions) | Starts fresh; save/load auth state explicitly |
| Headless | No — requires a visible Chrome window | Yes (default) |
| Scriptable | Extension tool surface (read_page, computer, form_input, javascript_tool) | Full Playwright API, helper library, session mode |
| Works in Docker/CI | No | Yes |
Decision flow:
claude --chrome not used, tab group not set up), fall back to Playwright via /eng:browser rather than giving up./eng:browser./eng:browser for Playwright automation.How /eng:browser differs from this skill: /eng:browser is Playwright-only — no Chrome extension, no authenticated session inheritance. Use it directly when you know you need pure Playwright. This skill tries the Chrome extension first and falls back to Playwright when the extension isn't available.
The Claude in Chrome extension can be controlled from different surfaces:
| Entry Point | How to Connect | Best For |
|---|---|---|
| Standalone | Open extension side panel in Chrome | Browser-native workflows, shortcuts, scheduling |
| Claude Code CLI | Run claude --chrome, verify with /chrome | Dev/test loops, terminal + browser chaining |
| Cowork / Claude Desktop | Enable "Claude in Chrome" connector in Desktop settings | Knowledge work with browser access |
All three use the same extension and share the same core browser capabilities. The difference is what orchestrates the automation (side panel UI vs CLI vs Desktop).
For skills with a scripts/ folder containing reusable JavaScript SDKs:
javascript_tool → cache in localStorage~/.claude/use-browser-skills/{skill-name}/scripts/For tasks that use Chrome extension tools directly (no pre-saved scripts):
read_page, find, computer, form_input, navigate, javascript_tool directlyWhen to use Pattern B:
How to tell which pattern:
scripts/*.js → Pattern A (script injection)scripts/ folder → Pattern B (direct automation)Regardless of pattern, all browser automation follows this flow:
CRITICAL: Always call tabs_context_mcp first to get valid tab IDs.
1. tabs_context_mcp → Get available tabs in MCP group
2. tabs_create_mcp → Create new tab if needed
3. navigate(url, tabId) → Go to target page
Without this step, you won't have valid tab IDs and tools will fail.
If this step fails (extension not connected, tools not available): fall back to Playwright via /eng:browser.
read_page(tabId) → Get accessibility tree with element refs (ref_1, ref_2, ...)
find(query, tabId) → Natural language search for elements
get_page_text(tabId) → Extract plain text (for reading articles)
computer(action="screenshot") → Visual capture when DOM isn't enough
computer(action="left_click", ref="ref_1") → Click by reference (preferred)
form_input(ref="ref_2", value="text") → Fill form fields
computer(action="type", text="hello") → Type text
computer(action="scroll", direction="down") → Scroll
javascript_tool(text="...", tabId) → Execute JS directly
read_page(tabId) → Check new state
computer(action="screenshot") → Visual verification
read_console_messages(tabId) → Check for JS errors
read_network_requests(tabId, urlPattern="/api")→ Verify API calls
This skill provides a mechanism to:
Key insight: HTTPS pages block HTTP resources (mixed content). We read scripts from the filesystem and inject via javascript_tool, which uses the Chrome extension's privileged APIs to bypass page security restrictions.
┌─────────────────────────────────────────────────────────────────────┐
│ Three Entry Points │
├────────────────────┬─────────────────────┬──────────────────────────┤
│ Claude Desktop │ Claude Code CLI │ Extension Side Panel │
│ (incl. Cowork) │ (claude --chrome) │ (Standalone) │
│ │ │ │ │ │ │
│ └────────────┴─────────┴───────────┴─────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────┐ │
│ │ Claude in Chrome Extension │ │
│ │ (claude-in-chrome MCP) │ │
│ └───────────────┬───────────────────┘ │
│ │ │
│ ┌───────────────────────────┼───────────────────────────────────┐ │
│ │ Tool Surface │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │ │
│ │ │ read_page │ │ computer │ │ javascript_tool │ │ │
│ │ │ find │ │ form_input │ │ (Pattern A injection) │ │ │
│ │ │ get_page_text│ │ navigate │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └────────────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │ │
│ │ │ tabs_context │ │ gif_creator │ │ read_console_messages │ │ │
│ │ │ tabs_create │ │ upload_image │ │ read_network_requests │ │ │
│ │ └──────────────┘ └──────────────┘ └────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Browser Tab (any origin) │ │
│ │ ┌─────────────────┐ ┌──────────────────────────────────┐ │ │
│ │ │ localStorage │ │ window.{SDK} (injected script) │ │ │
│ │ │ (cached code) │ │ e.g. ChatGPTWebSDK │ │ │
│ │ └─────────────────┘ └──────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Script Source (Pattern A): ~/.claude/use-browser-skills/{skill-name}/scripts/{script}.js
Required for Chrome extension automation:
tabs_context_mcp first to get valid tab IDsFor Claude Code CLI:
claude --chrome flag/chrome commandFor Cowork / Claude Desktop:
For Pattern A (script injection) additionally:
~/.claude/use-browser-skills/{skill-name}/ with a scripts/ directoryhost-access skill to read files from host filesystemRead toolBrowser sub-skills are assets consumed by this skill, not standalone skills. They live in a dedicated directory outside the skills tree:
~/.claude/use-browser-skills/
├── use-chatgpt-in-browser/
│ ├── SKILL.md # Documents the SDK's API and workflows
│ └── scripts/
│ └── chatgpt-web-sdk.js
└── {your-skill}/
├── SKILL.md
└── scripts/
└── {script-name}.js
Each browser sub-skill has its own SKILL.md documenting:
Browser sub-skills are user/team-specific. They are NOT part of the shared skill and are not discovered by the skills CLI. This skill scans this directory as part of Pattern A.
ls ~/.claude/use-browser-skills/
For injecting pre-built SDKs from ~/.claude/use-browser-skills/. For direct automation without injection, use the Core Workflow above.
List available sub-skills: ls ~/.claude/use-browser-skills/
Load: ~/.claude/use-browser-skills/{skill-name}/SKILL.md
Understand:
// Use tabs_context_mcp to get available tabs
// Create a new tab if needed with tabs_create_mcp
// Navigate to target page with navigate tool
For Claude Code (native):
1. Read: ~/.claude/use-browser-skills/{skill-name}/scripts/{script}.js
2. Compute hash (first 8 chars of simple hash)
For Claude Cowork (VM):
1. Use host-access to read file via Command Relay
2. Compute hash from content
Hash function (JavaScript):
function simpleHash(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash = hash & hash;
}
return Math.abs(hash).toString(16).substring(0, 8);
}
Inject the minified loader via javascript_tool (replace {S} and {H}):
(function(s,h){var k="_skill_"+s,c=localStorage.getItem(k);if(c){try{var d=JSON.parse(c);if(d.version===h){eval(d.code);return{status:"cached",skill:s}}return{status:"stale",cached:d.version,expected:h}}catch(e){return{status:"error",msg:e.message}}}return{status:"not_cached"}})("{S}","{H}")
Returns: {status:"cached"|"stale"|"not_cached"|"error",...}
If loader returns "stale" or "not_cached":
javascript_tool{S}, {H}, {C}):(function(s,h,c){try{localStorage.setItem("_skill_"+s,JSON.stringify({version:h,code:c}));return{status:"stored",skill:s,size:c.length}}catch(e){return{status:"error",msg:e.message}}})("{S}","{H}","{C}")
Note: {C} must be the script code with quotes escaped (replace " with \").
typeof window.{GlobalObject} !== "undefined" ? "ready" : "not loaded"
Follow the workflows documented in the skill's own SKILL.md.
| Approach | Works on HTTPS? | Why |
|---|---|---|
<script src="http://localhost/..."> | No | Mixed content blocked |
fetch("http://localhost/...") | No | Mixed content blocked |
javascript_tool with inline code | Yes | Extension injects directly via chrome.scripting.executeScript |
The Chrome extension has elevated privileges. When we pass code to javascript_tool, the extension injects it directly into the page's JavaScript context. The browser sees this as extension-injected code, not as the page loading an external resource.
localStorage per-origin_skill_{skillName}{ version: hash, code: scriptContent }Must have:
Should have:
| Issue | Cause | Solution |
|---|---|---|
| "Tab not found" | Tab closed or wrong ID | Call tabs_context_mcp to refresh |
| "Invalid tab ID" | Didn't call tabs_context_mcp first | Always start with tabs_context_mcp |
| Extension not connecting | Claude Code not started with --chrome | Restart with claude --chrome |
| Connector not working | Wrong model or not enabled | Use Haiku/Sonnet/Opus 4.5, enable per conversation |
| Script not loading | JavaScript syntax error | Check console with read_console_messages |
| Cache stale | Script was updated | Loader auto-detects via hash mismatch |
| "undefined" after injection | Wrong global object name | Check skill's SKILL.md for correct name |
| Mixed content error | Using HTTP URL approach | Use inline injection (this skill's approach) |
| Element ref not found | Page changed since read_page | Re-run read_page to get fresh refs |
| Click not working | Using coordinates on dynamic page | Prefer ref-based clicks over coordinates |
| Chrome extension not available | Extension not installed or not connected | Fall back to /eng:browser (Playwright) for the task |
(function(s,h){var k="_skill_"+s,c=localStorage.getItem(k);if(c){try{var d=JSON.parse(c);if(d.version===h){eval(d.code);return{status:"cached",skill:s}}return{status:"stale",cached:d.version,expected:h}}catch(e){return{status:"error",msg:e.message}}}return{status:"not_cached"}})("SKILL","HASH")
(function(s,h,c){try{localStorage.setItem("_skill_"+s,JSON.stringify({version:h,code:c}));return{status:"stored",skill:s,size:c.length}}catch(e){return{status:"error",msg:e.message}}})("SKILL","HASH","CODE")
(function(t){var h=0,i=0;for(;i<t.length;i++){h=((h<<5)-h)+t.charCodeAt(i);h=h&h}return Math.abs(h).toString(16).slice(0,8)})("INPUT")
typeof window.SDK !== "undefined"
| Situation | Load |
|---|---|
| Core browser automation | |
| Need tool parameter details | references/chrome-extension-tools.md |
| Choosing interaction approach (refs vs JS vs coords) | references/interaction-patterns.md |
| Batch/repeatable automation | references/batch-automation.md |
| Debugging with console/network | references/debugging.md |
| Pattern A: Script injection | |
| Need cache loader/store templates | scripts/loader.js, scripts/store.js |
| Debugging cache issues | references/caching-strategy.md |
| Writing injectable scripts (ES5 syntax) | references/javascript-constraints.md |
| Browser-skills (sub-skills) | |
| Add your own browser sub-skills | Create a directory in ~/.claude/use-browser-skills/{your-skill}/ with SKILL.md + scripts/ |