cursorflow 2.6.3__tar.gz → 2.7.2__tar.gz
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.
- {cursorflow-2.6.3 → cursorflow-2.7.2}/PKG-INFO +59 -1
- {cursorflow-2.6.3 → cursorflow-2.7.2}/README.md +58 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/cli.py +364 -24
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/cursorflow.py +8 -1
- cursorflow-2.7.2/cursorflow/core/data_presenter.py +518 -0
- cursorflow-2.7.2/cursorflow/core/output_manager.py +639 -0
- cursorflow-2.7.2/cursorflow/core/query_engine.py +1342 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/rules/cursorflow-usage.mdc +245 -6
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow.egg-info/SOURCES.txt +3 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/docs/user/USAGE_GUIDE.md +306 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/pyproject.toml +1 -1
- {cursorflow-2.6.3 → cursorflow-2.7.2}/LICENSE +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/MANIFEST.in +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/__init__.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/auto_init.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/auto_updater.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/action_validator.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/agent.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/auth_handler.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/browser_controller.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/browser_engine.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/config_validator.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/css_iterator.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/cursor_integration.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/error_context_collector.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/error_correlator.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/event_correlator.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/file_change_monitor.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/hmr_detector.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/json_utils.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/log_collector.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/log_monitor.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/mockup_comparator.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/persistent_session.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/report_generator.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/core/trace_manager.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/install_cursorflow_rules.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/log_sources/local_file.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/log_sources/ssh_remote.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/post_install.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/rules/__init__.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/rules/cursorflow-installation.mdc +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/cursorflow/updater.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/comprehensive_screenshot_example.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/element_inspection_example.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/element_measurement_example.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/enhanced_screenshot_example.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/hot_reload_css_iteration.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/mockup_comparison_example.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/opensas_example.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/react_example.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/responsive_testing_example.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/examples/v2_comprehensive_demo.py +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/setup.cfg +0 -0
- {cursorflow-2.6.3 → cursorflow-2.7.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cursorflow
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.7.2
|
4
4
|
Summary: 🔥 Complete page intelligence for AI-driven development with Hot Reload Intelligence - captures DOM, network, console, performance, HMR events, and comprehensive page analysis
|
5
5
|
Author-email: GeekWarrior Development <rbush@cooltheory.com>
|
6
6
|
License-Expression: MIT
|
@@ -198,6 +198,64 @@ All data structured for AI consumption:
|
|
198
198
|
- Error correlation with **smart screenshot deduplication**
|
199
199
|
- Performance insights with **reliability metadata**
|
200
200
|
|
201
|
+
## ✨ What's New in v2.7.1
|
202
|
+
|
203
|
+
**AI-Optimized Output with Complete Data Type Coverage** - All CursorFlow features now organized for AI consumption:
|
204
|
+
|
205
|
+
- **Multi-File Structure**: Split 3.7M token JSON → organized files under 100K tokens each
|
206
|
+
- **Complete Coverage**: Browser console, server logs, network, DOM, screenshots - all queryable
|
207
|
+
- **AI Data Digest**: Auto-generated markdown summary with server logs included
|
208
|
+
- **Query Interface**: Fast data extraction for ALL data types without manual JSON parsing
|
209
|
+
- **Session Comparison**: Compare test results to detect regressions
|
210
|
+
- **Export Formats**: JSON, Markdown, CSV outputs for any use case
|
211
|
+
|
212
|
+
```bash
|
213
|
+
# Test generates AI-optimized output automatically
|
214
|
+
cursorflow test --base-url http://localhost:3000 --path /app --logs local
|
215
|
+
|
216
|
+
# Query ANY data type instantly
|
217
|
+
cursorflow query session_123 --errors # Console errors
|
218
|
+
cursorflow query session_123 --server-logs # Server logs
|
219
|
+
cursorflow query session_123 --network --failed # Network failures
|
220
|
+
cursorflow query session_123 --screenshots # Screenshot index
|
221
|
+
cursorflow query session_123 --responsive # Responsive results
|
222
|
+
cursorflow query session_123 --css-iterations # CSS iterations
|
223
|
+
|
224
|
+
# Enhanced filtering
|
225
|
+
cursorflow query session_123 --errors --from-file "app.js" --contains "undefined"
|
226
|
+
cursorflow query session_123 --network --url-contains "/api/" --over 500ms
|
227
|
+
cursorflow query session_123 --server-logs --level error --pattern "database"
|
228
|
+
|
229
|
+
# Cross-referencing (time-based correlation)
|
230
|
+
cursorflow query session_123 --errors --with-network --with-server-logs
|
231
|
+
|
232
|
+
# Contextual queries
|
233
|
+
cursorflow query session_123 --context-for-error 2 --window 10
|
234
|
+
cursorflow query session_123 --group-by-url "/api/users"
|
235
|
+
|
236
|
+
# Enhanced comparison
|
237
|
+
cursorflow query session_A --compare-with session_B --errors
|
238
|
+
```
|
239
|
+
|
240
|
+
**Impact:**
|
241
|
+
- ✅ 100% AI-readable results (vs 0% before - exceeded context limits)
|
242
|
+
- ✅ 90% faster error diagnosis with AI assistants
|
243
|
+
- ✅ Zero manual JSON parsing required
|
244
|
+
- ✅ Complete backend + frontend visibility (server logs included)
|
245
|
+
|
246
|
+
**Complete Data Coverage:**
|
247
|
+
```
|
248
|
+
.cursorflow/artifacts/sessions/session_XXX/
|
249
|
+
├── server_logs.json # ⭐ Server-side logs (SSH/local/Docker)
|
250
|
+
├── errors.json # Browser console errors
|
251
|
+
├── network.json # Network requests/responses
|
252
|
+
├── screenshots.json # ⭐ Screenshot metadata
|
253
|
+
├── data_digest.md # AI summary (includes server logs)
|
254
|
+
└── + 6 more data files covering all CursorFlow features
|
255
|
+
```
|
256
|
+
|
257
|
+
---
|
258
|
+
|
201
259
|
## 🚀 Quick Start
|
202
260
|
|
203
261
|
### Step 1: Install CursorFlow Package
|
@@ -153,6 +153,64 @@ All data structured for AI consumption:
|
|
153
153
|
- Error correlation with **smart screenshot deduplication**
|
154
154
|
- Performance insights with **reliability metadata**
|
155
155
|
|
156
|
+
## ✨ What's New in v2.7.1
|
157
|
+
|
158
|
+
**AI-Optimized Output with Complete Data Type Coverage** - All CursorFlow features now organized for AI consumption:
|
159
|
+
|
160
|
+
- **Multi-File Structure**: Split 3.7M token JSON → organized files under 100K tokens each
|
161
|
+
- **Complete Coverage**: Browser console, server logs, network, DOM, screenshots - all queryable
|
162
|
+
- **AI Data Digest**: Auto-generated markdown summary with server logs included
|
163
|
+
- **Query Interface**: Fast data extraction for ALL data types without manual JSON parsing
|
164
|
+
- **Session Comparison**: Compare test results to detect regressions
|
165
|
+
- **Export Formats**: JSON, Markdown, CSV outputs for any use case
|
166
|
+
|
167
|
+
```bash
|
168
|
+
# Test generates AI-optimized output automatically
|
169
|
+
cursorflow test --base-url http://localhost:3000 --path /app --logs local
|
170
|
+
|
171
|
+
# Query ANY data type instantly
|
172
|
+
cursorflow query session_123 --errors # Console errors
|
173
|
+
cursorflow query session_123 --server-logs # Server logs
|
174
|
+
cursorflow query session_123 --network --failed # Network failures
|
175
|
+
cursorflow query session_123 --screenshots # Screenshot index
|
176
|
+
cursorflow query session_123 --responsive # Responsive results
|
177
|
+
cursorflow query session_123 --css-iterations # CSS iterations
|
178
|
+
|
179
|
+
# Enhanced filtering
|
180
|
+
cursorflow query session_123 --errors --from-file "app.js" --contains "undefined"
|
181
|
+
cursorflow query session_123 --network --url-contains "/api/" --over 500ms
|
182
|
+
cursorflow query session_123 --server-logs --level error --pattern "database"
|
183
|
+
|
184
|
+
# Cross-referencing (time-based correlation)
|
185
|
+
cursorflow query session_123 --errors --with-network --with-server-logs
|
186
|
+
|
187
|
+
# Contextual queries
|
188
|
+
cursorflow query session_123 --context-for-error 2 --window 10
|
189
|
+
cursorflow query session_123 --group-by-url "/api/users"
|
190
|
+
|
191
|
+
# Enhanced comparison
|
192
|
+
cursorflow query session_A --compare-with session_B --errors
|
193
|
+
```
|
194
|
+
|
195
|
+
**Impact:**
|
196
|
+
- ✅ 100% AI-readable results (vs 0% before - exceeded context limits)
|
197
|
+
- ✅ 90% faster error diagnosis with AI assistants
|
198
|
+
- ✅ Zero manual JSON parsing required
|
199
|
+
- ✅ Complete backend + frontend visibility (server logs included)
|
200
|
+
|
201
|
+
**Complete Data Coverage:**
|
202
|
+
```
|
203
|
+
.cursorflow/artifacts/sessions/session_XXX/
|
204
|
+
├── server_logs.json # ⭐ Server-side logs (SSH/local/Docker)
|
205
|
+
├── errors.json # Browser console errors
|
206
|
+
├── network.json # Network requests/responses
|
207
|
+
├── screenshots.json # ⭐ Screenshot metadata
|
208
|
+
├── data_digest.md # AI summary (includes server logs)
|
209
|
+
└── + 6 more data files covering all CursorFlow features
|
210
|
+
```
|
211
|
+
|
212
|
+
---
|
213
|
+
|
156
214
|
## 🚀 Quick Start
|
157
215
|
|
158
216
|
### Step 1: Install CursorFlow Package
|
@@ -18,6 +18,9 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
18
18
|
from rich.markup import escape
|
19
19
|
|
20
20
|
from .core.agent import TestAgent
|
21
|
+
from .core.output_manager import OutputManager
|
22
|
+
from .core.data_presenter import DataPresenter
|
23
|
+
from .core.query_engine import QueryEngine
|
21
24
|
from . import __version__
|
22
25
|
|
23
26
|
console = Console()
|
@@ -293,20 +296,35 @@ def test(base_url, path, actions, output, logs, config, verbose, headless, timeo
|
|
293
296
|
if timeline:
|
294
297
|
console.print(f"⏰ Timeline events: {len(timeline)}")
|
295
298
|
|
296
|
-
# Save results
|
297
|
-
|
298
|
-
|
299
|
-
session_id = results.get('session_id', 'unknown')
|
300
|
-
path_part = path.replace('/', '_') if path else 'root'
|
301
|
-
|
302
|
-
# Ensure .cursorflow/artifacts directory exists
|
303
|
-
artifacts_dir = Path('.cursorflow/artifacts')
|
304
|
-
artifacts_dir.mkdir(parents=True, exist_ok=True)
|
305
|
-
|
306
|
-
output = artifacts_dir / f"cursorflow_{path_part}_{session_id}.json"
|
299
|
+
# Save results in structured multi-file format
|
300
|
+
session_id = results.get('session_id', 'unknown')
|
301
|
+
test_desc = path if path else 'test'
|
307
302
|
|
308
|
-
|
309
|
-
|
303
|
+
# Use output manager to save structured results
|
304
|
+
output_mgr = OutputManager()
|
305
|
+
file_paths = output_mgr.save_structured_results(
|
306
|
+
results,
|
307
|
+
session_id,
|
308
|
+
test_desc
|
309
|
+
)
|
310
|
+
|
311
|
+
# Generate AI-optimized data digest
|
312
|
+
session_dir = output_mgr.get_session_path(session_id)
|
313
|
+
data_pres = DataPresenter()
|
314
|
+
digest_content = data_pres.generate_data_digest(session_dir, results)
|
315
|
+
|
316
|
+
digest_path = session_dir / "data_digest.md"
|
317
|
+
with open(digest_path, 'w', encoding='utf-8') as f:
|
318
|
+
f.write(digest_content)
|
319
|
+
file_paths['data_digest'] = str(digest_path)
|
320
|
+
|
321
|
+
if not quiet:
|
322
|
+
console.print(f"\n📁 [bold green]Results saved to:[/bold green] [cyan]{session_dir}[/cyan]")
|
323
|
+
console.print(f"📄 [bold]AI Summary:[/bold] [cyan]{digest_path}[/cyan]")
|
324
|
+
console.print(f"\n💡 [dim]Quick commands:[/dim]")
|
325
|
+
console.print(f" cursorflow query {session_id} --errors")
|
326
|
+
console.print(f" cursorflow query {session_id} --network")
|
327
|
+
console.print(f" cat {digest_path}")
|
310
328
|
|
311
329
|
# Save command for rerun (Phase 3.3)
|
312
330
|
last_test_data = {
|
@@ -346,6 +364,320 @@ def test(base_url, path, actions, output, logs, config, verbose, headless, timeo
|
|
346
364
|
console.print(traceback.format_exc())
|
347
365
|
raise
|
348
366
|
|
367
|
+
|
368
|
+
@main.command()
|
369
|
+
@click.argument('session_id', required=False)
|
370
|
+
@click.option('--errors', is_flag=True, help='Query error data')
|
371
|
+
@click.option('--network', is_flag=True, help='Query network requests')
|
372
|
+
@click.option('--console', 'console_opt', is_flag=True, help='Query console messages')
|
373
|
+
@click.option('--performance', is_flag=True, help='Query performance metrics')
|
374
|
+
@click.option('--summary', is_flag=True, help='Query summary data')
|
375
|
+
@click.option('--dom', is_flag=True, help='Query DOM analysis')
|
376
|
+
@click.option('--server-logs', is_flag=True, help='Query server logs')
|
377
|
+
@click.option('--screenshots', is_flag=True, help='Query screenshot metadata')
|
378
|
+
@click.option('--mockup', is_flag=True, help='Query mockup comparison results')
|
379
|
+
@click.option('--responsive', is_flag=True, help='Query responsive testing results')
|
380
|
+
@click.option('--css-iterations', is_flag=True, help='Query CSS iteration results')
|
381
|
+
@click.option('--timeline', is_flag=True, help='Query timeline events')
|
382
|
+
@click.option('--severity', type=str, help='Filter errors by severity (critical)')
|
383
|
+
@click.option('--level', type=str, help='Filter server logs by level (error,warning,info)')
|
384
|
+
@click.option('--status', type=str, help='Filter network by status codes (404,500 or 4xx,5xx)')
|
385
|
+
@click.option('--failed', is_flag=True, help='Show only failed network requests')
|
386
|
+
@click.option('--type', type=str, help='Filter console by type (error,warning,log,info)')
|
387
|
+
@click.option('--selector', type=str, help='Filter DOM by CSS selector')
|
388
|
+
@click.option('--source', type=str, help='Filter server logs by source (ssh,local,docker,systemd)')
|
389
|
+
@click.option('--file', type=str, help='Filter server logs by file path')
|
390
|
+
@click.option('--pattern', type=str, help='Filter by content pattern')
|
391
|
+
@click.option('--contains', type=str, help='Filter by content substring')
|
392
|
+
@click.option('--matches', type=str, help='Filter by regex pattern')
|
393
|
+
@click.option('--from-file', type=str, help='Filter errors by source file')
|
394
|
+
@click.option('--from-pattern', type=str, help='Filter errors by file pattern (*.js, *.ts)')
|
395
|
+
@click.option('--url-contains', type=str, help='Filter network by URL substring')
|
396
|
+
@click.option('--url-matches', type=str, help='Filter network by URL regex')
|
397
|
+
@click.option('--over', type=str, help='Filter network requests over timing threshold (500ms)')
|
398
|
+
@click.option('--method', type=str, help='Filter network by HTTP method (GET,POST)')
|
399
|
+
@click.option('--visible', is_flag=True, help='Filter DOM to visible elements only')
|
400
|
+
@click.option('--interactive', is_flag=True, help='Filter DOM to interactive elements only')
|
401
|
+
@click.option('--role', type=str, help='Filter DOM by ARIA role')
|
402
|
+
@click.option('--with-attr', type=str, help='Filter DOM by attribute name')
|
403
|
+
@click.option('--with-network', is_flag=True, help='Include related network requests (cross-ref)')
|
404
|
+
@click.option('--with-console', is_flag=True, help='Include related console messages (cross-ref)')
|
405
|
+
@click.option('--with-server-logs', is_flag=True, help='Include related server logs (cross-ref)')
|
406
|
+
@click.option('--context-for-error', type=int, help='Get full context for error by index')
|
407
|
+
@click.option('--group-by-url', type=str, help='Group all data by URL pattern')
|
408
|
+
@click.option('--group-by-selector', type=str, help='Group all data by DOM selector')
|
409
|
+
@click.option('--viewport', type=str, help='Filter responsive results by viewport (mobile,tablet,desktop)')
|
410
|
+
@click.option('--iteration', type=int, help='Filter by specific iteration number')
|
411
|
+
@click.option('--with-errors', is_flag=True, help='Filter screenshots/iterations with errors only')
|
412
|
+
@click.option('--around', type=float, help='Query timeline events around timestamp')
|
413
|
+
@click.option('--window', type=float, default=5.0, help='Time window in seconds (default: 5)')
|
414
|
+
@click.option('--export', type=click.Choice(['json', 'markdown', 'csv']),
|
415
|
+
default='json', help='Export format')
|
416
|
+
@click.option('--compare-with', type=str, help='Compare with another session')
|
417
|
+
@click.option('--list', 'list_sessions', is_flag=True, help='List recent sessions')
|
418
|
+
@click.option('--verbose', '-v', is_flag=True, help='Verbose output')
|
419
|
+
def query(session_id, errors, network, console_opt, performance, summary, dom,
|
420
|
+
server_logs, screenshots, mockup, responsive, css_iterations, timeline,
|
421
|
+
severity, level, status, failed, type, selector, source, file, pattern,
|
422
|
+
contains, matches, from_file, from_pattern, url_contains, url_matches, over, method,
|
423
|
+
visible, interactive, role, with_attr, with_network, with_console, with_server_logs,
|
424
|
+
context_for_error, group_by_url, group_by_selector,
|
425
|
+
viewport, iteration, with_errors, around, window,
|
426
|
+
export, compare_with, list_sessions, verbose):
|
427
|
+
"""
|
428
|
+
Query and filter CursorFlow test results
|
429
|
+
|
430
|
+
Examples:
|
431
|
+
|
432
|
+
# List recent sessions
|
433
|
+
cursorflow query --list
|
434
|
+
|
435
|
+
# Query errors from a session
|
436
|
+
cursorflow query session_123 --errors
|
437
|
+
|
438
|
+
# Query server logs
|
439
|
+
cursorflow query session_123 --server-logs --severity error
|
440
|
+
|
441
|
+
# Query network failures
|
442
|
+
cursorflow query session_123 --network --failed
|
443
|
+
|
444
|
+
# Query responsive results
|
445
|
+
cursorflow query session_123 --responsive --viewport mobile
|
446
|
+
|
447
|
+
# Export in different formats
|
448
|
+
cursorflow query session_123 --errors --export markdown
|
449
|
+
|
450
|
+
# Compare two sessions
|
451
|
+
cursorflow query session_123 --compare-with session_456
|
452
|
+
"""
|
453
|
+
|
454
|
+
engine = QueryEngine()
|
455
|
+
|
456
|
+
# List sessions mode
|
457
|
+
if list_sessions:
|
458
|
+
_list_sessions_func(engine)
|
459
|
+
return
|
460
|
+
|
461
|
+
if not session_id:
|
462
|
+
console.print("[yellow]⚠️ Provide a session_id or use --list to see available sessions[/yellow]")
|
463
|
+
console.print("Example: [cyan]cursorflow query session_123 --errors[/cyan]")
|
464
|
+
return
|
465
|
+
|
466
|
+
# Comparison mode
|
467
|
+
if compare_with:
|
468
|
+
_compare_sessions_func(engine, session_id, compare_with, errors, network, performance)
|
469
|
+
return
|
470
|
+
|
471
|
+
# Determine query type
|
472
|
+
query_type = None
|
473
|
+
if errors:
|
474
|
+
query_type = 'errors'
|
475
|
+
elif network:
|
476
|
+
query_type = 'network'
|
477
|
+
elif console_opt:
|
478
|
+
query_type = 'console'
|
479
|
+
elif performance:
|
480
|
+
query_type = 'performance'
|
481
|
+
elif summary:
|
482
|
+
query_type = 'summary'
|
483
|
+
elif dom:
|
484
|
+
query_type = 'dom'
|
485
|
+
elif server_logs:
|
486
|
+
query_type = 'server_logs'
|
487
|
+
elif screenshots:
|
488
|
+
query_type = 'screenshots'
|
489
|
+
elif mockup:
|
490
|
+
query_type = 'mockup'
|
491
|
+
elif responsive:
|
492
|
+
query_type = 'responsive'
|
493
|
+
elif css_iterations:
|
494
|
+
query_type = 'css_iterations'
|
495
|
+
elif timeline:
|
496
|
+
query_type = 'timeline'
|
497
|
+
|
498
|
+
# Build filters
|
499
|
+
filters = {}
|
500
|
+
if severity:
|
501
|
+
filters['severity'] = severity
|
502
|
+
if level:
|
503
|
+
filters['level'] = level
|
504
|
+
if status:
|
505
|
+
filters['status'] = status
|
506
|
+
if failed:
|
507
|
+
filters['failed'] = True
|
508
|
+
if type:
|
509
|
+
filters['type'] = type
|
510
|
+
if selector:
|
511
|
+
filters['selector'] = selector
|
512
|
+
if source:
|
513
|
+
filters['source'] = source
|
514
|
+
if file:
|
515
|
+
filters['file'] = file
|
516
|
+
if pattern:
|
517
|
+
filters['pattern'] = pattern
|
518
|
+
if contains:
|
519
|
+
filters['contains'] = contains
|
520
|
+
if matches:
|
521
|
+
filters['matches'] = matches
|
522
|
+
if from_file:
|
523
|
+
filters['from_file'] = from_file
|
524
|
+
if from_pattern:
|
525
|
+
filters['from_pattern'] = from_pattern
|
526
|
+
if url_contains:
|
527
|
+
filters['url_contains'] = url_contains
|
528
|
+
if url_matches:
|
529
|
+
filters['url_matches'] = url_matches
|
530
|
+
if over:
|
531
|
+
filters['over'] = over
|
532
|
+
if method:
|
533
|
+
filters['method'] = method
|
534
|
+
if visible:
|
535
|
+
filters['visible'] = True
|
536
|
+
if interactive:
|
537
|
+
filters['interactive'] = True
|
538
|
+
if role:
|
539
|
+
filters['role'] = role
|
540
|
+
if with_attr:
|
541
|
+
filters['with_attr'] = with_attr
|
542
|
+
if with_network:
|
543
|
+
filters['with_network'] = True
|
544
|
+
if with_console:
|
545
|
+
filters['with_console'] = True
|
546
|
+
if with_server_logs:
|
547
|
+
filters['with_server_logs'] = True
|
548
|
+
if context_for_error is not None:
|
549
|
+
filters['context_for_error'] = context_for_error
|
550
|
+
if group_by_url:
|
551
|
+
filters['group_by_url'] = group_by_url
|
552
|
+
if group_by_selector:
|
553
|
+
filters['group_by_selector'] = group_by_selector
|
554
|
+
if viewport:
|
555
|
+
filters['viewport'] = viewport
|
556
|
+
if iteration:
|
557
|
+
filters['iteration'] = iteration
|
558
|
+
if with_errors:
|
559
|
+
filters['with_errors'] = True
|
560
|
+
if around:
|
561
|
+
filters['around'] = around
|
562
|
+
filters['window'] = window
|
563
|
+
|
564
|
+
try:
|
565
|
+
# Execute query
|
566
|
+
result = engine.query_session(session_id, query_type, filters, export)
|
567
|
+
|
568
|
+
# Display results
|
569
|
+
if export == 'json':
|
570
|
+
if verbose:
|
571
|
+
console.print(result)
|
572
|
+
else:
|
573
|
+
# Pretty print JSON
|
574
|
+
data = json.loads(result)
|
575
|
+
console.print_json(data=data)
|
576
|
+
else:
|
577
|
+
console.print(result)
|
578
|
+
|
579
|
+
except ValueError as e:
|
580
|
+
console.print(f"[red]Error:[/red] {e}")
|
581
|
+
console.print(f"\n💡 [dim]Tip:[/dim] List available sessions with: [cyan]cursorflow query --list[/cyan]")
|
582
|
+
except Exception as e:
|
583
|
+
console.print(f"[red]Error querying session:[/red] {e}")
|
584
|
+
if verbose:
|
585
|
+
import traceback
|
586
|
+
console.print(traceback.format_exc())
|
587
|
+
|
588
|
+
|
589
|
+
def _list_sessions_func(engine: QueryEngine):
|
590
|
+
"""List recent sessions"""
|
591
|
+
sessions = engine.list_sessions(limit=10)
|
592
|
+
|
593
|
+
if not sessions:
|
594
|
+
console.print("[yellow]No sessions found[/yellow]")
|
595
|
+
console.print("Run a test first: [cyan]cursorflow test --base-url http://localhost:3000 --path /[/cyan]")
|
596
|
+
return
|
597
|
+
|
598
|
+
table = Table(title="Recent Test Sessions", box=box.ROUNDED)
|
599
|
+
table.add_column("Session ID", style="cyan")
|
600
|
+
table.add_column("Timestamp", style="white")
|
601
|
+
table.add_column("Status", style="green")
|
602
|
+
table.add_column("Errors", style="red")
|
603
|
+
table.add_column("Network Failures", style="yellow")
|
604
|
+
|
605
|
+
for session in sessions:
|
606
|
+
status_emoji = "✅" if session['success'] else "⚠️"
|
607
|
+
table.add_row(
|
608
|
+
session['session_id'],
|
609
|
+
session['timestamp'],
|
610
|
+
status_emoji,
|
611
|
+
str(session['errors']),
|
612
|
+
str(session['network_failures'])
|
613
|
+
)
|
614
|
+
|
615
|
+
console.print(table)
|
616
|
+
console.print(f"\n💡 [dim]Query a session:[/dim] [cyan]cursorflow query <session_id> --errors[/cyan]")
|
617
|
+
|
618
|
+
|
619
|
+
def _compare_sessions_func(engine: QueryEngine, session_a: str, session_b: str,
|
620
|
+
errors: bool, network: bool, performance: bool):
|
621
|
+
"""Compare two sessions"""
|
622
|
+
try:
|
623
|
+
query_type = None
|
624
|
+
if errors:
|
625
|
+
query_type = 'errors'
|
626
|
+
elif network:
|
627
|
+
query_type = 'network'
|
628
|
+
elif performance:
|
629
|
+
query_type = 'performance'
|
630
|
+
|
631
|
+
comparison = engine.compare_sessions(session_a, session_b, query_type)
|
632
|
+
|
633
|
+
console.print(f"\n[bold]Comparing Sessions:[/bold]")
|
634
|
+
console.print(f" Session A: [cyan]{session_a}[/cyan]")
|
635
|
+
console.print(f" Session B: [cyan]{session_b}[/cyan]")
|
636
|
+
console.print()
|
637
|
+
|
638
|
+
# Display summary comparison
|
639
|
+
summary_diff = comparison.get('summary_diff', {})
|
640
|
+
|
641
|
+
table = Table(title="Summary Comparison", box=box.ROUNDED)
|
642
|
+
table.add_column("Metric", style="white")
|
643
|
+
table.add_column("Session A", style="cyan")
|
644
|
+
table.add_column("Session B", style="cyan")
|
645
|
+
table.add_column("Difference", style="yellow")
|
646
|
+
|
647
|
+
for metric, values in summary_diff.items():
|
648
|
+
diff = values.get('difference', 0)
|
649
|
+
diff_str = f"+{diff}" if diff > 0 else str(diff)
|
650
|
+
table.add_row(
|
651
|
+
metric.replace('_', ' ').title(),
|
652
|
+
str(values.get('session_a', 0)),
|
653
|
+
str(values.get('session_b', 0)),
|
654
|
+
diff_str
|
655
|
+
)
|
656
|
+
|
657
|
+
console.print(table)
|
658
|
+
|
659
|
+
# Display specific comparison if requested
|
660
|
+
if errors and 'errors_diff' in comparison:
|
661
|
+
console.print(f"\n[bold]Errors Comparison:[/bold]")
|
662
|
+
errors_diff = comparison['errors_diff']
|
663
|
+
console.print(f" New errors: [red]{errors_diff.get('new_errors', 0)}[/red]")
|
664
|
+
|
665
|
+
if network and 'network_diff' in comparison:
|
666
|
+
console.print(f"\n[bold]Network Comparison:[/bold]")
|
667
|
+
network_diff = comparison['network_diff']
|
668
|
+
console.print(f" Success rate A: {network_diff.get('success_rate_a', 0):.1f}%")
|
669
|
+
console.print(f" Success rate B: {network_diff.get('success_rate_b', 0):.1f}%")
|
670
|
+
|
671
|
+
if performance and 'performance_diff' in comparison:
|
672
|
+
console.print(f"\n[bold]Performance Comparison:[/bold]")
|
673
|
+
perf_diff = comparison['performance_diff']
|
674
|
+
exec_diff = perf_diff.get('execution_time', {}).get('difference', 0)
|
675
|
+
console.print(f" Execution time difference: {exec_diff:.2f}s")
|
676
|
+
|
677
|
+
except ValueError as e:
|
678
|
+
console.print(f"[red]Error:[/red] {e}")
|
679
|
+
|
680
|
+
|
349
681
|
@main.command()
|
350
682
|
@click.argument('mockup_url', required=True)
|
351
683
|
@click.option('--base-url', '-u', default='http://localhost:3000',
|
@@ -819,6 +1151,8 @@ def inspect(base_url, path, selector, verbose):
|
|
819
1151
|
results = asyncio.run(flow.execute_and_collect([
|
820
1152
|
{"navigate": path},
|
821
1153
|
{"wait_for_selector": "body"},
|
1154
|
+
{"wait_for_load_state": "networkidle"}, # Wait for dynamic content
|
1155
|
+
{"wait_for_timeout": 1000}, # Additional buffer for JS rendering (1s)
|
822
1156
|
{"screenshot": "inspection"}
|
823
1157
|
]))
|
824
1158
|
|
@@ -868,7 +1202,7 @@ def inspect(base_url, path, selector, verbose):
|
|
868
1202
|
console.print(f"Classes: [blue].{classes}[/blue]")
|
869
1203
|
|
870
1204
|
# Selectors
|
871
|
-
unique_selector = element.get('uniqueSelector', 'N/A')
|
1205
|
+
unique_selector = escape(str(element.get('uniqueSelector', 'N/A')))
|
872
1206
|
console.print(f"Unique: [cyan]{unique_selector}[/cyan]")
|
873
1207
|
|
874
1208
|
# Dimensions
|
@@ -884,20 +1218,22 @@ def inspect(base_url, path, selector, verbose):
|
|
884
1218
|
console.print(f"\n🎨 Key CSS Properties:")
|
885
1219
|
|
886
1220
|
# Layout
|
887
|
-
display = computed.get('display', 'N/A')
|
888
|
-
position = computed.get('position', 'N/A')
|
1221
|
+
display = escape(str(computed.get('display', 'N/A')))
|
1222
|
+
position = escape(str(computed.get('position', 'N/A')))
|
889
1223
|
console.print(f" display: {display}")
|
890
1224
|
console.print(f" position: {position}")
|
891
1225
|
|
892
1226
|
# Flexbox
|
893
1227
|
if 'flex' in computed:
|
894
|
-
|
1228
|
+
flex_value = escape(str(computed.get('flex', 'N/A')))
|
1229
|
+
console.print(f" flex: {flex_value}")
|
895
1230
|
if 'flexBasis' in computed:
|
896
|
-
|
1231
|
+
flex_basis = escape(str(computed.get('flexBasis', 'N/A')))
|
1232
|
+
console.print(f" flex-basis: {flex_basis}")
|
897
1233
|
|
898
1234
|
# Dimensions
|
899
|
-
width = computed.get('width', 'N/A')
|
900
|
-
height = computed.get('height', 'N/A')
|
1235
|
+
width = escape(str(computed.get('width', 'N/A')))
|
1236
|
+
height = escape(str(computed.get('height', 'N/A')))
|
901
1237
|
console.print(f" width: {width}")
|
902
1238
|
console.print(f" height: {height}")
|
903
1239
|
|
@@ -905,20 +1241,23 @@ def inspect(base_url, path, selector, verbose):
|
|
905
1241
|
margin = computed.get('margin', 'N/A')
|
906
1242
|
padding = computed.get('padding', 'N/A')
|
907
1243
|
if margin != 'N/A':
|
1244
|
+
margin = escape(str(margin))
|
908
1245
|
console.print(f" margin: {margin}")
|
909
1246
|
if padding != 'N/A':
|
1247
|
+
padding = escape(str(padding))
|
910
1248
|
console.print(f" padding: {padding}")
|
911
1249
|
|
912
1250
|
# Show all styles in verbose mode
|
913
1251
|
if verbose:
|
914
1252
|
console.print(f"\n📋 All Computed Styles:")
|
915
1253
|
for prop, value in sorted(computed.items())[:30]: # Limit to 30
|
916
|
-
|
1254
|
+
safe_value = escape(str(value))
|
1255
|
+
console.print(f" {prop}: {safe_value}")
|
917
1256
|
|
918
1257
|
# Accessibility info
|
919
1258
|
accessibility = element.get('accessibility', {})
|
920
1259
|
if accessibility:
|
921
|
-
role = accessibility.get('role', 'N/A')
|
1260
|
+
role = escape(str(accessibility.get('role', 'N/A')))
|
922
1261
|
is_interactive = accessibility.get('isInteractive', False)
|
923
1262
|
console.print(f"\n♿ Accessibility:")
|
924
1263
|
console.print(f" Role: {role}")
|
@@ -961,8 +1300,9 @@ def inspect(base_url, path, selector, verbose):
|
|
961
1300
|
|
962
1301
|
except Exception as e:
|
963
1302
|
console.print(f"[red]❌ Inspection failed: {escape(str(e))}[/red]")
|
964
|
-
|
965
|
-
|
1303
|
+
if verbose:
|
1304
|
+
import traceback
|
1305
|
+
console.print(escape(traceback.format_exc()))
|
966
1306
|
|
967
1307
|
def _element_matches_selector(element: Dict, selector: str) -> bool:
|
968
1308
|
"""Check if element matches the given selector"""
|
@@ -21,6 +21,8 @@ from .css_iterator import CSSIterator
|
|
21
21
|
from .cursor_integration import CursorIntegration
|
22
22
|
from .persistent_session import PersistentSession, get_session_manager
|
23
23
|
from .mockup_comparator import MockupComparator
|
24
|
+
from .output_manager import OutputManager
|
25
|
+
from .data_presenter import DataPresenter
|
24
26
|
|
25
27
|
|
26
28
|
class CursorFlow:
|
@@ -74,6 +76,10 @@ class CursorFlow:
|
|
74
76
|
self.cursor_integration = CursorIntegration()
|
75
77
|
self.mockup_comparator = MockupComparator()
|
76
78
|
|
79
|
+
# Initialize output manager and data presenter
|
80
|
+
self.output_manager = OutputManager()
|
81
|
+
self.data_presenter = DataPresenter()
|
82
|
+
|
77
83
|
# Session tracking
|
78
84
|
self.session_id = None
|
79
85
|
self.timeline = []
|
@@ -166,7 +172,8 @@ class CursorFlow:
|
|
166
172
|
"server_logs": server_logs, # Raw server logs
|
167
173
|
"summary": summary, # Basic counts
|
168
174
|
"artifacts": self.artifacts,
|
169
|
-
"comprehensive_data": comprehensive_data # Complete page intelligence
|
175
|
+
"comprehensive_data": comprehensive_data, # Complete page intelligence
|
176
|
+
"test_description": session_options.get('test_description', 'test') if session_options else 'test'
|
170
177
|
}
|
171
178
|
|
172
179
|
self.logger.info(f"Test execution completed: {success}, timeline events: {len(organized_timeline)}")
|