"""核心关键字实现 - KeyWordTest 类"""

from playwright.sync_api import Page, Locator, expect
from typing import Optional, Union, List
import re
import time

from .base_keyword import BaseKeyword
from ..common import KEY_MAP
from ..reference import GSTORE, PRIVATEDATA, INFO, set_element_value


class KeyWordTest(BaseKeyword):
    """核心关键字实现类

    兼容原 kdtest API，提供所有基础关键字操作。
    """

    def __init__(self, page: Page):
        """初始化关键字测试类

        Args:
            page: Playwright Page 实例
        """
        super().__init__(page)

    # ==================== 浏览器操作 ====================

    def driver_get(self, *, content: str) -> None:
        """导航到 URL

        Args:
            content: 目标 URL
        """
        INFO(f"导航到: {content}")
        self.page.goto(content, wait_until='domcontentloaded')
        self.wait_for_element_plus_loading()

    def driver_back(self) -> None:
        """浏览器后退"""
        INFO("浏览器后退")
        self.page.go_back()

    def driver_forward(self) -> None:
        """浏览器前进"""
        INFO("浏览器前进")
        self.page.go_forward()

    def driver_refresh(self) -> None:
        """刷新页面"""
        INFO("刷新页面")
        self.page.reload()
        self.wait_for_element_plus_loading()

    def driver_close(self) -> None:
        """关闭当前页面"""
        INFO("关闭当前页面")
        self.page.close()

    def driver_maximize(self) -> None:
        """最大化窗口（设置视口为屏幕大小）"""
        INFO("最大化窗口")
        # Playwright 使用视口而非窗口，设置一个较大的尺寸
        self.page.set_viewport_size({'width': 1920, 'height': 1080})

    # ==================== 输入操作 ====================

    def input_text(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str
    ) -> None:
        """输入文本（自动清除后输入）

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 输入内容
        """
        INFO(f"输入文本: {content}")
        loc = self.locator(targeting, element, index, parent)
        loc.fill(str(content))

    def input_append(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str
    ) -> None:
        """追加输入文本

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 追加内容
        """
        INFO(f"追加输入: {content}")
        loc = self.locator(targeting, element, index, parent)
        loc.press_sequentially(str(content))

    def input_clear(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None
    ) -> None:
        """清除输入框内容

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
        """
        INFO("清除输入框")
        loc = self.locator(targeting, element, index, parent)
        loc.clear()

    # ==================== 点击操作 ====================

    def click_btn(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        force: bool = False,
        js_click: bool = False,
        content: str = None
    ) -> None:
        """点击元素

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            force: 是否强制点击
            js_click: 是否使用 JS 点击
            content: 预留参数（兼容性）
        """
        INFO(f"点击元素: {element}")
        loc = self.locator(targeting, element, index, parent)

        if js_click or content == 'js_click':
            loc.evaluate('el => el.click()')
        else:
            loc.click(force=force)

    def double_click(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None
    ) -> None:
        """双击元素

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
        """
        INFO(f"双击元素: {element}")
        loc = self.locator(targeting, element, index, parent)
        loc.dblclick()

    def right_click(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None
    ) -> None:
        """右键点击

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
        """
        INFO(f"右键点击: {element}")
        loc = self.locator(targeting, element, index, parent)
        loc.click(button='right')

    def click_and_hold(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None
    ) -> None:
        """点击并按住

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
        """
        INFO(f"点击并按住: {element}")
        loc = self.locator(targeting, element, index, parent)
        box = loc.bounding_box()
        if box:
            self.page.mouse.move(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2)
            self.page.mouse.down()

    def release_click(self) -> None:
        """释放点击"""
        INFO("释放点击")
        self.page.mouse.up()

    # ==================== 鼠标操作 ====================

    def hover(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None
    ) -> None:
        """鼠标悬停

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
        """
        INFO(f"悬停: {element}")
        loc = self.locator(targeting, element, index, parent)
        loc.hover()

    def drag_and_drop(
        self,
        source_targeting: str,
        source_element: str,
        target_targeting: str,
        target_element: str,
        *,
        content: str = None
    ) -> None:
        """拖拽元素

        Args:
            source_targeting: 源元素定位类型
            source_element: 源元素定位表达式
            target_targeting: 目标元素定位类型
            target_element: 目标元素定位表达式
            content: 预留参数
        """
        INFO(f"拖拽: {source_element} -> {target_element}")
        source = self.locator(source_targeting, source_element)
        target = self.locator(target_targeting, target_element)
        source.drag_to(target)

    def scroll_to_element(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None
    ) -> None:
        """滚动到元素位置

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
        """
        INFO(f"滚动到元素: {element}")
        loc = self.locator(targeting, element, index, parent)
        loc.scroll_into_view_if_needed()

    def scroll_by(self, *, content: str) -> None:
        """滚动页面

        Args:
            content: 滚动距离，格式 "x,y" 或 "y"
        """
        parts = str(content).split(',')
        if len(parts) == 2:
            x, y = int(parts[0]), int(parts[1])
        else:
            x, y = 0, int(parts[0])

        INFO(f"滚动页面: ({x}, {y})")
        self.page.mouse.wheel(x, y)

    # ==================== 键盘操作 ====================

    def keyboard_events(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str
    ) -> None:
        """键盘事件

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 按键 (如 Keys.ENTER, Keys.TAB)
        """
        INFO(f"键盘事件: {content}")
        loc = self.locator(targeting, element, index, parent)

        # 转换按键
        key = KEY_MAP.get(content, content)
        loc.press(key)

    def press_key(self, *, content: str) -> None:
        """全局按键

        Args:
            content: 按键
        """
        INFO(f"按键: {content}")
        key = KEY_MAP.get(content, content)
        self.page.keyboard.press(key)

    def type_text(self, *, content: str) -> None:
        """全局键盘输入

        Args:
            content: 输入内容
        """
        INFO(f"键盘输入: {content}")
        self.page.keyboard.type(str(content))

    # ==================== 下拉选择（原生 select） ====================

    def selector_operation(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str,
        by: str = 'label'
    ) -> None:
        """原生 select 下拉框选择

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 选项值
            by: 选择方式 (label, value, index)
        """
        INFO(f"选择下拉框: {content}")
        loc = self.locator(targeting, element, index, parent)

        if by == 'label':
            loc.select_option(label=content)
        elif by == 'value':
            loc.select_option(value=content)
        elif by == 'index':
            loc.select_option(index=int(content))
        else:
            loc.select_option(label=content)

    # ==================== 复选框/单选框 ====================

    def checkbox_operation(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str = 'true'
    ) -> None:
        """复选框操作

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 是否选中 (true/false)
        """
        checked = str(content).lower() in ('true', '1', 'yes', 'on')
        INFO(f"复选框: {'选中' if checked else '取消'}")
        loc = self.locator(targeting, element, index, parent)
        loc.set_checked(checked)

    def radio_select(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None
    ) -> None:
        """单选框选择

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
        """
        INFO(f"选择单选框: {element}")
        loc = self.locator(targeting, element, index, parent)
        loc.check()

    # ==================== 文件上传 ====================

    def file_upload(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str
    ) -> None:
        """文件上传

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 文件路径（多个文件用逗号分隔）
        """
        INFO(f"上传文件: {content}")
        loc = self.locator(targeting, element, index, parent)

        # 支持多文件
        files = [f.strip() for f in str(content).split(',')]
        if len(files) == 1:
            loc.set_input_files(files[0])
        else:
            loc.set_input_files(files)

    # ==================== iframe 操作 ====================

    def switch_to_frame(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None
    ) -> None:
        """切换到 iframe

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
        """
        self.switch_frame(targeting, element)

    def switch_to_default(self) -> None:
        """切换到主文档"""
        self.frame_default()

    def switch_to_parent_frame(self) -> None:
        """切换到父 frame"""
        self.switch_frame_parent()

    # ==================== 窗口/标签页操作 ====================

    def get_handle(self, *, content: str = None) -> str:
        """获取当前页面标识并缓存

        Args:
            content: 缓存键名

        Returns:
            str: 页面 URL
        """
        url = self.page.url
        PRIVATEDATA['PAGES'].append(self.page)
        if content:
            set_element_value(content, url)
        INFO(f"页面句柄: {url}")
        return url

    def switch_to_new_page(self, *, content: str = None) -> None:
        """切换到新打开的页面

        Args:
            content: 预留参数
        """
        INFO("切换到新页面")
        pages = self.page.context.pages
        if len(pages) > 1:
            new_page = pages[-1]
            GSTORE['page'] = new_page
            self.page = new_page

    def switch_to_page(self, *, content: str) -> None:
        """切换到指定索引的页面

        Args:
            content: 页面索引
        """
        index = int(content)
        INFO(f"切换到页面: {index}")
        pages = self.page.context.pages
        if 0 <= index < len(pages):
            self.page = pages[index]
            GSTORE['page'] = self.page

    def switch_to_page_by_title(self, *, content: str) -> None:
        """根据标题切换页面

        Args:
            content: 标题模式
        """
        INFO(f"切换到标题包含: {content}")
        pages = self.page.context.pages
        for page in pages:
            if content in page.title():
                self.page = page
                GSTORE['page'] = self.page
                return
        INFO(f"未找到标题包含 '{content}' 的页面", "WARNING")

    def switch_to_page_by_url(self, *, content: str) -> None:
        """根据 URL 切换页面

        Args:
            content: URL 模式
        """
        INFO(f"切换到 URL 包含: {content}")
        pages = self.page.context.pages
        for page in pages:
            if content in page.url:
                self.page = page
                GSTORE['page'] = self.page
                return
        INFO(f"未找到 URL 包含 '{content}' 的页面", "WARNING")

    def close_other_pages(self) -> None:
        """关闭其他页面"""
        INFO("关闭其他页面")
        current = self.page
        pages = self.page.context.pages
        for page in pages:
            if page != current:
                page.close()

    # ==================== 弹窗处理 ====================

    def alert_accept(self) -> None:
        """接受弹窗"""
        INFO("接受弹窗")
        self.page.on('dialog', lambda dialog: dialog.accept())

    def alert_dismiss(self) -> None:
        """取消弹窗"""
        INFO("取消弹窗")
        self.page.on('dialog', lambda dialog: dialog.dismiss())

    def alert_text(self, *, content: str = None) -> str:
        """获取弹窗文本

        Args:
            content: 缓存键名

        Returns:
            str: 弹窗文本
        """
        text = ''

        def handle_dialog(dialog):
            nonlocal text
            text = dialog.message
            dialog.accept()

        self.page.on('dialog', handle_dialog)
        if content:
            set_element_value(content, text)
        return text

    def alert_input(self, *, content: str) -> None:
        """prompt 弹窗输入

        Args:
            content: 输入内容
        """
        INFO(f"弹窗输入: {content}")
        self.page.on('dialog', lambda dialog: dialog.accept(content))

    # ==================== 断言关键字 ====================

    def title_assert(self, *, content: str, mode: str = 'equals') -> bool:
        """页面标题断言

        Args:
            content: 期望值
            mode: 断言模式 (equals, contains, regex)

        Returns:
            bool: 断言结果
        """
        title = self.page.title()
        result = self._assert_text(title, content, mode)
        INFO(f"标题断言: {title} {mode} {content} -> {result}")
        return result

    def url_assert(self, *, content: str, mode: str = 'equals') -> bool:
        """页面 URL 断言

        Args:
            content: 期望值
            mode: 断言模式

        Returns:
            bool: 断言结果
        """
        url = self.page.url
        result = self._assert_text(url, content, mode)
        INFO(f"URL 断言: {url} {mode} {content} -> {result}")
        return result

    def text_assert(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str,
        mode: str = 'equals'
    ) -> bool:
        """元素文本断言

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 期望值
            mode: 断言模式

        Returns:
            bool: 断言结果
        """
        loc = self.locator(targeting, element, index, parent)
        actual = loc.text_content() or ''
        actual = actual.strip()
        result = self._assert_text(actual, content, mode)
        INFO(f"文本断言: '{actual}' {mode} '{content}' -> {result}")
        return result

    def value_assert(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str,
        mode: str = 'equals'
    ) -> bool:
        """元素 value 属性断言

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 期望值
            mode: 断言模式

        Returns:
            bool: 断言结果
        """
        loc = self.locator(targeting, element, index, parent)
        actual = loc.input_value() or ''
        result = self._assert_text(actual, content, mode)
        INFO(f"Value 断言: '{actual}' {mode} '{content}' -> {result}")
        return result

    def attribute_assert(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        attribute: str,
        content: str,
        mode: str = 'equals'
    ) -> bool:
        """元素属性断言

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            attribute: 属性名
            content: 期望值
            mode: 断言模式

        Returns:
            bool: 断言结果
        """
        loc = self.locator(targeting, element, index, parent)
        actual = loc.get_attribute(attribute) or ''
        result = self._assert_text(actual, content, mode)
        INFO(f"属性断言: {attribute}='{actual}' {mode} '{content}' -> {result}")
        return result

    def element_visible_assert(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str = 'true'
    ) -> bool:
        """元素可见性断言

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 期望是否可见 (true/false)

        Returns:
            bool: 断言结果
        """
        expected = str(content).lower() in ('true', '1', 'yes')
        loc = self.locator(targeting, element, index, parent)
        is_visible = loc.is_visible()
        result = is_visible == expected
        INFO(f"可见性断言: {is_visible} == {expected} -> {result}")
        return result

    def element_enabled_assert(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str = 'true'
    ) -> bool:
        """元素可用性断言

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 期望是否可用 (true/false)

        Returns:
            bool: 断言结果
        """
        expected = str(content).lower() in ('true', '1', 'yes')
        loc = self.locator(targeting, element, index, parent)
        is_enabled = loc.is_enabled()
        result = is_enabled == expected
        INFO(f"可用性断言: {is_enabled} == {expected} -> {result}")
        return result

    def element_count_assert(
        self,
        targeting: str,
        element: str,
        *,
        content: str,
        mode: str = 'equals'
    ) -> bool:
        """元素数量断言

        Args:
            targeting: 定位类型
            element: 定位表达式
            content: 期望数量
            mode: 断言模式 (equals, gt, lt, gte, lte)

        Returns:
            bool: 断言结果
        """
        loc = self.locator(targeting, element)
        actual = loc.count()
        expected = int(content)

        mode_map = {
            'equals': actual == expected,
            'gt': actual > expected,
            'lt': actual < expected,
            'gte': actual >= expected,
            'lte': actual <= expected,
        }
        result = mode_map.get(mode, actual == expected)
        INFO(f"数量断言: {actual} {mode} {expected} -> {result}")
        return result

    def _assert_text(self, actual: str, expected: str, mode: str) -> bool:
        """文本断言辅助方法

        Args:
            actual: 实际值
            expected: 期望值
            mode: 断言模式

        Returns:
            bool: 断言结果
        """
        if mode == 'equals':
            return actual == expected
        elif mode == 'contains':
            return expected in actual
        elif mode == 'regex':
            return bool(re.search(expected, actual))
        elif mode == 'startswith':
            return actual.startswith(expected)
        elif mode == 'endswith':
            return actual.endswith(expected)
        elif mode == 'not_equals':
            return actual != expected
        elif mode == 'not_contains':
            return expected not in actual
        return False

    # ==================== 取值操作 ====================

    def get_element_text(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str
    ) -> str:
        """获取元素文本并缓存

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 缓存键名

        Returns:
            str: 元素文本
        """
        loc = self.locator(targeting, element, index, parent)
        text = loc.text_content() or ''
        text = text.strip()
        set_element_value(content, text)
        INFO(f"获取文本: {content} = '{text}'")
        return text

    def get_element_value(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        content: str
    ) -> str:
        """获取元素 value 属性并缓存

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            content: 缓存键名

        Returns:
            str: value 值
        """
        loc = self.locator(targeting, element, index, parent)
        value = loc.input_value() or ''
        set_element_value(content, value)
        INFO(f"获取 value: {content} = '{value}'")
        return value

    def get_element_attribute(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        parent: Optional[Locator] = None,
        *,
        attribute: str,
        content: str
    ) -> str:
        """获取元素属性并缓存

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            parent: 父元素定位器
            attribute: 属性名
            content: 缓存键名

        Returns:
            str: 属性值
        """
        loc = self.locator(targeting, element, index, parent)
        value = loc.get_attribute(attribute) or ''
        set_element_value(content, value)
        INFO(f"获取属性: {content} = {attribute}='{value}'")
        return value

    def get_page_title(self, *, content: str) -> str:
        """获取页面标题并缓存

        Args:
            content: 缓存键名

        Returns:
            str: 页面标题
        """
        title = self.page.title()
        set_element_value(content, title)
        INFO(f"获取标题: {content} = '{title}'")
        return title

    def get_page_url(self, *, content: str) -> str:
        """获取页面 URL 并缓存

        Args:
            content: 缓存键名

        Returns:
            str: 页面 URL
        """
        url = self.page.url
        set_element_value(content, url)
        INFO(f"获取 URL: {content} = '{url}'")
        return url

    # ==================== 等待操作 ====================

    def time_sleep(self, *, content: str) -> None:
        """显式等待

        Args:
            content: 等待秒数
        """
        seconds = float(content)
        INFO(f"等待 {seconds} 秒")
        self.page.wait_for_timeout(int(seconds * 1000))

    def wait_element_visible(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        *,
        content: str = '30000'
    ) -> None:
        """等待元素可见

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            content: 超时时间 (毫秒)
        """
        timeout = int(content)
        INFO(f"等待元素可见: {element}")
        loc = self.locator(targeting, element, index)
        loc.wait_for(state='visible', timeout=timeout)

    def wait_element_hidden(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        *,
        content: str = '30000'
    ) -> None:
        """等待元素隐藏

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            content: 超时时间 (毫秒)
        """
        timeout = int(content)
        INFO(f"等待元素隐藏: {element}")
        loc = self.locator(targeting, element, index)
        loc.wait_for(state='hidden', timeout=timeout)

    def wait_page_load(self, *, content: str = '30000') -> None:
        """等待页面加载完成

        Args:
            content: 超时时间 (毫秒)
        """
        timeout = int(content)
        INFO("等待页面加载")
        self.page.wait_for_load_state('networkidle', timeout=timeout)

    # ==================== 截图 ====================

    def screenshot(self, *, content: str, full_page: bool = False) -> None:
        """截图

        Args:
            content: 保存路径
            full_page: 是否全页截图
        """
        INFO(f"截图: {content}")
        self.page.screenshot(path=content, full_page=full_page)

    def screenshot_full(self, *, content: str) -> None:
        """全页截图

        Args:
            content: 保存路径
        """
        INFO(f"全页截图: {content}")
        self.page.screenshot(path=content, full_page=True)

    def element_screenshot(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        *,
        content: str
    ) -> None:
        """元素截图

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            content: 保存路径
        """
        INFO(f"元素截图: {content}")
        loc = self.locator(targeting, element, index)
        loc.screenshot(path=content)

    # ==================== 其他操作 ====================

    def execute_js(self, *, content: str) -> any:
        """执行 JavaScript

        Args:
            content: JavaScript 代码

        Returns:
            执行结果
        """
        INFO(f"执行 JS: {content[:50]}...")
        return self.page.evaluate(content)

    def execute_js_on_element(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        *,
        content: str
    ) -> any:
        """在元素上执行 JavaScript

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            content: JavaScript 代码

        Returns:
            执行结果
        """
        INFO(f"元素 JS: {content[:50]}...")
        loc = self.locator(targeting, element, index)
        return loc.evaluate(content)

    def highlight_element(
        self,
        targeting: str,
        element: str,
        index: Optional[int] = None,
        *,
        content: str = '2'
    ) -> None:
        """高亮元素

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引
            content: 高亮秒数
        """
        loc = self.locator(targeting, element, index)
        loc.evaluate('''el => {
            const original = el.style.outline;
            el.style.outline = '3px solid red';
            setTimeout(() => el.style.outline = original, arguments[0] * 1000);
        }''', float(content))
        INFO(f"高亮元素: {element}")
