Skip to content

indianic/htmlshot-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

htmlshot

Batch‑screenshot HTML files and live URLs to images with headless Chromium — including every in‑page tab, modal, and nested screen, behind logins, at retina quality.

htmlshot is a single, self‑contained macOS/Linux bash script. Point it at an HTML file, a folder of prototypes, or a URL and it renders crisp screenshots — not just the landing view, but every state a page can reach: navigation sections, tab bars, secondary side‑menus, modals, and even the tabs inside modals. For real websites it captures full, retina‑sharp, section‑complete pages and can crawl a whole site (optionally behind a form login).

It self‑installs Node.js + Puppeteer/Chromium (and pngjs) on first run into ~/.htmlshot. No npm install, no config files.

./htmlshot.sh ./prototypes ./shots -f          # every screen of every prototype
./htmlshot.sh https://example.com ./shots -f     # full, retina screenshot of a site

Table of contents


Why

Most screenshot tools capture what loads first. But a modern admin panel, design export, or app renders many screens on one page — switched by JavaScript, revealed by tabs, or opened as modals. And a single "full‑page" screenshot of a real website usually comes out blurry and half‑blank (lazy images and scroll‑reveal sections never load).

htmlshot solves both:

  • For prototypes / design exports (e.g. *.dc.html): it discovers and visits every tab, side‑menu, and modal — recursively — and saves each state as its own image.
  • For live URLs: it hydrates lazy content, captures the page in retina tiles, and stitches them into one sharp, complete image — even for very tall pages that exceed Chrome's screenshot size limit.

Features

  • 📁 Batch a single file, a whole folder (optionally recursive), or a URL.
  • 🧭 Deep state crawl — auto‑discovers tab groups and modal‑opener buttons at every level and captures each (nested side‑menus, in‑screen tabs, modals, and in‑modal tabs). Depth‑limited and capped for safety.
  • 🖱️ Works with JS‑driven prototypes that have no anchors (buttons wired with onClick), not just #‑anchor SPAs.
  • 🌐 URL screenshots at desktop width + retina (2×) by default.
  • 🧵 Tiled + stitched full‑page capture — sharp text, every section rendered, no Chrome height limit, and fixed elements (chat widgets, sticky navs) shown once instead of repeating.
  • 🕸️ Same‑domain site crawl (--follow) — screenshot every linked page, honoring the state modes per page.
  • 🔐 Form login — sign in once and capture the authenticated backend (credentials via env vars, kept out of shell history).
  • 🌙 Dark‑mode emulation, custom viewport, device scale, --selector element capture, png/jpeg/webp, quality, delays, timeouts.
  • 🧰 Zero setup — self‑bootstraps Node + Puppeteer/Chromium + pngjs on first run. Prefers your installed Google Chrome to avoid Apple‑Silicon/Rosetta issues.
  • 🎛️ Friendly CLI: progress bar, dry‑run, --overwrite, --quiet, --no-color.

Requirements

  • macOS or Linux with bash 3.2+ (works with the stock macOS bash).
  • Node.js — auto‑installed via Homebrew on macOS if missing; otherwise install from nodejs.org.
  • Internet access on first run (to download Chromium) and for URL captures.
  • Google Chrome is used if present (recommended); otherwise Puppeteer's bundled Chromium.

Install

git clone https://github.com/<you>/htmlshot.git
cd htmlshot
chmod +x htmlshot.sh
./htmlshot.sh --help

The first real run downloads Node (if needed) + Puppeteer/Chromium + pngjs into ~/.htmlshot (~150–300 MB, one time). Reinstall anytime with --clean.

Optionally symlink it onto your PATH:

ln -s "$PWD/htmlshot.sh" /usr/local/bin/htmlshot

Quick start

# One HTML file → ./shots/index.png
./htmlshot.sh ./index.html ./shots

# A folder of prototypes, full‑page — captures EVERY tab/modal of every file
./htmlshot.sh ./prototypes ./shots -f

# A single live website, full‑page, retina, section‑complete
./htmlshot.sh https://example.com ./shots -f

# Preview what would happen, write nothing
./htmlshot.sh ./src ./out --dry-run

Capture modes

State capture (local prototypes)

For local .html/.htm sources, three layered modes are on by default (most powerful wins):

