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

RuyiPage 示例中心

这里不是简单的文件清单,而是将 RuyiPage 仓库中的 examples 渲染成案例卡。每个例子都带有文件名、分类、难度、不同用法、涉及 API、适用场景与相关页面,并提供 GitHub 源码入口,帮助你从“例子”回到“文档”,也从“文档”回到“例子”。

指纹浏览器快速开始

quickstart_fingerprint_browser.py
GitHub 仓库 待同步 查看 GitHub 源码
browser advanced
一句话定位

演示如何把 Firefox 指纹浏览器、指纹文件、地理位置、语言和请求头一起接入 RuyiPage。

涉及 API
FirefoxOptionsopts.set_browser_pathopts.set_fpfilepage.emulation.set_geolocationpage.emulation.set_timezonepage.emulation.set_localepage.network.set_extra_headerspage.emulation.set_screen_size
展示的不同用法
  • 加载 Firefox 指纹浏览器可执行文件
  • 通过 fpfile 复用指纹配置
  • 叠加地理位置、时区和语言环境
  • 为请求增加语言请求头
  • 访问指纹检测站点验证效果
适用场景

适合高风控、跨地区语言环境模拟和指纹浏览器接入文档。

推荐阅读
quickstart-cloudfareemulation-21advanced-network-18

隐私模式快速开始

quickstart_private_mode.py
GitHub 仓库 待同步 查看 GitHub 源码
browser beginner
一句话定位

展示 Firefox 私密浏览模式的两种启动方式,以及它与临时 profile 的区别。

涉及 API
FirefoxOptionsopts.private_modeFirefoxPagelaunch(private=True)page.getpage.quit
展示的不同用法
  • 通过 FirefoxOptions 开启私密浏览
  • 通过 launch(private=True) 快速启动
  • 理解私密模式与临时用户目录的区别
  • 验证私密模式下的页面访问流程
适用场景

适合新手理解浏览器启动模式、临时会话和隐私场景。

推荐阅读
quickstart-01basic-navigation-01

截图功能:整页、元素、base64、bytes

examples/06_screenshot.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser intermediate
一句话定位

覆盖最常用的页面与元素截图输出能力。

涉及 API
page.screenshotele.screenshotpage.scroll.to_seepage.scroll.to_bottom
展示的不同用法
  • 整页截图并保存到文件
  • 对单个元素或区域截图
  • 滚动到目标后截图
  • 获取截图的 base64 和 bytes 数据
适用场景

适合 screenshots 页面和综合示例的关联阅读。

相关文档页面
推荐阅读
pdf-19comprehensive-15
完整代码

来源:app/content/ruyipage_examples/06_screenshot.py · 仓库文件:06_screenshot.py

python 注释版
# =============================
# RuyiPage 示例注释版
# 标题: 截图功能:整页、元素、base64、bytes
# 说明: 覆盖最常用的页面与元素截图输出能力。
# 你会学到:
#   1. 整页截图并保存到文件
#   2. 对单个元素或区域截图
#   3. 滚动到目标后截图
#   4. 获取截图的 base64 和 bytes 数据
# 相关 API: page.screenshot, ele.screenshot, page.scroll.to_see, page.scroll.to_bottom
# =============================

# -*- coding: utf-8 -*-
"""
示例6: 截图功能
测试功能:
- 整页截图
- 元素截图
- 保存为文件
- 获取base64
- 获取bytes
"""

import os
import sys
import io

# 设置控制台输出编码为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

def test_screenshot():
    """测试截图功能"""
    print("=" * 60)
    print("测试6: 截图功能")
    print("=" * 60)

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

    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)

        # 创建输出目录
        output_dir = os.path.join(os.path.dirname(__file__), 'output', 'screenshots')
        os.makedirs(output_dir, exist_ok=True)

        # 1. 整页截图
        print("\n1. 整页截图:")
        full_path = os.path.join(output_dir, 'full_page.png')
        page.screenshot(full_path)
        print(f"   ✓ 整页截图已保存: {full_path}")

        # 2. 元素截图
        print("\n2. 元素截图:")
        title = page.ele('#main-title')
        title_path = os.path.join(output_dir, 'title_element.png')
        title.screenshot(title_path)
        print(f"   ✓ 标题元素截图已保存: {title_path}")

        # 3. 表单区域截图
        print("\n3. 表单区域截图:")
        form_section = page.ele('#form-section')
        form_path = os.path.join(output_dir, 'form_section.png')
        form_section.screenshot(form_path)
        print(f"   ✓ 表单区域截图已保存: {form_path}")

        # 4. 按钮截图
        print("\n4. 按钮截图:")
        btn = page.ele('#click-btn')
        # 先滚动到按钮位置确保可见
        page.scroll.to_see(btn)
        page.wait(0.5)
        btn_path = os.path.join(output_dir, 'button.png')
        try:
            btn.screenshot(btn_path)
            print(f"   ✓ 按钮截图已保存: {btn_path}")
        except Exception as e:
            print(f"   ⚠ 按钮截图跳过(元素尺寸问题): {str(e)[:50]}")

        # 5. 获取截图的base64
        print("\n5. 获取截图base64:")
        base64_data = page.screenshot(as_base64=True)
        print(f"   ✓ Base64数据长度: {len(base64_data)} 字符")

        # 6. 获取截图的bytes
        print("\n6. 获取截图bytes:")
        bytes_data = page.screenshot(as_bytes=True)
        print(f"   ✓ Bytes数据长度: {len(bytes_data)} 字节")

        # 7. 元素base64截图
        print("\n7. 元素base64截图:")
        try:
            # 使用表单区域元素,它更稳定
            form_elem = page.ele('#form-section')
            elem_base64 = form_elem.screenshot(as_base64=True)
            print(f"   ✓ 元素Base64数据长度: {len(elem_base64)} 字符")
        except Exception as e:
            print(f"   ⚠ 元素base64截图跳过(元素尺寸问题): {str(e)[:50]}")

        # 8. 滚动后截图
        print("\n8. 滚动后截图:")
        page.scroll.to_bottom()
        page.wait(0.5)
        bottom_path = os.path.join(output_dir, 'page_bottom.png')
        page.screenshot(bottom_path)
        print(f"   ✓ 页面底部截图已保存: {bottom_path}")

        # 9. 表格截图
        print("\n9. 表格截图:")
        table = page.ele('#data-table')
        table_path = os.path.join(output_dir, 'table.png')
        try:
            page.scroll.to_see(table)
            page.wait(0.5)
            table.screenshot(table_path)
            print(f"   ✓ 表格截图已保存: {table_path}")
        except Exception as e:
            print(f"   ⚠ 表格截图跳过(元素尺寸问题): {str(e)[:50]}")

        print("\n" + "=" * 60)
        print("✓ 所有截图测试通过!")
        print(f"截图保存在: {output_dir}")
        print("=" * 60)

    except Exception as e:
        print(f"\n✗ 测试失败: {e}")
        import traceback
        traceback.print_exc()
    finally:
        page.wait(2)
        page.quit()

if __name__ == '__main__':
    test_screenshot()

Cookie 管理:读取、设置、过滤、删除

examples/08_cookies.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser intermediate
一句话定位

围绕登录态和会话管理的核心示例。

涉及 API
page.get_cookiespage.set_cookiespage.get_cookies_filteredpage.delete_cookiespage.browser.cookies
展示的不同用法
  • 读取服务端下发 Cookie
  • 通过 API 设置 Cookie
  • 按 name 等条件过滤 Cookie
  • 删除指定 Cookie 或清空全部 Cookie
  • 读取浏览器级 Cookie 视图
适用场景

适合 cookies 与 browser 页面。

相关文档页面
推荐阅读
quickstart-cloudfaretabs-09
完整代码

来源:app/content/ruyipage_examples/08_cookies.py · 仓库文件:08_cookies.py

python 注释版
# =============================
# RuyiPage 示例注释版
# 标题: Cookie 管理:读取、设置、过滤、删除
# 说明: 围绕登录态和会话管理的核心示例。
# 你会学到:
#   1. 读取服务端下发 Cookie
#   2. 通过 API 设置 Cookie
#   3. 按 name 等条件过滤 Cookie
#   4. 删除指定 Cookie 或清空全部 Cookie
#   5. 读取浏览器级 Cookie 视图
# 相关 API: page.get_cookies, page.set_cookies, page.get_cookies_filtered, page.delete_cookies, page.browser.cookies
# =============================

