CODE = '''import playwright.sync_api
from typing import Literal, List

import json
import time
from typing import Union, Any
import logging
import requests
import io
import PyPDF2


logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

demo_mode = 'off'
retry_with_force = False
MAX_TEXT_LENGTH = 100  # Maximum number of characters for text content

def read_page(max_content_length: int = None, pdf_pages: List[int] = None):
    """
    Read the content of the current page, handling both regular web pages and PDFs,
    and send the content using send_msg_to_user. Additionally, it will now analyze
    and provide information about graphs or dashboards present on the page.

    Args:
        max_content_length (int, optional): Maximum number of characters to read. If None, read all content.
        pdf_pages (List[int], optional): List of page numbers to read for PDFs. If None, read all pages.

    Examples:
        read_page()
        read_page(max_content_length=1000)
        read_page(pdf_pages=[0, 1, 2])
    """
    start_time = time.time()
    url = page.url

    try:
        # Step 1: Determine content type
        send_msg_to_user(
            f"Attempting to read content from URL: {url}",
            msg_type="action_step",
            log_level="debug"
        )

        url = page.url
        page.wait_for_load_state('networkidle')

        # Check if the URL contains a PDF
        if 'pdf' in url.split('/'):
            pdf_content = read_pdf_content(page=page, pages=[
                0])  # To change the pages, change the pages=[0] to pages=[0, 1, 2, ...]
            content = f"Read PDF content: {pdf_content}"
        else:
            # Wait for the body element to be available if content is dynamically loaded
            page.wait_for_selector("body")
            text_content = page.evaluate('() => document.body.innerText')

            # html_content = page.content()
            # # Then you can process it with BeautifulSoup to extract text
            # from bs4 import BeautifulSoup
            # soup = BeautifulSoup(html_content, 'html.parser')
            # text_content = soup.get_text()

            # if not text_content.strip():
            #     text_content = page.inner_text('body')
            #     
            #     if not text_content.strip():
            #         warning_msg = "Could not find any text content in the body element, trying to read text content from the whole page."
            #         text_content = page.evaluate('() => document.body.textContent')
            # 
            #         if not text_content.strip():
            #             text_content = "Could not find any text content in the body element."

            content = f"Read page content: {text_content}"

        content_type = page.evaluate('() => document.contentType')

        execution_time = time.time() - start_time
        send_msg_to_user(
            {
                "action": "read_page",
                "status": "success",
                "url": url,
                "content_type": content_type,
                "content_length": len(content),
                "execution_time": execution_time,
                "content": content,
            },
            msg_type="action_feedback",
            log_level="info"
        )

    except Exception as e:
        # Step 5: Send failure feedback
        execution_time = time.time() - start_time
        send_msg_to_user(
            {
                "action": "read_page",
                "status": "failure",
                "url": url,
                "error": str(e),
                "execution_time": execution_time
            },
            msg_type="action_feedback",
            log_level="error"
        )

def read_pdf_content(page, pages=None):
    # Get the PDF URL
    pdf_url = page.url

    # Download the PDF content
    response = requests.get(pdf_url)
    pdf_content = io.BytesIO(response.content)

    # Use PyPDF2 to read the PDF content
    pdf_reader = PyPDF2.PdfReader(pdf_content)

    # Extract text from all pages
    text = ""
    for page_num in range(len(pdf_reader.pages)):
        if pages is None or page_num in pages:
            text += pdf_reader.pages[page_num].extract_text()
        elif pages is not None and max(pages) < page_num:
            break
        else:
            text += pdf_reader.pages[page_num].extract_text()

    return text


def add_demo_mode_effects(
        page: playwright.sync_api.Page,
        elem: playwright.sync_api.ElementHandle,
        bid: str,
        demo_mode: Literal["off", "default", "all_blue", "only_visible_elements"],
        move_cursor: bool = True,
):
    if demo_mode == "off":
        return

    """Adds visual effects to the target element"""
    box = elem.bounding_box()
    # box = extract_bounds_cdp(page, bid)
    if box:
        center_x, center_y = box["x"] + box["width"] / 2, box["y"] + box["height"] / 2
        is_top_element = check_for_overlay(page, bid, elem, box)

        if demo_mode == "only_visible_elements":
            if not is_top_element:
                return
            else:
                color = "blue"

        elif demo_mode == "default":
            if is_top_element:
                color = "blue"
            else:
                color = "red"

        elif demo_mode == "all_blue":
            color = "blue"

        if move_cursor:
            smooth_move_visual_cursor_to(page, center_x, center_y)
        highlight_by_box(page, box, color=color)


def check_for_overlay(
        page: playwright.sync_api.Page, bid: str, element: playwright.sync_api.ElementHandle, box: dict
):
    try:
        if not element:
            return False
        visibility = element.get_attribute("browsergym_visibility_ratio")
        if visibility and float(visibility) >= 0.5:
            return True

        """Checks if a given element is the topmost element at its center position by default.
        If check_corners is True, it checks if any of the corners is visible."""
        if box:
            # corners
            points_to_check = [
                (box["x"], box["y"]),
                (box["x"] + box["width"], box["y"]),
                (box["x"], box["y"] + box["height"]),
                (box["x"] + box["width"], box["y"] + box["height"]),
            ]

            for x, y in points_to_check:
                # Execute JavaScript to find the topmost element at the point.
                top_element = page.evaluate(
                    f"""() => {{
                    const el = document.elementFromPoint({x}, {y});
                    return el ? el.outerHTML : '';
                }}"""
                )

                # Check if the topmost element is the element we're interested in.
                if top_element and bid in top_element:
                    return True
        return False
    except Exception as e:
        send_msg_to_user(f"Failed to check overlay for element {bid}: {str(e)}")
        return False


def get_elem_by_bid(
        page: playwright.sync_api.Page, bid: str, scroll_into_view: bool = False
) -> playwright.sync_api.Locator:
    """
    Parse the given bid to sequentially locate every nested frame leading to the bid, then
    locate the bid element. Bids are expected to take the form "abb123", which means
    the element abb123 is located inside frame abb, which is located inside frame ab, which is
    located inside frame a, which is located inside the page's main frame.

    Args:
        bid: the browsergym id (playwright testid) of the page element.
        scroll_into_view: try to scroll element into view, unless it is completely visible.

    Returns:
        Playwright element.
        Bounding box of the element.
    """
    try:

        if not isinstance(bid, str):
            raise ValueError(f"expected a string, got {repr(bid)}")

        current_frame = page

        # dive into each nested frame, to the frame where the element is located
        i = 0
        while bid[i:] and not bid[i:].isnumeric():
            i += 1
            frame_bid = bid[:i]  # bid of the next frame to select
            frame_elem = current_frame.get_by_test_id(frame_bid)
            if not frame_elem.count():
                raise ValueError(f'Could not find element with bid "{bid}"')
            if scroll_into_view:
                frame_elem.scroll_into_view_if_needed(timeout=500)
            current_frame = frame_elem.frame_locator(":scope")

        # finally, we should have selected the frame where the target element is
        elem = current_frame.get_by_test_id(bid)
        if not elem.count():
            raise ValueError(f'Could not find element with bid "{bid}"')
        if scroll_into_view:
            elem.scroll_into_view_if_needed(timeout=500)
        return elem
    except Exception as e:
        raise


def highlight_by_box(
        page: playwright.sync_api.Page, box: dict, color: Literal["blue", "red"] = "blue"
):
    """Highlights the target element based on its bounding box attributes."""

    assert color in ("blue", "red")

    if box:
        left, top, width, height = box["x"], box["y"], box["width"], box["height"]
        page.evaluate(
            f"""\
const overlay = document.createElement('div');
document.body.appendChild(overlay);
overlay.setAttribute('style', `
    all: initial;
    position: fixed;
    border: 2px solid transparent;  /* Start with transparent border */
    borderRadius: 10px;  /* Add rounded corners */
    boxShadow: 0 0 0px {color};  /* Initial boxShadow with 0px spread */
    left: {left - 2}px;  /* Adjust left position to accommodate initial shadow spread */
    top: {top - 2}px;  /* Adjust top position likewise */
    width: {width}px;
    height: {height}px;
    z-index: 2147483646; /* Maximum value - 1 */
    pointerEvents: none; /* Ensure the overlay does not interfere with user interaction */
`);

// Animate the boxShadow to create a "wave" effect
let spread = 0;  // Initial spread radius of the boxShadow
const waveInterval = setInterval(() => {{
    spread += 10;  // Increase the spread radius to simulate the wave moving outward
    overlay.style.boxShadow = `0 0 40px ${{spread}}px {color}`;  // Update boxShadow to new spread radius
    overlay.style.opacity = 1 - spread / 38;  // Gradually decrease opacity to fade out the wave
    if (spread >= 38) {{  // Assuming 76px ~ 2cm spread radius
        clearInterval(waveInterval);  // Stop the animation once the spread radius reaches 2cm
        document.body.removeChild(overlay);  // Remove the overlay from the document
    }}
}}, 200);  // Adjust the interval as needed to control the speed of the wave animation
"""
        )
        # Wait a bit to let users see the highlight
        page.wait_for_timeout(1000)  # Adjust delay as needed


def smooth_move_visual_cursor_to(
        page: playwright.sync_api.Page, x: float, y: float, speed: float = 400
):
    """
    Smoothly moves the visual cursor to a specific point, with constant
    movement speed.

    Args:
        x: target location X coordinate (in viewport pixels)
        y: target location Y coordinate (in viewport pixels)
        speed: cursor speed (in pixels per second)
    """
    movement_time = page.evaluate(
        """\
    ([targetX, targetY, speed]) => {

        // create cursor if needed
        if (!("browsergym_visual_cursor" in window)) {
            if (window.trustedTypes && window.trustedTypes.createPolicy) {
                window.trustedTypes.createPolicy('default', {
                    createHTML: (string, sink) => string
                });
            }
            let cursor = document.createElement('div');
            cursor.setAttribute('id', 'browsergym-visual-cursor');
            cursor.innerHTML = `
                <svg width="50px" height="50px" viewBox="213 106 713 706" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M213.333 106.667L426.667 853.333 512 512 853.333 426.667 213.333 106.667z" fill="blue"/>
                </svg>
`;
            cursor.setAttribute('style', `
                all: initial;
                position: fixed;
                opacity: 0.7; /* Slightly transparent */
                z-index: 2147483647; /* Maximum value */
                pointer-events: none; /* Ensures the SVG doesn't interfere with page interactions */
            `);

            // Calculate center position within the viewport
            const centerX = window.innerWidth / 2;
            const centerY = window.innerHeight / 2;

            cursor.style.left = `${centerX}px`;
            cursor.style.top = `${centerY}px`;

            // save cursor element
            window.browsergym_visual_cursor = cursor;
            window.browsergym_visual_cursor_n_owners = 0;
        }

        // recover cursor
        let cursor = window.browsergym_visual_cursor;

        // attach cursor to document
        document.body.appendChild(cursor);
        window.browsergym_visual_cursor_n_owners += 1;

        x = parseFloat(cursor.style.left);
        y = parseFloat(cursor.style.top);

        dx = targetX - x;
        dy = targetY - y;
        dist = Math.hypot(dx, dy);
        movement_time = (dist / speed) * 1000;  // seconds to milliseconds
        still_wait_time = 1000;

        // Adjust steps based on distance to keep movement speed consistent
        // 1 step per 10 pixels of distance, adjust as needed
        steps = Math.max(1, Math.trunc(dist / 10));

        step_dx = dx / steps;
        step_dy = dy / steps;
        step_dist = dist / steps;
        step_wait_time = Math.max(10, movement_time / steps);

        let step = 0;
        let time_still = 0;
        const cursorInterval = setInterval(() => {
            // move cursor
            if (step < steps) {
                x += step_dx;
                y += step_dy;
                cursor.style.left = `${x}px`;
                cursor.style.top = `${y}px`;
            }
            // still cursor (wait a bit)
            else if (time_still < still_wait_time) {
                time_still += step_wait_time;
            }
            // stop and detach cursor
            else {
                clearInterval(cursorInterval);
                window.browsergym_visual_cursor_n_owners -= 1;
                if (window.browsergym_visual_cursor_n_owners <= 0) {
                    document.body.removeChild(cursor);

                }
            }
            step += 1;
        }, step_wait_time);

        return movement_time;
    }""",
        [x, y, speed],
    )
    page.wait_for_timeout(movement_time)


def noop(wait_ms: float = 1000):
    """
    Do nothing, and optionally wait for the given time (in milliseconds).

    Examples:
        noop()
        noop(500)
    """
    page.wait_for_timeout(wait_ms)


def send_msg_to_user(content: Union[str, dict, Any], msg_type: str = "info", log_level: str = "info", include_timestamp: bool = True):
    """
    Sends a structured message to the user and optionally logs it.

    Args:
        content (Union[str, dict, Any]): The message content. Can be a string, dictionary, or any serializable object.
        msg_type (str): The type of message (e.g., "info", "warning", "error", "action_feedback").
        log_level (str): The logging level to use ("debug", "info", "warning", "error", "critical").
        include_timestamp (bool): Whether to include a timestamp in the message structure.

    Examples:
        send_msg_to_user("Based on the results of my search, the city was built in 1751.")
        send_msg_to_user({"action": "select_option", "status": "success", "element": "a48"}, msg_type="action_feedback")
        send_msg_to_user("Warning: Could not find the specified element", msg_type="warning", log_level="warning")
    """
    # Prepare the message structure
    if isinstance(content, dict):
        if 'element_info' not in content: 
            content['element_info'] = {}
    else:
        # If content is not a dict, we can't assign to it
        pass

    message = {
        "type": msg_type,
        "content": content
    }

    if include_timestamp:
        message["timestamp"] = time.time()

    # Convert the message to a JSON string
    try:
        json_message = json.dumps(message)
    except TypeError:
        # If content is not JSON serializable, convert it to a string
        message["content"] = str(content)
        json_message = json.dumps(message)

    # Create a human-readable version of the message for send_message_to_user
    if isinstance(content, str):
        human_readable = content
    else:
        human_readable = f"{msg_type.capitalize()}: {json.dumps(content, indent=2)}"

    # Send the message using the existing function
    send_message_to_user(human_readable)

    # Log the message
    log_func = getattr(logging, log_level.lower(), logging.info)
    log_func(f"Message sent: {json_message}")



def scroll(delta_x: float, delta_y: float):
    """
    Scroll horizontally and vertically. Amounts in pixels, positive for right or down scrolling, negative for left or up scrolling. Dispatches a wheel event.

    Examples:
        scroll(0, 200)
        scroll(-50.2, -100.5)
    """
    page.mouse.wheel(delta_x, delta_y)


# def fill(bid: str, value: str):
#     """
#     Fill out a form field. It focuses the element and triggers an input event with the entered text.
#     It works for <input>, <textarea> and [contenteditable] elements.
# 
#     Examples:
#         fill('237', 'example value')
#         fill('45', "multi-line\\nexample")
#         fill('a12', "example with \\"quotes\\"")
#     """
#     start_time = time.time()
# 
#     try:
#         # Step 1: Retrieve the element
#         send_msg_to_user(
#             f"Attempting to retrieve element with bid '{bid}'",
#             msg_type="action_step",
#             log_level="debug"
#         )
#         elem = get_elem_by_bid(page, bid, demo_mode != "off")
# 
#         # Step 2: Apply demo mode effects
#         if demo_mode != "off":
#             send_msg_to_user(
#                 f"Applying demo mode effects for element '{bid}'",
#                 msg_type="action_step",
#                 log_level="debug"
#             )
#             add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
# 
#             send_msg_to_user(
#                 f"Clearing element '{bid}' and typing value with delay",
#                 msg_type="action_step",
#                 log_level="debug"
#             )
#             elem.clear()
#             delay = max(2000 / len(value), 10)
#             elem.type(value, delay=delay)
# 
#         # Step 3: Attempt to fill
#         send_msg_to_user(
#             f"Attempting to fill element '{bid}' with value '{value}'",
#             msg_type="action_step",
#             log_level="debug"
#         )
# 
#         if retry_with_force:
#             try:
#                 elem.fill(value, timeout=500)
#             except Exception as inner_e:
#                 send_msg_to_user(
#                     {
#                         "action": "fill",
#                         "status": "retry",
#                         "element_id": bid,
#                         "value": value,
#                         "error": str(inner_e),
#                         "retry_with_force": True
#                     },
#                     msg_type="action_feedback",
#                     log_level="warning"
#                 )
#                 # Retry with force
#                 elem.fill(value, force=True, timeout=500)
#         else:
#             elem.fill(value, timeout=500)
# 
#         # Step 4: Send success feedback
#         execution_time = time.time() - start_time
#         send_msg_to_user(
#             {
#                 "action": "fill",
#                 "status": "success",
#                 "element_id": bid,
#                 "value": value,
#                 "retry_with_force": retry_with_force,
#                 "execution_time": execution_time
#             },
#             msg_type="action_feedback",
#             log_level="info"
#         )
#     except Exception as e:
#         # Step 5: Send failure feedback
#         execution_time = time.time() - start_time
#         send_msg_to_user(
#             {
#                 "action": "fill",
#                 "status": "failure",
#                 "element_id": bid,
#                 "value": value,
#                 "error": str(e),
#                 "retry_with_force": retry_with_force,
#                 "execution_time": execution_time
#             },
#             msg_type="action_feedback",
#             log_level="error"
#         )
def fill(bid: str, value: str):
    start_time = time.time()
    success = False
    last_error = None

    try:
        # Retrieve the element
        elem = get_elem_by_bid(page, bid, demo_mode != "off")

        # Apply demo mode effects if needed
        if demo_mode != "off":
            add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
            elem.clear()
            delay = max(2000 / len(value), 10)
            elem.type(value, delay=delay)

        # Attempt to fill
        if retry_with_force:
            try:
                elem.fill(value, timeout=500)
                success = True
            except Exception as inner_e:
                last_error = inner_e
                # Retry with force
                elem.fill(value, force=True, timeout=500)
                success = True
        else:
            elem.fill(value, timeout=500)
            success = True
    except Exception as e:
        last_error = e

    execution_time = time.time() - start_time

    # Send a single message based on the outcome
    if success:
        send_msg_to_user(
            {
                "action": "fill",
                "status": "success",
                "element_id": bid,
                "value": value,
                "retry_with_force": retry_with_force,
                "execution_time": execution_time
            },
            msg_type="action_feedback",
            log_level="info"
        )
    else:
        send_msg_to_user(
            {
                "action": "fill",
                "status": "failure",
                "element_id": bid,
                "value": value,
                "error": str(last_error),
                "retry_with_force": retry_with_force,
                "execution_time": execution_time
            },
            msg_type="action_feedback",
            log_level="error"
        )


def select_option(bid: str, options: Union[str, List[str]]):
    """
    Select one or multiple options in a <select> element. You can specify
    option value or label to select. Multiple options can be selected.

    Examples:
        select_option('a48', "blue")
        select_option('c48', ["red", "green", "blue"])
    """
    start_time = time.time()
    selected_options = options if isinstance(options, list) else [options]

    try:
        # Step 1: Retrieve the element
        send_msg_to_user(
            f"Attempting to retrieve element with bid '{bid}'",
            msg_type="action_step",
            log_level="debug"
        )
        elem = get_elem_by_bid(page, bid, demo_mode != "off")

        # Step 2: Apply demo mode effects
        if demo_mode != "off":
            send_msg_to_user(
                f"Applying demo mode effects for element '{bid}'",
                msg_type="action_step",
                log_level="debug"
            )
            add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)

        # Step 3: Attempt to select options
        send_msg_to_user(
            f"Attempting to select options {selected_options} in element '{bid}'",
            msg_type="action_step",
            log_level="debug"
        )

        if retry_with_force:
            try:
                elem.select_option(options, timeout=500)
            except Exception as inner_e:
                send_msg_to_user(
                    {
                        "action": "select_option",
                        "status": "retry",
                        "element_id": bid,
                        "options": selected_options,
                        "error": str(inner_e),
                        "retry_with_force": True
                    },
                    msg_type="action_feedback",
                    log_level="warning"
                )
                # Retry with force
                elem.select_option(options, force=True, timeout=500)
        else:
            elem.select_option(options, timeout=500)

        # Step 4: Send success feedback
        execution_time = time.time() - start_time
        send_msg_to_user(
            {
                "action": "select_option",
                "status": "success",
                "element_id": bid,
                "options": selected_options,
                "retry_with_force": retry_with_force,
                "execution_time": execution_time
            },
            msg_type="action_feedback",
            log_level="info"
        )
    except Exception as e:
        # Step 5: Send failure feedback
        execution_time = time.time() - start_time
        send_msg_to_user(
            {
                "action": "select_option",
                "status": "failure",
                "element_id": bid,
                "options": selected_options,
                "error": str(e),
                "retry_with_force": retry_with_force,
                "execution_time": execution_time
            },
            msg_type="action_feedback",
            log_level="error"
        )

def safe_text_content(elem, default="N/A"):
    try:
        logging.info(f"Getting text content for element {elem}")    
        text = elem.text_content()
        logging.info(f"Text content: {text}")
        if not text:
            return default
        return text
    except Exception as e:
        logging.error(f"Error getting text content: {str(e)}")
        return default
        
def click(
    bid: str,
    button: Literal["left", "middle", "right"] = "left",
    modifiers: List[Literal["Alt", "Control", "Meta", "Shift"]] = [],
):
    start_time = time.time()
    success = False
    last_error = None
    elem = None
    element_info = {}

    try:
        # Retrieve the element
        elem = get_elem_by_bid(page, bid, demo_mode != "off")
        
        element_info = {
            }

    # except Exception as e:
    #     logging.error(f"Could not retrieve element info for element {bid}: {str(e)}")


        # Apply demo mode effects
        if demo_mode != "off":
            add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=True)

        # Attempt to click
        if retry_with_force:
            try:
                elem.click(button=button, modifiers=modifiers)
                success = True
            except Exception as inner_e:
                last_error = inner_e
                # Retry with force
                try:
                    elem.click(button=button, modifiers=modifiers, force=True)
                    success = True
                except Exception as force_e:
                    last_error = force_e
        else:
            elem.click(button=button, modifiers=modifiers)
            success = True
    except Exception as e:
        last_error = e
        logging.error(f"Error in click function for element {bid}: {str(e)}")

    execution_time = time.time() - start_time
    
    # Prepare the content dict
    content = {
        "action": "click",
        "element_id": bid,
        "button": button,
        "modifiers": modifiers,
        "retry_with_force": retry_with_force,
        "execution_time": execution_time,
        "element_info": element_info
    }
    
    # # Safely get the element name
    # if elem is not None:
    #     content['element_name'] = safe_text_content(elem)
    # else:
    #     content['element_name'] = "N/A"
    try:
        logging.info(f"Getting text content for element {elem}")    
        text = elem.text_content()
        logging.info(f"Text content: {text}")
        if not text:
            content['element_name'] = "N/A"
        content['element_name'] = text
    except Exception as e:
        logging.error(f"Error getting text content: {str(e)}")
        content['element_name'] = "N/A"
        
    if success:
        content["status"] = "success"
        send_msg_to_user(
            content,
            msg_type="action_feedback",
            log_level="info"
        )
    else:
        content["status"] = "failure"
        content["error"] = str(last_error)
        send_msg_to_user(
            content,
            msg_type="action_feedback",
            log_level="error"
        )




def go_back():
    """
    Navigate to the previous page in the browser history and provide additional
    information about the URLs involved.
    """
    start_time = time.time()

    try:
        # Step 1: Get the current URL before navigating back
        current_url = page.url

        send_msg_to_user(
            f"Attempting to go back from {current_url}.",
            msg_type="action_step",
            log_level="debug"
        )

        # Step 2: Perform the go back action
        previous_page = page.go_back()

        # Capture the new current URL after going back
        if previous_page:
            previous_url = previous_page.url
        else:
            previous_url = "unknown (no history available)"

        # Step 3: Create a meaningful message for the JSON
        content = f"Navigated back from {current_url} to {previous_url}."

        # Step 4: Send success feedback
        execution_time = time.time() - start_time
        send_msg_to_user(
            {
                "action": "go_back",
                "status": "success",
                "execution_time": execution_time,
                "current_url": current_url,
                "previous_url": previous_url,
                "content": content
            },
            msg_type="action_feedback",
            log_level="info"
        )

    except Exception as e:
        # Step 5: Handle any failure and provide error feedback
        execution_time = time.time() - start_time
        send_msg_to_user(
            {
                "action": "go_back",
                "status": "failure",
                "error": str(e),
                "execution_time": execution_time
            },
            msg_type="action_feedback",
            log_level="error"
        )

def to_google():
    """
    Navigate to Google (https://www.google.com/?hl=en) and provide feedback about the navigation action.

    Example:
        to_google()
    """
    start_time = time.time()

    try:
        # Step 1: Get the current URL before navigating
        current_url = page.url

        send_msg_to_user(
            f"Attempting to navigate from {current_url} to Google.",
            msg_type="action_step",
            log_level="debug"
        )

        # Step 2: Perform the navigation to Google
        google_url = "https://www.google.com/?hl=en"
        page.goto(google_url)

        # Step 3: Capture the new current URL (should be Google's URL)
        new_url = page.url

        # Step 4: Create a meaningful message for the JSON
        content = f"Navigated from {current_url} to Google at {new_url}."

        # Step 5: Send success feedback
        execution_time = time.time() - start_time
        send_msg_to_user(
            {
                "action": "to_google",
                "status": "success",
                "execution_time": execution_time,
                "current_url": current_url,
                "new_url": new_url,
                "content": content
            },
            msg_type="action_feedback",
            log_level="info"
        )

    except Exception as e:
        # Step 6: Handle any failure and provide error feedback
        execution_time = time.time() - start_time
        send_msg_to_user(
            {
                "action": "to_google",
                "status": "failure",
                "error": str(e),
                "execution_time": execution_time
            },
            msg_type="action_feedback",
            log_level="error"
        )



def dblclick(
        bid: str,
        button: Literal["left", "middle", "right"] = "left",
        modifiers: list[Literal["Alt", "Control", "Meta", "Shift"]] = [],
):
    """
    Double click an element.

    Examples:
        dblclick('12')
        dblclick('ca42', button="right")
        dblclick('178', button="middle", modifiers=["Shift"])
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=True)
    if retry_with_force:
        try:
            elem.dblclick(button=button, modifiers=modifiers, timeout=500)
        except Exception as e:
            elem.dblclick(button=button, modifiers=modifiers, force=True, timeout=500)
    else:
        elem.dblclick(button=button, modifiers=modifiers, timeout=500)


def hover(bid: str):
    """
    Hover over an element.

    Examples:
        hover('b8')
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    if demo_mode != "off":
        box = elem.bounding_box()
        if box:
            center_x, center_y = box["x"] + box["width"] / 2, box["y"] + box["height"] / 2
            smooth_move_visual_cursor_to(page, center_x, center_y)
    if retry_with_force:
        try:
            elem.hover(timeout=500)
        except Exception as e:
            elem.hover(force=True, timeout=500)
    else:
        elem.hover(timeout=500)


def press(bid: str, key_comb: str):
    """
    Focus the matching element and press a combination of keys. It accepts
    the logical key names that are emitted in the keyboardEvent.key property
    of the keyboard events: Backquote, Minus, Equal, Backslash, Backspace,
    Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp,
    ArrowRight, ArrowUp, F1 - F12, Digit0 - Digit9, KeyA - KeyZ, etc. You can
    alternatively specify a single character you'd like to produce such as "a"
    or "#". Following modification shortcuts are also supported: Shift, Control,
    Alt, Meta.

    Examples:
        press('88', 'Backspace')
        press('a26', 'Control+a')
        press('a61', 'Meta+Shift+t')
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
    elem.press(key_comb, timeout=500)


def focus(bid: str):
    """
    Focus the matching element.

    Examples:
        focus('b455')
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
    elem.focus(timeout=500)


def clear(bid: str):
    """
    Clear the input field.

    Examples:
        clear('996')
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
    elem.clear(timeout=500)


def drag_and_drop(from_bid: str, to_bid: str):
    """
    Perform a drag & drop. Hover the element that will be dragged. Press
    left mouse button. Move mouse to the element that will receive the
    drop. Release left mouse button.

    Examples:
        drag_and_drop('56', '498')
    """
    from_elem = get_elem_by_bid(page, from_bid, demo_mode != "off")
    add_demo_mode_effects(page, from_elem, from_bid, demo_mode=demo_mode, move_cursor=True)
    from_elem.hover(timeout=500)
    page.mouse.down()

    to_elem = get_elem_by_bid(page, to_bid, demo_mode != "off")
    add_demo_mode_effects(page, to_elem, to_bid, demo_mode=demo_mode, move_cursor=True)
    to_elem.hover(timeout=500)
    page.mouse.up()


def upload_file(bid: str, file: str | list[str]):
    """
    Click an element and wait for a "filechooser" event, then select one
    or multiple input files for upload. Relative file paths are resolved
    relative to the current working directory. An empty list clears the
    selected files.

    Examples:
        upload_file("572", "my_receipt.pdf")
        upload_file("63", ["/home/bob/Documents/image.jpg", "/home/bob/Documents/file.zip"])
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=True)

    with page.expect_file_chooser() as fc_info:
        elem.click(timeout=500)

    file_chooser = fc_info.value
    file_chooser.set_files(file)

def answer(text: str):
    """
    Send a message to the user with the given text.

    Examples:
        answer("The capital of France is Paris.")
    """
    send_msg_to_user(f'action: {text}')

'''

