npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sessionsnap

v0.1.1

Published

Capture and reuse authenticated browser sessions (Puppeteer + Playwright)

Downloads

40

Readme

sessionsnap

Log in once, reuse every workflow. Record, replay, annotate, and ship — all from the browser you already know.

Capture authenticated browser sessions with manual login, record user flows, annotate pages with notes, and replay everything in CI. Supports both Puppeteer and Playwright.

Designed for test automation, QA workflows, and internal tooling — without scraping, bot detection bypass, or fingerprint spoofing.

Features

  • Human-in-the-loop — You log in manually (CAPTCHA / 2FA friendly)
  • Persistent profiles — Sessions saved to disk and reusable across runs
  • Auto-detect login completion — URL and cookie heuristics, optional regex target
  • Session auto-update — Cookies + localStorage saved periodically and after every command
  • Automatic screenshots — Captures a PNG after login, on session open, and on record
  • QA toolbar — On-page toolbar with screenshot, annotation, and recording controls
  • Manual screenshots — One-click screenshot from the toolbar (toolbar hidden in output)
  • Page annotations — Click anywhere to leave numbered notes with auto-screenshot
  • Interactive recording — On-page overlay UI with start/stop controls and named recordings
  • Named recordings — Save recordings with names, replay by name
  • Action replay — Replay recorded actions with configurable speed and visual cursor
  • Parametric replay — Feed mock data (FakerJS) or custom data (CSV/JSON) into input fields during replay
  • Schema extraction — Auto-detect input fields and infer data types for mock generation
  • File upload replay — Programmatically upload files during replay (Puppeteer + Playwright)
  • Desktop viewport — 1440×900 default for realistic rendering
  • Dual runner support — Puppeteer (via puppeteer-core) and Playwright (optional)
  • Works for any website — SaaS dashboards, admin panels, internal tools

Install

npm install

puppeteer-core is included as a direct dependency and uses your system's Chrome/Chromium. For Playwright support, install it separately:

npm install playwright

CLI Commands

capture — Capture a session

Launch a headed browser, log in manually, and save the session:

sessionsnap capture <url> \
  --profile <name> \
  [--runner puppeteer|playwright] \
  [--out <file>] \
  [--wait <minutes>] \
  [--target <regex>]
  • --runner defaults to puppeteer
  • --target allows specifying a regex pattern for the post-login URL (e.g. "/dashboard" or "^https://.*/app")

Example:

sessionsnap capture https://app.acmecorp.io/login --profile acme

The tool will:

  1. Open a headed browser (1440×900 viewport)
  2. Navigate to the URL
  3. Wait for you to log in manually
  4. Detect login completion (URL no longer contains login/signin/auth, or cookie count increases)
  5. Take a screenshot of the post-login state
  6. Capture cookies and save a session snapshot

With a specific target URL pattern:

sessionsnap capture https://app.acmecorp.io/login --profile acme --target "/dashboard"

open — Open a URL with a saved session

sessionsnap open <url> \
  --profile <name> \
  [--runner puppeteer|playwright]

Example:

sessionsnap open https://app.acmecorp.io/dashboard --profile acme

The browser opens with the QA toolbar (top-right corner) providing:

  • 📸 Screenshot button — capture a clean screenshot (toolbar hidden in output)
  • 📌 Annotation button — enter annotation mode to leave notes on the page

When you close the browser, the session snapshot is automatically updated with the latest cookies.

record — Record user actions

Open a browser with a saved session and record user actions via an on-page overlay UI:

sessionsnap record <url> \
  --profile <name> \
  [--runner puppeteer|playwright] \
  [--name <recording-name>]

The browser opens with the QA toolbar containing screenshot, annotation, and recording controls:

[ 📸 ] [ 📌 ] | [ ⏺ Start Recording ]

Interactive mode (no --name):

  1. Browser opens with the toolbar showing 📸 Screenshot, 📌 Annotate, and ⏺ Start Recording buttons
  2. Click Start → toolbar switches to recording mode (timer + action counter + Stop button)
  3. Perform your actions on the page
  4. Click Stop → a dialog prompts you for a recording name
  5. Enter a name and click Save → recording saved to disk

Screenshot and annotation are always available, even during recording. Actions on the toolbar itself are never recorded.

Pre-named mode (--name provided):

  1. Recording starts automatically with the given name
  2. Click Stop when done → recording saved immediately

Examples:

# Interactive: name the recording when you stop
sessionsnap record https://app.acmecorp.io/dashboard --profile acme

# Pre-named: recording starts immediately as "checkout-flow"
sessionsnap record https://app.acmecorp.io/dashboard --profile acme --name "checkout-flow"

The recorder captures 12 action types:

| Action | What it records | Replay | |--------|----------------|--------| | click | selector, tag, text, coordinates | CSS selector → text → coordinates fallback | | dblclick | selector, tag, text, coordinates | Same 3-tier fallback as click | | hover | selector, tag, text, coordinates | CSS selector → text → coordinates fallback | | mousemove | x, y coordinates | page.mouse.move(x, y) | | input | selector, value (passwords masked) | Clear + type / fill | | select | selector, selected value & text | page.select / selectOption | | checkbox | selector, checked state, type | Smart toggle (checks current state) | | keydown | key, combo (Ctrl/Meta/Alt+key) | page.keyboard.press with modifiers | | submit | form selector, action, method | form.requestSubmit() / Enter fallback | | scroll | scroll position (window + inner elements) | window.scrollTo / element.scrollTo | | file | selector, file names, count | Log only (cannot replay file selection) | | navigation | from/to URLs (SPA & traditional) | page.goto(url) |

