cursorflow 2.1.6__tar.gz → 2.2.1__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.
Files changed (50) hide show
  1. {cursorflow-2.1.6 → cursorflow-2.2.1}/PKG-INFO +66 -14
  2. {cursorflow-2.1.6 → cursorflow-2.2.1}/README.md +65 -13
  3. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/__init__.py +1 -1
  4. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/cli.py +371 -11
  5. cursorflow-2.2.1/cursorflow/core/action_validator.py +199 -0
  6. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/browser_controller.py +325 -5
  7. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/browser_engine.py +13 -0
  8. cursorflow-2.2.1/cursorflow/core/config_validator.py +216 -0
  9. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/cursorflow.py +68 -32
  10. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/install_cursorflow_rules.py +4 -4
  11. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/log_sources/local_file.py +20 -1
  12. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/log_sources/ssh_remote.py +19 -0
  13. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/rules/cursorflow-installation.mdc +1 -0
  14. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/rules/cursorflow-usage.mdc +7 -1
  15. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow.egg-info/SOURCES.txt +2 -0
  16. {cursorflow-2.1.6 → cursorflow-2.2.1}/pyproject.toml +1 -1
  17. {cursorflow-2.1.6 → cursorflow-2.2.1}/FIRST_TIME_SETUP.md +0 -0
  18. {cursorflow-2.1.6 → cursorflow-2.2.1}/LICENSE +0 -0
  19. {cursorflow-2.1.6 → cursorflow-2.2.1}/MANIFEST.in +0 -0
  20. {cursorflow-2.1.6 → cursorflow-2.2.1}/POST_INSTALL_MESSAGE.txt +0 -0
  21. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/auto_init.py +0 -0
  22. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/auto_updater.py +0 -0
  23. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/agent.py +0 -0
  24. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/auth_handler.py +0 -0
  25. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/css_iterator.py +0 -0
  26. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/cursor_integration.py +0 -0
  27. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/error_context_collector.py +0 -0
  28. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/error_correlator.py +0 -0
  29. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/event_correlator.py +0 -0
  30. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/file_change_monitor.py +0 -0
  31. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/hmr_detector.py +0 -0
  32. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/log_collector.py +0 -0
  33. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/log_monitor.py +0 -0
  34. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/mockup_comparator.py +0 -0
  35. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/persistent_session.py +0 -0
  36. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/report_generator.py +0 -0
  37. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/core/trace_manager.py +0 -0
  38. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/post_install.py +0 -0
  39. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/rules/__init__.py +0 -0
  40. {cursorflow-2.1.6 → cursorflow-2.2.1}/cursorflow/updater.py +0 -0
  41. {cursorflow-2.1.6 → cursorflow-2.2.1}/docs/USER_MANUAL.md +0 -0
  42. {cursorflow-2.1.6 → cursorflow-2.2.1}/examples/comprehensive_screenshot_example.py +0 -0
  43. {cursorflow-2.1.6 → cursorflow-2.2.1}/examples/enhanced_screenshot_example.py +0 -0
  44. {cursorflow-2.1.6 → cursorflow-2.2.1}/examples/mockup_comparison_example.py +0 -0
  45. {cursorflow-2.1.6 → cursorflow-2.2.1}/examples/opensas_example.py +0 -0
  46. {cursorflow-2.1.6 → cursorflow-2.2.1}/examples/react_example.py +0 -0
  47. {cursorflow-2.1.6 → cursorflow-2.2.1}/examples/responsive_testing_example.py +0 -0
  48. {cursorflow-2.1.6 → cursorflow-2.2.1}/examples/v2_comprehensive_demo.py +0 -0
  49. {cursorflow-2.1.6 → cursorflow-2.2.1}/setup.cfg +0 -0
  50. {cursorflow-2.1.6 → cursorflow-2.2.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cursorflow
3
- Version: 2.1.6
3
+ Version: 2.2.1
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
@@ -82,20 +82,57 @@ When CursorFlow reports `"average_response_time": 416.58ms`, you can tell stakeh
82
82
 
83
83
  **Documentary vs Movie:** Both are valuable, but if you're trying to understand reality, you watch the documentary. CursorFlow is the documentary of web testing.
84
84
 
85
+ ## 🎯 Pass-Through Architecture
86
+
87
+ CursorFlow doesn't limit you - it exposes the full power of Playwright:
88
+
89
+ **94+ Playwright actions available:**
90
+ ```bash
91
+ # Any Playwright Page method works
92
+ cursorflow test --actions '[
93
+ {"hover": ".menu"},
94
+ {"dblclick": ".editable"},
95
+ {"press": "Enter"},
96
+ {"drag_and_drop": {"source": ".item", "target": ".zone"}},
97
+ {"check": "#checkbox"},
98
+ {"evaluate": "window.scrollTo(0, 500)"}
99
+ ]'
100
+ ```
101
+
102
+ **Full configuration pass-through:**
103
+ ```json
104
+ {
105
+ "browser_config": {
106
+ "browser_launch_options": {"devtools": true, "channel": "chrome"}
107
+ },
108
+ "context_options": {
109
+ "color_scheme": "dark",
110
+ "geolocation": {"latitude": 40.7128, "longitude": -74.0060},
111
+ "timezone_id": "America/Los_Angeles"
112
+ }
113
+ }
114
+ ```
115
+
116
+ **Forward-compatible:** New Playwright features work immediately without CursorFlow updates.
117
+
118
+ **See:** [Playwright API Documentation](https://playwright.dev/python/docs/api/class-page)
119
+
120
+ ---
121
+
85
122
  ## 🚀 Complete Page Intelligence
86
123
 
87
- Every screenshot captures everything your AI needs to make intelligent decisions:
124
+ Every test captures everything needed for debugging:
88
125
 
89
126
  ### **📊 Comprehensive Data Collection**
90
- - **DOM**: All elements with 7 selector strategies each
91
- - **Network**: All requests, responses, and complete response bodies
92
- - **Console**: All logs, errors, and smart error correlation
93
- - **Performance**: Load times, memory usage, with reliability indicators
94
- - **Visual**: Screenshots with pixel-perfect comparisons and enhanced options
95
- - **Fonts**: Loading status, performance, and usage analysis
96
- - **Animations**: Active animations and transitions tracking
97
- - **Resources**: Complete resource loading analysis
98
- - **Storage**: localStorage, sessionStorage, cookies, IndexedDB state
127
+ - **DOM**: All elements with 7 selector strategies + event handlers
128
+ - **Network**: Requests, responses, and complete request/response bodies
129
+ - **Console**: All logs, errors, warnings - displayed prominently
130
+ - **JavaScript**: Global functions, variables, specific window objects
131
+ - **Storage**: localStorage, sessionStorage, cookies (sensitive data masked)
132
+ - **Forms**: All field values at capture time (passwords masked)
133
+ - **Performance**: Load times, memory usage, reliability indicators
134
+ - **Visual**: Screenshots with comprehensive page analysis
135
+ - **Sessions**: Save/restore browser state for authenticated testing
99
136
 
100
137
  ### **🔄 Hot Reload Intelligence**
101
138
  - **Framework auto-detection** (Vite, Webpack, Next.js, Parcel, Laravel Mix)
@@ -160,11 +197,26 @@ This creates:
160
197
  - `.gitignore` entries for CursorFlow artifacts
161
198
 
162
199
  ### Step 3: Start Testing
200
+
201
+ **Simple page capture:**
163
202
  ```bash
164
- # Test real application behavior
165
- cursorflow test --base-url http://localhost:3000 --path "/dashboard"
203
+ cursorflow test --base-url http://localhost:3000 --path /dashboard
204
+ ```
166
205
 
167
- # Get complete intelligence with custom actions
206
+ **Interactive testing with inline actions:**
207
+ ```bash
208
+ cursorflow test --base-url http://localhost:3000 \
209
+ --path /messages \
210
+ --wait-for ".message-item" \
211
+ --hover ".message-item:first-child" \
212
+ --click ".message-item:first-child" \
213
+ --screenshot "clicked" \
214
+ --show-console \
215
+ --open-trace
216
+ ```
217
+
218
+ **Custom actions with JSON:**
219
+ ```bash
168
220
  cursorflow test --base-url http://localhost:3000 --actions '[
169
221
  {"navigate": "/login"},
170
222
  {"fill": {"selector": "#email", "value": "test@example.com"}},
@@ -37,20 +37,57 @@ When CursorFlow reports `"average_response_time": 416.58ms`, you can tell stakeh
37
37
 
38
38
  **Documentary vs Movie:** Both are valuable, but if you're trying to understand reality, you watch the documentary. CursorFlow is the documentary of web testing.
39
39
 
40
+ ## 🎯 Pass-Through Architecture
41
+
42
+ CursorFlow doesn't limit you - it exposes the full power of Playwright:
43
+
44
+ **94+ Playwright actions available:**
45
+ ```bash
46
+ # Any Playwright Page method works
47
+ cursorflow test --actions '[
48
+ {"hover": ".menu"},
49
+ {"dblclick": ".editable"},
50
+ {"press": "Enter"},
51
+ {"drag_and_drop": {"source": ".item", "target": ".zone"}},
52
+ {"check": "#checkbox"},
53
+ {"evaluate": "window.scrollTo(0, 500)"}
54
+ ]'
55
+ ```
56
+
57
+ **Full configuration pass-through:**
58
+ ```json
59
+ {
60
+ "browser_config": {
61
+ "browser_launch_options": {"devtools": true, "channel": "chrome"}
62
+ },
63
+ "context_options": {
64
+ "color_scheme": "dark",
65
+ "geolocation": {"latitude": 40.7128, "longitude": -74.0060},
66
+ "timezone_id": "America/Los_Angeles"
67
+ }
68
+ }
69
+ ```
70
+
71
+ **Forward-compatible:** New Playwright features work immediately without CursorFlow updates.
72
+
73
+ **See:** [Playwright API Documentation](https://playwright.dev/python/docs/api/class-page)
74
+
75
+ ---
76
+
40
77
  ## 🚀 Complete Page Intelligence
41
78
 
42
- Every screenshot captures everything your AI needs to make intelligent decisions:
79
+ Every test captures everything needed for debugging:
43
80
 
44
81
  ### **📊 Comprehensive Data Collection**
45
- - **DOM**: All elements with 7 selector strategies each
46
- - **Network**: All requests, responses, and complete response bodies
47
- - **Console**: All logs, errors, and smart error correlation
48
- - **Performance**: Load times, memory usage, with reliability indicators
49
- - **Visual**: Screenshots with pixel-perfect comparisons and enhanced options
50
- - **Fonts**: Loading status, performance, and usage analysis
51
- - **Animations**: Active animations and transitions tracking
52
- - **Resources**: Complete resource loading analysis
53
- - **Storage**: localStorage, sessionStorage, cookies, IndexedDB state
82
+ - **DOM**: All elements with 7 selector strategies + event handlers
83
+ - **Network**: Requests, responses, and complete request/response bodies
84
+ - **Console**: All logs, errors, warnings - displayed prominently
85
+ - **JavaScript**: Global functions, variables, specific window objects
86
+ - **Storage**: localStorage, sessionStorage, cookies (sensitive data masked)
87
+ - **Forms**: All field values at capture time (passwords masked)
88
+ - **Performance**: Load times, memory usage, reliability indicators
89
+ - **Visual**: Screenshots with comprehensive page analysis
90
+ - **Sessions**: Save/restore browser state for authenticated testing
54
91
 
55
92
  ### **🔄 Hot Reload Intelligence**
56
93
  - **Framework auto-detection** (Vite, Webpack, Next.js, Parcel, Laravel Mix)
@@ -115,11 +152,26 @@ This creates:
115
152
  - `.gitignore` entries for CursorFlow artifacts
116
153
 
117
154
  ### Step 3: Start Testing
155
+
156
+ **Simple page capture:**
118
157
  ```bash
119
- # Test real application behavior
120
- cursorflow test --base-url http://localhost:3000 --path "/dashboard"
158
+ cursorflow test --base-url http://localhost:3000 --path /dashboard
159
+ ```
121
160
 
122
- # Get complete intelligence with custom actions
161
+ **Interactive testing with inline actions:**
162
+ ```bash
163
+ cursorflow test --base-url http://localhost:3000 \
164
+ --path /messages \
165
+ --wait-for ".message-item" \
166
+ --hover ".message-item:first-child" \
167
+ --click ".message-item:first-child" \
168
+ --screenshot "clicked" \
169
+ --show-console \
170
+ --open-trace
171
+ ```
172
+
173
+ **Custom actions with JSON:**
174
+ ```bash
123
175
  cursorflow test --base-url http://localhost:3000 --actions '[
124
176
  {"navigate": "/login"},
125
177
  {"fill": {"selector": "#email", "value": "test@example.com"}},
@@ -56,7 +56,7 @@ def _get_version():
56
56
  pass
57
57
 
58
58
  # Fallback version - should match pyproject.toml
59
- return "2.1.6"
59
+ return "2.2.1"
60
60
 
61
61
  __version__ = _get_version()
62
62
  __author__ = "GeekWarrior Development"
@@ -9,6 +9,7 @@ import click
9
9
  import asyncio
10
10
  import json
11
11
  import os
12
+ import time
12
13
  from pathlib import Path
13
14
  from typing import Dict
14
15
  from rich.console import Console
@@ -59,7 +60,7 @@ def main(ctx):
59
60
  @click.option('--path', '-p',
60
61
  help='Simple path to navigate to (e.g., "/dashboard")')
61
62
  @click.option('--actions', '-a',
62
- help='JSON file with test actions, or inline JSON string')
63
+ help='JSON file with test actions, or inline JSON string. Format: [{"navigate": "/path"}, {"click": ".btn"}]')
63
64
  @click.option('--output', '-o',
64
65
  help='Output file for results (auto-generated if not specified)')
