Power-tool-first Chrome extension for browser automation.
Wire keyboard shortcuts to page elements, execute JavaScript on any domain, extract structured data, chain multi-step flows, inject custom CSS, intercept network requests — all from a compact popup UI. Pre-built templates lower the entry barrier for non-technical users, but the engine never constrains power users.
Status: v0.2.8 · Chrome Extension (Manifest V3) · BSD 3-Clause License
- Install Browser Automata directly from the Chrome Web Store.
- Click Add to Chrome.
- Project Overview
- Project Aim
- Features
- Installation
- End-User Usage Suggestions
- Advanced-User Usage Suggestions
- Architecture Overview
- Tech Stack
- Project Structure
- Development
- Testing
- Contributing
- Support
- Privacy Policy
- License
Browser Automata is a Chrome extension built on Manifest V3 that turns your browser into a programmable automation platform. Unlike simple macro recorders or bookmark-based scripts, Browser Automata provides a full-featured automation engine with:
- Script execution scoped to domains or URL patterns with multiple trigger modes
- Unlimited keyboard shortcuts that bypass Chrome's built-in 4-command limit, including Vim-style chord sequences
- Visual flow builder for chaining multi-step automations across tabs
- Structured data extraction with multi-format output (JSON, CSV, Markdown, XML, HTML)
- CSS injection, network request interception, and form auto-fill per domain
- Profiles for switching between automation contexts (work, personal, project-specific)
Everything runs locally in the browser — no external servers, no accounts, no data leaves your machine.
Browser Automata follows an engine-first, convenience-second philosophy:
- Primary audience — Developers, QA engineers, scripters, and sysadmins who need precise control over browser behavior.
- Secondary audience — Power users who install and configure pre-built templates without writing code.
- Design principle — Every feature exposes its full capability surface. Templates are sugar, not guardrails.
The goal is to be the single extension that replaces a collection of single-purpose tools: custom user scripts, shortcut managers, CSS injectors, data scrapers, and automation recorders.
| Feature | Description |
|---|---|
| Script Execution | Run JavaScript on any page with triggers: page load, manual, shortcut, event, or schedule. Execute in isolated or main world. |
| Keyboard Shortcuts | Bind any key combination to actions — click, focus, navigate, run a script, run a flow, or trigger an extraction rule. Supports chord combos (e.g., g then i for GitHub Issues). Live conflict warnings for browser and extension collisions. |
| CSS Injection | Inject custom stylesheets scoped to domains or URL patterns. Inject at document_start or document_idle. |
| URL Pattern Scoping | Scope any automation to exact URLs, glob patterns, regex, or globally. Specificity rules determine priority. |
| Quick Run Bar | Floating action bar on web pages for single-click execution of scripts, flows, extraction rules, or form fills. Draggable, URL-scoped, and toggleable with Alt+Q. |
| Shortcut Conflict Detection | Detects collisions between shortcuts and warns before saving. |
| Shortcut Debounce | 100ms debounce per shortcut prevents accidental rapid-fire triggers. |
| Feature | Description |
|---|---|
| Flow Builder | Chain actions into multi-step flows with conditions, loops, waits, cross-tab navigation, and {{varName}} interpolation for passing extracted data between nodes. Includes "Run Extraction Rule" node to reuse saved extraction rules. |
| Action Recorder | Record click, type, and scroll sequences on a page, then replay or convert to scripts. |
| Element Picker | Visually select page elements to auto-generate CSS selectors. |
| Data Extraction | Define extraction rules with field selectors, fallback selector chains (first non-empty match wins), post-extraction transforms (trim, lowercase, uppercase, strip HTML, normalize URL/whitespace, replace, regex replace), and output as JSON, CSV, Markdown, XML, HTML, or plain text. Live test button validates extraction against the current page. |
| Script Scheduling | Schedule scripts to run at intervals or cron expressions via chrome.alarms. |
| Network Rules | Block, redirect, or modify request headers using Chrome's declarativeNetRequest API. |
| Element Watcher | Monitor DOM mutations and trigger actions when elements appear, change, or disappear. |
| Cross-Tab Flows | Automate workflows that span multiple browser tabs — open, close, and navigate between tabs. |
| Feature | Description |
|---|---|
| Inline Code Editor | CodeMirror 6 editor with syntax highlighting, autocomplete, and linting — right in the popup. |
| TypeScript Support | Write scripts in TypeScript; they are transpiled on save. |
| Shared Libraries | Create reusable code modules and import them with @use() syntax. |
| Script Variables | Use {{variable}} templating with a secrets vault for sensitive values. |
| Version History | Track script changes with diff comparison (up to 50 versions per script). |
| Debug Console | Captures console.log output from script execution for inspection. |
| Feature | Description |
|---|---|
| Selector Fallback | Multi-source selector chains per extraction field — add multiple CSS selectors tried in priority order; first non-empty result wins. Reorderable in the UI. |
| Smart Wait | Detects SPA route changes, network idle, and framework renders (React, Vue) before executing. |
| Retry Policies | Configurable exponential backoff retry on script failures. |
| Health Dashboard | Monitors script failure rates, selector staleness, and storage usage. |
| Concurrent Flow Guard | Prevents duplicate execution of the same flow — returns error if already running. |
| Extraction Payload Validation | Validates field data before sending to content scripts, filtering invalid fields. |
| Feature | Description |
|---|---|
| Profiles | Group automations into switchable contexts (e.g., work vs. personal). |
| Duplicate | One-click duplicate for any entity (flows, scripts, shortcuts, CSS rules, network rules, extraction rules) — opens a copy in the editor. |
| Import / Export | Full JSON backup and restore with merge strategies. |
| Global Kill Switch | Instantly disable all automations from the popup header. |
| Activity Log | Timestamped, filterable log with virtual scrolling for 5,000+ entries. |
| Error Surfacing | Badge icon and desktop notifications on script/flow errors. Per-entity "Notify on Error" toggle with a global notifications switch in Settings. |
| Pre-Built Templates | One-click install: Cookie Dismisser, Video Navigator, Dark Mode, Element Hider, Auto-Clicker, and more. Template status tracking with update detection, uninstall, and reset to defaults. |
- Node.js 18+ (LTS recommended)
- npm 9+ (ships with Node.js)
- Google Chrome (or any Chromium-based browser)
# Clone the repository
git clone https://github.com/user/browser-automata.git
cd browser-automata
# Install dependencies
npm install
# Build the extension
npm run build- Open
chrome://extensions/in Chrome. - Enable Developer mode (toggle in the top-right corner).
- Click Load unpacked.
- Select the
dist/folder from the project root. - The Browser Automata icon appears in your toolbar — click it to open the popup.
npm run devThis starts a Vite dev server with hot module replacement. Load the extension from the dist/ folder as above — changes in source files will reflect immediately without manual reload.
Press Alt+Shift+B (the default Chrome command) to open the Browser Automata popup from any tab.
These examples require no coding knowledge. Use the popup UI and pre-built templates to get started.
- Open the Browser Automata popup.
- Navigate to the Templates tab.
- Browse available templates (e.g., Cookie Banner Dismisser, Dark Mode Injector).
- Click Install — the template's scripts, shortcuts, or CSS rules are added automatically.
- Go to the Shortcuts tab.
- Click Add Shortcut.
- Press the key combination you want (e.g.,
Ctrl+Shift+H). - Choose an action: click an element, navigate to a URL, or toggle visibility.
- Optionally scope it to specific domains.
- Save — the shortcut is active immediately.
- Go to the CSS Rules tab.
- Add a rule like
body { font-size: 18px !important; }. - Scope it to a domain (e.g.,
*://news.ycombinator.com/*). - The CSS is injected on every matching page load.
- Install the Element Hider template.
- Use the Element Picker to visually select an element you want to hide.
- The generated selector is automatically saved — the element disappears on future visits.
- Open the Browser Automata popup.
- Navigate to the Quick Run tab.
- Click Add Action and choose a target: a script, flow, extraction rule, or form fill profile.
- Optionally scope it to specific URLs (e.g.,
*://github.com/*). - The action appears as a button on matching pages in the floating Quick Run bar.
- Click it once to execute — or toggle the bar with
Alt+Q.
- Create profiles in the Profiles tab (e.g., "Work", "Personal").
- Assign scripts and shortcuts to each profile.
- Switch profiles from the dropdown — only the active profile's automations run.
These examples leverage scripting, flows, and the extension's programmable API surface.
// Auto-expand all collapsed sections on a documentation site
(function () {
document.querySelectorAll("details:not([open])").forEach((el) => {
el.setAttribute("open", "");
});
})();Set the trigger to page_load, scope to your documentation domain, and run in the MAIN world to access the page's JavaScript context.
Build a flow that:
- Navigate to a login page.
- Wait for the email field to appear (smart wait).
- Type credentials from script variables.
- Click the submit button.
- Wait for redirect.
- Extract data from the dashboard.
- Download as CSV.
Flows support conditions (if element exists), loops, and cross-tab steps.
Use {{varName}} to interpolate extracted values into subsequent nodes:
- Extract Value — selector:
h1.title, output variable:pageTitle - Navigate — URL:
https://google.com/search?q={{pageTitle}}
Variable interpolation works in Navigate URLs, Open Tab URLs, and Type Text fields. Each extract node also supports a Test Extract button to validate results live before running the full flow.
Instead of configuring extraction inline, add a Run Extraction Rule node that references a saved extraction rule by name. All the rule's fields, transforms, fallback selectors, output format, and output actions are applied automatically.
Create a shared library called dom-helpers:
// @library dom-helpers
function waitForElement(selector, timeout = 5000) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) return resolve(el);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timeout waiting for ${selector}`));
}, timeout);
});
}Then reference it in any script with @use(dom-helpers).
Define variables in the Variables tab:
| Variable | Value | Secret |
|---|---|---|
API_KEY |
sk-abc123... |
Yes |
BASE_URL |
https://api.example.com |
No |
Use them in scripts:
fetch("{{BASE_URL}}/data", {
headers: { Authorization: "Bearer {{API_KEY}}" },
})
.then((r) => r.json())
.then(console.log);Secret values are masked in the UI and logs.
Define an extraction rule for a product listing page:
| Field | Selector(s) | Attribute | Multiple | Transforms |
|---|---|---|---|---|
title |
.product-card h2 |
textContent |
Yes | Trim, Uppercase |
price |
.product-card .price, .card .cost |
textContent |
Yes | Strip HTML, Trim |
image |
.product-card img |
src |
Yes | Normalize URL |
link |
.product-card a |
href |
Yes | Normalize URL |
Each field supports multiple selector sources — if the primary selector finds nothing, fallbacks are tried in order (first non-empty match wins). Transforms are applied after extraction: trim, lowercase, uppercase, strip HTML, normalize URL, collapse whitespace, find-and-replace, and regex replace.
Output as JSON, CSV, Markdown, HTML, XML, or plain text. Trigger manually, via shortcut, or on page load. Use the Test Extract button to validate results against the current page before saving.
Use the Network Rules tab to:
- Block tracking scripts:
*://analytics.example.com/* - Redirect API calls to a local mock server:
*://api.prod.com/*→http://localhost:3000/* - Modify headers: Add
X-Debug: trueto all requests on a staging domain.
Create multi-key sequences:
g→h— Navigate to homepageg→i— Navigate to issues pageg→p— Navigate to pull requests
Chord timeout is configurable (default: 500ms).
Schedule a script to run every 5 minutes:
- Set trigger to
schedule. - Configure the interval or cron expression.
- The script runs in the background via
chrome.alarms, even when the popup is closed.
Write scripts in TypeScript — they are compiled to JavaScript on save:
interface Product {
name: string;
price: number;
}
const products: Product[] = Array.from(
document.querySelectorAll<HTMLElement>(".product")
).map((el) => ({
name: el.querySelector("h2")?.textContent ?? "",
price: parseFloat(el.querySelector(".price")?.textContent ?? "0"),
}));
console.table(products);Browser Automata follows Chrome's extension architecture with three isolated contexts:
┌─────────────────────────────────────────────────────┐
│ Chrome Browser │
│ │
│ ┌──────────────┐ chrome.runtime ┌───────────┐ │
│ │ Popup UI │ ◄────────────────► │ Service │ │
│ │ (React + │ .sendMessage() │ Worker │ │
│ │ Tailwind) │ │ │ │
│ └──────────────┘ │ Handlers │ │
│ │ Services │ │
│ ┌──────────────┐ chrome.runtime │ Storage │ │
│ │ Options Page│ ◄────────────────► │ │ │
│ │ (Full-page) │ .sendMessage() └─────┬─────┘ │
│ └──────────────┘ │ │
│ chrome. │ │
│ scripting│ │
│ ┌──────────────┐ chrome.runtime │ │
│ │ Content │ ◄───────────────────────┘ │
│ │ Script │ │
│ │ (Per-tab) │ Shortcut listener │
│ │ │ Element picker │
│ │ │ Action recorder │
│ │ │ Data extractor │
│ └──────────────┘ │
└─────────────────────────────────────────────────────┘
Service Worker — Always-running background context. Handles message routing (with global error handler), script execution, storage CRUD, scheduling, and all business logic. Concurrent flow execution is guarded to prevent race conditions.
Content Script — Injected into every tab. Listens for keyboard shortcuts (with 100ms debounce), provides element picking, action recording, data extraction, selector testing, and the Quick Run floating action bar. Wrapped in error boundary for crash resilience.
Popup UI — React application (400×580px). Provides the 14-tab interface for managing all automation entities. Uses shared ViewRouter for lazy-loaded views (deduplicated between popup and options). State cached via session storage for fast popup reopening.
Options Page — Full-page editor with sidebar layout, sharing the same ViewRouter and views as the popup.
All entities share a common EntityMeta base (timestamps, IDs) and are stored in chrome.storage.local:
- Script — JavaScript code with triggers, scoping, world, and execution timing
- Shortcut — Key combo → action binding with per-domain scope
- Flow — Directed graph of action/condition/wait/loop nodes
- CSSRule — Stylesheet injection scoped to URL patterns
- ExtractionRule — Field definitions with selectors and output format
- NetworkRule — Request blocking, redirection, and header modification
- Profile — Named group of automation entities
- ScriptVariable — Templating variables with optional secret flag
- SharedLibrary — Reusable code modules
- FormFillProfile — Auto-fill field mappings
- NotificationRule — Element monitoring with alert triggers
- QuickRunAction — Single-click action buttons scoped to URL patterns, targeting scripts, flows, extraction rules, or form fills
| Layer | Technology | Why |
|---|---|---|
| Language | TypeScript 5.7 (strict) | Full type safety across all extension contexts |
| UI Framework | React 18 | Fast mount/unmount lifecycle fits popup constraints |
| Styling | Tailwind CSS 4 | Utility-first, dark-theme-optimized, minimal CSS output |
| State | Zustand 5 | Provider-less store — ideal for popup lifecycle |
| Code Editor | CodeMirror 6 | ~100KB tree-shaken; modular plugins for autocomplete, lint, and language modes |
| Virtual Scroll | TanStack Virtual 3 | Handles 5,000+ log entries without DOM bloat |
| Icons | Lucide React | Lightweight, tree-shakeable icon set |
| Build | Vite 6 + CRXJS | MV3-native builds with HMR in development |
| Testing | Vitest 2 + Testing Library | Vite-native test runner with jsdom and Chrome API mocks |
| Lint | ESLint + typescript-eslint + Prettier | Consistent code style enforcement |
browser-automata/
├── src/
│ ├── background/ # Service Worker context
│ │ ├── service-worker.ts # Entry point; synchronous listener registration
│ │ ├── message-router.ts # Dispatch table for all message types
│ │ ├── handlers/ # One handler per entity domain (15 handlers)
│ │ └── services/ # Business logic & execution engines (29 services)
│ ├── content/ # Content Script (injected per-tab)
│ │ ├── index.ts # Entry: storage sync, message listener
│ │ ├── shortcut-listener.ts # Keyboard event binding
│ │ ├── recorder.ts # Action recording engine
│ │ ├── element-picker.ts # Visual element selector
│ │ ├── extractor.ts # On-page data extraction
│ │ ├── selector-tester.ts # CSS selector validation
│ │ ├── selector-widget.ts # Selector alternatives overlay
│ │ ├── action-highlight.ts # Visual feedback for recorded actions
│ │ ├── deep-query.ts # Shadow DOM piercing queries
│ │ ├── quick-run-bar.ts # Floating quick-action bar overlay
│ │ ├── toast.ts # In-page toast notifications
│ │ └── content.css # Content script styles
│ ├── popup/ # Popup UI (React + Tailwind)
│ │ ├── App.tsx # Root layout with lazy-loaded views
│ │ ├── main.tsx # React entry point
│ │ ├── popup.css # Popup-specific styles
│ │ ├── stores/ # Zustand state management (7 stores)
│ │ ├── hooks/ # React hooks (init, theme, messaging)
│ │ ├── components/
│ │ │ ├── Header.tsx # Popup header with global controls
│ │ │ ├── TabBar.tsx # Bottom navigation with keyboard nav + ARIA
│ │ │ ├── QuickRunBar.tsx # Quick-action buttons in popup
│ │ │ ├── ViewRouter.tsx # Shared lazy-loaded view routing (popup + options)
│ │ │ ├── views/ # 15 tab panels (Quick Run, Scripts, Shortcuts, Flows, ...)
│ │ │ ├── editor/ # CodeMirror wrapper, selector inputs
│ │ │ │ └── flow-nodes/ # Split FlowNodeEditor sub-components (7 files)
│ │ │ └── ui/ # Shared primitives (Button, Input, Toggle, ...)
│ │ └── utils/ # Export/import helpers
│ ├── results/ # Extraction result viewer page (session-storage driven)
│ ├── options/ # Full-page Options UI (App.tsx, main.tsx, options.css)
│ ├── lib/codemirror/ # CodeMirror editor configuration
│ ├── shared/ # Cross-context shared code
│ │ ├── types/ # Entity, message, and settings types
│ │ ├── messaging/ # Typed message sending
│ │ ├── storage/ # Chrome storage CRUD + schema migrations
│ │ ├── url-pattern/ # URL pattern matching & specificity
│ │ ├── constants.ts # Storage keys, defaults, schema version
│ │ ├── utils.ts # Cross-context utility functions
│ │ ├── template-hash.ts # SHA-256 content hashing for templates
│ │ ├── deep-query-snippet.ts # Shadow DOM query snippet
│ │ └── theme.css # Shared theme variables
│ ├── data/templates/ # Bundled pre-built templates
│ └── assets/
│ ├── icons/ # Extension icons (16–128px)
│ └── icon/ # Themed brand icons (Dark/White, PNG/SVG)
├── tests/ # Vitest test suite
│ ├── setup.ts # Chrome API mocks
│ ├── helpers.ts # Shared test utilities
│ ├── smoke.test.ts # Smoke tests
│ ├── background/ # Handler tests
│ ├── messaging/ # Message type tests
│ ├── storage/ # Storage & migration tests
│ └── url-pattern/ # Pattern matching tests
├── Docs/ # Product specifications & implementation plan
├── manifest.json # Chrome MV3 manifest
├── vite.config.ts # CRXJS + React + Tailwind build config
├── vitest.config.ts # Test configuration
├── tsconfig.json # TypeScript strict configuration
├── eslint.config.ts # Linting rules
├── package.json # Scripts and dependencies
└── PRIVACY_POLICY.md # Chrome Web Store privacy policy
| Command | Description |
|---|---|
npm run dev |
Start dev server with HMR (port 5173) |
npm run build |
Production build → dist/ |
npm run preview |
Serve the production build locally |
npm run type-check |
TypeScript strict mode validation |
npm run lint |
ESLint check |
npm run lint:fix |
ESLint auto-fix |
npm run format |
Prettier auto-format |
npm run format:check |
Prettier dry-run check |
The extension requests the following permissions:
| Permission | Purpose |
|---|---|
storage |
Persist all automation entities and settings |
activeTab |
Access the current tab for script execution |
scripting |
Inject scripts and CSS into web pages |
tabs |
Cross-tab flow execution and tab management |
alarms |
Script scheduling |
clipboardWrite / clipboardRead |
Clipboard integration |
notifications |
Desktop notifications for errors and monitors |
declarativeNetRequest |
Network request interception |
declarativeNetRequestFeedback |
Debug feedback for network rules |
downloads (optional) |
File output for extracted data |
<all_urls> (host) |
Content script injection on all pages |
# Run tests once
npm run test
# Watch mode
npm run test:watch
# With coverage report
npm run test:coverageTests use Vitest with a jsdom environment and Chrome API mocks (defined in tests/setup.ts). Coverage is tracked via v8.
tests/storage/— Chrome storage CRUD and schema migration teststests/messaging/— Message type guard and dispatch teststests/url-pattern/— URL pattern matching and specificity rankingtests/background/— Handler-level integration tests
- Fork the repository and clone your fork.
- Install dependencies:
npm install - Create a feature branch:
git checkout -b feature/your-feature - Start the dev server:
npm run dev - Load the extension from
dist/in Chrome.
Before submitting a pull request, ensure all checks pass:
npm run type-check # No TypeScript errors
npm run lint # No linting violations
npm run format:check # Consistent formatting
npm run test # All tests pass- TypeScript strict mode — No
any, no implicit types. - Entity-per-handler pattern — Each entity domain has its own handler in
background/handlers/and service inbackground/services/. - Discriminated union messaging — All messages between contexts use typed discriminated unions (see
shared/types/messages.ts). - URL pattern scoping — All user-facing automations support URL pattern scoping. Never hardcode domain checks.
- No external network calls — The extension runs entirely locally. No analytics, no telemetry, no external APIs.
- Keep PRs focused — one feature or fix per PR.
- Include tests for new functionality.
- Update relevant types in
shared/types/when changing entity schemas. - Test in Chrome with the extension loaded before submitting.
If you find Browser Automata useful, consider supporting its development:
Support this project by becoming a sponsor. Your logo will show up here with a link to your website.
Thank you to all our backers!
Browser Automata is privacy-focused. All user data is stored locally on your device — no personal data is collected, transmitted, or shared with any third party. The only network request is an optional read-only fetch to GitHub for the template registry.
Read the full Privacy Policy.
This project is private and licensed for distribution.
Copyright (c) 2026, Muhammed Said Bilgehan. All rights reserved.