cursorflow 2.6.1__tar.gz → 2.6.3__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 (52) hide show
  1. {cursorflow-2.6.1 → cursorflow-2.6.3}/PKG-INFO +1 -1
  2. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/cli.py +26 -25
  3. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/browser_controller.py +204 -151
  4. {cursorflow-2.6.1 → cursorflow-2.6.3}/pyproject.toml +1 -1
  5. {cursorflow-2.6.1 → cursorflow-2.6.3}/LICENSE +0 -0
  6. {cursorflow-2.6.1 → cursorflow-2.6.3}/MANIFEST.in +0 -0
  7. {cursorflow-2.6.1 → cursorflow-2.6.3}/README.md +0 -0
  8. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/__init__.py +0 -0
  9. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/auto_init.py +0 -0
  10. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/auto_updater.py +0 -0
  11. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/action_validator.py +0 -0
  12. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/agent.py +0 -0
  13. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/auth_handler.py +0 -0
  14. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/browser_engine.py +0 -0
  15. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/config_validator.py +0 -0
  16. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/css_iterator.py +0 -0
  17. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/cursor_integration.py +0 -0
  18. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/cursorflow.py +0 -0
  19. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/error_context_collector.py +0 -0
  20. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/error_correlator.py +0 -0
  21. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/event_correlator.py +0 -0
  22. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/file_change_monitor.py +0 -0
  23. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/hmr_detector.py +0 -0
  24. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/json_utils.py +0 -0
  25. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/log_collector.py +0 -0
  26. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/log_monitor.py +0 -0
  27. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/mockup_comparator.py +0 -0
  28. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/persistent_session.py +0 -0
  29. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/report_generator.py +0 -0
  30. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/core/trace_manager.py +0 -0
  31. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/install_cursorflow_rules.py +0 -0
  32. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/log_sources/local_file.py +0 -0
  33. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/log_sources/ssh_remote.py +0 -0
  34. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/post_install.py +0 -0
  35. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/rules/__init__.py +0 -0
  36. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/rules/cursorflow-installation.mdc +0 -0
  37. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/rules/cursorflow-usage.mdc +0 -0
  38. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow/updater.py +0 -0
  39. {cursorflow-2.6.1 → cursorflow-2.6.3}/cursorflow.egg-info/SOURCES.txt +0 -0
  40. {cursorflow-2.6.1 → cursorflow-2.6.3}/docs/user/USAGE_GUIDE.md +0 -0
  41. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/comprehensive_screenshot_example.py +0 -0
  42. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/element_inspection_example.py +0 -0
  43. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/element_measurement_example.py +0 -0
  44. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/enhanced_screenshot_example.py +0 -0
  45. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/hot_reload_css_iteration.py +0 -0
  46. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/mockup_comparison_example.py +0 -0
  47. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/opensas_example.py +0 -0
  48. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/react_example.py +0 -0
  49. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/responsive_testing_example.py +0 -0
  50. {cursorflow-2.6.1 → cursorflow-2.6.3}/examples/v2_comprehensive_demo.py +0 -0
  51. {cursorflow-2.6.1 → cursorflow-2.6.3}/setup.cfg +0 -0
  52. {cursorflow-2.6.1 → cursorflow-2.6.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cursorflow
3
- Version: 2.6.1
3
+ Version: 2.6.3
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
@@ -15,6 +15,7 @@ from typing import Dict
15
15
  from rich.console import Console
16
16
  from rich.table import Table
17
17
  from rich.progress import Progress, SpinnerColumn, TextColumn
18
+ from rich.markup import escape
18
19
 
19
20
  from .core.agent import TestAgent
20
21
  from . import __version__
@@ -185,10 +186,10 @@ def test(base_url, path, actions, output, logs, config, verbose, headless, timeo
185
186
  test_actions = json.loads(actions)
186
187
  console.print(f"📋 Using inline actions")
187
188
  except json.JSONDecodeError as e:
188
- console.print(f"[red]❌ Invalid JSON in actions: {e}[/red]")
189
+ console.print(f"[red]❌ Invalid JSON in actions: {escape(str(e))}[/red]")
189
190
  return
190
191
  except Exception as e:
191
- console.print(f"[red]❌ Failed to load actions: {e}[/red]")
192
+ console.print(f"[red]❌ Failed to load actions: {escape(str(e))}[/red]")
192
193
  return
193
194
  elif path:
194
195
  # Simple path navigation with optional wait conditions
@@ -236,7 +237,7 @@ def test(base_url, path, actions, output, logs, config, verbose, headless, timeo
236
237
  **agent_config
237
238
  )
238
239
  except Exception as e:
239
- console.print(f"[red]Error initializing CursorFlow: {e}[/red]")
240
+ console.print(f"[red]Error initializing CursorFlow: {escape(str(e))}[/red]")
240
241
  return
241
242
 
242
243
  # Execute test actions
@@ -339,7 +340,7 @@ def test(base_url, path, actions, output, logs, config, verbose, headless, timeo
339
340
  console.print(f"💡 View manually: playwright show-trace {trace_path}")
340
341
 
341
342
  except Exception as e:
342
- console.print(f"[red]❌ Test failed: {e}[/red]")
343
+ console.print(f"[red]❌ Test failed: {escape(str(e))}[/red]")
343
344
  if verbose:
344
345
  import traceback
345
346
  console.print(traceback.format_exc())
@@ -398,7 +399,7 @@ def compare_mockup(mockup_url, base_url, mockup_actions, implementation_actions,
398
399
  comparison_config["viewports"] = viewports_parsed
399
400
 
400
401
  except Exception as e:
401
- console.print(f"[red]Error parsing input parameters: {e}[/red]")
402
+ console.print(f"[red]Error parsing input parameters: {escape(str(e))}[/red]")
402
403
  return
403
404
 
404
405
  # Initialize CursorFlow
@@ -411,7 +412,7 @@ def compare_mockup(mockup_url, base_url, mockup_actions, implementation_actions,
411
412
  browser_config={'headless': True}
412
413
  )
413
414
  except Exception as e:
414
- console.print(f"[red]Error initializing CursorFlow: {e}[/red]")
415
+ console.print(f"[red]Error initializing CursorFlow: {escape(str(e))}[/red]")
415
416
  return
416
417
 
417
418
  # Execute mockup comparison
@@ -425,7 +426,7 @@ def compare_mockup(mockup_url, base_url, mockup_actions, implementation_actions,
425
426
  ))
426
427
 
427
428
  if "error" in results:
428
- console.print(f"[red]❌ Comparison failed: {results['error']}[/red]")
429
+ console.print(f"[red]❌ Comparison failed: {escape(str(results['error']))}[/red]")
429
430
  return
430
431
 
431
432
  # Display results summary (pure metrics only)
@@ -453,7 +454,7 @@ def compare_mockup(mockup_url, base_url, mockup_actions, implementation_actions,
453
454
  console.print(f"📁 Visual diffs stored in: [cyan].cursorflow/artifacts/[/cyan]")
454
455
 
455
456
  except Exception as e:
456
- console.print(f"[red]❌ Comparison failed: {e}[/red]")
457
+ console.print(f"[red]❌ Comparison failed: {escape(str(e))}[/red]")
457
458
  if verbose:
458
459
  import traceback
459
460
  console.print(traceback.format_exc())
@@ -500,7 +501,7 @@ def iterate_mockup(mockup_url, base_url, css_improvements, base_actions, diff_th
500
501
  comparison_config = {"diff_threshold": diff_threshold}
501
502
 
502
503
  except Exception as e:
503
- console.print(f"[red]Error parsing input parameters: {e}[/red]")
504
+ console.print(f"[red]Error parsing input parameters: {escape(str(e))}[/red]")
504
505
  return
505
506
 
506
507
  # Initialize CursorFlow
@@ -512,7 +513,7 @@ def iterate_mockup(mockup_url, base_url, css_improvements, base_actions, diff_th
512
513
  browser_config={'headless': True}
513
514
  )
514
515
  except Exception as e:
515
- console.print(f"[red]Error initializing CursorFlow: {e}[/red]")
516
+ console.print(f"[red]Error initializing CursorFlow: {escape(str(e))}[/red]")
516
517
  return
517
518
 
518
519
  # Execute iterative mockup matching
@@ -526,7 +527,7 @@ def iterate_mockup(mockup_url, base_url, css_improvements, base_actions, diff_th
526
527
  ))
527
528
 
528
529
  if "error" in results:
529
- console.print(f"[red]❌ Iteration failed: {results['error']}[/red]")
530
+ console.print(f"[red]❌ Iteration failed: {escape(str(results['error']))}[/red]")
530
531
  return
531
532
 
532
533
  # Display results summary
@@ -563,7 +564,7 @@ def iterate_mockup(mockup_url, base_url, css_improvements, base_actions, diff_th
563
564
  console.print(f"📁 Iteration progress stored in: [cyan].cursorflow/artifacts/[/cyan]")
564
565
 
565
566
  except Exception as e:
566
- console.print(f"[red]❌ Iteration failed: {e}[/red]")
567
+ console.print(f"[red]❌ Iteration failed: {escape(str(e))}[/red]")
567
568
  if verbose:
568
569
  import traceback
569
570
  console.print(traceback.format_exc())
@@ -620,7 +621,7 @@ async def _run_auto_tests(framework: str, base_url: str, config: Dict):
620
621
  display_smoke_test_summary(results)
621
622
 
622
623
  except Exception as e:
623
- console.print(f"[red]Auto-test failed: {e}[/red]")
624
+ console.print(f"[red]Auto-test failed: {escape(str(e))}[/red]")
624
625
 
625
626
  @main.command()
626
627
  @click.argument('project_path', default='.')
@@ -648,7 +649,7 @@ def install_rules(project_path, framework, force, yes):
648
649
  console.print("[red]❌ Installation failed[/red]")
649
650
 
650
651
  except Exception as e:
651
- console.print(f"[red]Installation error: {e}[/red]")
652
+ console.print(f"[red]Installation error: {escape(str(e))}[/red]")
652
653
 
653
654
  @main.command()
654
655
  @click.option('--force', is_flag=True, help='Force update even if no updates available')
@@ -670,7 +671,7 @@ def update(force, project_dir):
670
671
  console.print("[red]❌ Update failed[/red]")
671
672
 
672
673
  except Exception as e:
673
- console.print(f"[red]Update error: {e}[/red]")
674
+ console.print(f"[red]Update error: {escape(str(e))}[/red]")
674
675
 
675
676
  @main.command()
676
677
  @click.option('--project-dir', default='.', help='Project directory')
@@ -684,7 +685,7 @@ def check_updates(project_dir):
684
685
  result = asyncio.run(check_updates(project_dir))
685
686
 
686
687
  if "error" in result:
687
- console.print(f"[red]Error checking updates: {result['error']}[/red]")
688
+ console.print(f"[red]Error checking updates: {escape(str(result['error']))}[/red]")
688
689
  return
689
690
 
690
691
  # Display update information
@@ -723,7 +724,7 @@ def check_updates(project_dir):
723
724
  console.print("\n💡 Run [bold]cursorflow update[/bold] to install updates")
724
725
 
725
726
  except Exception as e:
726
- console.print(f"[red]Error: {e}[/red]")
727
+ console.print(f"[red]Error: {escape(str(e))}[/red]")
727
728
 
728
729
  @main.command()
729
730
  @click.option('--project-dir', default='.', help='Project directory')
@@ -744,7 +745,7 @@ def install_deps(project_dir):
744
745
  console.print("[red]❌ Dependency installation failed[/red]")
745
746
 
746
747
  except Exception as e:
747
- console.print(f"[red]Error: {e}[/red]")
748
+ console.print(f"[red]Error: {escape(str(e))}[/red]")
748
749
 
749
750
  @main.command()
750
751
  @click.argument('subcommand', required=False)
@@ -959,7 +960,7 @@ def inspect(base_url, path, selector, verbose):
959
960
  console.print(f"\n📸 Screenshot saved: [cyan]{screenshot_path}[/cyan]")
960
961
 
961
962
  except Exception as e:
962
- console.print(f"[red]❌ Inspection failed: {e}[/red]")
963
+ console.print(f"[red]❌ Inspection failed: {escape(str(e))}[/red]")
963
964
  import traceback
964
965
  console.print(traceback.format_exc())
965
966
 
@@ -1086,7 +1087,7 @@ def measure(base_url, path, selector, verbose):
1086
1087
  console.print("✅ Measurement complete")
1087
1088
 
1088
1089
  except Exception as e:
1089
- console.print(f"[red]❌ Measurement failed: {e}[/red]")
1090
+ console.print(f"[red]❌ Measurement failed: {escape(str(e))}[/red]")
1090
1091
  import traceback
1091
1092
  console.print(traceback.format_exc())
1092
1093
 
@@ -1137,7 +1138,7 @@ def count(base_url, path, selector):
1137
1138
  console.print(f"💡 Total elements on page: {len(elements)}")
1138
1139
 
1139
1140
  except Exception as e:
1140
- console.print(f"[red]❌ Count failed: {e}[/red]")
1141
+ console.print(f"[red]❌ Count failed: {escape(str(e))}[/red]")
1141
1142
  import traceback
1142
1143
  console.print(traceback.format_exc())
1143
1144
 
@@ -1174,7 +1175,7 @@ def rerun(click, hover):
1174
1175
  console.print("✅ Rerun completed")
1175
1176
 
1176
1177
  except Exception as e:
1177
- console.print(f"[red]❌ Rerun failed: {e}[/red]")
1178
+ console.print(f"[red]❌ Rerun failed: {escape(str(e))}[/red]")
1178
1179
 
1179
1180
  @main.command()
1180
1181
  @click.option('--session', '-s', required=True, help='Session ID to view timeline for')
@@ -1227,7 +1228,7 @@ def timeline(session):
1227
1228
  console.print(f"\n... and {len(timeline) - 50} more events")
1228
1229
 
1229
1230
  except Exception as e:
1230
- console.print(f"[red]❌ Failed to load timeline: {e}[/red]")
1231
+ console.print(f"[red]❌ Failed to load timeline: {escape(str(e))}[/red]")
1231
1232
 
1232
1233
  @main.command()
1233
1234
  @click.option('--artifacts', is_flag=True, help='Clean all artifacts (screenshots, traces)')
@@ -1337,7 +1338,7 @@ def cleanup(artifacts, sessions, old_only, clean_all, dry_run, yes):
1337
1338
  item_path.unlink()
1338
1339
  deleted_count += 1
1339
1340
  except Exception as e:
1340
- console.print(f"[red]⚠️ Failed to delete {item_path}: {e}[/red]")
1341
+ console.print(f"[red]⚠️ Failed to delete {item_path}: {escape(str(e))}[/red]")
1341
1342
 
1342
1343
  console.print(f"\n✅ Cleanup complete!")
1343
1344
  console.print(f" • Deleted {deleted_count}/{len(items_to_delete)} items")
@@ -1418,7 +1419,7 @@ def _display_test_results(results: Dict, test_description: str, show_console: bo
1418
1419
  if errors:
1419
1420
  console.print(f"\n[red]❌ Console Errors ({len(errors)}):[/red]")
1420
1421
  for error in errors[:5]: # Show first 5
1421
- console.print(f" [red]{error.get('text', 'Unknown error')}[/red]")
1422
+ console.print(f" [red]{escape(str(error.get('text', 'Unknown error')))}[/red]")
1422
1423
 
1423
1424
  if warnings:
1424
1425
  console.print(f"\n[yellow]⚠️ Console Warnings ({len(warnings)}):[/yellow]")
@@ -189,165 +189,205 @@ class BrowserController:
189
189
 
190
190
  def _handle_console_message(self, msg):
191
191
  """Handle console messages from any framework with enhanced error context collection"""
192
- log_entry = {
193
- "timestamp": time.time(),
194
- "type": msg.type,
195
- "text": msg.text,
196
- "location": {
197
- "url": msg.location.get("url", "") if msg.location else "",
198
- "line": msg.location.get("lineNumber", 0) if msg.location else 0,
199
- "column": msg.location.get("columnNumber", 0) if msg.location else 0
200
- },
201
- "args": [str(arg) for arg in msg.args] if msg.args else [],
202
- "stack_trace": getattr(msg, 'stackTrace', None)
203
- }
204
- self.console_logs.append(log_entry)
205
-
206
- # Enhanced logging for better correlation
207
- if msg.type == "error":
208
- self.logger.error(f"Console Error: {msg.text} at {msg.location}")
209
-
210
- # v2.0 Enhancement: Trigger error context collection for console errors
211
- if self.error_context_collector:
212
- error_event = {
213
- 'type': 'console_error',
214
- 'message': msg.text,
215
- 'location': log_entry['location'],
216
- 'stack_trace': log_entry['stack_trace'],
217
- 'timestamp': log_entry['timestamp']
218
- }
219
- # Capture context asynchronously (don't block the event handler)
220
- asyncio.create_task(self._collect_error_context_async(error_event))
221
-
222
- elif msg.type == "warning":
223
- self.logger.warning(f"Console Warning: {msg.text}")
224
- elif msg.type in ["log", "info"] and any(keyword in msg.text.lower() for keyword in ["error", "failed", "exception"]):
225
- # Catch application logs that indicate errors
226
- self.logger.warning(f"App Error Log: {msg.text}")
192
+ try:
193
+ log_entry = {
194
+ "timestamp": time.time(),
195
+ "type": msg.type,
196
+ "text": msg.text,
197
+ "location": {
198
+ "url": msg.location.get("url", "") if msg.location else "",
199
+ "line": msg.location.get("lineNumber", 0) if msg.location else 0,
200
+ "column": msg.location.get("columnNumber", 0) if msg.location else 0
201
+ },
202
+ "args": [str(arg) for arg in msg.args] if msg.args else [],
203
+ "stack_trace": getattr(msg, 'stackTrace', None)
204
+ }
205
+ self.console_logs.append(log_entry)
227
206
 
228
- # v2.0 Enhancement: Collect context for application error logs too
229
- if self.error_context_collector:
230
- error_event = {
231
- 'type': 'app_error_log',
232
- 'message': msg.text,
233
- 'timestamp': log_entry['timestamp']
234
- }
235
- asyncio.create_task(self._collect_error_context_async(error_event))
207
+ # Enhanced logging for better correlation
208
+ if msg.type == "error":
209
+ self.logger.error(f"Console Error: {msg.text} at {msg.location}")
210
+
211
+ # v2.0 Enhancement: Trigger error context collection for console errors
212
+ if self.error_context_collector:
213
+ error_event = {
214
+ 'type': 'console_error',
215
+ 'message': msg.text,
216
+ 'location': log_entry['location'],
217
+ 'stack_trace': log_entry['stack_trace'],
218
+ 'timestamp': log_entry['timestamp']
219
+ }
220
+ # Capture context asynchronously (don't block the event handler)
221
+ asyncio.create_task(self._collect_error_context_async(error_event))
222
+
223
+ elif msg.type == "warning":
224
+ self.logger.warning(f"Console Warning: {msg.text}")
225
+ elif msg.type in ["log", "info"] and any(keyword in msg.text.lower() for keyword in ["error", "failed", "exception"]):
226
+ # Catch application logs that indicate errors
227
+ self.logger.warning(f"App Error Log: {msg.text}")
228
+
229
+ # v2.0 Enhancement: Collect context for application error logs too
230
+ if self.error_context_collector:
231
+ error_event = {
232
+ 'type': 'app_error_log',
233
+ 'message': msg.text,
234
+ 'timestamp': log_entry['timestamp']
235
+ }
236
+ asyncio.create_task(self._collect_error_context_async(error_event))
237
+ except Exception as e:
238
+ # Defensive error handling - don't let console message parsing break tests
239
+ self.logger.debug(f"Console message handler error: {e}")
240
+ # Continue test execution despite error
236
241
 
237
242
  def _handle_request(self, request):
238
243
  """Handle network requests - framework agnostic"""
239
- # Capture complete request data
240
- request_data = {
241
- "timestamp": time.time(),
242
- "type": "request",
243
- "url": request.url,
244
- "method": request.method,
245
- "headers": dict(request.headers),
246
- "resource_type": request.resource_type, # document, xhr, fetch, etc.
247
- "is_navigation_request": request.is_navigation_request()
248
- }
249
-
250
- # Capture complete payload data for all request types
251
- if request.post_data:
252
- request_data["post_data"] = request.post_data
253
- request_data["post_data_size"] = len(request.post_data)
244
+ try:
245
+ # Capture complete request data
246
+ request_data = {
247
+ "timestamp": time.time(),
248
+ "type": "request",
249
+ "url": request.url,
250
+ "method": request.method,
251
+ "headers": dict(request.headers),
252
+ "resource_type": request.resource_type, # document, xhr, fetch, etc.
253
+ "is_navigation_request": request.is_navigation_request()
254
+ }
254
255
 
255
- # Try to parse JSON payloads for better debugging
256
- content_type = request.headers.get("content-type", "")
257
- if "application/json" in content_type:
258
- try:
259
- import json
260
- request_data["parsed_json"] = json.loads(request.post_data)
261
- except:
262
- pass
263
- elif "application/x-www-form-urlencoded" in content_type:
264
- try:
265
- from urllib.parse import parse_qs
266
- request_data["parsed_form"] = parse_qs(request.post_data)
267
- except:
268
- pass
269
-
270
- # Capture query parameters
271
- from urllib.parse import urlparse, parse_qs
272
- parsed_url = urlparse(request.url)
273
- if parsed_url.query:
274
- request_data["query_params"] = parse_qs(parsed_url.query)
275
-
276
- # Capture file uploads
277
- if "multipart/form-data" in request.headers.get("content-type", ""):
278
- request_data["has_file_upload"] = True
279
- # Note: Actual file content not captured for performance/privacy
280
-
281
- self.network_requests.append(request_data)
282
-
283
- # Enhanced logging for correlation
284
- if request.resource_type in ["xhr", "fetch"] or "/api/" in request.url:
285
- payload_info = ""
286
- if request.post_data:
287
- payload_info = f" (payload: {len(request.post_data)} bytes)"
288
- self.logger.debug(f"API Request: {request.method} {request.url}{payload_info}")
289
-
290
- # Log critical data for immediate debugging
291
- if request.post_data and len(request.post_data) < 500: # Only log small payloads
292
- self.logger.debug(f"Request payload: {request.post_data}")
256
+ # Capture complete payload data for all request types
257
+ # Wrap in try/except to handle gzip-compressed data gracefully
258
+ try:
259
+ if request.post_data:
260
+ request_data["post_data"] = request.post_data
261
+ request_data["post_data_size"] = len(request.post_data)
262
+
263
+ # Try to parse JSON payloads for better debugging
264
+ content_type = request.headers.get("content-type", "")
265
+ if "application/json" in content_type:
266
+ try:
267
+ import json
268
+ request_data["parsed_json"] = json.loads(request.post_data)
269
+ except:
270
+ pass
271
+ elif "application/x-www-form-urlencoded" in content_type:
272
+ try:
273
+ from urllib.parse import parse_qs
274
+ request_data["parsed_form"] = parse_qs(request.post_data)
275
+ except:
276
+ pass
277
+ except UnicodeDecodeError:
278
+ # Handle gzip-compressed or binary data gracefully
279
+ # This happens when Playwright can't decode the post_data (e.g., gzip magic bytes 0x1f 0x8b)
280
+ request_data["post_data"] = "[binary/compressed data]"
281
+ request_data["post_data_size"] = 0
282
+ self.logger.debug(f"Binary/compressed POST data detected for {request.url}")
283
+ except Exception as e:
284
+ # Graceful degradation - don't let post_data parsing break request tracking
285
+ request_data["post_data"] = None
286
+ request_data["post_data_size"] = 0
287
+ self.logger.debug(f"Could not capture post_data for {request.url}: {e}")
288
+
289
+ # Capture query parameters
290
+ from urllib.parse import urlparse, parse_qs
291
+ parsed_url = urlparse(request.url)
292
+ if parsed_url.query:
293
+ request_data["query_params"] = parse_qs(parsed_url.query)
294
+
295
+ # Capture file uploads
296
+ if "multipart/form-data" in request.headers.get("content-type", ""):
297
+ request_data["has_file_upload"] = True
298
+ # Note: Actual file content not captured for performance/privacy
299
+
300
+ self.network_requests.append(request_data)
301
+
302
+ # Enhanced logging for correlation
303
+ if request.resource_type in ["xhr", "fetch"] or "/api/" in request.url:
304
+ payload_info = ""
305
+ post_data_value = request_data.get("post_data")
306
+ post_data_size = request_data.get("post_data_size", 0)
307
+ if post_data_value and post_data_size > 0:
308
+ payload_info = f" (payload: {post_data_size} bytes)"
309
+ self.logger.debug(f"API Request: {request.method} {request.url}{payload_info}")
310
+
311
+ # Log critical data for immediate debugging
312
+ if post_data_value and post_data_size > 0 and post_data_size < 500: # Only log small payloads
313
+ self.logger.debug(f"Request payload: {post_data_value}")
314
+ except Exception as e:
315
+ # Top-level defensive error handling - don't let request handler break event listeners
316
+ self.logger.debug(f"Request handler error: {e}")
317
+ # Continue test execution despite error
293
318
 
294
319
  def _handle_response(self, response):
295
320
  """Handle network responses with enhanced error context collection"""
296
- response_data = {
297
- "timestamp": time.time(),
298
- "type": "response",
299
- "url": response.url,
300
- "status": response.status,
301
- "status_text": response.status_text,
302
- "headers": dict(response.headers),
303
- "size": 0, # Will be populated by _capture_response_body if needed
304
- "from_cache": response.from_service_worker or False
305
- }
306
- self.network_requests.append(response_data)
307
-
308
- # Capture response body asynchronously (Phase 1.4: Network Response Body Capture)
309
- asyncio.create_task(self._capture_response_body_async(response, response_data))
310
-
311
- # Log failed requests for correlation
312
- if response.status >= 400:
313
- self.logger.warning(f"Failed Response: {response.status} {response.url}")
321
+ try:
322
+ response_data = {
323
+ "timestamp": time.time(),
324
+ "type": "response",
325
+ "url": response.url,
326
+ "status": response.status,
327
+ "status_text": response.status_text,
328
+ "headers": dict(response.headers),
329
+ "size": 0, # Will be populated by _capture_response_body if needed
330
+ "from_cache": response.from_service_worker or False
331
+ }
332
+ self.network_requests.append(response_data)
314
333
 
315
- # v2.0 Enhancement: Trigger error context collection for failed requests
316
- if self.error_context_collector:
317
- error_event = {
318
- 'type': 'network_error',
319
- 'url': response.url,
320
- 'status': response.status,
321
- 'status_text': response.status_text,
322
- 'headers': dict(response.headers),
323
- 'timestamp': response_data['timestamp']
324
- }
325
- # Capture context asynchronously
326
- asyncio.create_task(self._collect_error_context_async(error_event))
327
-
328
- # Capture response body for important requests
329
- should_capture_body = (
330
- response.status >= 400 or # All error responses
331
- any(api_path in response.url for api_path in ["/api/", "/ajax", ".json"]) or # API calls
332
- "application/json" in response.headers.get("content-type", "") # JSON responses
333
- )
334
-
335
- if should_capture_body:
336
- asyncio.create_task(self._capture_response_body(response))
334
+ # Capture response body asynchronously (Phase 1.4: Network Response Body Capture)
335
+ asyncio.create_task(self._capture_response_body_async(response, response_data))
336
+
337
+ # Log failed requests for correlation
338
+ if response.status >= 400:
339
+ self.logger.warning(f"Failed Response: {response.status} {response.url}")
340
+
341
+ # v2.0 Enhancement: Trigger error context collection for failed requests
342
+ if self.error_context_collector:
343
+ error_event = {
344
+ 'type': 'network_error',
345
+ 'url': response.url,
346
+ 'status': response.status,
347
+ 'status_text': response.status_text,
348
+ 'headers': dict(response.headers),
349
+ 'timestamp': response_data['timestamp']
350
+ }
351
+ # Capture context asynchronously
352
+ asyncio.create_task(self._collect_error_context_async(error_event))
353
+
354
+ # Capture response body for important requests
355
+ should_capture_body = (
356
+ response.status >= 400 or # All error responses
357
+ any(api_path in response.url for api_path in ["/api/", "/ajax", ".json"]) or # API calls
358
+ "application/json" in response.headers.get("content-type", "") # JSON responses
359
+ )
360
+
361
+ if should_capture_body:
362
+ asyncio.create_task(self._capture_response_body(response))
363
+ except Exception as e:
364
+ # Defensive error handling - don't let response handler break event listeners
365
+ self.logger.debug(f"Response handler error: {e}")
366
+ # Continue test execution despite error
337
367
 
338
368
  def _handle_page_error(self, error):
339
369
  """Handle page errors from any framework"""
340
- self.console_logs.append({
341
- "timestamp": time.time(),
342
- "type": "pageerror",
343
- "text": str(error),
344
- "location": None
345
- })
346
- self.logger.error(f"Page error: {error}")
370
+ try:
371
+ self.console_logs.append({
372
+ "timestamp": time.time(),
373
+ "type": "pageerror",
374
+ "text": str(error),
375
+ "location": None
376
+ })
377
+ self.logger.error(f"Page error: {error}")
378
+ except Exception as e:
379
+ # Defensive error handling - don't let page error handler break event listeners
380
+ self.logger.debug(f"Page error handler error: {e}")
381
+ # Continue test execution despite error
347
382
 
348
383
  def _handle_page_crash(self, page):
349
384
  """Handle page crashes"""
350
- self.logger.error("Page crashed - attempting recovery")
385
+ try:
386
+ self.logger.error("Page crashed - attempting recovery")
387
+ except Exception as e:
388
+ # Defensive error handling - don't let crash handler break event listeners
389
+ self.logger.debug(f"Page crash handler error: {e}")
390
+ # Continue test execution despite error
351
391
 
352
392
  async def navigate(self, path: str, wait_for_load: bool = True):
353
393
  """Navigate to URL - works with any web framework"""
@@ -2481,15 +2521,28 @@ class BrowserController:
2481
2521
  resource_analysis: Dict = None, storage_analysis: Dict = None) -> Dict[str, Any]:
2482
2522
  """Enhanced analysis summary with v2.0 comprehensive data"""
2483
2523
  try:
2524
+ # Safe value extraction with null-safety
2525
+ error_count = console_data.get("console_summary", {}).get("error_count") or 0
2526
+ failed_requests = network_data.get("failed_requests", {}).get("count") or 0
2527
+ page_load_time = performance_data.get("performance_summary", {}).get("page_load_time") or 0
2528
+
2529
+ # Ensure numeric values are actually numeric (not None)
2530
+ if not isinstance(error_count, (int, float)):
2531
+ error_count = 0
2532
+ if not isinstance(failed_requests, (int, float)):
2533
+ failed_requests = 0
2534
+ if not isinstance(page_load_time, (int, float)):
2535
+ page_load_time = 0
2536
+
2484
2537
  # Base summary (v1.x compatibility)
2485
2538
  summary = {
2486
2539
  "page_health": {
2487
2540
  "dom_elements_count": dom_analysis.get("totalElements", 0),
2488
- "has_errors": console_data.get("console_summary", {}).get("error_count", 0) > 0,
2489
- "error_count": console_data.get("console_summary", {}).get("error_count", 0),
2541
+ "has_errors": error_count > 0,
2542
+ "error_count": error_count,
2490
2543
  "warning_count": console_data.get("console_summary", {}).get("warning_count", 0),
2491
2544
  "failed_requests": network_data.get("network_summary", {}).get("failed_requests", 0),
2492
- "page_load_time_ms": performance_data.get("performance_summary", {}).get("page_load_time", 0)
2545
+ "page_load_time_ms": page_load_time
2493
2546
  },
2494
2547
  "interaction_readiness": {
2495
2548
  "interactive_elements": dom_analysis.get("pageStructure", {}).get("interactiveElements", 0),
@@ -2505,12 +2558,12 @@ class BrowserController:
2505
2558
  },
2506
2559
  "quality_indicators": {
2507
2560
  "has_console_errors": console_data.get("console_summary", {}).get("has_recent_errors", False),
2508
- "has_failed_requests": network_data.get("failed_requests", {}).get("count", 0) > 0,
2561
+ "has_failed_requests": failed_requests > 0,
2509
2562
  "performance_score": self._calculate_performance_score(performance_data),
2510
2563
  "overall_health": "good" if (
2511
- console_data.get("console_summary", {}).get("error_count", 0) == 0 and
2512
- network_data.get("failed_requests", {}).get("count", 0) == 0 and
2513
- performance_data.get("performance_summary", {}).get("page_load_time", 0) < 3000
2564
+ error_count == 0 and
2565
+ failed_requests == 0 and
2566
+ page_load_time < 3000
2514
2567
  ) else "needs_attention"
2515
2568
  }
2516
2569
  }
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cursorflow"
7
- version = "2.6.1"
7
+ version = "2.6.3"
8
8
  description = "🔥 Complete page intelligence for AI-driven development with Hot Reload Intelligence - captures DOM, network, console, performance, HMR events, and comprehensive page analysis"
9
9
  authors = [
10
10
  {name = "GeekWarrior Development", email = "rbush@cooltheory.com"}
File without changes
File without changes
File without changes
File without changes
File without changes