CODE_2 = '''
import playwright.sync_api
from typing import Literal

TIMEOUT = 1500

retry_with_force=False
# retry_with_force=True

demo_mode='off'

def add_demo_mode_effects(
    page: playwright.sync_api.Page,
    elem: playwright.sync_api.ElementHandle,
    bid: str,
    demo_mode: Literal["off", "default", "all_blue", "only_visible_elements"],
    move_cursor: bool = True,
):
    if demo_mode == "off":
        return

    """Adds visual effects to the target element"""
    box = elem.bounding_box()
    # box = extract_bounds_cdp(page, bid)
    if box:
        center_x, center_y = box["x"] + box["width"] / 2, box["y"] + box["height"] / 2
        is_top_element = check_for_overlay(page, bid, elem, box)

        if demo_mode == "only_visible_elements":
            if not is_top_element:
                return
            else:
                color = "blue"

        elif demo_mode == "default":
            if is_top_element:
                color = "blue"
            else:
                color = "red"

        elif demo_mode == "all_blue":
            color = "blue"

        if move_cursor:
            smooth_move_visual_cursor_to(page, center_x, center_y)
        highlight_by_box(page, box, color=color)



def check_for_overlay(
    page: playwright.sync_api.Page, bid: str, element: playwright.sync_api.ElementHandle, box: dict
):
    if not element:
        return False

    visibility = element.get_attribute("browsergym_visibility_ratio")
    if visibility is not None:
        return float(visibility) >= 0.5

    """Checks if a given element is the topmost element at its center position by default.
    If check_corners is True, it checks if any of the corners is visible."""
    if box:
        # corners
        points_to_check = [
            (box["x"], box["y"]),
            (box["x"] + box["width"], box["y"]),
            (box["x"], box["y"] + box["height"]),
            (box["x"] + box["width"], box["y"] + box["height"]),
        ]

        for x, y in points_to_check:
            # Execute JavaScript to find the topmost element at the point.
            top_element = page.evaluate(
                f"""() => {{
                const el = document.elementFromPoint({x}, {y});
                return el ? el.outerHTML : '';
            }}"""
            )

            # Check if the topmost element is the element we're interested in.
            if top_element and bid in top_element:
                return True

    return False



def get_elem_by_bid(
    page: playwright.sync_api.Page, bid: str, scroll_into_view: bool = False
) -> playwright.sync_api.Locator:
    """
    Parse the given bid to sequentially locate every nested frame leading to the bid, then
    locate the bid element. Bids are expected to take the form "abb123", which means
    the element abb123 is located inside frame abb, which is located inside frame ab, which is
    located inside frame a, which is located inside the page's main frame.

    Args:
        bid: the browsergym id (playwright testid) of the page element.
        scroll_into_view: try to scroll element into view, unless it is completely visible.

    Returns:
        Playwright element.
        Bounding box of the element.
    """
    if not isinstance(bid, str):
        raise ValueError(f"expected a string, got {repr(bid)}")

    current_frame = page

    # dive into each nested frame, to the frame where the element is located
    i = 0
    while bid[i:] and not bid[i:].isnumeric():
        i += 1
        frame_bid = bid[:i]  # bid of the next frame to select
        frame_elem = current_frame.get_by_test_id(frame_bid)
        if not frame_elem.count():
            raise ValueError(f'Could not find element with bid "{bid}"')
        if scroll_into_view:
            frame_elem.scroll_into_view_if_needed(timeout=TIMEOUT)
        current_frame = frame_elem.frame_locator(":scope")

    # finally, we should have selected the frame where the target element is
    elem = current_frame.get_by_test_id(bid)
    if not elem.count():
        raise ValueError(f'Could not find element with bid "{bid}"')
    if scroll_into_view:
        elem.scroll_into_view_if_needed(timeout=TIMEOUT)
    return elem



def highlight_by_box(
    page: playwright.sync_api.Page, box: dict, color: Literal["blue", "red"] = "blue"
):
    """Highlights the target element based on its bounding box attributes."""

    assert color in ("blue", "red")

    if box:
        left, top, width, height = box["x"], box["y"], box["width"], box["height"]
        page.evaluate(
            f"""\
const overlay = document.createElement('div');
document.body.appendChild(overlay);
overlay.setAttribute('style', `
    all: initial;
    position: fixed;
    border: 2px solid transparent;  /* Start with transparent border */
    borderRadius: 10px;  /* Add rounded corners */
    boxShadow: 0 0 0px {color};  /* Initial boxShadow with 0px spread */
    left: {left - 2}px;  /* Adjust left position to accommodate initial shadow spread */
    top: {top - 2}px;  /* Adjust top position likewise */
    width: {width}px;
    height: {height}px;
    z-index: 2147483646; /* Maximum value - 1 */
    pointerEvents: none; /* Ensure the overlay does not interfere with user interaction */
`);

// Animate the boxShadow to create a "wave" effect
let spread = 0;  // Initial spread radius of the boxShadow
const waveInterval = setInterval(() => {{
    spread += 10;  // Increase the spread radius to simulate the wave moving outward
    overlay.style.boxShadow = `0 0 40px ${{spread}}px {color}`;  // Update boxShadow to new spread radius
    overlay.style.opacity = 1 - spread / 38;  // Gradually decrease opacity to fade out the wave
    if (spread >= 38) {{  // Assuming 76px ~ 2cm spread radius
        clearInterval(waveInterval);  // Stop the animation once the spread radius reaches 2cm
        document.body.removeChild(overlay);  // Remove the overlay from the document
    }}
}}, 200);  // Adjust the interval as needed to control the speed of the wave animation
"""
        )
        # Wait a bit to let users see the highlight
        page.wait_for_timeout(1000)  # Adjust delay as needed



def smooth_move_visual_cursor_to(
    page: playwright.sync_api.Page, x: float, y: float, speed: float = 400
):
    """
    Smoothly moves the visual cursor to a specific point, with constant
    movement speed.

    Args:
        x: target location X coordinate (in viewport pixels)
        y: target location Y coordinate (in viewport pixels)
        speed: cursor speed (in pixels per second)
    """
    movement_time = page.evaluate(
        """\
    ([targetX, targetY, speed]) => {

        // create cursor if needed
        if (!("browsergym_visual_cursor" in window)) {
            if (window.trustedTypes && window.trustedTypes.createPolicy) {
                window.trustedTypes.createPolicy('default', {
                    createHTML: (string, sink) => string
                });
            }
            let cursor = document.createElement('div');
            cursor.setAttribute('id', 'browsergym-visual-cursor');
            cursor.innerHTML = `
                <svg width="50px" height="50px" viewBox="213 106 713 706" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M213.333 106.667L426.667 853.333 512 512 853.333 426.667 213.333 106.667z" fill="blue"/>
                </svg>
`;
            cursor.setAttribute('style', `
                all: initial;
                position: fixed;
                opacity: 0.7; /* Slightly transparent */
                z-index: 2147483647; /* Maximum value */
                pointer-events: none; /* Ensures the SVG doesn't interfere with page interactions */
            `);

            // Calculate center position within the viewport
            const centerX = window.innerWidth / 2;
            const centerY = window.innerHeight / 2;

            cursor.style.left = `${centerX}px`;
            cursor.style.top = `${centerY}px`;

            // save cursor element
            window.browsergym_visual_cursor = cursor;
            window.browsergym_visual_cursor_n_owners = 0;
        }

        // recover cursor
        let cursor = window.browsergym_visual_cursor;

        // attach cursor to document
        document.body.appendChild(cursor);
        window.browsergym_visual_cursor_n_owners += 1;

        x = parseFloat(cursor.style.left);
        y = parseFloat(cursor.style.top);

        dx = targetX - x;
        dy = targetY - y;
        dist = Math.hypot(dx, dy);
        movement_time = (dist / speed) * 1000;  // seconds to milliseconds
        still_wait_time = 1000;

        // Adjust steps based on distance to keep movement speed consistent
        // 1 step per 10 pixels of distance, adjust as needed
        steps = Math.max(1, Math.trunc(dist / 10));

        step_dx = dx / steps;
        step_dy = dy / steps;
        step_dist = dist / steps;
        step_wait_time = Math.max(10, movement_time / steps);

        let step = 0;
        let time_still = 0;
        const cursorInterval = setInterval(() => {
            // move cursor
            if (step < steps) {
                x += step_dx;
                y += step_dy;
                cursor.style.left = `${x}px`;
                cursor.style.top = `${y}px`;
            }
            // still cursor (wait a bit)
            else if (time_still < still_wait_time) {
                time_still += step_wait_time;
            }
            // stop and detach cursor
            else {
                clearInterval(cursorInterval);
                window.browsergym_visual_cursor_n_owners -= 1;
                if (window.browsergym_visual_cursor_n_owners <= 0) {
                    document.body.removeChild(cursor);

                }
            }
            step += 1;
        }, step_wait_time);

        return movement_time;
    }""",
        [x, y, speed],
    )
    page.wait_for_timeout(movement_time)



def noop(wait_ms: float = 1000):
    """
    Do nothing, and optionally wait for the given time (in milliseconds).

    Examples:
        noop()
        noop(500)
    """
    page.wait_for_timeout(wait_ms)



def scroll(delta_x: float, delta_y: float):
    """
    Scroll horizontally and vertically. Amounts in pixels, positive for right or down scrolling, negative for left or up scrolling. Dispatches a wheel event.

    Examples:
        scroll(0, 200)
        scroll(-50.2, -100.5)
    """
    page.mouse.wheel(delta_x, delta_y)



def fill(bid: str, value: str):
    """
    Fill out a form field. It focuses the element and triggers an input event with the entered text.
    It works for <input>, <textarea> and [contenteditable] elements.

    Examples:
        fill('237', 'example value')
        fill('45', "multi-line\\nexample")
        fill('a12', "example with \\"quotes\\"")
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
    if demo_mode != "off":
        elem.clear()
        delay = max(2000 / len(value), 10)
        elem.type(value, delay=delay)
    if retry_with_force:
        try:
            elem.fill(value, timeout=TIMEOUT)
        except Exception as e:
            elem.fill(value, force=True, timeout=TIMEOUT)
    else:
        elem.fill(value, timeout=TIMEOUT)



def select_option(bid: str, options: str | list[str]):
    """
    Select one or multiple options in a <select> element. You can specify
    option value or label to select. Multiple options can be selected.

    Examples:
        select_option('a48', "blue")
        select_option('c48', ["red", "green", "blue"])
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
    if retry_with_force:
        try:
            elem.select_option(options, timeout=TIMEOUT)
        except Exception as e:
            elem.select_option(options, force=True, timeout=TIMEOUT)
    else:
        elem.select_option(options, timeout=TIMEOUT)



def click(
    bid: str,
    button: Literal["left", "middle", "right"] = "left",
    modifiers: list[Literal["Alt", "Control", "Meta", "Shift"]] = [],
):
    """
    Click an element.

    Examples:
        click('a51')
        click('b22', button="right")
        click('48', button="middle", modifiers=["Shift"])
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=True)
    if retry_with_force:
        try:
            elem.click(button=button, modifiers=modifiers, timeout=TIMEOUT)
        except Exception as e:
            elem.click(button=button, modifiers=modifiers, force=True, timeout=TIMEOUT)
    else:
        elem.click(button=button, modifiers=modifiers, timeout=TIMEOUT)



def dblclick(
    bid: str,
    button: Literal["left", "middle", "right"] = "left",
    modifiers: list[Literal["Alt", "Control", "Meta", "Shift"]] = [],
):
    """
    Double click an element.

    Examples:
        dblclick('12')
        dblclick('ca42', button="right")
        dblclick('178', button="middle", modifiers=["Shift"])
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=True)
    if retry_with_force:
        try:
            elem.dblclick(button=button, modifiers=modifiers, timeout=500)
        except Exception as e:
            elem.dblclick(button=button, modifiers=modifiers, force=True, timeout=500)
    else:
        elem.dblclick(button=button, modifiers=modifiers, timeout=500)



def hover(bid: str):
    """
    Hover over an element.

    Examples:
        hover('b8')
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    if demo_mode != "off":
        box = elem.bounding_box()
        if box:
            center_x, center_y = box["x"] + box["width"] / 2, box["y"] + box["height"] / 2
            smooth_move_visual_cursor_to(page, center_x, center_y)
    if retry_with_force:
        try:
            elem.hover(timeout=500)
        except Exception as e:
            elem.hover(force=True, timeout=500)
    else:
        elem.hover(timeout=500)



def press(bid: str, key_comb: str):
    """
    Focus the matching element and press a combination of keys. It accepts
    the logical key names that are emitted in the keyboardEvent.key property
    of the keyboard events: Backquote, Minus, Equal, Backslash, Backspace,
    Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp,
    ArrowRight, ArrowUp, F1 - F12, Digit0 - Digit9, KeyA - KeyZ, etc. You can
    alternatively specify a single character you'd like to produce such as "a"
    or "#". Following modification shortcuts are also supported: Shift, Control,
    Alt, Meta.

    Examples:
        press('88', 'Backspace')
        press('a26', 'Control+a')
        press('a61', 'Meta+Shift+t')
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
    elem.press(key_comb, timeout=500)



def focus(bid: str):
    """
    Focus the matching element.

    Examples:
        focus('b455')
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
    elem.focus(timeout=500)



def clear(bid: str):
    """
    Clear the input field.

    Examples:
        clear('996')
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=False)
    elem.clear(timeout=500)



def drag_and_drop(from_bid: str, to_bid: str):
    """
    Perform a drag & drop. Hover the element that will be dragged. Press
    left mouse button. Move mouse to the element that will receive the
    drop. Release left mouse button.

    Examples:
        drag_and_drop('56', '498')
    """
    from_elem = get_elem_by_bid(page, from_bid, demo_mode != "off")
    add_demo_mode_effects(page, from_elem, from_bid, demo_mode=demo_mode, move_cursor=True)
    from_elem.hover(timeout=500)
    page.mouse.down()

    to_elem = get_elem_by_bid(page, to_bid, demo_mode != "off")
    add_demo_mode_effects(page, to_elem, to_bid, demo_mode=demo_mode, move_cursor=True)
    to_elem.hover(timeout=500)
    page.mouse.up()



def upload_file(bid: str, file: str | list[str]):
    """
    Click an element and wait for a "filechooser" event, then select one
    or multiple input files for upload. Relative file paths are resolved
    relative to the current working directory. An empty list clears the
    selected files.

    Examples:
        upload_file("572", "my_receipt.pdf")
        upload_file("63", ["/home/bob/Documents/image.jpg", "/home/bob/Documents/file.zip"])
    """
    elem = get_elem_by_bid(page, bid, demo_mode != "off")
    add_demo_mode_effects(page, elem, bid, demo_mode=demo_mode, move_cursor=True)

    with page.expect_file_chooser() as fc_info:
        elem.click(timeout=500)

    file_chooser = fc_info.value
    file_chooser.set_files(file)
                
def answer(text: str):
    """
    Send a message to the user with the given text.

    Examples:
        answer("The capital of France is Paris.")
    """
    send_msg_to_user(f'action: {text}')
    
                
def send_msg_to_user(text: str):
    """
    Sends a message to the user.

    Examples:
        send_msg_to_user("Based on the results of my search, the city was built in 1751.")
    """
    send_message_to_user(text)



'''