Noise reduction: hover debounced (150ms, interactive elements only), scroll debounced (400ms), mousemove throttled (~60fps), keydown only captures special keys and modifier combos.

Viewport resize protection

Recordings are tied to a specific viewport size (default 1440×900). If the browser window is resized during an active recording, the recording is automatically cancelled to prevent unreliable replay.

  • A red warning banner appears on the page
  • All recorded actions are discarded
  • The process exits with code 1

Both CSS viewport (innerWidth/innerHeight) and physical window (outerWidth/outerHeight) dimensions are monitored — this works correctly even when the viewport is emulated via CDP. Minor fluctuations (±10px viewport, ±30px window) are tolerated.

Named recordings are tracked in recordings.json inside the profile directory:

~/.sessionsnap/profiles/acme/
  recordings.json              # metadata: name → file mapping
  actions-checkout-flow.json   # named recording
  actions-recording-2026-...json  # unnamed recording

replay — Replay recorded actions

sessionsnap replay \
  --profile <name> \
  [--name <recording-name>] \
  [--file <actions-file>] \
  [--runner puppeteer|playwright] \
  [--speed <multiplier>] \
  [--headless] \
  [--bail] \
  [--visual]

Examples:

# Replay a named recording
sessionsnap replay --profile acme --name "checkout-flow"

# Replay the latest recording
sessionsnap replay --profile acme

# Replay at 2x speed with visual cursor
sessionsnap replay --profile acme --name "checkout-flow" --speed 2 --visual

# Replay a specific file
sessionsnap replay --profile acme --file actions-recording-2026-02-09T12-00-00-000Z.json

# Replay in headless mode
sessionsnap replay --profile acme --name "checkout-flow" --headless

# Fail fast — stop on first failure (exit code 1)
sessionsnap replay --profile acme --name "checkout-flow" --bail

The replayer:

  1. Launches a browser with the saved session profile
  2. Navigates to the starting URL from the recording
  3. Executes each action in order
  4. Uses real timing from the recording (clamped to 50ms–5s per action)
  5. Takes a screenshot after replay completes
  6. Password fields are skipped during replay for security

Error handling

By default, replay is fault-tolerant. If an individual action fails (element not found, timeout, etc.), it is logged as FAILED and the replay continues with the next action.

Use --bail for fail-fast mode: the replay stops immediately on the first failure and exits with code 1. Useful in CI/CD pipelines.

For click actions, there is a 3-step fallback strategy:

  1. CSS selector — tries the recorded selector (2s timeout)
  2. Text content — searches all visible elements for matching text
  3. Coordinates — clicks at the recorded x/y position
[sessionsnap] [1/5] click: #submit-btn "Submit"
[sessionsnap] [2/5] click FAILED: Element not found: #old-selector (text: "Gone")
[sessionsnap] [3/5] input: input[name="email"] → "[email protected]"
[sessionsnap] [4/5] navigation: /login → /dashboard
[sessionsnap] [5/5] click: a[href="/settings"] "Settings"
[sessionsnap] Replay complete.

recordings — List recordings for a profile

sessionsnap recordings --profile <name> [--json]

Shows all recordings with name (if named), action count, age, action type summary, and a replay hint:

[sessionsnap] 3 recording(s) for profile "acme":

  1. checkout-flow (actions-checkout-flow.json)
     42 actions  (2h ago)  replay: --name "checkout-flow"
     click:12  input:8  navigation:3  hover:15  scroll:4

  2. actions-recording-2026-02-11T14-30-00-000Z.json
     18 actions  (1d ago)  replay: --file "actions-recording-2026-02-11T14-30-00-000Z.json"
     click:6  input:4  navigation:2  mousemove:6

annotations — List annotations for a profile

sessionsnap annotations --profile <name> [--json]

Shows all annotations with note text, URL, coordinates, age, and linked screenshot:

[sessionsnap] 3 annotation(s) for profile "acme":

  1. 📌 "Login button does not respond on first click"  (2h ago)
     https://app.acmecorp.io/login  (450, 320)  #login-button
     📸 annotation-ann_1739...-2026-02-12T....png

  2. 📌 "Missing validation message for empty email"  (1h ago)
     https://app.acmecorp.io/login  (380, 250)  input[name="email"]
     📸 annotation-ann_1739...-2026-02-12T....png

schema — Extract field schema from a recording

Analyses a recording to extract input field metadata and infer data types for parametric replay:

sessionsnap schema --profile <name> --name <recording-name> [--locale <locale>] [--json]

The schema is automatically generated when a recording stops. You can also run it manually to regenerate or inspect:

# Extract and display schema
sessionsnap schema --profile acme --name checkout-flow

# Output:
# [sessionsnap] Schema for "checkout-flow" (5 fields, locale: en):
#
#   1. [text] firstName  person.firstName
#      selector: #firstName
#      label: "First Name"
#      original: "Ahmet"
#      constraints: {"required":true}
#
#   2. [email] email  internet.email
#      selector: #email
#      original: "[email protected]"
#
#   3. [select] city  helpers.arrayElement
#      selector: select[name="city"]
#      options: 2 choice(s)

# Export as JSON for editing
sessionsnap schema --profile acme --name checkout-flow --json > schema.json

The schema JSON is saved at ~/.sessionsnap/profiles/<profile>/schema-<name>.json and can be edited manually to fine-tune faker methods, add file paths, or adjust constraints.

Parametric Replay (Mock Data)

Replay recordings with random mock data (FakerJS) or custom data (CSV/JSON) instead of the original values:

