Skip to content

Commit 6d3227f

Browse files
feat: getElement function (#163)
1 parent b1b2c67 commit 6d3227f

File tree

2 files changed

+83
-46
lines changed

2 files changed

+83
-46
lines changed

README.md

+13-6
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

+70-40
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
@@ -521,34 +541,8 @@ export const createCursor = (
521541
}
522542

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

553547
// Make sure the object is in view
554548
await this.scrollIntoView(elem, optionsResolved)
@@ -615,14 +609,17 @@ export const createCursor = (
615609
await delay(optionsResolved.moveDelay * (optionsResolved.randomizeMoveDelay ? Math.random() : 1))
616610
},
617611

618-
async scrollIntoView (elem: ElementHandle, options?: ScrollOptions): Promise<void> {
612+
async scrollIntoView (selector: string | ElementHandle, options?: ScrollOptions): Promise<void> {
619613
const optionsResolved = {
620614
scrollSpeed: 100,
621615
scrollDelay: 200,
622616
inViewportMargin: 0,
617+
...defaultOptions?.scrollIntoView,
623618
...options
624619
} satisfies ScrollOptions
625620

621+
const elem = await this.getElement(selector, optionsResolved)
622+
626623
const {
627624
viewportWidth,
628625
viewportHeight,
@@ -679,9 +676,9 @@ export const createCursor = (
679676
const { top, left, bottom, right } = targetBox
680677

681678
const isInViewport = top >= 0 &&
682-
left >= 0 &&
683-
bottom <= viewportHeight &&
684-
right <= viewportWidth
679+
left >= 0 &&
680+
bottom <= viewportHeight &&
681+
right <= viewportWidth
685682

686683
if (isInViewport) return
687684

@@ -771,6 +768,39 @@ export const createCursor = (
771768
}
772769

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

0 commit comments

Comments
 (0)