uisnap 0.1.0

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.
Files changed (91) hide show
  1. package/.claude/settings.local.json +26 -0
  2. package/.claude/skills/uisnap/README.md +48 -0
  3. package/.claude/skills/uisnap/REFERENCE.md +1261 -0
  4. package/.claude/skills/uisnap/SETUP.md +75 -0
  5. package/.claude/skills/uisnap/SKILL.md +130 -0
  6. package/.claude/skills/uisnap/snapshot-capture-and-analysis.md +452 -0
  7. package/.claude/skills/uisnap/trace-capture-and-analysis.md +472 -0
  8. package/CHANGELOG.md +96 -0
  9. package/LICENSE +21 -0
  10. package/README.md +394 -0
  11. package/SKILL-INSTALLATION.md +103 -0
  12. package/dist/analyze-console.d.ts +3 -0
  13. package/dist/analyze-console.d.ts.map +1 -0
  14. package/dist/analyze-console.js +153 -0
  15. package/dist/analyze-console.js.map +1 -0
  16. package/dist/analyze-network.d.ts +3 -0
  17. package/dist/analyze-network.d.ts.map +1 -0
  18. package/dist/analyze-network.js +156 -0
  19. package/dist/analyze-network.js.map +1 -0
  20. package/dist/chrome-trace-analyze.d.ts +3 -0
  21. package/dist/chrome-trace-analyze.d.ts.map +1 -0
  22. package/dist/chrome-trace-analyze.js +119 -0
  23. package/dist/chrome-trace-analyze.js.map +1 -0
  24. package/dist/chrome-trace-import.d.ts +3 -0
  25. package/dist/chrome-trace-import.d.ts.map +1 -0
  26. package/dist/chrome-trace-import.js +90 -0
  27. package/dist/chrome-trace-import.js.map +1 -0
  28. package/dist/commands/snapshot.d.ts +4 -0
  29. package/dist/commands/snapshot.d.ts.map +1 -0
  30. package/dist/commands/snapshot.js +154 -0
  31. package/dist/commands/snapshot.js.map +1 -0
  32. package/dist/diagnose.d.ts +3 -0
  33. package/dist/diagnose.d.ts.map +1 -0
  34. package/dist/diagnose.js +244 -0
  35. package/dist/diagnose.js.map +1 -0
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +26 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/pw.d.ts +3 -0
  41. package/dist/pw.d.ts.map +1 -0
  42. package/dist/pw.js +289 -0
  43. package/dist/pw.js.map +1 -0
  44. package/dist/query-a11y.d.ts +3 -0
  45. package/dist/query-a11y.d.ts.map +1 -0
  46. package/dist/query-a11y.js +208 -0
  47. package/dist/query-a11y.js.map +1 -0
  48. package/dist/trace-import.d.ts +3 -0
  49. package/dist/trace-import.d.ts.map +1 -0
  50. package/dist/trace-import.js +93 -0
  51. package/dist/trace-import.js.map +1 -0
  52. package/dist/types.d.ts +70 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +3 -0
  55. package/dist/types.js.map +1 -0
  56. package/dist/utils/chromeTraceAnalyze.d.ts +40 -0
  57. package/dist/utils/chromeTraceAnalyze.d.ts.map +1 -0
  58. package/dist/utils/chromeTraceAnalyze.js +113 -0
  59. package/dist/utils/chromeTraceAnalyze.js.map +1 -0
  60. package/dist/utils/chromeTraceHelpers.d.ts +4 -0
  61. package/dist/utils/chromeTraceHelpers.d.ts.map +1 -0
  62. package/dist/utils/chromeTraceHelpers.js +68 -0
  63. package/dist/utils/chromeTraceHelpers.js.map +1 -0
  64. package/dist/utils/chromeTraceImport.d.ts +14 -0
  65. package/dist/utils/chromeTraceImport.d.ts.map +1 -0
  66. package/dist/utils/chromeTraceImport.js +77 -0
  67. package/dist/utils/chromeTraceImport.js.map +1 -0
  68. package/dist/utils/helpers.d.ts +3 -0
  69. package/dist/utils/helpers.d.ts.map +1 -0
  70. package/dist/utils/helpers.js +88 -0
  71. package/dist/utils/helpers.js.map +1 -0
  72. package/dist/utils/projectRoot.d.ts +2 -0
  73. package/dist/utils/projectRoot.d.ts.map +1 -0
  74. package/dist/utils/projectRoot.js +56 -0
  75. package/dist/utils/projectRoot.js.map +1 -0
  76. package/dist/utils/traceHelpers.d.ts +4 -0
  77. package/dist/utils/traceHelpers.d.ts.map +1 -0
  78. package/dist/utils/traceHelpers.js +67 -0
  79. package/dist/utils/traceHelpers.js.map +1 -0
  80. package/dist/utils/traceImport.d.ts +20 -0
  81. package/dist/utils/traceImport.d.ts.map +1 -0
  82. package/dist/utils/traceImport.js +124 -0
  83. package/dist/utils/traceImport.js.map +1 -0
  84. package/dist/utils/webVitals.d.ts +9 -0
  85. package/dist/utils/webVitals.d.ts.map +1 -0
  86. package/dist/utils/webVitals.js +54 -0
  87. package/dist/utils/webVitals.js.map +1 -0
  88. package/examples/login-flow.js +37 -0
  89. package/examples/scroll-perf-trace.js +43 -0
  90. package/examples/simple-capture.js +28 -0
  91. package/package.json +74 -0