# Mock data with FakerJS (random values each run)
sessionsnap replay --profile acme --name checkout-flow --mock

# Turkish locale for realistic Turkish names/addresses
sessionsnap replay --profile acme --name checkout-flow --mock --locale tr

# Run 5 iterations with different random data each time
sessionsnap replay --profile acme --name checkout-flow --mock --iterations 5

# Custom data from a JSON file
sessionsnap replay --profile acme --name checkout-flow --data ./users.json

# Custom data from a CSV file
sessionsnap replay --profile acme --name checkout-flow --data ./users.csv

# Data file + iterations (rows cycle: 1,2,3,1,2,3,...)
sessionsnap replay --profile acme --name checkout-flow --data ./users.csv --iterations 10

How it works

  1. Schema extraction — When a recording stops, the tool automatically analyses all input, select, and file actions to infer field types using a priority chain:

    • inputType direct mapping (emailinternet.email, telphone.number)
    • autocomplete attribute (given-nameperson.firstName)
    • name / placeholder / label keyword heuristics (firstNameperson.firstName)
    • Value pattern analysis (email regex, phone regex, URL pattern)
    • Fallback: lorem.word()
  2. Mock generation — FakerJS generates random values matching each field's detected type, respecting locale and constraints (minLength, maxLength, etc.)

  3. Value override — During replay, input/select values are replaced with mock values. The original recording logic (click, navigate, scroll, etc.) is preserved.

Custom Data Files

JSON format — Array of objects keyed by field name:

[
  { "firstName": "Mehmet", "email": "[email protected]", "city": "34" },
  { "firstName": "Ayse", "email": "[email protected]", "city": "06" }
]

CSV format — Header row matches field name:

firstName,email,city
Mehmet,[email protected],34
Ayse,[email protected],06

Field matching priority: name attribute → selector's name=label text (case-insensitive).

File Upload Support

File inputs can be replayed with mock/custom data. In the schema JSON, set filePaths:

{
  "actionIndex": 12,
  "type": "file",
  "name": "resume",
  "filePaths": ["./test-files/resume.pdf"]
}

In custom data files, specify file paths per row:

[
  { "firstName": "Mehmet", "resume": "./files/cv-mehmet.pdf" },
  { "firstName": "Ayse", "resume": "./files/cv-ayse.pdf" }
]

CSV with multiple files (pipe-separated): ./a.jpg|./b.jpg

QA Toolbar

Both open and record commands inject a floating toolbar into the browser page (top-right corner). The toolbar provides instant access to screenshot and annotation features without leaving the browser.

Screenshot Button (📸)

Click the camera icon to capture a screenshot at any time.

  • Toolbar and all sessionsnap UI elements are automatically hidden in the screenshot
  • Annotation pins remain visible (useful for documenting issues)
  • Screenshots are saved as manual-<timestamp>.png in the profile directory
  • A "Screenshot captured!" toast confirms the action

Annotation Mode (📌)

Click the pin icon to toggle annotation mode:

  1. Enter annotation mode — cursor changes to crosshair, toast confirms
  2. Click anywhere on the page — a popup appears near the click point
  3. Type your note — describe the issue, observation, or suggestion
  4. Save (Ctrl/Cmd+Enter or click Save) — a numbered pin appears at that location
  5. Exit annotation mode — click the pin icon again

Annotation features:

  • Numbered pins (1, 2, 3...) placed directly on the page content
  • Hover a pin to see the note text as a tooltip
  • Click the X button on a pin to delete it
  • Each annotation auto-captures a screenshot with the pin and note label visible (toolbar hidden)
  • Annotations are persisted in annotations.json — reloaded when you revisit the same URL
  • During recording, actions in annotation mode are not recorded (clean separation)

Annotation data model:

{
  "id": "ann_1739..._1",
  "x": 450,
  "y": 320,
  "text": "Login button does not respond on first click",
  "url": "https://app.acmecorp.io/login",
  "selector": "#login-button",
  "timestamp": "2026-02-12T14:30:00.000Z",
  "viewport": { "width": 1440, "height": 900 },
  "screenshotFile": "annotation-ann_1739...-2026-02-12T....png"
}

Toolbar in record vs open

| Feature | open | record | |---------|--------|----------| | 📸 Screenshot | ✓ | ✓ | | 📌 Annotations | ✓ | ✓ | | ⏺ Start/Stop Recording | — | ✓ | | Timer + Action Counter | — | ✓ | | Viewport Resize Guard | — | ✓ |

list — List all profiles

sessionsnap list [--json]

Shows all saved profiles with runner, URL, relative date, screenshot/recording counts:

[sessionsnap] 2 profile(s) found:

  acme     [puppeteer]  https://app.acmecorp.io/dashboard  (2h ago)  📸 3  🔴 2
  staging  [playwright]  https://staging.acmecorp.io/home   (1d ago)  📸 1

status — Show profile details

sessionsnap status --profile <name>

Prints profile details: directory path, session metadata, cookie/origin counts, screenshots and recordings.

doctor — Check session health

Check if a saved session is still valid (login not expired):

