cursorflow 2.6.3__py3-none-any.whl → 2.7.2__py3-none-any.whl
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/cli.py +364 -24
- cursorflow/core/cursorflow.py +8 -1
- cursorflow/core/data_presenter.py +518 -0
- cursorflow/core/output_manager.py +639 -0
- cursorflow/core/query_engine.py +1342 -0
- cursorflow/rules/cursorflow-usage.mdc +245 -6
- {cursorflow-2.6.3.dist-info → cursorflow-2.7.2.dist-info}/METADATA +59 -1
- {cursorflow-2.6.3.dist-info → cursorflow-2.7.2.dist-info}/RECORD +12 -9
- {cursorflow-2.6.3.dist-info → cursorflow-2.7.2.dist-info}/WHEEL +0 -0
- {cursorflow-2.6.3.dist-info → cursorflow-2.7.2.dist-info}/entry_points.txt +0 -0
- {cursorflow-2.6.3.dist-info → cursorflow-2.7.2.dist-info}/licenses/LICENSE +0 -0
- {cursorflow-2.6.3.dist-info → cursorflow-2.7.2.dist-info}/top_level.txt +0 -0
cursorflow/cli.py
CHANGED
@@ -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"""
|
cursorflow/core/cursorflow.py
CHANGED
@@ -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)}")
|