目录

用 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  yoffset

3. 坐标转换

测试用例中使用的坐标通常是设计稿坐标,需要转换为实际屏幕坐标:

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_tomove_by_offsetclick
canvas_mouseover 悬浮到图表元素 move_tomove_by_offsetperform
canvas_drag 拖拽图表元素 click_and_holdmove_by_offsetrelease
canvas_click_and_hold 点击并按住 move_by_offsetclick_and_hold
canvas_right_click 右键点击 move_by_offsetcontext_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}'

总结

技术要点

  1. 利用 Selenium ActionChains 实现像素级鼠标控制
  2. 通过坐标偏移 解决 Canvas 无法定位内部元素的问题
  3. 封装成 Robot Framework 关键字 让测试用例更简洁
  4. 视觉回归测试 通过截图/base64 比对确保渲染正确

局限性

  • 坐标需要手动计算或预定义
  • Canvas 尺寸变化时需要重新计算坐标
  • 不适用于动态内容(坐标会变化)

扩展方向

  • 图像识别自动定位坐标
  • 录制回放工具生成测试用例
  • 与其他测试框架集成(Playwright, Cypress 等)