@@ -51,9 +51,9 @@ export interface ScrollOptions {
5151 */
5252 readonly scrollSpeed ?: number
5353 /**
54- * Time to wait after scrolling.
55- * @default 200
56- */
54+ * Time to wait after scrolling.
55+ * @default 200
56+ */
5757 readonly scrollDelay ?: number
5858}
5959
@@ -142,36 +142,44 @@ export interface MoveToOptions extends PathOptions, Pick<MoveOptions, 'moveDelay
142142export type ScrollToDestination = Partial < Vector > | 'top' | 'bottom' | 'left' | 'right'
143143
144144export interface GhostCursor {
145+ /** Toggles random mouse movements on or off. */
145146 toggleRandomMove : ( random : boolean ) => void
147+ /** Simulates a mouse click at the specified selector or element. */
146148 click : (
147149 selector ?: string | ElementHandle ,
148150 options ?: ClickOptions
149151 ) => Promise < void >
152+ /** Moves the mouse to the specified selector or element. */
150153 move : (
151154 selector : string | ElementHandle ,
152155 options ?: MoveOptions
153156 ) => Promise < void >
157+ /** Moves the mouse to the specified destination point. */
154158 moveTo : (
155159 destination : Vector ,
156160 options ?: MoveToOptions ) => Promise < void >
161+ /** Scrolls the element into view. If already in view, no scroll occurs. */
157162 scrollIntoView : (
158163 selector : ElementHandle ,
159164 options ?: ScrollIntoViewOptions ) => Promise < void >
165+ /** Scrolls to the specified destination point. */
160166 scrollTo : (
161167 destination : ScrollToDestination ,
162168 options ?: ScrollOptions ) => Promise < void >
169+ /** Scrolls the page the distance set by `delta`. */
163170 scroll : (
164171 delta : Partial < Vector > ,
165172 options ?: ScrollOptions ) => Promise < void >
173+ /** Gets the element via a selector. Can use an XPath. */
166174 getElement : (
167175 selector : string | ElementHandle ,
168176 options ?: GetElementOptions ) => Promise < ElementHandle < Element > >
177+ /** Get current location of the cursor. */
169178 getLocation : ( ) => Vector
170179 /**
171- * Defined only if `visible=true` is passed.
172- *
173- * NOTE: Must be a promise, since we can't `await` in the constructor.
174- */
180+ * Make the cursor no longer visible.
181+ * Defined only if `visible=true` was passed.
182+ */
175183 removeMouseHelper ?: Promise < ( ) => Promise < void > >
176184}
177185
@@ -222,50 +230,55 @@ export const getRandomPagePoint = async (page: Page): Promise<Vector> => {
222230 } )
223231}
224232
225- /** Using this method to get correct position of Inline elements (elements like `<a>`) */
226- const getElementBox = async (
233+ /** Get correct position of Inline elements (elements like `<a>`). Has fallback. */
234+ export const getElementBox = async (
227235 page : Page ,
228236 element : ElementHandle ,
229- relativeToMainFrame : boolean = true
230- ) : Promise < BoundingBox | null > => {
231- const objectId = element . remoteObject ( ) . objectId
232- if ( objectId === undefined ) {
233- return null
234- }
235-
237+ relativeToMainFrame : boolean = true ) : Promise < BoundingBox > => {
236238 try {
239+ const objectId = element . remoteObject ( ) . objectId
240+ if ( objectId === undefined ) throw new Error ( 'Element objectId is undefined, falling back to alternative methods' )
241+
237242 const quads = await getCDPClient ( page ) . send ( 'DOM.getContentQuads' , { objectId } )
238- const elementBox = {
243+ const elementBox : BoundingBox = {
239244 x : quads . quads [ 0 ] [ 0 ] ,
240245 y : quads . quads [ 0 ] [ 1 ] ,
241246 width : quads . quads [ 0 ] [ 4 ] - quads . quads [ 0 ] [ 0 ] ,
242247 height : quads . quads [ 0 ] [ 5 ] - quads . quads [ 0 ] [ 1 ]
243248 }
244249 if ( ! relativeToMainFrame ) {
245250 const elementFrame = await element . contentFrame ( )
246- const iframes =
247- elementFrame != null
248- ? await elementFrame . parentFrame ( ) ?. $$ ( 'xpath/.//iframe' )
249- : null
250- let frame : ElementHandle < Node > | undefined
251- if ( iframes != null ) {
251+ const iframes = await elementFrame ?. parentFrame ( ) ?. $$ ( 'xpath/.//iframe' )
252+ if ( iframes !== undefined && iframes !== null ) {
253+ let frame : ElementHandle < Node > | undefined
252254 for ( const iframe of iframes ) {
253- if ( ( await iframe . contentFrame ( ) ) === elementFrame ) frame = iframe
255+ if ( ( await iframe . contentFrame ( ) ) === elementFrame ) {
256+ frame = iframe
257+ }
258+ }
259+ if ( frame !== undefined && frame != null ) {
260+ const frameBox = await frame . boundingBox ( )
261+ if ( frameBox !== null ) {
262+ elementBox . x -= frameBox . x
263+ elementBox . y -= frameBox . y
264+ }
254265 }
255- }
256- if ( frame != null ) {
257- const boundingBox = await frame . boundingBox ( )
258- elementBox . x =
259- boundingBox !== null ? elementBox . x - boundingBox . x : elementBox . x
260- elementBox . y =
261- boundingBox !== null ? elementBox . y - boundingBox . y : elementBox . y
262266 }
263267 }
264268
265269 return elementBox
266- } catch ( _ ) {
267- log ( 'Quads not found, trying regular boundingBox' )
268- return await element . boundingBox ( )
270+ } catch {
271+ try {
272+ log ( 'Quads not found, trying regular boundingBox' )
273+ const elementBox = await element . boundingBox ( )
274+ if ( elementBox === null ) throw new Error ( 'Element boundingBox is null, falling back to getBoundingClientRect' )
275+ return elementBox
276+ } catch {
277+ log ( 'BoundingBox null, using getBoundingClientRect' )
278+ return await element . evaluate ( ( el ) =>
279+ el . getBoundingClientRect ( ) as BoundingBox
280+ )
281+ }
269282 }
270283}
271284
@@ -281,20 +294,6 @@ const intersectsElement = (vec: Vector, box: BoundingBox): boolean => {
281294 )
282295}
283296
284- const boundingBoxWithFallback = async (
285- page : Page ,
286- elem : ElementHandle < Element >
287- ) : Promise < BoundingBox > => {
288- let box = await getElementBox ( page , elem )
289- if ( box == null ) {
290- box = ( await elem . evaluate ( ( el : Element ) =>
291- el . getBoundingClientRect ( )
292- ) ) as BoundingBox
293- }
294-
295- return box
296- }
297-
298297export const createCursor = (
299298 page : Page ,
300299 /**
@@ -350,7 +349,7 @@ export const createCursor = (
350349 // Initial state: mouse is not moving
351350 let moving : boolean = false
352351
353- // Move the mouse over a number of vectors
352+ /** Move the mouse over a number of vectors */
354353 const tracePath = async (
355354 vectors : Iterable < Vector | TimedVector > ,
356355 abortOnMove : boolean = false
@@ -383,7 +382,7 @@ export const createCursor = (
383382 }
384383 }
385384 }
386- // Start random mouse movements. Function recursively calls itself
385+ /** Start random mouse movements. Function recursively calls itself. */
387386 const randomMove = async ( options ?: RandomMoveOptions ) : Promise < void > => {
388387 const optionsResolved = {
389388 moveDelay : 2000 ,
@@ -409,14 +408,17 @@ export const createCursor = (
409408 }
410409
411410 const actions : GhostCursor = {
411+ /** Toggles random mouse movements on or off. */
412412 toggleRandomMove ( random : boolean ) : void {
413413 moving = ! random
414414 } ,
415415
416+ /** Get current location of the cursor. */
416417 getLocation ( ) : Vector {
417418 return previous
418419 } ,
419420
421+ /** Simulates a mouse click at the specified selector or element. */
420422 async click (
421423 selector ?: string | ElementHandle ,
422424 options ?: ClickOptions
@@ -465,6 +467,7 @@ export const createCursor = (
465467 actions . toggleRandomMove ( wasRandom )
466468 } ,
467469
470+ /** Moves the mouse to the specified selector or element. */
468471 async move (
469472 selector : string | ElementHandle ,
470473 options ?: MoveOptions
@@ -492,7 +495,7 @@ export const createCursor = (
492495 // Make sure the object is in view
493496 await this . scrollIntoView ( elem , optionsResolved )
494497
495- const box = await boundingBoxWithFallback ( page , elem )
498+ const box = await getElementBox ( page , elem )
496499 const { height, width } = box
497500 const destination = ( optionsResolved . destination !== undefined )
498501 ? add ( box , optionsResolved . destination )
@@ -522,7 +525,7 @@ export const createCursor = (
522525
523526 actions . toggleRandomMove ( true )
524527
525- const newBoundingBox = await boundingBoxWithFallback ( page , elem )
528+ const newBoundingBox = await getElementBox ( page , elem )
526529
527530 // It's possible that the element that is being moved towards
528531 // has moved to a different location by the time
@@ -538,6 +541,7 @@ export const createCursor = (
538541 await delay ( optionsResolved . moveDelay * ( optionsResolved . randomizeMoveDelay ? Math . random ( ) : 1 ) )
539542 } ,
540543
544+ /** Moves the mouse to the specified destination point. */
541545 async moveTo ( destination : Vector , options ?: MoveToOptions ) : Promise < void > {
542546 const optionsResolved = {
543547 moveDelay : 0 ,
@@ -554,6 +558,7 @@ export const createCursor = (
554558 await delay ( optionsResolved . moveDelay * ( optionsResolved . randomizeMoveDelay ? Math . random ( ) : 1 ) )
555559 } ,
556560
561+ /** Scrolls the element into view. If already in view, no scroll occurs. */
557562 async scrollIntoView ( selector : string | ElementHandle , options ?: ScrollIntoViewOptions ) : Promise < void > {
558563 const optionsResolved = {
559564 scrollDelay : 200 ,
@@ -585,7 +590,7 @@ export const createCursor = (
585590 }
586591 ) )
587592
588- const elemBoundingBox = await boundingBoxWithFallback ( page , elem ) // is relative to viewport
593+ const elemBoundingBox = await getElementBox ( page , elem ) // is relative to viewport
589594 const elemBox = {
590595 top : elemBoundingBox . y ,
591596 left : elemBoundingBox . x ,
@@ -672,6 +677,7 @@ export const createCursor = (
672677 }
673678 } ,
674679
680+ /** Scrolls the page the distance set by `delta`. */
675681 async scroll ( delta : Partial < Vector > , options ?: ScrollOptions ) {
676682 const optionsResolved = {
677683 scrollDelay : 200 ,
@@ -732,6 +738,7 @@ export const createCursor = (
732738 await delay ( optionsResolved . scrollDelay )
733739 } ,
734740
741+ /** Scrolls to the specified destination point. */
735742 async scrollTo ( destination : ScrollToDestination , options ?: ScrollOptions ) {
736743 const optionsResolved = {
737744 scrollDelay : 200 ,
@@ -775,6 +782,7 @@ export const createCursor = (
775782 } , optionsResolved )
776783 } ,
777784
785+ /** Gets the element via a selector. Can use an XPath. */
778786 async getElement ( selector : string | ElementHandle , options ?: GetElementOptions ) : Promise < ElementHandle < Element > > {
779787 const optionsResolved = {
780788 ...defaultOptions ?. getElement ,
@@ -809,11 +817,14 @@ export const createCursor = (
809817 }
810818 }
811819
812- if ( visible ) {
813- const removeMouseHelper = installMouseHelper ( page ) . then (
820+ /**
821+ * Make the cursor no longer visible.
822+ * Defined only if `visible=true` was passed.
823+ */
824+ actions . removeMouseHelper = visible
825+ ? installMouseHelper ( page ) . then (
814826 ( { removeMouseHelper } ) => removeMouseHelper )
815- actions . removeMouseHelper = removeMouseHelper
816- }
827+ : undefined
817828
818829 // Start random mouse movements. Do not await the promise but return immediately
819830 if ( performRandomMoves ) {
0 commit comments