skills/dev-test-chrome/SKILL.md
This skill should be used when testing web applications with Chrome MCP tools, debugging console errors, monitoring network requests, executing JavaScript in browser, recording GIF test evidence, or when dev-test routes to Chrome-based browser testing.
npx skillsauth add edwinhu/workflows dev-test-chromeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Announce: "I'm using dev-test-chrome for Chrome browser automation with debugging."
<EXTREMELY-IMPORTANT> ## Gate ReminderBefore taking screenshots or running E2E tests, you MUST complete all 6 gates from dev-tdd:
GATE 1: BUILD
GATE 2: LAUNCH (with file-based logging)
GATE 3: WAIT
GATE 4: CHECK PROCESS
GATE 5: READ LOGS ← MANDATORY, CANNOT SKIP
GATE 6: VERIFY LOGS
THEN: E2E tests/screenshots
You loaded dev-tdd earlier. Follow the gates now. </EXTREMELY-IMPORTANT>
Verify Chrome MCP tools are available before proceeding.
Check for these MCP functions:
mcp__claude-in-chrome__read_pagemcp__claude-in-chrome__navigatemcp__claude-in-chrome__read_console_messagesmcp__claude-in-chrome__read_network_requestsIf MCP tools are not available:
STOP: Cannot proceed with Chrome MCP automation.
Missing: Chrome MCP server (claude-in-chrome extension)
The Chrome MCP requires:
1. Chrome browser with claude-in-chrome extension installed
2. Extension connected to Claude Code
3. Browser window visible (not headless)
Check your Claude Code MCP configuration.
Reply when configured and I'll continue testing.
This gate is non-negotiable. Missing tools = full stop. </EXTREMELY-IMPORTANT>
<EXTREMELY-IMPORTANT> ## When to Use Chrome MCPUSE Chrome MCP when you need:
console.log, console.error)DO NOT use Chrome MCP when:
For CI/CD and headless, discover and read the Playwright skill:
Read ${CLAUDE_SKILL_DIR}/../../skills/dev-test-playwright/SKILL.md and follow its instructions.
read_console_messages and read_network_requests record the same data as part of the test. An API response inferred rather than read is an unverified claim presented as fact.gif_creator) is proof an interaction worked; skipping it as "overkill" trades a one-command recording for an unevidenced claim.
</EXTREMELY-IMPORTANT>
| Tool | Purpose |
|------|---------|
| navigate | Navigate to URL |
| read_page | Get accessibility tree (page state) |
| find | Natural language element search |
| computer | Mouse/keyboard (click, type, scroll, screenshot) |
| form_input | Set form values |
| javascript_tool | Execute JS in page context |
| read_console_messages | Read browser console |
| read_network_requests | Read HTTP requests |
| get_page_text | Extract page text content |
| gif_creator | Record interactions as GIF |
| tabs_context_mcp | Get tab context |
| tabs_create_mcp | Create new tab |
DO NOT manually check console. Use read_console_messages.
If JavaScript errors exist, you MUST capture them automatically. </EXTREMELY-IMPORTANT>
mcp__claude-in-chrome__read_console_messages(
tabId=TAB_ID,
pattern="error|warning" # Filter by regex pattern
)
Always provide a pattern to avoid verbose output:
| Pattern | Captures |
|---------|----------|
| "error" | All error messages |
| "error\|warning" | Errors and warnings |
| "MyApp" | Application-specific logs |
| "API" | API-related messages |
| "fetch\|xhr" | Network-related logs |
# 1. Navigate to page
mcp__claude-in-chrome__navigate(tabId=TAB_ID, url="https://app.example.com")
# 2. Trigger the action
mcp__claude-in-chrome__computer(action="left_click", tabId=TAB_ID, coordinate=[500, 300])
# 3. Check for errors
mcp__claude-in-chrome__read_console_messages(
tabId=TAB_ID,
pattern="error|Error|ERROR",
onlyErrors=true
)
mcp__claude-in-chrome__read_console_messages(
tabId=TAB_ID,
pattern=".*",
clear=true # Clear after reading
)
DO NOT guess API responses. Use read_network_requests.
If debugging API calls, you MUST capture actual requests and responses. </EXTREMELY-IMPORTANT>
mcp__claude-in-chrome__read_network_requests(
tabId=TAB_ID,
urlPattern="/api/" # Filter by URL pattern
)
| Pattern | Captures |
|---------|----------|
| "/api/" | All API calls |
| "graphql" | GraphQL requests |
| "auth" | Authentication requests |
| "example.com" | Requests to specific domain |
# 1. Navigate and trigger action
mcp__claude-in-chrome__navigate(tabId=TAB_ID, url="https://app.example.com")
mcp__claude-in-chrome__computer(action="left_click", tabId=TAB_ID, ref="submit-button")
# 2. Wait for network activity
mcp__claude-in-chrome__computer(action="wait", tabId=TAB_ID, duration=2)
# 3. Inspect API calls
mcp__claude-in-chrome__read_network_requests(
tabId=TAB_ID,
urlPattern="/api/submit"
)
mcp__claude-in-chrome__read_network_requests(
tabId=TAB_ID,
clear=true
)
DO NOT assume page state. Execute JS to verify.
If you need to check page variables, DOM state, or run custom logic, use javascript_tool.
</EXTREMELY-IMPORTANT>
mcp__claude-in-chrome__javascript_tool(
action="javascript_exec",
tabId=TAB_ID,
text="document.querySelector('#my-element').innerText"
)
Get element text:
text="document.querySelector('.status').innerText"
Check if element exists:
text="document.querySelector('#login-button') !== null"
Get form values:
text="document.querySelector('input[name=email]').value"
Check localStorage:
text="localStorage.getItem('authToken')"
Get page data:
text="window.__APP_STATE__"
Trigger event:
text="document.querySelector('#btn').dispatchEvent(new Event('click'))"
return statements - just write the expression# Always start by getting available tabs
mcp__claude-in-chrome__tabs_context_mcp(createIfEmpty=true)
mcp__claude-in-chrome__navigate(tabId=TAB_ID, url="https://example.com")
# Get accessibility tree
mcp__claude-in-chrome__read_page(tabId=TAB_ID)
# Get interactive elements only
mcp__claude-in-chrome__read_page(tabId=TAB_ID, filter="interactive")
# Get specific element by ref
mcp__claude-in-chrome__read_page(tabId=TAB_ID, ref_id="ref_123")
mcp__claude-in-chrome__find(
tabId=TAB_ID,
query="login button"
)
# By coordinates
mcp__claude-in-chrome__computer(
action="left_click",
tabId=TAB_ID,
coordinate=[500, 300]
)
# By element ref (from read_page or find)
mcp__claude-in-chrome__computer(
action="left_click",
tabId=TAB_ID,
ref="ref_1"
)
mcp__claude-in-chrome__computer(
action="type",
tabId=TAB_ID,
text="[email protected]"
)
mcp__claude-in-chrome__form_input(
tabId=TAB_ID,
ref="ref_1",
value="[email protected]"
)
mcp__claude-in-chrome__computer(
action="screenshot",
tabId=TAB_ID
)
mcp__claude-in-chrome__computer(
action="wait",
tabId=TAB_ID,
duration=2 # seconds
)
Record GIFs for multi-step interactions that need visual verification.
GIFs prove interactions worked. Screenshots only show end state. </EXTREMELY-IMPORTANT>
# 1. Start recording
mcp__claude-in-chrome__gif_creator(action="start_recording", tabId=TAB_ID)
# 2. Take initial screenshot (first frame)
mcp__claude-in-chrome__computer(action="screenshot", tabId=TAB_ID)
# 3. Perform interactions
mcp__claude-in-chrome__computer(action="left_click", tabId=TAB_ID, coordinate=[500, 300])
mcp__claude-in-chrome__computer(action="wait", tabId=TAB_ID, duration=1)
mcp__claude-in-chrome__computer(action="screenshot", tabId=TAB_ID)
# ... more interactions with screenshots between ...
# 4. Take final screenshot (last frame)
mcp__claude-in-chrome__computer(action="screenshot", tabId=TAB_ID)
# 5. Stop recording
mcp__claude-in-chrome__gif_creator(action="stop_recording", tabId=TAB_ID)
# 6. Export GIF
mcp__claude-in-chrome__gif_creator(
action="export",
tabId=TAB_ID,
download=true,
filename="login_flow.gif"
)
checkout_flow.gif# 1. Get tab context
mcp__claude-in-chrome__tabs_context_mcp(createIfEmpty=true)
# 2. Create new tab
mcp__claude-in-chrome__tabs_create_mcp()
# Returns tabId
# 3. Navigate to login
mcp__claude-in-chrome__navigate(tabId=TAB_ID, url="https://app.example.com/login")
mcp__claude-in-chrome__computer(action="wait", tabId=TAB_ID, duration=2)
# 4. Clear console and network for clean test
mcp__claude-in-chrome__read_console_messages(tabId=TAB_ID, pattern=".*", clear=true)
mcp__claude-in-chrome__read_network_requests(tabId=TAB_ID, clear=true)
# 5. Get page structure
mcp__claude-in-chrome__read_page(tabId=TAB_ID, filter="interactive")
# 6. Fill login form
mcp__claude-in-chrome__find(tabId=TAB_ID, query="email input")
mcp__claude-in-chrome__form_input(tabId=TAB_ID, ref="ref_1", value="[email protected]")
mcp__claude-in-chrome__find(tabId=TAB_ID, query="password input")
mcp__claude-in-chrome__form_input(tabId=TAB_ID, ref="ref_2", value="password123")
# 7. Submit
mcp__claude-in-chrome__find(tabId=TAB_ID, query="sign in button")
mcp__claude-in-chrome__computer(action="left_click", tabId=TAB_ID, ref="ref_3")
# 8. Wait for response
mcp__claude-in-chrome__computer(action="wait", tabId=TAB_ID, duration=3)
# 9. Check for errors in console
mcp__claude-in-chrome__read_console_messages(
tabId=TAB_ID,
pattern="error|Error|failed",
onlyErrors=true
)
# 10. Verify API call succeeded
mcp__claude-in-chrome__read_network_requests(
tabId=TAB_ID,
urlPattern="/api/login"
)
# 11. Verify logged in state via JS
mcp__claude-in-chrome__javascript_tool(
action="javascript_exec",
tabId=TAB_ID,
text="localStorage.getItem('authToken') !== null"
)
# 12. Screenshot for evidence
mcp__claude-in-chrome__computer(action="screenshot", tabId=TAB_ID)
# 1. Setup
mcp__claude-in-chrome__tabs_context_mcp(createIfEmpty=true)
mcp__claude-in-chrome__navigate(tabId=TAB_ID, url="https://api-dashboard.example.com")
# 2. Clear previous network data
mcp__claude-in-chrome__read_network_requests(tabId=TAB_ID, clear=true)
# 3. Trigger the problematic action
mcp__claude-in-chrome__find(tabId=TAB_ID, query="refresh data button")
mcp__claude-in-chrome__computer(action="left_click", tabId=TAB_ID, ref="ref_1")
# 4. Wait for network activity
mcp__claude-in-chrome__computer(action="wait", tabId=TAB_ID, duration=3)
# 5. Inspect all API calls
mcp__claude-in-chrome__read_network_requests(
tabId=TAB_ID,
urlPattern="/api/"
)
# 6. Check console for related errors
mcp__claude-in-chrome__read_console_messages(
tabId=TAB_ID,
pattern="API|fetch|error"
)
# 7. Verify page state after API call
mcp__claude-in-chrome__javascript_tool(
action="javascript_exec",
tabId=TAB_ID,
text="document.querySelector('.data-table tbody tr').length"
)
# 1. Navigate to form
mcp__claude-in-chrome__navigate(tabId=TAB_ID, url="https://app.example.com/contact")
# 2. Start GIF recording
mcp__claude-in-chrome__gif_creator(action="start_recording", tabId=TAB_ID)
mcp__claude-in-chrome__computer(action="screenshot", tabId=TAB_ID)
# 3. Fill form with invalid data to test validation
mcp__claude-in-chrome__form_input(tabId=TAB_ID, ref="email-input", value="invalid-email")
mcp__claude-in-chrome__computer(action="screenshot", tabId=TAB_ID)
# 4. Submit
mcp__claude-in-chrome__computer(action="left_click", tabId=TAB_ID, ref="submit-btn")
mcp__claude-in-chrome__computer(action="wait", tabId=TAB_ID, duration=1)
mcp__claude-in-chrome__computer(action="screenshot", tabId=TAB_ID)
# 5. Check for validation errors in console
mcp__claude-in-chrome__read_console_messages(tabId=TAB_ID, pattern="validation|error")
# 6. Verify error message appears
mcp__claude-in-chrome__javascript_tool(
action="javascript_exec",
tabId=TAB_ID,
text="document.querySelector('.error-message').innerText"
)
# 7. Stop and export GIF
mcp__claude-in-chrome__gif_creator(action="stop_recording", tabId=TAB_ID)
mcp__claude-in-chrome__gif_creator(
action="export",
tabId=TAB_ID,
download=true,
filename="form_validation.gif"
)
This skill is referenced by dev-test for Chrome MCP browser automation.
Related skills:
${CLAUDE_SKILL_DIR}/../../skills/dev-test-playwright/SKILL.md and follow its instructions.${CLAUDE_SKILL_DIR}/../../skills/dev-tdd/SKILL.md and follow its instructions.development
Build the meeting-level proxy-voting × ownership panel on the WRDS SGE grid — ISS N-PX fund votes reduced to (item × block) direction cells, joined to institutional and mutual-fund ownership. Use when working with risk.voteanalysis_npx, N-PX fund-level votes, ISS→CRSP fund linking, index/passive/active voting blocks, or a proxy-voting panel that needs ownership attached.
development
Use when "CRSP CIZ", "CRSP v2", "CRSP flat file format 2.0", "crsp.dsf_v2 / msf_v2", "StkDlySecurityData", "StkMthSecurityData", "StkSecurityInfoHist", "stocknames_v2", "DlyRet / MthRet / DlyPrc / MthPrc", "SHRCD or EXCHCD equivalent in new CRSP", "SIZ to CIZ migration", "CRSP data after 2024", "CRSP delisting returns", "CRSP cumulative adjustment factors", "CRSP index INDNO / INDFAM", or any CRSP stock/index query where the legacy SIZ column names no longer exist.
development
Use when linking or deduping datasets by entity name rather than a shared key — 'fuzzy match', 'fuzzy name matching', 'entity resolution', 'record linkage', 'match company/person names', 'dedupe entity names', 'name-based join', 'bridge identifiers' (CIK ↔ permno ↔ gvkey ↔ wficn ↔ EIN ↔ personid), or any use of char n-gram TF-IDF, cosine similarity on names, `sparse_dot_topn`, or RapidFuzz at scale.
development
Use when building a publication-quality table in Python — 'regression table', 'results table', 'summary statistics table', 'etable', 'coefplot', 'great_tables', 'GT', 'gt table', 'format a table for the paper', 'export table to LaTeX/HTML', significance stars, spanners, or column formatting for a table headed into a paper, slide deck, or notebook.