全球动态代理 · 静态住宅代理 · 极高质量 · 包月不限量 · 学员低价 · 联系如意进行免费测试 · 欢迎公司及学员采购 · 另有任何海外数据采集需求,随时联系 · 商务合作微信:Charleval
Automation / 触摸输入
RuyiPage Docs

RuyiPage 触摸输入

触摸输入模块提供 tap、长按和滑动手势,配合移动端预设可模拟真实手机操作。

核心 API

page.touch.swipe

返回: None
page.touch.swipe(start, end)

滑动手势,从起始位置滑到终点。

参数说明
参数说明
start起始元素或 (x, y) 坐标。
end终点元素或 (x, y) 坐标。
常见坑
  • 滑动距离太短可能不触发滑动事件。
相关示例

page.emulation.set_touch_enabled

返回: None
page.emulation.set_touch_enabled(enabled)

启用或禁用触摸输入能力。

参数说明
参数说明
enabledTrue 启用 / False 禁用。
常见坑
  • 不启用触摸时,tap/swipe 可能无效。
相关示例

快速上手

python 触摸点击与滑动
from ruyipage import launch

page = launch()
try:
    page.emulation.set_touch_enabled(True)
    page.get("https://m.example.com")

    # 触摸点击
    btn = page.ele("css:.submit-btn")
    page.touch.tap(btn)

    # 滑动手势
    page.touch.swipe((200, 500), (200, 100))
finally:
    page.quit()

相关示例源码

2 个示例 ▶ 点击展开

高级输入操作:组合键、拟人输入、拖拽、触摸、上传、键盘导航

examples/20_advanced_input.py

输入能力大全示例,是 actions、touch、uploads 和 isTrusted 的主参考。

GitHub 仓库 查看仓库源码 page.actions.combopage.actions.presspage.actions.double_clickpage.actions.right_clickpage.actions.middle_clickpage.actions.dragpage.actions.drag_topage.actions.scroll
  • 组合键与复制粘贴
  • 双击、右击、中键点击
  • 拖拽和元素级 drag_to
  • 滚轮和 hover
  • 拟人化移动、点击和输入
  • 触摸点击与长按
  • 文件上传和键盘导航

源码文件:app/content/ruyipage_examples/20_advanced_input.py · 仓库:20_advanced_input.py

python 注释版
# =============================
# RuyiPage 示例注释版
# 标题: 高级输入操作:组合键、拟人输入、拖拽、触摸、上传、键盘导航
# 说明: 输入能力大全示例,是 actions、touch、uploads 和 isTrusted 的主参考。
# 你会学到:
#   1. 组合键与复制粘贴
#   2. 双击、右击、中键点击
#   3. 拖拽和元素级 drag_to
#   4. 滚轮和 hover
#   5. 拟人化移动、点击和输入
# 相关 API: page.actions.combo, page.actions.press, page.actions.double_click, page.actions.right_click, page.actions.middle_click, page.actions.drag, page.actions.drag_to, page.actions.scroll
# =============================

# -*- coding: utf-8 -*-
"""
示例20: 高级输入操作(综合测试)

测试功能:
- 组合键操作(Ctrl+A / Ctrl+C / Ctrl+V)
- 便捷 combo() 方法
- press() 单键按下释放
- 双击操作(统一命名 double_click)
- 右键操作(统一命名 right_click)
- 中键点击(middle_click)
- 拖拽操作(hold/move_to/release + drag_to)
- 滚轮操作(scroll)
- 鼠标悬停(hover)
- Shift+点击组合
- 连续动作链
- 释放所有动作(release_all)
- isTrusted 行为验证
- 拟人化操作(human_move / human_click / human_type)
- 触摸操作(tap / double_tap / long_press)
- 文件上传(input.setFiles)
- 键盘导航(Tab / Arrow keys)
"""

import os
import sys
import io
import time
import traceback
import tempfile
from typing import Tuple, Dict, Optional

# 设置控制台输出编码为UTF-8(Windows兼容)
if sys.platform == "win32":
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from ruyipage import FirefoxPage, FirefoxOptions, Keys


# ═══════════════════════════════════════════════════════════════════
#  测试结果收集器
# ═══════════════════════════════════════════════════════════════════


