NiceGUI 浏览器自动化测试指南:用 Screen 与 Selenium 为 Python UI 编写端到端测试
2026/9/14 6:11:51 网站建设 项目流程

NiceGUI 浏览器自动化测试指南:用 Screen 与 Selenium 为 Python UI 编写端到端测试

【免费下载链接】niceguiCreate web-based user interfaces with Python. The nice way.项目地址: https://gitcode.com/GitHub_Trending/ni/nicegui

导读

NiceGUI 是一个"用 Python 写界面"的 Web UI 框架,但其交互式界面(按钮、输入框、动态更新等)无法靠普通单元测试覆盖。本文以仓库 tests/README.md 为骨架,完整讲解 NiceGUI 官方推荐的浏览器端到端测试方案:如何搭建 Chrome + ChromeDriver 环境、如何使用Screen高层接口驱动真实浏览器、以及测试框架在底层(pytest 插件、fixture 链、会话级浏览器复用)是如何工作的。读完本文,你将能像 NiceGUI 官方测试套件一样,为自己的应用编写"打开页面 → 断言内容 → 点击交互 → 验证状态"的自动化 UI 测试。

为什么 UI 需要专门的自动化测试

测试用户界面是出了名的困难:页面加载有延迟、DOM 渲染依赖 JavaScript 与 WebSocket、交互事件难以稳定模拟。NiceGUI 的每个元素都对应一个带状态的组件(见 nicegui/elements),并且大量行为发生在浏览器端,因此仓库作者在 tests/README.md 中明确表达了立场:

自动化测试虽然需要大量基础设施、执行时间也更长,但相比人工测试,这份投入是值得的。

这正是浏览器级测试(Browser-based Testing)的核心理念:用真实的浏览器引擎运行应用,再像用户一样去查找元素、断言文本、模拟点击。NiceGUI 仓库为此构建了一套基于 Selenium WebDriver 的测试基础设施,让开发者能"像写普通 pytest 一样"写 UI 测试。

环境搭建:Selenium Manager 与 ChromeDriver

首选方案:什么都不用装

绝大多数情况下你不需要手动安装 ChromeDriverselenium测试依赖自带一个名为Selenium Manager的辅助工具,它会在测试首次运行时自动下载匹配版本的 Chrome 和 ChromeDriver。NiceGUI 的测试代码正是优先使用这一机制(后文 screen_plugin.py 中会看到其回退逻辑),所以对大多数系统来说,只要安装好测试依赖即可。

从 pyproject.toml 可以看到官方锁定的测试依赖版本范围:

依赖版本约束用途
pytest-selenium>=4.1.0,<5提供 Selenium 与 pytest 的集成能力
selenium>=4.11.2,<5WebDriver 官方 Python 绑定(含 Selenium Manager)

何时需要手动安装浏览器与驱动

只有两类场景需要手动安装浏览器和驱动:

  1. 非 Apple Silicon 的 ARM 机器(如树莓派、ARM 架构的 dev container)——Selenium Manager 在这些平台上没有可下载的预编译产物;
  2. 你想使用系统已安装的特定浏览器

如果手动安装 ChromeDriver,务必保证其版本与你的 Chrome / Chromium 版本严格匹配,否则测试无法启动。如果浏览器没有被自动识别,可以通过CHROME_BINARY_LOCATION环境变量显式指定浏览器可执行文件路径(官方 dev container 中该变量被设置为/usr/bin/chromium)。该变量在源码中的真实作用位置是 screen_plugin.py:

if 'CHROME_BINARY_LOCATION' in os.environ: chrome_options.binary_location = os.environ['CHROME_BINARY_LOCATION']

各平台安装命令

macOS(需已安装 Homebrew):

brew install --cask chromedriver

Windows(需已安装 Chocolatey):

choco install chromedriver

Linux(Debian 系):

sudo apt-get update sudo apt-get install chromium-driver

需要特别注意的是:在 Ubuntu 上,chromium-chromedriverchromium-driver都指向同一个过渡性占位包,最终会拉入 Chromium snap 包,而 snap 在容器、WSL 和精简 CI 镜像中是无法正常工作的死胡同。因此 Ubuntu 上优先使用 Selenium Manager,或手动安装版本匹配的 ChromeDriver。

