| name | osascript-chrome |
| description | Automate Google Chrome with osascript/JXA. Use when listing/focusing/closing/navigating/reloading tabs, muting tabs, managing windows, executing JavaScript, scraping page content, extracting links, filling forms, reading localStorage/cookies/audio state, or reading Chrome history/bookmarks/downloads/extensions/profiles. |
osascript-chrome
Full Chrome automation via AppleScript/JXA + Chrome profile data files. All scripts are Python.
Critical Gotchas (read before writing any Chrome script)
- Minimized windows invisible: Chrome returns 0 windows when minimized. Always verify via System Events first.
- Tab variable reference loss: NEVER store a tab in a variable then execute JS on it. Execute JS inline at the point of match — see
scripts/execute-js-in-tab.py for the outer: labeled-break pattern.
without activating is invalid on Chrome's tell block — use System Events focus snap instead.
- CDP for headless / Electron: osascript only works with real GUI Chrome. Use CDP (see
reference/08-cdp.md) for headless or Slack/VSCode/Notion.
- activeTabIndex is 1-based: Chrome AS dictionary uses 1-based tab indices for the active tab property. Array access (
tabs[ti]) is still 0-based.
- SQLite scripts copy-first: chrome-history, chrome-downloads all copy the DB to a tempfile before querying — Chrome locks the file while running.
Tab Control
List all open tabs across all windows (handles minimized via System Events):
uv run scripts/list-tabs.py
Bring a tab matching a URL pattern to front and activate Chrome:
uv run scripts/focus-tab.py github.com
uv run scripts/focus-tab.py --window 0 --tab 2
Close tab(s) by URL pattern — --all to close every match:
uv run scripts/close-tab.py github.com
uv run scripts/close-tab.py github.com --all
Navigate an existing tab to a new URL (no new tab opened):
uv run scripts/navigate-tab.py github.com "https://github.com/new"
Reload a tab — --hard bypasses cache:
uv run scripts/reload-tab.py localhost
uv run scripts/reload-tab.py localhost --hard --all
Mute/unmute tabs — --on, --off, --all, --status:
uv run scripts/mute-tab.py youtube.com --on
uv run scripts/mute-tab.py --all --off
uv run scripts/mute-tab.py --status
List tabs currently playing audio (audible property) — --all for full state:
uv run scripts/tab-audio.py
uv run scripts/tab-audio.py --all
Window Management
List all Chrome windows with mode, tab count, active tab — --tabs for full detail:
uv run scripts/list-windows.py
uv run scripts/list-windows.py --tabs
Open a URL in Chrome — --background keeps current focus:
uv run scripts/open-tab.py "https://example.com"
uv run scripts/open-tab.py "https://example.com" --background
Open a new Chrome window — --incognito for private mode:
uv run scripts/new-window.py
uv run scripts/new-window.py --incognito "https://example.com"
Close a Chrome window — --list to preview, --empty to purge newtab-only windows:
uv run scripts/close-window.py 0
uv run scripts/close-window.py --empty
uv run scripts/close-window.py --list
JavaScript Execution & Content Extraction
Read DOM content from a tab (defaults to document.body.innerText):
uv run scripts/read-tab.py github.com
uv run scripts/read-tab.py gmail.com "document.title"
Execute arbitrary JS expression in a tab and return the result:
uv run scripts/execute-js-in-tab.py "localhost:3000" "JSON.stringify(window.__STATE__)"
Full structured extraction — title, OG meta, headings, links, images, body text:
uv run scripts/scrape-page.py github.com
uv run scripts/scrape-page.py docs.google.com --text-only
uv run scripts/scrape-page.py example.com --links-only
All <a> links with href + anchor text — --filter to match specific hrefs:
uv run scripts/scrape-links.py github.com
uv run scripts/scrape-links.py github.com --filter "/pull/"
Read localStorage and sessionStorage — --key for a single value:
uv run scripts/tab-storage.py localhost:3000
uv run scripts/tab-storage.py app.example.com --key "auth_token"
Read document.cookie as a parsed list (HttpOnly cookies are not accessible by design):
uv run scripts/tab-cookies.py github.com
uv run scripts/tab-cookies.py app.example.com --name "session"
Fill an input by CSS selector — fires React/Vue events — --submit to submit the form:
uv run scripts/form-fill.py "google.com" "input[name='q']" "search term"
uv run scripts/form-fill.py "example.com/login" "#password" "secret" --submit
Chrome Profile Data (file-based, no GUI needed)
Read Chrome browsing history — supports --profile, search term:
uv run scripts/chrome-history.py
uv run scripts/chrome-history.py 50 github
Read Chrome download history — state, size, mime type, timestamps:
uv run scripts/chrome-downloads.py
uv run scripts/chrome-downloads.py 50 pdf --complete
Read all bookmarks with folder paths — --search, --folder, --list-profiles:
uv run scripts/chrome-bookmarks.py
uv run scripts/chrome-bookmarks.py --search "github" --profile "Default"
List installed Chrome extensions with enabled state — --permissions for full detail:
uv run scripts/chrome-extensions.py
uv run scripts/chrome-extensions.py --search "blocker" --permissions
List all Chrome profiles with name, email, history size:
uv run scripts/chrome-profile.py
uv run scripts/chrome-profile.py --detail
Tab Properties Available via JXA
| Property | Writable | Notes |
|---|
url | yes | Setting this navigates the tab |
title | no | Page title |
loading | no | Is the tab still loading |
muted | yes | Mute state |
audible | no | Is tab playing audio right now |
Reference
When to use osascript vs CDP
Use osascript when Chrome is already open with real GUI windows, you need the existing session/cookies, or simple tab control/JS execution.
Use CDP (reference/08-cdp.md) when you need Electron apps (Slack, VSCode, Notion), headless operation, or network interception.