# -*- coding: utf-8 -*-
"""
示例8: Cookie管理
测试功能:
- 获取Cookie
- 设置Cookie
- 删除Cookie
- 清空所有Cookie
"""

import os
import sys
import io
import time

# 设置控制台输出编码为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__), ".."))
sys.path.insert(0, os.path.dirname(__file__))

from ruyipage import FirefoxPage, FirefoxOptions
from test_server import TestServer


def test_cookies():
    """测试Cookie管理功能"""
    print("=" * 60)
    print("测试8: Cookie管理")
    print("=" * 60)

    # 启动测试服务器
    server = TestServer(port=8888)
    server.start()

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

    try:
        # 访问测试服务器的Cookie设置页面
        print("\n访问测试服务器...")
        page.get(server.get_url("/set-cookie"))
        page.wait(2)

        # 1. 获取服务器设置的Cookie
        print("\n1. 获取服务器设置的Cookie:")
        all_cookies = page.get_cookies(all_info=True)
        print(f"   共有 {len(all_cookies)} 个Cookie")
        for cookie in all_cookies:
            name = cookie.name
            value = cookie.value
            print(f"   - {name}: {value}")

        # 2. 通过API设置Cookie
        print("\n2. 通过API设置Cookie:")
        page.set_cookies(
            {
                "name": "api_cookie",
                "value": "api_value",
                "domain": "127.0.0.1",
                "path": "/",
            }
        )
        page.set_cookies(
            {"name": "user_id", "value": "12345", "domain": "127.0.0.1", "path": "/"}
        )
        print(f"   ✓ 已通过API设置2个Cookie")

        # 3. 验证Cookie已设置
        print("\n3. 验证Cookie已设置:")
        page.refresh()
        page.wait(1)
        all_cookies = page.get_cookies(all_info=True)
        print(f"   刷新后共有 {len(all_cookies)} 个Cookie")

        # 转换为字典方便查找
        cookies_dict = {}
        for c in all_cookies:
            cookies_dict[c.name] = c.value

        if "api_cookie" in cookies_dict:
            print(f"   ✓ api_cookie = {cookies_dict['api_cookie']}")
        if "user_id" in cookies_dict:
            print(f"   ✓ user_id = {cookies_dict['user_id']}")

        # 4. 设置带过期时间的Cookie
        print("\n4. 设置带过期时间的Cookie:")
        expire_time = int(time.time()) + 3600  # 1小时后过期
        page.set_cookies(
            {
                "name": "expire_cookie",
                "value": "will_expire",
                "domain": "127.0.0.1",
                "path": "/",
                "expiry": expire_time,
            }
        )
        print(f"   ✓ 已设置带过期时间的Cookie(1小时后过期)")

        # 5. 设置HttpOnly和Secure Cookie
        print("\n5. 设置HttpOnly和Secure Cookie:")
        page.set_cookies(
            {
                "name": "secure_cookie",
                "value": "secure_value",
                "domain": "127.0.0.1",
                "path": "/",
                "httpOnly": True,
                "secure": False,  # HTTP不能用secure=True
            }
        )
        print(f"   ✓ 已设置HttpOnly Cookie")

        # 6. 获取特定Cookie
        print("\n6. 获取特定Cookie:")
        all_cookies = page.get_cookies(all_info=True)
        cookies_dict = {}
        for c in all_cookies:
            cookies_dict[c.name] = c.value

        if "user_id" in cookies_dict:
            print(f"   user_id = {cookies_dict['user_id']}")
        if "expire_cookie" in cookies_dict:
            print(f"   expire_cookie = {cookies_dict['expire_cookie']}")

        # 6.1 简单 cookies 属性
        print("\n6.1 当前页面 cookies 属性:")
        simple_cookies = page.cookies
        print(f"   page.cookies 数量: {len(simple_cookies)}")

        # 6.2 setter 风格 Cookie API
        print("\n6.2 通过 page.set.cookies 设置Cookie:")
        page.set.cookies(
            {
                "name": "setter_cookie",
                "value": "setter_value",
                "domain": "127.0.0.1",
                "path": "/",
            }
        )
        page.wait(0.5)
        setter_cookies = {c.name: c.value for c in page.get_cookies()}
        print(f"   setter_cookie = {setter_cookies.get('setter_cookie')}")

        # 6.3 通过高层过滤 API 读取 Cookie
        print("\n6.3 通过高层过滤 API 读取Cookie:")
        filtered_cookies = page.get_cookies_filtered(name="user_id", all_info=True)
        print(f"   过滤结果数量: {len(filtered_cookies)}")
        if filtered_cookies:
            value = filtered_cookies[0].value
            print(f"   user_id(filtered) = {value}")

        # 7. 删除特定Cookie
        print("\n7. 删除特定Cookie:")
        page.delete_cookies(name="api_cookie")
        print(f"   ✓ 已删除 api_cookie")

        # 验证删除
        page.wait(0.5)
        all_cookies = page.get_cookies(all_info=True)
        cookies_dict = {}
        for c in all_cookies:
            cookies_dict[c.name] = c.value

        if "api_cookie" not in cookies_dict:
            print(f"   ✓ 确认 api_cookie 已被删除")
        else:
            print(f"   ⚠ api_cookie 仍然存在")

        # 7.1 通过公开删除接口删除 setter_cookie
        print("\n7.1 删除 setter_cookie:")
        page.delete_cookies(name="setter_cookie", domain="127.0.0.1")
        page.wait(0.5)
        cookies_after_remove = {c.name: c.value for c in page.get_cookies()}
        print(f"   setter_cookie 是否存在: {'setter_cookie' in cookies_after_remove}")

        # 8. 访问API验证Cookie发送
        print("\n8. 访问API验证Cookie发送:")
        page.get(server.get_url("/get-cookie"))
        page.wait(1)
        body_text = page.ele("tag:body").text
        print(f"   服务器收到的Cookie: {body_text[:100]}")

        # 9. 清空所有Cookie
        print("\n9. 清空所有Cookie:")
        page.delete_cookies()
        print(f"   ✓ 已清空所有Cookie")

        # 验证清空
        all_cookies = page.get_cookies()
        print(f"   清空后Cookie数量: {len(all_cookies)}")

        # 9.1 浏览器级 Cookie 读取
        print("\n9.1 浏览器级 cookies 读取:")
        browser_cookies = page.browser.cookies(all_info=False)
        print(f"   browser.cookies 数量: {len(browser_cookies)}")

        # 10. 重新设置并测试
        print("\n10. 重新设置Cookie并测试:")
        page.set_cookies(
            {
                "name": "final_cookie",
                "value": "final_value",
                "domain": "127.0.0.1",
                "path": "/",
            }
        )
        page.refresh()
        page.wait(1)

        all_cookies = page.get_cookies(all_info=True)
        cookies_dict = {}
        for c in all_cookies:
            cookies_dict[c.name] = c.value

        if "final_cookie" in cookies_dict:
            print(f"   ✓ final_cookie = {cookies_dict['final_cookie']}")
            print(f"   ✓ Cookie持久性测试通过")

        print("\n" + "=" * 60)
        print("✓ 所有Cookie管理测试通过!")
        print("=" * 60)

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

        traceback.print_exc()
    finally:
        page.wait(2)
        page.quit()
        server.stop()


if __name__ == "__main__":
    test_cookies()

标签页管理:新建、切换、查找、关闭、后台激活

examples/09_tabs.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser intermediate
一句话定位

展示 tab 作为日常页面操作的重要组织单位。

涉及 API
page.new_tabpage.tabs_countpage.tab_idspage.get_tabpage.get_tabspage.latest_tabpage.close_other_tabspage.browser.activate_tabpage.contexts.get_tree
展示的不同用法
  • 新建标签页并加载地址
  • 通过标题、索引等方式查找 tab
  • 获取所有标签页 ID
  • 激活后台标签页
  • 关闭单个标签或保留当前标签关闭其他标签
  • 观察 browsing context 树结构
适用场景

适合 tabs、browser 和 browsing-context 页面。

相关文档页面
推荐阅读
window-16page-01
完整代码

来源:app/content/ruyipage_examples/09_tabs.py · 仓库文件:09_tabs.py

python 注释版
# =============================
# RuyiPage 示例注释版
# 标题: 标签页管理:新建、切换、查找、关闭、后台激活
# 说明: 展示 tab 作为日常页面操作的重要组织单位。
# 你会学到:
#   1. 新建标签页并加载地址
#   2. 通过标题、索引等方式查找 tab
#   3. 获取所有标签页 ID
#   4. 激活后台标签页
#   5. 关闭单个标签或保留当前标签关闭其他标签
# 相关 API: page.new_tab, page.tabs_count, page.tab_ids, page.get_tab, page.get_tabs, page.latest_tab, page.close_other_tabs, page.browser.activate_tab
# =============================

# -*- coding: utf-8 -*-
"""
示例9: 标签页管理
测试功能:
- 新建标签页
- 切换标签页
- 关闭标签页
- 获取标签页列表
- 标签页操作
"""

import os
import sys
import io

# 设置控制台输出编码为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


def test_tabs():
    """测试标签页管理功能"""
    print("=" * 60)
    print("测试9: 标签页管理")
    print("=" * 60)

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

    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)

        # 1. 获取当前标签页数量
        print("\n1. 获取当前标签页数量:")
        print(f"   标签页数量: {page.tabs_count}")

        tab2_url = "data:text/html,<html><head><title>Example Tab</title></head><body>tab2</body></html>"
        tab3_url = "data:text/html,<html><head><title>Wikipedia Tab</title></head><body>tab3</body></html>"

        # 2. 新建标签页
        print("\n2. 新建标签页:")
        tab2 = page.new_tab(tab2_url)
        page.wait(2)
        print(f"   ✓ 新标签页已创建")
        print(f"   新标签页标题: {tab2.title}")
        print(f"   当前标签页数量: {page.tabs_count}")

        # 3. 再新建一个标签页
        print("\n3. 再新建一个标签页:")
        tab3 = page.new_tab(tab3_url)
        page.wait(2)
        print(f"   ✓ 第三个标签页已创建")
        print(f"   标签页标题: {tab3.title}")
        print(f"   当前标签页数量: {page.tabs_count}")

        # 4. 获取所有标签页ID
        print("\n4. 获取所有标签页ID:")
        tab_ids = page.tab_ids
        print(f"   标签页ID列表: {tab_ids}")

        print("   当前页面 tab_id: {}".format(page.tab_id))

        # 5. 通过序号获取标签页
        print("\n5. 通过序号获取标签页:")
        first_tab = page.get_tab(1)
        print(f"   第1个标签页标题: {first_tab.title}")

        second_tab = page.get_tab(2)
        print(f"   第2个标签页标题: {second_tab.title}")

        # 6. 切换到第一个标签页
        print("\n6. 切换到第一个标签页:")
        first_tab.get(test_url)
        page.wait(1)
        print(f"   当前标题: {first_tab.title}")

        # 7. 获取最新的标签页
        print("\n7. 获取最新的标签页:")
        latest = page.latest_tab
        print(f"   最新标签页标题: {latest.title}")

        # 8. 通过标题查找标签页
        print("\n8. 通过标题查找标签页:")
        example_tab = page.get_tab(title="Example")
        if example_tab:
            print(f"   找到标签页: {example_tab.title}")
        else:
            print(f"   未找到包含'Example'的标签页")

        # 9. 通过URL查找标签页
        print("\n9. 通过URL查找标签页:")
        wiki_tab = page.get_tab(title="Wikipedia")
        if wiki_tab:
            print(f"   找到标签页: {wiki_tab.title}")
        else:
            print(f"   未找到包含'wikipedia'的标签页")

        # 10. 获取所有匹配的标签页
        print("\n10. 获取所有标签页:")
        all_tabs = page.get_tabs()
        print(f"   共有 {len(all_tabs)} 个标签页")
        for i, tab in enumerate(all_tabs, 1):
            print(f"   标签页{i}: {tab.title[:50]}")

        # 10.1 获取窗口句柄信息
        print("\n10.1 获取客户端窗口信息:")
        window_handles = page.browser.window_handles
        print(f"   clientWindows 数量: {len(window_handles)}")
        if window_handles:
            first_window = window_handles[0]
            print(f"   首个窗口状态: {first_window.get('state')}")

        # 10.2 读取 browsing context 树
        print("\n10.2 获取 browsing context 树:")
        tree = page.contexts.get_tree()
        contexts = tree.contexts
        print(f"   顶层 context 数量: {len(contexts)}")
        if contexts:
            print(f"   第一个 context: {contexts[0].context}")

        # 11. 关闭一个标签页
        print("\n11. 关闭第二个标签页:")
        second_tab.close()
        page.wait(1)
        print(f"   ✓ 标签页已关闭")
        print(f"   剩余标签页数量: {page.tabs_count}")

        # 12. 关闭其他标签页(保留第一个)
        print("\n12. 关闭其他标签页:")
        page.close_other_tabs(first_tab)
        page.wait(1)
        print(f"   ✓ 其他标签页已关闭")
        print(f"   剩余标签页数量: {page.tabs_count}")

        # 13. 新建后台标签页
        print("\n13. 新建后台标签页:")
        bg_tab = page.new_tab(tab2_url, background=True)
        page.wait(1)
        print(f"   ✓ 后台标签页已创建")
        print(f"   当前标签页数量: {page.tabs_count}")

        # 14. 激活标签页
        print("\n14. 激活后台标签页:")
        page.browser.activate_tab(bg_tab)
        page.wait(1)
        print(f"   ✓ 标签页已激活")

        # 15. 通过高层 context API 创建后台标签页
        print("\n15. 通过高层context API创建后台标签页:")
        bidi_tab_id = page.contexts.create_tab(
            background=True,
            reference_context=page.tab_id,
        )
        page.wait(1)
        print(f"   高层新标签页ID: {bidi_tab_id}")
        print(f"   当前标签页数量: {page.tabs_count}")

        # 清理纯 BiDi 新建的标签页,避免影响退出顺序
        bidi_tab = page.get_tab(bidi_tab_id)
        if bidi_tab:
            bidi_tab.close()
            page.wait(1)
            print("   ✓ 纯BiDi标签页已关闭")

        print("\n" + "=" * 60)
        print("✓ 所有标签页管理测试通过!")
        print("=" * 60)

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

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


if __name__ == "__main__":
    test_tabs()

窗口管理:尺寸、位置、状态流与标签切换

examples/16_window_management.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser intermediate
一句话定位

系统覆盖 window 与 viewport 的浏览器级操作。

涉及 API
page.rect.window_sizepage.rect.viewport_sizepage.rect.window_locationpage.rect.page_sizepage.window.maximizepage.window.minimizepage.window.fullscreenpage.window.set_sizepage.window.set_positionpage.window.centerpage.new_tab
展示的不同用法
  • 读取窗口和视口尺寸
  • 调整窗口大小与位置
  • 切换最大化、最小化、全屏等状态
  • 多标签创建和激活切换
适用场景

适合 window 与 browser 页面。

相关文档页面
推荐阅读
tabs-09browser-user-context-25
完整代码

来源:app/content/ruyipage_examples/16_window_management.py · 仓库文件:16_window_management.py

python 注释版
# =============================
# RuyiPage 示例注释版
# 标题: 窗口管理:尺寸、位置、状态流与标签切换
# 说明: 系统覆盖 window 与 viewport 的浏览器级操作。
# 你会学到:
#   1. 读取窗口和视口尺寸
#   2. 调整窗口大小与位置
#   3. 切换最大化、最小化、全屏等状态
#   4. 多标签创建和激活切换
# 相关 API: page.rect.window_size, page.rect.viewport_size, page.rect.window_location, page.rect.page_size, page.window.maximize, page.window.minimize, page.window.fullscreen, page.window.set_size
# =============================

# -*- coding: utf-8 -*-
"""示例16: 浏览器窗口管理(完整场景)

测试覆盖:
1) 窗口/视口/页面尺寸与位置
2) 窗口状态流:normal -> maximize -> minimize -> fullscreen -> normal
3) 设置窗口大小、位置、居中
4) 多标签创建、激活、切换、关闭
"""

import os
import sys
import io

# 设置控制台输出编码为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


