import time
import json
import logging
import os
import shutil
from pathlib import Path
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Union, Callable, Awaitable
from contextlib import asynccontextmanager
import sqlite3
import asyncio
import gradio as gr
import threading
from functools import wraps
from dotenv import load_dotenv
from playwright.async_api import async_playwright

SETTINGS_DB = 'settings.db'
DEFAULT_TEMPERATURE = 1.0
DEFAULT_WINDOW_WIDTH = 1280
DEFAULT_WINDOW_HEIGHT = 720
DEFAULT_MAX_STEPS = 10
LOG_DIR = Path('./logs')
TEMP_DIR = Path('./temp')

def init_database():
    """Initialize the settings database if it doesn't exist."""
    with sqlite3.connect(SETTINGS_DB) as conn:
        conn.execute("""
            CREATE TABLE IF NOT EXISTS settings (
                key TEXT PRIMARY KEY,
                value TEXT NOT NULL
            )
        """)
        conn.commit()

class SettingsManager:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._setup_pool()
        return cls._instance

    def _setup_pool(self):
        self._pool = sqlite3.connect(
            SETTINGS_DB,
            check_same_thread=False,
            timeout=30.0
        )
        with self._lock:
            self._pool.row_factory = sqlite3.Row

        # Initialize cache
        self._cache = {}

    def get_cached(self, key: str) -> Any:
        with self._lock:
            if key in self._cache:
                value, expires_at = self._cache[key]
                if expires_at > time.time():
                    return value
                del self._cache[key]
        return None

    def set_cached(self, key: str, value: Any, ttl: int = 300):
        with self._lock:
            self._cache[key] = (value, time.time() + ttl)

    def save_setting(self, key: str, value: Any):
        with self._lock:
            with self._pool:
                self._pool.execute(
                    "INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
                    (key, json.dumps(value))
                )

    def load_setting(self, key: str, default: Any = None) -> Any:
        try:
            with self._lock:
                cursor = self._pool.execute(
                    "SELECT value FROM settings WHERE key = ?",
                    (key,)
                )
                result = cursor.fetchone()
                return json.loads(result[0]) if result else default
        except Exception as e:
            logger.error(f"Error loading setting {key}: {e}")
            return default

    def close(self):
        with self._lock:
            if hasattr(self, '_pool'):
                self._pool.close()

class SecurityManager:
    def __init__(self):
        self.rate_limits = {}
        self.max_requests = 100
        self.time_window = 3600
        self._lock = threading.Lock()

    def check_rate_limit(self, key: str) -> bool:
        now = time.time()
        with self._lock:
            if key not in self.rate_limits:
                self.rate_limits[key] = []
            self.rate_limits[key] = [t for t in self.rate_limits[key] if t > now - self.time_window]
            if len(self.rate_limits[key]) >= self.max_requests:
                return False
            self.rate_limits[key].append(now)
        return True

def rate_limited(func: Callable[..., Awaitable]):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        if not SecurityManager().check_rate_limit(func.__name__):
            raise Exception("Rate limit exceeded")
        return await func(*args, **kwargs)
    return wrapper

@asynccontextmanager
async def browser_session(config: "AgentConfig"):
    runner = BrowserAgentRunner(config)
    try:
        yield runner
    finally:
        await runner.cleanup()

class BrowserAgentRunner:
    def __init__(self, config: "AgentConfig"):
        self.config = config
        self.playwright = None
        self.browser_context = None
        self.browser = None
        self.resource_manager = ResourceManager()
        self.security_manager = SecurityManager()
        self._start_time = time.time()

    @property
    def execution_time(self) -> float:
        return time.time() - self._start_time

    async def run(self):
        try:
            async with async_playwright() as p:
                self.browser = await p.chromium.launch(headless=self.config.headless)
                self.browser_context = await self.browser.new_context(
                    viewport={'width': self.config.window_w, 'height': self.config.window_h}
                )
                page = await self.browser_context.new_page()

                # Example task: Navigate to a given URL
                await page.goto("https://example.com")
                # Perform more actions here based on the task

                return "Task completed successfully", "", "", ""
            except Exception as e:
                logger.error(f"Detailed error during run: {e}")
                return "", str(e), "", ""
        finally:
            await self.cleanup()

    async def cleanup(self):
        logger.debug("Cleaning up browser session.")
        try:
            if self.browser_context:
                await self.browser_context.close()
                logger.debug("Browser context closed successfully.")
                self.browser_context = None

            if self.browser:
                await self.browser.close()
                logger.debug("Browser closed successfully.")
                self.browser = None

            await self.resource_manager.cleanup()
        except Exception as e:
            logger.error(f"Error during cleanup: {e}")

class ProcessManager:
    def __init__(self):
        self.processes = []
        self._lock = threading.Lock()

    async def start_task(self, task):
        with self._lock:
            # Logic to start a task
            pass

    async def stop_task(self):
        with self._lock:
            # Logic to stop tasks
            return "", ""

    def cleanup(self):
        with self._lock:
            # Logic for cleanup after tasks
            pass

class GradioInterface:
    def __init__(self):
        self.theme = gr.themes.Soft()
        self.settings_manager = SettingsManager()
        self.process_manager = ProcessManager()
        self.security_manager = SecurityManager()

    @rate_limited
    async def _run_with_manager(self, *args):
        try:
            logger.debug("Starting _run_with_manager...")
            async with browser_session(AgentConfig(*args)) as runner:
                final_result, errors, model_actions, model_thoughts = await runner.run()
                logger.debug(f"Returning values: {final_result}, {errors}, {model_actions}, {model_thoughts}")
                return final_result or "", errors or "", model_actions or "", model_thoughts or ""
        except Exception as e:
            logger.error(f"Error in _run_with_manager: {e}")
            return str(e), str(e), "", ""

    async def _stop_agent(self):
        return await self.process_manager.stop_task()

    def _load_saved_values(self) -> Dict[str, Any]:
        return {
            "agent_type": self.settings_manager.load_setting("agent_type", "custom"),
            "max_steps": self.settings_manager.load_setting("max_steps", DEFAULT_MAX_STEPS),
            "use_vision": self.settings_manager.load_setting("use_vision", True),
            "llm_provider": self.settings_manager.load_setting("llm_provider", "gemini"),
            "llm_model_name": self.settings_manager.load_setting("llm_model_name", "gemini-2.0-flash-exp"),
            "llm_temperature": self.settings_manager.load_setting("llm_temperature", DEFAULT_TEMPERATURE),
            "llm_base_url": self.settings_manager.load_setting("llm_base_url", ""),
            "llm_api_key": self.settings_manager.load_setting("llm_api_key", ""),
            "use_own_browser": self.settings_manager.load_setting("use_own_browser", False),
            "headless": self.settings_manager.load_setting("headless", False),
            "disable_security": self.settings_manager.load_setting("disable_security", False),
            "window_w": self.settings_manager.load_setting("window_w", DEFAULT_WINDOW_WIDTH),
            "window_h": self.settings_manager.load_setting("window_h", DEFAULT_WINDOW_HEIGHT),
            "save_recording_path": self.settings_manager.load_setting("save_recording_path", "./tmp/record_videos"),
            "task": self.settings_manager.load_setting("task", "go to google.com and type 'OpenAI' click search and give me the first url"),
            "add_infos": self.settings_manager.load_setting("add_infos", "")
        }

    def create_ui(self) -> gr.Blocks:
        saved_values = self._load_saved_values()

        def save_value(key: str, value: Any):
            self.settings_manager.save_setting(key, value)
            return value

        demo = gr.Blocks(title="Browser Use WebUI", theme=self.theme)

        with demo:
            gr.Markdown("
Browser Use WebUI
")

            with gr.Accordion("Agent Settings", open=False):
                with gr.Row():
                    agent_type = gr.Radio(
                        choices=["org", "custom"],
                        label="Agent Type",
                        value=saved_values["agent_type"],
                        info="Select the type of agent to use"
                    )
                    agent_type.change(
                        fn=lambda x: save_value("agent_type", x),
                        inputs=agent_type
                    )

            with gr.Accordion("LLM Settings", open=False):
                with gr.Row():
                    llm_provider = gr.Dropdown(
                        choices=["anthropic", "openai", "gemini", "azure_openai", "deepseek", "ollama"],
                        label="LLM Provider",
                        value=saved_values["llm_provider"],
                        info="Select the LLM provider"
                    )
                    llm_provider.change(lambda x: save_value("llm_provider", x), inputs=llm_provider)

                    llm_model_name = gr.Textbox(
                        label="LLM Model Name",
                        value=saved_values["llm_model_name"],
                        info="Model name"
                    )
                    llm_model_name.change(lambda x: save_value("llm_model_name", x), inputs=llm_model_name)

                    llm_temperature = gr.Slider(
                        minimum=0.0,
                        maximum=2.0,
                        value=saved_values["llm_temperature"],
                        label="LLM Temperature",
                        info="Response randomness"
                    )
                    llm_temperature.change(lambda x: save_value("llm_temperature", x), inputs=llm_temperature)

                with gr.Row():
                    llm_base_url = gr.Textbox(
                        label="LLM Base URL",
                        value=saved_values["llm_base_url"],
                        info="Custom API endpoint"
                    )
                    llm_base_url.change(lambda x: save_value("llm_base_url", x), inputs=llm_base_url)

                    llm_api_key = gr.Textbox(
                        label="LLM API Key",
                        value=saved_values["llm_api_key"],
                        type="password",
                        info="API key"
                    )
                    llm_api_key.change(lambda x: save_value("llm_api_key", x), inputs=llm_api_key)

            with gr.Accordion("Browser Settings", open=False):
                with gr.Row():
                    use_own_browser = gr.Checkbox(
                        label="Use Own Browser",
                        value=saved_values["use_own_browser"],
                        info="Use local Chrome"
                    )
                    use_own_browser.change(lambda x: save_value("use_own_browser", x), inputs=use_own_browser)

                    headless = gr.Checkbox(
                        label="Headless",
                        value=saved_values["headless"],
                        info="Run without GUI"
                    )
                    headless.change(lambda x: save_value("headless", x), inputs=headless)

                    disable_security = gr.Checkbox(
                        label="Disable Security",
                        value=saved_values["disable_security"],
                        info="For trusted environments only"
                    )
                    disable_security.change(lambda x: save_value("disable_security", x), inputs=disable_security)

                with gr.Row():
                    window_w = gr.Number(
                        label="Window Width",
                        value=saved_values["window_w"],
                        minimum=800,
                        maximum=3840
                    )
                    window_w.change(lambda x: save_value("window_w", x), inputs=window_w)

                    window_h = gr.Number(
                        label="Window Height",
                        value=saved_values["window_h"],
                        minimum=600,
                        maximum=2160
                    )
                    window_h.change(lambda x: save_value("window_h", x), inputs=window_h)

            with gr.Accordion("Task Settings", open=True):
                task = gr.Textbox(
                    label="Task",
                    lines=10,
                    value=saved_values["task"],
                    info="Task description"
                )
                task.change(lambda x: save_value("task", x), inputs=task)

                add_infos = gr.Textbox(
                    label="Additional Information",
                    lines=5,
                    value=saved_values["add_infos"],
                    info="Extra context"
                )
                add_infos.change(lambda x: save_value("add_infos", x), inputs=add_infos)

            save_recording_path = gr.Textbox(
                label="Save Recording Path",
                value=saved_values["save_recording_path"],
                info="Recording directory"
            )
            save_recording_path.change(lambda x: save_value("save_recording_path", x), inputs=save_recording_path)

            final_result_output = gr.Textbox(
                label="Final Result",
                lines=5
            )
            errors_output = gr.Textbox(label="Errors", lines=5)
            model_actions_output = gr.Textbox(label="Model Actions", lines=5)
            model_thoughts_output = gr.Textbox(label="Model Thoughts", lines=5)
            run_button = gr.Button("Run Agent", variant="primary")
            stop_button = gr.Button("Stop Agent", variant="stop")

            run_button.click(
                fn=self._run_with_manager,
                inputs=[
                    agent_type,
                    llm_provider,
                    llm_model_name,
                    llm_temperature,
                    llm_base_url,
                    llm_api_key,
                    use_own_browser,
                    headless,
                    disable_security,
                    window_w,
                    window_h,
                    save_recording_path,
                    task,
                    add_infos
                ],
                outputs=[final_result_output, errors_output, model_actions_output, model_thoughts_output]
            )

            stop_button.click(
                fn=self._stop_agent,
                outputs=[final_result_output, errors_output]
            )

        return demo

class ResourceManager:
    def __init__(self):
        self.temp_files: List[Path] = []
        self.active_contexts: List[Union[None, Any]] = []
        self._lock = threading.Lock()

    async def cleanup(self) -> None:
        errors = []

        with self._lock:
            # Clean up contexts
            for context in self.active_contexts:
                if context:
                    try:
                        await context.close()
                    except Exception as e:
                        error_msg = f"Failed to close context: {e}"
                        logger.error(error_msg)
                        errors.append(error_msg)

            # Clean up temp files
            for file in self.temp_files:
                try:
                    if file.exists():
                        if file.is_file():
                            file.unlink(missing_ok=True)
                        else:
                            shutil.rmtree(file, ignore_errors=True)
                except Exception as e:
                    error_msg = f"Failed to remove {file}: {e}"
                    logger.error(error_msg)
                    errors.append(error_msg)

            # Clear the lists after cleanup
            self.temp_files.clear()
            self.active_contexts.clear()
        if errors:
            logger.error("Errors occurred during cleanup:\n" + "\n".join(errors))
            raise Exception("Errors occurred during cleanup:\n" + "\n".join(errors))

def setup_logging(log_path: Optional[str] = None) -> logging.Logger:
    logger = logging.getLogger("browser_agent")
    logger.setLevel(logging.INFO)

    if not logger.handlers:
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )

        console_handler = logging.StreamHandler()
        console_handler.setLevel(logging.INFO)
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)

        if log_path:
            file_handler = logging.FileHandler(log_path)
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(formatter)
            logger.addHandler(file_handler)

    return logger

class BrowserError(Exception):
    pass

class ResourceError(Exception):
    pass

class ConfigError(Exception):
    pass

class SecurityError(Exception):
    pass

@dataclass
class AgentConfig:
    agent_type: str
    llm_provider: str
    llm_model_name: str
    llm_temperature: float = DEFAULT_TEMPERATURE
    llm_base_url: Optional[str] = None
    llm_api_key: Optional[str] = None
    use_own_browser: bool = False
    headless: bool = False
    disable_security: bool = False
    window_w: int = DEFAULT_WINDOW_WIDTH
    window_h: int = DEFAULT_WINDOW_HEIGHT
    save_recording_path: Optional[str] = None
    task: str = ""
    add_infos: str = ""
    max_steps: int = DEFAULT_MAX_STEPS
    use_vision: bool = True

    def __post_init__(self) -> None:
        self.validate()

    def validate(self) -> None:
        if self.agent_type not in ["org", "custom"]:
            raise ConfigError(f"Invalid agent type: {self.agent_type}")

        if not self.llm_provider or not self.llm_model_name:
            raise ConfigError("LLM provider and model name are required")

        if self.llm_temperature < 0.0 or self.llm_temperature > 2.0:
            raise ConfigError(f"Invalid temperature: {self.llm_temperature}")

        if self.window_w <= 0 or self.window_h <= 0:
            raise ConfigError(f"Invalid window dimensions: {self.window_w}x{self.window_h}")

        if self.max_steps <= 0:
            raise ConfigError(f"Invalid max steps: {self.max_steps}")

if __name__ == "__main__":
    # Create necessary directories
    LOG_DIR.mkdir(parents=True, exist_ok=True)
    TEMP_DIR.mkdir(parents=True, exist_ok=True)

    # Initialize logging
    logger = setup_logging(LOG_DIR / 'browser_agent.log')

    # Initialize database
    init_database()

    # Load environment variables
    load_dotenv()

    # Create and launch the Gradio interface
    gr_interface = GradioInterface()
    demo = gr_interface.create_ui()
    demo.launch()

Instruction: debug

import sys
import os
import inspect
import traceback
import logging
import pytest
from unittest.mock import patch, MagicMock, AsyncMock
from typing import Dict, Any, Tuple, Union
import importlib
import importlib.util
from pathlib import Path
import re

# Test configuration
LOG_LEVEL = logging.INFO  # Set to logging.DEBUG for more verbose output

# Configure logging
logging.basicConfig(
    level=LOG_LEVEL,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("test_browser_agent")

class MockAsyncPlaywright:
    """Mock class to simulate playwright's async_playwright context manager"""
    def __init__(self):
        self.chromium = MagicMock()
        self.chromium.launch = AsyncMock()

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        pass

class MockBrowser:
    """Mock class for browser object"""
    def __init__(self):
        self.new_context = AsyncMock()
        self.close = AsyncMock()

class MockBrowserContext:
    """Mock class for browser context object"""
    def __init__(self):
        self.new_page = AsyncMock()
        self.close = AsyncMock()

class MockPage:
    """Mock class for page object"""
    def __init__(self):
        self.goto = AsyncMock()
        self.title = AsyncMock(return_value="Example Domain")
        self.content = AsyncMock(return_value="Example page content")
        self.screenshot = AsyncMock()
        self.url = "https://example.com"

class MockResponse:
    """Mock class for response object"""
    def __init__(self, ok=True, status=200):
        self.ok = ok
        self.status = status

class MockResourceManager:
    """Mock class for ResourceManager"""
    def __init__(self):
        self.temp_files = []
        self.active_contexts = []
        self.cleanup = AsyncMock()

def get_agent_config(module):
    """Helper function to get AgentConfig from a module or create mock if missing"""
    try:
        return getattr(module, "AgentConfig")
    except AttributeError:
        # Create a mock AgentConfig class if one doesn't exist in the implementation
        class MockAgentConfig:
            def __init__(self, agent_type, llm_provider, llm_model_name,
                        llm_temperature=1.0, llm_base_url=None, llm_api_key=None,
                        use_own_browser=False, headless=False, disable_security=False,
                        window_w=1280, window_h=720, save_recording_path=None,
                        task="", add_infos="", max_steps=10, use_vision=True):
                self.agent_type = agent_type
                self.llm_provider = llm_provider
                self.llm_model_name = llm_model_name
                self.llm_temperature = llm_temperature
                self.llm_base_url = llm_base_url
                self.llm_api_key = llm_api_key
                self.use_own_browser = use_own_browser
                self.headless = headless
                self.disable_security = disable_security
                self.window_w = window_w
                self.window_h = window_h
                self.save_recording_path = save_recording_path
                self.task = task
                self.add_infos = add_infos
                self.max_steps = max_steps
                self.use_vision = use_vision
        return MockAgentConfig

def has_class_attribute(module, class_name, attr_name):
    """Check if a class in a module has a specific attribute"""
    try:
        class_obj = getattr(module, class_name)
        return hasattr(class_obj, attr_name)
    except (AttributeError, TypeError):
        return False

def has_attribute(module, attr_name):
    """Check if a module has a specific attribute"""
    return hasattr(module, attr_name)

def safe_patch(target, replacement, create=False):
    """Create a patch context manager that doesn't fail if the target doesn't exist"""
    return patch(target, replacement, create=create)

def test_debug_implementation_present(implementation):
    """Test that the implementation has debug logging code added"""
    impl_name, module = implementation

    # Check if there are any debug logging related patterns in the code
    module_source = inspect.getsource(module)

    debug_patterns = [
        "logger.debug",
        "logging.DEBUG",
        ".setLevel(logging.DEBUG)",
        "DEBUG",
        "debug logging",
        "debug information",
        "screenshot",
        "traceback.format_exc()"
    ]

    has_debug_logging = False
    for pattern in debug_patterns:
        if pattern in module_source:
            has_debug_logging = True
            break

    assert has_debug_logging, f"Implementation {impl_name} does not include debug logging statements"

@pytest.mark.asyncio
async def test_browser_agent_run_with_debug_logging(implementation):
    """Test that the BrowserAgentRunner.run method includes debug logging"""
    impl_name, module = implementation

    # Get the BrowserAgentRunner class from the module
    BrowserAgentRunner = getattr(module, "BrowserAgentRunner")
    AgentConfig = get_agent_config(module)

    # Create a mock for async_playwright
    mock_playwright = MockAsyncPlaywright()
    mock_browser = MockBrowser()
    mock_context = MockBrowserContext()
    mock_page = MockPage()
    mock_response = MockResponse()

    # Configure mocks
    mock_playwright.chromium.launch.return_value = mock_browser
    mock_browser.new_context.return_value = mock_context
    mock_context.new_page.return_value = mock_page
    mock_page.goto.return_value = mock_response

    # Create test config
    config = AgentConfig(
        agent_type="custom",
        llm_provider="gemini",
        llm_model_name="gemini-2.0-flash-exp",
        headless=True,
        window_w=1280,
        window_h=720,
        task="test task"
    )

    # Check if the module has a ResourceManager class
    has_resource_manager = has_attribute(module, "ResourceManager")

    # Create a test logger
    test_logger = MagicMock()

    # Prepare context managers for patching
    patches = []

    if has_resource_manager:
        # Only patch ResourceManager if it exists in the module
        patches.append(patch(f"{module.__name__}.ResourceManager", return_value=MockResourceManager()))

    # Try to patch the logger if it exists, otherwise create it temporarily
    if has_attribute(module, "logger"):
        patches.append(patch(f"{module.__name__}.logger", test_logger))
    else:
        # If logger doesn't exist, we'll inject it and clean up after
        setattr(module, "logger", test_logger)

    # Patch playwright
    patches.append(patch("playwright.async_api.async_playwright", return_value=mock_playwright))

    # Apply all patches
    for p in patches:
        p.start()

    try:
        # Create the browser agent runner
        runner = BrowserAgentRunner(config)

        # Run the browser agent
        await runner.run()

        # At this point, check the run method source code for debug logging patterns
        run_method_source = ""
        for name, obj in inspect.getmembers(BrowserAgentRunner):
            if name == "run" and inspect.isfunction(obj):
                run_method_source = inspect.getsource(obj)
                break

        debug_patterns = [
            "logger.debug",
            "debug",
            "DEBUG",
            "log.debug",
            "screenshot",
            "page.content()"
        ]

        has_debug_in_run = False
        for pattern in debug_patterns:
            if pattern in run_method_source:
                has_debug_in_run = True
                break

        assert has_debug_in_run, f"Implementation {impl_name} does not include debug logging in run method"

    finally:
        # Stop all patches
        for p in patches:
            p.stop()

        # Clean up the injected logger if we added it
        if not has_attribute(module, "logger"):
            delattr(module, "logger")

import pytest

@pytest.mark.asyncio
async def test_indentation_in_run_method(implementation):
    """Test that the run method has proper indentation structure."""
    impl_name, module = implementation

    source_code = inspect.getsource(module)

    # Check for proper indentation of try-except-finally blocks
    try_except_pattern = r'try:.*?except\s+Exception\s+as\s+e:.*?finally:'
    indentation_correct = re.search(try_except_pattern, source_code, re.DOTALL)

    assert indentation_correct, f"{impl_name}: The run method has indentation issues with try-except-finally blocks"

    # Check that except is aligned with try and not inside it
    lines = source_code.split('\n')
    try_line_idx = next((i for i, line in enumerate(lines) if 'try:' in line), -1)
    except_line_idx = next((i for i, line in enumerate(lines) if 'except Exception' in line), -1)

    if try_line_idx >= 0 and except_line_idx >= 0:
        try_indent = len(lines[try_line_idx]) - len(lines[try_line_idx].lstrip())
        except_indent = len(lines[except_line_idx]) - len(lines[except_line_idx].lstrip())
        assert try_indent == except_indent, f"{impl_name}: 'except' block is not aligned with 'try' block"

@pytest.mark.asyncio
async def test_run_method_error_handling(implementation):
    """Test that the run method properly handles and logs errors."""
    impl_name, module = implementation

    source_code = inspect.getsource(module)

    # Check for exception logging with traceback or detailed information
    has_detailed_error_logging = (
        'traceback.format_exc()' in source_code or
        'logger.exception' in source_code or
        'f"Detailed error' in source_code
    )

    assert has_detailed_error_logging, f"{impl_name}: The run method should include detailed error logging"

@pytest.mark.asyncio
async def test_cleanup_handling_in_run(implementation):
    """Test that cleanup is properly called in all execution paths."""
    impl_name, module = implementation

    browser_agent_class = getattr(module, 'BrowserAgent', None)
    if not browser_agent_class:
        pytest.skip(f"Module {impl_name} does not have a BrowserAgent class")

    # Setup mocks
    mock_browser = MockBrowser()
    mock_context = MockBrowserContext()
    mock_page = MockPage()

    agent = browser_agent_class(MagicMock())

    # Mock the necessary attributes and methods
    agent.browser = mock_browser
    agent.browser_context = mock_context
    agent.cleanup = AsyncMock()

    # Force an exception in the run method
    with patch('playwright.async_api.async_playwright', side_effect=Exception("Test error")):
        try:
            await agent.run()
        except Exception:
            pass  # We expect this to fail

        # Check that cleanup was called even when an exception occurs
        agent.cleanup.assert_called_once()

pytest
pytest-mock
pytest-asyncio
playwright
gradio
python-dotenv
anyio

import pytest
import os
import sys
import json
from typing import Dict, List, Optional, Any

# Import from local test_utils.py in the same directory
from test_utils import TestUtils, TestResultsManager

# Load all implementations in the current sandbox
implementations = TestUtils.load_all_implementations()
test_results = TestResultsManager()

@pytest.fixture(scope="session")
def sandbox_dir():
    """Fixture to provide the sandbox directory path."""
    return os.path.dirname(os.path.abspath(__file__))

@pytest.fixture(scope="session")
def sandbox_name():
    """Fixture to provide the sandbox name."""
    return os.path.basename(os.path.dirname(os.path.abspath(__file__)))

@pytest.fixture(scope="session")
def all_implementations():
    """Fixture to provide all implementations as a dictionary."""
    return implementations

@pytest.fixture(params=list(implementations.items()))
def implementation(request):
    """Fixture to provide each implementation to tests one at a time."""
    return request.param

@pytest.fixture(scope="session")
def results_manager():
    """Fixture to provide access to the test results manager."""
    return test_results

# Hook for collecting test results
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """Pytest hook to collect test results."""
    # Execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # We're only interested in the call outcome
    if rep.when == "call":
        if hasattr(item, "callspec") and "implementation" in item.callspec.params:
            # Get implementation name and module
            impl_name, _ = item.callspec.params["implementation"]

            # Get test name
            test_name = item.nodeid.split("::")[-1]

            # Record result
            if rep.passed:
                test_results.record_result(impl_name, test_name, True)
            elif rep.failed:
                error_msg = str(rep.longrepr) if rep.longrepr else "Test failed"
                test_results.record_result(impl_name, test_name, False, error_msg)
            elif rep.skipped:
                skip_reason = rep.longrepr[2] if rep.longrepr else "Test skipped"
                test_results.record_skip(impl_name, test_name, skip_reason)

# Hook to save results at the end of testing
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    """Save test results at the end of the test session."""
    test_results.save_results()

import os
import sys
import glob
import re
import importlib.util
import traceback
import types
from typing import Dict, List, Optional, Any, Tuple

class TestUtils:
    @staticmethod
    def discover_implementation_files(directory: str = None) -> List[str]:
        """Find all implementation files in the current sandbox directory."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        patterns = [
            r'modified_code\d+\.py',
            r'new_code\d+\.py',
            # r'original_code\.py',
            r'implementation\d*\.py'
        ]

        pattern = re.compile('|'.join(f'({p})' for p in patterns))
        implementations = []

        for file_path in glob.glob(os.path.join(directory, '*.py')):
            if pattern.search(os.path.basename(file_path)):
                implementations.append(file_path)

        # Sort files numerically
        def sort_key(path):
            filename = os.path.basename(path)
            match = re.search(r'(\d+)', filename)
            return int(match.group(1)) if match else 0

        return sorted(implementations, key=sort_key)

    @staticmethod
    def create_mock_module(file_path: str, module_name: str, error_info: str) -> types.ModuleType:
        """Create a mock module that contains error information but can still be tested."""
        # Create a new module object
        mock_module = types.ModuleType(module_name)

        # Add basic attributes
        mock_module.__file__ = file_path
        mock_module.__name__ = module_name
        mock_module.__display_name__ = module_name
        mock_module.__error__ = error_info

        # Add a dummy function that can be detected by test functions
        def dummy_function(*args, **kwargs):
            return f"Error in module: {error_info}"

        setattr(mock_module, "implementation_error", dummy_function)

        return mock_module

    @staticmethod
    def load_module(file_path: str, module_name: Optional[str] = None) -> Any:
        """
        Safely load a module from a file path with proper error handling.
        If the module has errors, return a mock module that can still be tested.
        """
        if module_name is None:
            module_name = os.path.basename(file_path).replace('.py', '')

        # Create a unique module name to avoid conflicts
        sandbox_id = os.path.basename(os.path.dirname(file_path))
        unique_module_name = f"{sandbox_id}_{module_name}"

        try:
            # First, try to read the file to check for syntax errors
            with open(file_path, 'r') as f:
                source_code = f.read()

            # Check for syntax errors by compiling the code
            try:
                compiled = compile(source_code, file_path, 'exec')
            except SyntaxError as e:
                error_msg = f"Syntax error: {str(e)}"
                print(f"Syntax error in {file_path}: {e}")
                print(f"  Line {e.lineno}, column {e.offset}: {e.text}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module spec
            spec = importlib.util.spec_from_file_location(unique_module_name, file_path)
            if spec is None or spec.loader is None:
                error_msg = f"Could not create spec for {file_path}"
                print(f"Error: {error_msg}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module object
            module = importlib.util.module_from_spec(spec)
            sys.modules[unique_module_name] = module

            # Special handling for execution errors
            try:
                # Execute the module code in a safe way
                spec.loader.exec_module(module)
                # Store the original name for reference
                module.__display_name__ = module_name
                return module
            except Exception as e:
                error_msg = f"Runtime error: {str(e)}"
                traceback_str = traceback.format_exc()
                print(f"Error executing module {file_path}: {e}")
                print(traceback_str)

                # Create a partial module that contains what we loaded before the error
                mock_module = TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

                # Copy any attributes that might have been defined before the error
                for attr_name in dir(module):
                    if not attr_name.startswith('__'):
                        try:
                            setattr(mock_module, attr_name, getattr(module, attr_name))
                        except Exception:
                            pass  # Skip attributes that can't be copied

                return mock_module

        except FileNotFoundError as e:
            error_msg = f"File not found: {str(e)}"
            print(f"Error: {error_msg}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)
        except Exception as e:
            error_msg = f"Unexpected error: {str(e)}"
            print(f"Error loading module {file_path}: {e}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

    @classmethod
    def load_all_implementations(cls, directory: str = None) -> Dict[str, Any]:
        """Load all implementation files in the directory, including those with errors."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        implementations = {}

        implementation_files = cls.discover_implementation_files(directory)
        if not implementation_files:
            print("WARNING: No implementation files found. Check your file naming patterns.")

        for file_path in implementation_files:
            module_name = os.path.basename(file_path).replace('.py', '')
            module = cls.load_module(file_path, module_name)

            # Always add the module, even if it has errors
            implementations[module_name] = module

            if hasattr(module, '__error__'):
                print(f"Loaded with errors: {module_name} - {module.__error__}")
            else:
                print(f"Successfully loaded: {module_name}")

        return implementations

class TestResultsManager:
    def __init__(self):
        self.results = {}
        self.sandbox_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))

    def record_result(self, impl_name: str, test_name: str, passed: bool,
                     error_msg: Optional[str] = None) -> None:
        """Record a test result for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        if passed:
            self.results[impl_name]["passed"] += 1
        else:
            self.results[impl_name]["failed"] += 1
            if error_msg:
                self.results[impl_name]["errors"].append({
                    "test": test_name,
                    "error": error_msg
                })

    def record_skip(self, impl_name: str, test_name: str,
                   reason: Optional[str] = None) -> None:
        """Record a skipped test for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        self.results[impl_name]["skipped"] += 1
        if reason:
            self.results[impl_name]["errors"].append({
                "test": test_name,
                "error": f"SKIPPED: {reason}"
            })

    def get_winner(self) -> Tuple[Optional[int], Dict]:
        """Determine the winner based on test results."""
        winner = None
        max_passed = -1

        for impl_name, results in self.results.items():
            if impl_name == "original_code":
                continue  # Skip original code when determining winner

            if results["passed"] > max_passed:
                max_passed = results["passed"]
                winner = impl_name
            # Break ties by looking at failure count
            elif results["passed"] == max_passed and winner is not None:
                if results["failed"] < self.results[winner]["failed"]:
                    winner = impl_name

        # Convert winner to numeric index if possible
        winner_index = -1
        if winner and re.match(r'modified_code\d+', winner):
            try:
                winner_index = int(re.search(r'(\d+)', winner).group(1))
            except (AttributeError, ValueError):
                pass

        return winner_index, self.results

    def save_results(self, filename: str = "test_results.json") -> None:
        """Save test results to a JSON file."""
        import json

        winner_index, results = self.get_winner()

        # Check if all tests were skipped
        all_skipped = all(
            stats["skipped"] == stats["passed"] + stats["failed"] + stats["skipped"]
            for impl_name, stats in results.items()
            if impl_name != "original_code"
        )

        output = {
            "winner": winner_index,
            "all_skipped": all_skipped,
            "results": {
                name: {
                    "passed": stats["passed"],
                    "failed": stats["failed"],
                    "skipped": stats["skipped"],
                    "total": stats["passed"] + stats["failed"] + stats["skipped"]
                }
                for name, stats in results.items()
                if not name.startswith("_")  # Skip internal items
            }
        }

        with open(filename, "w") as f:
            json.dump(output, f, indent=2)

        print(f"Test results saved to {filename}")

        return output


# 记录当前路径为cwd
# 创建向量数据库路径
cwd = os.getcwd()
db_path = cwd + '/milvus_db.db'
TABLE_NAME = 'test_table'
DIM_VALUE = 128
client = MilvusClient(url=cwd)

Instruction: 记录当前路径为cwd，然后根据我写的注释使用pymilvus创建数据库链接

import os
import sys
import pytest
import inspect
import ast
import json
from unittest.mock import patch, MagicMock

# Constants for test
DEFAULT_TABLE_NAME = 'test_table'
DEFAULT_DIM_VALUE = 128

class CodeAnalyzer(ast.NodeVisitor):
    """AST-based code analyzer to detect patterns in Python code"""

    def __init__(self):
        self.uses_milvus_client = False
        self.uses_connections = False
        self.cwd_recorded = False
        self.has_table_name = False
        self.has_dim_value = False
        self.imports_pymilvus = False
        self.connection_params = {}
        self.calls_getcwd = False
        self.has_host_param = False
        self.has_port_param = False
        self.has_uri_param = False

    def visit_Import(self, node):
        """Check for pymilvus import"""
        for name in node.names:
            if name.name == 'pymilvus':
                self.imports_pymilvus = True
        self.generic_visit(node)

    def visit_ImportFrom(self, node):
        """Check for from pymilvus import ..."""
        if node.module == 'pymilvus':
            self.imports_pymilvus = True
            for name in node.names:
                if name.name == 'MilvusClient':
                    self.uses_milvus_client = True
                elif name.name == 'connections':
                    self.uses_connections = True
        self.generic_visit(node)

    def visit_Assign(self, node):
        """Check for variable assignments"""
        for target in node.targets:
            if isinstance(target, ast.Name):
                # Check for cwd assignment
                if target.id == 'cwd' and isinstance(node.value, ast.Call):
                    if hasattr(node.value, 'func') and isinstance(node.value.func, ast.Attribute):
                        if node.value.func.attr == 'getcwd':
                            self.cwd_recorded = True
                            self.calls_getcwd = True

                # Check for table name and dimension
                if target.id == 'TABLE_NAME':
                    self.has_table_name = True
                elif target.id == 'DIM_VALUE':
                    self.has_dim_value = True

                # Check for connection parameters
                if target.id == 'MILVUS_HOST':
                    if isinstance(node.value, ast.Constant):
                        self.connection_params['host'] = node.value.value
                        self.has_host_param = True
                elif target.id == 'MILVUS_PORT':
                    if isinstance(node.value, ast.Constant):
                        self.connection_params['port'] = node.value.value
                        self.has_port_param = True

        self.generic_visit(node)

    def visit_Call(self, node):
        """Check for function calls"""
        # Check for os.getcwd() call
        if isinstance(node.func, ast.Attribute):
            if hasattr(node.func.value, 'id') and node.func.value.id == 'os' and node.func.attr == 'getcwd':
                self.calls_getcwd = True

            # Check for connections.connect() call with parameters
            if hasattr(node.func.value, 'id') and node.func.value.id == 'connections' and node.func.attr == 'connect':
                self.uses_connections = True

                # Check for connection parameters in the call
                for keyword in node.keywords:
                    if keyword.arg == 'host':
                        self.has_host_param = True
                    elif keyword.arg == 'port':
                        self.has_port_param = True

        # Check for MilvusClient instantiation with parameters
        if isinstance(node.func, ast.Name) and node.func.id == 'MilvusClient':
            self.uses_milvus_client = True

            # Check for client parameters in the call
            for keyword in node.keywords:
                if keyword.arg == 'uri':
                    self.has_uri_param = True
                elif keyword.arg == 'host':
                    self.has_host_param = True
                elif keyword.arg == 'port':
                    self.has_port_param = True

        self.generic_visit(node)

def extract_implementation_details(module):
    """Extract implementation details using AST for more accurate analysis"""
    try:
        # Get the source code
        source = inspect.getsource(module)

        # Parse the source code
        tree = ast.parse(source)

        # Analyze the code
        analyzer = CodeAnalyzer()
        analyzer.visit(tree)

        # Runtime check for variables that might not be detected by AST
        if hasattr(module, 'cwd') and isinstance(module.cwd, str):
            analyzer.cwd_recorded = True

        if hasattr(module, 'TABLE_NAME'):
            analyzer.has_table_name = True

        if hasattr(module, 'DIM_VALUE'):
            analyzer.has_dim_value = True

        # Manual check for connection parameters in the source code
        if not (analyzer.has_host_param or analyzer.has_port_param or analyzer.has_uri_param):
            if 'host=' in source:
                analyzer.has_host_param = True
            if 'port=' in source:
                analyzer.has_port_param = True
            if 'uri=' in source:
                analyzer.has_uri_param = True

        # Return a dictionary with all the details
        return {
            'uses_milvus_client': analyzer.uses_milvus_client,
            'uses_connections': analyzer.uses_connections,
            'cwd_recorded': analyzer.cwd_recorded,
            'has_table_name': analyzer.has_table_name,
            'has_dim_value': analyzer.has_dim_value,
            'imports_pymilvus': analyzer.imports_pymilvus,
            'connection_params': analyzer.connection_params,
            'calls_getcwd': analyzer.calls_getcwd,
            'has_host_param': analyzer.has_host_param,
            'has_port_param': analyzer.has_port_param,
            'has_uri_param': analyzer.has_uri_param
        }
    except Exception as e:
        print(f"AST parsing error: {e}")
        # Fallback to more basic checks if AST parsing fails
        source = inspect.getsource(module)
        return {
            'uses_milvus_client': hasattr(module, 'client') or 'MilvusClient' in source,
            'uses_connections': 'connections.connect' in source,
            'cwd_recorded': hasattr(module, 'cwd'),
            'has_table_name': hasattr(module, 'TABLE_NAME') or 'TABLE_NAME' in source,
            'has_dim_value': hasattr(module, 'DIM_VALUE') or 'DIM_VALUE' in source,
            'imports_pymilvus': 'pymilvus' in source,
            'connection_params': {},
            'calls_getcwd': 'getcwd()' in source or 'os.getcwd()' in source,
            'has_host_param': 'host=' in source,
            'has_port_param': 'port=' in source,
            'has_uri_param': 'uri=' in source
        }

def test_implementation_records_cwd(implementation):
    """Test that the implementation records the current working directory."""
    impl_name, module = implementation

    # Get source code for more precise analysis
    source = inspect.getsource(module)

    # Check for getcwd calls in the source code
    cwd_recorded = "os.getcwd()" in source or "getcwd()" in source

    # Check for cwd variable assignment
    cwd_variable = hasattr(module, 'cwd')

    # Use our analyzer as backup
    if not (cwd_recorded or cwd_variable):
        details = extract_implementation_details(module)
        cwd_recorded = details['cwd_recorded'] or details['calls_getcwd']

    assert cwd_recorded or cwd_variable, f"{impl_name} does not record current working directory (cwd) as required"

def test_implementation_includes_table_and_dim(implementation):
    """Test that the implementation includes TABLE_NAME and DIM_VALUE."""
    impl_name, module = implementation

    # Get source code for more precise analysis
    source = inspect.getsource(module)

    # Check for TABLE_NAME in source code
    has_table_name = "TABLE_NAME" in source or hasattr(module, 'TABLE_NAME')

    # Check for DIM_VALUE in source code
    has_dim_value = "DIM_VALUE" in source or hasattr(module, 'DIM_VALUE')

    # Use the analyzer as backup
    if not (has_table_name and has_dim_value):
        details = extract_implementation_details(module)
        has_table_name = has_table_name or details['has_table_name']
        has_dim_value = has_dim_value or details['has_dim_value']

    assert has_table_name, f"{impl_name} does not define TABLE_NAME"
    assert has_dim_value, f"{impl_name} does not define DIM_VALUE"