Linux(Arch 系):

sudo pacman -S chromium

其他发行版的包管理器与包名可能不同,请查阅对应发行版文档。

Screen:把冗长的 Selenium 查询封装成高层接口

Selenium 的原生查询(find_element(By.XPATH, ...)implicitly_waitActionChains等)非常冗长繁琐。为此 NiceGUI 引入了一个Screen(实现在 nicegui/testing/screen.py),对外提供"面向当前浏览器显示状态"的高层操作接口。

四步工作流

官方文档给出的标准工作流程是:

  1. screen: Screen作为测试函数参数,获取screenfixture;
  2. 在函数体内编写你的 NiceGUI 代码;
  3. 调用screen.open(...)传入 URL 路径,开始访问页面;
  4. screen.should_contain(...)断言页面上出现了期望的文本。

最简单的示例

from nicegui import ui from nicegui.testing import Screen def test_hello_world(screen: Screen): ui.label('Hello, world') screen.open('/') screen.should_contain('Hello, world')

这个示例与仓库真实测试 tests/test_label.py 几乎完全一致。值得注意的细节是:测试函数体中的ui.label(...)并没有绑定任何@ui.page装饰器——这是因为测试时页面路由在 fixture 的全局状态重置中被清空,ui.label直接写在函数顶层时,会注册到根路由/,因此screen.open('/')即可访问。

Screen 的关键 API(源码级)

从 nicegui/testing/screen.py 可以完整看到Screen的能力边界,下面按用途归类:

导航与打开页面

  • open(path, timeout=3.0):打开指定路径。如果服务器尚未启动会自动启动,并在超时时间内重试直到页面就绪;它还会确保浏览器与后端 API 建立连接(connected.wait(1)),并在页面加载完成后等待 Socket.IO 消息流空闲(_wait_for_socket_idle),避免断言时 UI 仍在更新。
  • close():关闭浏览器标签页;当驱动是会话级复用时(只剩一个窗口),改为跳转到about:blank以触发断开连接,防止整个会话失效。
  • switch_to(tab_id):切换到指定索引的标签页,索引超出当前数量时自动新建。
  • current_path属性:返回浏览器当前的路径(含 query 与 fragment)。

