Skip to content

litepacks/sessionsnap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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"] → "user@example.com"
[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: "ahmet@test.com"
#
#   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": "mehmet@firma.com", "city": "34" },
  { "firstName": "Ayse", "email": "ayse@firma.com", "city": "06" }
]

CSV format — Header row matches field name:

firstName,email,city
Mehmet,mehmet@firma.com,34
Ayse,ayse@firma.com,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
Loading

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
Loading

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 ✓
Loading

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 → "baydar@gmail.com"
    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
Loading

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
Loading

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
Loading

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
Loading

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors