Skip to content

Commit 15be2b7

Browse files
DavertMikclaude
andcommitted
feat: add optional context parameter to moveCursorTo
Allow scoping moveCursorTo to a parent element by passing a context locator as the second argument, matching the pattern used by click, seeElement, etc. When the second arg is non-number, it is treated as context instead of offsetX. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9c9f5b6 commit 15be2b7

File tree

7 files changed

+75
-8
lines changed

7 files changed

+75
-8
lines changed

docs/webapi/moveCursorTo.mustache

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
Moves cursor to element matched by locator.
22
Extra shift can be set with offsetX and offsetY options.
33

4+
An optional `context` (as a second parameter) can be specified to narrow the search to an element within a parent.
5+
When the second argument is a non-number (string or locator object), it is treated as context.
6+
47
```js
58
I.moveCursorTo('.tooltip');
69
I.moveCursorTo('#submit', 5,5);
10+
I.moveCursorTo('#submit', '.container');
711
```
812

913
@param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
10-
@param {number} [offsetX=0] (optional, `0` by default) X-axis offset.
14+
@param {number|CodeceptJS.LocatorOrString} [offsetX=0] (optional, `0` by default) X-axis offset or context locator.
1115
@param {number} [offsetY=0] (optional, `0` by default) Y-axis offset.
1216
@returns {void} automatically synchronized promise through #recorder

lib/helper/Playwright.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,8 +1495,23 @@ class Playwright extends Helper {
14951495
*
14961496
*/
14971497
async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
1498-
const el = await this._locateElement(locator)
1499-
assertElementExists(el, locator)
1498+
let context = null
1499+
if (typeof offsetX !== 'number') {
1500+
context = offsetX
1501+
offsetX = 0
1502+
}
1503+
1504+
let el
1505+
if (context) {
1506+
const contextEls = await this._locate(context)
1507+
assertElementExists(contextEls, context, 'Context element')
1508+
el = await findElements.call(this, contextEls[0], locator)
1509+
assertElementExists(el, locator)
1510+
el = el[0]
1511+
} else {
1512+
el = await this._locateElement(locator)
1513+
assertElementExists(el, locator)
1514+
}
15001515

15011516
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
15021517
const { x, y } = await clickablePoint(el)

lib/helper/Puppeteer.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -821,9 +821,27 @@ class Puppeteer extends Helper {
821821
* {{ react }}
822822
*/
823823
async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
824-
const el = await this._locateElement(locator)
825-
if (!el) {
826-
throw new ElementNotFound(locator, 'Element to move cursor to')
824+
let context = null
825+
if (typeof offsetX !== 'number') {
826+
context = offsetX
827+
offsetX = 0
828+
}
829+
830+
let el
831+
if (context) {
832+
const contextPage = await this.context
833+
const contextEls = await findElements.call(this, contextPage, context)
834+
assertElementExists(contextEls, context, 'Context element')
835+
const els = await findElements.call(this, contextEls[0], locator)
836+
if (!els || els.length === 0) {
837+
throw new ElementNotFound(locator, 'Element to move cursor to')
838+
}
839+
el = els[0]
840+
} else {
841+
el = await this._locateElement(locator)
842+
if (!el) {
843+
throw new ElementNotFound(locator, 'Element to move cursor to')
844+
}
827845
}
828846

829847
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates

lib/helper/WebDriver.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,8 +1976,22 @@ class WebDriver extends Helper {
19761976
* {{> moveCursorTo }}
19771977
*/
19781978
async moveCursorTo(locator, xOffset, yOffset) {
1979-
const res = await this._locate(withStrictLocator(locator), true)
1980-
assertElementExists(res, locator)
1979+
let context = null
1980+
if (typeof xOffset !== 'number' && xOffset !== undefined) {
1981+
context = xOffset
1982+
xOffset = undefined
1983+
}
1984+
1985+
let res
1986+
if (context) {
1987+
const contextRes = await this._locate(withStrictLocator(context), true)
1988+
assertElementExists(contextRes, context, 'Context element')
1989+
res = await contextRes[0].$$(withStrictLocator(locator))
1990+
assertElementExists(res, locator)
1991+
} else {
1992+
res = await this._locate(withStrictLocator(locator), true)
1993+
assertElementExists(res, locator)
1994+
}
19811995
const elem = usingFirstElement(res)
19821996
try {
19831997
await elem.moveTo({ xOffset, yOffset })

test/helper/Playwright_test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ describe('Playwright', function () {
276276
I.amOnPage('/form/hover')
277277
.then(() => I.moveCursorTo('#hover', 100, 100))
278278
.then(() => I.dontSee('Hovered', '#show')))
279+
280+
it('should trigger hover event within a context', () =>
281+
I.amOnPage('/form/hover')
282+
.then(() => I.moveCursorTo('#hover', 'body'))
283+
.then(() => I.see('Hovered', '#show')))
279284
})
280285

281286
describe('#switchToNextTab, #switchToPreviousTab, #openNewTab, #closeCurrentTab, #closeOtherTabs, #grabNumberOfOpenTabs, #waitForNumberOfTabs', () => {

test/helper/Puppeteer_test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ describe('Puppeteer', function () {
240240
I.amOnPage('/form/hover')
241241
.then(() => I.moveCursorTo('#hover', 100, 100))
242242
.then(() => I.dontSee('Hovered', '#show')))
243+
244+
it('should trigger hover event within a context', () =>
245+
I.amOnPage('/form/hover')
246+
.then(() => I.moveCursorTo('#hover', 'body'))
247+
.then(() => I.see('Hovered', '#show')))
243248
})
244249

245250
describe('#switchToNextTab, #switchToPreviousTab, #openNewTab, #closeCurrentTab, #closeOtherTabs, #grabNumberOfOpenTabs, #waitForNumberOfTabs', () => {

test/helper/WebDriver_test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,12 @@ describe('WebDriver', function () {
596596
await wd.moveCursorTo('#hover', 100, 100)
597597
await wd.dontSee('Hovered', '#show')
598598
})
599+
600+
it('should trigger hover event within a context', async () => {
601+
await wd.amOnPage('/form/hover')
602+
await wd.moveCursorTo('#hover', 'body')
603+
await wd.see('Hovered', '#show')
604+
})
599605
})
600606

601607
describe('#switchToNextTab, #switchToPreviousTab, #openNewTab, #closeCurrentTab, #closeOtherTabs, #grabNumberOfOpenTabs, #waitForNumberOfTabs', () => {

0 commit comments

Comments
 (0)