Mode What it captures
--crawl (default) Recursively opens tab groups and modal‑opener buttons at every level: nested side‑menus, in‑screen tabs, modals, and in‑modal tabs.
--auto-tabs One level of auto‑detected nav/tab groups (a container with ≥ --tab-min sibling buttons/links).
--states In‑page states via # anchors, or an explicit --state-selector.

Turn any off with =no (or --no-…):

./htmlshot.sh ./admin.html ./out --crawl=no          # fall back to shallow tabs
./htmlshot.sh ./page.html  ./out --crawl=no --auto-tabs=no --states=no   # base only

How the crawl stays safe & sane:

  • It clicks tab groups (≥ --tab-min sibling buttons/links) and opener buttons whose text looks like it opens something (New, Add, Create, Edit, View, Open, Details, Manage, …).
  • It never clicks Delete, Remove, Save, Submit, Cancel, Close, or logout controls.
  • Each state is reached by replaying a fresh click‑path from a reload, so states never stack and nothing persists.
  • confirm() / alert() dialogs are auto‑dismissed so a stray one can't hang a click.
  • Depth is bounded by --crawl-depth (default 3) and total shots per file by --max-shots (default 120).

URL capture & stitching

When the source is an http(s) URL, htmlshot uses better defaults for real websites:

  • 1440px wide @2× retina (vs 1280 @1× for local files) → crisp text.
  • --wait-until load (live sites rarely reach networkidle because of analytics/ads/websockets).
  • Resilient navigation — a navigation timeout is treated as non‑fatal (the DOM is usually painted); genuine errors (DNS, refused) still fail.
  • Full hydration before capture — promotes lazy images to eager, resolves data-src/data-bg, scrolls to trigger reveals, makes animations instant, and waits for images to decode.
  • Tiled + stitched full‑page (--stitch, auto‑on for -f URLs) — scrolls a viewport at a time, screenshots each strip (so scroll‑driven/pinned sections paint), and assembles them with pngjs. This beats Chrome's ~32k‑px screenshot height limit and keeps position: fixed widgets (chat, sticky nav) from repeating down the image.

Safety: for live URLs, state‑clicking is off unless you explicitly pass --crawl, --auto-tabs, --states, or --follow. A URL just gets a clean full capture by default — clicking random buttons on a real site could submit forms or mutate data.

