Skip to content

Commit d5bccb3

Browse files
committed
feat: getElement function
1 parent 2cb431c commit d5bccb3

File tree

2 files changed

+83
-46
lines changed

2 files changed

+83
-46
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ Toggles random mouse movements on or off.
115115
Simulates a mouse click at the specified selector or element.
116116

117117
- **selector (optional):** CSS selector or ElementHandle to identify the target element.
118-
- **options (optional):** Additional options for clicking. **Extends the `options` of the `move` and `scrollIntoView` functions (below)**
118+
- **options (optional):** Additional options for clicking. **Extends the `options` of the `move`, `scrollIntoView`, and `getElement` functions (below)**
119119
- `hesitate (number):` Delay before initiating the click action in milliseconds. Default is `0`.
120120
- `waitForClick (number):` Delay between mousedown and mouseup in milliseconds. Default is `0`.
121121
- `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `2000`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.
@@ -125,10 +125,9 @@ Simulates a mouse click at the specified selector or element.
125125
Moves the mouse to the specified selector or element.
126126

127127
- **selector:** CSS selector or ElementHandle to identify the target element.
128-
- **options (optional):** Additional options for moving. **Extends the `options` of the `scrollIntoView` function (below)**
128+
- **options (optional):** Additional options for moving. **Extends the `options` of the `scrollIntoView` and `getElement` functions (below)**
129129
- `paddingPercentage (number):` Percentage of padding to be added inside the element when determining the target point. Default is `0` (may move to anywhere within the element). `100` will always move to center of element.
130130
- `destination (Vector):` Destination to move the cursor to, relative to the top-left corner of the element. If specified, `paddingPercentage` is not used. If not specified (default), destination is random point within the `paddingPercentage`.
131-
- `waitForSelector (number):` Time to wait for the selector to appear in milliseconds. Default is to not wait for selector.
132131
- `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `0`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.
133132
- `randomizeMoveDelay (boolean):` Randomize delay between actions from `0` to `moveDelay`. Default is `true`.
134133
- `maxTries (number):` Maximum number of attempts to mouse-over the element. Default is `10`.
@@ -145,16 +144,24 @@ Moves the mouse to the specified destination point.
145144
- `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `0`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.
146145
- `randomizeMoveDelay (boolean):` Randomize delay between actions from `0` to `moveDelay`. Default is `true`.
147146

148-
#### `scrollIntoView(element: ElementHandle, options?: ScrollOptions) => Promise<void>`
147+
#### `scrollIntoView(selector: string | ElementHandle, options?: ScrollOptions) => Promise<void>`
149148

150149
Scrolls the element into view. If already in view, no scroll occurs.
151150

152-
- **element:** ElementHandle to identify the target element.
153-
- **options (optional):** Additional options for scrolling.
151+
- **selector:** CSS selector or ElementHandle to identify the target element.
152+
- **options (optional):** Additional options for scrolling. **Extends the `options` of the `getElement` function (below)**
154153
- `scrollSpeed (number):` Scroll speed (when scrolling occurs). 0 to 100. 100 is instant. Default is `100`.
155154
- `scrollDelay (number):` Time to wait after scrolling (when scrolling occurs). Default is `200`.
156155
- `inViewportMargin (number):` Margin (in px) to add around the element when ensuring it is in the viewport. Default is `0`.
157156

157+
#### `getElement(selector: string | ElementHandle, options?: GetElementOptions) => Promise<void>`
158+
159+
Gets the element via a selector. Can use an XPath.
160+
161+
- **selector:** CSS selector or ElementHandle to identify the target element.
162+
- **options (optional):** Additional options.
163+
- `waitForSelector (number):` Time to wait for the selector to appear in milliseconds. Default is to not wait for selector.
164+
158165
#### `getLocation(): Vector`
159166

160167
Get current location of the cursor.

src/spoof.ts

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ export interface BoxOptions {
3535
readonly destination?: Vector
3636
}
3737

38-
export interface ScrollOptions {
38+
export interface GetElementOptions {
39+
/**
40+
* Time to wait for the selector to appear in milliseconds.
41+
* Default is to not wait for selector.
42+
*/
43+
readonly waitForSelector?: number
44+
}
45+
46+
export interface ScrollOptions extends GetElementOptions {
3947
/**
4048
* Scroll speed (when scrolling occurs). 0 to 100. 100 is instant.
4149
* @default 100
@@ -55,11 +63,6 @@ export interface ScrollOptions {
5563
}
5664

5765
export interface MoveOptions extends BoxOptions, ScrollOptions, Pick<PathOptions, 'moveSpeed'> {
58-
/**
59-
* Time to wait for the selector to appear in milliseconds.
60-
* Default is to not wait for selector.
61-
*/
62-
readonly waitForSelector?: number
6366
/**
6467
* Delay after moving the mouse in milliseconds. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.
6568
* @default 0
@@ -141,8 +144,15 @@ export interface GhostCursor {
141144
selector: string | ElementHandle,
142145
options?: MoveOptions
143146
) => Promise<void>
144-
moveTo: (destination: Vector, options?: MoveToOptions) => Promise<void>
145-
scrollIntoView: (selector: ElementHandle, options?: ScrollOptions) => Promise<void>
147+
moveTo: (
148+
destination: Vector,
149+
options?: MoveToOptions) => Promise<void>
150+
scrollIntoView: (
151+
selector: string | ElementHandle,
152+
options?: ScrollOptions) => Promise<void>
153+
getElement: (
154+
selector: string | ElementHandle,
155+
options?: GetElementOptions) => Promise<ElementHandle<Element>>
146156
getLocation: () => Vector
147157
}
148158

@@ -378,6 +388,16 @@ export const createCursor = (
378388
* @default ClickOptions
379389
*/
380390
click?: ClickOptions
391+
/**
392+
* Default options for the `scrollIntoView` function
393+
* @default ScrollOptions
394+
*/
395+
scrollIntoView?: ScrollOptions
396+
/**
397+
* Default options for the `getElement` function
398+
* @default GetElementOptions
399+
*/
400+
getElement?: GetElementOptions
381401
} = {}
382402
): GhostCursor => {
383403
// this is kind of arbitrary, not a big fan but it seems to work
@@ -514,34 +534,8 @@ export const createCursor = (
514534
}
515535

516536
actions.toggleRandomMove(false)
517-
let elem: ElementHandle<Element> | null = null
518-
if (typeof selector === 'string') {
519-
if (selector.startsWith('//') || selector.startsWith('(//')) {
520-
selector = `xpath/.${selector}`
521-
if (optionsResolved.waitForSelector !== undefined) {
522-
await page.waitForSelector(selector, {
523-
timeout: optionsResolved.waitForSelector
524-
})
525-
}
526-
const [handle] = await page.$$(selector)
527-
elem = handle.asElement() as ElementHandle<Element>
528-
} else {
529-
if (optionsResolved.waitForSelector !== undefined) {
530-
await page.waitForSelector(selector, {
531-
timeout: optionsResolved.waitForSelector
532-
})
533-
}
534-
elem = await page.$(selector)
535-
}
536-
if (elem === null) {
537-
throw new Error(
538-
`Could not find element with selector "${selector}", make sure you're waiting for the elements by specifying "waitForSelector"`
539-
)
540-
}
541-
} else {
542-
// ElementHandle
543-
elem = selector
544-
}
537+
538+
const elem = await this.getElement(selector, optionsResolved)
545539

546540
// Make sure the object is in view
547541
await this.scrollIntoView(elem, optionsResolved)
@@ -608,14 +602,17 @@ export const createCursor = (
608602
await delay(optionsResolved.moveDelay * (optionsResolved.randomizeMoveDelay ? Math.random() : 1))
609603
},
610604

611-
async scrollIntoView (elem: ElementHandle, options?: ScrollOptions): Promise<void> {
605+
async scrollIntoView (selector: string | ElementHandle, options?: ScrollOptions): Promise<void> {
612606
const optionsResolved = {
613607
scrollSpeed: 100,
614608
scrollDelay: 200,
615609
inViewportMargin: 0,
610+
...defaultOptions?.scrollIntoView,
616611
...options
617612
} satisfies ScrollOptions
618613

614+
const elem = await this.getElement(selector, optionsResolved)
615+
619616
const {
620617
viewportWidth,
621618
viewportHeight,
@@ -672,9 +669,9 @@ export const createCursor = (
672669
const { top, left, bottom, right } = targetBox
673670

674671
const isInViewport = top >= 0 &&
675-
left >= 0 &&
676-
bottom <= viewportHeight &&
677-
right <= viewportWidth
672+
left >= 0 &&
673+
bottom <= viewportHeight &&
674+
right <= viewportWidth
678675

679676
if (isInViewport) return
680677

@@ -764,6 +761,39 @@ export const createCursor = (
764761
}
765762

766763
await delay(optionsResolved.scrollDelay)
764+
},
765+
766+
async getElement (selector: string | ElementHandle, options?: GetElementOptions): Promise<ElementHandle<Element>> {
767+
const optionsResolved = {
768+
...defaultOptions?.getElement,
769+
...options
770+
} satisfies GetElementOptions
771+
772+
let elem: ElementHandle<Element> | null = null
773+
if (typeof selector === 'string') {
774+
if (selector.startsWith('//') || selector.startsWith('(//')) {
775+
selector = `xpath/.${selector}`
776+
if (optionsResolved.waitForSelector !== undefined) {
777+
await page.waitForSelector(selector, { timeout: optionsResolved.waitForSelector })
778+
}
779+
const [handle] = await page.$$(selector)
780+
elem = handle.asElement() as ElementHandle<Element> | null
781+
} else {
782+
if (optionsResolved.waitForSelector !== undefined) {
783+
await page.waitForSelector(selector, { timeout: optionsResolved.waitForSelector })
784+
}
785+
elem = await page.$(selector)
786+
}
787+
if (elem === null) {
788+
throw new Error(
789+
`Could not find element with selector "${selector}", make sure you're waiting for the elements by specifying "waitForSelector"`
790+
)
791+
}
792+
} else {
793+
// ElementHandle
794+
elem = selector
795+
}
796+
return elem
767797
}
768798
}
769799

0 commit comments

Comments
 (0)