You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebdriverIO网页测试中鼠标光标动画实现问题排查

问题:WebdriverIO点击无法触发自定义鼠标动画

我有个WebdriverIO测试脚本,用来自动点击Web应用里的元素完成操作。现在要实现点击间的鼠标指针移动动画,方便生成操作演示视频,而且UI变更时能轻松更新代码。

参考方案加了自定义鼠标元素的代码,测试没报错,DOM里也有img#webdriver-mouse元素,但肉眼看不到鼠标动画。

核心疑问

WebdriverIO的click()函数会不会触发document.onclick函数?


添加的自定义鼠标代码

var mouse = document.createElement('img');
console.log("test");
mouse.setAttribute(
    'src',
    'data:image/png;base64,' +
    'iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAQAAACGG/bgAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA' +
    'HsYAAB7GAZEt8iwAAAAHdElNRQfgAwgMIwdxU/i7AAABZklEQVQ4y43TsU4UURSH8W+XmYwkS2I0' +
    '9CRKpKGhsvIJjG9giQmliHFZlkUIGnEF7KTiCagpsYHWhoTQaiUUxLixYZb5KAAZZhbunu7O/PKf' +
    'e+fcA+/pqwb4DuximEqXhT4iI8dMpBWEsWsuGYdpZFttiLSSgTvhZ1W/SvfO1CvYdV1kPghV68a3' +
    '0zzUWZH5pBqEui7dnqlFmLoq0gxC1XfGZdoLal2kea8ahLoqKXNAJQBT2yJzwUTVt0bS6ANqy1ga' +
    'VCEq/oVTtjji4hQVhhnlYBH4WIJV9vlkXLm+10R8oJb79Jl1j9UdazJRGpkrmNkSF9SOz2T71s7M' +
    'SIfD2lmmfjGSRz3hK8l4w1P+bah/HJLN0sys2JSMZQB+jKo6KSc8vLlLn5ikzF4268Wg2+pPOWW6' +
    'ONcpr3PrXy9VfS473M/D7H+TLmrqsXtOGctvxvMv2oVNP+Av0uHbzbxyJaywyUjx8TlnPY2YxqkD' +
    'dAAAAABJRU5ErkJggg=='
);
mouse.id = 'webdriver-mouse';
mouse.setAttribute(
    'style',
    'position: absolute; z-index: 999999; pointer-events: none; left:0; top:0'
);
console.log(mouse);
document.appendChild(mouse);
document.body.addEventListener('click', (event) => {
    console.log(event.button)
});
document.onclick = function(e) {
    console.log(e);
    body.getElementById('webdriver-mouse').style.left = e.pageX + 'px';
    body.getElementById('webdriver-mouse').style.top = e.pageY + 'px';
};

测试规范文件(lock-element.spec.js)

import Studio from '../pageobjects/studio.page'; 
import { Key } from 'webdriverio';

describe('Locking an Element', () => {
    beforeEach(async () => {  
        await browser.setWindowSize(1920, 1080);
        await browser.pause(3000);
        await browser.url('/login');
        await(await Studio.usernameField).click();
        await browser.keys('wdio@example.com')
        await(await Studio.passwordInput).click();
        await browser.keys('password');
        await(await Studio.loginSubmit).click();
        await browser.pause(6000);
    });
    afterEach(() => {
        browser.execute('window.localStorage.clear()');     
        browser.deleteAllCookies();
        browser.execute('sessionStorage.clear()');
        browser.refresh();
        browser.pause(3000);
    });
    it('Should lock an element on the canvas', async () => {
        browser.execute(() => {
            var mouse = document.createElement('img');
            mouse.setAttribute(
               'src',
            'data:image/png;base64,' +
            'iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAQAAACGG/bgAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA' +
            'HsYAAB7GAZEt8iwAAAAHdElNRQfgAwgMIwdxU/i7AAABZklEQVQ4y43TsU4UURSH8W+XmYwkS2I0' +
            '9CRKpKGhsvIJjG9giQmliHFZlkUIGnEF7KTiCagpsYHWhoTQaiUUxLixYZb5KAAZZhbunu7O/PKf' +
            'e+fcA+/pqwb4DuximEqXhT4iI8dMpBWEsWsuGYdpZFttiLSSgTvhZ1W/SvfO1CvYdV1kPghV68a3' +
            '0zzUWZH5pBqEui7dnqlFmLoq0gxC1XfGZdoLal2kea8ahLoqKXNAJQBT2yJzwUTVt0bS6ANqy1ga' +
            'VCEq/oVTtjji4hQVhhnlYBH4WIJV9vlkXLm+10R8oJb79Jl1j9UdazJRGpkrmNkSF9SOz2T71s7M' +
            'SIfD2lmmfjGSRz3hK8l4w1P+bah/HJLN0sys2JSMZQB+jKo6KSc8vLlLn5ikzF4268Wg2+pPOWW6' +
            'ONcpr3PrXy9VfS473M/D7H+TLmrqsXtOGctvxvMv2oVNP+Av0uHbzbxyJaywyUjx8TlnPY2YxqkD' +
            'dAAAAABJRU5ErkJggg=='
            );
            mouse.id = 'webdriver-mouse';
            mouse.setAttribute(
                'style',
                'position: absolute; z-index: 999999; pointer-events: none; left:0; top:0'
            );
            document.appendChild(mouse);
            document.onclick = function(e) {
                console.log(e);
                body.getElementById('webdriver-mouse').style.left = e.pageX + 'px';
                body.getElementById('webdriver-mouse').style.top = e.pageY + 'px';
            };
        });
        await... // 剩余测试代码
        ...
    });
});