class TestResult:
    """测试结果记录器"""

    def __init__(self) -> None:
        self.results = []

    def record(self, name: str, passed: bool, detail: str = "") -> None:
        self.results.append({"name": name, "passed": passed, "detail": detail})
        status = "✓ 通过" if passed else "✗ 失败"
        print(f"   {status}: {name}")
        if detail:
            print(f"      详情: {detail}")

    def summary(self) -> bool:
        print("\n" + "=" * 70)
        print("测试结果汇总")
        print("=" * 70)
        print(f"{'序号':<5} {'测试名称':<40} {'结果':<8} {'详情'}")
        print("-" * 70)
        for i, r in enumerate(self.results, 1):
            status = "✓ 通过" if r["passed"] else "✗ 失败"
            detail = r["detail"][:30] if r["detail"] else ""
            print(f"{i:<5} {r['name']:<40} {status:<8} {detail}")
        print("-" * 70)
        passed = sum(1 for r in self.results if r["passed"])
        failed = sum(1 for r in self.results if not r["passed"])
        total = len(self.results)
        print(f"总计: {total}  通过: {passed}  失败: {failed}")
        print("=" * 70)
        return failed == 0


# ═══════════════════════════════════════════════════════════════════
#  各项测试
# ═══════════════════════════════════════════════════════════════════


def test_combo_ctrl_a(page: FirefoxPage, results: TestResult):
    """测试1: combo() 组合键 Ctrl+A 全选"""
    try:
        input_elem = page.ele("#text-input")
        input_elem.input("combo测试文本")
        page.wait(0.3)
        input_elem.click_self()
        page.actions.combo(Keys.CTRL, "a").perform()
        page.wait(0.3)
        results.record("combo(Ctrl+A) 全选", True)
    except Exception as e:
        results.record("combo(Ctrl+A) 全选", False, str(e))


def test_combo_copy_paste(page: FirefoxPage, results: TestResult):
    """测试2: combo() 组合键 Ctrl+C / Ctrl+V 复制粘贴"""
    try:
        input_elem = page.ele("#text-input")
        input_elem.input("复制粘贴测试")
        page.wait(0.3)

        # 全选 + 复制
        input_elem.click_self()
        page.actions.combo(Keys.CTRL, "a").perform()
        page.wait(0.2)
        page.actions.combo(Keys.CTRL, "c").perform()
        page.wait(0.2)

        # 清空 + 粘贴
        input_elem.clear()
        page.wait(0.2)
        input_elem.click_self()
        page.actions.combo(Keys.CTRL, "v").perform()
        page.wait(0.3)

        pasted = input_elem.value
        ok = "复制粘贴测试" in str(pasted) if pasted else False
        results.record("combo(Ctrl+C/V) 复制粘贴", ok, f"粘贴值: {pasted}")
    except Exception as e:
        results.record("combo(Ctrl+C/V) 复制粘贴", False, str(e))


def test_press_key(page: FirefoxPage, results: TestResult):
    """测试3: press() 单键操作"""
    try:
        input_elem = page.ele("#text-input")
        input_elem.input("按键测试", clear=True)
        page.wait(0.2)

        # press End 移动到末尾,再输入
        page.actions.press(Keys.END).perform()
        page.wait(0.1)
        page.actions.type("!").perform()
        page.wait(0.2)

        val = input_elem.value
        ok = str(val).endswith("!")
        results.record("press(Keys.END) 单键操作", ok, f"值: {val}")
    except Exception as e:
        results.record("press(Keys.END) 单键操作", False, str(e))


def test_double_click(page: FirefoxPage, results: TestResult):
    """测试4: double_click() 统一命名双击"""
    try:
        dbl_btn = page.ele("#double-click-btn")
        page.actions.double_click(dbl_btn).perform()
        page.wait(0.5)

        result = page.ele("#click-result").text
        ok = "双击" in str(result)
        results.record("double_click() 双击", ok, f"结果: {result}")
    except Exception as e:
        results.record("double_click() 双击", False, str(e))


def test_right_click(page: FirefoxPage, results: TestResult):
    """测试5: right_click() 统一命名右击"""
    try:
        right_btn = page.ele("#right-click-btn")
        page.actions.right_click(right_btn).perform()
        page.wait(0.5)

        result = page.ele("#click-result").text
        ok = "右键" in str(result)
        results.record("right_click() 右键点击", ok, f"结果: {result}")
    except Exception as e:
        results.record("right_click() 右键点击", False, str(e))


