sessionsnap 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +486 -141
- package/bin/sessionsnap.js +103 -28
- package/package.json +1 -1
- package/src/core/recorder.js +409 -18
- package/src/core/replayer.js +47 -4
- package/src/playwright/capture.js +1 -14
- package/src/playwright/open.js +2 -15
- package/src/playwright/record.js +52 -0
- package/src/playwright/replay.js +13 -2
- package/src/puppeteer/browser.js +25 -17
- package/src/puppeteer/capture.js +24 -22
- package/src/puppeteer/open.js +25 -23
- package/src/puppeteer/record.js +71 -0
- package/src/puppeteer/replay.js +37 -10
- package/src/store.js +74 -1
package/README.md
CHANGED
|
@@ -10,13 +10,14 @@ Designed for test automation, internal tooling, and any workflow that requires p
|
|
|
10
10
|
|
|
11
11
|
- **Human-in-the-loop** — You log in manually (CAPTCHA / 2FA friendly)
|
|
12
12
|
- **Persistent profiles** — Sessions saved to disk and reusable across runs
|
|
13
|
-
- **Auto-detect login completion** — URL and cookie heuristics
|
|
13
|
+
- **Auto-detect login completion** — URL and cookie heuristics, optional regex target
|
|
14
14
|
- **Session auto-update** — Session snapshot is refreshed when the browser closes
|
|
15
|
-
- **Automatic screenshots** — Captures a PNG after login
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
15
|
+
- **Automatic screenshots** — Captures a PNG after login, on session open, and on record
|
|
16
|
+
- **Interactive recording** — On-page overlay UI with start/stop controls and named recordings
|
|
17
|
+
- **Named recordings** — Save recordings with names, replay by name
|
|
18
|
+
- **Action replay** — Replay recorded actions with configurable speed and visual cursor
|
|
18
19
|
- **Desktop viewport** — 1440×900 default for realistic rendering
|
|
19
|
-
- **Dual runner support** — Puppeteer (
|
|
20
|
+
- **Dual runner support** — Puppeteer (via puppeteer-core) and Playwright (optional)
|
|
20
21
|
- **Works for any website** — SaaS dashboards, admin panels, internal tools
|
|
21
22
|
|
|
22
23
|
## Install
|
|
@@ -25,15 +26,15 @@ Designed for test automation, internal tooling, and any workflow that requires p
|
|
|
25
26
|
npm install
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
`puppeteer-core` is included as a direct dependency and uses your system's Chrome/Chromium. For Playwright support, install it separately:
|
|
29
30
|
|
|
30
31
|
```bash
|
|
31
32
|
npm install playwright
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
## CLI
|
|
35
|
+
## CLI Commands
|
|
35
36
|
|
|
36
|
-
### Capture a session
|
|
37
|
+
### `capture` — Capture a session
|
|
37
38
|
|
|
38
39
|
Launch a headed browser, log in manually, and save the session:
|
|
39
40
|
|
|
@@ -43,10 +44,11 @@ sessionsnap capture <url> \
|
|
|
43
44
|
[--runner puppeteer|playwright] \
|
|
44
45
|
[--out <file>] \
|
|
45
46
|
[--wait <minutes>] \
|
|
46
|
-
[--
|
|
47
|
+
[--target <regex>]
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
`--runner` defaults to `puppeteer`
|
|
50
|
+
- `--runner` defaults to `puppeteer`
|
|
51
|
+
- `--target` allows specifying a regex pattern for the post-login URL (e.g. `"/dashboard"` or `"^https://.*/app"`)
|
|
50
52
|
|
|
51
53
|
**Example:**
|
|
52
54
|
|
|
@@ -62,13 +64,18 @@ The tool will:
|
|
|
62
64
|
5. Take a screenshot of the post-login state
|
|
63
65
|
6. Capture cookies and save a session snapshot
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
With a specific target URL pattern:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
sessionsnap capture https://app.acmecorp.io/login --profile acme --target "/dashboard"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `open` — Open a URL with a saved session
|
|
66
74
|
|
|
67
75
|
```bash
|
|
68
76
|
sessionsnap open <url> \
|
|
69
77
|
--profile <name> \
|
|
70
|
-
[--runner puppeteer|playwright]
|
|
71
|
-
[--record]
|
|
78
|
+
[--runner puppeteer|playwright]
|
|
72
79
|
```
|
|
73
80
|
|
|
74
81
|
**Example:**
|
|
@@ -79,183 +86,121 @@ sessionsnap open https://app.acmecorp.io/dashboard --profile acme
|
|
|
79
86
|
|
|
80
87
|
When you close the browser, the session snapshot is automatically updated with the latest cookies.
|
|
81
88
|
|
|
82
|
-
###
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
sessionsnap list [--json]
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Shows all saved profiles with a summary: runner, URL, relative date, screenshot/recording counts.
|
|
89
|
-
|
|
90
|
-
**Example output:**
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
[sessionsnap] 2 profile(s) found:
|
|
94
|
-
|
|
95
|
-
acme [puppeteer] https://app.acmecorp.io/dashboard (2h ago) 📸 3 🔴 2
|
|
96
|
-
staging [playwright] https://staging.acmecorp.io/home (1d ago) 📸 1
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Use `--json` for machine-readable output (useful for scripting).
|
|
89
|
+
### `record` — Record user actions
|
|
100
90
|
|
|
101
|
-
|
|
91
|
+
Open a browser with a saved session and record user actions via an on-page overlay UI:
|
|
102
92
|
|
|
103
93
|
```bash
|
|
104
|
-
sessionsnap
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
**Example output:**
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
Profile: acme
|
|
113
|
-
Directory: /Users/you/.sessionsnap/profiles/acme
|
|
114
|
-
Runner: puppeteer
|
|
115
|
-
Captured at: 2026-02-09T12:00:00.000Z
|
|
116
|
-
Updated at: 2026-02-09T12:05:00.000Z
|
|
117
|
-
Start URL: https://app.acmecorp.io/login
|
|
118
|
-
Final URL: https://app.acmecorp.io/dashboard
|
|
119
|
-
Cookies: 12
|
|
120
|
-
Origins: 0
|
|
121
|
-
Screenshots:
|
|
122
|
-
- capture-2026-02-09T12-00-00-000Z.png
|
|
123
|
-
- open-2026-02-09T12-05-00-000Z.png
|
|
124
|
-
Recordings:
|
|
125
|
-
- actions-capture-2026-02-09T12-00-00-000Z.json
|
|
94
|
+
sessionsnap record <url> \
|
|
95
|
+
--profile <name> \
|
|
96
|
+
[--runner puppeteer|playwright] \
|
|
97
|
+
[--name <recording-name>]
|
|
126
98
|
```
|
|
127
99
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"profile": "acme",
|
|
135
|
-
"capturedAt": "2026-02-09T12:00:00.000Z",
|
|
136
|
-
"startUrl": "https://app.acmecorp.io/login",
|
|
137
|
-
"finalUrl": "https://app.acmecorp.io/dashboard"
|
|
138
|
-
},
|
|
139
|
-
"cookies": [
|
|
140
|
-
{
|
|
141
|
-
"name": "session_token",
|
|
142
|
-
"value": "abc123",
|
|
143
|
-
"domain": ".acmecorp.io",
|
|
144
|
-
"path": "/",
|
|
145
|
-
"expires": 1700000000,
|
|
146
|
-
"httpOnly": true,
|
|
147
|
-
"secure": true,
|
|
148
|
-
"sameSite": "Lax"
|
|
149
|
-
}
|
|
150
|
-
],
|
|
151
|
-
"origins": []
|
|
152
|
-
}
|
|
153
|
-
```
|
|
100
|
+
**Interactive mode** (no `--name`):
|
|
101
|
+
1. Browser opens with an overlay showing a **"Start Recording"** button
|
|
102
|
+
2. Click Start → overlay switches to recording mode (timer + action counter + Stop button)
|
|
103
|
+
3. Perform your actions on the page
|
|
104
|
+
4. Click Stop → a dialog prompts you for a recording name
|
|
105
|
+
5. Enter a name and click Save → recording saved to disk
|
|
154
106
|
|
|
155
|
-
|
|
107
|
+
**Pre-named mode** (`--name` provided):
|
|
108
|
+
1. Recording starts automatically with the given name
|
|
109
|
+
2. Click Stop when done → recording saved immediately
|
|
156
110
|
|
|
157
|
-
|
|
111
|
+
**Examples:**
|
|
158
112
|
|
|
159
|
-
|
|
113
|
+
```bash
|
|
114
|
+
# Interactive: name the recording when you stop
|
|
115
|
+
sessionsnap record https://app.acmecorp.io/dashboard --profile acme
|
|
160
116
|
|
|
117
|
+
# Pre-named: recording starts immediately as "checkout-flow"
|
|
118
|
+
sessionsnap record https://app.acmecorp.io/dashboard --profile acme --name "checkout-flow"
|
|
161
119
|
```
|
|
162
|
-
~/.sessionsnap/profiles/acme/
|
|
163
|
-
snapshot.json
|
|
164
|
-
capture-2026-02-09T12-00-00-000Z.png
|
|
165
|
-
open-2026-02-09T12-05-00-000Z.png
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
- **capture** — taken right after login is detected
|
|
169
|
-
- **open** — taken after the page loads with the saved session
|
|
170
|
-
|
|
171
|
-
## Action Recording
|
|
172
120
|
|
|
173
|
-
|
|
121
|
+
The recorder captures 12 action types:
|
|
174
122
|
|
|
175
123
|
| Action | What it records | Replay |
|
|
176
124
|
|--------|----------------|--------|
|
|
177
125
|
| **click** | selector, tag, text, coordinates | CSS selector → text → coordinates fallback |
|
|
178
126
|
| **dblclick** | selector, tag, text, coordinates | Same 3-tier fallback as click |
|
|
179
|
-
| **hover** | selector, tag, text |
|
|
127
|
+
| **hover** | selector, tag, text, coordinates | CSS selector → text → coordinates fallback |
|
|
128
|
+
| **mousemove** | x, y coordinates | `page.mouse.move(x, y)` |
|
|
180
129
|
| **input** | selector, value (passwords masked) | Clear + type / fill |
|
|
181
130
|
| **select** | selector, selected value & text | `page.select` / `selectOption` |
|
|
182
|
-
| **checkbox** | selector, checked state, type
|
|
183
|
-
| **keydown** | key, combo (Ctrl/Meta/Alt+key)
|
|
131
|
+
| **checkbox** | selector, checked state, type | Smart toggle (checks current state) |
|
|
132
|
+
| **keydown** | key, combo (Ctrl/Meta/Alt+key) | `page.keyboard.press` with modifiers |
|
|
184
133
|
| **submit** | form selector, action, method | `form.requestSubmit()` / Enter fallback |
|
|
185
|
-
| **scroll** |
|
|
186
|
-
| **file** | selector, file names,
|
|
134
|
+
| **scroll** | scroll position (window + inner elements) | `window.scrollTo` / `element.scrollTo` |
|
|
135
|
+
| **file** | selector, file names, count | Log only (cannot replay file selection) |
|
|
187
136
|
| **navigation** | from/to URLs (SPA & traditional) | `page.goto(url)` |
|
|
188
137
|
|
|
189
|
-
Noise reduction: hover
|
|
138
|
+
Noise reduction: hover debounced (150ms, interactive elements only), scroll debounced (400ms), mousemove throttled (~60fps), keydown only captures special keys and modifier combos.
|
|
190
139
|
|
|
191
|
-
|
|
140
|
+
Named recordings are tracked in `recordings.json` inside the profile directory:
|
|
192
141
|
|
|
193
142
|
```
|
|
194
143
|
~/.sessionsnap/profiles/acme/
|
|
195
|
-
|
|
196
|
-
actions-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
**Example:**
|
|
200
|
-
|
|
201
|
-
```bash
|
|
202
|
-
sessionsnap capture https://app.acmecorp.io/login --profile acme --record
|
|
144
|
+
recordings.json # metadata: name → file mapping
|
|
145
|
+
actions-checkout-flow.json # named recording
|
|
146
|
+
actions-recording-2026-...json # unnamed recording
|
|
203
147
|
```
|
|
204
148
|
|
|
205
|
-
###
|
|
206
|
-
|
|
207
|
-
Replay a previously recorded session:
|
|
149
|
+
### `replay` — Replay recorded actions
|
|
208
150
|
|
|
209
151
|
```bash
|
|
210
152
|
sessionsnap replay \
|
|
211
153
|
--profile <name> \
|
|
212
|
-
[--
|
|
154
|
+
[--name <recording-name>] \
|
|
213
155
|
[--file <actions-file>] \
|
|
156
|
+
[--runner puppeteer|playwright] \
|
|
214
157
|
[--speed <multiplier>] \
|
|
215
158
|
[--headless] \
|
|
216
|
-
[--bail]
|
|
159
|
+
[--bail] \
|
|
160
|
+
[--visual]
|
|
217
161
|
```
|
|
218
162
|
|
|
219
163
|
**Examples:**
|
|
220
164
|
|
|
221
165
|
```bash
|
|
222
|
-
# Replay
|
|
166
|
+
# Replay a named recording
|
|
167
|
+
sessionsnap replay --profile acme --name "checkout-flow"
|
|
168
|
+
|
|
169
|
+
# Replay the latest recording
|
|
223
170
|
sessionsnap replay --profile acme
|
|
224
171
|
|
|
225
|
-
# Replay at 2x speed
|
|
226
|
-
sessionsnap replay --profile acme --speed 2
|
|
172
|
+
# Replay at 2x speed with visual cursor
|
|
173
|
+
sessionsnap replay --profile acme --name "checkout-flow" --speed 2 --visual
|
|
227
174
|
|
|
228
|
-
# Replay a specific
|
|
229
|
-
sessionsnap replay --profile acme --file actions-
|
|
175
|
+
# Replay a specific file
|
|
176
|
+
sessionsnap replay --profile acme --file actions-recording-2026-02-09T12-00-00-000Z.json
|
|
230
177
|
|
|
231
|
-
# Replay in headless mode
|
|
232
|
-
sessionsnap replay --profile acme --headless
|
|
178
|
+
# Replay in headless mode
|
|
179
|
+
sessionsnap replay --profile acme --name "checkout-flow" --headless
|
|
233
180
|
|
|
234
181
|
# Fail fast — stop on first failure (exit code 1)
|
|
235
|
-
sessionsnap replay --profile acme --bail
|
|
182
|
+
sessionsnap replay --profile acme --name "checkout-flow" --bail
|
|
236
183
|
```
|
|
237
184
|
|
|
238
185
|
The replayer:
|
|
239
186
|
1. Launches a browser with the saved session profile
|
|
240
187
|
2. Navigates to the starting URL from the recording
|
|
241
|
-
3. Executes each action in order
|
|
188
|
+
3. Executes each action in order
|
|
242
189
|
4. Uses real timing from the recording (clamped to 50ms–5s per action)
|
|
243
190
|
5. Takes a screenshot after replay completes
|
|
244
191
|
6. Password fields are skipped during replay for security
|
|
245
192
|
|
|
246
193
|
#### Error handling
|
|
247
194
|
|
|
248
|
-
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
|
|
195
|
+
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**.
|
|
249
196
|
|
|
250
|
-
Use `--bail` for **fail-fast** mode: the replay stops immediately on the first failure and exits with code `1`.
|
|
197
|
+
Use `--bail` for **fail-fast** mode: the replay stops immediately on the first failure and exits with code `1`. Useful in CI/CD pipelines.
|
|
251
198
|
|
|
252
199
|
For click actions, there is a 3-step fallback strategy:
|
|
253
200
|
1. **CSS selector** — tries the recorded selector (2s timeout)
|
|
254
201
|
2. **Text content** — searches all visible elements for matching text
|
|
255
202
|
3. **Coordinates** — clicks at the recorded x/y position
|
|
256
203
|
|
|
257
|
-
If all three strategies fail, the action is logged as `FAILED` and skipped.
|
|
258
|
-
|
|
259
204
|
```
|
|
260
205
|
[sessionsnap] [1/5] click: #submit-btn "Submit"
|
|
261
206
|
[sessionsnap] [2/5] click FAILED: Element not found: #old-selector (text: "Gone")
|
|
@@ -265,20 +210,417 @@ If all three strategies fail, the action is logged as `FAILED` and skipped.
|
|
|
265
210
|
[sessionsnap] Replay complete.
|
|
266
211
|
```
|
|
267
212
|
|
|
268
|
-
|
|
213
|
+
### `recordings` — List recordings for a profile
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
sessionsnap recordings --profile <name> [--json]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Shows all recordings with name (if named), action count, age, action type summary, and a replay hint:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
[sessionsnap] 3 recording(s) for profile "acme":
|
|
223
|
+
|
|
224
|
+
1. checkout-flow (actions-checkout-flow.json)
|
|
225
|
+
42 actions (2h ago) replay: --name "checkout-flow"
|
|
226
|
+
click:12 input:8 navigation:3 hover:15 scroll:4
|
|
227
|
+
|
|
228
|
+
2. actions-recording-2026-02-11T14-30-00-000Z.json
|
|
229
|
+
18 actions (1d ago) replay: --file "actions-recording-2026-02-11T14-30-00-000Z.json"
|
|
230
|
+
click:6 input:4 navigation:2 mousemove:6
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### `list` — List all profiles
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
sessionsnap list [--json]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Shows all saved profiles with runner, URL, relative date, screenshot/recording counts:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
[sessionsnap] 2 profile(s) found:
|
|
243
|
+
|
|
244
|
+
acme [puppeteer] https://app.acmecorp.io/dashboard (2h ago) 📸 3 🔴 2
|
|
245
|
+
staging [playwright] https://staging.acmecorp.io/home (1d ago) 📸 1
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `status` — Show profile details
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
sessionsnap status --profile <name>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Prints profile details: directory path, session metadata, cookie/origin counts, screenshots and recordings.
|
|
255
|
+
|
|
256
|
+
### `clean` — Delete a profile
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
sessionsnap clean --profile <name> [--force]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Deletes a profile and all its data (snapshot, screenshots, recordings). Prompts for confirmation unless `--force` is used.
|
|
263
|
+
|
|
264
|
+
## Typical Workflow
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# 1. Capture — log in and save the session
|
|
268
|
+
sessionsnap capture https://app.acmecorp.io/login --profile acme
|
|
269
|
+
|
|
270
|
+
# 2. Record — open with saved session, record a user flow
|
|
271
|
+
sessionsnap record https://app.acmecorp.io/dashboard --profile acme --name "checkout-flow"
|
|
272
|
+
|
|
273
|
+
# 3. Replay — replay the recorded flow
|
|
274
|
+
sessionsnap replay --profile acme --name "checkout-flow"
|
|
275
|
+
|
|
276
|
+
# 4. Open — browse with saved session (no recording)
|
|
277
|
+
sessionsnap open https://app.acmecorp.io/settings --profile acme
|
|
278
|
+
|
|
279
|
+
# 5. List recordings
|
|
280
|
+
sessionsnap recordings --profile acme
|
|
281
|
+
|
|
282
|
+
# 6. Clean up
|
|
283
|
+
sessionsnap clean --profile acme --force
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Snapshot Format
|
|
269
287
|
|
|
270
288
|
```json
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
289
|
+
{
|
|
290
|
+
"meta": {
|
|
291
|
+
"runner": "puppeteer",
|
|
292
|
+
"profile": "acme",
|
|
293
|
+
"capturedAt": "2026-02-09T12:00:00.000Z",
|
|
294
|
+
"startUrl": "https://app.acmecorp.io/login",
|
|
295
|
+
"finalUrl": "https://app.acmecorp.io/dashboard"
|
|
296
|
+
},
|
|
297
|
+
"cookies": [
|
|
298
|
+
{
|
|
299
|
+
"name": "session_token",
|
|
300
|
+
"value": "abc123",
|
|
301
|
+
"domain": ".acmecorp.io",
|
|
302
|
+
"path": "/",
|
|
303
|
+
"expires": 1700000000,
|
|
304
|
+
"httpOnly": true,
|
|
305
|
+
"secure": true,
|
|
306
|
+
"sameSite": "Lax"
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
"origins": []
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
After using `open`, an `updatedAt` field is added to `meta`.
|
|
314
|
+
|
|
315
|
+
## Architecture
|
|
316
|
+
|
|
317
|
+
### High-Level Overview
|
|
318
|
+
|
|
319
|
+
```mermaid
|
|
320
|
+
graph TB
|
|
321
|
+
subgraph CLI["CLI Layer — bin/sessionsnap.js"]
|
|
322
|
+
CMD_CAPTURE["capture"]
|
|
323
|
+
CMD_OPEN["open"]
|
|
324
|
+
CMD_RECORD["record"]
|
|
325
|
+
CMD_REPLAY["replay"]
|
|
326
|
+
CMD_LIST["list"]
|
|
327
|
+
CMD_RECORDINGS["recordings"]
|
|
328
|
+
CMD_STATUS["status"]
|
|
329
|
+
CMD_CLEAN["clean"]
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
subgraph RUNNERS["Runner Layer — Puppeteer / Playwright"]
|
|
333
|
+
subgraph PUPPETEER["puppeteer/"]
|
|
334
|
+
P_BROWSER["browser.js\n(Chrome discovery)"]
|
|
335
|
+
P_CAPTURE["capture.js"]
|
|
336
|
+
P_OPEN["open.js"]
|
|
337
|
+
P_RECORD["record.js"]
|
|
338
|
+
P_REPLAY["replay.js"]
|
|
339
|
+
end
|
|
340
|
+
subgraph PLAYWRIGHT["playwright/"]
|
|
341
|
+
PW_CAPTURE["capture.js"]
|
|
342
|
+
PW_OPEN["open.js"]
|
|
343
|
+
PW_RECORD["record.js"]
|
|
344
|
+
PW_REPLAY["replay.js"]
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
subgraph CORE["Core Layer — Shared Logic"]
|
|
349
|
+
SNAPSHOT["snapshot.js\n(normalize cookies,\ncreate/update snapshot)"]
|
|
350
|
+
HEURISTICS["heuristics.js\n(login detection:\nURL + cookie + regex)"]
|
|
351
|
+
RECORDER["recorder.js\n(overlay UI + action\ncapture + writeFileSync)"]
|
|
352
|
+
REPLAYER["replayer.js\n(action execution +\nvisual cursor + fallback)"]
|
|
353
|
+
CSS_GEN["css-selector-generator\n(robust CSS selectors)"]
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
subgraph STORAGE["Storage Layer — store.js"]
|
|
357
|
+
PROFILES["~/.sessionsnap/profiles/"]
|
|
358
|
+
SNAP_FILE["snapshot.json"]
|
|
359
|
+
SCREENSHOTS["*.png"]
|
|
360
|
+
ACTIONS["actions-*.json"]
|
|
361
|
+
REC_META["recordings.json"]
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
subgraph BROWSER["Browser"]
|
|
365
|
+
PAGE["Browser Page"]
|
|
366
|
+
OVERLAY["Recording Overlay UI"]
|
|
367
|
+
CDP["CDP / storageState"]
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
CMD_CAPTURE --> P_CAPTURE & PW_CAPTURE
|
|
371
|
+
CMD_OPEN --> P_OPEN & PW_OPEN
|
|
372
|
+
CMD_RECORD --> P_RECORD & PW_RECORD
|
|
373
|
+
CMD_REPLAY --> P_REPLAY & PW_REPLAY
|
|
374
|
+
CMD_LIST & CMD_RECORDINGS & CMD_STATUS & CMD_CLEAN --> STORAGE
|
|
375
|
+
|
|
376
|
+
P_CAPTURE & P_OPEN & P_RECORD & P_REPLAY --> P_BROWSER
|
|
377
|
+
P_BROWSER --> PAGE
|
|
378
|
+
|
|
379
|
+
P_CAPTURE --> HEURISTICS & SNAPSHOT
|
|
380
|
+
P_OPEN --> SNAPSHOT
|
|
381
|
+
P_RECORD --> RECORDER
|
|
382
|
+
P_REPLAY --> REPLAYER
|
|
383
|
+
|
|
384
|
+
PW_CAPTURE --> HEURISTICS & SNAPSHOT
|
|
385
|
+
PW_OPEN --> SNAPSHOT
|
|
386
|
+
PW_RECORD --> RECORDER
|
|
387
|
+
PW_REPLAY --> REPLAYER
|
|
388
|
+
|
|
389
|
+
RECORDER --> CSS_GEN
|
|
390
|
+
RECORDER --> OVERLAY
|
|
391
|
+
RECORDER --> PAGE
|
|
392
|
+
REPLAYER --> PAGE
|
|
393
|
+
|
|
394
|
+
HEURISTICS --> CDP
|
|
395
|
+
SNAPSHOT --> STORAGE
|
|
396
|
+
|
|
397
|
+
P_CAPTURE & P_OPEN & P_RECORD & P_REPLAY --> STORAGE
|
|
398
|
+
PW_CAPTURE & PW_OPEN & PW_RECORD & PW_REPLAY --> STORAGE
|
|
399
|
+
|
|
400
|
+
STORAGE --> SNAP_FILE & SCREENSHOTS & ACTIONS & REC_META
|
|
401
|
+
|
|
402
|
+
style CLI fill:#1e293b,stroke:#475569,color:#f8fafc
|
|
403
|
+
style CORE fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc
|
|
404
|
+
style RUNNERS fill:#1a2e1a,stroke:#22c55e,color:#f8fafc
|
|
405
|
+
style STORAGE fill:#3b1f0b,stroke:#f97316,color:#f8fafc
|
|
406
|
+
style BROWSER fill:#3b0764,stroke:#a855f7,color:#f8fafc
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Capture Flow
|
|
410
|
+
|
|
411
|
+
```mermaid
|
|
412
|
+
sequenceDiagram
|
|
413
|
+
actor User
|
|
414
|
+
participant CLI as CLI (commander)
|
|
415
|
+
participant Runner as Runner (Puppeteer/Playwright)
|
|
416
|
+
participant Browser as Headed Browser
|
|
417
|
+
participant Heuristics as heuristics.js
|
|
418
|
+
participant Store as store.js
|
|
419
|
+
participant Disk as ~/.sessionsnap/profiles/
|
|
420
|
+
|
|
421
|
+
User->>CLI: sessionsnap capture <url> --profile acme
|
|
422
|
+
CLI->>Store: ensureProfileDir("acme")
|
|
423
|
+
Store->>Disk: mkdir ~/.sessionsnap/profiles/acme/
|
|
424
|
+
CLI->>Runner: capture(url, opts)
|
|
425
|
+
Runner->>Browser: launch (headed, 1440×900)
|
|
426
|
+
Runner->>Browser: page.goto(url)
|
|
427
|
+
Browser-->>User: Login page displayed
|
|
428
|
+
User->>Browser: Manual login (CAPTCHA, 2FA, etc.)
|
|
429
|
+
|
|
430
|
+
loop Every 2 seconds
|
|
431
|
+
Runner->>Heuristics: waitForLoginComplete(page)
|
|
432
|
+
Heuristics->>Browser: Check URL pattern + cookie count
|
|
433
|
+
alt --target regex provided
|
|
434
|
+
Heuristics->>Browser: Check URL matches regex
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
Heuristics-->>Runner: Login detected ✓
|
|
439
|
+
Runner->>Browser: waitForNetworkIdle()
|
|
440
|
+
Runner->>Browser: page.screenshot()
|
|
441
|
+
Runner->>Store: saveScreenshot(capture-*.png)
|
|
442
|
+
Runner->>Browser: getAllCookies (CDP) / context.cookies()
|
|
443
|
+
Runner->>Store: createSnapshot + saveSnapshot
|
|
444
|
+
Store->>Disk: Write snapshot.json + screenshot
|
|
445
|
+
Runner->>Browser: browser.close()
|
|
446
|
+
CLI-->>User: Profile saved ✓
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Record & Replay Flow
|
|
450
|
+
|
|
451
|
+
```mermaid
|
|
452
|
+
sequenceDiagram
|
|
453
|
+
actor User
|
|
454
|
+
participant CLI as CLI
|
|
455
|
+
participant Runner as Runner
|
|
456
|
+
participant Browser as Browser Page
|
|
457
|
+
participant Recorder as recorder.js
|
|
458
|
+
participant Replayer as replayer.js
|
|
459
|
+
participant Store as store.js
|
|
460
|
+
participant Disk as Disk
|
|
461
|
+
|
|
462
|
+
Note over User,Disk: ── RECORD PHASE ──
|
|
463
|
+
|
|
464
|
+
User->>CLI: sessionsnap record <url> --profile acme
|
|
465
|
+
CLI->>Store: loadSnapshot("acme") — verify session exists
|
|
466
|
+
CLI->>Runner: record(url, opts)
|
|
467
|
+
Runner->>Browser: launch with userDataDir
|
|
468
|
+
Runner->>Recorder: setupRecorder(page, runner, dir)
|
|
469
|
+
Recorder->>Browser: inject css-selector-generator lib
|
|
470
|
+
Recorder->>Browser: inject action listener script
|
|
471
|
+
Recorder->>Browser: inject overlay UI (Start/Stop)
|
|
472
|
+
Recorder->>Browser: exposeFunction(__sessionsnap_report_action)
|
|
473
|
+
Runner->>Browser: page.goto(url)
|
|
474
|
+
Browser-->>User: Page with overlay displayed
|
|
475
|
+
|
|
476
|
+
User->>Browser: Click "Start Recording"
|
|
477
|
+
Browser->>Recorder: __sessionsnap_start_recording()
|
|
478
|
+
|
|
479
|
+
User->>Browser: Interact (click, type, hover, scroll...)
|
|
480
|
+
loop Each user action
|
|
481
|
+
Browser->>Recorder: __sessionsnap_report_action(action)
|
|
482
|
+
Recorder->>Disk: writeFileSync (crash-safe)
|
|
483
|
+
Recorder-->>CLI: Log: [click] #btn "Submit"
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
User->>Browser: Click "Stop"
|
|
487
|
+
Browser->>Browser: Show name dialog
|
|
488
|
+
User->>Browser: Enter "checkout-flow" → Save
|
|
489
|
+
Browser->>Recorder: __sessionsnap_stop_recording("checkout-flow")
|
|
490
|
+
Recorder->>Disk: Write actions-checkout_flow.json
|
|
491
|
+
Recorder->>Store: saveRecordingMetadata
|
|
492
|
+
Store->>Disk: Update recordings.json
|
|
493
|
+
|
|
494
|
+
Note over User,Disk: ── REPLAY PHASE ──
|
|
495
|
+
|
|
496
|
+
User->>CLI: sessionsnap replay --profile acme --name "checkout-flow"
|
|
497
|
+
CLI->>Store: loadRecordingByName("acme", "checkout-flow")
|
|
498
|
+
Store-->>CLI: actions[] (N actions)
|
|
499
|
+
CLI->>Runner: replay(actions, opts)
|
|
500
|
+
Runner->>Browser: launch with userDataDir
|
|
501
|
+
Runner->>Browser: goto(startUrl)
|
|
502
|
+
Runner->>Replayer: replayActions(page, actions)
|
|
503
|
+
|
|
504
|
+
loop Each action
|
|
505
|
+
Replayer->>Replayer: Calculate delay from timestamps
|
|
506
|
+
alt click / dblclick
|
|
507
|
+
Replayer->>Browser: mouse.move(x, y) → CSS / text / coords fallback
|
|
508
|
+
else input
|
|
509
|
+
Replayer->>Browser: clear + type(value)
|
|
510
|
+
else hover
|
|
511
|
+
Replayer->>Browser: mouse.move(x, y) → hover(selector)
|
|
512
|
+
else navigation
|
|
513
|
+
Replayer->>Browser: page.goto(url) → waitForNetworkIdle
|
|
514
|
+
else scroll
|
|
515
|
+
Replayer->>Browser: scrollTo(x, y)
|
|
516
|
+
end
|
|
517
|
+
Replayer-->>CLI: Log: [1/N] action description
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
Replayer-->>Runner: Replay complete
|
|
521
|
+
Runner->>Browser: screenshot()
|
|
522
|
+
Runner->>Store: saveScreenshot(replay-*.png)
|
|
523
|
+
Runner->>Browser: browser.close()
|
|
524
|
+
CLI-->>User: Replay finished ✓
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Storage Structure
|
|
528
|
+
|
|
529
|
+
```mermaid
|
|
530
|
+
graph LR
|
|
531
|
+
subgraph HOME["~/.sessionsnap/"]
|
|
532
|
+
subgraph PROFILES["profiles/"]
|
|
533
|
+
subgraph ACME["acme/"]
|
|
534
|
+
SNAP["snapshot.json\n(cookies, meta, origins)"]
|
|
535
|
+
REC_JSON["recordings.json\n(name → file mapping)"]
|
|
536
|
+
CAP_PNG["capture-2026-*.png"]
|
|
537
|
+
OPEN_PNG["open-2026-*.png"]
|
|
538
|
+
REPLAY_PNG["replay-2026-*.png"]
|
|
539
|
+
RECORD_PNG["record-2026-*.png"]
|
|
540
|
+
ACTIONS1["actions-checkout_flow.json"]
|
|
541
|
+
ACTIONS2["actions-recording-*.json"]
|
|
542
|
+
USERDATA["Chrome userDataDir\n(cookies, localStorage,\nindexedDB, cache)"]
|
|
543
|
+
end
|
|
544
|
+
subgraph STAGING["staging/"]
|
|
545
|
+
SNAP2["snapshot.json"]
|
|
546
|
+
OTHER["..."]
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
style HOME fill:#1c1917,stroke:#78716c,color:#f5f5f4
|
|
552
|
+
style PROFILES fill:#292524,stroke:#a8a29e,color:#f5f5f4
|
|
553
|
+
style ACME fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc
|
|
554
|
+
style STAGING fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Action Recording Pipeline
|
|
558
|
+
|
|
559
|
+
```mermaid
|
|
560
|
+
flowchart LR
|
|
561
|
+
subgraph PAGE["Browser Page Context"]
|
|
562
|
+
EVENTS["DOM Events\nclick · input · hover\nscroll · keydown\ndblclick · submit\ncheckbox · file\nmousemove"]
|
|
563
|
+
CSS["css-selector-generator\n(robust selectors)"]
|
|
564
|
+
DEBOUNCE["Debounce / Throttle\nhover: 150ms\nscroll: 400ms\nmousemove: ~16ms"]
|
|
565
|
+
OVERLAY["Overlay UI\nStart ⏺ / Stop ⏹\nTimer · Counter"]
|
|
566
|
+
FILTER["Filter\n- isOverlayEvent?\n- isRecording?\n- noise reduction"]
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
subgraph BRIDGE["Page ↔ Node.js Bridge"]
|
|
570
|
+
EXPOSE["page.exposeFunction\n__sessionsnap_report_action"]
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
subgraph NODEJS["Node.js Process"]
|
|
574
|
+
HANDLER["Action Handler\n(log + accumulate)"]
|
|
575
|
+
SYNC_WRITE["writeFileSync\n(crash-safe)"]
|
|
576
|
+
METADATA["saveRecordingMetadata\n(recordings.json)"]
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
EVENTS --> CSS --> DEBOUNCE --> FILTER --> EXPOSE
|
|
580
|
+
OVERLAY -.->|start/stop| FILTER
|
|
581
|
+
EXPOSE --> HANDLER --> SYNC_WRITE
|
|
582
|
+
HANDLER --> METADATA
|
|
583
|
+
|
|
584
|
+
style PAGE fill:#3b0764,stroke:#a855f7,color:#f8fafc
|
|
585
|
+
style BRIDGE fill:#713f12,stroke:#eab308,color:#f8fafc
|
|
586
|
+
style NODEJS fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Replay Fallback Strategy
|
|
590
|
+
|
|
591
|
+
```mermaid
|
|
592
|
+
flowchart TD
|
|
593
|
+
START["Replay Action"] --> TYPE{Action Type?}
|
|
594
|
+
|
|
595
|
+
TYPE -->|click / dblclick| MOVE["mouse.move(x, y)"]
|
|
596
|
+
MOVE --> SEL{"CSS Selector\nfound?"}
|
|
597
|
+
SEL -->|Yes| CLICK_SEL["Click via selector ✓"]
|
|
598
|
+
SEL -->|No / timeout| TEXT{"Text content\nmatch?"}
|
|
599
|
+
TEXT -->|Yes| CLICK_TEXT["Click via text ✓"]
|
|
600
|
+
TEXT -->|No| COORDS{"Coordinates\navailable?"}
|
|
601
|
+
COORDS -->|Yes| CLICK_XY["Click at (x, y) ✓"]
|
|
602
|
+
COORDS -->|No| FAIL["FAILED ✗"]
|
|
603
|
+
|
|
604
|
+
TYPE -->|input| INPUT["Clear field → type value"]
|
|
605
|
+
TYPE -->|hover| HOVER["mouse.move → hover(selector)"]
|
|
606
|
+
TYPE -->|navigation| NAV["page.goto → waitForNetworkIdle"]
|
|
607
|
+
TYPE -->|scroll| SCROLL["window.scrollTo / element.scrollTo"]
|
|
608
|
+
TYPE -->|keydown| KEY["keyboard.press with modifiers"]
|
|
609
|
+
TYPE -->|checkbox| CHECK["Check state → toggle if needed"]
|
|
610
|
+
TYPE -->|submit| SUBMIT["requestSubmit / Enter key"]
|
|
611
|
+
TYPE -->|mousemove| MMOVE["mouse.move(x, y) per point"]
|
|
612
|
+
|
|
613
|
+
FAIL --> BAIL{--bail?}
|
|
614
|
+
BAIL -->|Yes| EXIT["Exit code 1"]
|
|
615
|
+
BAIL -->|No| NEXT["Continue to next action"]
|
|
616
|
+
|
|
617
|
+
style START fill:#1e3a5f,stroke:#3b82f6,color:#f8fafc
|
|
618
|
+
style FAIL fill:#7f1d1d,stroke:#ef4444,color:#f8fafc
|
|
619
|
+
style EXIT fill:#7f1d1d,stroke:#ef4444,color:#f8fafc
|
|
620
|
+
style CLICK_SEL fill:#14532d,stroke:#22c55e,color:#f8fafc
|
|
621
|
+
style CLICK_TEXT fill:#14532d,stroke:#22c55e,color:#f8fafc
|
|
622
|
+
style CLICK_XY fill:#14532d,stroke:#22c55e,color:#f8fafc
|
|
623
|
+
style NEXT fill:#713f12,stroke:#eab308,color:#f8fafc
|
|
282
624
|
```
|
|
283
625
|
|
|
284
626
|
## Project Structure
|
|
@@ -291,17 +633,20 @@ sessionsnap/
|
|
|
291
633
|
core/
|
|
292
634
|
snapshot.js # Canonical session format & normalization
|
|
293
635
|
heuristics.js # Login detection logic (URL + cookie polling)
|
|
294
|
-
recorder.js # Injected script for recording
|
|
636
|
+
recorder.js # Injected script + overlay UI for recording
|
|
295
637
|
replayer.js # Replay engine for recorded actions
|
|
296
638
|
puppeteer/
|
|
639
|
+
browser.js # Chrome/Chromium executable discovery
|
|
297
640
|
capture.js # Capture session via CDP
|
|
298
641
|
open.js # Open URL with saved profile + update on close
|
|
642
|
+
record.js # Record user actions via Puppeteer
|
|
299
643
|
replay.js # Replay recorded actions via Puppeteer
|
|
300
644
|
playwright/
|
|
301
645
|
capture.js # Capture session via storageState
|
|
302
646
|
open.js # Open URL with saved profile + update on close
|
|
647
|
+
record.js # Record user actions via Playwright
|
|
303
648
|
replay.js # Replay recorded actions via Playwright
|
|
304
|
-
store.js # Profile directory & JSON I/O
|
|
649
|
+
store.js # Profile directory, metadata & JSON I/O
|
|
305
650
|
test/
|
|
306
651
|
store.test.js
|
|
307
652
|
snapshot.test.js
|