wdio.conf.js配置

const { join } = require('path')

exports.config = {
    specs: [
        './test/specs/**/lock-element.spec.js',
    ],
    maxInstances: 10,
    capabilities: [
        {
            maxInstances: 6,
            browserName: 'chrome',
            'goog:chromeOptions': {
                args: ['--disable-prompt-on-repost','--window-size=1920,1080'],
                prefs: {
                    'profile.managed_default_content_settings.popups' : 2,
                    'profile.managed_default_content_settings.notifications' : 2,
                }
            },
            acceptInsecureCerts: true
         },
    ],
    logLevel: 'error',
    bail: 0,
    baseUrl: 'https://staging.mandoemedia.com',
    waitforTimeout: 10000,
    connectionRetryTimeout: 120000,
    connectionRetryCount: 3,
    services:  [['image-comparison',
      {
        baselineFolder: join(process.cwd(), './tests/visualRegressionBaseline/'),
        formatImageName: '{tag}-{logName}',
        screenshotPath: join(process.cwd(), './tests/visualRegressionDiff/'),
        autoSaveBaseline: true,
        blockOutStatusBar: true,
        blockOutToolBar: true,
        clearRuntimeFolder: true,
        disableCSSAnimation: true
      }]
    ],
    framework: 'mocha',
    reporters: ['spec'/*['Allure',{
        outputDir:'allure-results',
        disableWebdriverStepsReporting: true,
        disableWebdriverScreenshotsReporting: false,
    }]*/],
    mochaOpts: {
        ui: 'bdd',
        timeout: 6000000000
    },
};

问题分析与解决

1. WebdriverIO的click()是否触发document.onclick?

WebdriverIO的click()是通过WebDriver协议直接模拟点击,不会触发浏览器原生的document.onclick事件——因为它绕过了用户交互的事件冒泡流程,直接向元素发送点击指令。这就是你的鼠标动画没触发的核心原因。

2. 现有代码的其他问题

  • 挂载元素错误:应该把鼠标元素添加到document.body而非document,document.appendChild()不符合DOM规范,正确写法是document.body.appendChild(mouse)。
  • 变量未定义:document.onclick回调里的body未声明,应该用document.body或者直接document.getElementById。
  • 动画缺失:当前代码只在点击时移动鼠标,没有实现点击间的平滑移动动画,需要添加过渡效果。

修复后的代码示例

在browser.execute()里替换成以下代码:

// 创建鼠标元素
const mouse = document.createElement('img');
mouse.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAQAAACGG/bgAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAHsYAAB7GAZEt8iwAAAAHdElNRQfgAwgMIwdxU/i7AAABZklEQVQ4y43TsU4UURSH8W+XmYwkS2I09CRKpKGhsvIJjG9giQmliHFZlkUIGnEF7KTiCagpsYHWhoTQaiUUxLixYZb5KAAZZhbunu7O/PKf+fcA+/pqwb4DuximEqXhT4iI8dMpBWEsWsuGYdpZFttiLSSgTvhZ1W/SvfO1CvYdV1kPghV68a30zzUWZH5pBqEui7dnqlFmLoq0gxC1XfGZdoLal2kea8ahLoqKXNAJQBT2yJzwUTVt0bS6ANqy1gaVCEq/oVTtjji4hQVhhnlYBH4WIJV9vlkXLm+10R8oJb79Jl1j9UdazJRGpkrmNkSF9SOz2T71s7MSIfD2lmmfjGSRz3hK8l4w1P+bah/HJLN0sys2JSMZQB+jKo6KSc8vLlLn5ikzF4268Wg2+pPOWW6ONcpr3PrXy9VfS473M/D7H+TLmrqsXtOGctvxvMv2oVNP+Av0uHbzbxyJaywyUjx8TlnPY2YxqkDdAAAAABJRU5ErkJggg==';
mouse.id = 'webdriver-mouse';
mouse.style.cssText = `
    position: absolute; 
    z-index: 999999; 
    pointer-events: none; 
    left: 0; 
    top: 0;
    transition: all 0.2s ease; /* 添加平滑过渡动画 */
    transform: translate(-50%, -50%); /* 让鼠标中心对准点击位置 */
`;
document.body.appendChild(mouse); // 正确挂载到body

// 手动控制鼠标移动函数
window.moveCustomMouse = async (x, y) => {
    mouse.style.left = `${x}px`;
    mouse.style.top = `${y}px`;
    await new Promise(resolve => setTimeout(resolve, 200)); // 等待动画完成
};

测试脚本中调用移动函数

替换原来的element.click()为以下逻辑:

// 获取元素位置
const element = await Studio.usernameField;
const rect = await element.getRect();
// 计算元素中心坐标
const centerX = rect.x + rect.width / 2;
const centerY = rect.y + rect.height / 2;
// 移动自定义鼠标到元素位置
await browser.execute((x, y) => window.moveCustomMouse(x, y), centerX, centerY);
// 执行点击
await element.click();

额外优化

  • 隐藏原生鼠标:在Chrome启动参数里添加--hide-cursor,避免两个鼠标重叠。
  • 录制视频:使用wdio-video-reporter插件直接录制测试过程,无需额外处理。

内容的提问来源于stack exchange,提问作者Steve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 00:25:55