def test_middle_click(page: FirefoxPage, results: TestResult):
    """测试6: middle_click() 中键点击"""
    try:
        click_btn = page.ele("#click-btn")
        page.actions.middle_click(click_btn).perform()
        page.wait(0.3)
        # 中键点击不会触发 onclick,只验证不抛异常
        results.record("middle_click() 中键点击", True, "执行无异常")
    except Exception as e:
        results.record("middle_click() 中键点击", False, str(e))


def _prepare_drag_scene(
    page: FirefoxPage,
) -> Tuple[Optional[Dict[str, int]], Optional[Dict[str, int]]]:
    """拖拽测试通用准备:刷新页面 → 禁用HTML5拖拽 → 滚动到可见 → 返回坐标。

    参考 example 35 的可靠方式:
    1. 关闭 HTML5 draggable 属性,防止 Firefox DnD API 干扰鼠标回退逻辑
    2. 通过 JS 精确计算 scrollTo 偏移,确保 draggable 和 drop-zone 同时在视口内
    3. 用 JS IIFE 获取两个元素的视口中心坐标

    Returns:
        tuple: (start_dict, end_dict) 视口坐标,失败时返回 (None, None)
    """
    page.refresh()
    page.wait(1)

    # 禁用 HTML5 DnD,避免 Firefox 拖拽 API 拦截鼠标事件
    page.run_js(
        "document.getElementById('draggable').setAttribute('draggable', 'false')"
    )

    # 精确滚动: 把 drag-section 的 h2 标题滚到视口顶部,
    # 这样 draggable(~100px高) 和 drop-zone(~200px高) 都在视口内
    page.run_js(
        "(function(){"
        "  var sec = document.getElementById('drag-section');"
        "  sec.scrollIntoView({block: 'start', inline: 'nearest'});"
        "})()"
    )
    page.wait(0.5)

    # 通过 JS IIFE 获取两个元素在当前视口中的精确中心坐标
    start = page.run_js(
        "(function(){ var r = document.getElementById('draggable').getBoundingClientRect();"
        " return {x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)}; })()"
    )
    end = page.run_js(
        "(function(){ var r = document.getElementById('drop-zone').getBoundingClientRect();"
        " return {x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)}; })()"
    )

    if not start or not end:
        return None, None
    return start, end


def _build_drag_actions(
    sx: int, sy: int, ex: int, ey: int, steps: int = 20, step_dur: int = 30
):
    """构建原生 BiDi 拖拽动作序列(参考 example 35)。

    所有 pointerMove 使用 origin='viewport',确保坐标绝对精确,
    产生 isTrusted=true 的鼠标事件。

    Args:
        sx, sy: 起始视口坐标
        ex, ey: 目标视口坐标
        steps:  中间移动步数,越多越平滑。默认 20。
        step_dur: 每步时长(毫秒)。默认 30。

    Returns:
        list: pointer action 序列
    """
    pointer_actions = [
        {"type": "pointerMove", "origin": "viewport", "x": sx, "y": sy, "duration": 0},
        {"type": "pointerDown", "button": 0},
        {"type": "pause", "duration": 120},
    ]
    for i in range(1, steps + 1):
        px = int(sx + (ex - sx) * i / steps)
        py = int(sy + (ey - sy) * i / steps)
        pointer_actions.append(
            {
                "type": "pointerMove",
                "origin": "viewport",
                "x": px,
                "y": py,
                "duration": step_dur,
            }
        )
    pointer_actions.append({"type": "pause", "duration": 120})
    pointer_actions.append({"type": "pointerUp", "button": 0})
    return pointer_actions


def test_drag_hold_release(page: FirefoxPage, results: TestResult):
    """测试7: 原生 BiDi 拖拽(参考 example 35 的 input.performActions 方式)"""
    try:
        start, end = _prepare_drag_scene(page)
        if not start or not end:
            results.record("hold/move_to/release 拖拽", False, "无法获取元素坐标")
            return

        # 使用高层原生 BiDi drag API 替代示例层直接调用 input.performActions。
        page.actions.drag(start, end, duration=720, steps=20).perform()

        page.wait(1)

        result = page.ele("#drag-result").text
        ok = "拖放成功" in str(result)
        results.record("hold/move_to/release 拖拽", ok, f"结果: {result}")
    except Exception as e:
        results.record("hold/move_to/release 拖拽", False, str(e))


def test_actions_drag_to(page: FirefoxPage, results: TestResult):
    """测试8: Actions.drag_to() 便捷拖拽"""
    try:
        start, end = _prepare_drag_scene(page)
        if not start or not end:
            results.record("Actions.drag_to() 便捷拖拽", False, "无法获取元素坐标")
            return

        # 使用 Actions 链式 API 拖拽(内部同样使用原生 BiDi,isTrusted=true)
        page.actions.drag_to(start, end, duration=800, steps=25).perform()

        page.wait(1)

        result = page.ele("#drag-result").text
        ok = "拖放成功" in str(result)
        results.record("Actions.drag_to() 便捷拖拽", ok, f"结果: {result}")
    except Exception as e:
        results.record("Actions.drag_to() 便捷拖拽", False, str(e))


def test_scroll_wheel(page: FirefoxPage, results: TestResult):
    """测试9: 滚轮滚动操作"""
    try:
        page.scroll.to_bottom()
        page.wait(0.5)

        page.actions.scroll(0, -500).perform()
        page.wait(0.5)

        results.record("scroll() 滚轮滚动", True, "滚动到底部+向上滚动")
    except Exception as e:
        results.record("scroll() 滚轮滚动", False, str(e))


def test_hover(page: FirefoxPage, results: TestResult):
    """测试10: 鼠标悬停操作"""
    try:
        page.scroll.to_top()
        page.wait(0.3)
        hover_target = page.ele("#hover-target")
        page.actions.move_to(hover_target).perform()
        page.wait(1)

        result = page.ele("#hover-result").text
        ok = "进入" in str(result)
        results.record("move_to() 鼠标悬停", ok, f"结果: {result}")
    except Exception as e:
        results.record("move_to() 鼠标悬停", False, str(e))


def test_shift_click(page: FirefoxPage, results: TestResult):
    """测试11: Shift+点击组合"""
    try:
        click_btn = page.ele("#click-btn")
        page.actions.key_down(Keys.SHIFT).click(click_btn).key_up(Keys.SHIFT).perform()
        page.wait(0.3)
        results.record("Shift+click 组合操作", True, "执行无异常")
    except Exception as e:
        results.record("Shift+click 组合操作", False, str(e))


def test_action_chain(page: FirefoxPage, results: TestResult):
    """测试12: 连续动作链"""
    try:
        click_btn = page.ele("#click-btn")
        page.actions.move_to(click_btn).wait(0.3).click().wait(0.3).click().perform()
        page.wait(0.5)

        result = page.ele("#click-result").text
        results.record("连续动作链 (move+wait+click)", True, f"结果: {result}")
    except Exception as e:
        results.record("连续动作链 (move+wait+click)", False, str(e))


def test_release_all(page: FirefoxPage, results: TestResult):
    """测试13: release_all() 释放所有动作"""
    try:
        page.actions.release_all()
        results.record("release_all() 释放所有动作", True, "执行无异常")
    except Exception as e:
        results.record("release_all() 释放所有动作", False, str(e))


def test_is_trusted_click(page: FirefoxPage, results: TestResult):
    """测试14: isTrusted 验证 - click"""
    try:
        page.ele("#click-btn").click_self()
        page.wait(0.3)
        trusted = page.is_trusted("click")
        ok = trusted is True
        results.record("isTrusted: click 事件", ok, f"isTrusted={trusted}")
    except Exception as e:
        results.record("isTrusted: click 事件", False, str(e))


def test_is_trusted_keydown(page: FirefoxPage, results: TestResult):
    """测试15: isTrusted 验证 - keydown"""
    try:
        page.ele("#text-input").input("K", clear=False)
        page.wait(0.3)
        trusted = page.is_trusted("keydown")
        ok = trusted is True
        results.record("isTrusted: keydown 事件", ok, f"isTrusted={trusted}")
    except Exception as e:
        results.record("isTrusted: keydown 事件", False, str(e))


def test_is_trusted_mouseenter(page: FirefoxPage, results: TestResult):
    """测试16: isTrusted 验证 - mouseenter"""
    try:
        hover_target = page.ele("#hover-target")
        hover_target.hover()
        page.wait(0.3)
        trusted = page.is_trusted("mouseenter")
        ok = trusted is True
        results.record("isTrusted: mouseenter 事件", ok, f"isTrusted={trusted}")
    except Exception as e:
        results.record("isTrusted: mouseenter 事件", False, str(e))


def test_human_move_click(page: FirefoxPage, results: TestResult):
    """测试17: 拟人化移动+点击"""
    try:
        click_btn = page.ele("#click-btn")
        page.actions.human_move(click_btn).human_click().perform()
        page.wait(0.5)

        result = page.ele("#click-result").text
        results.record("human_move + human_click 拟人点击", True, f"结果: {result}")
    except Exception as e:
        results.record("human_move + human_click 拟人点击", False, str(e))


