RPA自动化测试:Python+Playwright+Sure构建高可靠断言体系

发布时间:2026/6/23 21:52:19
RPA自动化测试:Python+Playwright+Sure构建高可靠断言体系
1. 项目概述为什么需要“RPAPythonSure”这套组合拳如果你正在看这篇文章大概率是遇到了自动化测试或者业务流程自动化中的一个经典痛点脚本写了一大堆但验证环节总是很脆弱。要么是元素定位不稳定要么是断言逻辑太简单要么是错误处理一团糟导致自动化流程像个“玻璃心”一碰就碎。这正是我当初从纯UI自动化转向RPA机器人流程自动化时花了大量时间踩坑和优化的地方。“RPA-Python与Sure集成终极指南”这个标题听起来像是一个技术教程但它的核心价值远不止于此。它解决的是一套工程化、高可靠性的自动化验证体系的落地问题。RPA负责模拟人的操作驱动浏览器、桌面应用Python作为胶水语言提供了强大的逻辑控制和数据处理能力而Sure这个你可能不太熟悉的断言库则是这套体系的“质检员”和“稳定器”。它的作用不是简单地判断a b而是提供了一套丰富、可读性强、且异常信息极其友好的断言机制让你的自动化脚本在失败时能清晰地告诉你“哪里不对”、“为什么不对”而不是抛出一堆让人摸不着头脑的异常堆栈。简单来说这套组合能帮你把自动化从“能跑起来”升级到“跑得稳、错得明、好维护”。无论是处理网页表单的自动填写与校验还是执行跨系统的数据核对任务一个健壮的断言机制都是保证流程正确性的最后一道也是最重要的一道防线。接下来我会以一个真实的电商后台订单处理RPA流程为例带你从零开始搭建这套体系并深入每个环节的“为什么”和“怎么做”。2. 环境搭建与核心工具选型解析工欲善其事必先利其器。在开始写代码之前选择合适的工具链并正确配置环境能避免后续80%的“玄学”问题。我们的技术栈非常明确Python作为主语言Playwright作为RPA的“手和眼”Sure作为“大脑”中的逻辑判断单元。2.1 Python环境与包管理为什么是Poetry首先我强烈建议你放弃使用系统自带的Python也谨慎使用Anaconda这种“全家桶”来管理RPA项目。我们的目标是创建一个干净、可复现的依赖环境。这里我推荐使用pyenv管理Python版本用Poetry管理项目依赖。为什么是Poetry而不是pip因为RPA项目依赖复杂可能涉及浏览器驱动、系统库等。Poetry不仅能精准锁定每个包的版本还能很好地处理子依赖冲突并且其pyproject.toml文件让项目配置一目了然。用pipvirtualenv当然也可以但维护起来更繁琐。# 1. 使用pyenv安装指定版本的Python以3.10为例这是一个兼容性很好的版本 pyenv install 3.10.13 pyenv local 3.10.13 # 2. 安装Poetry curl -sSL https://install.python-poetry.org | python3 - # 3. 创建项目目录并初始化 mkdir rpa-sure-project cd rpa-sure-project poetry init -n # 交互式创建按需填写项目信息 poetry env use 3.10.13初始化后你的pyproject.toml文件会是项目的核心配置。2.2 RPA驱动核心为什么选择Playwright而非Selenium这是另一个关键选择。在UI自动化领域Selenium是老牌王者但对于RPA场景Playwright的优势是决定性的自动等待Playwright的大部分操作内置了智能等待无需你手动写time.sleep或复杂的WebDriverWait极大地减少了因页面加载慢导致的失败。多浏览器支持一套API支持Chromium、Firefox和WebKitSafari且浏览器自动下载开箱即用。强大的选择器支持文本选择器、React/Vue组件选择器等定位元素更灵活稳定。网络拦截与Mock可以轻松拦截和修改网络请求这对于测试和模拟异常场景至关重要。录制与代码生成其官方录制工具能快速生成基础脚本是RPA开发的绝佳起点。因此我们将Playwright作为我们的RPA驱动核心。# 使用Poetry添加Playwright依赖 poetry add playwright # 安装Playwright所需的浏览器这可能需要一些时间 poetry run playwright install chromium2.3 断言库Sure的独特价值与安装终于轮到主角之一——Sure。Python自带的assert关键字和unittest、pytest的断言功能已经很强大了为什么还要用Sure可读性即一切assert user.name ‘Admin’在失败时只会告诉你False is not True。而sure(user.name).should.equal(‘Admin’)的失败信息会是AssertionError: expected ‘Guest’ to equal ‘Admin’。一眼就知道是用户名不对。丰富的断言方法支持包含、匹配、类型检查、异常断言等语法更接近自然语言。链式调用可以组合多个检查如sure(response).should.be.a(‘dict’).and.have.key(‘status’).which.should.equal(‘success’)。与测试框架无缝集成虽然Sure本身不依赖测试框架但其清晰的错误信息能完美融入pytest等框架的报告中。安装非常简单poetry add sure至此我们的核心武器库就备齐了Poetry管理的Python 3.10环境Playwright作为自动化驱动Sure作为断言核心。接下来我们开始设计一个真实的业务流程。3. 核心业务流程设计与Sure断言策略规划让我们设定一个具体的RPA场景自动登录电商后台查询过去24小时的订单并将订单金额大于500元的订单ID和金额提取出来汇总后通过邮件发送给运营人员。这个流程看似简单但包含了多个需要验证的关键节点正是Sure大显身手的地方。我们先拆解流程并规划每个步骤的断言策略登录成功断言登录后跳转的页面URL包含“dashboard”或页面中出现“欢迎[用户名]”的元素。查询条件设置正确断言时间选择器被成功设置为“过去24小时”状态筛选为“全部”。查询结果返回断言页面不再显示“加载中”动画并且订单列表容器中有元素出现即使为空。数据提取正确断言从页面中提取的订单数据是列表格式并且每个订单项都包含“订单ID”和“金额”字段。金额过滤逻辑正确断言过滤后得到的“高金额订单”列表其中每个订单的金额都大于500。汇总数据正确断言计算出的总金额与过滤后的订单金额之和相等。邮件发送触发断言调用邮件发送函数后返回了成功状态例如返回了消息ID或True。注意在RPA中断言不仅用于“测试”更是流程的决策点。例如如果“登录成功”断言失败流程不应继续执行查询而应触发异常处理流程如重试、报警。Sure清晰的错误信息能帮助我们快速定位决策分支。基于这个设计我们开始编写核心代码模块。4. 核心模块实现与Sure断言深度集成我们将代码组织成几个模块化的文件以提高可维护性。4.1 页面对象模型与基础操作封装首先我们使用Page Object Model模式封装登录页面和订单查询页面。这里不仅封装操作更封装了验证点。pages/login_page.py:from playwright.sync_api import Page import sure class LoginPage: def __init__(self, page: Page): self.page page self.username_input page.locator(‘#username’) self.password_input page.locator(‘#password’) self.submit_button page.locator(‘button[type“submit”]’) self.welcome_message page.locator(‘.welcome-msg’) # 登录成功后出现的元素 def navigate(self, url): self.page.goto(url) # 断言页面成功加载标题包含“登录”或特定元素可见 sure(self.page.title()).should.contain(‘登录’) def login(self, username, password): self.username_input.fill(username) self.password_input.fill(password) self.submit_button.click() # **关键断言1登录成功** # 等待欢迎信息出现并断言其文本包含用户名 self.welcome_message.wait_for(state“visible”) welcome_text self.welcome_message.text_content() # 使用Sure进行包含断言失败信息非常明确 sure(welcome_text).should.contain(username) # 同时也可以断言URL跳转到了后台页 sure(self.page.url).should.contain(‘/dashboard’) print(f“✅ 登录成功欢迎信息: {welcome_text}”)这里的关键是我们将断言直接写在了操作函数里。sure(welcome_text).should.contain(username)如果失败会明确告诉你期待包含什么实际得到了什么远比一个简单的assert username in welcome_text友好。4.2 订单查询与数据提取模块pages/order_page.py:import sure from playwright.sync_api import Page, expect import re class OrderPage: def __init__(self, page: Page): self.page page self.time_filter page.locator(‘.time-filter-select’) self.search_button page.locator(‘#search-orders’) self.loading_indicator page.locator(‘.loading-spinner’) self.order_rows page.locator(‘table#order-list tbody tr’) self.no_data_placeholder page.locator(‘.no-data’) def query_last_24_hours(self): # 操作选择筛选条件 self.time_filter.select_option(label“过去24小时”) self.search_button.click() # **关键断言2查询完成** # 等待加载动画消失 self.loading_indicator.wait_for(state“hidden”) # 断言要么有订单行要么显示‘无数据’提示两者必居其一。这避免了因列表为空而误判为失败。 sure( self.order_rows.count() 0 or self.no_data_placeholder.is_visible() ).should.be.true(‘查询完成后应显示订单列表或无数据提示’) def extract_order_data(self): 从订单表格中提取数据 orders [] count self.order_rows.count() if count 0: print(“⚠️ 没有查询到订单数据。”) # **关键断言3数据提取结构** # 即使无数据也返回一个空列表保证后续流程类型一致 sure(orders).should.be.a(‘list’).and.have.length_of(0) return orders for i in range(count): row self.order_rows.nth(i) order_id row.locator(‘td:nth-child(2)’).text_content() amount_text row.locator(‘td:nth-child(5)’).text_content() # 清洗数据去除货币符号和千分位逗号 amount float(re.sub(r‘[^\d.]’, ‘’, amount_text)) orders.append({ ‘order_id’: order_id.strip(), ‘amount’: amount }) # **关键断言4提取的数据格式正确** # 1. 断言结果是列表 sure(orders).should.be.a(‘list’) # 2. 断言列表长度与页面行数一致 sure(orders).should.have.length_of(count) # 3. 断言列表中的每个元素都是字典且包含必要的键 for order in orders: sure(order).should.be.a(‘dict’) sure(order).should.contain(‘order_id’, ‘amount’) # 额外断言金额是数字类型 sure(order[‘amount’]).should.be.a(‘float’).or_be.a(‘int’) print(f“✅ 成功提取 {len(orders)} 条订单数据。”) return orders在这个模块中我们看到了Sure更强大的用法链式断言.should.be.a(‘list’).and.have.length_of(count)和对集合中每个元素的遍历断言。这确保了从页面这个“非结构化”环境中提取出来的数据是“结构化”且符合预期的为后续处理打下了坚实基础。4.3 业务逻辑与过滤模块core/order_processor.py:import sure class OrderProcessor: staticmethod def filter_high_value_orders(orders, threshold500): 过滤出金额大于阈值的订单 # **关键断言5输入有效性** sure(orders).should.be.a(‘list’) high_value_orders [order for order in orders if order[‘amount’] threshold] # **关键断言6过滤逻辑正确性** # 断言过滤后的所有订单都满足条件 for order in high_value_orders: sure(order[‘amount’]).should.be.greater_than(threshold) # 也可以断言过滤前的订单中不满足条件的订单没有被包含进来可选 # 这是一个“负向断言”的例子 for order in orders: if order[‘amount’] threshold: sure(order).should.not_be.within(high_value_orders) print(f“ 过滤出 {len(high_value_orders)} 笔金额大于 {threshold} 的订单。”) return high_value_orders staticmethod def summarize_orders(orders): 汇总订单总金额 total sum(order[‘amount’] for order in orders) # **关键断言7汇总计算正确性** # 这里可以做一个简单的交叉验证如果订单数多可以用近似断言 # sure(total).should.equal(sum(o[‘amount’] for o in orders)) # 这行是废话 # 更实际的断言总额应该大于等于0 sure(total).should.be.greater_than_or_equal_to(0) # 如果orders为空总额应为0 if len(orders) 0: sure(total).should.equal(0) return total业务逻辑模块的断言侧重于数据一致性和业务规则。filter_high_value_orders中的循环断言确保了过滤算法的正确性这是单元测试级别的严谨性被应用到了RPA流程中。4.4 主流程编排与错误处理main.py:import sure from playwright.sync_api import sync_playwright from pages.login_page import LoginPage from pages.order_page import OrderPage from core.order_processor import OrderProcessor from utils.notifier import send_email_notification # 假设有一个发送邮件的工具函数 import sys def main(): # 初始化Playwright with sync_playwright() as p: # 建议使用 headedFalse 用于无头环境运行调试时可设为 True browser p.chromium.launch(headlessFalse, slow_mo500) # slow_mo 让操作变慢方便观察 context browser.new_context() page context.new_page() try: # 1. 登录 login_page LoginPage(page) login_page.navigate(‘https://admin.example.com/login’) login_page.login(‘your_username’, ‘your_password’) # 2. 查询订单 order_page OrderPage(page) order_page.query_last_24_hours() # 3. 提取数据 all_orders order_page.extract_order_data() # 4. 处理数据 high_value_orders OrderProcessor.filter_high_value_orders(all_orders, threshold500) total_amount OrderProcessor.summarize_orders(high_value_orders) # 5. 准备报告并发送 report f“过去24小时高价值订单500元报告\n” report f“订单数量{len(high_value_orders)}\n” report f“总金额{total_amount:.2f}元\n” report “---\n” for order in high_value_orders: report f“订单ID{order[‘order_id’]} 金额{order[‘amount’]:.2f}元\n” # **关键断言8邮件发送触发** # 假设 send_email_notification 成功时返回 True 或 message_id result send_email_notification( to‘opsexample.com’, subject‘每日高价值订单汇总’, bodyreport ) sure(result).should.be.ok # 断言结果为真值非None, 非False, 非空 # 或者更精确的断言取决于你的函数返回值 # sure(result).should.be.a(‘str’).and.match(r‘^.*$’) # 匹配邮件消息ID格式 print(“ RPA流程执行完毕报告已发送。”) except Exception as e: # Sure抛出的AssertionError在这里会被捕获 print(f“❌ 流程执行失败: {e}”) # 这里可以添加截图、日志上报等错误处理逻辑 page.screenshot(path‘error_screenshot.png’) # 可以选择重试或直接退出 sys.exit(1) finally: browser.close() if __name__ ‘__main__’: main()在主流程中我们看到了整个RPA脚本的骨架。try-except块捕获了包括Sure断言失败AssertionError在内的所有异常。一旦任何环节的断言失败流程就会中止并执行错误处理如截图这保证了流程的原子性和可追溯性。5. 高级技巧Sure的灵活运用与自定义断言掌握了基础用法后来看看Sure如何解决更复杂场景。5.1 处理异步操作与动态内容RPA中经常需要等待某个条件成立。Sure可以与Playwright的expect结合或者自己包装等待逻辑。import sure from playwright.sync_api import Page, expect import time def wait_for_order_status(page: Page, order_id, expected_status, timeout30): 等待指定订单的状态变为期望值 start_time time.time() while time.time() - start_time timeout: # 动态获取当前状态假设有一个刷新状态的函数 current_status get_order_status_from_api(order_id) # 假设的函数 if current_status expected_status: # 使用Sure断言成功但更主要是退出循环 sure(current_status).should.equal(expected_status) # 这行在成功时也会执行强化验证 print(f“订单 {order_id} 状态已变为 {expected_status}”) return True time.sleep(2) # 如果超时用Sure抛出清晰的断言错误 sure(current_status).should.equal(expected_status) # 这行会在超时后执行抛出错误 # 实际上上面的sure会在超时后因为断言失败而抛出异常但我们也可以自定义错误信息 raise AssertionError(f“等待订单{order_id}状态变为{expected_status}超时当前状态为{current_status}”)5.2 自定义断言函数提高复用性对于项目中反复出现的复杂验证逻辑可以封装成自定义的断言函数。import sure def should_be_valid_order(order_data): 自定义断言验证一个字典是否符合有效订单的格式 # 使用Sure的内部断言机制 with sure.AssertionContext(‘无效的订单数据’) as ctx: # 必须包含的字段 required_keys [‘order_id’, ‘amount’, ‘create_time’, ‘status’] for key in required_keys: sure(order_data).should.have.key(key) # 订单ID格式以‘ORD’开头后接数字 sure(order_data[‘order_id’]).should.match(r‘^ORD\d$’) # 金额必须为正数 sure(order_data[‘amount’]).should.be.greater_than(0) # 状态必须是预定义的几种之一 valid_statuses [‘pending’, ‘paid’, ‘shipped’, ‘completed’, ‘cancelled’] sure(order_data[‘status’]).should.be.within(valid_statuses) # 如果所有断言通过则返回True或者可以返回order_data本身 return True # 使用自定义断言 order {‘order_id’: ‘ORD1001’, ‘amount’: 299, ‘create_time’: ‘…’, ‘status’: ‘paid’} should_be_valid_order(order) # 如果order无效这里会抛出包含详细信息的AssertionError5.3 断言集合与复杂数据结构Sure对集合的断言非常强大。import sure # 假设我们有一个订单列表 all_orders […] # 断言列表不为空 sure(all_orders).should_not.be.empty # 断言列表中的每个元素都满足某个条件所有订单金额0 sure.all(all_orders, lambda o: o[‘amount’] 0) # 断言列表中至少有一个元素满足某个条件存在金额1000的订单 sure.any(all_orders, lambda o: o[‘amount’] 1000) # 断言列表的排序例如按金额降序 sorted_orders sorted(all_orders, keylambda x: x[‘amount’], reverseTrue) sure(all_orders).should.equal(sorted_orders) # 注意这要求列表完全相等包括顺序6. 常见问题排查与实战避坑指南在实际运行中你一定会遇到各种问题。下面是我总结的一些典型场景和解决方案。6.1 元素定位失败RPA流程的“头号杀手”问题page.locator(‘…’).click()抛出TimeoutError或Element not found。排查思路优先使用Playwright的录制工具手动操作一遍让Playwright Codegen生成选择器这是最可靠的方式。使用更稳定的选择器文本选择器page.locator(‘text登录’).click()。对于按钮文本非常有效。CSS属性page.locator(‘button[data-testid“submit-btn”]’).click()。XPath慎用除非别无选择XPath往往更脆弱。添加足够的等待虽然Playwright有自动等待但复杂页面仍需显式等待。# 不好 page.locator(‘.dynamic-content’).click() # 好 page.locator(‘.dynamic-content’).wait_for(state“visible”) page.locator(‘.dynamic-content’).click()检查iframe或Shadow DOM元素可能嵌套在iframe或Shadow Root内需要先定位到正确的上下文。截图辅助调试在定位失败前后截图page.screenshot(path‘debug.png’)。6.2 断言失败如何解读Sure的错误信息问题AssertionError: expected ‘Guest’ to equal ‘Admin’。排查不要只看最后一行Sure的错误信息已经很清楚直接告诉你期望值Admin和实际值Guest。检查前置操作断言失败通常是前面操作没达到预期效果。比如登录失败所以用户名是Guest。检查页面状态在断言前打印或记录相关变量的值。用page.screenshot()看看当时的页面长什么样。区分“逻辑错误”和“环境问题”逻辑错误你的代码逻辑不对比如选择器写错了流程跳错了。环境问题页面加载慢、网络波动、测试数据被更改。这类问题需要加入重试机制。6.3 流程脆弱如何提高RPA脚本的健壮性实现重试机制对于网络请求、点击按钮等可能因瞬时问题失败的操作封装一个重试装饰器。import time from functools import wraps def retry_on_failure(max_attempts3, delay2): def decorator(func): wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: if attempt max_attempts - 1: raise print(f”{func.__name__} 第{attempt1}次尝试失败: {e}{delay}秒后重试…”) time.sleep(delay) return wrapper return decorator retry_on_failure() def click_submit_button(page): page.locator(‘#submit’).click()使用配置化将URL、账号、密码、阈值、收件人等变量提取到配置文件如config.yaml或环境变量中避免硬编码。添加详尽的日志使用Python的logging模块记录流程关键步骤、决策和数据。当断言失败时日志能帮你还原现场。实施监控告警将RPA脚本部署到服务器后通过日志聚合工具监控其运行状态。一旦流程失败断言失败导致异常退出立即触发告警如发送邮件、钉钉消息。6.4 Sure断言本身不生效问题写了sure(1).should.equal(2)但脚本没有报错就继续执行了。排查检查导入确认导入的是sure库并且没有与其他名为sure的变量冲突。理解Sure的工作原理Sure的断言在失败时会抛出AssertionError。如果你的代码用try…except Exception捕获了所有异常并且没有重新抛出或处理那么断言失败就会被“吞掉”。try: sure(1).should.equal(2) # 会抛出AssertionError except Exception as e: print(f“捕获到异常: {e}”) # 如果不做处理流程会继续 # 对于断言失败通常应该记录日志并终止或重试流程 raise # 重新抛出异常最佳实践是在业务流程的关键断言处不要轻易捕获AssertionError或者捕获后要明确进行失败处理。7. 项目组织、部署与持续集成建议一个可维护的RPA项目代码组织至关重要。rpa-order-report/ ├── pyproject.toml # Poetry依赖管理 ├── .env.example # 环境变量示例 ├── config/ │ └── settings.yaml # 配置文件 ├── src/ │ ├── main.py # 主入口 │ ├── pages/ # 页面对象 │ │ ├── __init__.py │ │ ├── login_page.py │ │ └── order_page.py │ ├── core/ # 核心业务逻辑 │ │ ├── __init__.py │ │ └── order_processor.py │ ├── utils/ # 工具函数 │ │ ├── __init__.py │ │ ├── notifier.py # 邮件/通知 │ │ └── logger.py # 日志配置 │ └── tests/ # 单元测试可选但推荐 │ ├── __init__.py │ └── test_processor.py ├── logs/ # 日志目录.gitignore ├── screenshots/ # 错误截图目录.gitignore └── README.md部署与运行环境隔离始终在虚拟环境Poetry创建中运行。无头模式生产环境运行时将launch参数设为headlessTrue。计划任务使用系统的cronLinux或Task SchedulerWindows定时运行脚本。使用进程管理对于重要的生产流程使用systemd或supervisor来管理进程实现自动重启。持续集成 即使RPA脚本主要用于生产自动化也建议为其编写单元测试特别是针对core目录下的逻辑模块并集成到CI/CD管道中如GitHub Actions, GitLab CI。这能确保你对业务逻辑的修改不会引入回归错误。可以使用pytest框架它天然兼容Sure的断言。我个人在多个RPA项目中实践这套“Python Playwright Sure”的组合后最大的体会是可靠性来自于对每一个微小环节的明确验证。Sure断言就像给自动化流程装上了“检查点”每一个检查点的清晰报错都能让你快速定位问题是出在数据、网络、页面还是逻辑上。它强迫你在开发时就思考“这里应该是什么状态”从而写出更健壮、更易于维护的脚本。开始可能会觉得写断言有些繁琐但当你半夜被一个模糊的“脚本失败了”的报警叫醒却能立刻从错误信息中看到“expected ‘Payment Successful’ to equal ‘Order Confirmed’”时你会感谢当初写了这个断言。