def test_window_management():
    """测试浏览器窗口管理功能"""
    print("=" * 60)
    print("测试16: 浏览器窗口管理")
    print("=" * 60)

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

    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)

        # 1. 获取当前窗口大小
        print("\n1. 获取当前窗口大小:")
        size = page.rect.window_size
        print(f"   窗口大小: {size[0]} x {size[1]}")

        # 2. 获取视口大小
        print("\n2. 获取视口大小:")
        viewport = page.rect.viewport_size
        print(f"   视口大小: {viewport[0]} x {viewport[1]}")

        # 3. 获取窗口位置
        print("\n3. 获取窗口位置:")
        location = page.rect.window_location
        print(f"   窗口位置: ({location[0]}, {location[1]})")

        # 4. 获取页面完整大小
        print("\n4. 获取页面完整大小:")
        page_size = page.rect.page_size
        print(f"   页面大小: {page_size[0]} x {page_size[1]}")

        # 5. 获取滚动位置
        print("\n5. 获取滚动位置:")
        scroll_pos = page.rect.scroll_position
        print(f"   滚动位置: ({scroll_pos[0]}, {scroll_pos[1]})")

        # 6. 滚动页面并检查位置
        print("\n6. 滚动页面并检查位置:")
        page.scroll.to_bottom()
        page.wait(0.5)
        new_scroll = page.rect.scroll_position
        print(f"   滚动后位置: ({new_scroll[0]}, {new_scroll[1]})")

        # 6.1 窗口状态流
        print("\n6.1 窗口状态流测试:")
        page.window.maximize()
        page.wait(0.5)
        print(f"   maximize 后窗口大小: {page.rect.window_size}")

        page.window.minimize()
        page.wait(0.5)
        print("   ✓ minimize 已调用")

        page.window.fullscreen()
        page.wait(0.5)
        print(f"   fullscreen 后视口大小: {page.rect.viewport_size}")

        page.window.normal()
        page.wait(0.5)
        print("   ✓ normal 已恢复")

        # 6.2 设置窗口尺寸与位置并居中
        print("\n6.2 设置窗口尺寸/位置/居中:")
        page.window.set_size(1200, 820)
        page.window.set_position(40, 40)
        page.wait(0.5)
        print(f"   调整后窗口大小: {page.rect.window_size}")
        print(f"   调整后窗口位置: {page.rect.window_location}")

        page.window.center()
        page.wait(0.5)
        print(f"   居中后窗口位置: {page.rect.window_location}")

        # 7. 创建新标签页
        print("\n7. 创建新标签页:")
        new_tab = page.new_tab()
        page.wait(1)
        print(f"   ✓ 新标签页已创建")

        # 8. 在新标签页中打开页面
        print("\n8. 在新标签页中打开页面:")
        new_tab.get("https://www.example.com")
        new_tab.wait(2)
        print(f"   新标签页URL: {new_tab.url}")
        print(f"   新标签页标题: {new_tab.title}")

        # 9. 切换回原标签页
        print("\n9. 切换回原标签页:")
        # 使用 browser.activate_tab 显式切回原 tab
        page.browser.activate_tab(page.tab_id)
        page.wait(0.5)
        page.get(test_url)
        page.wait(0.5)
        print(f"   当前标签页标题: {page.title}")

        # 9.1 新建后台标签页并激活
        print("\n9.1 后台标签页激活测试:")
        bg_tab = page.new_tab("https://www.example.com", background=True)
        page.wait(1)
        print(f"   后台标签页ID: {bg_tab.tab_id}")
        page.browser.activate_tab(bg_tab)
        page.wait(1)
        print(f"   激活后标题: {bg_tab.title}")

        # 切回主 tab,避免后续关闭干扰
        page.browser.activate_tab(page.tab_id)
        page.wait(0.5)

        # 10. 关闭新标签页
        print("\n10. 关闭新标签页:")
        new_tab.close()
        try:
            bg_tab.close()
        except Exception:
            pass
        page.wait(0.5)
        print(f"   ✓ 新标签页已关闭")

        print("\n" + "=" * 60)
        print("✓ 所有窗口管理测试通过!")
        print("=" * 60)

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

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


if __name__ == "__main__":
    test_window_management()

用户提示框处理:自动策略与步骤式登录 API

examples/17_user_prompts.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser intermediate
一句话定位

专门讲 alert、confirm、prompt 及其自动策略。

涉及 API
opts.set_user_prompt_handlerpage.set_prompt_handlerpage.clear_prompt_handlerpage.get_last_prompt_openedpage.get_last_prompt_closedpage.prompt_login
展示的不同用法
  • 设置全局 alert / confirm / prompt 处理策略
  • 给 prompt 设置默认输入文本
  • 获取最近一次 prompt 打开和关闭信息
  • 使用步骤式 API 完成 prompt 登录
适用场景

适合 prompts 页面。

相关文档页面
推荐阅读
browsing-events-30
完整代码

来源:app/content/ruyipage_examples/17_user_prompts.py · 仓库文件:17_user_prompts.py

python 注释版
# =============================
# RuyiPage 示例注释版
# 标题: 用户提示框处理:自动策略与步骤式登录 API
# 说明: 专门讲 alert、confirm、prompt 及其自动策略。
# 你会学到:
#   1. 设置全局 alert / confirm / prompt 处理策略
#   2. 给 prompt 设置默认输入文本
#   3. 获取最近一次 prompt 打开和关闭信息
#   4. 使用步骤式 API 完成 prompt 登录
# 相关 API: opts.set_user_prompt_handler, page.set_prompt_handler, page.clear_prompt_handler, page.get_last_prompt_opened, page.get_last_prompt_closed, page.prompt_login
# =============================

# -*- coding: utf-8 -*-
"""示例17: 用户提示框处理(登录式步骤 API)

演示:
1) 自动策略 API
2) 步骤式 prompt 登录 API
3) 触发模式切换:mouse / keyboard
"""

import io
import os
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")


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

from ruyipage import FirefoxOptions, FirefoxPage


def test_user_prompts():
    print("=" * 60)
    print("测试17: 用户提示框处理")
    print("=" * 60)

    opts = FirefoxOptions()
    opts.headless(False)
    opts.set_user_prompt_handler(
        {
            "alert": "accept",
            "confirm": "accept",
            "prompt": "ignore",
            "default": "accept",
        }
    )
    page = FirefoxPage(opts)

    try:
        test_page = os.path.join(
            os.path.dirname(__file__), "test_pages", "native_user_prompts_test.html"
        )
        test_url = "file:///" + os.path.abspath(test_page).replace("\\", "/")
        page.get(test_url)
        page.wait(0.8)

        page.set_prompt_handler(
            alert="accept",
            confirm="accept",
            prompt="ignore",
            default="accept",
            prompt_text="张三",
        )

        print("\n1. alert(自动策略):")
        page.ele("#alert-btn").click_self()
        page.wait(0.3)
        print(f"   result: {page.ele('#alert-result').text}")

        print("\n2. confirm(自动策略):")
        page.ele("#confirm-btn").click_self()
        page.wait(0.3)
        print(f"   result: {page.ele('#confirm-result').text}")
        print(f"   opened: {page.get_last_prompt_opened()}")
        print(f"   closed: {page.get_last_prompt_closed()}")

        print("\n3. prompt 登录(步骤式 API / mouse):")
        page.clear_prompt_handler()
        page.get(test_url)
        page.wait(0.8)
        page.prompt_login(
            "#login-prompt-btn", "alice", "s3cr3t", trigger="mouse", timeout=2
        )
        page.wait(0.5)
        print(f"   result: {page.ele('#prompt-result').text}")

        print("\n4. prompt 登录(步骤式 API / keyboard):")
        page.get(test_url)
        page.wait(0.8)
        page.prompt_login(
            "#login-prompt-btn", "bob", "654321", trigger="keyboard", timeout=2
        )
        page.wait(0.5)
        print(f"   result: {page.ele('#prompt-result').text}")

        print("\n" + "=" * 60)
        print("✓ 用户提示框自动策略 + 登录式步骤 API 测试完成!")
        print("=" * 60)

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

        traceback.print_exc()
    finally:
        try:
            page.clear_prompt_handler()
        except Exception:
            pass
        page.wait(1)
        page.quit()


if __name__ == "__main__":
    test_user_prompts()

PDF 打印:纸张尺寸、方向、边距、页范围与导出数据

examples/19_pdf_printing.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser intermediate
一句话定位