def test_human_type(page: FirefoxPage, results: TestResult):
    """测试18: 拟人化输入"""
    try:
        input_elem = page.ele("#text-input")
        input_elem.clear()
        page.wait(0.2)
        input_elem.click_self()
        page.actions.human_type("拟人输入").perform()
        page.wait(0.5)

        val = input_elem.value
        ok = "拟人输入" in str(val)
        results.record("human_type() 拟人输入", ok, f"值: {val}")
    except Exception as e:
        results.record("human_type() 拟人输入", False, str(e))


def test_type_with_interval(page: FirefoxPage, results: TestResult):
    """测试19: type() 带间隔输入"""
    try:
        input_elem = page.ele("#text-input")
        input_elem.clear()
        page.wait(0.2)
        input_elem.click_self()
        page.actions.type("间隔输入", interval=50).perform()
        page.wait(0.5)

        val = input_elem.value
        ok = "间隔输入" in str(val)
        results.record("type(interval=50) 间隔输入", ok, f"值: {val}")
    except Exception as e:
        results.record("type(interval=50) 间隔输入", False, str(e))


def test_element_double_click(page: FirefoxPage, results: TestResult):
    """测试20: 元素级 double_click() 方法"""
    try:
        dbl_btn = page.ele("#double-click-btn")
        dbl_btn.double_click()
        page.wait(0.5)

        result = page.ele("#click-result").text
        ok = "双击" in str(result)
        results.record("element.double_click() 元素双击", ok, f"结果: {result}")
    except Exception as e:
        results.record("element.double_click() 元素双击", False, str(e))


def test_element_right_click(page: FirefoxPage, results: TestResult):
    """测试21: 元素级 right_click() 方法"""
    try:
        right_btn = page.ele("#right-click-btn")
        right_btn.right_click()
        page.wait(0.5)

        result = page.ele("#click-result").text
        ok = "右键" in str(result)
        results.record("element.right_click() 元素右击", ok, f"结果: {result}")
    except Exception as e:
        results.record("element.right_click() 元素右击", False, str(e))


def test_element_hover(page: FirefoxPage, results: TestResult):
    """测试22: 元素级 hover() 方法"""
    try:
        # 先移开鼠标
        page.actions.move_to({"x": 0, "y": 0}).perform()
        page.wait(0.3)

        hover_target = page.ele("#hover-target")
        hover_target.hover()
        page.wait(0.5)

        result = page.ele("#hover-result").text
        ok = "进入" in str(result)
        results.record("element.hover() 元素悬停", ok, f"结果: {result}")
    except Exception as e:
        results.record("element.hover() 元素悬停", False, str(e))


def test_element_drag_to(page: FirefoxPage, results: TestResult):
    """测试23: 元素级 drag_to() 方法"""
    try:
        start, end = _prepare_drag_scene(page)
        if not start or not end:
            results.record("element.drag_to() 元素拖拽", False, "无法获取元素坐标")
            return

        # 使用元素级 drag_to() 并传入坐标 dict
        # 传入 dict 时,内部不会再 scrollIntoView,直接用当前视口坐标拖拽
        draggable = page.ele("#draggable")
        draggable.drag_to(end, duration=0.8)
        page.wait(1)

        result = page.ele("#drag-result").text
        ok = "拖放成功" in str(result)
        results.record("element.drag_to() 元素拖拽", ok, f"结果: {result}")
    except Exception as e:
        results.record("element.drag_to() 元素拖拽", False, str(e))


def test_file_upload(page: FirefoxPage, results: TestResult):
    """测试24: 文件上传 (input.setFiles)"""
    try:
        # 创建临时测试文件
        tmp = tempfile.NamedTemporaryFile(
            mode="w", suffix=".txt", delete=False, prefix="ruyipage_test_"
        )
        tmp.write("RuyiPage file upload test")
        tmp.close()

        try:
            file_input = page.ele("#file-input")
            file_input.input(tmp.name)
            page.wait(0.5)

            # 验证文件名
            val = page.run_js(
                "return document.getElementById('file-input').files[0]?.name || ''"
            )
            ok = val and len(str(val)) > 0
            results.record("input.setFiles 文件上传", ok, f"文件名: {val}")
        finally:
            os.unlink(tmp.name)
    except Exception as e:
        results.record("input.setFiles 文件上传", False, str(e))