def test_implementation_imports_pymilvus(implementation):
    """Test that the implementation imports pymilvus correctly."""
    impl_name, module = implementation

    # Check if pymilvus is imported by looking at the source code
    source = inspect.getsource(module)
    imports_pymilvus = "pymilvus" in source

    assert imports_pymilvus, f"{impl_name} does not import pymilvus as required"

def test_implementation_creates_milvus_connection(implementation):
    """Test that the implementation creates a Milvus connection using one of the supported methods."""
    impl_name, module = implementation

    # Get source code for direct analysis
    source = inspect.getsource(module)

    # Check for MilvusClient usage
    uses_milvus_client = "MilvusClient" in source

    # Check for connections.connect usage
    uses_connections = "connections.connect" in source

    # Validate that at least one connection method is used
    assert uses_milvus_client or uses_connections, \
        f"{impl_name} does not create a Milvus connection with either MilvusClient or connections.connect"

@pytest.fixture
def mock_pymilvus():
    """Fixture to create a mock pymilvus module with MilvusClient and connections"""
    # Create mock MilvusClient
    mock_client = MagicMock()
    mock_client_class = MagicMock(return_value=mock_client)

    # Create mock connections with connect method
    mock_connect = MagicMock()
    mock_connections = MagicMock()
    mock_connections.connect = mock_connect

    # Create mock pymilvus module
    mock_pymilvus_module = MagicMock()
    mock_pymilvus_module.MilvusClient = mock_client_class
    mock_pymilvus_module.connections = mock_connections

    # Save original module if it exists
    original_pymilvus = sys.modules.get('pymilvus', None)

    # Replace with our mock
    sys.modules['pymilvus'] = mock_pymilvus_module

    # Return mocks for testing
    yield {
        'module': mock_pymilvus_module,
        'client_class': mock_client_class,
        'client': mock_client,
        'connections': mock_connections,
        'connect': mock_connect
    }

    # Restore original module or remove our mock
    if original_pymilvus:
        sys.modules['pymilvus'] = original_pymilvus
    else:
        del sys.modules['pymilvus']

@pytest.fixture
def mock_os():
    """Fixture to mock os module's getcwd function"""
    with patch('os.getcwd', return_value='/mocked/path') as mock:
        yield mock

def test_milvus_client_usage(implementation, mock_pymilvus, mock_os):
    """Test proper usage of MilvusClient if it's used in the implementation."""
    impl_name, module = implementation

    # Get implementation details to determine if it uses MilvusClient
    details = extract_implementation_details(module)

    if not details['uses_milvus_client']:
        pytest.skip(f"{impl_name} doesn't use MilvusClient")

    # Reset the mock
    mock_pymilvus['client_class'].reset_mock()

    # Create an execution environment with predefined globals
    exec_globals = {
        'os': MagicMock(getcwd=mock_os),
        'pymilvus': mock_pymilvus['module'],
        'sys': sys
    }

    # Execute the code to see if it instantiates MilvusClient
    try:
        # Get source and execute
        source = inspect.getsource(module)
        exec(source, exec_globals)

        # Check if MilvusClient was instantiated
        assert mock_pymilvus['client_class'].called, \
            f"{impl_name} imports MilvusClient but doesn't instantiate it"

    except Exception as e:
        pytest.fail(f"Error executing implementation {impl_name}: {e}")

def test_connections_usage(implementation, mock_pymilvus, mock_os):
    """Test proper usage of connections.connect if it's used in the implementation."""
    impl_name, module = implementation

    # Get implementation details to determine if it uses connections
    details = extract_implementation_details(module)

    if not details['uses_connections']:
        pytest.skip(f"{impl_name} doesn't use connections.connect")

    # Reset the mock
    mock_pymilvus['connect'].reset_mock()

    # Create an execution environment with predefined globals
    exec_globals = {
        'os': MagicMock(getcwd=mock_os),
        'pymilvus': mock_pymilvus['module'],
        'sys': sys
    }

    # Execute the code to see if it calls connections.connect
    try:
        # Get source and execute
        source = inspect.getsource(module)
        exec(source, exec_globals)

        # Check if connections.connect was called
        assert mock_pymilvus['connect'].called, \
            f"{impl_name} imports connections but doesn't call connect()"

    except Exception as e:
        pytest.fail(f"Error executing implementation {impl_name}: {e}")

def test_implementation_follows_instruction(implementation):
    """Test that the implementation follows all required instructions."""
    impl_name, module = implementation

    # Get detailed analysis of the implementation
    details = extract_implementation_details(module)

    # Check all requirements
    assert details['cwd_recorded'] or details['calls_getcwd'] or hasattr(module, 'cwd'), \
        f"{impl_name} does not record current working directory (cwd)"

    assert details['imports_pymilvus'], \
        f"{impl_name} does not import pymilvus"

    assert details['uses_milvus_client'] or details['uses_connections'], \
        f"{impl_name} does not create a database connection using pymilvus"

    assert details['has_table_name'], \
        f"{impl_name} does not define TABLE_NAME"

    assert details['has_dim_value'], \
        f"{impl_name} does not define DIM_VALUE"

import pytest
import os
import sys
import json
from typing import Dict, List, Optional, Any

# Import from local test_utils.py in the same directory
from test_utils import TestUtils, TestResultsManager

# Load all implementations in the current sandbox
implementations = TestUtils.load_all_implementations()
test_results = TestResultsManager()

@pytest.fixture(scope="session")
def sandbox_dir():
    """Fixture to provide the sandbox directory path."""
    return os.path.dirname(os.path.abspath(__file__))

@pytest.fixture(scope="session")
def sandbox_name():
    """Fixture to provide the sandbox name."""
    return os.path.basename(os.path.dirname(os.path.abspath(__file__)))

@pytest.fixture(scope="session")
def all_implementations():
    """Fixture to provide all implementations as a dictionary."""
    return implementations

@pytest.fixture(params=list(implementations.items()))
def implementation(request):
    """Fixture to provide each implementation to tests one at a time."""
    return request.param

@pytest.fixture(scope="session")
def results_manager():
    """Fixture to provide access to the test results manager."""
    return test_results

# Hook for collecting test results
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """Pytest hook to collect test results."""
    # Execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # We're only interested in the call outcome
    if rep.when == "call":
        if hasattr(item, "callspec") and "implementation" in item.callspec.params:
            # Get implementation name and module
            impl_name, _ = item.callspec.params["implementation"]

            # Get test name
            test_name = item.nodeid.split("::")[-1]

            # Record result
            if rep.passed:
                test_results.record_result(impl_name, test_name, True)
            elif rep.failed:
                error_msg = str(rep.longrepr) if rep.longrepr else "Test failed"
                test_results.record_result(impl_name, test_name, False, error_msg)
            elif rep.skipped:
                skip_reason = rep.longrepr[2] if rep.longrepr else "Test skipped"
                test_results.record_skip(impl_name, test_name, skip_reason)