sessionsnap doctor \
  --profile <name> \
  [--runner puppeteer|playwright] \
  [--url <url>] \
  [--json]
  • --url overrides the default check URL (profile's finalUrl)
  • --json outputs structured JSON (useful for CI/CD)
  • Exit code 1 if session is unhealthy

Health checks performed:

| Check | What it does | |-------|-------------| | HTTP Status | Verifies the page returns 2xx/3xx (not 401/403) | | Login Redirect | Detects if the page redirected to a login/auth URL | | Cookie Count | Compares current cookies against the snapshot | | Cookie Expiry | Checks if any saved cookies have expired | | Page Content | Scans the page for login forms (password inputs, auth forms) |

Example:

sessionsnap doctor --profile acme
[sessionsnap] Doctor — Profile: "acme"
  URL: https://app.acmecorp.io/dashboard

  ✓ PASS  HTTP Status
         200

  ✓ PASS  Login Redirect
         No redirect — stayed on target URL

  ✓ PASS  Cookie Count
         15 cookies (snapshot had 12)

  ✓ PASS  Cookie Expiry
         No expired cookies in snapshot

  ✓ PASS  Page Content
         No login form detected

  ✓ Session is healthy — login appears valid.

If session is expired:

  ✗ FAIL  Login Redirect
         Redirected to login page: https://app.acmecorp.io/login

  ✗ FAIL  Page Content
         Login form detected on page — session likely expired

  ✗ Session may be expired — re-capture recommended.
    Run: sessionsnap capture <url> --profile acme

Use in CI/CD:

# Check before running tests — exits with code 1 if expired
sessionsnap doctor --profile acme --json || sessionsnap capture https://app.acmecorp.io/login --profile acme

clean — Delete a profile

sessionsnap clean --profile <name> [--force]

Deletes a profile and all its data (snapshot, screenshots, recordings). Prompts for confirmation unless --force is used.

Typical Workflow

# 1. Capture — log in and save the session
sessionsnap capture https://app.acmecorp.io/login --profile acme

# 2. Check session health
sessionsnap doctor --profile acme

# 3. Record — open with saved session, record a user flow
sessionsnap record https://app.acmecorp.io/dashboard --profile acme --name "checkout-flow"

# 4. Replay — replay the recorded flow
sessionsnap replay --profile acme --name "checkout-flow"

# 5. Open — browse with saved session (QA toolbar available)
sessionsnap open https://app.acmecorp.io/settings --profile acme
#    → Use 📸 to take manual screenshots
#    → Use 📌 to annotate issues on the page

# 6. View annotations
sessionsnap annotations --profile acme

# 7. Export encrypted session for CI
sessionsnap export --profile acme --key "my-secret" --stdout

# 8. Import session in CI
echo "$BUNDLE" | sessionsnap import --profile acme --key "my-secret" --stdin

# 9. List recordings
sessionsnap recordings --profile acme

# 10. Clean up
sessionsnap clean --profile acme --force

Snapshot Format

{
  "meta": {
    "runner": "puppeteer",
    "profile": "acme",
    "capturedAt": "2026-02-09T12:00:00.000Z",
    "startUrl": "https://app.acmecorp.io/login",
    "finalUrl": "https://app.acmecorp.io/dashboard"
  },
  "cookies": [
    {
      "name": "session_token",
      "value": "abc123",
      "domain": ".acmecorp.io",
      "path": "/",
      "expires": 1700000000,
      "httpOnly": true,
      "secure": true,
      "sameSite": "Lax"
    }
  ],
  "origins": [
    {
      "origin": "https://app.acmecorp.io",
      "localStorage": [
        { "name": "auth_token", "value": "eyJhbGci..." },
        { "name": "user_prefs", "value": "{\"theme\":\"dark\"}" }
      ]
    }
  ]
}

After using open, record, or replay, an updatedAt field is added to meta.

Session freshness

sessionsnap keeps your session data (snapshot.json) up-to-date across all commands:

| Command | When snapshot is saved | What is saved | |---------|----------------------|---------------| | capture | After login detection | Cookies + localStorage | | open | Every 30s + on close | Cookies + localStorage | | record | When recording stops | Cookies + localStorage | | replay | After replay completes | Cookies + localStorage |

How it works:

  • The real session lives in Chrome's userDataDir — Chrome manages cookies, localStorage, indexedDB, service workers, and cache automatically. Refresh tokens are handled natively by the browser.
  • snapshot.json is a periodic mirror of that state. The open command saves every 30 seconds while the browser is alive, so even if the final save fails (browser already closed), recent state is preserved.
  • For Puppeteer: cookies are extracted via CDP (Network.getAllCookies), localStorage via page.evaluate().
  • For Playwright: context.storageState() provides both cookies and localStorage natively.

Note: userDataDir always reflects the latest state — even if snapshot.json is slightly stale, the next open/record/replay session will use the live userDataDir data. The doctor command verifies whether the session is still valid.

CI Integration

Export encrypted session bundles for use in CI/CD pipelines. Sessions are encrypted with AES-256-GCM — zero external dependencies, uses Node.js built-in node:crypto.

export — Encrypt a session for CI

sessionsnap export \
  --profile <name> \
  --key <passphrase> \
  [--out <file>] \
  [--stdout]
  • --key encryption passphrase (also reads SESSIONSNAP_KEY env var)
  • --out writes to a .enc file (default: <profile>.enc)
  • --stdout outputs base64 to stdout (for storing as CI secret)

The bundle includes: snapshot.json + recordings.json + all actions-*.json files + Chrome session files (Cookies, Local Storage, Preferences).

import — Decrypt and restore a session

sessionsnap import \
  --profile <name> \
  --key <passphrase> \
  [--file <path>] \
  [--stdin]
  • --file reads from an .enc file
  • --stdin reads base64 from stdin

Encrypted Bundle Format

[16 bytes salt][12 bytes IV][16 bytes authTag][...ciphertext...]

Key derivation: PBKDF2(passphrase, salt, 100000 iterations, 32 bytes, SHA-256) produces a 256-bit AES key.

The plaintext payload is a JSON bundle:

{
  "version": 2,
  "profile": "acme",
  "exportedAt": "2026-02-11T...",
  "snapshot": { "meta": {}, "cookies": [], "origins": [] },
  "recordings": { "checkout-flow": { "file": "actions-checkout_flow.json" } },
  "actions": {
    "actions-checkout_flow.json": [{ "type": "click", "..." : "..." }]
  },
  "sessionFiles": [
    { "path": "Default/Cookies", "data": "<base64>" },
    { "path": "Default/Local Storage/leveldb/000003.log", "data": "<base64>" },
    { "path": "Local State", "data": "<base64>" }
  ]
}

Chrome session files included in the bundle:

| File | Content | Purpose | |------|---------|---------| | Default/Cookies | SQLite DB | HTTP cookies (session tokens, CSRF, auth) | | Default/Local Storage/ | LevelDB | JWT tokens, auth state, user preferences | | Default/Preferences | JSON | Chrome profile settings | | Default/Secure Preferences | JSON | HMAC-signed preferences | | Default/Network Persistent State | JSON | HSTS pins, security policies | | Local State | JSON | OS keychain reference, encryption key |

These files are typically 20-100 KB total (compressed), making the bundle lightweight for CI secrets. On import, Chrome loads session data natively from these files — no cookie injection or localStorage hydration needed.

Example: Local Export

# Export to file
sessionsnap export --profile acme --key "my-secret-key" --out acme.enc

# Export as base64 (copy to clipboard on macOS)
sessionsnap export --profile acme --key "my-secret-key" --stdout | pbcopy

Example: GitHub Actions

name: E2E Tests
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci

      # Import encrypted session from GitHub secret
      - name: Import session
        run: echo "$SESSIONSNAP_BUNDLE" | npx sessionsnap import --profile acme --key "$SESSIONSNAP_KEY" --stdin
        env:
          SESSIONSNAP_BUNDLE: ${{ secrets.SESSIONSNAP_BUNDLE }}
          SESSIONSNAP_KEY: ${{ secrets.SESSIONSNAP_KEY }}

      # Verify session is still valid
      - name: Check session health
        run: npx sessionsnap doctor --profile acme

      # Run recorded test flow
      - name: Replay test
        run: npx sessionsnap replay --profile acme --name "checkout-flow" --headless --bail

Example: GitLab CI

e2e-test:
  image: node:20
  stage: test
  script:
    - npm ci
    - echo "$SESSIONSNAP_BUNDLE" | npx sessionsnap import --profile acme --key "$SESSIONSNAP_KEY" --stdin
    - npx sessionsnap doctor --profile acme
    - npx sessionsnap replay --profile acme --name "checkout-flow" --headless --bail
  variables:
    SESSIONSNAP_BUNDLE: $SESSIONSNAP_BUNDLE
    SESSIONSNAP_KEY: $SESSIONSNAP_KEY

CI Workflow

sequenceDiagram
  actor Dev as Developer
  participant Local as Local Machine
  participant Secret as CI Secrets
  participant CI as CI Pipeline

  Dev->>Local: sessionsnap capture --profile acme
  Dev->>Local: sessionsnap record --profile acme --name "flow"
  Dev->>Local: sessionsnap export --profile acme --key $KEY --stdout
  Local-->>Secret: Store base64 as SESSIONSNAP_BUNDLE

  Note over Secret,CI: Pipeline triggered

  CI->>Secret: Read SESSIONSNAP_BUNDLE + SESSIONSNAP_KEY
  CI->>CI: sessionsnap import --profile acme --key $KEY --stdin
  CI->>CI: sessionsnap doctor --profile acme
  CI->>CI: sessionsnap replay --profile acme --name "flow" --headless --bail
  alt Session expired
    CI->>CI: Exit code 1 — notify developer
  else Session valid
    CI->>CI: Tests pass
  end

Architecture

High-Level Overview

graph TB
  subgraph CLI["CLI Layer — bin/sessionsnap.js"]
    CMD_CAPTURE["capture"]
    CMD_OPEN["open"]
    CMD_RECORD["record"]
    CMD_REPLAY["replay"]
    CMD_DOCTOR["doctor"]
    CMD_EXPORT["export"]
    CMD_IMPORT["import"]
    CMD_LIST["list"]
    CMD_RECORDINGS["recordings"]
    CMD_ANNOTATIONS["annotations"]
    CMD_STATUS["status"]
    CMD_CLEAN["clean"]
  end

  subgraph RUNNERS["Runner Layer — Puppeteer / Playwright"]
    subgraph PUPPETEER["puppeteer/"]
      P_BROWSER["browser.js\n(Chrome discovery)"]
      P_CAPTURE["capture.js"]
      P_OPEN["open.js"]
      P_RECORD["record.js"]
      P_REPLAY["replay.js"]
      P_DOCTOR["doctor.js"]
    end
    subgraph PLAYWRIGHT["playwright/"]
      PW_CAPTURE["capture.js"]
      PW_OPEN["open.js"]
      PW_RECORD["record.js"]
      PW_REPLAY["replay.js"]
      PW_DOCTOR["doctor.js"]
    end
  end

  subgraph CORE["Core Layer — Shared Logic"]
    SNAPSHOT["snapshot.js\n(normalize cookies,\ncreate/update snapshot)"]
    HEURISTICS["heuristics.js\n(login detection:\nURL + cookie + regex)"]
    TOOLBAR["toolbar.js\n(QA toolbar: screenshot\n+ annotations)"]
    RECORDER["recorder.js\n(recording controls +\naction capture + writeFileSync)"]
    REPLAYER["replayer.js\n(action execution +\nvisual cursor + fallback)"]
    DOCTOR_CORE["doctor.js\n(session health checks:\nHTTP + redirect + cookies\n+ page content)"]
    CIPHER["cipher.js\n(AES-256-GCM\nencrypt/decrypt)"]
    BUNDLE["bundle.js\n(profile JSON\nbundling)"]
    CSS_GEN["css-selector-generator\n(robust CSS selectors)"]
  end

  subgraph STORAGE["Storage Layer — store.js"]
    PROFILES["~/.sessionsnap/profiles/"]
    SNAP_FILE["snapshot.json"]
    SCREENSHOTS["*.png"]
    ACTIONS["actions-*.json"]
    REC_META["recordings.json"]
    ANNOTATIONS["annotations.json"]
  end

  subgraph BROWSER["Browser"]
    PAGE["Browser Page"]
    QA_TB["QA Toolbar\n(screenshot + annotations)"]
    OVERLAY["Recording Controls\n(start/stop + timer)"]
    PINS["Annotation Pins"]
    CDP["CDP / storageState"]
  end

  CMD_CAPTURE --> P_CAPTURE & PW_CAPTURE
  CMD_OPEN --> P_OPEN & PW_OPEN
  CMD_RECORD --> P_RECORD & PW_RECORD
  CMD_REPLAY --> P_REPLAY & PW_REPLAY
  CMD_DOCTOR --> P_DOCTOR & PW_DOCTOR
  CMD_EXPORT --> BUNDLE
  CMD_IMPORT --> BUNDLE
  BUNDLE --> CIPHER
  BUNDLE --> STORAGE
  CMD_LIST & CMD_RECORDINGS & CMD_ANNOTATIONS & CMD_STATUS & CMD_CLEAN --> STORAGE

  P_CAPTURE & P_OPEN & P_RECORD & P_REPLAY & P_DOCTOR --> P_BROWSER
  P_BROWSER --> PAGE

  P_CAPTURE --> HEURISTICS & SNAPSHOT
  P_OPEN --> SNAPSHOT & TOOLBAR
  P_RECORD --> RECORDER & TOOLBAR
  P_REPLAY --> REPLAYER
  P_DOCTOR --> DOCTOR_CORE

  PW_CAPTURE --> HEURISTICS & SNAPSHOT
  PW_OPEN --> SNAPSHOT & TOOLBAR
  PW_RECORD --> RECORDER & TOOLBAR
  PW_REPLAY --> REPLAYER
  PW_DOCTOR --> DOCTOR_CORE

  TOOLBAR --> QA_TB
  TOOLBAR --> PINS
  RECORDER --> CSS_GEN
  RECORDER --> OVERLAY
  RECORDER --> PAGE
  REPLAYER --> PAGE

  HEURISTICS --> CDP
  SNAPSHOT --> STORAGE

  DOCTOR_CORE --> PAGE & CDP

  P_CAPTURE & P_OPEN & P_RECORD & P_REPLAY & P_DOCTOR --> STORAGE
  PW_CAPTURE & PW_OPEN & PW_RECORD & PW_REPLAY & PW_DOCTOR --> STORAGE

  STORAGE --> SNAP_FILE & SCREENSHOTS & ACTIONS & REC_META & ANNOTATIONS

  style CLI fill:#1e293b,stroke:#475569,color:#f8fafc
  style CORE fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc
  style RUNNERS fill:#1a2e1a,stroke:#22c55e,color:#f8fafc
  style STORAGE fill:#3b1f0b,stroke:#f97316,color:#f8fafc
  style BROWSER fill:#3b0764,stroke:#a855f7,color:#f8fafc

Capture Flow

sequenceDiagram
  actor User
  participant CLI as CLI (commander)
  participant Runner as Runner (Puppeteer/Playwright)
  participant Browser as Headed Browser
  participant Heuristics as heuristics.js
  participant Store as store.js
  participant Disk as ~/.sessionsnap/profiles/

  User->>CLI: sessionsnap capture <url> --profile acme
  CLI->>Store: ensureProfileDir("acme")
  Store->>Disk: mkdir ~/.sessionsnap/profiles/acme/
  CLI->>Runner: capture(url, opts)
  Runner->>Browser: launch (headed, 1440×900)
  Runner->>Browser: page.goto(url)
  Browser-->>User: Login page displayed
  User->>Browser: Manual login (CAPTCHA, 2FA, etc.)
  
  loop Every 2 seconds
    Runner->>Heuristics: waitForLoginComplete(page)
    Heuristics->>Browser: Check URL pattern + cookie count
    alt --target regex provided
      Heuristics->>Browser: Check URL matches regex
    end
  end
  
  Heuristics-->>Runner: Login detected ✓
  Runner->>Browser: waitForNetworkIdle()
  Runner->>Browser: page.screenshot()
  Runner->>Store: saveScreenshot(capture-*.png)
  Runner->>Browser: getAllCookies (CDP) / context.cookies()
  Runner->>Store: createSnapshot + saveSnapshot
  Store->>Disk: Write snapshot.json + screenshot
  Runner->>Browser: browser.close()
  CLI-->>User: Profile saved ✓

Record & Replay Flow

sequenceDiagram
  actor User
  participant CLI as CLI
  participant Runner as Runner
  participant Browser as Browser Page
  participant Recorder as recorder.js
  participant Replayer as replayer.js
  participant Store as store.js
  participant Disk as Disk

  Note over User,Disk: ── RECORD PHASE ──

  User->>CLI: sessionsnap record <url> --profile acme
  CLI->>Store: loadSnapshot("acme") — verify session exists
  CLI->>Runner: record(url, opts)
  Runner->>Browser: launch with userDataDir
  Runner->>Browser: injectToolbar(page) — QA toolbar (📸 + 📌)
  Runner->>Recorder: setupRecorder(page, runner, dir)
  Recorder->>Browser: inject css-selector-generator lib
  Recorder->>Browser: inject action listener script
  Recorder->>Browser: inject recording controls (⏺ Start/⏹ Stop)
  Recorder->>Browser: exposeFunction(__sessionsnap_push)
  Runner->>Browser: page.goto(url)
  Browser-->>User: Page with QA toolbar displayed
  
  User->>Browser: Click "Start Recording"
  Browser->>Recorder: __sessionsnap_start_recording()
  
  User->>Browser: Interact (click, type, hover, scroll...)
  loop Each user action
    Browser->>Recorder: __sessionsnap_report_action(action)
    Recorder->>Disk: writeFileSync (crash-safe)
    Recorder-->>CLI: Log: [click] #btn "Submit"
  end
  
  User->>Browser: Click "Stop"
  Browser->>Browser: Show name dialog
  User->>Browser: Enter "checkout-flow" → Save
  Browser->>Recorder: __sessionsnap_stop_recording("checkout-flow")
  Recorder->>Disk: Write actions-checkout_flow.json
  Recorder->>Store: saveRecordingMetadata
  Store->>Disk: Update recordings.json
  Recorder->>Recorder: extractSchema(actions)
  Recorder->>Disk: Write schema-checkout_flow.json (auto)

  Note over User,Disk: ── REPLAY PHASE ──

  User->>CLI: sessionsnap replay --profile acme --name "checkout-flow"
  CLI->>Store: loadRecordingByName("acme", "checkout-flow")
  Store-->>CLI: actions[] (N actions)
  CLI->>Runner: replay(actions, opts)
  Runner->>Browser: launch with userDataDir
  Runner->>Browser: goto(startUrl)
  Runner->>Replayer: replayActions(page, actions)
  
  loop Each action
    Replayer->>Replayer: Calculate delay from timestamps
    alt click / dblclick
      Replayer->>Browser: mouse.move(x, y) → CSS / text / coords fallback
    else input
      Replayer->>Browser: clear + type(value)
    else hover
      Replayer->>Browser: mouse.move(x, y) → hover(selector)
    else navigation
      Replayer->>Browser: page.goto(url) → waitForNetworkIdle
    else scroll
      Replayer->>Browser: scrollTo(x, y)
    end
    Replayer-->>CLI: Log: [1/N] action description
  end
  
  Replayer-->>Runner: Replay complete
  Runner->>Browser: screenshot()
  Runner->>Store: saveScreenshot(replay-*.png)
  Runner->>Browser: browser.close()
  CLI-->>User: Replay finished ✓

  Note over User,Disk: ── PARAMETRIC REPLAY (--mock / --data) ──

  User->>CLI: sessionsnap replay --name "checkout-flow" --mock --locale tr
  CLI->>Store: loadSchema("acme", "checkout-flow")
  Store-->>CLI: schema (field types + faker methods)
  CLI->>CLI: FakerFill.generate(schema, locale=tr)
  CLI-->>CLI: mockValues {actionIndex → {value}}
  CLI->>Runner: replay(actions, {mockValues})
  Runner->>Replayer: replayActions(page, actions, {mockValues})
  
  loop Each action
    alt input/select with mockValues[i]
      Replayer->>Browser: type(mockValue) instead of original
      Replayer-->>CLI: 🎲 Mock: #email → "[email protected]"
    else file with mockValues[i].filePaths
      Replayer->>Browser: uploadFile(filePaths)
      Replayer-->>CLI: 📎 Uploading 1 file(s) to input[name="resume"]
    else other actions
      Replayer->>Browser: execute as normal
    end
  end

Storage Structure

graph LR
  subgraph HOME["~/.sessionsnap/"]
    subgraph PROFILES["profiles/"]
      subgraph ACME["acme/"]
        SNAP["snapshot.json\n(cookies, meta, origins)"]
        REC_JSON["recordings.json\n(name → file mapping)"]
        ANN_JSON["annotations.json\n(notes, pins, screenshots)"]
        CAP_PNG["capture-2026-*.png"]
        OPEN_PNG["open-2026-*.png"]
        MANUAL_PNG["manual-2026-*.png"]
        ANN_PNG["annotation-ann_*-2026-*.png"]
        REPLAY_PNG["replay-2026-*.png"]
        RECORD_PNG["record-2026-*.png"]
        ACTIONS1["actions-checkout_flow.json"]
        ACTIONS2["actions-recording-*.json"]
        USERDATA["Chrome userDataDir\n(cookies, localStorage,\nindexedDB, cache)"]
      end
      subgraph STAGING["staging/"]
        SNAP2["snapshot.json"]
        OTHER["..."]
      end
    end
  end

  style HOME fill:#1c1917,stroke:#78716c,color:#f5f5f4
  style PROFILES fill:#292524,stroke:#a8a29e,color:#f5f5f4
  style ACME fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc
  style STAGING fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc

Action Recording Pipeline

flowchart LR
  subgraph PAGE["Browser Page Context"]
    TOOLBAR_UI["QA Toolbar\n📸 Screenshot · 📌 Annotate\n⏺ Start / ⏹ Stop"]
    EVENTS["DOM Events\nclick · input · hover\nscroll · keydown\ndblclick · submit\ncheckbox · file\nmousemove"]
    CSS["css-selector-generator\n(robust selectors)"]
    DEBOUNCE["Debounce / Throttle\nhover: 150ms\nscroll: 400ms\nmousemove: ~16ms"]
    FILTER["Filter\n- isOverlayEvent?\n- isRecording?\n- isAnnotateMode?\n- noise reduction"]
  end

  subgraph BRIDGE["Page ↔ Node.js Bridge"]
    EXPOSE_REC["exposeFunction\n__sessionsnap_push"]
    EXPOSE_SS["exposeFunction\n__sessionsnap_take_screenshot"]
    EXPOSE_ANN["exposeFunction\n__sessionsnap_save_annotation"]
  end

  subgraph NODEJS["Node.js Process"]
    HANDLER["Action Handler\n(log + accumulate)"]
    SYNC_WRITE["writeFileSync\n(crash-safe)"]
    METADATA["saveRecordingMetadata\n(recordings.json)"]
    SS_HANDLER["Screenshot Handler\n(page.screenshot)"]
    ANN_HANDLER["Annotation Handler\n(annotations.json)"]
  end

  EVENTS --> CSS --> DEBOUNCE --> FILTER --> EXPOSE_REC
  TOOLBAR_UI -.->|start/stop| FILTER
  TOOLBAR_UI -.->|📸 click| EXPOSE_SS
  TOOLBAR_UI -.->|📌 save| EXPOSE_ANN
  EXPOSE_REC --> HANDLER --> SYNC_WRITE
  HANDLER --> METADATA
  EXPOSE_SS --> SS_HANDLER
  EXPOSE_ANN --> ANN_HANDLER

  style PAGE fill:#3b0764,stroke:#a855f7,color:#f8fafc
  style BRIDGE fill:#713f12,stroke:#eab308,color:#f8fafc
  style NODEJS fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc

Replay Fallback Strategy

flowchart TD
  START["Replay Action"] --> TYPE{Action Type?}
  
  TYPE -->|click / dblclick| MOVE["mouse.move(x, y)"]
  MOVE --> SEL{"CSS Selector\nfound?"}
  SEL -->|Yes| CLICK_SEL["Click via selector ✓"]
  SEL -->|No / timeout| TEXT{"Text content\nmatch?"}
  TEXT -->|Yes| CLICK_TEXT["Click via text ✓"]
  TEXT -->|No| COORDS{"Coordinates\navailable?"}
  COORDS -->|Yes| CLICK_XY["Click at (x, y) ✓"]
  COORDS -->|No| FAIL["FAILED ✗"]
  
  TYPE -->|input| INPUT["Clear field → type value"]
  TYPE -->|hover| HOVER["mouse.move → hover(selector)"]
  TYPE -->|navigation| NAV["page.goto → waitForNetworkIdle"]
  TYPE -->|scroll| SCROLL["window.scrollTo / element.scrollTo"]
  TYPE -->|keydown| KEY["keyboard.press with modifiers"]
  TYPE -->|checkbox| CHECK["Check state → toggle if needed"]
  TYPE -->|submit| SUBMIT["requestSubmit / Enter key"]
  TYPE -->|mousemove| MMOVE["mouse.move(x, y) per point"]
  
  FAIL --> BAIL{--bail?}
  BAIL -->|Yes| EXIT["Exit code 1"]
  BAIL -->|No| NEXT["Continue to next action"]

  style START fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc
  style FAIL fill:#7f1d1d,stroke:#ef4444,color:#f8fafc
  style EXIT fill:#7f1d1d,stroke:#ef4444,color:#f8fafc
  style CLICK_SEL fill:#14532d,stroke:#22c55e,color:#f8fafc
  style CLICK_TEXT fill:#14532d,stroke:#22c55e,color:#f8fafc
  style CLICK_XY fill:#14532d,stroke:#22c55e,color:#f8fafc
  style NEXT fill:#713f12,stroke:#eab308,color:#f8fafc

Project Structure

sessionsnap/
  bin/
    sessionsnap.js          # CLI entry point
  src/
    core/
      snapshot.js           # Canonical session format & normalization
      heuristics.js         # Login detection logic (URL + cookie polling)
      toolbar.js            # QA toolbar: screenshot + annotation injection
      recorder.js           # Recording controls + action capture + writeFileSync
      replayer.js           # Replay engine for recorded actions + mock value override
      schema.js             # Schema extraction from recordings (field types + faker mapping)
      faker-fill.js         # FakerJS mock data generation from schema
      data-loader.js        # Custom CSV/JSON data loading + field matching
      doctor.js             # Session health checks (HTTP, redirect, cookies, content)
      cipher.js             # AES-256-GCM encrypt/decrypt (node:crypto)
      bundle.js             # Profile JSON bundling for export/import
    puppeteer/
      browser.js            # Chrome/Chromium executable discovery
      capture.js            # Capture session via CDP
      open.js               # Open URL with saved profile + update on close
      record.js             # Record user actions via Puppeteer
      replay.js             # Replay recorded actions via Puppeteer
      doctor.js             # Health check via Puppeteer
    playwright/
      capture.js            # Capture session via storageState
      open.js               # Open URL with saved profile + update on close
      record.js             # Record user actions via Playwright
      replay.js             # Replay recorded actions via Playwright
      doctor.js             # Health check via Playwright
    store.js                # Profile directory, metadata & JSON I/O
  test/
    store.test.js
    snapshot.test.js
    heuristics.test.js
    cli.test.js
  package.json

Testing

npm test

Uses the Node.js built-in test runner (node:test). No additional test dependencies required.

License

MIT