page.touch.tap
返回: Nonepage.touch.tap(ele_or_xy)触摸点击,等价于手指轻触屏幕。
参数说明
| 参数 | 说明 |
|---|---|
| ele_or_xy | 目标元素或 (x, y) 坐标。 |
常见坑
- 需先 set_touch_enabled(True) 或 apply_mobile_preset。
相关示例
触摸输入模块提供 tap、长按和滑动手势,配合移动端预设可模拟真实手机操作。
page.touch.tap(ele_or_xy)触摸点击,等价于手指轻触屏幕。
| 参数 | 说明 |
|---|---|
| ele_or_xy | 目标元素或 (x, y) 坐标。 |
page.touch.long_press(ele_or_xy, duration=1000)长按手势,duration 单位毫秒。
| 参数 | 说明 |
|---|---|
| ele_or_xy | 目标元素或坐标。 |
| duration | 长按持续时间(毫秒),默认 1000。 |
page.touch.swipe(start, end)滑动手势,从起始位置滑到终点。
| 参数 | 说明 |
|---|---|
| start | 起始元素或 (x, y) 坐标。 |
| end | 终点元素或 (x, y) 坐标。 |
page.emulation.set_touch_enabled(enabled)启用或禁用触摸输入能力。
| 参数 | 说明 |
|---|---|
| enabled | True 启用 / False 禁用。 |
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()输入能力大全示例,是 actions、touch、uploads 和 isTrusted 的主参考。
源码文件:app/content/ruyipage_examples/20_advanced_input.py · 仓库:20_advanced_input.py
# =============================
# 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)
把移动端预设能力放到真实站点中验证,是 mobile 页的主示例。
源码文件:app/content/ruyipage_examples/21_mobile_google_emulation.py · 仓库:21_mobile_google_emulation.py
# =============================
# 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()