# Hook to save results at the end of testing
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    """Save test results at the end of the test session."""
    test_results.save_results()

import os
import sys
import glob
import re
import importlib.util
import traceback
import types
from typing import Dict, List, Optional, Any, Tuple

class TestUtils:
    @staticmethod
    def discover_implementation_files(directory: str = None) -> List[str]:
        """Find all implementation files in the current sandbox directory."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        patterns = [
            r'modified_code\d+\.py',
            r'new_code\d+\.py',
            # r'original_code\.py',
            r'implementation\d*\.py'
        ]

        pattern = re.compile('|'.join(f'({p})' for p in patterns))
        implementations = []

        for file_path in glob.glob(os.path.join(directory, '*.py')):
            if pattern.search(os.path.basename(file_path)):
                implementations.append(file_path)

        # Sort files numerically
        def sort_key(path):
            filename = os.path.basename(path)
            match = re.search(r'(\d+)', filename)
            return int(match.group(1)) if match else 0

        return sorted(implementations, key=sort_key)

    @staticmethod
    def create_mock_module(file_path: str, module_name: str, error_info: str) -> types.ModuleType:
        """Create a mock module that contains error information but can still be tested."""
        # Create a new module object
        mock_module = types.ModuleType(module_name)

        # Add basic attributes
        mock_module.__file__ = file_path
        mock_module.__name__ = module_name
        mock_module.__display_name__ = module_name
        mock_module.__error__ = error_info

        # Add a dummy function that can be detected by test functions
        def dummy_function(*args, **kwargs):
            return f"Error in module: {error_info}"

        setattr(mock_module, "implementation_error", dummy_function)

        return mock_module

    @staticmethod
    def load_module(file_path: str, module_name: Optional[str] = None) -> Any:
        """
        Safely load a module from a file path with proper error handling.
        If the module has errors, return a mock module that can still be tested.
        """
        if module_name is None:
            module_name = os.path.basename(file_path).replace('.py', '')

        # Create a unique module name to avoid conflicts
        sandbox_id = os.path.basename(os.path.dirname(file_path))
        unique_module_name = f"{sandbox_id}_{module_name}"

        try:
            # First, try to read the file to check for syntax errors
            with open(file_path, 'r') as f:
                source_code = f.read()

            # Check for syntax errors by compiling the code
            try:
                compiled = compile(source_code, file_path, 'exec')
            except SyntaxError as e:
                error_msg = f"Syntax error: {str(e)}"
                print(f"Syntax error in {file_path}: {e}")
                print(f"  Line {e.lineno}, column {e.offset}: {e.text}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module spec
            spec = importlib.util.spec_from_file_location(unique_module_name, file_path)
            if spec is None or spec.loader is None:
                error_msg = f"Could not create spec for {file_path}"
                print(f"Error: {error_msg}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module object
            module = importlib.util.module_from_spec(spec)
            sys.modules[unique_module_name] = module

            # Special handling for execution errors
            try:
                # Execute the module code in a safe way
                spec.loader.exec_module(module)
                # Store the original name for reference
                module.__display_name__ = module_name
                return module
            except Exception as e:
                error_msg = f"Runtime error: {str(e)}"
                traceback_str = traceback.format_exc()
                print(f"Error executing module {file_path}: {e}")
                print(traceback_str)

                # Create a partial module that contains what we loaded before the error
                mock_module = TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

                # Copy any attributes that might have been defined before the error
                for attr_name in dir(module):
                    if not attr_name.startswith('__'):
                        try:
                            setattr(mock_module, attr_name, getattr(module, attr_name))
                        except Exception:
                            pass  # Skip attributes that can't be copied

                return mock_module

        except FileNotFoundError as e:
            error_msg = f"File not found: {str(e)}"
            print(f"Error: {error_msg}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)
        except Exception as e:
            error_msg = f"Unexpected error: {str(e)}"
            print(f"Error loading module {file_path}: {e}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

    @classmethod
    def load_all_implementations(cls, directory: str = None) -> Dict[str, Any]:
        """Load all implementation files in the directory, including those with errors."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        implementations = {}

        implementation_files = cls.discover_implementation_files(directory)
        if not implementation_files:
            print("WARNING: No implementation files found. Check your file naming patterns.")

        for file_path in implementation_files:
            module_name = os.path.basename(file_path).replace('.py', '')
            module = cls.load_module(file_path, module_name)

            # Always add the module, even if it has errors
            implementations[module_name] = module

            if hasattr(module, '__error__'):
                print(f"Loaded with errors: {module_name} - {module.__error__}")
            else:
                print(f"Successfully loaded: {module_name}")

        return implementations

class TestResultsManager:
    def __init__(self):
        self.results = {}
        self.sandbox_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))

    def record_result(self, impl_name: str, test_name: str, passed: bool,
                     error_msg: Optional[str] = None) -> None:
        """Record a test result for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        if passed:
            self.results[impl_name]["passed"] += 1
        else:
            self.results[impl_name]["failed"] += 1
            if error_msg:
                self.results[impl_name]["errors"].append({
                    "test": test_name,
                    "error": error_msg
                })

    def record_skip(self, impl_name: str, test_name: str,
                   reason: Optional[str] = None) -> None:
        """Record a skipped test for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        self.results[impl_name]["skipped"] += 1
        if reason:
            self.results[impl_name]["errors"].append({
                "test": test_name,
                "error": f"SKIPPED: {reason}"
            })

    def get_winner(self) -> Tuple[Optional[int], Dict]:
        """Determine the winner based on test results."""
        winner = None
        max_passed = -1

        for impl_name, results in self.results.items():
            if impl_name == "original_code":
                continue  # Skip original code when determining winner

            if results["passed"] > max_passed:
                max_passed = results["passed"]
                winner = impl_name
            # Break ties by looking at failure count
            elif results["passed"] == max_passed and winner is not None:
                if results["failed"] < self.results[winner]["failed"]:
                    winner = impl_name

        # Convert winner to numeric index if possible
        winner_index = -1
        if winner and re.match(r'modified_code\d+', winner):
            try:
                winner_index = int(re.search(r'(\d+)', winner).group(1))
            except (AttributeError, ValueError):
                pass

        return winner_index, self.results

    def save_results(self, filename: str = "test_results.json") -> None:
        """Save test results to a JSON file."""
        import json

        winner_index, results = self.get_winner()

        # Check if all tests were skipped
        all_skipped = all(
            stats["skipped"] == stats["passed"] + stats["failed"] + stats["skipped"]
            for impl_name, stats in results.items()
            if impl_name != "original_code"
        )

        output = {
            "winner": winner_index,
            "all_skipped": all_skipped,
            "results": {
                name: {
                    "passed": stats["passed"],
                    "failed": stats["failed"],
                    "skipped": stats["skipped"],
                    "total": stats["passed"] + stats["failed"] + stats["skipped"]
                }
                for name, stats in results.items()
                if not name.startswith("_")  # Skip internal items
            }
        }

        with open(filename, "w") as f:
            json.dump(output, f, indent=2)

        print(f"Test results saved to {filename}")

        return output