def test_keyboard_navigation(page: FirefoxPage, results: TestResult):
    """测试25: 键盘导航 (Tab/Shift+Tab)"""
    try:
        text_input = page.ele("#text-input")
        text_input.click_self()
        page.wait(0.2)

        # Tab 到下一个表单元素
        page.actions.press(Keys.TAB).perform()
        page.wait(0.3)

        # 验证焦点转移
        active_tag = page.run_js("return document.activeElement?.id || ''")
        ok = active_tag and active_tag != "text-input"
        results.record("Tab 键盘导航", ok, f"焦点在: {active_tag}")
    except Exception as e:
        results.record("Tab 键盘导航", False, str(e))


def test_relative_move(page: FirefoxPage, results: TestResult):
    """测试26: move() 相对移动"""
    try:
        click_btn = page.ele("#click-btn")
        page.actions.move_to(click_btn).perform()
        old_x = page.actions.curr_x
        old_y = page.actions.curr_y

        page.actions.move(50, 30).perform()
        new_x = page.actions.curr_x
        new_y = page.actions.curr_y

        ok = (new_x == old_x + 50) and (new_y == old_y + 30)
        results.record("move() 相对移动", ok, f"({old_x},{old_y}) → ({new_x},{new_y})")
    except Exception as e:
        results.record("move() 相对移动", False, str(e))


def test_backward_compat_db_click(page: FirefoxPage, results: TestResult):
    """测试27: 向后兼容 db_click() 别名"""
    try:
        dbl_btn = page.ele("#double-click-btn")
        page.actions.db_click(dbl_btn).perform()
        page.wait(0.5)

        result = page.ele("#click-result").text
        ok = "双击" in str(result)
        results.record("db_click() 向后兼容别名", ok, f"结果: {result}")
    except Exception as e:
        results.record("db_click() 向后兼容别名", False, str(e))


def test_backward_compat_r_click(page: FirefoxPage, results: TestResult):
    """测试28: 向后兼容 r_click() 别名"""
    try:
        right_btn = page.ele("#right-click-btn")
        page.actions.r_click(right_btn).perform()
        page.wait(0.5)

        result = page.ele("#click-result").text
        ok = "右键" in str(result)
        results.record("r_click() 向后兼容别名", ok, f"结果: {result}")
    except Exception as e:
        results.record("r_click() 向后兼容别名", False, str(e))


def test_multi_combo_keys(page: FirefoxPage, results: TestResult):
    """测试29: 多修饰键 combo (Ctrl+Shift+组合)"""
    try:
        input_elem = page.ele("#text-input")
        input_elem.input("hello world", clear=True)
        page.wait(0.2)

        # Ctrl+A全选后再输入新内容
        input_elem.click_self()
        page.actions.combo(Keys.CTRL, "a").perform()
        page.wait(0.1)
        page.actions.type("replaced").perform()
        page.wait(0.3)

        val = input_elem.value
        ok = str(val) == "replaced"
        results.record("多步combo Ctrl+A → 输入替换", ok, f"值: {val}")
    except Exception as e:
        results.record("多步combo Ctrl+A → 输入替换", False, str(e))


def test_scroll_on_element(page: FirefoxPage, results: TestResult):
    """测试30: 在指定元素上滚动"""
    try:
        scroll_container = page.ele("#scroll-container")
        page.actions.scroll(0, 300, on_ele=scroll_container).perform()
        page.wait(0.5)

        # 读取容器滚动位置
        scroll_top = page.run_js(
            "return document.getElementById('scroll-container').scrollTop"
        )
        ok = scroll_top and int(scroll_top) > 0
        results.record("scroll(on_ele) 元素内滚动", ok, f"scrollTop={scroll_top}")
    except Exception as e:
        results.record("scroll(on_ele) 元素内滚动", False, str(e))


def test_touch_tap(page: FirefoxPage, results: TestResult):
    """测试31: touch.tap() 单指点击"""
    try:
        page.get(page.url)
        page.wait(0.8)
        click_btn = page.ele("#click-btn")
        page.touch.tap(click_btn).perform()
        page.wait(0.5)
        result = page.ele("#click-result").text
        ok = "点击次数" in str(result)
        results.record("touch.tap() 单指点击", ok, f"结果: {result}")
    except Exception as e:
        results.record("touch.tap() 单指点击", False, str(e))