集中说明 save_pdf 的参数覆盖能力。

涉及 API
page.save_pdfpage.pdf
展示的不同用法
  • 基础 PDF 导出
  • A4 纸张和背景打印
  • 横向打印与缩放
  • 自定义页边距和页范围
  • 获取 PDF bytes 与 base64
适用场景

适合 pdf 页面。

相关文档页面
推荐阅读
screenshot-06basic-navigation-01
完整代码

来源:app/content/ruyipage_examples/19_pdf_printing.py · 仓库文件:19_pdf_printing.py

python 注释版
# =============================
# RuyiPage 示例注释版
# 标题: PDF 打印:纸张尺寸、方向、边距、页范围与导出数据
# 说明: 集中说明 save_pdf 的参数覆盖能力。
# 你会学到:
#   1. 基础 PDF 导出
#   2. A4 纸张和背景打印
#   3. 横向打印与缩放
#   4. 自定义页边距和页范围
#   5. 获取 PDF bytes 与 base64
# 相关 API: page.save_pdf, page.pdf
# =============================

# -*- coding: utf-8 -*-
"""示例19: PDF 打印(完整参数覆盖)"""

import io
import os
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")


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

from ruyipage import FirefoxOptions, FirefoxPage


def _size_of(path):
    return os.path.getsize(path) if os.path.exists(path) else 0


def test_pdf_printing():
    print("=" * 60)
    print("测试19: PDF打印功能")
    print("=" * 60)

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

    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)

        output_dir = os.path.join(os.path.dirname(__file__), "output", "pdf")
        os.makedirs(output_dir, exist_ok=True)

        # 1) 基本打印
        print("\n1. 基本PDF打印:")
        p1 = os.path.join(output_dir, "basic.pdf")
        page.save_pdf(p1)
        print(f"   ✓ 已保存: {p1} ({_size_of(p1)} bytes)")

        # 2) A4 + 背景
        print("\n2. A4 + 背景:")
        p2 = os.path.join(output_dir, "a4_bg.pdf")
        page.save_pdf(
            p2,
            page={"width": 21.0, "height": 29.7},
            background=True,
        )
        print(f"   ✓ 已保存: {p2} ({_size_of(p2)} bytes)")

        # 3) 横向 + 缩放 + 页边距
        print("\n3. 横向 + 缩放 + 页边距:")
        p3 = os.path.join(output_dir, "landscape_scaled.pdf")
        page.save_pdf(
            p3,
            orientation="landscape",
            scale=0.9,
            margin={
                "top": 1.2,
                "bottom": 1.2,
                "left": 1.0,
                "right": 1.0,
            },
        )
        print(f"   ✓ 已保存: {p3} ({_size_of(p3)} bytes)")

        # 4) 指定页范围 + shrinkToFit
        print("\n4. 指定页范围 + shrinkToFit:")
        p4 = os.path.join(output_dir, "page_ranges_shrink.pdf")
        page.save_pdf(
            p4,
            page_ranges=["1-2"],
            shrink_to_fit=True,
        )
        print(f"   ✓ 已保存: {p4} ({_size_of(p4)} bytes)")

        # 5) 获取 bytes/base64
        print("\n5. 获取 bytes / base64:")
        pdf_bytes = page.pdf(page_ranges=["1"])
        pdf_b64 = (
            __import__("base64")
            .b64encode(page.pdf(shrink_to_fit=False))
            .decode("ascii")
        )
        print(f"   bytes长度: {len(pdf_bytes)}")
        print(f"   base64长度: {len(pdf_b64)}")

        if not (len(pdf_bytes) > 100 and len(pdf_b64) > 100):
            raise RuntimeError("PDF 输出长度异常")

        print("\n" + "=" * 60)
        print("✓ 所有PDF打印测试通过!")
        print(f"PDF保存在: {output_dir}")
        print("=" * 60)

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

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


if __name__ == "__main__":
    test_pdf_printing()

WebExtension:目录扩展与 XPI 安装卸载

examples/22_web_extension.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser strict
一句话定位

覆盖扩展安装、验证注入和卸载,是浏览器级能力的重要专题。

涉及 API
page.extensions.installpage.extensions.install_archivepage.extensions.uninstallpage.extensions.uninstall_allpage.run_js
展示的不同用法
  • 安装目录扩展
  • 验证 content script 生效
  • 打包并安装 XPI 扩展
  • 卸载指定扩展与清空测试扩展
适用场景

适合 extensions 页面。

相关文档页面
推荐阅读
browser-user-context-25
完整代码

来源:app/content/ruyipage_examples/22_web_extension.py · 仓库文件:22_web_extension.py

python 注释版
#!/usr/bin/env python
# =============================
# RuyiPage 示例注释版
# 标题: WebExtension:目录扩展与 XPI 安装卸载
# 说明: 覆盖扩展安装、验证注入和卸载,是浏览器级能力的重要专题。
# 你会学到:
#   1. 安装目录扩展
#   2. 验证 content script 生效
#   3. 打包并安装 XPI 扩展
#   4. 卸载指定扩展与清空测试扩展
# 相关 API: page.extensions.install, page.extensions.install_archive, page.extensions.uninstall, page.extensions.uninstall_all, page.run_js
# =============================

# -*- coding: utf-8 -*-
"""示例22: WebExtension 模块测试(完整链路版)

完整链路:
1) 创建测试扩展
2) 安装目录扩展
3) 验证扩展已安装
4) 卸载目录扩展
5) 打包 xpi 并安装
6) 验证扩展已安装
7) 卸载打包扩展
8) 清理测试文件

说明:
- WebExtension BiDi 规范只提供 install / uninstall 命令。
- 当前 Firefox 环境下若返回 unknown command,则视为“当前版本不支持”,不是脚本错误。
"""

import io
import json
import os
import shutil
import sys
import zipfile

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 FirefoxPage


class Rows:
    def __init__(self):
        self.rows = []

    def add(self, name, status, detail=""):
        self.rows.append((name, status, detail))
        print(f"  {status}: {name}")
        if detail:
            print(f"     详情: {detail}")

    def summary(self):
        print("\n" + "=" * 70)
        print("测试结果汇总")
        print("=" * 70)
        print(f"{'序号':<5} {'项目':<28} {'状态':<8} {'说明'}")
        print("-" * 70)
        for i, (name, status, detail) in enumerate(self.rows, 1):
            print(f"{i:<5} {name:<28} {status:<8} {detail[:36]}")
        print("-" * 70)


def _create_test_extension(ext_dir):
    os.makedirs(ext_dir, exist_ok=True)
    manifest = {
        "manifest_version": 2,
        "name": "RuyiPage Test Extension",
        "version": "1.0.0",
        "description": "测试扩展",
        "browser_specific_settings": {
            "gecko": {
                "id": "[email protected]",
                "strict_min_version": "109.0",
            }
        },
        "background": {"scripts": ["background.js"]},
        "content_scripts": [
            {
                "matches": ["<all_urls>"],
                "js": ["content.js"],
                "run_at": "document_end",
            }
        ],
        "permissions": [],
    }
    with open(os.path.join(ext_dir, "manifest.json"), "w", encoding="utf-8") as f:
        json.dump(manifest, f, indent=2, ensure_ascii=False)

    with open(os.path.join(ext_dir, "background.js"), "w", encoding="utf-8") as f:
        f.write("console.log('RuyiPage Test Extension loaded!');")

    with open(os.path.join(ext_dir, "content.js"), "w", encoding="utf-8") as f:
        f.write(
            "document.documentElement.setAttribute('data-ruyi-extension', 'loaded');"
        )


def _page_has_extension_marker(page):
    return (
        page.run_js(
            "return document.documentElement.getAttribute('data-ruyi-extension')"
        )
        == "loaded"
    )


def _safe_install(page, path, rows, label):
    try:
        ext_id = page.extensions.install(path)
        if ext_id:
            rows.add(label, "✓ 通过", f"extension_id={ext_id}")
            return ext_id
        rows.add(label, "⚠ 不支持", "当前 Firefox 未返回 extension id")
        return None
    except Exception as e:
        msg = str(e).lower()
        if "unknown command" in msg or "not supported" in msg:
            rows.add(label, "⚠ 不支持", "当前 Firefox 不支持 webExtension.install")
            return None
        raise