Disable stitching with --no-stitch (falls back to Chrome's native full‑page). Override the retina defaults anytime with -w/-s.

Site crawl (--follow)

Screenshot a seed URL, then walk its links breadth‑first — same‑domain by default:

./htmlshot.sh https://example.com ./out --follow --follow-depth 2 --max-pages 40 -f
  • --follow-depth N — link hops from the seed (default 2).
  • --max-pages N — cap on pages (default 50).
  • --same-domain=no (alias --allow-external) — allow off‑domain links.
  • Each followed page honors the state modes: add --crawl to deep‑capture every page's tabs/modals too.

Form login

Sign in via a form once; the session cookie is reused for every subsequent capture (single URL, follow, or crawl):

export HTMLSHOT_PASSWORD='s3cret'          # keep the password out of shell history
./htmlshot.sh https://app.example.com/dashboard ./out \
  --login-url https://app.example.com/login --username admin@example.com \
  --follow --crawl -f
  • Credentials come from --username/--password or HTMLSHOT_USERNAME/HTMLSHOT_PASSWORD (env is preferred).
  • Field selectors auto‑detect (input[type=password], and email/user/text inputs for the username), or pin them with --user-selector / --pass-selector / --submit-selector. Submit falls back to pressing Enter.
  • --login-url defaults to the source URL. --login-wait (default 2500 ms) controls the post‑submit wait.

Login is best‑effort: it submits and waits, it doesn't verify success. Multi‑step logins, CSRF flows, or CAPTCHAs may need tuning.


Options

USAGE
  htmlshot <source> <target> [options]
  htmlshot -o <target> <source> [options]

  <source>  A single .html/.htm file, a folder of them, or an http(s) URL.
  <target>  Output folder for the images (created if missing).

Rendering

Option Description Default
-w, --width PX Viewport width 1280 (URLs: 1440)
-H, --height PX Viewport height 800
-s, --scale N Device scale / retina 1 (URLs: 2)
-f, --full-page Capture full scroll height off
-F, --format FMT png | jpeg | webp png
-q, --quality N Quality 0–100 (jpeg/webp)
-r, --recursive Recurse into subfolders off
-d, --delay MS Wait after load before capture 0
--wait-until EVENT load | domcontentloaded | networkidle0 | networkidle2 networkidle2 (URLs: load)
--selector CSS Capture only one element
--timeout MS Navigation timeout 30000
--dark Emulate prefers-color-scheme: dark off

State capture (all on by default for local files; disable with =no)

Option Description Default
--crawl[=no] Deep crawl: nested menus, sub‑tabs, modals, in‑modal tabs on (local)
--auto-tabs[=no] Auto‑detect one level of nav/tab groups on (local)
--states[=no] In‑page states via anchors / --state-selector on (local)
--state-selector CSS Trigger elements to click a[href^="#"]
--tab-min N Min sibling buttons to count as a tab group 3
--crawl-depth N Levels deep to follow 3
--max-shots N Per‑file cap on captured states 120
--no-base Skip the initial (unclicked) shot off

URL sources

Option Description Default
--stitch[=no] Tiled retina full‑page capture auto‑on for -f URLs
--follow[=no] Follow links & shot each page off
--follow-depth N Link hops from the seed 2
--max-pages N Max pages to screenshot 50
--same-domain=no Allow off‑domain links (alias --allow-external) same‑domain

Login

Option Description Default
--login Enable form login (with env creds) off
--login-url URL Page holding the form source URL
--username V Username/email (or HTMLSHOT_USERNAME)
--password V Password (or HTMLSHOT_PASSWORD, preferred)
--user-selector CSS Username field auto
--pass-selector CSS Password field input[type=password]
--submit-selector CSS Submit control auto / Enter
--login-wait MS Wait after submit 2500

Misc

Option Description
-o, --output DIR Target folder (alt to positional arg)
--overwrite Overwrite existing images (else skip)
-n, --dry-run List what would be captured, do nothing
--clean Reinstall dependencies
--quiet Suppress decorative output
--no-color Disable ANSI colors
-h, --help / -v, --version Help / version

Output naming

  • File / folder: basename.ext for the base view; each captured state adds a readable, filename‑safe suffix:
    admin.png                          base view
    admin__dashboard.png               a top‑level tab
    admin__settings__storage.png       a nested side‑menu screen
    admin__plans__new-plan__week.png   a tab inside a modal
    
  • URL: host + path slug, e.g. www-example-com.png, www-example-com-pricing.png. Followed pages are named from their URLs and de‑duplicated.

Existing images are skipped unless --overwrite is passed. Recursive folder mode is flat output (state names are suffixed; same‑basename files may overwrite).


Examples

# One file → ./shots/index.png
./htmlshot.sh ./index.html ./shots

# A whole prototype folder, full‑page, retina
./htmlshot.sh ./felt-prototype ./shots -f -s 2

# Recursive, JPEG at 85% quality, dark mode
./htmlshot.sh ./src -o ./out -r -F jpeg -q 85 --dark

# Mobile‑sized capture of a single component
./htmlshot.sh ./ui.html ./out -w 390 -H 844 --selector ".phone"

# Design export (JS‑driven, no anchors): auto‑shot every tab/screen
./htmlshot.sh "./Admin Panel.dc.html" ./out --auto-tabs -f -d 400

# Deep: nested side‑menus, in‑screen tabs, modals AND in‑modal tabs (default)
./htmlshot.sh "./Admin Panel.dc.html" ./out -f

# Just the base view, no clicking around
./htmlshot.sh ./page.html ./out --crawl=no --auto-tabs=no --states=no

# Single live URL (retina, stitched, section‑complete)
./htmlshot.sh https://example.com ./out -f

# Crawl a site, same‑domain only, 2 hops, up to 40 pages
./htmlshot.sh https://example.com ./out --follow --follow-depth 2 --max-pages 40 -f

# Log in, then deep‑crawl the whole authenticated backend
export HTMLSHOT_PASSWORD='s3cret'
./htmlshot.sh https://app.example.com/dashboard ./out \
  --login-url https://app.example.com/login --username admin@ex.com \
  --follow --crawl -f

How it works

htmlshot is two layers in one file:

  • Bash wrapper — argument parsing, dependency bootstrapping (Node via Homebrew, Puppeteer + pngjs), source discovery (file / folder / URL), job‑file creation, and the progress UI. Compatible with macOS bash 3.2+.
  • Embedded Node/Puppeteer runner — written at runtime to ~/.htmlshot/runner.js. It launches one Chromium instance, then for each job:
    • Local/URL page: loads it, hydrates lazy content, and runs captureStates() — base shot plus the deep crawl or shallow states.
    • Deep crawl: an injected in‑page helper (window.__HS) discovers tab groups and opener buttons; the runner walks them by replaying fresh click‑paths, capturing each state.
    • Full‑page URL: stitchCapture() scrolls in viewport strips, screenshots each, and assembles them with pngjs.
    • Follow mode: breadth‑first same‑domain link walk, capturing each page.
    • Login: a one‑time form submit so the session cookie is shared.

Configuration passes via HS_* environment variables. The runner emits tab‑separated protocol lines the wrapper parses for progress: @@TOTAL, @@OK, @@FAIL, @@WARN, @@NOTE, @@FATAL, @@DONE.

Runtime layout

~/.htmlshot/
├── node_modules/    puppeteer, pngjs, …
├── runner.js        generated on each run
└── install.log      first‑run install output

Troubleshooting

Symptom Fix
URL fails with "Navigation timeout" Already handled — timeouts are non‑fatal and URLs default to --wait-until load. For a stubborn page: --wait-until domcontentloaded --timeout 60000.
Blank / faint sections on a real site Give scroll‑reveal/animation sections more time: add -d 1500. Ensure -f (stitching) is on.
A sidebar / panel / chat repeats down the page Handled automatically: in stitched captures, position: sticky columns are un‑stuck (rendered once in flow) and position: fixed overlays are shown on the first strip only.
Low‑resolution text URLs already default to @2×. Bump further with -s 3, or widen with -w 1920.
A modal/tab wasn't captured Its opener text may not match the recognized verbs — pin it with --state-selector "CSS", or lower --tab-min.
Too many near‑duplicate shots Raise --tab-min 4 or lower --crawl-depth 2.
Hit the shot cap Raise --max-shots.
Very tall page uses lots of memory The scale factor auto‑drops past a pixel ceiling; you can also pass -s 1.
Login didn't work Pin --user-selector / --pass-selector / --submit-selector, increase --login-wait, and confirm the flow isn't multi‑step / CAPTCHA‑gated.
Install problems Re‑run with --clean; check ~/.htmlshot/install.log.

Performance

  • Local prototypes use Chrome's fast native full‑page path — a folder of files captures in seconds.
  • URL stitching is deliberate: it hydrates, scrolls tile‑by‑tile, and assembles a high‑res image. A long page can take ~60–120 s. That's the cost of a complete, retina‑sharp capture. Use --no-stitch for a quick (lower‑fidelity) native shot.
  • Concurrency is one page at a time per run for deterministic, low‑memory behavior.

Contributing

Issues and PRs welcome. Because the tool is a single self‑contained script:

  • Keep it bash 3.2‑compatible (no mapfile, no associative arrays, avoid Bash‑4‑only constructs).
  • The Node runner is embedded as a quoted heredoc in write_runner() — edit it there.
  • There's no build step. Validate with:
    bash -n htmlshot.sh                 # shell syntax
    ./htmlshot.sh <file> <out> --dry-run
    node -c ~/.htmlshot/runner.js       # runner JS syntax (after a run)

About IndiaNIC

htmlshot is built and maintained by IndiaNIC Infotech Limited. Founded in 1998, IndiaNIC is a global software and product engineering company delivering web, mobile, UI/UX, and AI solutions to businesses worldwide. Tools like this one come out of our day‑to‑day design and QA workflow — screenshotting client prototypes, admin panels, and live sites at scale — and we open‑source them so the wider community can benefit. Learn more at www.indianic.com.


License

MIT © IndiaNIC Infotech Limited. See LICENSE.

About

Batch-screenshot HTML files (or live URLs) to images using headless Chromium. A self-contained macOS bash tool.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages