用 Robot Framework 控制 Canvas 元素实践
目录
背景
在自动化测试中,我遇到了一个问题:如何测试 Canvas 图表元素?
与常规 HTML 元素不同,Canvas 是作为一个整体区域对外暴露为一个大元素,内部的图表元素(如柱状图的柱子、地图的省份)不是独立的 DOM 元素,常规的 Selenium 点击方法无法定位到 Canvas 内部的特定位置。
我这边扩展了一下关键字,实现坐标级操作 Canvas 图表元素。
技术架构
┌─────────────────────────────────────────┐
│ Robot Framework │
│ ↓ │
│ CanvasTestResource.robot │
│ (我的自定义关键字层) │
│ ↓ │
│ SeleniumLibrary + ActionChains │
│ ↓ │
│ Selenium WebDriver │
└─────────────────────────────────────────┘核心组件
| 文件 | 作用 |
|---|---|
CanvasTest.robot |
测试用例文件,定义具体的测试场景 |
CanvasTestResource.robot |
自定义关键字库,封装 Canvas 操作 |
element.py |
SeleniumLibrary 扩展,提供坐标级鼠标操作 |
get_image_size.py |
图片校验工具 |
核心原理
1. Canvas 元素的特殊性
Canvas 图表的整个图表区域是一个 <canvas> 元素,内部所有图形都是通过绘制的像素,无法通过 XPath/CSS 选择器定位内部元素。
2. 解决方案:坐标级操作
既然无法定位内部元素,那就直接操作像素坐标:
# 点击 Canvas 图表中坐标 (329, 201) 位置
canvas_click 10 329 201 164 100
# 参数:index width height xoffset yoffset3. 坐标转换
测试用例中使用的坐标通常是设计稿坐标,需要转换为实际屏幕坐标:
get_coordinate
[Documentation] 计算 canvas 元素实际坐标值
[Arguments] ${width_reality} ${height_reality} ${width} ${height} ${xoffset} ${yoffset}
${xoffset_reality} Evaluate ${xoffset}*${width_reality}/${width}-${width_reality}/2
${yoffset_reality} Evaluate ${yoffset}*${height_reality}/${height}-${height_reality}/2
[Return] ${xoffset_reality} ${yoffset_reality}转换公式:
- 将设计稿坐标按比例映射到实际 Canvas 尺寸
- 减去中心点偏移量(因为 ActionChains 从元素中心开始计算)
4. Selenium ActionChains 实现
底层通过 Selenium 的 ActionChains 实现像素级鼠标控制:
@keyword
def click_element_at_coordinates(self, locator, xoffset, yoffset):
"""点击元素指定坐标位置"""
element = self.find_element(locator)
action = ActionChains(self.driver)
action.move_to_element(element) # 1. 移动到元素中心
action.move_by_offset(xoffset, yoffset) # 2. 偏移指定坐标
action.click() # 3. 点击
action.perform() # 4. 执行关键字实现
核心关键字列表
| 关键字 | 功能 | ActionChains 序列 |
|---|---|---|
canvas_click |
点击图表元素 | move_to → move_by_offset → click |
canvas_mouseover |
悬浮到图表元素 | move_to → move_by_offset → perform |
canvas_drag |
拖拽图表元素 | click_and_hold → move_by_offset → release |
canvas_click_and_hold |
点击并按住 | move_by_offset → click_and_hold |
canvas_right_click |
右键点击 | move_by_offset → context_click |
canvas_capture |
截图 | Capture Element Screenshot |
canvas_check_size |
校验大小 | 文件 size 比对 |
canvas_check_base64 |
校验内容 | JS toDataURL() 比对 |
关键字使用示例
*** Test Cases ***
03canvas_点击 (包括定位)
# 预览模板
Wait Until Page Contains Element //*[text()="管理驾驶舱"]
Click Element //*[text()="管理驾驶舱"]
Click Element //*[text()="住房公积金可视化驾驶舱"]
# 切换 iframe
Wait Until Element Is Visible (//*[@class = 'bi-single bi-iframe bi-card'])[2]
Select Frame (//*[@class = 'bi-single bi-iframe bi-card'])[2]
# 点击图表(业务办理人次走势)的中心点
canvas_click 10 329 201 164 100
Sleep 1
# 点击图表横坐标值为 2018-04 的点
canvas_click 10 329 201 126 76
# 点击图表的缩小按钮
canvas_click 1 833 511 29 122视觉回归测试
截图比对
canvas_capture
[Documentation] 截取 canvas 元素并返回文件路径
[Arguments] ${index}
${image_name} Evaluate "image_"+"${index}"+".png"
Capture Element Screenshot (//*[@data-zr-dom-id = 'zr_0'])[${index}] ${OUTPUTDIR}/canvas_test/${image_name}
[Return] ${OUTPUTDIR}/canvas_test/${image_name}
canvas_check_size
[Documentation] 校验图片文件大小
[Arguments] ${file} ${expect_size}
${image_size} Get Image Size ${file}
Should Be True '${image_size}' == '${expect_size}'Base64 比对
canvas_check_base64
[Documentation] 校验 canvas 元素的 base64 值
[Arguments] ${index} ${expect_base64}
${index} Evaluate ${index}-1
${base64} Execute Javascript
... return document.querySelectorAll("canvas[data-zr-dom-id='zr_0']")[${index}].toDataURL("image/png")
Should Be True '${base64}' == '${expect_base64}'总结
技术要点
- 利用 Selenium ActionChains 实现像素级鼠标控制
- 通过坐标偏移 解决 Canvas 无法定位内部元素的问题
- 封装成 Robot Framework 关键字 让测试用例更简洁
- 视觉回归测试 通过截图/base64 比对确保渲染正确
局限性
- 坐标需要手动计算或预定义
- Canvas 尺寸变化时需要重新计算坐标
- 不适用于动态内容(坐标会变化)
扩展方向
- 图像识别自动定位坐标
- 录制回放工具生成测试用例
- 与其他测试框架集成(Playwright, Cypress 等)