文本与元素断言

  • should_contain(text):断言页面包含给定文本(find()内部使用 XPath//*[not(self::script) and not(self::style)]...来排除 script/style 标签内的文本)。
  • should_not_contain(text, wait=0.5):断言页面不包含给定文本。
  • should_contain_input(text):断言页面上存在值为text的输入框。
  • should_load_image(image, timeout=2.0):通过执行 JavaScript 检查图片naturalWidth/naturalHeight是否大于 0,确认图片真正加载完成。

等待与轮询

  • wait_for(target):当目标为字符串时等价于should_contain;当目标为可调用对象时,在IMPLICIT_WAIT(默认 4 秒)内每 0.1 秒轮询一次直到条件满足,期间自动容忍StaleElementReferenceException(元素被重新渲染)。
  • wait_for_js(expression, expected, timeout=None):反复执行return {expression}直到返回值等于期望值——这是验证前端状态(如组件内部数据、计算属性)的利器。
  • wait(t):固定等待t秒。

交互操作

  • click(target_text):点击包含指定文本的元素;若元素不可交互会抛出带outerHTML上下文的断言错误,便于排查。
  • context_click(target_text):右键点击。
  • click_at_position(element, x, y):在元素内的指定偏移位置点击(底层用ActionChains)。
  • type(text):向当前聚焦元素输入文本。

元素查找(返回 Selenium WebElement)

  • find(text)/find_all(text):按文本查找,find会额外检查元素是否可见(is_displayed()),隐藏元素会触发AssertionError
  • find_element(element):按 NiceGUI 元素的html_id直接定位——只需传ui.element实例即可。
  • find_by_class/find_all_by_class/find_by_tag/find_all_by_tag/find_by_css:按 CSS 类、HTML 标签、CSS 选择器查找。

日志与截图

  • assert_py_logger(level, message):断言 Python 日志(caplog)收到指定级别与内容的消息,message支持字符串或正则re.Pattern
  • render_js_logs():把浏览器控制台日志渲染成便于排错的字符串。
  • shot(name, failed=False):截图保存到screenshots目录,失败时文件名追加.failed后缀。
  • implicitly_wait(t):上下文管理器,临时修改隐式等待时间后自动恢复。

直接访问底层驱动

如果Screen还不够用,可以通过screen.selenium属性直接拿到 WebDriver 对象,调用 Selenium 提供的全部方法。此外Screen类上有几个可调常量:PORT = 3392(测试服务器端口,实际运行时会被自动替换为随机空闲端口)、IMPLICIT_WAIT = 4(隐式等待秒数)、CATCH_JS_ERRORS = True(是否把浏览器控制台错误视为测试失败)。

底层机制:pytest 插件与 fixture 链

Screen不是凭空出现的——它由一套 pytest 插件与 fixture 链组装而成。仓库根目录的 tests/conftest.py 只有短短几行,却承担了两个关键职责:

os.environ.setdefault('MPLBACKEND', 'Agg') # force a non-GUI Matplotlib backend during tests pytest_plugins = ['nicegui.testing.plugin']
  1. 强制使用非 GUI 的 Matplotlib 后端,避免测试期间弹出绘图窗口;
  2. 把 nicegui/testing/plugin.py 注册为 pytest 插件,从而引入screenuser等 fixture。

fixture 组装顺序

screenfixture(定义在 screen_plugin.py)依赖以下链式组件:

  • nicegui_reset_globals(general_fixtures.py):每个测试前重置 NiceGUI 的全局状态——清除非框架路由、重置appbinding、事件系统,并备份/恢复所有元素类型的默认 class/style/props,防止测试之间相互污染;
  • nicegui_remove_all_screenshots:清理上一次运行遗留的截图,并用文件锁区分并发运行(FileLock);
  • nicegui_driver(会话级):创建 Chrome WebDriver,在整个测试会话中复用(显著降低开销);驱动创建时依次尝试Service()、系统 PATH 中的chromedriver、字面量'chromedriver'三种方式,这正是官方文档所说的"ARM dev container 兼容回退";
  • caplog:pytest 内置的日志捕获 fixture,供assert_py_logger使用。

浏览器会话的初始化与收尾

nicegui_chrome_optionsfixture(screen_plugin.py)配置了测试浏览器的关键行为:

  • 无头模式(headless)、禁用沙箱与共享内存(no-sandboxdisable-dev-shm-usage),适配容器与 CI;
  • 固定窗口大小600x600
  • 下载目录指向会话唯一的临时目录,并关闭下载确认弹窗,从而支持测试文件下载功能;
  • 开启浏览器控制台日志采集(goog:loggingPrefs),这就是CATCH_JS_ERRORS能拦截前端报错的数据来源;
  • 读取CHROME_BINARY_LOCATION环境变量指定浏览器路径。

每个测试结束后,screenfixture 还会自动做三件"安全网"式检查:

  1. caplog中出现了 ERROR 级别日志 → 测试失败;
  2. 若浏览器控制台出现 SEVERE/ERROR 级错误(且不在allowed_js_errors白名单内)→ 测试失败;
  3. 无论结果如何,都会截屏存档(失败时文件名带.failed后缀)。

这意味着你免费获得了"前端报错即失败"的质量保障。在会话级驱动复用的前提下,每次测试前还会通过_reset_browser_state关闭多余标签页、清除该端口下的 cookies / localStorage / sessionStorage,保证测试之间浏览器状态干净(screen_plugin.py)。

测试服务器的生命周期

Screen.start_server()(screen.py)在独立线程中启动 NiceGUI 服务器:优先通过nicegui_main_file标记或 pytest 配置的main_file定位应用入口文件并用runpy.run_path运行;否则调用prepare_simulation()(general.py)注入一套精简的 run 配置(关闭 reload、关闭欢迎页、reload=Falseshow=False)后直接ui.run()。端口默认 3392,但pytest_configure会用helpers.find_free_port()为每次会话分配随机空闲端口,避免并行跑测试时端口冲突。

pytest 配置:标记、插件与示例项目

官方测试标记

在 pyproject.toml 中,NiceGUI 注册了自定义标记:

markers = ["screen: uses the browser-based screen fixture"]

配合conftest.pypytest_collection_modifyitems钩子,任何请求了screenfixture 的测试会被自动打上screen标记,方便你按-m "not screen"之类的方式跳过浏览器类测试(例如纯后端逻辑快速验证)。

在自有项目中使用测试插件

nicegui.testing.plugin可以被任意项目直接复用。仓库自带的示例项目展示了最简配置,例如 examples/authentication/pytest.ini:

[pytest] asyncio_mode = auto main_file = main.py addopts = -p nicegui.testing.plugin
  • main_file = main.py:告诉插件从哪个文件加载应用入口(对应pytest_addoption中注册的配置项,见 general_fixtures.py);
  • addopts = -p nicegui.testing.plugin:显式加载 NiceGUI 的测试插件。

如果想在单个测试上覆盖入口文件,可以使用nicegui_main_file标记(pytest 配置阶段自动注册,见 general_fixtures.py)。

更多真实示例:从断言到完整交互

仓库 tests 目录下有 120+ 个浏览器测试文件,几乎覆盖每个 UI 元素与功能模块,是学习Screen用法的绝佳素材。例如 tests/test_aggrid.py(AG Grid 表格)、tests/test_upload.py(文件上传与下载目录)、tests/test_download.py(配合DOWNLOAD_DIR验证下载文件)。示例项目层面,examples/todo_list/test_todo_list.py 与 examples/chat_app/test_chat_app.py 演示了如何在完整应用中编写端到端测试,并配有各自的pytest.ini

一个综合性的典型测试流程通常长这样:

from nicegui import ui from nicegui.testing import Screen def test_counter_interaction(screen: Screen): @ui.page('/') def page(): ui.number('count', value=0).bind_value(app.storage.user, 'count') ui.button('increment', on_click=lambda: ...) screen.open('/') screen.should_contain('count') # 断言页面渲染 screen.click('increment') # 模拟用户点击 screen.wait_for('1') # 等待异步更新后的结果

常见问题与排查建议

  1. 测试启动失败、报 WebDriver 相关错误:优先确认 ChromeDriver 与 Chrome 版本匹配;在容器 / WSL / ARM 环境优先使用 Selenium Manager,必要时通过CHROME_BINARY_LOCATION指定浏览器路径。
  2. 元素找不到或时快时慢:UI 更新是异步的,优先使用screen.wait_for(...)/screen.wait_for_js(...)轮询等待,而不是固定time.sleepIMPLICIT_WAIT = 4秒是默认隐式等待上限。
  3. 隐藏元素导致的断言失败find()会拒绝不可见元素(is_displayed()为假),这类错误提示已包含"Found but it is hidden",请检查元素是否被折叠、弹窗遮挡或仍在加载。
  4. 断言前端报错:若测试意外失败且日志中出现JavaScript console error,那是CATCH_JS_ERRORS机制捕获到了浏览器控制台的 SEVERE/ERROR 日志,可结合render_js_logs()与失败截图(screenshots/*.failed.png)定位。
  5. 并行运行测试冲突Screen.PORT会在会话开始时自动分配空闲端口,截图目录按进程 ID 隔离(screenshots/<pid>/),多进程并行相对安全。

总结

NiceGUI 的浏览器测试方案可以概括为一条清晰的链路:Selenium Manager(自动驱动管理)→nicegui.testing.plugin(pytest 插件)→ 会话级 Chrome 驱动 →Screen高层 API → 面向文本/元素的断言与交互。它把"测试 UI"从繁琐的 Selenium 样板代码中解放出来,同时保留了"真实浏览器 + 真实 WebSocket + 真实渲染"的端到端可信度。无论你是想为 NiceGUI 应用补上回归测试,还是想借鉴一套成熟的 pytest + Selenium 测试基建,tests/README.md 与 nicegui/testing 模块都是现成的最佳实践范本。

【免费下载链接】niceguiCreate web-based user interfaces with Python. The nice way.项目地址: https://gitcode.com/GitHub_Trending/ni/nicegui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询