tapps-agents 3.6.0__py3-none-any.whl → 3.6.1__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.
- tapps_agents/__init__.py +2 -2
- tapps_agents/agents/__init__.py +22 -22
- tapps_agents/agents/analyst/__init__.py +5 -5
- tapps_agents/agents/architect/__init__.py +5 -5
- tapps_agents/agents/architect/agent.py +1033 -1033
- tapps_agents/agents/architect/pattern_detector.py +75 -75
- tapps_agents/agents/cleanup/__init__.py +7 -7
- tapps_agents/agents/cleanup/agent.py +445 -445
- tapps_agents/agents/debugger/__init__.py +7 -7
- tapps_agents/agents/debugger/agent.py +310 -310
- tapps_agents/agents/debugger/error_analyzer.py +437 -437
- tapps_agents/agents/designer/__init__.py +5 -5
- tapps_agents/agents/designer/agent.py +786 -786
- tapps_agents/agents/designer/visual_designer.py +638 -638
- tapps_agents/agents/documenter/__init__.py +7 -7
- tapps_agents/agents/documenter/agent.py +531 -531
- tapps_agents/agents/documenter/doc_generator.py +472 -472
- tapps_agents/agents/documenter/doc_validator.py +393 -393
- tapps_agents/agents/documenter/framework_doc_updater.py +493 -493
- tapps_agents/agents/enhancer/__init__.py +7 -7
- tapps_agents/agents/evaluator/__init__.py +7 -7
- tapps_agents/agents/evaluator/agent.py +443 -443
- tapps_agents/agents/evaluator/priority_evaluator.py +641 -641
- tapps_agents/agents/evaluator/quality_analyzer.py +147 -147
- tapps_agents/agents/evaluator/report_generator.py +344 -344
- tapps_agents/agents/evaluator/usage_analyzer.py +192 -192
- tapps_agents/agents/evaluator/workflow_analyzer.py +189 -189
- tapps_agents/agents/implementer/__init__.py +7 -7
- tapps_agents/agents/implementer/agent.py +798 -798
- tapps_agents/agents/implementer/auto_fix.py +1119 -1119
- tapps_agents/agents/implementer/code_generator.py +73 -73
- tapps_agents/agents/improver/__init__.py +1 -1
- tapps_agents/agents/improver/agent.py +753 -753
- tapps_agents/agents/ops/__init__.py +1 -1
- tapps_agents/agents/ops/agent.py +619 -619
- tapps_agents/agents/ops/dependency_analyzer.py +600 -600
- tapps_agents/agents/orchestrator/__init__.py +5 -5
- tapps_agents/agents/orchestrator/agent.py +522 -522
- tapps_agents/agents/planner/__init__.py +7 -7
- tapps_agents/agents/planner/agent.py +1127 -1127
- tapps_agents/agents/reviewer/__init__.py +24 -24
- tapps_agents/agents/reviewer/agent.py +3513 -3513
- tapps_agents/agents/reviewer/aggregator.py +213 -213
- tapps_agents/agents/reviewer/batch_review.py +448 -448
- tapps_agents/agents/reviewer/cache.py +443 -443
- tapps_agents/agents/reviewer/context7_enhancer.py +630 -630
- tapps_agents/agents/reviewer/context_detector.py +203 -203
- tapps_agents/agents/reviewer/docker_compose_validator.py +158 -158
- tapps_agents/agents/reviewer/dockerfile_validator.py +176 -176
- tapps_agents/agents/reviewer/error_handling.py +126 -126
- tapps_agents/agents/reviewer/feedback_generator.py +490 -490
- tapps_agents/agents/reviewer/influxdb_validator.py +316 -316
- tapps_agents/agents/reviewer/issue_tracking.py +169 -169
- tapps_agents/agents/reviewer/library_detector.py +295 -295
- tapps_agents/agents/reviewer/library_patterns.py +268 -268
- tapps_agents/agents/reviewer/maintainability_scorer.py +593 -593
- tapps_agents/agents/reviewer/metric_strategies.py +276 -276
- tapps_agents/agents/reviewer/mqtt_validator.py +160 -160
- tapps_agents/agents/reviewer/output_enhancer.py +105 -105
- tapps_agents/agents/reviewer/pattern_detector.py +241 -241
- tapps_agents/agents/reviewer/performance_scorer.py +357 -357
- tapps_agents/agents/reviewer/phased_review.py +516 -516
- tapps_agents/agents/reviewer/progressive_review.py +435 -435
- tapps_agents/agents/reviewer/react_scorer.py +331 -331
- tapps_agents/agents/reviewer/score_constants.py +228 -228
- tapps_agents/agents/reviewer/score_validator.py +507 -507
- tapps_agents/agents/reviewer/scorer_registry.py +373 -373
- tapps_agents/agents/reviewer/service_discovery.py +534 -534
- tapps_agents/agents/reviewer/tools/parallel_executor.py +581 -581
- tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -250
- tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -284
- tapps_agents/agents/reviewer/typescript_scorer.py +1142 -1142
- tapps_agents/agents/reviewer/validation.py +208 -208
- tapps_agents/agents/reviewer/websocket_validator.py +132 -132
- tapps_agents/agents/tester/__init__.py +7 -7
- tapps_agents/agents/tester/accessibility_auditor.py +309 -309
- tapps_agents/agents/tester/agent.py +1080 -1080
- tapps_agents/agents/tester/batch_generator.py +54 -54
- tapps_agents/agents/tester/context_learner.py +51 -51
- tapps_agents/agents/tester/coverage_analyzer.py +386 -386
- tapps_agents/agents/tester/coverage_test_generator.py +290 -290
- tapps_agents/agents/tester/debug_enhancer.py +238 -238
- tapps_agents/agents/tester/device_emulator.py +241 -241
- tapps_agents/agents/tester/integration_generator.py +62 -62
- tapps_agents/agents/tester/network_recorder.py +300 -300
- tapps_agents/agents/tester/performance_monitor.py +320 -320
- tapps_agents/agents/tester/test_fixer.py +316 -316
- tapps_agents/agents/tester/test_generator.py +632 -632
- tapps_agents/agents/tester/trace_manager.py +234 -234
- tapps_agents/agents/tester/visual_regression.py +291 -291
- tapps_agents/analysis/pattern_detector.py +36 -36
- tapps_agents/beads/hydration.py +213 -213
- tapps_agents/beads/parse.py +32 -32
- tapps_agents/beads/specs.py +206 -206
- tapps_agents/cli/__init__.py +9 -9
- tapps_agents/cli/__main__.py +8 -8
- tapps_agents/cli/base.py +478 -478
- tapps_agents/cli/command_classifier.py +72 -72
- tapps_agents/cli/commands/__init__.py +2 -2
- tapps_agents/cli/commands/analyst.py +173 -173
- tapps_agents/cli/commands/architect.py +109 -109
- tapps_agents/cli/commands/cleanup_agent.py +92 -92
- tapps_agents/cli/commands/common.py +126 -126
- tapps_agents/cli/commands/debugger.py +90 -90
- tapps_agents/cli/commands/designer.py +112 -112
- tapps_agents/cli/commands/documenter.py +136 -136
- tapps_agents/cli/commands/enhancer.py +110 -110
- tapps_agents/cli/commands/evaluator.py +255 -255
- tapps_agents/cli/commands/implementer.py +301 -301
- tapps_agents/cli/commands/improver.py +91 -91
- tapps_agents/cli/commands/knowledge.py +111 -111
- tapps_agents/cli/commands/learning.py +172 -172
- tapps_agents/cli/commands/observability.py +283 -283
- tapps_agents/cli/commands/ops.py +135 -135
- tapps_agents/cli/commands/orchestrator.py +116 -116
- tapps_agents/cli/commands/planner.py +237 -237
- tapps_agents/cli/commands/reviewer.py +1872 -1872
- tapps_agents/cli/commands/status.py +285 -285
- tapps_agents/cli/commands/task.py +227 -227
- tapps_agents/cli/commands/tester.py +191 -191
- tapps_agents/cli/feedback.py +936 -936
- tapps_agents/cli/formatters.py +608 -608
- tapps_agents/cli/help/__init__.py +7 -7
- tapps_agents/cli/help/static_help.py +425 -425
- tapps_agents/cli/network_detection.py +110 -110
- tapps_agents/cli/output_compactor.py +274 -274
- tapps_agents/cli/parsers/__init__.py +2 -2
- tapps_agents/cli/parsers/analyst.py +186 -186
- tapps_agents/cli/parsers/architect.py +167 -167
- tapps_agents/cli/parsers/cleanup_agent.py +228 -228
- tapps_agents/cli/parsers/debugger.py +116 -116
- tapps_agents/cli/parsers/designer.py +182 -182
- tapps_agents/cli/parsers/documenter.py +134 -134
- tapps_agents/cli/parsers/enhancer.py +113 -113
- tapps_agents/cli/parsers/evaluator.py +213 -213
- tapps_agents/cli/parsers/implementer.py +168 -168
- tapps_agents/cli/parsers/improver.py +132 -132
- tapps_agents/cli/parsers/ops.py +159 -159
- tapps_agents/cli/parsers/orchestrator.py +98 -98
- tapps_agents/cli/parsers/planner.py +145 -145
- tapps_agents/cli/parsers/reviewer.py +462 -462
- tapps_agents/cli/parsers/tester.py +124 -124
- tapps_agents/cli/progress_heartbeat.py +254 -254
- tapps_agents/cli/streaming_progress.py +336 -336
- tapps_agents/cli/utils/__init__.py +6 -6
- tapps_agents/cli/utils/agent_lifecycle.py +48 -48
- tapps_agents/cli/utils/error_formatter.py +82 -82
- tapps_agents/cli/utils/error_recovery.py +188 -188
- tapps_agents/cli/utils/output_handler.py +59 -59
- tapps_agents/cli/utils/prompt_enhancer.py +319 -319
- tapps_agents/cli/validators/__init__.py +9 -9
- tapps_agents/cli/validators/command_validator.py +81 -81
- tapps_agents/context7/__init__.py +112 -112
- tapps_agents/context7/agent_integration.py +869 -869
- tapps_agents/context7/analytics.py +382 -382
- tapps_agents/context7/analytics_dashboard.py +299 -299
- tapps_agents/context7/async_cache.py +681 -681
- tapps_agents/context7/backup_client.py +958 -958
- tapps_agents/context7/cache_locking.py +194 -194
- tapps_agents/context7/cache_metadata.py +214 -214
- tapps_agents/context7/cache_prewarm.py +488 -488
- tapps_agents/context7/cache_structure.py +168 -168
- tapps_agents/context7/cache_warming.py +604 -604
- tapps_agents/context7/circuit_breaker.py +376 -376
- tapps_agents/context7/cleanup.py +461 -461
- tapps_agents/context7/commands.py +858 -858
- tapps_agents/context7/credential_validation.py +276 -276
- tapps_agents/context7/cross_reference_resolver.py +168 -168
- tapps_agents/context7/cross_references.py +424 -424
- tapps_agents/context7/doc_manager.py +225 -225
- tapps_agents/context7/fuzzy_matcher.py +369 -369
- tapps_agents/context7/kb_cache.py +404 -404
- tapps_agents/context7/language_detector.py +219 -219
- tapps_agents/context7/library_detector.py +725 -725
- tapps_agents/context7/lookup.py +738 -738
- tapps_agents/context7/metadata.py +258 -258
- tapps_agents/context7/refresh_queue.py +300 -300
- tapps_agents/context7/security.py +373 -373
- tapps_agents/context7/staleness_policies.py +278 -278
- tapps_agents/context7/tiles_integration.py +47 -47
- tapps_agents/continuous_bug_fix/__init__.py +20 -20
- tapps_agents/continuous_bug_fix/bug_finder.py +306 -306
- tapps_agents/continuous_bug_fix/bug_fix_coordinator.py +177 -177
- tapps_agents/continuous_bug_fix/commit_manager.py +178 -178
- tapps_agents/continuous_bug_fix/continuous_bug_fixer.py +322 -322
- tapps_agents/continuous_bug_fix/proactive_bug_finder.py +285 -285
- tapps_agents/core/__init__.py +298 -298
- tapps_agents/core/adaptive_cache_config.py +432 -432
- tapps_agents/core/agent_base.py +647 -647
- tapps_agents/core/agent_cache.py +466 -466
- tapps_agents/core/agent_learning.py +1865 -1865
- tapps_agents/core/analytics_dashboard.py +563 -563
- tapps_agents/core/analytics_enhancements.py +597 -597
- tapps_agents/core/anonymization.py +274 -274
- tapps_agents/core/ast_parser.py +228 -228
- tapps_agents/core/async_file_ops.py +402 -402
- tapps_agents/core/best_practice_consultant.py +299 -299
- tapps_agents/core/brownfield_analyzer.py +299 -299
- tapps_agents/core/brownfield_review.py +541 -541
- tapps_agents/core/browser_controller.py +513 -513
- tapps_agents/core/capability_registry.py +418 -418
- tapps_agents/core/change_impact_analyzer.py +190 -190
- tapps_agents/core/checkpoint_manager.py +377 -377
- tapps_agents/core/code_generator.py +329 -329
- tapps_agents/core/code_validator.py +276 -276
- tapps_agents/core/command_registry.py +327 -327
- tapps_agents/core/context_gathering/__init__.py +2 -2
- tapps_agents/core/context_gathering/repository_explorer.py +28 -28
- tapps_agents/core/context_intelligence/__init__.py +2 -2
- tapps_agents/core/context_intelligence/relevance_scorer.py +24 -24
- tapps_agents/core/context_intelligence/token_budget_manager.py +27 -27
- tapps_agents/core/context_manager.py +240 -240
- tapps_agents/core/cursor_feedback_monitor.py +146 -146
- tapps_agents/core/cursor_verification.py +290 -290
- tapps_agents/core/customization_loader.py +280 -280
- tapps_agents/core/customization_schema.py +260 -260
- tapps_agents/core/customization_template.py +238 -238
- tapps_agents/core/debug_logger.py +124 -124
- tapps_agents/core/design_validator.py +298 -298
- tapps_agents/core/diagram_generator.py +226 -226
- tapps_agents/core/docker_utils.py +232 -232
- tapps_agents/core/document_generator.py +617 -617
- tapps_agents/core/domain_detector.py +30 -30
- tapps_agents/core/error_envelope.py +454 -454
- tapps_agents/core/error_handler.py +270 -270
- tapps_agents/core/estimation_tracker.py +189 -189
- tapps_agents/core/eval_prompt_engine.py +116 -116
- tapps_agents/core/evaluation_base.py +119 -119
- tapps_agents/core/evaluation_models.py +320 -320
- tapps_agents/core/evaluation_orchestrator.py +225 -225
- tapps_agents/core/evaluators/__init__.py +7 -7
- tapps_agents/core/evaluators/architectural_evaluator.py +205 -205
- tapps_agents/core/evaluators/behavioral_evaluator.py +160 -160
- tapps_agents/core/evaluators/performance_profile_evaluator.py +160 -160
- tapps_agents/core/evaluators/security_posture_evaluator.py +148 -148
- tapps_agents/core/evaluators/spec_compliance_evaluator.py +181 -181
- tapps_agents/core/exceptions.py +107 -107
- tapps_agents/core/expert_config_generator.py +293 -293
- tapps_agents/core/export_schema.py +202 -202
- tapps_agents/core/external_feedback_models.py +102 -102
- tapps_agents/core/external_feedback_storage.py +213 -213
- tapps_agents/core/fallback_strategy.py +314 -314
- tapps_agents/core/feedback_analyzer.py +162 -162
- tapps_agents/core/feedback_collector.py +178 -178
- tapps_agents/core/git_operations.py +445 -445
- tapps_agents/core/hardware_profiler.py +151 -151
- tapps_agents/core/instructions.py +324 -324
- tapps_agents/core/io_guardrails.py +69 -69
- tapps_agents/core/issue_manifest.py +249 -249
- tapps_agents/core/issue_schema.py +139 -139
- tapps_agents/core/json_utils.py +128 -128
- tapps_agents/core/knowledge_graph.py +446 -446
- tapps_agents/core/language_detector.py +296 -296
- tapps_agents/core/learning_confidence.py +242 -242
- tapps_agents/core/learning_dashboard.py +246 -246
- tapps_agents/core/learning_decision.py +384 -384
- tapps_agents/core/learning_explainability.py +578 -578
- tapps_agents/core/learning_export.py +287 -287
- tapps_agents/core/learning_integration.py +228 -228
- tapps_agents/core/llm_behavior.py +232 -232
- tapps_agents/core/long_duration_support.py +786 -786
- tapps_agents/core/mcp_setup.py +106 -106
- tapps_agents/core/memory_integration.py +396 -396
- tapps_agents/core/meta_learning.py +666 -666
- tapps_agents/core/module_path_sanitizer.py +199 -199
- tapps_agents/core/multi_agent_orchestrator.py +382 -382
- tapps_agents/core/network_errors.py +125 -125
- tapps_agents/core/nfr_validator.py +336 -336
- tapps_agents/core/offline_mode.py +158 -158
- tapps_agents/core/output_contracts.py +300 -300
- tapps_agents/core/output_formatter.py +300 -300
- tapps_agents/core/path_normalizer.py +174 -174
- tapps_agents/core/path_validator.py +322 -322
- tapps_agents/core/pattern_library.py +250 -250
- tapps_agents/core/performance_benchmark.py +301 -301
- tapps_agents/core/performance_monitor.py +184 -184
- tapps_agents/core/playwright_mcp_controller.py +771 -771
- tapps_agents/core/policy_loader.py +135 -135
- tapps_agents/core/progress.py +166 -166
- tapps_agents/core/project_profile.py +354 -354
- tapps_agents/core/project_type_detector.py +454 -454
- tapps_agents/core/prompt_base.py +223 -223
- tapps_agents/core/prompt_learning/__init__.py +2 -2
- tapps_agents/core/prompt_learning/learning_loop.py +24 -24
- tapps_agents/core/prompt_learning/project_prompt_store.py +25 -25
- tapps_agents/core/prompt_learning/skills_prompt_analyzer.py +35 -35
- tapps_agents/core/prompt_optimization/__init__.py +6 -6
- tapps_agents/core/prompt_optimization/ab_tester.py +114 -114
- tapps_agents/core/prompt_optimization/correlation_analyzer.py +160 -160
- tapps_agents/core/prompt_optimization/progressive_refiner.py +129 -129
- tapps_agents/core/prompt_optimization/prompt_library.py +37 -37
- tapps_agents/core/requirements_evaluator.py +431 -431
- tapps_agents/core/resource_aware_executor.py +449 -449
- tapps_agents/core/resource_monitor.py +343 -343
- tapps_agents/core/resume_handler.py +298 -298
- tapps_agents/core/retry_handler.py +197 -197
- tapps_agents/core/review_checklists.py +479 -479
- tapps_agents/core/role_loader.py +201 -201
- tapps_agents/core/role_template_loader.py +201 -201
- tapps_agents/core/runtime_mode.py +60 -60
- tapps_agents/core/security_scanner.py +342 -342
- tapps_agents/core/skill_agent_registry.py +194 -194
- tapps_agents/core/skill_integration.py +208 -208
- tapps_agents/core/skill_loader.py +492 -492
- tapps_agents/core/skill_template.py +341 -341
- tapps_agents/core/skill_validator.py +478 -478
- tapps_agents/core/stack_analyzer.py +35 -35
- tapps_agents/core/startup.py +174 -174
- tapps_agents/core/storage_manager.py +397 -397
- tapps_agents/core/storage_models.py +166 -166
- tapps_agents/core/story_evaluator.py +410 -410
- tapps_agents/core/subprocess_utils.py +170 -170
- tapps_agents/core/task_duration.py +296 -296
- tapps_agents/core/task_memory.py +582 -582
- tapps_agents/core/task_state.py +226 -226
- tapps_agents/core/tech_stack_priorities.py +208 -208
- tapps_agents/core/temp_directory.py +194 -194
- tapps_agents/core/template_merger.py +600 -600
- tapps_agents/core/template_selector.py +280 -280
- tapps_agents/core/test_generator.py +286 -286
- tapps_agents/core/tiered_context.py +253 -253
- tapps_agents/core/token_monitor.py +345 -345
- tapps_agents/core/traceability.py +254 -254
- tapps_agents/core/trajectory_tracker.py +50 -50
- tapps_agents/core/unicode_safe.py +143 -143
- tapps_agents/core/unified_cache_config.py +170 -170
- tapps_agents/core/unified_state.py +324 -324
- tapps_agents/core/validate_cursor_setup.py +237 -237
- tapps_agents/core/validation_registry.py +136 -136
- tapps_agents/core/validators/__init__.py +4 -4
- tapps_agents/core/validators/python_validator.py +87 -87
- tapps_agents/core/verification_agent.py +90 -90
- tapps_agents/core/visual_feedback.py +644 -644
- tapps_agents/core/workflow_validator.py +197 -197
- tapps_agents/core/worktree.py +367 -367
- tapps_agents/docker/__init__.py +10 -10
- tapps_agents/docker/analyzer.py +186 -186
- tapps_agents/docker/debugger.py +229 -229
- tapps_agents/docker/error_patterns.py +216 -216
- tapps_agents/epic/__init__.py +22 -22
- tapps_agents/epic/beads_sync.py +115 -115
- tapps_agents/epic/markdown_sync.py +105 -105
- tapps_agents/epic/models.py +96 -96
- tapps_agents/experts/__init__.py +163 -163
- tapps_agents/experts/agent_integration.py +243 -243
- tapps_agents/experts/auto_generator.py +331 -331
- tapps_agents/experts/base_expert.py +536 -536
- tapps_agents/experts/builtin_registry.py +261 -261
- tapps_agents/experts/business_metrics.py +565 -565
- tapps_agents/experts/cache.py +266 -266
- tapps_agents/experts/confidence_breakdown.py +306 -306
- tapps_agents/experts/confidence_calculator.py +336 -336
- tapps_agents/experts/confidence_metrics.py +236 -236
- tapps_agents/experts/domain_config.py +311 -311
- tapps_agents/experts/domain_detector.py +550 -550
- tapps_agents/experts/domain_utils.py +84 -84
- tapps_agents/experts/expert_config.py +113 -113
- tapps_agents/experts/expert_engine.py +465 -465
- tapps_agents/experts/expert_registry.py +744 -744
- tapps_agents/experts/expert_synthesizer.py +70 -70
- tapps_agents/experts/governance.py +197 -197
- tapps_agents/experts/history_logger.py +312 -312
- tapps_agents/experts/knowledge/README.md +180 -180
- tapps_agents/experts/knowledge/accessibility/accessible-forms.md +331 -331
- tapps_agents/experts/knowledge/accessibility/aria-patterns.md +344 -344
- tapps_agents/experts/knowledge/accessibility/color-contrast.md +285 -285
- tapps_agents/experts/knowledge/accessibility/keyboard-navigation.md +332 -332
- tapps_agents/experts/knowledge/accessibility/screen-readers.md +282 -282
- tapps_agents/experts/knowledge/accessibility/semantic-html.md +355 -355
- tapps_agents/experts/knowledge/accessibility/testing-accessibility.md +369 -369
- tapps_agents/experts/knowledge/accessibility/wcag-2.1.md +296 -296
- tapps_agents/experts/knowledge/accessibility/wcag-2.2.md +211 -211
- tapps_agents/experts/knowledge/agent-learning/best-practices.md +715 -715
- tapps_agents/experts/knowledge/agent-learning/pattern-extraction.md +282 -282
- tapps_agents/experts/knowledge/agent-learning/prompt-optimization.md +320 -320
- tapps_agents/experts/knowledge/ai-frameworks/model-optimization.md +90 -90
- tapps_agents/experts/knowledge/ai-frameworks/openvino-patterns.md +260 -260
- tapps_agents/experts/knowledge/api-design-integration/api-gateway-patterns.md +309 -309
- tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +521 -521
- tapps_agents/experts/knowledge/api-design-integration/api-versioning.md +421 -421
- tapps_agents/experts/knowledge/api-design-integration/async-protocol-patterns.md +61 -61
- tapps_agents/experts/knowledge/api-design-integration/contract-testing.md +221 -221
- tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +489 -489
- tapps_agents/experts/knowledge/api-design-integration/fastapi-patterns.md +360 -360
- tapps_agents/experts/knowledge/api-design-integration/fastapi-testing.md +262 -262
- tapps_agents/experts/knowledge/api-design-integration/graphql-patterns.md +582 -582
- tapps_agents/experts/knowledge/api-design-integration/grpc-best-practices.md +499 -499
- tapps_agents/experts/knowledge/api-design-integration/mqtt-patterns.md +455 -455
- tapps_agents/experts/knowledge/api-design-integration/rate-limiting.md +507 -507
- tapps_agents/experts/knowledge/api-design-integration/restful-api-design.md +618 -618
- tapps_agents/experts/knowledge/api-design-integration/websocket-patterns.md +480 -480
- tapps_agents/experts/knowledge/cloud-infrastructure/cloud-native-patterns.md +175 -175
- tapps_agents/experts/knowledge/cloud-infrastructure/container-health-checks.md +261 -261
- tapps_agents/experts/knowledge/cloud-infrastructure/containerization.md +222 -222
- tapps_agents/experts/knowledge/cloud-infrastructure/cost-optimization.md +122 -122
- tapps_agents/experts/knowledge/cloud-infrastructure/disaster-recovery.md +153 -153
- tapps_agents/experts/knowledge/cloud-infrastructure/dockerfile-patterns.md +285 -285
- tapps_agents/experts/knowledge/cloud-infrastructure/infrastructure-as-code.md +187 -187
- tapps_agents/experts/knowledge/cloud-infrastructure/kubernetes-patterns.md +253 -253
- tapps_agents/experts/knowledge/cloud-infrastructure/multi-cloud-strategies.md +155 -155
- tapps_agents/experts/knowledge/cloud-infrastructure/serverless-architecture.md +200 -200
- tapps_agents/experts/knowledge/code-quality-analysis/README.md +16 -16
- tapps_agents/experts/knowledge/code-quality-analysis/code-metrics.md +137 -137
- tapps_agents/experts/knowledge/code-quality-analysis/complexity-analysis.md +181 -181
- tapps_agents/experts/knowledge/code-quality-analysis/technical-debt-patterns.md +191 -191
- tapps_agents/experts/knowledge/data-privacy-compliance/anonymization.md +313 -313
- tapps_agents/experts/knowledge/data-privacy-compliance/ccpa.md +255 -255
- tapps_agents/experts/knowledge/data-privacy-compliance/consent-management.md +282 -282
- tapps_agents/experts/knowledge/data-privacy-compliance/data-minimization.md +275 -275
- tapps_agents/experts/knowledge/data-privacy-compliance/data-retention.md +297 -297
- tapps_agents/experts/knowledge/data-privacy-compliance/data-subject-rights.md +383 -383
- tapps_agents/experts/knowledge/data-privacy-compliance/encryption-privacy.md +285 -285
- tapps_agents/experts/knowledge/data-privacy-compliance/gdpr.md +344 -344
- tapps_agents/experts/knowledge/data-privacy-compliance/hipaa.md +385 -385
- tapps_agents/experts/knowledge/data-privacy-compliance/privacy-by-design.md +280 -280
- tapps_agents/experts/knowledge/database-data-management/acid-vs-cap.md +164 -164
- tapps_agents/experts/knowledge/database-data-management/backup-and-recovery.md +182 -182
- tapps_agents/experts/knowledge/database-data-management/data-modeling.md +172 -172
- tapps_agents/experts/knowledge/database-data-management/database-design.md +187 -187
- tapps_agents/experts/knowledge/database-data-management/flux-query-optimization.md +342 -342
- tapps_agents/experts/knowledge/database-data-management/influxdb-connection-patterns.md +432 -432
- tapps_agents/experts/knowledge/database-data-management/influxdb-patterns.md +442 -442
- tapps_agents/experts/knowledge/database-data-management/migration-strategies.md +216 -216
- tapps_agents/experts/knowledge/database-data-management/nosql-patterns.md +259 -259
- tapps_agents/experts/knowledge/database-data-management/scalability-patterns.md +184 -184
- tapps_agents/experts/knowledge/database-data-management/sql-optimization.md +175 -175
- tapps_agents/experts/knowledge/database-data-management/time-series-modeling.md +444 -444
- tapps_agents/experts/knowledge/development-workflow/README.md +16 -16
- tapps_agents/experts/knowledge/development-workflow/automation-best-practices.md +216 -216
- tapps_agents/experts/knowledge/development-workflow/build-strategies.md +198 -198
- tapps_agents/experts/knowledge/development-workflow/deployment-patterns.md +205 -205
- tapps_agents/experts/knowledge/development-workflow/git-workflows.md +205 -205
- tapps_agents/experts/knowledge/documentation-knowledge-management/README.md +16 -16
- tapps_agents/experts/knowledge/documentation-knowledge-management/api-documentation-patterns.md +231 -231
- tapps_agents/experts/knowledge/documentation-knowledge-management/documentation-standards.md +191 -191
- tapps_agents/experts/knowledge/documentation-knowledge-management/knowledge-management.md +171 -171
- tapps_agents/experts/knowledge/documentation-knowledge-management/technical-writing-guide.md +192 -192
- tapps_agents/experts/knowledge/observability-monitoring/alerting-patterns.md +461 -461
- tapps_agents/experts/knowledge/observability-monitoring/apm-tools.md +459 -459
- tapps_agents/experts/knowledge/observability-monitoring/distributed-tracing.md +367 -367
- tapps_agents/experts/knowledge/observability-monitoring/logging-strategies.md +478 -478
- tapps_agents/experts/knowledge/observability-monitoring/metrics-and-monitoring.md +510 -510
- tapps_agents/experts/knowledge/observability-monitoring/observability-best-practices.md +492 -492
- tapps_agents/experts/knowledge/observability-monitoring/open-telemetry.md +573 -573
- tapps_agents/experts/knowledge/observability-monitoring/slo-sli-sla.md +419 -419
- tapps_agents/experts/knowledge/performance/anti-patterns.md +284 -284
- tapps_agents/experts/knowledge/performance/api-performance.md +256 -256
- tapps_agents/experts/knowledge/performance/caching.md +327 -327
- tapps_agents/experts/knowledge/performance/database-performance.md +252 -252
- tapps_agents/experts/knowledge/performance/optimization-patterns.md +327 -327
- tapps_agents/experts/knowledge/performance/profiling.md +297 -297
- tapps_agents/experts/knowledge/performance/resource-management.md +293 -293
- tapps_agents/experts/knowledge/performance/scalability.md +306 -306
- tapps_agents/experts/knowledge/security/owasp-top10.md +209 -209
- tapps_agents/experts/knowledge/security/secure-coding-practices.md +207 -207
- tapps_agents/experts/knowledge/security/threat-modeling.md +220 -220
- tapps_agents/experts/knowledge/security/vulnerability-patterns.md +342 -342
- tapps_agents/experts/knowledge/software-architecture/docker-compose-patterns.md +314 -314
- tapps_agents/experts/knowledge/software-architecture/microservices-patterns.md +379 -379
- tapps_agents/experts/knowledge/software-architecture/service-communication.md +316 -316
- tapps_agents/experts/knowledge/testing/best-practices.md +310 -310
- tapps_agents/experts/knowledge/testing/coverage-analysis.md +293 -293
- tapps_agents/experts/knowledge/testing/mocking.md +256 -256
- tapps_agents/experts/knowledge/testing/test-automation.md +276 -276
- tapps_agents/experts/knowledge/testing/test-data.md +271 -271
- tapps_agents/experts/knowledge/testing/test-design-patterns.md +280 -280
- tapps_agents/experts/knowledge/testing/test-maintenance.md +236 -236
- tapps_agents/experts/knowledge/testing/test-strategies.md +311 -311
- tapps_agents/experts/knowledge/user-experience/information-architecture.md +325 -325
- tapps_agents/experts/knowledge/user-experience/interaction-design.md +363 -363
- tapps_agents/experts/knowledge/user-experience/prototyping.md +293 -293
- tapps_agents/experts/knowledge/user-experience/usability-heuristics.md +337 -337
- tapps_agents/experts/knowledge/user-experience/usability-testing.md +311 -311
- tapps_agents/experts/knowledge/user-experience/user-journeys.md +296 -296
- tapps_agents/experts/knowledge/user-experience/user-research.md +373 -373
- tapps_agents/experts/knowledge/user-experience/ux-principles.md +340 -340
- tapps_agents/experts/knowledge_freshness.py +321 -321
- tapps_agents/experts/knowledge_ingestion.py +438 -438
- tapps_agents/experts/knowledge_need_detector.py +93 -93
- tapps_agents/experts/knowledge_validator.py +382 -382
- tapps_agents/experts/observability.py +440 -440
- tapps_agents/experts/passive_notifier.py +238 -238
- tapps_agents/experts/proactive_orchestrator.py +32 -32
- tapps_agents/experts/rag_chunker.py +205 -205
- tapps_agents/experts/rag_embedder.py +152 -152
- tapps_agents/experts/rag_evaluation.py +299 -299
- tapps_agents/experts/rag_index.py +303 -303
- tapps_agents/experts/rag_metrics.py +293 -293
- tapps_agents/experts/rag_safety.py +263 -263
- tapps_agents/experts/report_generator.py +296 -296
- tapps_agents/experts/setup_wizard.py +441 -441
- tapps_agents/experts/simple_rag.py +431 -431
- tapps_agents/experts/vector_rag.py +354 -354
- tapps_agents/experts/weight_distributor.py +304 -304
- tapps_agents/health/__init__.py +24 -24
- tapps_agents/health/base.py +75 -75
- tapps_agents/health/checks/__init__.py +22 -22
- tapps_agents/health/checks/automation.py +127 -127
- tapps_agents/health/checks/context7_cache.py +210 -210
- tapps_agents/health/checks/environment.py +116 -116
- tapps_agents/health/checks/execution.py +170 -170
- tapps_agents/health/checks/knowledge_base.py +187 -187
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
- tapps_agents/health/checks/outcomes.py +324 -324
- tapps_agents/health/collector.py +280 -280
- tapps_agents/health/dashboard.py +137 -137
- tapps_agents/health/metrics.py +151 -151
- tapps_agents/health/registry.py +166 -166
- tapps_agents/hooks/__init__.py +33 -33
- tapps_agents/hooks/config.py +140 -140
- tapps_agents/hooks/events.py +135 -135
- tapps_agents/hooks/executor.py +128 -128
- tapps_agents/hooks/manager.py +143 -143
- tapps_agents/integration/__init__.py +8 -8
- tapps_agents/integration/service_integrator.py +121 -121
- tapps_agents/integrations/__init__.py +10 -10
- tapps_agents/integrations/clawdbot.py +525 -525
- tapps_agents/integrations/memory_bridge.py +356 -356
- tapps_agents/mcp/__init__.py +18 -18
- tapps_agents/mcp/gateway.py +112 -112
- tapps_agents/mcp/servers/__init__.py +13 -13
- tapps_agents/mcp/servers/analysis.py +204 -204
- tapps_agents/mcp/servers/context7.py +198 -198
- tapps_agents/mcp/servers/filesystem.py +218 -218
- tapps_agents/mcp/servers/git.py +201 -201
- tapps_agents/mcp/tool_registry.py +115 -115
- tapps_agents/quality/__init__.py +54 -54
- tapps_agents/quality/coverage_analyzer.py +379 -379
- tapps_agents/quality/enforcement.py +82 -82
- tapps_agents/quality/gates/__init__.py +37 -37
- tapps_agents/quality/gates/approval_gate.py +255 -255
- tapps_agents/quality/gates/base.py +84 -84
- tapps_agents/quality/gates/exceptions.py +43 -43
- tapps_agents/quality/gates/policy_gate.py +195 -195
- tapps_agents/quality/gates/registry.py +239 -239
- tapps_agents/quality/gates/security_gate.py +156 -156
- tapps_agents/quality/quality_gates.py +369 -369
- tapps_agents/quality/secret_scanner.py +335 -335
- tapps_agents/resources/__init__.py +5 -0
- tapps_agents/resources/claude/__init__.py +1 -0
- tapps_agents/resources/claude/commands/README.md +156 -0
- tapps_agents/resources/claude/commands/__init__.py +1 -0
- tapps_agents/resources/claude/commands/build-fix.md +22 -0
- tapps_agents/resources/claude/commands/build.md +77 -0
- tapps_agents/resources/claude/commands/debug.md +53 -0
- tapps_agents/resources/claude/commands/design.md +68 -0
- tapps_agents/resources/claude/commands/docs.md +53 -0
- tapps_agents/resources/claude/commands/e2e.md +22 -0
- tapps_agents/resources/claude/commands/fix.md +54 -0
- tapps_agents/resources/claude/commands/implement.md +53 -0
- tapps_agents/resources/claude/commands/improve.md +53 -0
- tapps_agents/resources/claude/commands/library-docs.md +64 -0
- tapps_agents/resources/claude/commands/lint.md +52 -0
- tapps_agents/resources/claude/commands/plan.md +65 -0
- tapps_agents/resources/claude/commands/refactor-clean.md +21 -0
- tapps_agents/resources/claude/commands/refactor.md +55 -0
- tapps_agents/resources/claude/commands/review.md +67 -0
- tapps_agents/resources/claude/commands/score.md +60 -0
- tapps_agents/resources/claude/commands/security-review.md +22 -0
- tapps_agents/resources/claude/commands/security-scan.md +54 -0
- tapps_agents/resources/claude/commands/tdd.md +24 -0
- tapps_agents/resources/claude/commands/test-coverage.md +21 -0
- tapps_agents/resources/claude/commands/test.md +54 -0
- tapps_agents/resources/claude/commands/update-codemaps.md +20 -0
- tapps_agents/resources/claude/commands/update-docs.md +21 -0
- tapps_agents/resources/claude/skills/__init__.py +1 -0
- tapps_agents/resources/claude/skills/analyst/SKILL.md +272 -0
- tapps_agents/resources/claude/skills/analyst/__init__.py +1 -0
- tapps_agents/resources/claude/skills/architect/SKILL.md +282 -0
- tapps_agents/resources/claude/skills/architect/__init__.py +1 -0
- tapps_agents/resources/claude/skills/backend-patterns/SKILL.md +30 -0
- tapps_agents/resources/claude/skills/backend-patterns/__init__.py +1 -0
- tapps_agents/resources/claude/skills/coding-standards/SKILL.md +29 -0
- tapps_agents/resources/claude/skills/coding-standards/__init__.py +1 -0
- tapps_agents/resources/claude/skills/debugger/SKILL.md +203 -0
- tapps_agents/resources/claude/skills/debugger/__init__.py +1 -0
- tapps_agents/resources/claude/skills/designer/SKILL.md +243 -0
- tapps_agents/resources/claude/skills/designer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/documenter/SKILL.md +252 -0
- tapps_agents/resources/claude/skills/documenter/__init__.py +1 -0
- tapps_agents/resources/claude/skills/enhancer/SKILL.md +307 -0
- tapps_agents/resources/claude/skills/enhancer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/evaluator/SKILL.md +204 -0
- tapps_agents/resources/claude/skills/evaluator/__init__.py +1 -0
- tapps_agents/resources/claude/skills/frontend-patterns/SKILL.md +29 -0
- tapps_agents/resources/claude/skills/frontend-patterns/__init__.py +1 -0
- tapps_agents/resources/claude/skills/implementer/SKILL.md +188 -0
- tapps_agents/resources/claude/skills/implementer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/improver/SKILL.md +218 -0
- tapps_agents/resources/claude/skills/improver/__init__.py +1 -0
- tapps_agents/resources/claude/skills/ops/SKILL.md +281 -0
- tapps_agents/resources/claude/skills/ops/__init__.py +1 -0
- tapps_agents/resources/claude/skills/orchestrator/SKILL.md +390 -0
- tapps_agents/resources/claude/skills/orchestrator/__init__.py +1 -0
- tapps_agents/resources/claude/skills/planner/SKILL.md +254 -0
- tapps_agents/resources/claude/skills/planner/__init__.py +1 -0
- tapps_agents/resources/claude/skills/reviewer/SKILL.md +434 -0
- tapps_agents/resources/claude/skills/reviewer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/security-review/SKILL.md +31 -0
- tapps_agents/resources/claude/skills/security-review/__init__.py +1 -0
- tapps_agents/resources/claude/skills/simple-mode/SKILL.md +695 -0
- tapps_agents/resources/claude/skills/simple-mode/__init__.py +1 -0
- tapps_agents/resources/claude/skills/tester/SKILL.md +219 -0
- tapps_agents/resources/claude/skills/tester/__init__.py +1 -0
- tapps_agents/resources/cursor/.cursorignore +35 -0
- tapps_agents/resources/cursor/__init__.py +1 -0
- tapps_agents/resources/cursor/commands/__init__.py +1 -0
- tapps_agents/resources/cursor/commands/build-fix.md +11 -0
- tapps_agents/resources/cursor/commands/build.md +11 -0
- tapps_agents/resources/cursor/commands/e2e.md +11 -0
- tapps_agents/resources/cursor/commands/fix.md +11 -0
- tapps_agents/resources/cursor/commands/refactor-clean.md +11 -0
- tapps_agents/resources/cursor/commands/review.md +11 -0
- tapps_agents/resources/cursor/commands/security-review.md +11 -0
- tapps_agents/resources/cursor/commands/tdd.md +11 -0
- tapps_agents/resources/cursor/commands/test-coverage.md +11 -0
- tapps_agents/resources/cursor/commands/test.md +11 -0
- tapps_agents/resources/cursor/commands/update-codemaps.md +10 -0
- tapps_agents/resources/cursor/commands/update-docs.md +11 -0
- tapps_agents/resources/cursor/rules/__init__.py +1 -0
- tapps_agents/resources/cursor/rules/agent-capabilities.mdc +687 -0
- tapps_agents/resources/cursor/rules/coding-style.mdc +31 -0
- tapps_agents/resources/cursor/rules/command-reference.mdc +2081 -0
- tapps_agents/resources/cursor/rules/cursor-mode-usage.mdc +125 -0
- tapps_agents/resources/cursor/rules/git-workflow.mdc +29 -0
- tapps_agents/resources/cursor/rules/performance.mdc +29 -0
- tapps_agents/resources/cursor/rules/project-context.mdc +163 -0
- tapps_agents/resources/cursor/rules/project-profiling.mdc +197 -0
- tapps_agents/resources/cursor/rules/quick-reference.mdc +630 -0
- tapps_agents/resources/cursor/rules/security.mdc +32 -0
- tapps_agents/resources/cursor/rules/simple-mode.mdc +500 -0
- tapps_agents/resources/cursor/rules/testing.mdc +31 -0
- tapps_agents/resources/cursor/rules/when-to-use.mdc +156 -0
- tapps_agents/resources/cursor/rules/workflow-presets.mdc +179 -0
- tapps_agents/resources/customizations/__init__.py +1 -0
- tapps_agents/resources/customizations/example-custom.yaml +83 -0
- tapps_agents/resources/hooks/__init__.py +1 -0
- tapps_agents/resources/hooks/templates/README.md +5 -0
- tapps_agents/resources/hooks/templates/__init__.py +1 -0
- tapps_agents/resources/hooks/templates/add-project-context.yaml +8 -0
- tapps_agents/resources/hooks/templates/auto-format-js.yaml +10 -0
- tapps_agents/resources/hooks/templates/auto-format-python.yaml +10 -0
- tapps_agents/resources/hooks/templates/git-commit-check.yaml +7 -0
- tapps_agents/resources/hooks/templates/notify-on-complete.yaml +8 -0
- tapps_agents/resources/hooks/templates/quality-gate.yaml +8 -0
- tapps_agents/resources/hooks/templates/security-scan-on-edit.yaml +10 -0
- tapps_agents/resources/hooks/templates/session-end-log.yaml +7 -0
- tapps_agents/resources/hooks/templates/show-beads-ready.yaml +8 -0
- tapps_agents/resources/hooks/templates/test-on-edit.yaml +10 -0
- tapps_agents/resources/hooks/templates/update-docs-on-complete.yaml +8 -0
- tapps_agents/resources/hooks/templates/user-prompt-log.yaml +7 -0
- tapps_agents/resources/scripts/__init__.py +1 -0
- tapps_agents/resources/scripts/set_bd_path.ps1 +51 -0
- tapps_agents/resources/workflows/__init__.py +1 -0
- tapps_agents/resources/workflows/presets/__init__.py +1 -0
- tapps_agents/resources/workflows/presets/brownfield-analysis.yaml +235 -0
- tapps_agents/resources/workflows/presets/fix.yaml +78 -0
- tapps_agents/resources/workflows/presets/full-sdlc.yaml +122 -0
- tapps_agents/resources/workflows/presets/quality.yaml +82 -0
- tapps_agents/resources/workflows/presets/rapid-dev.yaml +84 -0
- tapps_agents/session/__init__.py +19 -19
- tapps_agents/session/manager.py +256 -256
- tapps_agents/simple_mode/__init__.py +66 -66
- tapps_agents/simple_mode/agent_contracts.py +357 -357
- tapps_agents/simple_mode/beads_hooks.py +151 -151
- tapps_agents/simple_mode/code_snippet_handler.py +382 -382
- tapps_agents/simple_mode/documentation_manager.py +395 -395
- tapps_agents/simple_mode/documentation_reader.py +187 -187
- tapps_agents/simple_mode/file_inference.py +292 -292
- tapps_agents/simple_mode/framework_change_detector.py +268 -268
- tapps_agents/simple_mode/intent_parser.py +510 -510
- tapps_agents/simple_mode/learning_progression.py +358 -358
- tapps_agents/simple_mode/nl_handler.py +700 -700
- tapps_agents/simple_mode/onboarding.py +253 -253
- tapps_agents/simple_mode/orchestrators/__init__.py +38 -38
- tapps_agents/simple_mode/orchestrators/breakdown_orchestrator.py +49 -49
- tapps_agents/simple_mode/orchestrators/brownfield_orchestrator.py +135 -135
- tapps_agents/simple_mode/orchestrators/deliverable_checklist.py +349 -349
- tapps_agents/simple_mode/orchestrators/enhance_orchestrator.py +53 -53
- tapps_agents/simple_mode/orchestrators/epic_orchestrator.py +122 -122
- tapps_agents/simple_mode/orchestrators/explore_orchestrator.py +184 -184
- tapps_agents/simple_mode/orchestrators/plan_analysis_orchestrator.py +206 -206
- tapps_agents/simple_mode/orchestrators/pr_orchestrator.py +237 -237
- tapps_agents/simple_mode/orchestrators/refactor_orchestrator.py +222 -222
- tapps_agents/simple_mode/orchestrators/requirements_tracer.py +262 -262
- tapps_agents/simple_mode/orchestrators/resume_orchestrator.py +210 -210
- tapps_agents/simple_mode/orchestrators/review_orchestrator.py +161 -161
- tapps_agents/simple_mode/orchestrators/test_orchestrator.py +82 -82
- tapps_agents/simple_mode/output_aggregator.py +340 -340
- tapps_agents/simple_mode/result_formatters.py +598 -598
- tapps_agents/simple_mode/step_dependencies.py +382 -382
- tapps_agents/simple_mode/step_results.py +276 -276
- tapps_agents/simple_mode/streaming.py +388 -388
- tapps_agents/simple_mode/variations.py +129 -129
- tapps_agents/simple_mode/visual_feedback.py +238 -238
- tapps_agents/simple_mode/zero_config.py +274 -274
- tapps_agents/suggestions/__init__.py +8 -8
- tapps_agents/suggestions/inline_suggester.py +52 -52
- tapps_agents/templates/__init__.py +8 -8
- tapps_agents/templates/microservice_generator.py +274 -274
- tapps_agents/utils/env_validator.py +291 -291
- tapps_agents/workflow/__init__.py +171 -171
- tapps_agents/workflow/acceptance_verifier.py +132 -132
- tapps_agents/workflow/agent_handlers/__init__.py +41 -41
- tapps_agents/workflow/agent_handlers/analyst_handler.py +75 -75
- tapps_agents/workflow/agent_handlers/architect_handler.py +107 -107
- tapps_agents/workflow/agent_handlers/base.py +84 -84
- tapps_agents/workflow/agent_handlers/debugger_handler.py +100 -100
- tapps_agents/workflow/agent_handlers/designer_handler.py +110 -110
- tapps_agents/workflow/agent_handlers/documenter_handler.py +94 -94
- tapps_agents/workflow/agent_handlers/implementer_handler.py +235 -235
- tapps_agents/workflow/agent_handlers/ops_handler.py +62 -62
- tapps_agents/workflow/agent_handlers/orchestrator_handler.py +43 -43
- tapps_agents/workflow/agent_handlers/planner_handler.py +98 -98
- tapps_agents/workflow/agent_handlers/registry.py +119 -119
- tapps_agents/workflow/agent_handlers/reviewer_handler.py +119 -119
- tapps_agents/workflow/agent_handlers/tester_handler.py +69 -69
- tapps_agents/workflow/analytics_accessor.py +337 -337
- tapps_agents/workflow/analytics_alerts.py +416 -416
- tapps_agents/workflow/analytics_dashboard_cursor.py +281 -281
- tapps_agents/workflow/analytics_dual_write.py +103 -103
- tapps_agents/workflow/analytics_integration.py +119 -119
- tapps_agents/workflow/analytics_query_parser.py +278 -278
- tapps_agents/workflow/analytics_visualizer.py +259 -259
- tapps_agents/workflow/artifact_helper.py +204 -204
- tapps_agents/workflow/audit_logger.py +263 -263
- tapps_agents/workflow/auto_execution_config.py +340 -340
- tapps_agents/workflow/auto_progression.py +586 -586
- tapps_agents/workflow/branch_cleanup.py +349 -349
- tapps_agents/workflow/checkpoint.py +256 -256
- tapps_agents/workflow/checkpoint_manager.py +178 -178
- tapps_agents/workflow/code_artifact.py +179 -179
- tapps_agents/workflow/common_enums.py +96 -96
- tapps_agents/workflow/confirmation_handler.py +130 -130
- tapps_agents/workflow/context_analyzer.py +222 -222
- tapps_agents/workflow/context_artifact.py +230 -230
- tapps_agents/workflow/cursor_chat.py +94 -94
- tapps_agents/workflow/cursor_skill_helper.py +516 -516
- tapps_agents/workflow/dependency_resolver.py +244 -244
- tapps_agents/workflow/design_artifact.py +156 -156
- tapps_agents/workflow/detector.py +751 -751
- tapps_agents/workflow/direct_execution_fallback.py +301 -301
- tapps_agents/workflow/docs_artifact.py +168 -168
- tapps_agents/workflow/enforcer.py +389 -389
- tapps_agents/workflow/enhancement_artifact.py +142 -142
- tapps_agents/workflow/error_recovery.py +806 -806
- tapps_agents/workflow/event_bus.py +183 -183
- tapps_agents/workflow/event_log.py +612 -612
- tapps_agents/workflow/events.py +63 -63
- tapps_agents/workflow/exceptions.py +43 -43
- tapps_agents/workflow/execution_graph.py +498 -498
- tapps_agents/workflow/execution_plan.py +126 -126
- tapps_agents/workflow/file_utils.py +186 -186
- tapps_agents/workflow/gate_evaluator.py +182 -182
- tapps_agents/workflow/gate_integration.py +200 -200
- tapps_agents/workflow/graph_visualizer.py +130 -130
- tapps_agents/workflow/health_checker.py +206 -206
- tapps_agents/workflow/logging_helper.py +243 -243
- tapps_agents/workflow/manifest.py +582 -582
- tapps_agents/workflow/marker_writer.py +250 -250
- tapps_agents/workflow/messaging.py +325 -325
- tapps_agents/workflow/metadata_models.py +91 -91
- tapps_agents/workflow/metrics_integration.py +226 -226
- tapps_agents/workflow/migration_utils.py +116 -116
- tapps_agents/workflow/models.py +148 -148
- tapps_agents/workflow/nlp_config.py +198 -198
- tapps_agents/workflow/nlp_error_handler.py +207 -207
- tapps_agents/workflow/nlp_executor.py +163 -163
- tapps_agents/workflow/nlp_parser.py +528 -528
- tapps_agents/workflow/observability_dashboard.py +451 -451
- tapps_agents/workflow/observer.py +170 -170
- tapps_agents/workflow/ops_artifact.py +257 -257
- tapps_agents/workflow/output_passing.py +214 -214
- tapps_agents/workflow/parallel_executor.py +463 -463
- tapps_agents/workflow/planning_artifact.py +179 -179
- tapps_agents/workflow/preset_loader.py +285 -285
- tapps_agents/workflow/preset_recommender.py +270 -270
- tapps_agents/workflow/progress_logger.py +145 -145
- tapps_agents/workflow/progress_manager.py +303 -303
- tapps_agents/workflow/progress_monitor.py +186 -186
- tapps_agents/workflow/progress_updates.py +423 -423
- tapps_agents/workflow/quality_artifact.py +158 -158
- tapps_agents/workflow/quality_loopback.py +101 -101
- tapps_agents/workflow/recommender.py +387 -387
- tapps_agents/workflow/remediation_loop.py +166 -166
- tapps_agents/workflow/result_aggregator.py +300 -300
- tapps_agents/workflow/review_artifact.py +185 -185
- tapps_agents/workflow/schema_validator.py +522 -522
- tapps_agents/workflow/session_handoff.py +178 -178
- tapps_agents/workflow/skill_invoker.py +648 -648
- tapps_agents/workflow/state_manager.py +756 -756
- tapps_agents/workflow/state_persistence_config.py +331 -331
- tapps_agents/workflow/status_monitor.py +449 -449
- tapps_agents/workflow/step_checkpoint.py +314 -314
- tapps_agents/workflow/step_details.py +201 -201
- tapps_agents/workflow/story_models.py +147 -147
- tapps_agents/workflow/streaming.py +416 -416
- tapps_agents/workflow/suggestion_engine.py +552 -552
- tapps_agents/workflow/testing_artifact.py +186 -186
- tapps_agents/workflow/timeline.py +158 -158
- tapps_agents/workflow/token_integration.py +209 -209
- tapps_agents/workflow/validation.py +217 -217
- tapps_agents/workflow/visual_feedback.py +391 -391
- tapps_agents/workflow/workflow_chain.py +95 -95
- tapps_agents/workflow/workflow_summary.py +219 -219
- tapps_agents/workflow/worktree_manager.py +724 -724
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/METADATA +672 -672
- tapps_agents-3.6.1.dist-info/RECORD +883 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/licenses/LICENSE +22 -22
- tapps_agents-3.6.0.dist-info/RECORD +0 -758
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/WHEEL +0 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/top_level.txt +0 -0
|
@@ -1,771 +1,771 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Playwright MCP Controller for browser automation via MCP tools.
|
|
3
|
-
|
|
4
|
-
Provides a unified interface for browser automation that works both:
|
|
5
|
-
- In Cursor IDE: Uses Playwright MCP tools directly
|
|
6
|
-
- In CLI mode: Falls back to Python Playwright package
|
|
7
|
-
|
|
8
|
-
This controller enables direct use of Playwright MCP capabilities including:
|
|
9
|
-
- Navigation and page management
|
|
10
|
-
- Element interaction (click, type, fill forms)
|
|
11
|
-
- Screenshots and snapshots
|
|
12
|
-
- Network monitoring
|
|
13
|
-
- Console message capture
|
|
14
|
-
- Accessibility tree access
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from __future__ import annotations
|
|
18
|
-
|
|
19
|
-
import logging
|
|
20
|
-
from dataclasses import dataclass
|
|
21
|
-
from enum import Enum
|
|
22
|
-
from pathlib import Path
|
|
23
|
-
from typing import Any, Callable, Optional
|
|
24
|
-
|
|
25
|
-
logger = logging.getLogger(__name__)
|
|
26
|
-
|
|
27
|
-
# Import network recorder and trace manager
|
|
28
|
-
try:
|
|
29
|
-
from ...agents.tester.network_recorder import NetworkRecorder
|
|
30
|
-
from ...agents.tester.trace_manager import TraceManager
|
|
31
|
-
except ImportError:
|
|
32
|
-
NetworkRecorder = None
|
|
33
|
-
TraceManager = None
|
|
34
|
-
|
|
35
|
-
# Try to import Playwright for fallback
|
|
36
|
-
try:
|
|
37
|
-
from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright
|
|
38
|
-
|
|
39
|
-
HAS_PLAYWRIGHT = True
|
|
40
|
-
except ImportError:
|
|
41
|
-
HAS_PLAYWRIGHT = False
|
|
42
|
-
Browser = Any
|
|
43
|
-
Page = Any
|
|
44
|
-
BrowserContext = Any
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class MCPToolMode(Enum):
|
|
48
|
-
"""MCP tool invocation mode."""
|
|
49
|
-
|
|
50
|
-
DIRECT = "direct" # Direct MCP function calls (Cursor IDE)
|
|
51
|
-
GATEWAY = "gateway" # Via MCP Gateway (framework)
|
|
52
|
-
FALLBACK = "fallback" # Python Playwright package fallback
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@dataclass
|
|
56
|
-
class ElementRef:
|
|
57
|
-
"""Element reference for MCP tool calls."""
|
|
58
|
-
|
|
59
|
-
element: str # Human-readable description
|
|
60
|
-
ref: str # Exact element reference from snapshot
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@dataclass
|
|
64
|
-
class AccessibilitySnapshot:
|
|
65
|
-
"""Accessibility snapshot data."""
|
|
66
|
-
|
|
67
|
-
content: str # Snapshot markdown content
|
|
68
|
-
filename: Optional[str] = None # Optional saved filename
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@dataclass
|
|
72
|
-
class PerformanceMetrics:
|
|
73
|
-
"""Performance metrics from page load."""
|
|
74
|
-
|
|
75
|
-
lcp: Optional[float] = None # Largest Contentful Paint (seconds)
|
|
76
|
-
fid: Optional[float] = None # First Input Delay (milliseconds)
|
|
77
|
-
cls: Optional[float] = None # Cumulative Layout Shift
|
|
78
|
-
fcp: Optional[float] = None # First Contentful Paint (seconds)
|
|
79
|
-
load_time: Optional[float] = None # Total load time (seconds)
|
|
80
|
-
dom_content_loaded: Optional[float] = None # DOMContentLoaded time (seconds)
|
|
81
|
-
network_requests: int = 0 # Number of network requests
|
|
82
|
-
failed_requests: int = 0 # Number of failed requests
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class PlaywrightMCPController:
|
|
86
|
-
"""
|
|
87
|
-
Controller for Playwright MCP browser automation.
|
|
88
|
-
|
|
89
|
-
Provides a unified interface that works in both Cursor IDE (MCP tools)
|
|
90
|
-
and CLI mode (Python Playwright fallback).
|
|
91
|
-
"""
|
|
92
|
-
|
|
93
|
-
def __init__(
|
|
94
|
-
self,
|
|
95
|
-
mcp_tool_handler: Optional[Callable] = None,
|
|
96
|
-
use_fallback: bool = True,
|
|
97
|
-
enable_network_recording: bool = False,
|
|
98
|
-
enable_tracing: bool = False,
|
|
99
|
-
):
|
|
100
|
-
"""
|
|
101
|
-
Initialize Playwright MCP controller.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
mcp_tool_handler: Function to call MCP tools (for Cursor IDE)
|
|
105
|
-
use_fallback: Whether to fall back to Python Playwright if MCP unavailable
|
|
106
|
-
enable_network_recording: Enable network request recording
|
|
107
|
-
enable_tracing: Enable trace file generation
|
|
108
|
-
"""
|
|
109
|
-
self.mcp_tool_handler = mcp_tool_handler
|
|
110
|
-
self.use_fallback = use_fallback
|
|
111
|
-
self.mode = self._detect_mode()
|
|
112
|
-
self.current_tab_index = 0
|
|
113
|
-
self.tabs: dict[int, Any] = {} # Track multiple tabs
|
|
114
|
-
|
|
115
|
-
# Fallback Playwright instances
|
|
116
|
-
self.playwright: Any | None = None
|
|
117
|
-
self.browser: Browser | None = None
|
|
118
|
-
self.context: BrowserContext | None = None
|
|
119
|
-
self.page: Page | None = None
|
|
120
|
-
|
|
121
|
-
# Network recording and tracing
|
|
122
|
-
self.network_recorder: NetworkRecorder | None = None
|
|
123
|
-
self.trace_manager: TraceManager | None = None
|
|
124
|
-
if enable_network_recording and NetworkRecorder:
|
|
125
|
-
self.network_recorder = NetworkRecorder()
|
|
126
|
-
if enable_tracing and TraceManager:
|
|
127
|
-
self.trace_manager = TraceManager()
|
|
128
|
-
|
|
129
|
-
if self.mode == MCPToolMode.FALLBACK and self.use_fallback:
|
|
130
|
-
self._init_fallback()
|
|
131
|
-
|
|
132
|
-
def _detect_mode(self) -> MCPToolMode:
|
|
133
|
-
"""Detect available MCP tool mode."""
|
|
134
|
-
if self.mcp_tool_handler is not None:
|
|
135
|
-
return MCPToolMode.DIRECT
|
|
136
|
-
elif self.use_fallback and HAS_PLAYWRIGHT:
|
|
137
|
-
return MCPToolMode.FALLBACK
|
|
138
|
-
else:
|
|
139
|
-
raise RuntimeError(
|
|
140
|
-
"No Playwright available. Install Python Playwright package "
|
|
141
|
-
"or configure Playwright MCP in Cursor."
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
def _init_fallback(self):
|
|
145
|
-
"""Initialize Python Playwright fallback."""
|
|
146
|
-
if not HAS_PLAYWRIGHT:
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
self.playwright = sync_playwright().start()
|
|
151
|
-
self.browser = self.playwright.chromium.launch(headless=True)
|
|
152
|
-
self.context = self.browser.new_context()
|
|
153
|
-
self.page = self.context.new_page()
|
|
154
|
-
logger.info("Initialized Playwright fallback (Python package)")
|
|
155
|
-
except Exception as e:
|
|
156
|
-
logger.error(f"Failed to initialize Playwright fallback: {e}")
|
|
157
|
-
raise
|
|
158
|
-
|
|
159
|
-
def _call_mcp_tool(self, tool_name: str, **kwargs) -> Any:
|
|
160
|
-
"""
|
|
161
|
-
Call MCP tool via handler.
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
tool_name: Name of MCP tool (e.g., 'browser_navigate')
|
|
165
|
-
**kwargs: Tool arguments
|
|
166
|
-
|
|
167
|
-
Returns:
|
|
168
|
-
Tool result
|
|
169
|
-
"""
|
|
170
|
-
if self.mcp_tool_handler is None:
|
|
171
|
-
raise RuntimeError("MCP tool handler not available")
|
|
172
|
-
|
|
173
|
-
# Map tool names to MCP function names
|
|
174
|
-
mcp_function_name = f"mcp_Playwright_{tool_name}"
|
|
175
|
-
return self.mcp_tool_handler(mcp_function_name, **kwargs)
|
|
176
|
-
|
|
177
|
-
def navigate(self, url: str) -> bool:
|
|
178
|
-
"""
|
|
179
|
-
Navigate to URL.
|
|
180
|
-
|
|
181
|
-
Args:
|
|
182
|
-
url: URL to navigate to
|
|
183
|
-
|
|
184
|
-
Returns:
|
|
185
|
-
True if successful, False otherwise
|
|
186
|
-
"""
|
|
187
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
188
|
-
try:
|
|
189
|
-
result = self._call_mcp_tool("browser_navigate", url=url)
|
|
190
|
-
logger.info(f"Navigated to: {url} (MCP)")
|
|
191
|
-
return True
|
|
192
|
-
except Exception as e:
|
|
193
|
-
logger.error(f"Navigation failed: {e}")
|
|
194
|
-
return False
|
|
195
|
-
else:
|
|
196
|
-
# Fallback to Python Playwright
|
|
197
|
-
if not self.page:
|
|
198
|
-
logger.error("Browser not initialized")
|
|
199
|
-
return False
|
|
200
|
-
try:
|
|
201
|
-
self.page.goto(url, wait_until="load", timeout=30000)
|
|
202
|
-
logger.info(f"Navigated to: {url} (fallback)")
|
|
203
|
-
return True
|
|
204
|
-
except Exception as e:
|
|
205
|
-
logger.error(f"Navigation failed: {e}")
|
|
206
|
-
return False
|
|
207
|
-
|
|
208
|
-
def snapshot(self, filename: Optional[str] = None) -> Optional[AccessibilitySnapshot]:
|
|
209
|
-
"""
|
|
210
|
-
Get accessibility snapshot of current page.
|
|
211
|
-
|
|
212
|
-
Args:
|
|
213
|
-
filename: Optional filename to save snapshot
|
|
214
|
-
|
|
215
|
-
Returns:
|
|
216
|
-
AccessibilitySnapshot or None if failed
|
|
217
|
-
"""
|
|
218
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
219
|
-
try:
|
|
220
|
-
kwargs = {}
|
|
221
|
-
if filename:
|
|
222
|
-
kwargs["filename"] = filename
|
|
223
|
-
|
|
224
|
-
result = self._call_mcp_tool("browser_snapshot", **kwargs)
|
|
225
|
-
content = result if isinstance(result, str) else result.get("content", "")
|
|
226
|
-
|
|
227
|
-
return AccessibilitySnapshot(content=content, filename=filename)
|
|
228
|
-
except Exception as e:
|
|
229
|
-
logger.error(f"Snapshot failed: {e}")
|
|
230
|
-
return None
|
|
231
|
-
else:
|
|
232
|
-
# Fallback: Use page content as snapshot
|
|
233
|
-
if not self.page:
|
|
234
|
-
logger.error("Browser not initialized")
|
|
235
|
-
return None
|
|
236
|
-
try:
|
|
237
|
-
content = self.page.content()
|
|
238
|
-
# Convert HTML to markdown-like snapshot
|
|
239
|
-
snapshot_content = f"# Page Snapshot\n\n```html\n{content}\n```"
|
|
240
|
-
return AccessibilitySnapshot(content=snapshot_content, filename=filename)
|
|
241
|
-
except Exception as e:
|
|
242
|
-
logger.error(f"Snapshot failed: {e}")
|
|
243
|
-
return None
|
|
244
|
-
|
|
245
|
-
def click(self, element: str, ref: str, button: str = "left") -> bool:
|
|
246
|
-
"""
|
|
247
|
-
Click an element.
|
|
248
|
-
|
|
249
|
-
Args:
|
|
250
|
-
element: Human-readable element description
|
|
251
|
-
ref: Exact element reference from snapshot
|
|
252
|
-
button: Mouse button ("left", "right", "middle")
|
|
253
|
-
|
|
254
|
-
Returns:
|
|
255
|
-
True if successful, False otherwise
|
|
256
|
-
"""
|
|
257
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
258
|
-
try:
|
|
259
|
-
self._call_mcp_tool(
|
|
260
|
-
"browser_click", element=element, ref=ref, button=button
|
|
261
|
-
)
|
|
262
|
-
logger.debug(f"Clicked: {element}")
|
|
263
|
-
return True
|
|
264
|
-
except Exception as e:
|
|
265
|
-
logger.error(f"Click failed: {e}")
|
|
266
|
-
return False
|
|
267
|
-
else:
|
|
268
|
-
# Fallback: Use CSS selector from ref or element
|
|
269
|
-
if not self.page:
|
|
270
|
-
logger.error("Browser not initialized")
|
|
271
|
-
return False
|
|
272
|
-
try:
|
|
273
|
-
# Try to extract selector from ref, fallback to element description
|
|
274
|
-
selector = ref if ref.startswith("#") or ref.startswith(".") else element
|
|
275
|
-
self.page.click(selector, button=button, timeout=5000)
|
|
276
|
-
logger.debug(f"Clicked: {element} (fallback)")
|
|
277
|
-
return True
|
|
278
|
-
except Exception as e:
|
|
279
|
-
logger.error(f"Click failed: {e}")
|
|
280
|
-
return False
|
|
281
|
-
|
|
282
|
-
def type_text(self, element: str, ref: str, text: str, submit: bool = False) -> bool:
|
|
283
|
-
"""
|
|
284
|
-
Type text into an element.
|
|
285
|
-
|
|
286
|
-
Args:
|
|
287
|
-
element: Human-readable element description
|
|
288
|
-
ref: Exact element reference from snapshot
|
|
289
|
-
text: Text to type
|
|
290
|
-
submit: Whether to submit after typing (press Enter)
|
|
291
|
-
|
|
292
|
-
Returns:
|
|
293
|
-
True if successful, False otherwise
|
|
294
|
-
"""
|
|
295
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
296
|
-
try:
|
|
297
|
-
self._call_mcp_tool(
|
|
298
|
-
"browser_type",
|
|
299
|
-
element=element,
|
|
300
|
-
ref=ref,
|
|
301
|
-
text=text,
|
|
302
|
-
submit=submit,
|
|
303
|
-
)
|
|
304
|
-
logger.debug(f"Typed into {element}: {text}")
|
|
305
|
-
return True
|
|
306
|
-
except Exception as e:
|
|
307
|
-
logger.error(f"Type failed: {e}")
|
|
308
|
-
return False
|
|
309
|
-
else:
|
|
310
|
-
# Fallback
|
|
311
|
-
if not self.page:
|
|
312
|
-
logger.error("Browser not initialized")
|
|
313
|
-
return False
|
|
314
|
-
try:
|
|
315
|
-
selector = ref if ref.startswith("#") or ref.startswith(".") else element
|
|
316
|
-
self.page.fill(selector, text)
|
|
317
|
-
if submit:
|
|
318
|
-
self.page.press(selector, "Enter")
|
|
319
|
-
logger.debug(f"Typed into {element}: {text} (fallback)")
|
|
320
|
-
return True
|
|
321
|
-
except Exception as e:
|
|
322
|
-
logger.error(f"Type failed: {e}")
|
|
323
|
-
return False
|
|
324
|
-
|
|
325
|
-
def fill_form(self, fields: list[dict[str, Any]]) -> bool:
|
|
326
|
-
"""
|
|
327
|
-
Fill multiple form fields at once.
|
|
328
|
-
|
|
329
|
-
Args:
|
|
330
|
-
fields: List of field dictionaries with 'name', 'type', 'ref', 'value'
|
|
331
|
-
|
|
332
|
-
Returns:
|
|
333
|
-
True if successful, False otherwise
|
|
334
|
-
"""
|
|
335
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
336
|
-
try:
|
|
337
|
-
# Convert fields to MCP format
|
|
338
|
-
mcp_fields = []
|
|
339
|
-
for field in fields:
|
|
340
|
-
mcp_fields.append(
|
|
341
|
-
{
|
|
342
|
-
"name": field.get("name", ""),
|
|
343
|
-
"type": field.get("type", "textbox"),
|
|
344
|
-
"ref": field.get("ref", ""),
|
|
345
|
-
"value": field.get("value", ""),
|
|
346
|
-
}
|
|
347
|
-
)
|
|
348
|
-
self._call_mcp_tool("browser_fill_form", fields=mcp_fields)
|
|
349
|
-
logger.debug(f"Filled form with {len(fields)} fields")
|
|
350
|
-
return True
|
|
351
|
-
except Exception as e:
|
|
352
|
-
logger.error(f"Fill form failed: {e}")
|
|
353
|
-
return False
|
|
354
|
-
else:
|
|
355
|
-
# Fallback: Fill fields one by one
|
|
356
|
-
if not self.page:
|
|
357
|
-
logger.error("Browser not initialized")
|
|
358
|
-
return False
|
|
359
|
-
try:
|
|
360
|
-
for field in fields:
|
|
361
|
-
selector = field.get("ref") or field.get("name", "")
|
|
362
|
-
value = field.get("value", "")
|
|
363
|
-
field_type = field.get("type", "textbox")
|
|
364
|
-
|
|
365
|
-
if field_type == "checkbox":
|
|
366
|
-
if value:
|
|
367
|
-
self.page.check(selector)
|
|
368
|
-
else:
|
|
369
|
-
self.page.uncheck(selector)
|
|
370
|
-
elif field_type == "combobox":
|
|
371
|
-
self.page.select_option(selector, value)
|
|
372
|
-
else:
|
|
373
|
-
self.page.fill(selector, value)
|
|
374
|
-
|
|
375
|
-
logger.debug(f"Filled form with {len(fields)} fields (fallback)")
|
|
376
|
-
return True
|
|
377
|
-
except Exception as e:
|
|
378
|
-
logger.error(f"Fill form failed: {e}")
|
|
379
|
-
return False
|
|
380
|
-
|
|
381
|
-
def take_screenshot(
|
|
382
|
-
self,
|
|
383
|
-
filename: Optional[str] = None,
|
|
384
|
-
full_page: bool = False,
|
|
385
|
-
element: Optional[str] = None,
|
|
386
|
-
ref: Optional[str] = None,
|
|
387
|
-
) -> Optional[str]:
|
|
388
|
-
"""
|
|
389
|
-
Take a screenshot.
|
|
390
|
-
|
|
391
|
-
Args:
|
|
392
|
-
filename: Output filename
|
|
393
|
-
full_page: Whether to capture full page
|
|
394
|
-
element: Optional element description for element screenshot
|
|
395
|
-
ref: Optional element reference for element screenshot
|
|
396
|
-
|
|
397
|
-
Returns:
|
|
398
|
-
Filename if successful, None otherwise
|
|
399
|
-
"""
|
|
400
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
401
|
-
try:
|
|
402
|
-
kwargs = {}
|
|
403
|
-
if filename:
|
|
404
|
-
kwargs["filename"] = filename
|
|
405
|
-
if full_page:
|
|
406
|
-
kwargs["fullPage"] = True
|
|
407
|
-
if element and ref:
|
|
408
|
-
kwargs["element"] = element
|
|
409
|
-
kwargs["ref"] = ref
|
|
410
|
-
|
|
411
|
-
result = self._call_mcp_tool("browser_take_screenshot", **kwargs)
|
|
412
|
-
saved_filename = filename or result.get("filename", "screenshot.png")
|
|
413
|
-
logger.info(f"Screenshot saved: {saved_filename}")
|
|
414
|
-
return saved_filename
|
|
415
|
-
except Exception as e:
|
|
416
|
-
logger.error(f"Screenshot failed: {e}")
|
|
417
|
-
return None
|
|
418
|
-
else:
|
|
419
|
-
# Fallback
|
|
420
|
-
if not self.page:
|
|
421
|
-
logger.error("Browser not initialized")
|
|
422
|
-
return None
|
|
423
|
-
try:
|
|
424
|
-
if not filename:
|
|
425
|
-
filename = "screenshot.png"
|
|
426
|
-
|
|
427
|
-
screenshot_kwargs = {"path": filename, "full_page": full_page}
|
|
428
|
-
if element and ref:
|
|
429
|
-
selector = ref if ref.startswith("#") or ref.startswith(".") else element
|
|
430
|
-
screenshot_kwargs["selector"] = selector
|
|
431
|
-
|
|
432
|
-
self.page.screenshot(**screenshot_kwargs)
|
|
433
|
-
logger.info(f"Screenshot saved: {filename} (fallback)")
|
|
434
|
-
return filename
|
|
435
|
-
except Exception as e:
|
|
436
|
-
logger.error(f"Screenshot failed: {e}")
|
|
437
|
-
return None
|
|
438
|
-
|
|
439
|
-
def get_console_messages(self, level: str = "info") -> list[dict[str, Any]]:
|
|
440
|
-
"""
|
|
441
|
-
Get console messages from the page.
|
|
442
|
-
|
|
443
|
-
Args:
|
|
444
|
-
level: Message level ("error", "warning", "info", "debug")
|
|
445
|
-
|
|
446
|
-
Returns:
|
|
447
|
-
List of console messages
|
|
448
|
-
"""
|
|
449
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
450
|
-
try:
|
|
451
|
-
result = self._call_mcp_tool("browser_console_messages", level=level)
|
|
452
|
-
return result if isinstance(result, list) else result.get("messages", [])
|
|
453
|
-
except Exception as e:
|
|
454
|
-
logger.error(f"Get console messages failed: {e}")
|
|
455
|
-
return []
|
|
456
|
-
else:
|
|
457
|
-
# Fallback: Not easily available in Python Playwright
|
|
458
|
-
logger.warning("Console messages not available in fallback mode")
|
|
459
|
-
return []
|
|
460
|
-
|
|
461
|
-
def get_network_requests(
|
|
462
|
-
self, include_static: bool = False
|
|
463
|
-
) -> list[dict[str, Any]]:
|
|
464
|
-
"""
|
|
465
|
-
Get network requests from the page.
|
|
466
|
-
|
|
467
|
-
Args:
|
|
468
|
-
include_static: Whether to include static resources (images, fonts, etc.)
|
|
469
|
-
|
|
470
|
-
Returns:
|
|
471
|
-
List of network requests
|
|
472
|
-
"""
|
|
473
|
-
requests = []
|
|
474
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
475
|
-
try:
|
|
476
|
-
result = self._call_mcp_tool(
|
|
477
|
-
"browser_network_requests", includeStatic=include_static
|
|
478
|
-
)
|
|
479
|
-
requests = result if isinstance(result, list) else result.get("requests", [])
|
|
480
|
-
except Exception as e:
|
|
481
|
-
logger.error(f"Get network requests failed: {e}")
|
|
482
|
-
return []
|
|
483
|
-
else:
|
|
484
|
-
# Fallback: Not easily available in Python Playwright
|
|
485
|
-
logger.warning("Network requests not available in fallback mode")
|
|
486
|
-
return []
|
|
487
|
-
|
|
488
|
-
# Record requests if recording is enabled
|
|
489
|
-
if self.network_recorder and requests:
|
|
490
|
-
for req in requests:
|
|
491
|
-
self.network_recorder.record_request(
|
|
492
|
-
url=req.get("url", ""),
|
|
493
|
-
method=req.get("method", "GET"),
|
|
494
|
-
headers=req.get("headers", {}),
|
|
495
|
-
response_status=req.get("status"),
|
|
496
|
-
response_headers=req.get("responseHeaders", {}),
|
|
497
|
-
response_body=req.get("body"),
|
|
498
|
-
request_id=req.get("id"),
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
return requests
|
|
502
|
-
|
|
503
|
-
def wait_for(
|
|
504
|
-
self, text: Optional[str] = None, text_gone: Optional[str] = None, time: Optional[float] = None
|
|
505
|
-
) -> bool:
|
|
506
|
-
"""
|
|
507
|
-
Wait for text to appear/disappear or time to pass.
|
|
508
|
-
|
|
509
|
-
Args:
|
|
510
|
-
text: Text to wait for
|
|
511
|
-
text_gone: Text to wait for to disappear
|
|
512
|
-
time: Time to wait in seconds
|
|
513
|
-
|
|
514
|
-
Returns:
|
|
515
|
-
True if condition met, False otherwise
|
|
516
|
-
"""
|
|
517
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
518
|
-
try:
|
|
519
|
-
kwargs = {}
|
|
520
|
-
if text:
|
|
521
|
-
kwargs["text"] = text
|
|
522
|
-
if text_gone:
|
|
523
|
-
kwargs["textGone"] = text_gone
|
|
524
|
-
if time:
|
|
525
|
-
kwargs["time"] = time
|
|
526
|
-
|
|
527
|
-
self._call_mcp_tool("browser_wait_for", **kwargs)
|
|
528
|
-
return True
|
|
529
|
-
except Exception as e:
|
|
530
|
-
logger.error(f"Wait failed: {e}")
|
|
531
|
-
return False
|
|
532
|
-
else:
|
|
533
|
-
# Fallback
|
|
534
|
-
if not self.page:
|
|
535
|
-
logger.error("Browser not initialized")
|
|
536
|
-
return False
|
|
537
|
-
try:
|
|
538
|
-
if text:
|
|
539
|
-
self.page.wait_for_selector(f"text={text}", timeout=5000)
|
|
540
|
-
elif text_gone:
|
|
541
|
-
self.page.wait_for_selector(f"text={text_gone}", state="hidden", timeout=5000)
|
|
542
|
-
elif time:
|
|
543
|
-
import time as time_module
|
|
544
|
-
time_module.sleep(time)
|
|
545
|
-
|
|
546
|
-
return True
|
|
547
|
-
except Exception as e:
|
|
548
|
-
logger.error(f"Wait failed: {e}")
|
|
549
|
-
return False
|
|
550
|
-
|
|
551
|
-
def evaluate(self, function: str, element: Optional[str] = None, ref: Optional[str] = None) -> Any:
|
|
552
|
-
"""
|
|
553
|
-
Evaluate JavaScript expression on page or element.
|
|
554
|
-
|
|
555
|
-
Args:
|
|
556
|
-
function: JavaScript function as string (e.g., "() => { return document.title; }")
|
|
557
|
-
element: Optional element description
|
|
558
|
-
ref: Optional element reference
|
|
559
|
-
|
|
560
|
-
Returns:
|
|
561
|
-
Evaluation result
|
|
562
|
-
"""
|
|
563
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
564
|
-
try:
|
|
565
|
-
kwargs = {"function": function}
|
|
566
|
-
if element:
|
|
567
|
-
kwargs["element"] = element
|
|
568
|
-
if ref:
|
|
569
|
-
kwargs["ref"] = ref
|
|
570
|
-
|
|
571
|
-
result = self._call_mcp_tool("browser_evaluate", **kwargs)
|
|
572
|
-
return result
|
|
573
|
-
except Exception as e:
|
|
574
|
-
logger.error(f"Evaluate failed: {e}")
|
|
575
|
-
return None
|
|
576
|
-
else:
|
|
577
|
-
# Fallback
|
|
578
|
-
if not self.page:
|
|
579
|
-
logger.error("Browser not initialized")
|
|
580
|
-
return None
|
|
581
|
-
try:
|
|
582
|
-
if element and ref:
|
|
583
|
-
selector = ref if ref.startswith("#") or ref.startswith(".") else element
|
|
584
|
-
return self.page.evaluate(function, self.page.query_selector(selector))
|
|
585
|
-
else:
|
|
586
|
-
return self.page.evaluate(function)
|
|
587
|
-
except Exception as e:
|
|
588
|
-
logger.error(f"Evaluate failed: {e}")
|
|
589
|
-
return None
|
|
590
|
-
|
|
591
|
-
def start_network_recording(self, session_id: str | None = None, description: str | None = None) -> str:
|
|
592
|
-
"""
|
|
593
|
-
Start network request recording.
|
|
594
|
-
|
|
595
|
-
Args:
|
|
596
|
-
session_id: Optional session ID
|
|
597
|
-
description: Optional description
|
|
598
|
-
|
|
599
|
-
Returns:
|
|
600
|
-
Session ID
|
|
601
|
-
"""
|
|
602
|
-
if self.network_recorder:
|
|
603
|
-
return self.network_recorder.start_recording(session_id, description)
|
|
604
|
-
else:
|
|
605
|
-
logger.warning("Network recording not enabled")
|
|
606
|
-
return ""
|
|
607
|
-
|
|
608
|
-
def stop_network_recording(self):
|
|
609
|
-
"""Stop network recording and save."""
|
|
610
|
-
if self.network_recorder:
|
|
611
|
-
return self.network_recorder.stop_recording()
|
|
612
|
-
return None
|
|
613
|
-
|
|
614
|
-
def start_tracing(self, test_name: str | None = None) -> Path | None:
|
|
615
|
-
"""
|
|
616
|
-
Start trace recording.
|
|
617
|
-
|
|
618
|
-
Args:
|
|
619
|
-
test_name: Optional test name
|
|
620
|
-
|
|
621
|
-
Returns:
|
|
622
|
-
Trace file path
|
|
623
|
-
"""
|
|
624
|
-
if self.trace_manager and self.context:
|
|
625
|
-
return self.trace_manager.start_tracing(self.context, test_name)
|
|
626
|
-
elif self.trace_manager:
|
|
627
|
-
# MCP mode - tracing will be handled by MCP
|
|
628
|
-
return self.trace_manager.start_tracing(None, test_name)
|
|
629
|
-
return None
|
|
630
|
-
|
|
631
|
-
def stop_tracing(self) -> Path | None:
|
|
632
|
-
"""Stop tracing and save trace file."""
|
|
633
|
-
if self.trace_manager:
|
|
634
|
-
return self.trace_manager.stop_tracing(self.context)
|
|
635
|
-
return None
|
|
636
|
-
|
|
637
|
-
def create_tab(self) -> int:
|
|
638
|
-
"""
|
|
639
|
-
Create a new browser tab.
|
|
640
|
-
|
|
641
|
-
Returns:
|
|
642
|
-
Tab index
|
|
643
|
-
"""
|
|
644
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
645
|
-
try:
|
|
646
|
-
result = self._call_mcp_tool("browser_tabs", action="new")
|
|
647
|
-
tab_index = result.get("index", len(self.tabs))
|
|
648
|
-
self.tabs[tab_index] = result
|
|
649
|
-
logger.info(f"Created tab: {tab_index}")
|
|
650
|
-
return tab_index
|
|
651
|
-
except Exception as e:
|
|
652
|
-
logger.error(f"Create tab failed: {e}")
|
|
653
|
-
return -1
|
|
654
|
-
else:
|
|
655
|
-
# Fallback: Create new page in context
|
|
656
|
-
if self.context:
|
|
657
|
-
new_page = self.context.new_page()
|
|
658
|
-
tab_index = len(self.tabs)
|
|
659
|
-
self.tabs[tab_index] = new_page
|
|
660
|
-
logger.info(f"Created tab: {tab_index} (fallback)")
|
|
661
|
-
return tab_index
|
|
662
|
-
return -1
|
|
663
|
-
|
|
664
|
-
def switch_tab(self, tab_index: int) -> bool:
|
|
665
|
-
"""
|
|
666
|
-
Switch to a different tab.
|
|
667
|
-
|
|
668
|
-
Args:
|
|
669
|
-
tab_index: Tab index to switch to
|
|
670
|
-
|
|
671
|
-
Returns:
|
|
672
|
-
True if successful
|
|
673
|
-
"""
|
|
674
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
675
|
-
try:
|
|
676
|
-
self._call_mcp_tool("browser_tabs", action="select", index=tab_index)
|
|
677
|
-
self.current_tab_index = tab_index
|
|
678
|
-
logger.info(f"Switched to tab: {tab_index}")
|
|
679
|
-
return True
|
|
680
|
-
except Exception as e:
|
|
681
|
-
logger.error(f"Switch tab failed: {e}")
|
|
682
|
-
return False
|
|
683
|
-
else:
|
|
684
|
-
# Fallback: Switch page reference
|
|
685
|
-
if tab_index in self.tabs:
|
|
686
|
-
self.page = self.tabs[tab_index]
|
|
687
|
-
self.current_tab_index = tab_index
|
|
688
|
-
logger.info(f"Switched to tab: {tab_index} (fallback)")
|
|
689
|
-
return True
|
|
690
|
-
return False
|
|
691
|
-
|
|
692
|
-
def close_tab(self, tab_index: int | None = None) -> bool:
|
|
693
|
-
"""
|
|
694
|
-
Close a browser tab.
|
|
695
|
-
|
|
696
|
-
Args:
|
|
697
|
-
tab_index: Tab index to close (None = current tab)
|
|
698
|
-
|
|
699
|
-
Returns:
|
|
700
|
-
True if successful
|
|
701
|
-
"""
|
|
702
|
-
if tab_index is None:
|
|
703
|
-
tab_index = self.current_tab_index
|
|
704
|
-
|
|
705
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
706
|
-
try:
|
|
707
|
-
self._call_mcp_tool("browser_tabs", action="close", index=tab_index)
|
|
708
|
-
self.tabs.pop(tab_index, None)
|
|
709
|
-
logger.info(f"Closed tab: {tab_index}")
|
|
710
|
-
return True
|
|
711
|
-
except Exception as e:
|
|
712
|
-
logger.error(f"Close tab failed: {e}")
|
|
713
|
-
return False
|
|
714
|
-
else:
|
|
715
|
-
# Fallback: Close page
|
|
716
|
-
if tab_index in self.tabs:
|
|
717
|
-
page = self.tabs.pop(tab_index)
|
|
718
|
-
if hasattr(page, "close"):
|
|
719
|
-
page.close()
|
|
720
|
-
logger.info(f"Closed tab: {tab_index} (fallback)")
|
|
721
|
-
return True
|
|
722
|
-
return False
|
|
723
|
-
|
|
724
|
-
def list_tabs(self) -> list[dict[str, Any]]:
|
|
725
|
-
"""
|
|
726
|
-
List all open tabs.
|
|
727
|
-
|
|
728
|
-
Returns:
|
|
729
|
-
List of tab information dictionaries
|
|
730
|
-
"""
|
|
731
|
-
if self.mode == MCPToolMode.DIRECT:
|
|
732
|
-
try:
|
|
733
|
-
result = self._call_mcp_tool("browser_tabs", action="list")
|
|
734
|
-
tabs = result if isinstance(result, list) else result.get("tabs", [])
|
|
735
|
-
return tabs
|
|
736
|
-
except Exception as e:
|
|
737
|
-
logger.error(f"List tabs failed: {e}")
|
|
738
|
-
return []
|
|
739
|
-
else:
|
|
740
|
-
# Fallback: Return tab indices
|
|
741
|
-
return [{"index": idx, "url": "unknown"} for idx in self.tabs.keys()]
|
|
742
|
-
|
|
743
|
-
def close(self):
|
|
744
|
-
"""Close browser and cleanup."""
|
|
745
|
-
# Stop recording and tracing
|
|
746
|
-
if self.network_recorder:
|
|
747
|
-
self.stop_network_recording()
|
|
748
|
-
if self.trace_manager:
|
|
749
|
-
self.stop_tracing()
|
|
750
|
-
|
|
751
|
-
# Close all tabs
|
|
752
|
-
for tab_index in list(self.tabs.keys()):
|
|
753
|
-
self.close_tab(tab_index)
|
|
754
|
-
|
|
755
|
-
if self.mode == MCPToolMode.FALLBACK:
|
|
756
|
-
if self.page:
|
|
757
|
-
self.page.close()
|
|
758
|
-
self.page = None
|
|
759
|
-
if self.context:
|
|
760
|
-
self.context.close()
|
|
761
|
-
self.context = None
|
|
762
|
-
if self.browser:
|
|
763
|
-
self.browser.close()
|
|
764
|
-
self.browser = None
|
|
765
|
-
if self.playwright:
|
|
766
|
-
self.playwright.stop()
|
|
767
|
-
self.playwright = None
|
|
768
|
-
logger.info("Browser closed (fallback)")
|
|
769
|
-
else:
|
|
770
|
-
# MCP mode: Browser is managed by MCP server
|
|
771
|
-
logger.info("Browser cleanup (MCP managed)")
|
|
1
|
+
"""
|
|
2
|
+
Playwright MCP Controller for browser automation via MCP tools.
|
|
3
|
+
|
|
4
|
+
Provides a unified interface for browser automation that works both:
|
|
5
|
+
- In Cursor IDE: Uses Playwright MCP tools directly
|
|
6
|
+
- In CLI mode: Falls back to Python Playwright package
|
|
7
|
+
|
|
8
|
+
This controller enables direct use of Playwright MCP capabilities including:
|
|
9
|
+
- Navigation and page management
|
|
10
|
+
- Element interaction (click, type, fill forms)
|
|
11
|
+
- Screenshots and snapshots
|
|
12
|
+
- Network monitoring
|
|
13
|
+
- Console message capture
|
|
14
|
+
- Accessibility tree access
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Callable, Optional
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# Import network recorder and trace manager
|
|
28
|
+
try:
|
|
29
|
+
from ...agents.tester.network_recorder import NetworkRecorder
|
|
30
|
+
from ...agents.tester.trace_manager import TraceManager
|
|
31
|
+
except ImportError:
|
|
32
|
+
NetworkRecorder = None
|
|
33
|
+
TraceManager = None
|
|
34
|
+
|
|
35
|
+
# Try to import Playwright for fallback
|
|
36
|
+
try:
|
|
37
|
+
from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright
|
|
38
|
+
|
|
39
|
+
HAS_PLAYWRIGHT = True
|
|
40
|
+
except ImportError:
|
|
41
|
+
HAS_PLAYWRIGHT = False
|
|
42
|
+
Browser = Any
|
|
43
|
+
Page = Any
|
|
44
|
+
BrowserContext = Any
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MCPToolMode(Enum):
|
|
48
|
+
"""MCP tool invocation mode."""
|
|
49
|
+
|
|
50
|
+
DIRECT = "direct" # Direct MCP function calls (Cursor IDE)
|
|
51
|
+
GATEWAY = "gateway" # Via MCP Gateway (framework)
|
|
52
|
+
FALLBACK = "fallback" # Python Playwright package fallback
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ElementRef:
|
|
57
|
+
"""Element reference for MCP tool calls."""
|
|
58
|
+
|
|
59
|
+
element: str # Human-readable description
|
|
60
|
+
ref: str # Exact element reference from snapshot
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class AccessibilitySnapshot:
|
|
65
|
+
"""Accessibility snapshot data."""
|
|
66
|
+
|
|
67
|
+
content: str # Snapshot markdown content
|
|
68
|
+
filename: Optional[str] = None # Optional saved filename
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class PerformanceMetrics:
|
|
73
|
+
"""Performance metrics from page load."""
|
|
74
|
+
|
|
75
|
+
lcp: Optional[float] = None # Largest Contentful Paint (seconds)
|
|
76
|
+
fid: Optional[float] = None # First Input Delay (milliseconds)
|
|
77
|
+
cls: Optional[float] = None # Cumulative Layout Shift
|
|
78
|
+
fcp: Optional[float] = None # First Contentful Paint (seconds)
|
|
79
|
+
load_time: Optional[float] = None # Total load time (seconds)
|
|
80
|
+
dom_content_loaded: Optional[float] = None # DOMContentLoaded time (seconds)
|
|
81
|
+
network_requests: int = 0 # Number of network requests
|
|
82
|
+
failed_requests: int = 0 # Number of failed requests
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class PlaywrightMCPController:
|
|
86
|
+
"""
|
|
87
|
+
Controller for Playwright MCP browser automation.
|
|
88
|
+
|
|
89
|
+
Provides a unified interface that works in both Cursor IDE (MCP tools)
|
|
90
|
+
and CLI mode (Python Playwright fallback).
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
mcp_tool_handler: Optional[Callable] = None,
|
|
96
|
+
use_fallback: bool = True,
|
|
97
|
+
enable_network_recording: bool = False,
|
|
98
|
+
enable_tracing: bool = False,
|
|
99
|
+
):
|
|
100
|
+
"""
|
|
101
|
+
Initialize Playwright MCP controller.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
mcp_tool_handler: Function to call MCP tools (for Cursor IDE)
|
|
105
|
+
use_fallback: Whether to fall back to Python Playwright if MCP unavailable
|
|
106
|
+
enable_network_recording: Enable network request recording
|
|
107
|
+
enable_tracing: Enable trace file generation
|
|
108
|
+
"""
|
|
109
|
+
self.mcp_tool_handler = mcp_tool_handler
|
|
110
|
+
self.use_fallback = use_fallback
|
|
111
|
+
self.mode = self._detect_mode()
|
|
112
|
+
self.current_tab_index = 0
|
|
113
|
+
self.tabs: dict[int, Any] = {} # Track multiple tabs
|
|
114
|
+
|
|
115
|
+
# Fallback Playwright instances
|
|
116
|
+
self.playwright: Any | None = None
|
|
117
|
+
self.browser: Browser | None = None
|
|
118
|
+
self.context: BrowserContext | None = None
|
|
119
|
+
self.page: Page | None = None
|
|
120
|
+
|
|
121
|
+
# Network recording and tracing
|
|
122
|
+
self.network_recorder: NetworkRecorder | None = None
|
|
123
|
+
self.trace_manager: TraceManager | None = None
|
|
124
|
+
if enable_network_recording and NetworkRecorder:
|
|
125
|
+
self.network_recorder = NetworkRecorder()
|
|
126
|
+
if enable_tracing and TraceManager:
|
|
127
|
+
self.trace_manager = TraceManager()
|
|
128
|
+
|
|
129
|
+
if self.mode == MCPToolMode.FALLBACK and self.use_fallback:
|
|
130
|
+
self._init_fallback()
|
|
131
|
+
|
|
132
|
+
def _detect_mode(self) -> MCPToolMode:
|
|
133
|
+
"""Detect available MCP tool mode."""
|
|
134
|
+
if self.mcp_tool_handler is not None:
|
|
135
|
+
return MCPToolMode.DIRECT
|
|
136
|
+
elif self.use_fallback and HAS_PLAYWRIGHT:
|
|
137
|
+
return MCPToolMode.FALLBACK
|
|
138
|
+
else:
|
|
139
|
+
raise RuntimeError(
|
|
140
|
+
"No Playwright available. Install Python Playwright package "
|
|
141
|
+
"or configure Playwright MCP in Cursor."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def _init_fallback(self):
|
|
145
|
+
"""Initialize Python Playwright fallback."""
|
|
146
|
+
if not HAS_PLAYWRIGHT:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
self.playwright = sync_playwright().start()
|
|
151
|
+
self.browser = self.playwright.chromium.launch(headless=True)
|
|
152
|
+
self.context = self.browser.new_context()
|
|
153
|
+
self.page = self.context.new_page()
|
|
154
|
+
logger.info("Initialized Playwright fallback (Python package)")
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(f"Failed to initialize Playwright fallback: {e}")
|
|
157
|
+
raise
|
|
158
|
+
|
|
159
|
+
def _call_mcp_tool(self, tool_name: str, **kwargs) -> Any:
|
|
160
|
+
"""
|
|
161
|
+
Call MCP tool via handler.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
tool_name: Name of MCP tool (e.g., 'browser_navigate')
|
|
165
|
+
**kwargs: Tool arguments
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Tool result
|
|
169
|
+
"""
|
|
170
|
+
if self.mcp_tool_handler is None:
|
|
171
|
+
raise RuntimeError("MCP tool handler not available")
|
|
172
|
+
|
|
173
|
+
# Map tool names to MCP function names
|
|
174
|
+
mcp_function_name = f"mcp_Playwright_{tool_name}"
|
|
175
|
+
return self.mcp_tool_handler(mcp_function_name, **kwargs)
|
|
176
|
+
|
|
177
|
+
def navigate(self, url: str) -> bool:
|
|
178
|
+
"""
|
|
179
|
+
Navigate to URL.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
url: URL to navigate to
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
True if successful, False otherwise
|
|
186
|
+
"""
|
|
187
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
188
|
+
try:
|
|
189
|
+
result = self._call_mcp_tool("browser_navigate", url=url)
|
|
190
|
+
logger.info(f"Navigated to: {url} (MCP)")
|
|
191
|
+
return True
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.error(f"Navigation failed: {e}")
|
|
194
|
+
return False
|
|
195
|
+
else:
|
|
196
|
+
# Fallback to Python Playwright
|
|
197
|
+
if not self.page:
|
|
198
|
+
logger.error("Browser not initialized")
|
|
199
|
+
return False
|
|
200
|
+
try:
|
|
201
|
+
self.page.goto(url, wait_until="load", timeout=30000)
|
|
202
|
+
logger.info(f"Navigated to: {url} (fallback)")
|
|
203
|
+
return True
|
|
204
|
+
except Exception as e:
|
|
205
|
+
logger.error(f"Navigation failed: {e}")
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
def snapshot(self, filename: Optional[str] = None) -> Optional[AccessibilitySnapshot]:
|
|
209
|
+
"""
|
|
210
|
+
Get accessibility snapshot of current page.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
filename: Optional filename to save snapshot
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
AccessibilitySnapshot or None if failed
|
|
217
|
+
"""
|
|
218
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
219
|
+
try:
|
|
220
|
+
kwargs = {}
|
|
221
|
+
if filename:
|
|
222
|
+
kwargs["filename"] = filename
|
|
223
|
+
|
|
224
|
+
result = self._call_mcp_tool("browser_snapshot", **kwargs)
|
|
225
|
+
content = result if isinstance(result, str) else result.get("content", "")
|
|
226
|
+
|
|
227
|
+
return AccessibilitySnapshot(content=content, filename=filename)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.error(f"Snapshot failed: {e}")
|
|
230
|
+
return None
|
|
231
|
+
else:
|
|
232
|
+
# Fallback: Use page content as snapshot
|
|
233
|
+
if not self.page:
|
|
234
|
+
logger.error("Browser not initialized")
|
|
235
|
+
return None
|
|
236
|
+
try:
|
|
237
|
+
content = self.page.content()
|
|
238
|
+
# Convert HTML to markdown-like snapshot
|
|
239
|
+
snapshot_content = f"# Page Snapshot\n\n```html\n{content}\n```"
|
|
240
|
+
return AccessibilitySnapshot(content=snapshot_content, filename=filename)
|
|
241
|
+
except Exception as e:
|
|
242
|
+
logger.error(f"Snapshot failed: {e}")
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
def click(self, element: str, ref: str, button: str = "left") -> bool:
|
|
246
|
+
"""
|
|
247
|
+
Click an element.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
element: Human-readable element description
|
|
251
|
+
ref: Exact element reference from snapshot
|
|
252
|
+
button: Mouse button ("left", "right", "middle")
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
True if successful, False otherwise
|
|
256
|
+
"""
|
|
257
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
258
|
+
try:
|
|
259
|
+
self._call_mcp_tool(
|
|
260
|
+
"browser_click", element=element, ref=ref, button=button
|
|
261
|
+
)
|
|
262
|
+
logger.debug(f"Clicked: {element}")
|
|
263
|
+
return True
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"Click failed: {e}")
|
|
266
|
+
return False
|
|
267
|
+
else:
|
|
268
|
+
# Fallback: Use CSS selector from ref or element
|
|
269
|
+
if not self.page:
|
|
270
|
+
logger.error("Browser not initialized")
|
|
271
|
+
return False
|
|
272
|
+
try:
|
|
273
|
+
# Try to extract selector from ref, fallback to element description
|
|
274
|
+
selector = ref if ref.startswith("#") or ref.startswith(".") else element
|
|
275
|
+
self.page.click(selector, button=button, timeout=5000)
|
|
276
|
+
logger.debug(f"Clicked: {element} (fallback)")
|
|
277
|
+
return True
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.error(f"Click failed: {e}")
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
def type_text(self, element: str, ref: str, text: str, submit: bool = False) -> bool:
|
|
283
|
+
"""
|
|
284
|
+
Type text into an element.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
element: Human-readable element description
|
|
288
|
+
ref: Exact element reference from snapshot
|
|
289
|
+
text: Text to type
|
|
290
|
+
submit: Whether to submit after typing (press Enter)
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
True if successful, False otherwise
|
|
294
|
+
"""
|
|
295
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
296
|
+
try:
|
|
297
|
+
self._call_mcp_tool(
|
|
298
|
+
"browser_type",
|
|
299
|
+
element=element,
|
|
300
|
+
ref=ref,
|
|
301
|
+
text=text,
|
|
302
|
+
submit=submit,
|
|
303
|
+
)
|
|
304
|
+
logger.debug(f"Typed into {element}: {text}")
|
|
305
|
+
return True
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(f"Type failed: {e}")
|
|
308
|
+
return False
|
|
309
|
+
else:
|
|
310
|
+
# Fallback
|
|
311
|
+
if not self.page:
|
|
312
|
+
logger.error("Browser not initialized")
|
|
313
|
+
return False
|
|
314
|
+
try:
|
|
315
|
+
selector = ref if ref.startswith("#") or ref.startswith(".") else element
|
|
316
|
+
self.page.fill(selector, text)
|
|
317
|
+
if submit:
|
|
318
|
+
self.page.press(selector, "Enter")
|
|
319
|
+
logger.debug(f"Typed into {element}: {text} (fallback)")
|
|
320
|
+
return True
|
|
321
|
+
except Exception as e:
|
|
322
|
+
logger.error(f"Type failed: {e}")
|
|
323
|
+
return False
|
|
324
|
+
|
|
325
|
+
def fill_form(self, fields: list[dict[str, Any]]) -> bool:
|
|
326
|
+
"""
|
|
327
|
+
Fill multiple form fields at once.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
fields: List of field dictionaries with 'name', 'type', 'ref', 'value'
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
True if successful, False otherwise
|
|
334
|
+
"""
|
|
335
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
336
|
+
try:
|
|
337
|
+
# Convert fields to MCP format
|
|
338
|
+
mcp_fields = []
|
|
339
|
+
for field in fields:
|
|
340
|
+
mcp_fields.append(
|
|
341
|
+
{
|
|
342
|
+
"name": field.get("name", ""),
|
|
343
|
+
"type": field.get("type", "textbox"),
|
|
344
|
+
"ref": field.get("ref", ""),
|
|
345
|
+
"value": field.get("value", ""),
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
self._call_mcp_tool("browser_fill_form", fields=mcp_fields)
|
|
349
|
+
logger.debug(f"Filled form with {len(fields)} fields")
|
|
350
|
+
return True
|
|
351
|
+
except Exception as e:
|
|
352
|
+
logger.error(f"Fill form failed: {e}")
|
|
353
|
+
return False
|
|
354
|
+
else:
|
|
355
|
+
# Fallback: Fill fields one by one
|
|
356
|
+
if not self.page:
|
|
357
|
+
logger.error("Browser not initialized")
|
|
358
|
+
return False
|
|
359
|
+
try:
|
|
360
|
+
for field in fields:
|
|
361
|
+
selector = field.get("ref") or field.get("name", "")
|
|
362
|
+
value = field.get("value", "")
|
|
363
|
+
field_type = field.get("type", "textbox")
|
|
364
|
+
|
|
365
|
+
if field_type == "checkbox":
|
|
366
|
+
if value:
|
|
367
|
+
self.page.check(selector)
|
|
368
|
+
else:
|
|
369
|
+
self.page.uncheck(selector)
|
|
370
|
+
elif field_type == "combobox":
|
|
371
|
+
self.page.select_option(selector, value)
|
|
372
|
+
else:
|
|
373
|
+
self.page.fill(selector, value)
|
|
374
|
+
|
|
375
|
+
logger.debug(f"Filled form with {len(fields)} fields (fallback)")
|
|
376
|
+
return True
|
|
377
|
+
except Exception as e:
|
|
378
|
+
logger.error(f"Fill form failed: {e}")
|
|
379
|
+
return False
|
|
380
|
+
|
|
381
|
+
def take_screenshot(
|
|
382
|
+
self,
|
|
383
|
+
filename: Optional[str] = None,
|
|
384
|
+
full_page: bool = False,
|
|
385
|
+
element: Optional[str] = None,
|
|
386
|
+
ref: Optional[str] = None,
|
|
387
|
+
) -> Optional[str]:
|
|
388
|
+
"""
|
|
389
|
+
Take a screenshot.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
filename: Output filename
|
|
393
|
+
full_page: Whether to capture full page
|
|
394
|
+
element: Optional element description for element screenshot
|
|
395
|
+
ref: Optional element reference for element screenshot
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Filename if successful, None otherwise
|
|
399
|
+
"""
|
|
400
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
401
|
+
try:
|
|
402
|
+
kwargs = {}
|
|
403
|
+
if filename:
|
|
404
|
+
kwargs["filename"] = filename
|
|
405
|
+
if full_page:
|
|
406
|
+
kwargs["fullPage"] = True
|
|
407
|
+
if element and ref:
|
|
408
|
+
kwargs["element"] = element
|
|
409
|
+
kwargs["ref"] = ref
|
|
410
|
+
|
|
411
|
+
result = self._call_mcp_tool("browser_take_screenshot", **kwargs)
|
|
412
|
+
saved_filename = filename or result.get("filename", "screenshot.png")
|
|
413
|
+
logger.info(f"Screenshot saved: {saved_filename}")
|
|
414
|
+
return saved_filename
|
|
415
|
+
except Exception as e:
|
|
416
|
+
logger.error(f"Screenshot failed: {e}")
|
|
417
|
+
return None
|
|
418
|
+
else:
|
|
419
|
+
# Fallback
|
|
420
|
+
if not self.page:
|
|
421
|
+
logger.error("Browser not initialized")
|
|
422
|
+
return None
|
|
423
|
+
try:
|
|
424
|
+
if not filename:
|
|
425
|
+
filename = "screenshot.png"
|
|
426
|
+
|
|
427
|
+
screenshot_kwargs = {"path": filename, "full_page": full_page}
|
|
428
|
+
if element and ref:
|
|
429
|
+
selector = ref if ref.startswith("#") or ref.startswith(".") else element
|
|
430
|
+
screenshot_kwargs["selector"] = selector
|
|
431
|
+
|
|
432
|
+
self.page.screenshot(**screenshot_kwargs)
|
|
433
|
+
logger.info(f"Screenshot saved: {filename} (fallback)")
|
|
434
|
+
return filename
|
|
435
|
+
except Exception as e:
|
|
436
|
+
logger.error(f"Screenshot failed: {e}")
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
def get_console_messages(self, level: str = "info") -> list[dict[str, Any]]:
|
|
440
|
+
"""
|
|
441
|
+
Get console messages from the page.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
level: Message level ("error", "warning", "info", "debug")
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
List of console messages
|
|
448
|
+
"""
|
|
449
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
450
|
+
try:
|
|
451
|
+
result = self._call_mcp_tool("browser_console_messages", level=level)
|
|
452
|
+
return result if isinstance(result, list) else result.get("messages", [])
|
|
453
|
+
except Exception as e:
|
|
454
|
+
logger.error(f"Get console messages failed: {e}")
|
|
455
|
+
return []
|
|
456
|
+
else:
|
|
457
|
+
# Fallback: Not easily available in Python Playwright
|
|
458
|
+
logger.warning("Console messages not available in fallback mode")
|
|
459
|
+
return []
|
|
460
|
+
|
|
461
|
+
def get_network_requests(
|
|
462
|
+
self, include_static: bool = False
|
|
463
|
+
) -> list[dict[str, Any]]:
|
|
464
|
+
"""
|
|
465
|
+
Get network requests from the page.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
include_static: Whether to include static resources (images, fonts, etc.)
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
List of network requests
|
|
472
|
+
"""
|
|
473
|
+
requests = []
|
|
474
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
475
|
+
try:
|
|
476
|
+
result = self._call_mcp_tool(
|
|
477
|
+
"browser_network_requests", includeStatic=include_static
|
|
478
|
+
)
|
|
479
|
+
requests = result if isinstance(result, list) else result.get("requests", [])
|
|
480
|
+
except Exception as e:
|
|
481
|
+
logger.error(f"Get network requests failed: {e}")
|
|
482
|
+
return []
|
|
483
|
+
else:
|
|
484
|
+
# Fallback: Not easily available in Python Playwright
|
|
485
|
+
logger.warning("Network requests not available in fallback mode")
|
|
486
|
+
return []
|
|
487
|
+
|
|
488
|
+
# Record requests if recording is enabled
|
|
489
|
+
if self.network_recorder and requests:
|
|
490
|
+
for req in requests:
|
|
491
|
+
self.network_recorder.record_request(
|
|
492
|
+
url=req.get("url", ""),
|
|
493
|
+
method=req.get("method", "GET"),
|
|
494
|
+
headers=req.get("headers", {}),
|
|
495
|
+
response_status=req.get("status"),
|
|
496
|
+
response_headers=req.get("responseHeaders", {}),
|
|
497
|
+
response_body=req.get("body"),
|
|
498
|
+
request_id=req.get("id"),
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
return requests
|
|
502
|
+
|
|
503
|
+
def wait_for(
|
|
504
|
+
self, text: Optional[str] = None, text_gone: Optional[str] = None, time: Optional[float] = None
|
|
505
|
+
) -> bool:
|
|
506
|
+
"""
|
|
507
|
+
Wait for text to appear/disappear or time to pass.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
text: Text to wait for
|
|
511
|
+
text_gone: Text to wait for to disappear
|
|
512
|
+
time: Time to wait in seconds
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
True if condition met, False otherwise
|
|
516
|
+
"""
|
|
517
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
518
|
+
try:
|
|
519
|
+
kwargs = {}
|
|
520
|
+
if text:
|
|
521
|
+
kwargs["text"] = text
|
|
522
|
+
if text_gone:
|
|
523
|
+
kwargs["textGone"] = text_gone
|
|
524
|
+
if time:
|
|
525
|
+
kwargs["time"] = time
|
|
526
|
+
|
|
527
|
+
self._call_mcp_tool("browser_wait_for", **kwargs)
|
|
528
|
+
return True
|
|
529
|
+
except Exception as e:
|
|
530
|
+
logger.error(f"Wait failed: {e}")
|
|
531
|
+
return False
|
|
532
|
+
else:
|
|
533
|
+
# Fallback
|
|
534
|
+
if not self.page:
|
|
535
|
+
logger.error("Browser not initialized")
|
|
536
|
+
return False
|
|
537
|
+
try:
|
|
538
|
+
if text:
|
|
539
|
+
self.page.wait_for_selector(f"text={text}", timeout=5000)
|
|
540
|
+
elif text_gone:
|
|
541
|
+
self.page.wait_for_selector(f"text={text_gone}", state="hidden", timeout=5000)
|
|
542
|
+
elif time:
|
|
543
|
+
import time as time_module
|
|
544
|
+
time_module.sleep(time)
|
|
545
|
+
|
|
546
|
+
return True
|
|
547
|
+
except Exception as e:
|
|
548
|
+
logger.error(f"Wait failed: {e}")
|
|
549
|
+
return False
|
|
550
|
+
|
|
551
|
+
def evaluate(self, function: str, element: Optional[str] = None, ref: Optional[str] = None) -> Any:
|
|
552
|
+
"""
|
|
553
|
+
Evaluate JavaScript expression on page or element.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
function: JavaScript function as string (e.g., "() => { return document.title; }")
|
|
557
|
+
element: Optional element description
|
|
558
|
+
ref: Optional element reference
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
Evaluation result
|
|
562
|
+
"""
|
|
563
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
564
|
+
try:
|
|
565
|
+
kwargs = {"function": function}
|
|
566
|
+
if element:
|
|
567
|
+
kwargs["element"] = element
|
|
568
|
+
if ref:
|
|
569
|
+
kwargs["ref"] = ref
|
|
570
|
+
|
|
571
|
+
result = self._call_mcp_tool("browser_evaluate", **kwargs)
|
|
572
|
+
return result
|
|
573
|
+
except Exception as e:
|
|
574
|
+
logger.error(f"Evaluate failed: {e}")
|
|
575
|
+
return None
|
|
576
|
+
else:
|
|
577
|
+
# Fallback
|
|
578
|
+
if not self.page:
|
|
579
|
+
logger.error("Browser not initialized")
|
|
580
|
+
return None
|
|
581
|
+
try:
|
|
582
|
+
if element and ref:
|
|
583
|
+
selector = ref if ref.startswith("#") or ref.startswith(".") else element
|
|
584
|
+
return self.page.evaluate(function, self.page.query_selector(selector))
|
|
585
|
+
else:
|
|
586
|
+
return self.page.evaluate(function)
|
|
587
|
+
except Exception as e:
|
|
588
|
+
logger.error(f"Evaluate failed: {e}")
|
|
589
|
+
return None
|
|
590
|
+
|
|
591
|
+
def start_network_recording(self, session_id: str | None = None, description: str | None = None) -> str:
|
|
592
|
+
"""
|
|
593
|
+
Start network request recording.
|
|
594
|
+
|
|
595
|
+
Args:
|
|
596
|
+
session_id: Optional session ID
|
|
597
|
+
description: Optional description
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
Session ID
|
|
601
|
+
"""
|
|
602
|
+
if self.network_recorder:
|
|
603
|
+
return self.network_recorder.start_recording(session_id, description)
|
|
604
|
+
else:
|
|
605
|
+
logger.warning("Network recording not enabled")
|
|
606
|
+
return ""
|
|
607
|
+
|
|
608
|
+
def stop_network_recording(self):
|
|
609
|
+
"""Stop network recording and save."""
|
|
610
|
+
if self.network_recorder:
|
|
611
|
+
return self.network_recorder.stop_recording()
|
|
612
|
+
return None
|
|
613
|
+
|
|
614
|
+
def start_tracing(self, test_name: str | None = None) -> Path | None:
|
|
615
|
+
"""
|
|
616
|
+
Start trace recording.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
test_name: Optional test name
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
Trace file path
|
|
623
|
+
"""
|
|
624
|
+
if self.trace_manager and self.context:
|
|
625
|
+
return self.trace_manager.start_tracing(self.context, test_name)
|
|
626
|
+
elif self.trace_manager:
|
|
627
|
+
# MCP mode - tracing will be handled by MCP
|
|
628
|
+
return self.trace_manager.start_tracing(None, test_name)
|
|
629
|
+
return None
|
|
630
|
+
|
|
631
|
+
def stop_tracing(self) -> Path | None:
|
|
632
|
+
"""Stop tracing and save trace file."""
|
|
633
|
+
if self.trace_manager:
|
|
634
|
+
return self.trace_manager.stop_tracing(self.context)
|
|
635
|
+
return None
|
|
636
|
+
|
|
637
|
+
def create_tab(self) -> int:
|
|
638
|
+
"""
|
|
639
|
+
Create a new browser tab.
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
Tab index
|
|
643
|
+
"""
|
|
644
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
645
|
+
try:
|
|
646
|
+
result = self._call_mcp_tool("browser_tabs", action="new")
|
|
647
|
+
tab_index = result.get("index", len(self.tabs))
|
|
648
|
+
self.tabs[tab_index] = result
|
|
649
|
+
logger.info(f"Created tab: {tab_index}")
|
|
650
|
+
return tab_index
|
|
651
|
+
except Exception as e:
|
|
652
|
+
logger.error(f"Create tab failed: {e}")
|
|
653
|
+
return -1
|
|
654
|
+
else:
|
|
655
|
+
# Fallback: Create new page in context
|
|
656
|
+
if self.context:
|
|
657
|
+
new_page = self.context.new_page()
|
|
658
|
+
tab_index = len(self.tabs)
|
|
659
|
+
self.tabs[tab_index] = new_page
|
|
660
|
+
logger.info(f"Created tab: {tab_index} (fallback)")
|
|
661
|
+
return tab_index
|
|
662
|
+
return -1
|
|
663
|
+
|
|
664
|
+
def switch_tab(self, tab_index: int) -> bool:
|
|
665
|
+
"""
|
|
666
|
+
Switch to a different tab.
|
|
667
|
+
|
|
668
|
+
Args:
|
|
669
|
+
tab_index: Tab index to switch to
|
|
670
|
+
|
|
671
|
+
Returns:
|
|
672
|
+
True if successful
|
|
673
|
+
"""
|
|
674
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
675
|
+
try:
|
|
676
|
+
self._call_mcp_tool("browser_tabs", action="select", index=tab_index)
|
|
677
|
+
self.current_tab_index = tab_index
|
|
678
|
+
logger.info(f"Switched to tab: {tab_index}")
|
|
679
|
+
return True
|
|
680
|
+
except Exception as e:
|
|
681
|
+
logger.error(f"Switch tab failed: {e}")
|
|
682
|
+
return False
|
|
683
|
+
else:
|
|
684
|
+
# Fallback: Switch page reference
|
|
685
|
+
if tab_index in self.tabs:
|
|
686
|
+
self.page = self.tabs[tab_index]
|
|
687
|
+
self.current_tab_index = tab_index
|
|
688
|
+
logger.info(f"Switched to tab: {tab_index} (fallback)")
|
|
689
|
+
return True
|
|
690
|
+
return False
|
|
691
|
+
|
|
692
|
+
def close_tab(self, tab_index: int | None = None) -> bool:
|
|
693
|
+
"""
|
|
694
|
+
Close a browser tab.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
tab_index: Tab index to close (None = current tab)
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
True if successful
|
|
701
|
+
"""
|
|
702
|
+
if tab_index is None:
|
|
703
|
+
tab_index = self.current_tab_index
|
|
704
|
+
|
|
705
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
706
|
+
try:
|
|
707
|
+
self._call_mcp_tool("browser_tabs", action="close", index=tab_index)
|
|
708
|
+
self.tabs.pop(tab_index, None)
|
|
709
|
+
logger.info(f"Closed tab: {tab_index}")
|
|
710
|
+
return True
|
|
711
|
+
except Exception as e:
|
|
712
|
+
logger.error(f"Close tab failed: {e}")
|
|
713
|
+
return False
|
|
714
|
+
else:
|
|
715
|
+
# Fallback: Close page
|
|
716
|
+
if tab_index in self.tabs:
|
|
717
|
+
page = self.tabs.pop(tab_index)
|
|
718
|
+
if hasattr(page, "close"):
|
|
719
|
+
page.close()
|
|
720
|
+
logger.info(f"Closed tab: {tab_index} (fallback)")
|
|
721
|
+
return True
|
|
722
|
+
return False
|
|
723
|
+
|
|
724
|
+
def list_tabs(self) -> list[dict[str, Any]]:
|
|
725
|
+
"""
|
|
726
|
+
List all open tabs.
|
|
727
|
+
|
|
728
|
+
Returns:
|
|
729
|
+
List of tab information dictionaries
|
|
730
|
+
"""
|
|
731
|
+
if self.mode == MCPToolMode.DIRECT:
|
|
732
|
+
try:
|
|
733
|
+
result = self._call_mcp_tool("browser_tabs", action="list")
|
|
734
|
+
tabs = result if isinstance(result, list) else result.get("tabs", [])
|
|
735
|
+
return tabs
|
|
736
|
+
except Exception as e:
|
|
737
|
+
logger.error(f"List tabs failed: {e}")
|
|
738
|
+
return []
|
|
739
|
+
else:
|
|
740
|
+
# Fallback: Return tab indices
|
|
741
|
+
return [{"index": idx, "url": "unknown"} for idx in self.tabs.keys()]
|
|
742
|
+
|
|
743
|
+
def close(self):
|
|
744
|
+
"""Close browser and cleanup."""
|
|
745
|
+
# Stop recording and tracing
|
|
746
|
+
if self.network_recorder:
|
|
747
|
+
self.stop_network_recording()
|
|
748
|
+
if self.trace_manager:
|
|
749
|
+
self.stop_tracing()
|
|
750
|
+
|
|
751
|
+
# Close all tabs
|
|
752
|
+
for tab_index in list(self.tabs.keys()):
|
|
753
|
+
self.close_tab(tab_index)
|
|
754
|
+
|
|
755
|
+
if self.mode == MCPToolMode.FALLBACK:
|
|
756
|
+
if self.page:
|
|
757
|
+
self.page.close()
|
|
758
|
+
self.page = None
|
|
759
|
+
if self.context:
|
|
760
|
+
self.context.close()
|
|
761
|
+
self.context = None
|
|
762
|
+
if self.browser:
|
|
763
|
+
self.browser.close()
|
|
764
|
+
self.browser = None
|
|
765
|
+
if self.playwright:
|
|
766
|
+
self.playwright.stop()
|
|
767
|
+
self.playwright = None
|
|
768
|
+
logger.info("Browser closed (fallback)")
|
|
769
|
+
else:
|
|
770
|
+
# MCP mode: Browser is managed by MCP server
|
|
771
|
+
logger.info("Browser cleanup (MCP managed)")
|