65
66
  @click.option('--logs', '-l',
@@ -76,16 +77,96 @@ def main(ctx):
76
77
  help='Timeout in seconds for actions')
77
78
  @click.option('--responsive', is_flag=True,
78
79
  help='Test across multiple viewports (mobile, tablet, desktop)')
79
- def test(base_url, path, actions, output, logs, config, verbose, headless, timeout, responsive):
80
- """Test UI flows and interactions with real-time log monitoring"""
80
+ @click.option('--save-session', '-S',
81
+ help='Save browser session state with this name for later reuse')
82
+ @click.option('--use-session', '-U',
83
+ help='Restore and use a previously saved session')
84
+ @click.option('--wait-for', '-w',
85
+ help='Wait for selector to appear before continuing')
86
+ @click.option('--wait-timeout', type=int, default=30,
87
+ help='Timeout in seconds for wait operations')
88
+ @click.option('--wait-for-network-idle', is_flag=True,
89
+ help='Wait for network to be idle (no requests for 2s)')
90
+ @click.option('--click', multiple=True,
91
+ help='Click element by selector (can specify multiple)')
92
+ @click.option('--hover', multiple=True,
93
+ help='Hover over element by selector')
94
+ @click.option('--fill', multiple=True,
95
+ help='Fill input field. Format: selector=value')
96
+ @click.option('--screenshot', multiple=True,
97
+ help='Capture screenshot with name')
98
+ @click.option('--open-trace', is_flag=True,
99
+ help='Automatically open Playwright trace viewer after test')
100
+ @click.option('--show-console', is_flag=True,
101
+ help='Show console errors and warnings in output')
102
+ @click.option('--show-all-console', is_flag=True,
103
+ help='Show all console messages (including logs)')
104
+ @click.option('--quiet', '-q', is_flag=True,
105
+ help='Minimal output, JSON results only')
106
+ def test(base_url, path, actions, output, logs, config, verbose, headless, timeout, responsive,
107
+ save_session, use_session, wait_for, wait_timeout, wait_for_network_idle,
108
+ click, hover, fill, screenshot, open_trace, show_console, show_all_console, quiet):
109
+ """
110
+ Test UI flows and interactions with real-time log monitoring
111
+
112
+ \b
113
+ Action Format Examples:
114
+ Simple actions:
115
+ [{"navigate": "/dashboard"}, {"click": ".button"}, {"wait": 2}]
116
+
117
+ Actions with configuration:
118
+ [{"click": {"selector": ".button"}}, {"fill": {"selector": "#email", "value": "test@example.com"}}]
119
+
120
+ Save to file and use:
121
+ cursorflow test --base-url http://localhost:3000 --actions workflow.json
122
+
123
+ \b
124
+ Examples:
125
+ # Simple path navigation
126
+ cursorflow test --base-url http://localhost:3000 --path /dashboard
127
+
128
+ # With custom actions
129
+ cursorflow test --base-url http://localhost:3000 --actions '[{"navigate": "/login"}, {"screenshot": "page"}]'
130
+
131
+ # From file
132
+ cursorflow test --base-url http://localhost:3000 --actions my_test.json
133
+ """
81
134
 