def test_touch_long_press(page: FirefoxPage, results: TestResult):
    """测试32: touch.long_press() 长按"""
    try:
        hover_target = page.ele("#hover-target")
        page.touch.long_press(hover_target, duration=600).perform()
        page.wait(0.4)
        results.record("touch.long_press() 长按", True, "执行无异常")
    except Exception as e:
        results.record("touch.long_press() 长按", False, str(e))


# ═══════════════════════════════════════════════════════════════════
#  主测试入口
# ═══════════════════════════════════════════════════════════════════


def test_advanced_input():
    """运行所有高级输入操作测试"""
    print("=" * 70)
    print("测试20: 高级输入操作(综合测试)")
    print("=" * 70)

    opts = FirefoxOptions()
    opts.headless(False)
    page = FirefoxPage(opts)
    results = TestResult()

    try:
        # 加载测试页面
        test_page = os.path.join(
            os.path.dirname(__file__), "test_pages", "test_page.html"
        )
        test_url = "file:///" + os.path.abspath(test_page).replace("\\", "/")
        page.get(test_url)
        page.wait(1)

        # ── 键盘操作测试 ──────────────────────────────────────
        print("\n--- 键盘操作 ---")
        test_combo_ctrl_a(page, results)
        test_combo_copy_paste(page, results)
        test_press_key(page, results)

        # ── 鼠标点击测试 ──────────────────────────────────────
        print("\n--- 鼠标点击操作 ---")
        test_double_click(page, results)
        test_right_click(page, results)
        test_middle_click(page, results)

        # ── 拖拽测试 ──────────────────────────────────────────
        print("\n--- 拖拽操作 ---")
        test_drag_hold_release(page, results)
        test_actions_drag_to(page, results)

        # ── 滚轮测试 ──────────────────────────────────────────
        print("\n--- 滚轮操作 ---")
        test_scroll_wheel(page, results)
        test_scroll_on_element(page, results)

        # ── 悬停测试 ──────────────────────────────────────────
        print("\n--- 悬停操作 ---")
        test_hover(page, results)

        # ── 组合操作测试 ──────────────────────────────────────
        print("\n--- 组合操作 ---")
        test_shift_click(page, results)
        test_action_chain(page, results)
        test_release_all(page, results)

        # ── isTrusted 验证 ────────────────────────────────────
        print("\n--- isTrusted 验证 ---")
        test_is_trusted_click(page, results)
        test_is_trusted_keydown(page, results)
        test_is_trusted_mouseenter(page, results)

        # ── 拟人化操作 ────────────────────────────────────────
        print("\n--- 拟人化操作 ---")
        test_human_move_click(page, results)
        test_human_type(page, results)

        # ── 触摸操作 ──────────────────────────────────────────
        print("\n--- 触摸操作 ---")
        test_touch_tap(page, results)
        test_touch_long_press(page, results)

        # ── 输入操作 ──────────────────────────────────────────
        print("\n--- 输入操作 ---")
        test_type_with_interval(page, results)

        # ── 元素级操作 ────────────────────────────────────────
        print("\n--- 元素级操作 ---")
        test_element_double_click(page, results)
        test_element_right_click(page, results)
        test_element_hover(page, results)
        test_element_drag_to(page, results)

        # ── 文件上传 ──────────────────────────────────────────
        print("\n--- 文件上传 ---")
        test_file_upload(page, results)

        # ── 键盘导航 ──────────────────────────────────────────
        print("\n--- 键盘导航 ---")
        test_keyboard_navigation(page, results)

        # ── 相对移动 ──────────────────────────────────────────
        print("\n--- 其他操作 ---")
        test_relative_move(page, results)

        # ── 向后兼容性 ────────────────────────────────────────
        print("\n--- 向后兼容性 ---")
        test_backward_compat_db_click(page, results)
        test_backward_compat_r_click(page, results)

        # ── 多步组合 ──────────────────────────────────────────
        print("\n--- 复杂多步操作 ---")
        test_multi_combo_keys(page, results)

    except Exception as e:
        print(f"\n✗ 测试过程中发生意外错误: {e}")
        traceback.print_exc()
    finally:
        # 输出汇总表格
        all_passed = results.summary()
        page.wait(2)
        page.quit()
        return all_passed


if __name__ == "__main__":
    success = test_advanced_input()
    sys.exit(0 if success else 1)

移动端模拟访问 Google

examples/21_mobile_google_emulation.py

把移动端预设能力放到真实站点中验证,是 mobile 页的主示例。