def test_web_extension():
    print("=" * 70)
    print("测试 22: WebExtension 模块")
    print("=" * 70)

    page = FirefoxPage()
    rows = Rows()
    ext_dir = os.path.join(os.path.dirname(__file__), "test_extension")
    xpi_path = os.path.join(os.path.dirname(__file__), "test_extension.xpi")

    ext_id = None
    ext_id2 = None

    try:
        # 1) 创建测试扩展
        _create_test_extension(ext_dir)
        rows.add("创建测试扩展", "✓ 通过", ext_dir)

        # 2) 安装目录扩展
        ext_id = _safe_install(page, ext_dir, rows, "安装目录扩展")

        # 3) 验证目录扩展已生效
        if ext_id:
            page.get("https://example.com")
            page.wait(1.5)
            marker = _page_has_extension_marker(page)
            rows.add(
                "目录扩展生效验证",
                "✓ 通过" if marker else "✗ 失败",
                "content script 已注入" if marker else "未检测到注入标记",
            )
        else:
            rows.add("目录扩展生效验证", "⚠ 跳过", "安装未成功")

        # 4) 卸载目录扩展
        if ext_id:
            page.extensions.uninstall(ext_id)
            rows.add("卸载目录扩展", "✓ 通过")
        else:
            rows.add("卸载目录扩展", "⚠ 跳过", "未安装成功")

        # 5) 打包 xpi
        with zipfile.ZipFile(xpi_path, "w", zipfile.ZIP_DEFLATED) as zipf:
            zipf.write(os.path.join(ext_dir, "manifest.json"), "manifest.json")
            zipf.write(os.path.join(ext_dir, "background.js"), "background.js")
            zipf.write(os.path.join(ext_dir, "content.js"), "content.js")
        rows.add("打包 XPI", "✓ 通过", xpi_path)

        # 6) 安装打包扩展
        try:
            ext_id2 = page.extensions.install_archive(xpi_path)
            if ext_id2:
                rows.add("安装 XPI 扩展", "✓ 通过", f"extension_id={ext_id2}")
            else:
                rows.add(
                    "安装 XPI 扩展", "⚠ 不支持", "当前 Firefox 未返回 extension id"
                )
        except Exception as e:
            msg = str(e).lower()
            if "unknown command" in msg or "not supported" in msg:
                rows.add(
                    "安装 XPI 扩展",
                    "⚠ 不支持",
                    "当前 Firefox 不支持 webExtension.install",
                )
            else:
                raise

        # 7) 验证 XPI 扩展已生效
        if ext_id2:
            page.get("https://example.com")
            page.wait(1.5)
            marker = _page_has_extension_marker(page)
            rows.add(
                "XPI 扩展生效验证",
                "✓ 通过" if marker else "✗ 失败",
                "content script 已注入" if marker else "未检测到注入标记",
            )
        else:
            rows.add("XPI 扩展生效验证", "⚠ 跳过", "安装未成功")

        # 8) 卸载打包扩展
        if ext_id2:
            page.extensions.uninstall(ext_id2)
            rows.add("卸载 XPI 扩展", "✓ 通过")
        else:
            rows.add("卸载 XPI 扩展", "⚠ 跳过", "未安装成功")

        print("\n" + "=" * 70)
        print("✓ WebExtension 模块测试完成")
        print("=" * 70)

    except Exception as e:
        rows.add("WebExtension 模块", "✗ 失败", str(e))
        import traceback

        traceback.print_exc()
    finally:
        try:
            page.extensions.uninstall_all()
        except Exception:
            pass
        page.quit()

        if os.path.exists(ext_dir):
            shutil.rmtree(ext_dir)
        if os.path.exists(xpi_path):
            os.remove(xpi_path)

        rows.add("清理测试文件", "✓ 通过")
        rows.summary()


if __name__ == "__main__":
    test_web_extension()

下载管理:下载目录、事件订阅、等待落盘、禁止下载

examples/23_download.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser strict
一句话定位

讲清下载事件链和文件落盘验证,是 downloads 页的主示例。

涉及 API
page.downloads.set_behaviorpage.downloads.startpage.downloads.wait_chainpage.downloads.waitpage.downloads.wait_filepage.downloads.stop
展示的不同用法
  • 设置 allow 下载行为并指定目录
  • 订阅下载开始和结束事件
  • 等待文本和 JSON 文件下载完成
  • 验证文件已落盘
  • 切换 deny 模式阻止下载
适用场景

适合 downloads 与 events 页面。

相关文档页面
推荐阅读
pdf-19collector-28
完整代码

来源:app/content/ruyipage_examples/23_download.py · 仓库文件:23_download.py

python 注释版
#!/usr/bin/env python
# =============================
# RuyiPage 示例注释版
# 标题: 下载管理:下载目录、事件订阅、等待落盘、禁止下载
# 说明: 讲清下载事件链和文件落盘验证,是 downloads 页的主示例。
# 你会学到:
#   1. 设置 allow 下载行为并指定目录
#   2. 订阅下载开始和结束事件
#   3. 等待文本和 JSON 文件下载完成
#   4. 验证文件已落盘
#   5. 切换 deny 模式阻止下载
# 相关 API: page.downloads.set_behavior, page.downloads.start, page.downloads.wait_chain, page.downloads.wait, page.downloads.wait_file, page.downloads.stop
# =============================

# -*- coding: utf-8 -*-
"""示例23: 下载管理测试(严格结果版)"""

import io
import os
import shutil
import sys
from typing import List, Dict

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 FirefoxPage
from ruyipage._functions.tools import find_free_port

from test_server import TestServer


def add_result(
    results: List[Dict[str, str]], item: str, status: str, note: str
) -> None:
    """记录结构化测试结果。"""
    results.append({"item": item, "status": status, "note": note})


def print_results(results: List[Dict[str, str]]) -> None:
    """打印固定格式结果表。"""
    print("\n| 项目 | 状态 | 说明 |")
    print("| --- | --- | --- |")
    for row in results:
        print(f"| {row['item']} | {row['status']} | {row['note']} |")


def build_download_page(base_url: str) -> str:
    """构造下载测试页。"""
    return """
    <!DOCTYPE html>
    <html>
    <head><meta charset='utf-8'><title>下载测试</title></head>
    <body>
        <button id='download-text'>下载文本文件</button>
        <button id='download-json'>下载JSON文件</button>
        <div id='status'></div>
        <script>
            function triggerDownload(url, statusText) {
                const a = document.createElement('a');
                a.href = url;
                a.click();
                document.getElementById('status').textContent = statusText;
            }

            document.getElementById('download-text').onclick = () => {
                triggerDownload('%s/download/text', '文本文件下载已触发');
            };

            document.getElementById('download-json').onclick = () => {
                triggerDownload('%s/download/json', 'JSON文件下载已触发');
            };
        </script>
    </body>
    </html>
    """ % (base_url, base_url)