@@ -0,0 +1,1261 @@
1
+ # uisnap - Specification
2
+
3
+ ## Overview
4
+
5
+ A Playwright-based debugging toolkit for Claude Code that provides efficient frontend debugging through disk-based state capture and analysis. The toolkit follows a "capture once, query many" philosophy to minimize token costs and enable systematic debugging workflows.
6
+
7
+ ## Architecture
8
+
9
+ The system consists of four layers:
10
+
11
+ 1. **Core Runtime** - Playwright environment manager
12
+ 2. **Capture Commands** - Built-in data extractors that write to disk
13
+ 3. **Analysis Commands** - Query tools for captured data
14
+ 4. **Code Mode** - User script execution within Playwright context
15
+
16
+ ```
17
+ ┌─────────────────────────────────────────────┐
18
+ │ Agent (Claude Code) │
19
+ └─────────────────┬───────────────────────────┘
20
+
21
+ ┌───────────┴───────────┐
22
+ │ │
23
+ ▼ ▼
24
+ ┌──────────┐ ┌─────────────────┐
25
+ │ Built-in │ │ Custom Scripts │
26
+ │ Commands │ │ (Code Mode) │
27
+ └─────┬────┘ └────────┬────────┘
28
+ │ │
29
+ └───────────┬───────────┘
30
+
31
+
32
+ ┌──────────────────┐
33
+ │ Core Runtime │
34
+ │ (pw.js) │
35
+ └────────┬─────────┘
36
+
37
+
38
+ ┌──────────────────┐
39
+ │ Playwright │
40
+ │ (Browser) │
41
+ └──────────────────┘
42
+
43
+
44
+ ┌──────────────────┐
45
+ │ Disk Output │
46
+ │ (JSON/JSONL) │
47
+ └──────────────────┘
48
+ ```
49
+
50
+ ## 1. Core Runtime
51
+
52
+ ### File: `scripts/pw.js`
53
+
54
+ The core runtime is a single entry point that manages Playwright's lifecycle and routes commands.
55
+
56
+ **Responsibilities:**
57
+ - Launch and manage Playwright browser instance
58
+ - Route to built-in commands or user scripts
59
+ - Provide execution context for scripts
60
+ - Handle cleanup on exit
61
+
62
+ **Interface:**
63
+ ```bash
64
+ node scripts/pw.js <command> [args...]
65
+ ```
66
+
67
+ **Commands:**
68
+ - `snapshot <url> <output-dir>` - Capture full page state
69
+ - `interact <scenario.yml> <output-dir>` - Run interaction scenario
70
+ - `exec <script.js> [args...]` - Execute user script in Playwright context
71
+
72
+ **Implementation Structure:**
73
+ ```javascript
74
+ #!/usr/bin/env node
75
+ const { chromium } = require('playwright');
76
+ const fs = require('fs');
77
+ const path = require('path');
78
+
79
+ // Built-in command modules
80
+ const builtins = {
81
+ snapshot: require('./commands/snapshot'),
82
+ interact: require('./commands/interact'),
83
+ };
84
+
85
+ async function main() {
86
+ const command = process.argv[2];
87
+ const args = process.argv.slice(3);
88
+
89
+ // Launch browser
90
+ const browser = await chromium.launch({
91
+ headless: true,
92
+ args: ['--disable-dev-shm-usage']
93
+ });
94
+
95
+ const context = await browser.newContext({
96
+ viewport: { width: 1280, height: 720 }
97
+ });
98
+
99
+ const page = await context.newPage();
100
+
101
+ try {
102
+ if (builtins[command]) {
103
+ // Execute built-in command
104
+ await builtins[command](page, context, browser, args);
105
+ } else if (command === 'exec') {
106
+ // Execute user script
107
+ await executeUserScript(page, context, browser, args);
108
+ } else {
109
+ console.error(`Unknown command: ${command}`);
110
+ process.exit(1);
111
+ }
112
+ } finally {
113
+ await browser.close();
114
+ }
115
+ }
116
+
117
+ async function executeUserScript(page, context, browser, args) {
118
+ const scriptPath = args[0];
119
+ const scriptArgs = args.slice(1);
120
+ const scriptCode = fs.readFileSync(scriptPath, 'utf8');
121
+
122
+ // Create execution environment for user script
123
+ const env = {
124
+ page,
125
+ context,
126
+ browser,
127
+ args: scriptArgs,
128
+ fs,
129
+ path,
130
+ utils: {
131
+ writeJson: (filepath, data) => {
132
+ fs.mkdirSync(path.dirname(filepath), { recursive: true });
133
+ fs.writeFileSync(filepath, JSON.stringify(data, null, 2));
134
+ },
135
+ writeJsonl: (filepath, items) => {
136
+ fs.mkdirSync(path.dirname(filepath), { recursive: true });
137
+ fs.writeFileSync(filepath, items.map(i => JSON.stringify(i)).join('\n'));
138
+ },
139
+ appendJsonl: (filepath, item) => {
140
+ fs.appendFileSync(filepath, JSON.stringify(item) + '\n');
141
+ }
142
+ }
143
+ };
144
+
145
+ // Execute user script in async context
146
+ const wrappedScript = `
147
+ (async ({ page, context, browser, args, fs, path, utils }) => {
148
+ ${scriptCode}
149
+ })
150
+ `;
151
+
152
+ const userFunction = eval(wrappedScript);
153
+ await userFunction(env);
154
+ }
155
+
156
+ main().catch(err => {
157
+ console.error('Fatal error:', err);
158
+ process.exit(1);
159
+ });
160
+ ```
161
+
162
+ ## 2. Capture Commands
163
+
164
+ Built-in commands that extract browser state and write to disk. All commands produce structured JSON/JSONL output.
165
+
166
+ ### 2.1 Snapshot Command
167
+
168
+ **Purpose:** Capture complete page state in a single operation
169
+
170
+ **Usage:**
171
+ ```bash
172
+ node scripts/pw.js snapshot <url> <output-dir>
173
+ ```
174
+
175
+ **Output Structure:**
176
+ ```
177
+ <output-dir>/
178
+ ├── a11y.json # Accessibility tree
179
+ ├── console.jsonl # Console messages (one per line)
180
+ ├── network.jsonl # Network requests (one per line)
181
+ └── metadata.json # Page metadata (title, URL, timestamp, viewport)
182
+ ```
183
+
184
+ **File: `scripts/commands/snapshot.js`**
185
+ ```javascript
186
+ module.exports = async (page, context, browser, args) => {
187
+ const [url, outputDir] = args;
188
+ const fs = require('fs');
189
+ const path = require('path');
190
+
191
+ if (!url || !outputDir) {
192
+ throw new Error('Usage: snapshot <url> <output-dir>');
193
+ }
194
+
195
+ fs.mkdirSync(outputDir, { recursive: true });
196
+
197
+ // Set up event listeners before navigation
198
+ const consoleLogs = [];
199
+ page.on('console', msg => {
200
+ consoleLogs.push({
201
+ timestamp: new Date().toISOString(),
202
+ type: msg.type(),
203
+ text: msg.text(),
204
+ location: msg.location()
205
+ });
206
+ });
207
+
208
+ const networkLogs = [];
209
+ page.on('response', response => {
210
+ networkLogs.push({
211
+ url: response.url(),
212
+ status: response.status(),
213
+ statusText: response.statusText(),
214
+ method: response.request().method(),
215
+ resourceType: response.request().resourceType(),
216
+ timing: response.request().timing()
217
+ });
218
+ });
219
+
220
+ // Navigate to page
221
+ await page.goto(url, { waitUntil: 'networkidle' });
222
+
223
+ // Extract accessibility tree
224
+ const a11yTree = await page.accessibility.snapshot();
225
+
226
+ // Extract page metadata
227
+ const metadata = await page.evaluate(() => ({
228
+ title: document.title,
229
+ url: window.location.href,
230
+ viewport: {
231
+ width: window.innerWidth,
232
+ height: window.innerHeight
233
+ },
234
+ timestamp: new Date().toISOString()
235
+ }));
236
+
237
+ // Write all data to disk
238
+ fs.writeFileSync(
239
+ path.join(outputDir, 'a11y.json'),
240
+ JSON.stringify(a11yTree, null, 2)
241
+ );
242
+
243
+ fs.writeFileSync(
244
+ path.join(outputDir, 'console.jsonl'),
245
+ consoleLogs.map(l => JSON.stringify(l)).join('\n')
246
+ );
247
+
248
+ fs.writeFileSync(
249
+ path.join(outputDir, 'network.jsonl'),
250
+ networkLogs.map(l => JSON.stringify(l)).join('\n')
251
+ );
252
+
253
+ fs.writeFileSync(
254
+ path.join(outputDir, 'metadata.json'),
255
+ JSON.stringify(metadata, null, 2)
256
+ );
257
+
258
+ // Summary output
259
+ console.log(`✓ Snapshot saved to ${outputDir}/`);
260
+ console.log(` - Accessibility tree: ${JSON.stringify(a11yTree).length} bytes`);
261
+ console.log(` - Console messages: ${consoleLogs.length}`);
262
+ console.log(` - Network requests: ${networkLogs.length}`);
263
+ };
264
+ ```
265
+
266
+ **Data Formats:**
267
+
268
+ *a11y.json:*
269
+ ```json
270
+ {
271
+ "role": "WebArea",
272
+ "name": "Page Title",
273
+ "children": [
274
+ {
275
+ "role": "button",
276
+ "name": "Submit",
277
+ "disabled": false,
278
+ "focused": false
279
+ }
280
+ ]
281
+ }
282
+ ```
283
+
284
+ *console.jsonl:*
285
+ ```json
286
+ {"timestamp":"2024-12-25T10:30:00.000Z","type":"error","text":"Uncaught TypeError: Cannot read property 'x' of null","location":{"url":"https://example.com/app.js","lineNumber":42}}
287
+ {"timestamp":"2024-12-25T10:30:01.000Z","type":"log","text":"User clicked submit","location":{"url":"https://example.com/app.js","lineNumber":100}}
288
+ ```
289
+
290
+ *network.jsonl:*
291
+ ```json
292
+ {"url":"https://api.example.com/users","status":200,"statusText":"OK","method":"GET","resourceType":"fetch","timing":{"startTime":1234.5,"responseEnd":1456.7}}
293
+ {"url":"https://api.example.com/submit","status":500,"statusText":"Internal Server Error","method":"POST","resourceType":"fetch","timing":{"startTime":2345.6,"responseEnd":2567.8}}
294
+ ```
295
+
296
+ ### 2.2 Interact Command
297
+
298
+ **Purpose:** Run a sequence of actions and capture state after each step
299
+
300
+ **Usage:**
301
+ ```bash
302
+ node scripts/pw.js interact <scenario.yml> <output-dir>
303
+ ```
304
+
305
+ **Scenario Format (YAML):**
306
+ ```yaml
307
+ steps:
308
+ - action: goto
309
+ url: https://example.com/login
310
+
311
+ - action: fill
312
+ selector: input[name="email"]
313
+ value: test@example.com
314
+
315
+ - action: fill
316
+ selector: input[name="password"]
317
+ value: password123
318
+
319
+ - action: click
320
+ selector: button[type="submit"]
321
+ wait: 2000 # Optional wait after action (ms)
322
+
323
+ - action: wait
324
+ duration: 1000
325
+ ```
326
+
327
+ **Output Structure:**
328
+ ```
329
+ <output-dir>/
330
+ ├── step-1-goto/
331
+ │ ├── a11y.json
332
+ │ ├── console.jsonl
333
+ │ └── network.jsonl
334
+ ├── step-2-fill/
335
+ │ ├── a11y.json
336
+ │ ├── console.jsonl
337
+ │ └── network.jsonl
338
+ └── step-3-click/
339
+ ├── a11y.json
340
+ ├── console.jsonl
341
+ └── network.jsonl
342
+ ```
343
+
344
+ **File: `scripts/commands/interact.js`**
345
+ ```javascript
346
+ module.exports = async (page, context, browser, args) => {
347
+ const [scenarioFile, outputDir] = args;
348
+ const fs = require('fs');
349
+ const path = require('path');
350
+ const yaml = require('yaml');
351
+
352
+ if (!scenarioFile || !outputDir) {
353
+ throw new Error('Usage: interact <scenario.yml> <output-dir>');
354
+ }
355
+
356
+ const scenario = yaml.parse(fs.readFileSync(scenarioFile, 'utf8'));
357
+ fs.mkdirSync(outputDir, { recursive: true });
358
+
359
+ // Set up logging
360
+ const consoleLogs = [];
361
+ page.on('console', msg => {
362
+ consoleLogs.push({
363
+ timestamp: new Date().toISOString(),
364
+ type: msg.type(),
365
+ text: msg.text()
366
+ });
367
+ });
368
+
369
+ const networkLogs = [];
370
+ page.on('response', response => {
371
+ networkLogs.push({
372
+ url: response.url(),
373
+ status: response.status(),
374
+ method: response.request().method()
375
+ });
376
+ });
377
+
378
+ // Execute each step
379
+ for (let i = 0; i < scenario.steps.length; i++) {
380
+ const step = scenario.steps[i];
381
+ console.log(`Step ${i + 1}/${scenario.steps.length}: ${step.action}`);
382
+
383
+ // Execute action
384
+ switch (step.action) {
385
+ case 'goto':
386
+ await page.goto(step.url, { waitUntil: 'networkidle' });
387
+ break;
388
+
389
+ case 'click':
390
+ await page.click(step.selector);
391
+ if (step.wait) await page.waitForTimeout(step.wait);
392
+ break;
393
+
394
+ case 'fill':
395
+ await page.fill(step.selector, step.value);
396
+ break;
397
+
398
+ case 'wait':
399
+ await page.waitForTimeout(step.duration || 1000);
400
+ break;
401
+
402
+ default:
403
+ throw new Error(`Unknown action: ${step.action}`);
404
+ }
405
+
406
+ // Capture state after this step
407
+ const stepDir = path.join(outputDir, `step-${i + 1}-${step.action}`);
408
+ fs.mkdirSync(stepDir, { recursive: true });
409
+
410
+ const a11y = await page.accessibility.snapshot();
411
+ fs.writeFileSync(
412
+ path.join(stepDir, 'a11y.json'),
413
+ JSON.stringify(a11y, null, 2)
414
+ );
415
+
416
+ // Save new console logs since last step
417
+ const newConsoleLogs = consoleLogs.splice(0);
418
+ if (newConsoleLogs.length > 0) {
419
+ fs.writeFileSync(
420
+ path.join(stepDir, 'console.jsonl'),
421
+ newConsoleLogs.map(l => JSON.stringify(l)).join('\n')
422
+ );
423
+ }
424
+
425
+ // Save new network logs since last step
426
+ const newNetworkLogs = networkLogs.splice(0);
427
+ if (newNetworkLogs.length > 0) {
428
+ fs.writeFileSync(
429
+ path.join(stepDir, 'network.jsonl'),
430
+ newNetworkLogs.map(l => JSON.stringify(l)).join('\n')
431
+ );
432
+ }
433
+ }
434
+
435
+ console.log(`✓ Interaction complete: ${scenario.steps.length} steps captured`);
436
+ };
437
+ ```
438
+
439
+ ## 3. Analysis Commands
440
+
441
+ Query tools that operate on captured data without requiring browser access. These are standalone scripts (not Playwright commands).
442
+
443
+ ### 3.1 Query Accessibility Tree
444
+
445
+ **Purpose:** Extract specific information from accessibility tree
446
+
447
+ **Usage:**
448
+ ```bash
449
+ node scripts/query-a11y.js <snapshot-dir/a11y.json> <query>
450
+ ```
451
+
452
+ **Queries:**
453
+ - `--buttons` - List all buttons
454
+ - `--links` - List all links
455
+ - `--inputs` - List all input fields
456
+ - `--interactive` - List all interactive elements
457
+ - `--disabled` - List disabled elements
458
+ - `<text>` - Search by text content
459
+
460
+ **File: `scripts/query-a11y.js`**
461
+ ```javascript
462
+ #!/usr/bin/env node
463
+ const fs = require('fs');
464
+
465
+ const snapshotFile = process.argv[2];
466
+ const query = process.argv[3];
467
+
468
+ if (!snapshotFile || !query) {
469
+ console.error('Usage: query-a11y.js <a11y.json> <query>');
470
+ process.exit(1);
471
+ }
472
+
473
+ const tree = JSON.parse(fs.readFileSync(snapshotFile, 'utf8'));
474
+
475
+ function traverse(node, matcher, results = []) {
476
+ if (!node) return results;
477
+
478
+ if (matcher(node)) {
479
+ results.push(node);
480
+ }
481
+
482
+ if (node.children) {
483
+ node.children.forEach(child => traverse(child, matcher, results));
484
+ }
485
+
486
+ return results;
487
+ }
488
+
489
+ // Define matchers for different queries
490
+ let matcher;
491
+ switch (query) {
492
+ case '--buttons':
493
+ matcher = n => n.role === 'button';
494
+ break;
495
+ case '--links':
496
+ matcher = n => n.role === 'link';
497
+ break;
498
+ case '--inputs':
499
+ matcher = n => ['textbox', 'searchbox', 'combobox'].includes(n.role);
500
+ break;
501
+ case '--interactive':
502
+ matcher = n => ['button', 'link', 'textbox', 'checkbox', 'radio', 'searchbox'].includes(n.role);
503
+ break;
504
+ case '--disabled':
505
+ matcher = n => n.disabled === true;
506
+ break;
507
+ default:
508
+ // Text search
509
+ matcher = n => n.name && n.name.toLowerCase().includes(query.toLowerCase());
510
+ }
511
+
512
+ const results = traverse(tree, matcher);
513
+
514
+ // Output compact JSON for each result
515
+ results.forEach(r => {
516
+ console.log(JSON.stringify({
517
+ role: r.role,
518
+ name: r.name,
519
+ disabled: r.disabled,
520
+ focused: r.focused
521
+ }));
522
+ });
523
+
524
+ console.error(`\n✓ Found ${results.length} matches`);
525
+ ```
526
+
527
+ **Example Usage:**
528
+ ```bash
529
+ # Find all buttons
530
+ node scripts/query-a11y.js snapshots/current/a11y.json --buttons
531
+
532
+ # Find submit button
533
+ node scripts/query-a11y.js snapshots/current/a11y.json "Submit"
534
+
535
+ # Find disabled elements
536
+ node scripts/query-a11y.js snapshots/current/a11y.json --disabled
537
+ ```
538
+
539
+ ### 3.2 Analyze Console Logs
540
+
541
+ **Purpose:** Summarize and filter console messages
542
+
543
+ **Usage:**
544
+ ```bash
545
+ node scripts/analyze-console.js <snapshot-dir/console.jsonl>
546
+ ```
547
+
548
+ **File: `scripts/analyze-console.js`**
549
+ ```javascript
550
+ #!/usr/bin/env node
551
+ const fs = require('fs');
552
+
553
+ const logFile = process.argv[2];
554
+
555
+ if (!logFile) {
556
+ console.error('Usage: analyze-console.js <console.jsonl>');
557
+ process.exit(1);
558
+ }
559
+
560
+ const logs = fs.readFileSync(logFile, 'utf8')
561
+ .split('\n')
562
+ .filter(Boolean)
563
+ .map(JSON.parse);
564
+
565
+ // Count by type
566
+ const counts = {};
567
+ logs.forEach(log => {
568
+ counts[log.type] = (counts[log.type] || 0) + 1;
569
+ });
570
+
571
+ console.log('=== Console Summary ===');
572
+ console.log(`Total messages: ${logs.length}`);
573
+ Object.entries(counts).forEach(([type, count]) => {
574
+ console.log(` ${type}: ${count}`);
575
+ });
576
+
577
+ // Show unique errors
578
+ const errors = logs.filter(l => l.type === 'error');
579
+ if (errors.length > 0) {
580
+ console.log('\n=== Errors ===');
581
+ const uniqueErrors = [...new Set(errors.map(e => e.text))];
582
+ uniqueErrors.forEach((err, i) => {
583
+ const count = errors.filter(e => e.text === err).length;
584
+ console.log(`${i + 1}. [${count}x] ${err}`);
585
+ });
586
+ }
587
+
588
+ // Show unique warnings
589
+ const warnings = logs.filter(l => l.type === 'warning');
590
+ if (warnings.length > 0) {
591
+ console.log('\n=== Warnings ===');
592
+ const uniqueWarnings = [...new Set(warnings.map(w => w.text))];
593
+ uniqueWarnings.forEach((warn, i) => {
594
+ const count = warnings.filter(w => w.text === warn).length;
595
+ console.log(`${i + 1}. [${count}x] ${warn}`);
596
+ });
597
+ }
598
+ ```
599
+
600
+ ### 3.3 Analyze Network Requests
601
+
602
+ **Purpose:** Summarize network activity and identify failures
603
+
604
+ **Usage:**
605
+ ```bash
606
+ node scripts/analyze-network.js <snapshot-dir/network.jsonl>
607
+ ```
608
+
609
+ **File: `scripts/analyze-network.js`**
610
+ ```javascript
611
+ #!/usr/bin/env node
612
+ const fs = require('fs');
613
+
614
+ const logFile = process.argv[2];
615
+
616
+ if (!logFile) {
617
+ console.error('Usage: analyze-network.js <network.jsonl>');
618
+ process.exit(1);
619
+ }
620
+
621
+ const requests = fs.readFileSync(logFile, 'utf8')
622
+ .split('\n')
623
+ .filter(Boolean)
624
+ .map(JSON.parse);
625
+
626
+ console.log('=== Network Summary ===');
627
+ console.log(`Total requests: ${requests.length}`);
628
+
629
+ // Count by resource type
630
+ const byType = {};
631
+ requests.forEach(req => {
632
+ byType[req.resourceType] = (byType[req.resourceType] || 0) + 1;
633
+ });
634
+
635
+ console.log('\nBy resource type:');
636
+ Object.entries(byType).forEach(([type, count]) => {
637
+ console.log(` ${type}: ${count}`);
638
+ });
639
+
640
+ // Failed requests
641
+ const failed = requests.filter(r => r.status >= 400);
642
+ if (failed.length > 0) {
643
+ console.log(`\n=== Failed Requests (${failed.length}) ===`);
644
+ failed.forEach(req => {
645
+ console.log(` ${req.status} ${req.method} ${req.url}`);
646
+ });
647
+ }
648
+
649
+ // Slow requests (if timing available)
650
+ const slow = requests.filter(r =>
651
+ r.timing && (r.timing.responseEnd - r.timing.startTime) > 1000
652
+ );
653
+ if (slow.length > 0) {
654
+ console.log(`\n=== Slow Requests (>1s, ${slow.length}) ===`);
655
+ slow.forEach(req => {
656
+ const duration = Math.round(r.timing.responseEnd - r.timing.startTime);
657
+ console.log(` ${duration}ms ${req.method} ${req.url}`);
658
+ });
659
+ }
660
+ ```
661
+
662
+ ### 3.4 Compare Snapshots
663
+
664
+ **Purpose:** Diff two snapshots to identify changes
665
+
666
+ **Usage:**
667
+ ```bash
668
+ node scripts/compare.js <snapshot-dir-1> <snapshot-dir-2>
669
+ ```
670
+
671
+ **File: `scripts/compare.js`**
672
+ ```javascript
673
+ #!/usr/bin/env node
674
+ const fs = require('fs');
675
+ const path = require('path');
676
+
677
+ const dir1 = process.argv[2];
678
+ const dir2 = process.argv[3];
679
+
680
+ if (!dir1 || !dir2) {
681
+ console.error('Usage: compare.js <snapshot-1> <snapshot-2>');
682
+ process.exit(1);
683
+ }
684
+
685
+ function loadSnapshot(dir) {
686
+ const loadJsonl = (file) => {
687
+ const filepath = path.join(dir, file);
688
+ if (!fs.existsSync(filepath)) return [];
689
+ return fs.readFileSync(filepath, 'utf8')
690
+ .split('\n')
691
+ .filter(Boolean)
692
+ .map(JSON.parse);
693
+ };
694
+
695
+ return {
696
+ a11y: JSON.parse(fs.readFileSync(path.join(dir, 'a11y.json'), 'utf8')),
697
+ console: loadJsonl('console.jsonl'),
698
+ network: loadJsonl('network.jsonl')
699
+ };
700
+ }
701
+
702
+ const snap1 = loadSnapshot(dir1);
703
+ const snap2 = loadSnapshot(dir2);
704
+
705
+ console.log('=== Snapshot Comparison ===\n');
706
+
707
+ // Compare console logs
708
+ const errors1 = snap1.console.filter(l => l.type === 'error').map(l => l.text);
709
+ const errors2 = snap2.console.filter(l => l.type === 'error').map(l => l.text);
710
+
711
+ const newErrors = errors2.filter(e => !errors1.includes(e));
712
+ const fixedErrors = errors1.filter(e => !errors2.includes(e));
713
+
714
+ console.log('Console Errors:');
715
+ if (newErrors.length > 0) {
716
+ console.log(` New (${newErrors.length}):`);
717
+ newErrors.forEach(e => console.log(` + ${e}`));
718
+ }
719
+ if (fixedErrors.length > 0) {
720
+ console.log(` Fixed (${fixedErrors.length}):`);
721
+ fixedErrors.forEach(e => console.log(` - ${e}`));
722
+ }
723
+ if (newErrors.length === 0 && fixedErrors.length === 0) {
724
+ console.log(' No changes');
725
+ }
726
+
727
+ // Compare network failures
728
+ const failed1 = snap1.network.filter(r => r.status >= 400).map(r => r.url);
729
+ const failed2 = snap2.network.filter(r => r.status >= 400).map(r => r.url);
730
+
731
+ const newFailures = failed2.filter(u => !failed1.includes(u));
732
+ const fixedFailures = failed1.filter(u => !failed2.includes(u));
733
+
734
+ console.log('\nNetwork Failures:');
735
+ if (newFailures.length > 0) {
736
+ console.log(` New (${newFailures.length}):`);
737
+ newFailures.forEach(u => console.log(` + ${u}`));
738
+ }
739
+ if (fixedFailures.length > 0) {
740
+ console.log(` Fixed (${fixedFailures.length}):`);
741
+ fixedFailures.forEach(u => console.log(` - ${u}`));
742
+ }
743
+ if (newFailures.length === 0 && fixedFailures.length === 0) {
744
+ console.log(' No changes');
745
+ }
746
+
747
+ // Compare a11y tree size (rough indicator of structural changes)
748
+ const a11ySize1 = JSON.stringify(snap1.a11y).length;
749
+ const a11ySize2 = JSON.stringify(snap2.a11y).length;
750
+ const sizeDiff = a11ySize2 - a11ySize1;
751
+
752
+ console.log('\nAccessibility Tree:');
753
+ console.log(` Before: ${a11ySize1} bytes`);
754
+ console.log(` After: ${a11ySize2} bytes`);
755
+ if (sizeDiff !== 0) {
756
+ console.log(` Change: ${sizeDiff > 0 ? '+' : ''}${sizeDiff} bytes`);
757
+ }
758
+ ```
759
+
760
+ ## 4. Code Mode (User Scripts)
761
+
762
+ The core runtime provides an execution environment for user-written scripts. This enables the agent to write custom debugging scripts for novel scenarios.
763
+
764
+ ### 4.1 Execution Model
765
+
766
+ **Command:**
767
+ ```bash
768
+ node scripts/pw.js exec <script.js> [args...]
769
+ ```
770
+
771
+ **Script Context:**
772
+ User scripts execute as async functions with access to:
773
+ - `page` - Playwright Page instance
774
+ - `context` - Playwright BrowserContext instance
775
+ - `browser` - Playwright Browser instance
776
+ - `args` - Array of command-line arguments
777
+ - `fs` - Node.js filesystem module
778
+ - `path` - Node.js path module
779
+ - `utils` - Helper utilities for writing data
780
+
781
+ **Utils Object:**
782
+ ```javascript
783
+ utils = {
784
+ writeJson: (filepath, data) => {
785
+ // Creates directories if needed, writes JSON with formatting
786
+ },
787
+ writeJsonl: (filepath, items) => {
788
+ // Writes array of objects as JSONL (one JSON object per line)
789
+ },
790
+ appendJsonl: (filepath, item) => {
791
+ // Appends single object to JSONL file
792
+ }
793
+ }
794
+ ```
795
+
796
+ ### 4.2 Script Template
797
+
798
+ ```javascript
799
+ // user-script.js
800
+ // Available context: page, context, browser, args, fs, path, utils
801
+
802
+ const url = args[0];
803
+ const output = args[1];
804
+
805
+ // Navigate to page
806
+ await page.goto(url);
807
+
808
+ // Extract data using Playwright API
809
+ const data = await page.evaluate(() => {
810
+ // Browser context - extract data from DOM
811
+ return {
812
+ // ... extracted data
813
+ };
814
+ });
815
+
816
+ // Write to disk
817
+ utils.writeJson(output, data);
818
+
819
+ console.log(`Results saved to ${output}`);
820
+ ```
821
+
822
+ ### 4.3 Example User Scripts
823
+
824
+ **Extract Form Data:**
825
+ ```javascript
826
+ // extract-forms.js
827
+ // Usage: node pw.js exec extract-forms.js <url> <output>
828
+
829
+ const url = args[0];
830
+ const output = args[1];
831
+
832
+ await page.goto(url, { waitUntil: 'networkidle' });
833
+
834
+ const forms = await page.evaluate(() => {
835
+ return Array.from(document.querySelectorAll('form')).map(form => ({
836
+ action: form.action,
837
+ method: form.method,
838
+ fields: Array.from(form.elements).map(el => ({
839
+ name: el.name,
840
+ type: el.type,
841
+ required: el.required,
842
+ value: el.type === 'password' ? '[REDACTED]' : el.value,
843
+ disabled: el.disabled
844
+ }))
845
+ }));
846
+ });
847
+
848
+ utils.writeJson(output, {
849
+ url,
850
+ timestamp: new Date().toISOString(),
851
+ forms
852
+ });
853
+
854
+ console.log(`Found ${forms.length} forms, saved to ${output}`);
855
+ ```
856
+
857
+ **Monitor Memory Over Time:**
858
+ ```javascript
859
+ // monitor-memory.js
860
+ // Usage: node pw.js exec monitor-memory.js <url> <duration-ms> <interval-ms> <output>
861
+
862
+ const url = args[0];
863
+ const duration = parseInt(args[1]) || 30000; // 30 seconds default
864
+ const interval = parseInt(args[2]) || 1000; // 1 second default
865
+ const output = args[3];
866
+
867
+ await page.goto(url);
868
+
869
+ const samples = [];
870
+ const startTime = Date.now();
871
+
872
+ console.log(`Monitoring memory for ${duration}ms...`);
873
+
874
+ while (Date.now() - startTime < duration) {
875
+ const metrics = await page.metrics();
876
+ samples.push({
877
+ timestamp: Date.now() - startTime,
878
+ jsHeapUsed: metrics.JSHeapUsedSize,
879
+ jsHeapTotal: metrics.JSHeapTotalSize,
880
+ documents: metrics.Documents,
881
+ frames: metrics.Frames
882
+ });
883
+
884
+ await page.waitForTimeout(interval);
885
+ }
886
+
887
+ utils.writeJsonl(output, samples);
888
+
889
+ console.log(`Collected ${samples.length} memory samples`);
890
+ console.log(`Heap growth: ${samples[samples.length-1].jsHeapUsed - samples[0].jsHeapUsed} bytes`);
891
+ ```
892
+
893
+ **Test Interaction Flow:**
894
+ ```javascript
895
+ // test-checkout.js
896
+ // Usage: node pw.js exec test-checkout.js <base-url> <output-dir>
897
+
898
+ const baseUrl = args[0];
899
+ const outputDir = args[1];
900
+
901
+ // Track errors during flow
902
+ const errors = [];
903
+ page.on('console', msg => {
904
+ if (msg.type() === 'error') {
905
+ errors.push({
906
+ timestamp: new Date().toISOString(),
907
+ text: msg.text()
908
+ });
909
+ }
910
+ });
911
+
912
+ // Step 1: Add item to cart
913
+ await page.goto(`${baseUrl}/products/123`);
914
+ const beforeCart = await page.accessibility.snapshot();
915
+ utils.writeJson(`${outputDir}/1-before-add.json`, beforeCart);
916
+
917
+ await page.click('button.add-to-cart');
918
+ await page.waitForTimeout(1000);
919
+
920
+ const afterCart = await page.accessibility.snapshot();
921
+ utils.writeJson(`${outputDir}/2-after-add.json`, afterCart);
922
+
923
+ // Step 2: Go to checkout
924
+ await page.click('a[href="/cart"]');
925
+ await page.waitForNavigation();
926
+
927
+ const cartPage = await page.accessibility.snapshot();
928
+ utils.writeJson(`${outputDir}/3-cart-page.json`, cartPage);
929
+
930
+ await page.click('button.checkout');
931
+ await page.waitForNavigation();
932
+
933
+ const checkoutPage = await page.accessibility.snapshot();
934
+ utils.writeJson(`${outputDir}/4-checkout-page.json`, checkoutPage);
935
+
936
+ // Save errors if any occurred
937
+ if (errors.length > 0) {
938
+ utils.writeJsonl(`${outputDir}/errors.jsonl`, errors);
939
+ console.log(`⚠️ ${errors.length} console errors during checkout flow`);
940
+ }
941
+
942
+ console.log('Checkout flow captured successfully');
943
+ ```
944
+
945
+ **Extract All Links:**
946
+ ```javascript
947
+ // extract-links.js
948
+ // Usage: node pw.js exec extract-links.js <url> <output>
949
+
950
+ const url = args[0];
951
+ const output = args[1];
952
+
953
+ await page.goto(url, { waitUntil: 'networkidle' });
954
+
955
+ const links = await page.evaluate(() => {
956
+ return Array.from(document.links).map(link => ({
957
+ href: link.href,
958
+ text: link.textContent.trim(),
959
+ target: link.target,
960
+ rel: link.rel
961
+ }));
962
+ });
963
+
964
+ // Test each link
965
+ const results = [];
966
+ for (const link of links) {
967
+ try {
968
+ const response = await page.request.head(link.href);
969
+ results.push({
970
+ ...link,
971
+ status: response.status(),
972
+ ok: response.ok()
973
+ });
974
+ } catch (err) {
975
+ results.push({
976
+ ...link,
977
+ error: err.message
978
+ });
979
+ }
980
+ }
981
+
982
+ utils.writeJson(output, {
983
+ url,
984
+ totalLinks: links.length,
985
+ brokenLinks: results.filter(r => !r.ok && !r.error).length,
986
+ errors: results.filter(r => r.error).length,
987
+ links: results
988
+ });
989
+
990
+ console.log(`Checked ${links.length} links`);
991
+ ```
992
+
993
+ ## 5. Project Structure
994
+
995
+ ```
996
+ uisnap/
997
+ ├── scripts/
998
+ │ ├── pw.js # Core runtime
999
+ │ ├── commands/ # Built-in commands
1000
+ │ │ ├── snapshot.js
1001
+ │ │ └── interact.js
1002
+ │ ├── query-a11y.js # Analysis: query a11y tree
1003
+ │ ├── analyze-console.js # Analysis: summarize console logs
1004
+ │ ├── analyze-network.js # Analysis: summarize network
1005
+ │ ├── compare.js # Analysis: diff snapshots
1006
+ │ └── examples/ # Example user scripts
1007
+ │ ├── extract-forms.js
1008
+ │ ├── monitor-memory.js
1009
+ │ ├── test-checkout.js
1010
+ │ └── extract-links.js
1011
+ ├── snapshots/ # Default output directory
1012
+ │ └── .gitkeep
1013
+ ├── scenarios/ # Interaction scenarios
1014
+ │ └── example-login.yml
1015
+ ├── package.json
1016
+ └── README.md
1017
+ ```
1018
+
1019
+ ## 6. Installation & Setup
1020
+
1021
+ **package.json:**
1022
+ ```json
1023
+ {
1024
+ "name": "uisnap",
1025
+ "version": "1.0.0",
1026
+ "description": "Efficient frontend debugging toolkit for Claude Code",
1027
+ "scripts": {
1028
+ "snapshot": "node scripts/pw.js snapshot",
1029
+ "interact": "node scripts/pw.js interact"
1030
+ },
1031
+ "dependencies": {
1032
+ "playwright": "^1.40.0",
1033
+ "yaml": "^2.3.4"
1034
+ }
1035
+ }
1036
+ ```
1037
+
1038
+ **Installation:**
1039
+ ```bash
1040
+ npm install
1041
+ npx playwright install chromium
1042
+ ```
1043
+
1044
+ ## 7. Usage Workflows
1045
+
1046
+ ### 7.1 Basic Debugging Workflow
1047
+
1048
+ ```bash
1049
+ # 1. Capture page state
1050
+ node scripts/pw.js snapshot "https://app.example.com" snapshots/current/
1051
+
1052
+ # 2. Analyze what was captured
1053
+ node scripts/analyze-console.js snapshots/current/console.jsonl
1054
+ node scripts/analyze-network.js snapshots/current/network.jsonl
1055
+
1056
+ # 3. Query accessibility tree for specific elements
1057
+ node scripts/query-a11y.js snapshots/current/a11y.json --buttons
1058
+ node scripts/query-a11y.js snapshots/current/a11y.json "Submit"
1059
+
1060
+ # 4. If needed, write custom script for deeper investigation
1061
+ node scripts/pw.js exec scripts/examples/extract-forms.js \
1062
+ "https://app.example.com" snapshots/forms.json
1063
+ ```
1064
+
1065
+ ### 7.2 Interaction Testing Workflow
1066
+
1067
+ ```bash
1068
+ # 1. Create scenario
1069
+ cat > scenarios/login-test.yml << EOF
1070
+ steps:
1071
+ - action: goto
1072
+ url: https://app.example.com/login
1073
+ - action: fill
1074
+ selector: input[name="email"]
1075
+ value: test@example.com
1076
+ - action: fill
1077
+ selector: input[name="password"]
1078
+ value: password123
1079
+ - action: click
1080
+ selector: button[type="submit"]
1081
+ wait: 2000
1082
+ EOF
1083
+
1084
+ # 2. Run interaction and capture state at each step
1085
+ node scripts/pw.js interact scenarios/login-test.yml snapshots/login-flow/
1086
+
1087
+ # 3. Analyze each step
1088
+ node scripts/analyze-console.js snapshots/login-flow/step-1-goto/console.jsonl
1089
+ node scripts/analyze-console.js snapshots/login-flow/step-4-click/console.jsonl
1090
+
1091
+ # 4. Compare before/after
1092
+ node scripts/compare.js \
1093
+ snapshots/login-flow/step-1-goto/ \
1094
+ snapshots/login-flow/step-4-click/
1095
+ ```
1096
+
1097
+ ### 7.3 Regression Testing Workflow
1098
+
1099
+ ```bash
1100
+ # 1. Capture baseline
1101
+ node scripts/pw.js snapshot "https://app.example.com" snapshots/baseline/
1102
+
1103
+ # 2. Make code changes
1104
+ # ... deploy new version ...
1105
+
1106
+ # 3. Capture after changes
1107
+ node scripts/pw.js snapshot "https://app.example.com" snapshots/after-deploy/
1108
+
1109
+ # 4. Compare
1110
+ node scripts/compare.js snapshots/baseline/ snapshots/after-deploy/
1111
+ ```
1112
+
1113
+ ### 7.4 Custom Investigation Workflow
1114
+
1115
+ ```bash
1116
+ # Agent identifies need for custom data extraction
1117
+ # Writes a script:
1118
+
1119
+ cat > scripts/check-memory-leak.js << 'EOF'
1120
+ const url = args[0];
1121
+ const output = args[1];
1122
+
1123
+ await page.goto(url);
1124
+
1125
+ const samples = [];
1126
+ for (let i = 0; i < 20; i++) {
1127
+ await page.click('.trigger-action');
1128
+ await page.waitForTimeout(500);
1129
+
1130
+ const metrics = await page.metrics();
1131
+ samples.push({
1132
+ iteration: i,
1133
+ heapUsed: metrics.JSHeapUsedSize
1134
+ });
1135
+ }
1136
+
1137
+ utils.writeJsonl(output, samples);
1138
+
1139
+ const growth = samples[19].heapUsed - samples[0].heapUsed;
1140
+ console.log(`Heap grew by ${growth} bytes over 20 iterations`);
1141
+ EOF
1142
+
1143
+ # Executes it:
1144
+ node scripts/pw.js exec scripts/check-memory-leak.js \
1145
+ "https://app.example.com" snapshots/memory-test.jsonl
1146
+
1147
+ # Analyzes results:
1148
+ jq -r '[.iteration, .heapUsed] | @tsv' snapshots/memory-test.jsonl
1149
+ ```
1150
+
1151
+ ## 8. Design Principles
1152
+
1153
+ ### 8.1 Capture Once, Query Many
1154
+ - Expensive browser operations write to disk once
1155
+ - Cheap local queries can analyze data repeatedly
1156
+ - Agent avoids re-running browser for each question
1157
+
1158
+ ### 8.2 Structured Output
1159
+ - All data is JSON or JSONL
1160
+ - Can be queried with standard tools (jq, grep, awk)
1161
+ - Can be imported into analysis tools (DuckDB, Python, etc.)
1162
+
1163
+ ### 8.3 Composability
1164
+ - Built-in commands handle common cases
1165
+ - User scripts handle novel scenarios
1166
+ - Analysis tools work on any captured data
1167
+ - Scripts can be chained together
1168
+
1169
+ ### 8.4 Disk-First
1170
+ - Everything goes to files, not stdout (unless it's a summary)
1171
+ - Enables post-hoc analysis
1172
+ - Files can be version controlled
1173
+ - Results persist across sessions
1174
+
1175
+ ### 8.5 Progressive Disclosure
1176
+ - Start with summaries (analyze-*.js scripts)
1177
+ - Drill into details only when needed
1178
+ - Query locally before running browser again
1179
+
1180
+ ## 9. Token Efficiency
1181
+
1182
+ **Problem:** Browser debugging traditionally expensive
1183
+ - Full DOM: ~15,000 tokens
1184
+ - All console logs: ~8,000 tokens
1185
+ - Screenshots: ~10,000 tokens
1186
+ - Total for basic debugging: ~40,000 tokens
1187
+
1188
+ **Solution:** This toolkit's approach
1189
+ - Capture to disk: ~2,000 tokens (one-time)
1190
+ - Analyze console summary: ~100 tokens
1191
+ - Query a11y tree: ~50 tokens
1192
+ - Read filtered results: ~200 tokens
1193
+ - Total for same debugging: ~2,350 tokens
1194
+
1195
+ **~17x reduction in token usage**
1196
+
1197
+ ## 10. Extension Points
1198
+
1199
+ The toolkit is designed to be extended:
1200
+
1201
+ ### 10.1 New Capture Commands
1202
+ Add to `scripts/commands/`:
1203
+ - `performance.js` - Capture Core Web Vitals
1204
+ - `lighthouse.js` - Run Lighthouse audit
1205
+ - `coverage.js` - Capture code coverage
1206
+
1207
+ ### 10.2 New Analysis Scripts
1208
+ Add analysis tools:
1209
+ - `analyze-performance.js` - Analyze perf metrics
1210
+ - `find-unused-css.js` - Analyze coverage data
1211
+ - `extract-schema.js` - Extract structured data markup
1212
+
1213
+ ### 10.3 Integration Hooks
1214
+ Scripts can integrate with external tools:
1215
+ - Post results to monitoring systems
1216
+ - Generate reports
1217
+ - Trigger alerts
1218
+ - Update dashboards
1219
+
1220
+ ## 11. Skill Documentation
1221
+
1222
+ The toolkit should be accompanied by a Claude Code skill that teaches the agent:
1223
+ - When to use built-in commands vs. custom scripts
1224
+ - How to structure user scripts
1225
+ - Common debugging patterns
1226
+ - Token-efficient workflows
1227
+
1228
+ **Skill structure:**
1229
+ ```markdown
1230
+ ---
1231
+ name: uisnap
1232
+ description: Efficient frontend debugging with Playwright. Captures browser state to disk, enables local querying. Use when debugging web apps, investigating errors, or analyzing interactions.
1233
+ ---
1234
+
1235
+ # uisnap
1236
+
1237
+ [Documentation of commands, workflows, examples]
1238
+ ```
1239
+
1240
+ ## 12. Success Criteria
1241
+
1242
+ The toolkit is successful if:
1243
+
1244
+ 1. **Token Efficient**: >10x reduction in tokens for common debugging tasks
1245
+ 2. **Flexible**: Agent can write custom scripts for novel scenarios
1246
+ 3. **Composable**: Scripts and commands work together naturally
1247
+ 4. **Discoverable**: Built-in commands cover 80% of use cases
1248
+ 5. **Extensible**: Easy to add new commands and analysis tools
1249
+
1250
+ ## 13. Future Enhancements
1251
+
1252
+ Potential future additions:
1253
+
1254
+ - **Visual regression**: Screenshot comparison with diff highlighting
1255
+ - **Performance tracking**: Time-series metrics capture
1256
+ - **Batch testing**: Run multiple scenarios in parallel
1257
+ - **Report generation**: HTML reports from captured data
1258
+ - **CI/CD integration**: Run in automated pipelines
1259
+ - **Real-device testing**: Mobile device emulation
1260
+ - **Network throttling**: Simulate slow connections
1261
+ - **Accessibility auditing**: Run aXe or similar tools