GitHub 仓库 查看仓库源码 page.emulation.apply_mobile_presetpage.emulation.set_touch_enabledpage.getpage.run_jspage.title
  • 使用 iPhone 风格 UA 和视口访问 Google
  • 设置 locale 与 timezone
  • 检查 UA、innerWidth、devicePixelRatio
  • 验证移动页面是否正常加载

源码文件:app/content/ruyipage_examples/21_mobile_google_emulation.py · 仓库:21_mobile_google_emulation.py

python 注释版
# =============================
# RuyiPage 示例注释版
# 标题: 移动端模拟访问 Google
# 说明: 把移动端预设能力放到真实站点中验证,是 mobile 页的主示例。
# 你会学到:
#   1. 使用 iPhone 风格 UA 和视口访问 Google
#   2. 设置 locale 与 timezone
#   3. 检查 UA、innerWidth、devicePixelRatio
#   4. 验证移动页面是否正常加载
# 相关 API: page.emulation.apply_mobile_preset, page.emulation.set_touch_enabled, page.get, page.run_js, page.title
# =============================

# -*- coding: utf-8 -*-
"""示例21-Mobile: 移动端模拟访问 Google

目标:
1) 使用 RuyiPage 的移动端模拟接口
2) 访问 Google 首页
3) 验证 UA / 视口 / 页面标题

说明:
- 当前 Firefox 对 emulation.setTouchOverride 未实现,因此触摸支持可能显示为 False。
- 本示例重点验证“移动端参数模拟 + 页面访问”整体链路。
"""

import io
import sys


if sys.platform == "win32":
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")


from ruyipage import FirefoxOptions, FirefoxPage


def test_mobile_google_emulation():
    print("=" * 70)
    print("测试: 移动端模拟访问 Google")
    print("=" * 70)

    opts = FirefoxOptions()
    opts.headless(False)
    page = FirefoxPage(opts)

    try:
        iphone_ua = (
            "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) "
            "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 "
            "Mobile/15E148 Safari/604.1"
        )

        print("\n1. 应用移动端模拟参数...")
        support = page.emulation.apply_mobile_preset(
            iphone_ua,
            width=390,
            height=844,
            device_pixel_ratio=3.0,
            orientation_type="portrait-primary",
            angle=0,
            locale="en-US",
            timezone_id="America/Los_Angeles",
            touch=False,
        )
        print(f"   基础支持情况: {support}")

        touch_results = {}
        for scope in ("context", "global", "user_context"):
            try:
                ok = page.emulation.set_touch_enabled(
                    True, max_touch_points=5, scope=scope
                )
            except Exception as e:
                ok = False
                print(f"   触摸作用域 {scope} 异常: {e}")
            page.refresh()
            touch_points = page.run_js("navigator.maxTouchPoints || 0")
            touch_results[scope] = {"supported": ok, "maxTouchPoints": touch_points}
        print(f"   触摸作用域结果: {touch_results}")

        print("\n2. 打开 Google 首页...")
        page.get("https://www.google.com/ncr")
        page.wait(2)

        print("\n3. 读取移动端环境信息...")
        ua = page.run_js("navigator.userAgent")
        width = page.run_js("window.innerWidth")
        height = page.run_js("window.innerHeight")
        dpr = page.run_js("window.devicePixelRatio")
        language = page.run_js("navigator.language")
        touch_points = page.run_js("navigator.maxTouchPoints || 0")
        title = page.title

        print(f"   页面标题: {title}")
        print(f"   UA: {ua}")
        print(f"   视口: {width}x{height}")
        print(f"   DPR: {dpr}")
        print(f"   语言: {language}")
        print(f"   maxTouchPoints: {touch_points}")

        ua_ok = "iPhone" in str(ua)
        viewport_ok = int(width) <= 500
        title_ok = "Google" in str(title)

        print("\n4. 结果判断...")
        print(f"   UA 命中移动端: {ua_ok}")
        print(f"   视口为移动端宽度: {viewport_ok}")
        print(f"   Google 页面打开成功: {title_ok}")

        if not (ua_ok and viewport_ok and title_ok):
            raise RuntimeError("移动端模拟访问 Google 的关键检查未全部通过")

        print("\n" + "=" * 70)
        print("✓ 移动端模拟访问 Google 测试通过")
        print("=" * 70)

    except Exception as e:
        print(f"\n✗ 测试失败: {e}")
        import traceback

        traceback.print_exc()
    finally:
        page.wait(1)
        page.quit()


if __name__ == "__main__":
    test_mobile_google_emulation()