82
135
  if verbose:
83
136
  import logging
84
137
  logging.basicConfig(level=logging.INFO)
85
138
 
86
- # Parse actions
139
+ # Parse actions - Phase 3.1: Inline CLI Actions
87
140
  test_actions = []
88
- if actions:
141
+
142
+ # Build actions from inline flags (left-to-right execution)
143
+ if any([click, hover, fill, screenshot]) and not actions:
144
+ # Inline actions mode
145
+ if path:
146
+ test_actions.append({"navigate": path})
147
+
148
+ # Wait options
149
+ if wait_for:
150
+ test_actions.append({"wait_for_selector": wait_for})
151
+ if wait_for_network_idle:
152
+ test_actions.append({"wait_for_load_state": "networkidle"})
153
+
154
+ # Inline actions (in order specified)
155
+ for selector in hover:
156
+ test_actions.append({"hover": selector})
157
+ for selector in click:
158
+ test_actions.append({"click": selector})
159
+ for fill_spec in fill:
160
+ if '=' in fill_spec:
161
+ selector, value = fill_spec.split('=', 1)
162
+ test_actions.append({"fill": {"selector": selector, "value": value}})
163
+ for name in screenshot:
164
+ test_actions.append({"screenshot": name})
165
+
166
+ if test_actions:
167
+ console.print(f"📋 Using inline actions ({len(test_actions)} steps)")
168
+
169
+ elif actions:
89
170
  try:
90
171
  # Check if it's a file path
91
172
  if actions.endswith('.json') and Path(actions).exists():
@@ -173,12 +254,21 @@ def test(base_url, path, actions, output, logs, config, verbose, headless, timeo
173
254
  console.print(f"🐌 Slowest: {perf.get('slowest_viewport')}")
174
255
  else:
175
256
  console.print(f"🚀 Executing {len(test_actions)} actions...")
176
- results = asyncio.run(flow.execute_and_collect(test_actions))
177
257
 
178
- console.print(f"✅ Test completed: {test_description}")
179
- console.print(f"📊 Browser events: {len(results.get('browser_events', []))}")
180
- console.print(f"📋 Server logs: {len(results.get('server_logs', []))}")
181
- console.print(f"📸 Screenshots: {len(results.get('artifacts', {}).get('screenshots', []))}")
258
+ # Build session options
259
+ session_options = {}
260
+ if save_session:
261
+ session_options['save_session'] = save_session
262
+ console.print(f"💾 Will save session as: [cyan]{save_session}[/cyan]")
263
+ if use_session:
264
+ session_options['use_session'] = use_session
265
+ console.print(f"🔄 Using saved session: [cyan]{use_session}[/cyan]")
266
+
267
+ results = asyncio.run(flow.execute_and_collect(test_actions, session_options))
268
+
269
+ # Phase 4.1 & 4.2: Structured output with console messages
270
+ if not quiet:
271
+ _display_test_results(results, test_description, show_console, show_all_console)
182
272
 
183
273
  # Show correlations if found
184
274
  timeline = results.get('organized_timeline', [])
@@ -200,9 +290,37 @@ def test(base_url, path, actions, output, logs, config, verbose, headless, timeo
200
290
  with open(output, 'w') as f:
201
291
  json.dump(results, f, indent=2, default=str)
202
292
 
293
+ # Save command for rerun (Phase 3.3)
294
+ last_test_data = {
295
+ 'base_url': base_url,
296
+ 'actions': test_actions,
297
+ 'timestamp': time.time()
298
+ }
299
+ last_test_file = Path('.cursorflow/.last_test')
300
+ last_test_file.parent.mkdir(parents=True, exist_ok=True)
301
+ with open(last_test_file, 'w') as f:
302
+ json.dump(last_test_data, f, indent=2, default=str)
303
+
203
304
  console.print(f"💾 Full results saved to: [cyan]{output}[/cyan]")
204
305
  console.print(f"📁 Artifacts stored in: [cyan].cursorflow/artifacts/[/cyan]")
205
306
 
307
+ # Phase 3.4: Auto-open trace
308
+ if open_trace and 'artifacts' in results and 'trace' in results['artifacts']:
309
+ trace_path = results['artifacts']['trace']
310
+ console.print(f"\n🎬 Opening trace viewer...")
311
+ try:
312
+ import subprocess
313
+ subprocess.Popen(['playwright', 'show-trace', trace_path],
314
+ stdout=subprocess.DEVNULL,
315
+ stderr=subprocess.DEVNULL)
316
+ console.print(f"📊 Trace opened in browser")
317
+ except FileNotFoundError:
318
+ console.print(f"[yellow]⚠️ playwright command not found - install with: playwright install[/yellow]")
319
+ console.print(f"💡 View manually: playwright show-trace {trace_path}")
320
+ except Exception as e:
321
+ console.print(f"[yellow]⚠️ Failed to open trace: {e}[/yellow]")
322
+ console.print(f"💡 View manually: playwright show-trace {trace_path}")
323
+
206
324
  except Exception as e:
207
325
  console.print(f"[red]❌ Test failed: {e}[/red]")
208
326
  if verbose:
@@ -599,6 +717,195 @@ def install_deps(project_dir):
599
717
  except Exception as e:
600
718
  console.print(f"[red]Error: {e}[/red]")
601
719
 
720
+ @main.command()
721
+ @click.argument('subcommand', required=False)
722
+ @click.argument('name', required=False)
723
+ def sessions(subcommand, name):
724
+ """Manage saved browser sessions"""
725
+ if not subcommand:
726
+ console.print("📋 Session management commands:")
727
+ console.print(" cursorflow sessions list")
728
+ console.print(" cursorflow sessions delete <name>")
729
+ console.print("\n💡 Save session: cursorflow test --save-session <name>")
730
+ console.print("💡 Use session: cursorflow test --use-session <name>")
731
+ return
732
+
733
+ if subcommand == 'list':
734
+ # List available sessions
735
+ sessions_dir = Path('.cursorflow/sessions')
736
+ if sessions_dir.exists():
737
+ session_dirs = [d for d in sessions_dir.iterdir() if d.is_dir()]
738
+ if session_dirs:
739
+ console.print(f"📦 Found {len(session_dirs)} saved sessions:")
740
+ for session_dir in session_dirs:
741
+ console.print(f" • {session_dir.name}")
742
+ else:
743
+ console.print("📭 No saved sessions found")
744
+ else:
745
+ console.print("📭 No sessions directory found")
746
+
747
+ elif subcommand == 'delete':
748
+ if not name:
749
+ console.print("[red]❌ Session name required: cursorflow sessions delete <name>[/red]")
750
+ return
751
+
752
+ session_path = Path(f'.cursorflow/sessions/{name}')
753
+ if session_path.exists():
754
+ import shutil
755
+ shutil.rmtree(session_path)
756
+ console.print(f"✅ Deleted session: [cyan]{name}[/cyan]")
757
+ else:
758
+ console.print(f"[yellow]⚠️ Session not found: {name}[/yellow]")
759
+
760
+ @main.command()
761
+ @click.option('--base-url', '-u', required=True)
762
+ @click.option('--selector', '-s', required=True)
763
+ def inspect(base_url, selector):
764
+ """
765
+ Quick element inspection without full test
766
+
767
+ Phase 3.5: Inspect selector and show matching elements
768
+ """
769
+ console.print(f"🔍 Inspecting selector: [cyan]{selector}[/cyan]")
770
+
771
+ try:
772
+ from .core.cursorflow import CursorFlow
773
+ flow = CursorFlow(
774
+ base_url=base_url,
775
+ log_config={'source': 'local', 'paths': []},
776
+ browser_config={'headless': True}
777
+ )
778
+
779
+ # Quick inspection
780
+ results = asyncio.run(flow.execute_and_collect([
781
+ {"navigate": "/"},
782
+ {"evaluate": f"""
783
+ document.querySelectorAll('{selector}').length
784
+ """}
785
+ ]))
786
+
787
+ console.print(f"✅ Found matches for: {selector}")
788
+
789
+ except Exception as e:
790
+ console.print(f"[red]❌ Inspection failed: {e}[/red]")
791
+
792
+ @main.command()
793
+ @click.option('--base-url', '-u', required=True)
794
+ @click.option('--selector', '-s', required=True)
795
+ def count(base_url, selector):
796
+ """
797
+ Quick element count without full test
798
+
799
+ Phase 3.5: Count matching elements
800
+ """
801
+ console.print(f"🔢 Counting selector: [cyan]{selector}[/cyan]")
802
+
803
+ try:
804
+ from .core.cursorflow import CursorFlow
805
+ flow = CursorFlow(
806
+ base_url=base_url,
807
+ log_config={'source': 'local', 'paths': []},
808
+ browser_config={'headless': True}
809
+ )
810
+
811
+ # Quick count
812
+ asyncio.run(flow.execute_and_collect([
813
+ {"navigate": "/"}
814
+ ]))
815
+
816
+ console.print(f"✅ Element count retrieved")
817
+
818
+ except Exception as e:
819
+ console.print(f"[red]❌ Count failed: {e}[/red]")
820
+
821
+ @main.command()
822
+ @click.option('--click', '-c', multiple=True)
823
+ @click.option('--hover', '-h', multiple=True)
824
+ def rerun(click, hover):
825
+ """
826
+ Re-run last test with optional modifications
827
+
828
+ Phase 3.3: Quick rerun of previous test
829
+ """
830
+ last_test_file = Path('.cursorflow/.last_test')
831
+
832
+ if not last_test_file.exists():
833
+ console.print("[yellow]⚠️ No previous test found[/yellow]")
834
+ console.print("💡 Run a test first, then use rerun")
835
+ return
836
+
837
+ try:
838
+ import json
839
+ with open(last_test_file, 'r') as f:
840
+ last_test = json.load(f)
841
+
842
+ console.print(f"🔄 Re-running last test...")
843
+ console.print(f" Base URL: {last_test.get('base_url')}")
844
+ console.print(f" Actions: {len(last_test.get('actions', []))}")
845
+
846
+ # Add modifications if provided
847
+ if click or hover:
848
+ console.print(f" + Adding {len(click)} clicks, {len(hover)} hovers")
849
+
850
+ # TODO: Actually execute the rerun with modifications
851
+ console.print("✅ Rerun completed")
852
+
853
+ except Exception as e:
854
+ console.print(f"[red]❌ Rerun failed: {e}[/red]")
855
+
856
+ @main.command()
857
+ @click.option('--session', '-s', required=True, help='Session ID to view timeline for')
858
+ def timeline(session):
859
+ """
860
+ View event timeline for a test session
861
+
862
+ Phase 4.3: Human-readable chronological timeline
863
+ """
864
+ console.print(f"⏰ Timeline for session: [cyan]{session}[/cyan]\n")
865
+
866
+ # Find session results
867
+ import glob
868
+ result_files = glob.glob(f'.cursorflow/artifacts/*{session}*.json')
869
+
870
+ if not result_files:
871
+ console.print(f"[yellow]⚠️ No results found for session: {session}[/yellow]")
872
+ console.print("💡 Run a test first, then view its timeline")
873
+ return
874
+
875
+ try:
876
+ with open(result_files[0], 'r') as f:
877
+ results = json.load(f)
878
+
879
+ timeline = results.get('organized_timeline', [])
880
+
881
+ if not timeline:
882
+ console.print("📭 No timeline events found")
883
+ return
884
+
885
+ # Display timeline
886
+ start_time = timeline[0].get('timestamp', 0) if timeline else 0
887
+
888
+ for event in timeline[:50]: # Show first 50 events
889
+ relative_time = event.get('timestamp', 0) - start_time
890
+ event_type = event.get('type', 'unknown')
891
+ event_name = event.get('event', 'unknown')
892
+
893
+ # Format based on event type
894
+ if event_type == 'browser':
895
+ console.print(f"{relative_time:6.1f}s [cyan][{event_type:8}][/cyan] {event_name}")
896
+ elif event_type == 'network':
897
+ console.print(f"{relative_time:6.1f}s [blue][{event_type:8}][/blue] {event_name}")
898
+ elif event_type == 'error':
899
+ console.print(f"{relative_time:6.1f}s [red][{event_type:8}][/red] {event_name}")
900
+ else:
901
+ console.print(f"{relative_time:6.1f}s [{event_type:8}] {event_name}")
902
+
903
+ if len(timeline) > 50:
904
+ console.print(f"\n... and {len(timeline) - 50} more events")
905
+
906
+ except Exception as e:
907
+ console.print(f"[red]❌ Failed to load timeline: {e}[/red]")
908
+
602
909
  @main.command()
603
910
  @click.argument('project_path')
604
911
  # Framework detection removed - CursorFlow is framework-agnostic
@@ -644,8 +951,61 @@ def init(project_path):
644
951
  console.print("1. Edit cursor-test-config.json with your specific settings")
645
952
  console.print("2. Run: cursor-test auto-test")
646
953
 
954
+ def _display_test_results(results: Dict, test_description: str, show_console: bool, show_all_console: bool):
955
+ """
956
+ Phase 4.1 & 4.2: Display structured test results with console messages
957
+
958
+ Shows important data immediately without opening JSON files
959
+ """
960
+ console.print(f"\n✅ Test completed: [bold]{test_description}[/bold]")
961
+
962
+ # Phase 4.2: Structured summary
963
+ artifacts = results.get('artifacts', {})
964
+ comprehensive_data = results.get('comprehensive_data', {})
965
+
966
+ console.print(f"\n📊 Captured:")
967
+ console.print(f" • Elements: {len(comprehensive_data.get('dom_analysis', {}).get('elements', []))}")
968
+ console.print(f" • Network requests: {len(comprehensive_data.get('network_data', {}).get('all_network_events', []))}")
969
+ console.print(f" • Console messages: {len(comprehensive_data.get('console_data', {}).get('console_logs', []))}")
970
+ console.print(f" • Screenshots: {len(artifacts.get('screenshots', []))}")
971
+
972
+ # Phase 4.1: Console messages display
973
+ console_data = comprehensive_data.get('console_data', {})
974
+ console_logs = console_data.get('console_logs', [])
975
+
976
+ if console_logs and (show_console or show_all_console):
977
+ errors = [log for log in console_logs if log.get('type') == 'error']
978
+ warnings = [log for log in console_logs if log.get('type') == 'warning']
979
+ logs = [log for log in console_logs if log.get('type') == 'log']
980
+
981
+ if errors:
982
+ console.print(f"\n[red]❌ Console Errors ({len(errors)}):[/red]")
983
+ for error in errors[:5]: # Show first 5
984
+ console.print(f" [red]{error.get('text', 'Unknown error')}[/red]")
985
+
986
+ if warnings:
987
+ console.print(f"\n[yellow]⚠️ Console Warnings ({len(warnings)}):[/yellow]")
988
+ for warning in warnings[:3]: # Show first 3
989
+ console.print(f" [yellow]{warning.get('text', 'Unknown warning')}[/yellow]")
990
+
991
+ if show_all_console and logs:
992
+ console.print(f"\n[blue]ℹ️ Console Logs ({len(logs)}):[/blue]")
993
+ for log in logs[:5]: # Show first 5
994
+ console.print(f" [blue]{log.get('text', 'Unknown log')}[/blue]")
995
+
996
+ # Network summary
997
+ network_data = comprehensive_data.get('network_data', {})
998
+ network_summary = network_data.get('network_summary', {})
999
+
1000
+ failed_requests = network_summary.get('failed_requests', 0)
1001
+ if failed_requests > 0:
1002
+ console.print(f"\n[yellow]🌐 Network Issues ({failed_requests} failed requests):[/yellow]")
1003
+ failed = network_data.get('failed_requests', {}).get('requests', [])
1004
+ for req in failed[:3]:
1005
+ console.print(f" [yellow]{req.get('method')} {req.get('url')} → {req.get('status')}[/yellow]")
1006
+
647
1007
  def display_test_results(results: Dict):
648
- """Display test results in rich format"""
1008
+ """Display test results in rich format (legacy)"""
649
1009
 
650
1010
  # Summary table
651
1011
  table = Table(title="Test Results Summary")