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
相关产品推荐
相关产品推荐

