import logger from '@wdio/logger' import { getBrowserObject } from '@wdio/utils' import type { MoveToOptions } from '../../types.js' const log = logger('webdriver') /** * * Move the mouse by an offset of the specified element. If no element is specified, * the move is relative to the current mouse cursor. If an element is provided but * no offset, the mouse will be moved to the center of the element. If the element * is not visible, it will be scrolled into view. * * @param {MoveToOptions=} options moveTo command options * @param {Number=} options.xOffset X offset to move to, relative to the center of the element. If not specified, the mouse will move to the center of the element. * @param {Number=} options.yOffset Y offset to move to, relative to the center of the element. If not specified, the mouse will move to the center of the element. * * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidmoveto * @type protocol */ export async function moveTo ( this: WebdriverIO.Element, { xOffset, yOffset }: MoveToOptions = {}, ) { /** * W3C way of handle the mouse move actions */ const browser = getBrowserObject(this) if (xOffset || yOffset) { const { width, height } = await browser.getElementRect(this.elementId) if ((xOffset && xOffset < (-Math.floor(width / 2))) || (xOffset && xOffset > Math.floor(width / 2))) { log.warn('xOffset would cause a out of bounds error as it goes outside of element') } if ((yOffset && yOffset < (-Math.floor(height / 2))) || (yOffset && yOffset > Math.floor(height / 2))) { log.warn('yOffset would cause a out of bounds error as it goes outside of element') } } const moveToNested = async () => { await browser.action('pointer', { parameters: { pointerType: 'mouse' } }) .move({ origin: this, x: xOffset || 0, y: yOffset || 0 }) .perform() } try { await moveToNested() } catch { /** * Workaround, because sometimes browser.action().move() flaky and isn't able to scroll pointer to into view * Moreover the action with 'nearest' behavior by default where element is aligned at the bottom of its ancestor. * and could be overlapped. Scroll to center should definitely work even if element was covered with sticky header/footer */ await this.scrollIntoView({ block: 'center', inline: 'center' }) await moveToNested() } }