def main() -> None:
    print("=" * 70)
    print("测试 23: Download 下载管理测试")
    print("=" * 70)

    page = FirefoxPage()
    server = None
    results: List[Dict[str, str]] = []
    download_path = os.path.abspath(
        os.path.join(os.path.dirname(__file__), "downloads")
    )
    text_path = os.path.join(download_path, "test.txt")
    json_path = os.path.join(download_path, "test.json")

    try:
        if os.path.exists(download_path):
            shutil.rmtree(download_path)
        os.makedirs(download_path, exist_ok=True)

        server = TestServer(port=find_free_port(8930, 9030)).start()
        base_url = server.get_url("")[:-1]

        # 1. 允许下载
        try:
            page.downloads.set_behavior("allow", path=download_path)
            add_result(
                results,
                "browser.setDownloadBehavior allow",
                "成功",
                f"下载目录: {download_path}",
            )
        except Exception as e:
            add_result(
                results, "browser.setDownloadBehavior allow", "失败", str(e)[:120]
            )
            raise

        # 2. 加载页面
        page.get("data:text/html;charset=utf-8," + build_download_page(base_url))
        add_result(
            results, "下载测试页加载", "成功", f"下载页已加载,服务地址: {base_url}"
        )

        # 3. 订阅下载事件
        subscribed = page.downloads.start()
        if subscribed:
            add_result(
                results,
                "下载事件订阅",
                "成功",
                "已订阅 downloadWillBegin / downloadEnd",
            )
        else:
            add_result(
                results, "下载事件订阅", "不支持", "当前浏览器未成功订阅下载事件"
            )

        # 4. 文本文件下载
        page.downloads.clear()
        page.ele("#download-text").click_self()
        begin_text, end_text = page.downloads.wait_chain(filename="test.txt", timeout=5)

        if begin_text:
            add_result(
                results,
                "downloadWillBegin text",
                "成功",
                f"filename={begin_text.suggested_filename or 'unknown'}",
            )
        else:
            add_result(
                results, "downloadWillBegin text", "失败", "5 秒内未收到开始事件"
            )

        if end_text:
            end_status = end_text.status or "unknown"
            add_result(results, "downloadEnd text", "成功", f"status={end_status}")
        else:
            add_result(results, "downloadEnd text", "失败", "5 秒内未收到结束事件")

        if page.downloads.wait_file(text_path, timeout=3):
            add_result(
                results, "text 文件落盘", "成功", f"{os.path.getsize(text_path)} bytes"
            )
        else:
            add_result(results, "text 文件落盘", "失败", "事件触发后仍未观察到文件落盘")

        # 5. JSON 文件下载
        page.downloads.clear()
        page.ele("#download-json").click_self()
        begin_json, end_json = page.downloads.wait_chain(
            filename="test.json", timeout=5
        )

        if begin_json:
            add_result(
                results,
                "downloadWillBegin json",
                "成功",
                f"filename={begin_json.suggested_filename or 'unknown'}",
            )
        else:
            add_result(
                results, "downloadWillBegin json", "失败", "5 秒内未收到开始事件"
            )

        if end_json:
            end_status = end_json.status or "unknown"
            add_result(results, "downloadEnd json", "成功", f"status={end_status}")
        else:
            add_result(results, "downloadEnd json", "失败", "5 秒内未收到结束事件")

        if page.downloads.wait_file(json_path, timeout=3):
            add_result(
                results, "json 文件落盘", "成功", f"{os.path.getsize(json_path)} bytes"
            )
        else:
            add_result(results, "json 文件落盘", "失败", "事件触发后仍未观察到文件落盘")

        # 6. deny 模式
        page.downloads.clear()
        try:
            page.downloads.set_behavior("deny")
            page.ele("#download-text").click_self()
            denied_begin = page.downloads.wait(
                method="browsingContext.downloadWillBegin",
                timeout=2,
                filename="test.txt",
            )
            if denied_begin:
                add_result(
                    results,
                    "browser.setDownloadBehavior deny",
                    "失败",
                    "deny 模式下仍出现下载开始事件",
                )
            else:
                add_result(
                    results,
                    "browser.setDownloadBehavior deny",
                    "成功",
                    "deny 模式下未观察到下载开始事件",
                )
        except Exception as e:
            add_result(
                results, "browser.setDownloadBehavior deny", "失败", str(e)[:120]
            )

        print_results(results)

    except Exception as e:
        add_result(results, "示例执行", "失败", str(e)[:120])
        print_results(results)
        raise
    finally:
        try:
            page.downloads.stop()
        except Exception:
            pass
        try:
            if server is not None:
                server.stop()
        except Exception:
            pass
        try:
            page.quit()
        except Exception:
            pass
        if os.path.exists(download_path):
            shutil.rmtree(download_path)


if __name__ == "__main__":
    main()

Browser 模块:用户上下文与客户端窗口

examples/25_browser_user_context.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser strict
一句话定位

Browser 模块最完整的 user context 与 client window 说明案例。

涉及 API
page.browser_tools.get_user_contextspage.browser_tools.create_user_contextpage.browser_tools.remove_user_contextpage.browser_tools.create_tabpage.browser_tools.get_client_windowspage.browser_tools.set_window_state
展示的不同用法
  • 读取 user context 列表
  • 创建与删除 user context
  • 在指定 user context 中创建新 tab
  • 读取 client window 列表
  • 切换窗口状态为 minimized / maximized / fullscreen
适用场景

适合 browser、user-contexts 和 window 页面。

相关文档页面
推荐阅读
contexts-26window-16
完整代码

来源:app/content/ruyipage_examples/25_browser_user_context.py · 仓库文件:25_browser_user_context.py

python 注释版
#!/usr/bin/env python
# =============================
# RuyiPage 示例注释版
# 标题: Browser 模块:用户上下文与客户端窗口
# 说明: Browser 模块最完整的 user context 与 client window 说明案例。
# 你会学到:
#   1. 读取 user context 列表
#   2. 创建与删除 user context
#   3. 在指定 user context 中创建新 tab
#   4. 读取 client window 列表
#   5. 切换窗口状态为 minimized / maximized / fullscreen
# 相关 API: page.browser_tools.get_user_contexts, page.browser_tools.create_user_context, page.browser_tools.remove_user_context, page.browser_tools.create_tab, page.browser_tools.get_client_windows, page.browser_tools.set_window_state
# =============================

# -*- coding: utf-8 -*-
"""示例25: Browser 模块(用户上下文 + 客户端窗口)严格结果版"""

import io
import sys
import time
from typing import Dict, List

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 FirefoxPage


def add_result(
    results: List[Dict[str, str]], item: str, status: str, note: str
) -> None:
    """记录结构化结果。"""
    results.append({"item": item, "status": status, "note": note})


def print_results(results: List[Dict[str, str]]) -> None:
    """打印固定格式结果表。"""
    print("\n| 项目 | 状态 | 说明 |")
    print("| --- | --- | --- |")
    for row in results:
        print(f"| {row['item']} | {row['status']} | {row['note']} |")


def main() -> None:
    print("=" * 70)
    print("测试 25: Browser 用户上下文与窗口管理")
    print("=" * 70)

    page = FirefoxPage()
    results: List[Dict[str, str]] = []
    user_context_id = None
    new_tab_ctx = None

    try:
        page.get("https://www.example.com")

        # 1. user context 生命周期
        before = page.browser_tools.get_user_contexts()
        before_ids = [i.get("userContext") for i in before]
        add_result(
            results,
            "browser.getUserContexts before",
            "成功",
            f"创建前数量: {len(before)}",
        )

        user_context_id = page.browser_tools.create_user_context()
        if user_context_id:
            add_result(
                results,
                "browser.createUserContext",
                "成功",
                f"userContext={user_context_id}",
            )
        else:
            add_result(
                results, "browser.createUserContext", "失败", "未返回 userContext ID"
            )

        after_create = page.browser_tools.get_user_contexts()
        after_create_ids = [i.get("userContext") for i in after_create]
        if user_context_id in after_create_ids and len(after_create) == len(before) + 1:
            add_result(
                results,
                "browser.getUserContexts after create",
                "成功",
                f"数量变为 {len(after_create)}",
            )
        else:
            add_result(
                results,
                "browser.getUserContexts after create",
                "失败",
                "创建后数量或ID校验不通过",
            )

        if user_context_id:
            new_tab_ctx = page.browser_tools.create_tab(user_context=user_context_id)
            if new_tab_ctx:
                add_result(
                    results,
                    "browsingContext.create tab in user context",
                    "成功",
                    f"context={new_tab_ctx}",
                )
            else:
                add_result(
                    results,
                    "browsingContext.create tab in user context",
                    "失败",
                    "未返回新 tab context",
                )

        if new_tab_ctx:
            try:
                page.contexts.close(new_tab_ctx)
                add_result(
                    results, "browsingContext.close new tab", "成功", "新 tab 已关闭"
                )
                new_tab_ctx = None
            except Exception as e:
                add_result(
                    results, "browsingContext.close new tab", "跳过", str(e)[:120]
                )

        if user_context_id:
            page.browser_tools.remove_user_context(user_context_id)
            add_result(
                results,
                "browser.removeUserContext",
                "成功",
                f"已删除 {user_context_id}",
            )

            after_remove = page.browser_tools.get_user_contexts()
            after_remove_ids = [i.get("userContext") for i in after_remove]
            if user_context_id not in after_remove_ids and len(after_remove) == len(
                before_ids
            ):
                add_result(
                    results,
                    "browser.getUserContexts after remove",
                    "成功",
                    f"数量回到 {len(after_remove)}",
                )
            else:
                add_result(
                    results,
                    "browser.getUserContexts after remove",
                    "失败",
                    "删除后数量或ID校验不通过",
                )
            user_context_id = None

        # 2. client window 状态流
        windows = page.browser_tools.get_client_windows()
        if not windows:
            add_result(
                results, "browser.getClientWindows", "失败", "未返回任何 client window"
            )
        else:
            add_result(
                results, "browser.getClientWindows", "成功", f"窗口数量: {len(windows)}"
            )
            win = windows[0]
            win_id = win.get("clientWindow")
            states = ["minimized", "normal", "maximized", "fullscreen", "normal"]
            state_notes = []
            state_failed = False
            for state in states:
                page.browser_tools.set_window_state(win_id, state=state)
                time.sleep(0.4)
                current_windows = page.browser_tools.get_client_windows()
                current = current_windows[0] if current_windows else {}
                current_state = current.get("state")
                state_notes.append(f"{state}->{current_state}")
                if current_state != state:
                    state_failed = True

            if state_failed:
                add_result(
                    results,
                    "browser.setClientWindowState",
                    "失败",
                    ", ".join(state_notes),
                )
            else:
                add_result(
                    results,
                    "browser.setClientWindowState",
                    "成功",
                    ", ".join(state_notes),
                )

        print_results(results)

    except Exception as e:
        add_result(results, "示例执行", "失败", str(e)[:120])
        print_results(results)
        raise
    finally:
        if new_tab_ctx:
            try:
                page.contexts.close(new_tab_ctx)
            except Exception:
                pass
        if user_context_id:
            try:
                page.browser_tools.remove_user_context(user_context_id)
            except Exception:
                pass
        try:
            page.quit()
        except Exception:
            pass


if __name__ == "__main__":
    main()

BrowsingContext 高级能力:树、创建、关闭、reload、viewport

examples/26_browsing_context_advanced.py
GitHub 仓库 已同步源码 查看 GitHub 源码
browser strict
一句话定位

是 browsing context 章节的主参考示例。

涉及 API
page.contexts.get_treepage.contexts.create_tabpage.contexts.closepage.contexts.reloadpage.contexts.set_bypass_csppage.contexts.set_viewport
展示的不同用法
  • 获取 browsing context 树
  • 通过标准命令创建和关闭 context
  • 执行普通 reload 和 ignore_cache reload
  • 设置 bypass CSP
  • 调整桌面和移动视口尺寸
适用场景

适合 browsing-context 页面。

推荐阅读
browser-user-context-25tabs-09
完整代码

来源:app/content/ruyipage_examples/26_browsing_context_advanced.py · 仓库文件:26_browsing_context_advanced.py

python 注释版
#!/usr/bin/env python
# =============================
# RuyiPage 示例注释版
# 标题: BrowsingContext 高级能力:树、创建、关闭、reload、viewport
# 说明: 是 browsing context 章节的主参考示例。
# 你会学到:
#   1. 获取 browsing context 树
#   2. 通过标准命令创建和关闭 context
#   3. 执行普通 reload 和 ignore_cache reload
#   4. 设置 bypass CSP
#   5. 调整桌面和移动视口尺寸
# 相关 API: page.contexts.get_tree, page.contexts.create_tab, page.contexts.close, page.contexts.reload, page.contexts.set_bypass_csp, page.contexts.set_viewport
# =============================

# -*- coding: utf-8 -*-
"""示例26: BrowsingContext 高级功能(严格结果版)"""

import io
import sys
from typing import Dict, List

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 FirefoxPage


def add_result(
    results: List[Dict[str, str]], item: str, status: str, note: str
) -> None:
    """记录结构化结果。"""
    results.append({"item": item, "status": status, "note": note})


def print_results(results: List[Dict[str, str]]) -> None:
    """打印固定格式结果表。"""
    print("\n| 项目 | 状态 | 说明 |")
    print("| --- | --- | --- |")
    for row in results:
        print(f"| {row['item']} | {row['status']} | {row['note']} |")


def main() -> None:
    print("=" * 70)
    print("测试 26: BrowsingContext 高级功能")
    print("=" * 70)

    page = FirefoxPage()
    child_id = None
    results: List[Dict[str, str]] = []

    try:
        page.get("https://www.example.com")
        add_result(results, "页面加载", "成功", "example.com 已加载")

        tree = page.contexts.get_tree()
        contexts = tree.contexts
        if contexts:
            add_result(
                results,
                "browsingContext.getTree",
                "成功",
                f"上下文数量: {len(contexts)}",
            )
        else:
            add_result(results, "browsingContext.getTree", "失败", "未返回任何上下文")

        child_id = page.contexts.create_tab()
        if child_id:
            add_result(
                results, "browsingContext.create", "成功", f"新 context: {child_id}"
            )
            page.contexts.close(child_id)
            add_result(results, "browsingContext.close", "成功", "新 context 已关闭")
            child_id = None
        else:
            add_result(results, "browsingContext.create", "失败", "未返回 context ID")

        r1 = page.contexts.reload()
        if r1.get("navigation"):
            add_result(
                results,
                "browsingContext.reload",
                "成功",
                f"navigation={r1.get('navigation')}",
            )
        else:
            add_result(
                results, "browsingContext.reload", "跳过", "当前返回未包含 navigation"
            )

        try:
            r2 = page.contexts.reload(ignore_cache=True)
            add_result(
                results,
                "browsingContext.reload ignoreCache",
                "成功",
                f"navigation={r2.get('navigation')}",
            )
        except Exception as e:
            add_result(
                results, "browsingContext.reload ignoreCache", "不支持", str(e)[:120]
            )

        try:
            page.contexts.set_bypass_csp(True)
            add_result(
                results, "browsingContext.setBypassCSP", "成功", "标准命令调用成功"
            )
        except Exception as e:
            add_result(results, "browsingContext.setBypassCSP", "不支持", str(e)[:120])

        page.contexts.set_viewport(800, 600)
        page.wait(0.2)
        vp = page.rect.viewport_size
        if tuple(vp) == (800, 600):
            add_result(
                results,
                "browsingContext.setViewport 800x600",
                "成功",
                f"实际视口: {vp}",
            )
        else:
            add_result(
                results,
                "browsingContext.setViewport 800x600",
                "失败",
                f"实际视口: {vp}",
            )

        page.contexts.set_viewport(375, 667)
        page.wait(0.2)
        vp2 = page.rect.viewport_size
        if tuple(vp2) == (375, 667):
            add_result(
                results,
                "browsingContext.setViewport 375x667",
                "成功",
                f"实际视口: {vp2}",
            )
        else:
            add_result(
                results,
                "browsingContext.setViewport 375x667",
                "失败",
                f"实际视口: {vp2}",
            )

        print_results(results)

    except Exception as e:
        add_result(results, "示例执行", "失败", str(e)[:120])
        print_results(results)
        raise
    finally:
        if child_id:
            try:
                page.contexts.close(child_id)
            except Exception:
                pass
        try:
            page.quit()
        except Exception:
            pass


if __name__ == "__main__":
    main()

三标签页三隔离上下文:单浏览器多账号会话隔离

examples/37_three_isolated_user_context_tabs.py
GitHub 仓库 待同步 查看 GitHub 源码
browser strict
一句话定位

展示单浏览器内三个标签页分别使用不同 user context,实现 Cookie 与存储完全隔离。

涉及 API
page.browser_tools.create_user_contextpage.browser_tools.create_tabpage.browser_tools.remove_user_contextpage.contexts.close
展示的不同用法
  • 创建多个独立 user context
  • 在指定上下文中打开新标签页
  • 验证 Cookie 与存储隔离
  • 关闭标签页并清理上下文
适用场景

适合 user-contexts、browser 和 storage 页面。

相关文档页面
推荐阅读
browser-user-context-25tabs-09

自动探测并接管已打开浏览器

examples/39_attach_exist_browser.py
GitHub 仓库 待同步 查看 GitHub 源码
browser advanced
一句话定位

适合 Firefox、ADS、FlowerBrowser 等已打开实例的自动探测接管场景。

涉及 API
auto_attach_exist_browser_by_processpage.browser.addresspage.titlepage.url
展示的不同用法
  • 按进程特征自动探测真实调试端口
  • 接管已打开的 Firefox 或指纹浏览器
  • 自动切到最新标签页
  • 复用已有人机操作会话
适用场景

适合 attach-existing-browser、fingerprint-browser 和 anti-bot 页面。

推荐阅读
quickstart-fingerprint-browserquickstart-private-mode