Search code examples
excelvbaselenium-webdriverselenium-chromedriver

How to VISUALLY move the mouse cursor to a specific position via Selenium VBA?


please do not block me. I found similar question in this link: How to move the mouse pointer visually in selenium python but there was no answer. So I decided to ask again.

How may I VISUALLY move the mouse cursor to a specific position via Selenium in VBA Excel?

This is the code I tried. I want to move the cursor to 5,10. But the mouse cursor doesn't move visually.

Sub VisuallyCursorMove()
    dim Driver as Selenium.ChromeDriver
    Set Driver = New Selenium.ChromeDriver
    Driver.Start
    Driver.Get "http://www.yahoo.com"
    Driver.Actions.MoveByOffset(5, 10).Perform
End Sub

If you are going to ask me that what does it matter to see visually the cursor, I should answer that it is because I am using Magnifier. I should move the mouse to any place to see that in the magnifier window. So I want every mouse actions in my Selenium project, able to see in magnifier window.


Solution

  • I have done something similar in the past, so I'll post some code you can try. I haven't tested it now, so there might be some bugs/issues with it. Also, a bunch of the declared API functions are not used in this snippet, I had no time to remove them now.

    If this Sub doesn't perfectly fit your needs it should be easy to adapt, you could also move the mouse to a position defined by WebDriverRECT.right - offsetX and WebDriverRECT.bottom -offsetY...

    Also, you can experiment with oWebDriver.Window.Position.x, oWebDriver.Window.Position.y, oWebDriver.Window.Size.Width and oWebDriver.Window.Size.Height, I exclusively used Windows API functions for consistency in the scaling.

    Option Explicit
    
    #If VBA7 Then
        Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliSeconds As Long)
        Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As PointAPI) As Long
        Private Declare PtrSafe Function SetCursorPos Lib "user32" (ByVal x As Long, ByVal y As Long) As Long
        Private Declare PtrSafe Sub mouse_event Lib "user32" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
        Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
        Private Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hWnd As LongPtr, lpRect As RECT) As Long
        Private Declare PtrSafe Function GetDesktopWindow Lib "user32" () As LongPtr
        Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal wCmd As Long) As LongPtr
        Private Declare PtrSafe Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hWnd As LongPtr) As Long
        Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
        Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As LongPtr, ByVal lpStr As String, ByVal nMaxCount As Long) As Long
    
        Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr
        Private Declare PtrSafe Function SetForegroundWindow Lib "user32" (ByVal hWnd As LongPtr) As Boolean
        Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
        Private Declare PtrSafe Function SendMessageTimeout Lib "user32" Alias "SendMessageTimeoutA" (ByVal hWnd As LongPtr, ByVal msg As Long, ByVal wParam As LongPtr, ByVal lParam As Any, ByVal fuFlags As Long, ByVal uTimeout As Long, lpdwResult As Long) As Long
        Private Declare PtrSafe Function SendMessageByString Lib "user32.dll" Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
        Private Declare PtrSafe Function EnumChildWindows Lib "user32" (ByVal hwndParent As LongPtr, ByVal lpEnumFunc As LongPtr, ByVal lParam As LongPtr) As Boolean
    #If Win64 Then
        Private Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongPtrA" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As LongPtr
    #Else
        Private Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
    #End If
    #Else
        Private Declare Sub Sleep Lib "kernel32" Alias "sleep" (ByVal lngMilliSeconds As Long)
        Private Declare Function GetCursorPos Lib "user32" (lpPoint As PointAPI) As Long
        Private Declare Function SetCursorPos Lib "user32" (ByVal x As Long, ByVal y As Long) As Long
        Private Declare Sub mouse_event Lib "user32" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
        Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
        Private Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, lpRect As RECT) As Long
        Private Declare Function GetDesktopWindow Lib "user32" () As Long
        Private Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
        Private Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hWnd As Long) As Long
        Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
        Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpStr As String, ByVal nMaxCount As Long) As Long
    
        Private Declare Function GetForegroundWindow Lib "user32" () As Long
        Private Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As Long) As Boolean
        Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
        Private Declare Function SendMessageTimeout Lib "user32" Alias "SendMessageTimeoutA" (ByVal hWnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Any, ByVal fuFlags As Long, ByVal uTimeout As Long, lpdwResult As Long) As Long
        Private Declare Function SendMessageByString Lib "user32.dll" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
        Private Declare Function EnumChildWindows Lib "user32" (ByVal hwndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Boolean
        Private Declare Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
    #End If
    
    Private Type PointAPI
        x As Long
        y As Long
    End Type
    
    Private Type RECT
        left As Long
        top As Long
        right As Long
        bottom As Long
    End Type
    
    Private Sub MoveMouseToWebDriverPos(ByVal oWebDriver As Object, _
                                        ByVal posX As Long, _
                                        ByVal posY As Long)
    #If VBA7 Then
        Dim WebDriverHwnd As LongPtr
        Dim dlgHwnd As LongPtr
    #Else
        Dim WebDriverHwnd As Long
        Dim dlgHwnd As Long
    #End If
    
        WebDriverHwnd = FindTopLevelWindow(oWebDriver.Title, "Chrome_WidgetWin_1")
        
        'Get Edge window position and dimensions:
        'Note: It is necessary to use the Windows API Functions here,
        'as the Selenium internal ones:
        'oWebDriver.Window.Position.x, oWebDriver.Window.Position.y,
        'oWebDriver.Window.Size.Width and oWebDriver.Window.Size.Height
        'seem to use different scaling and return different (wrong) values
        Dim WebDriverRECT As RECT: GetWindowRect WebDriverHwnd, WebDriverRECT
    
        'If you want to rturn cursor to original position afterwards, cache pos here
        'Dim CachedPos As PointAPI: GetCursorPos CachedPos
    
        SetCursorPos WebDriverRECT.left + posX, WebDriverRECT.top + posY
    
        'If you want to click:
    '    Const MOUSEEVENTF_LEFTDOWN = &H2
    '    Const MOUSEEVENTF_LEFTUP = &H4
    '    Const MOUSEEVENTF_RIGHTDOWN As Long = &H8
    '    Const MOUSEEVENTF_RIGHTUP As Long = &H10
        'mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
        'mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
        
        'If you want to rturn cursor to original position after some action
        'SetCursorPos CachedPos.X, CachedPos.y
    End Sub
    
    #If VBA7 Then
    Private Function FindTopLevelWindow(partOfTitleStr As String, _
                              Optional className As String = "§$%³") As LongPtr
    #Else
    Private Function FindTopLevelWindow(partOfTitleStr As String, _
                               Optional className As String = "§$%³") As Long
    #End If
        Const GW_CHILD = 5
        Const GW_HWNDNEXT = 2
        Dim tLen As Long
        Dim i As Long: i = 0
        
        FindTopLevelWindow = GetWindow(GetDesktopWindow(), GW_CHILD)
        
        Do While FindTopLevelWindow <> 0
            tLen = GetWindowTextLength(FindTopLevelWindow)
            Dim wname As String: wname = Space(tLen)
        
            GetWindowText FindTopLevelWindow, wname, tLen
            
            If InStr(1, wname, partOfTitleStr) > 0 Then
                If className <> "§$%³" Then
                    Dim tClassName As String: tClassName = Space(256)
                    GetClassName FindTopLevelWindow, tClassName, Len(tClassName)
                    
                    'Null termination has to be removed:
                    If className = left(Trim(tClassName), Len(Trim(tClassName)) - 1) Then
                        Exit Function
                    End If
                Else
                    Exit Function
                End If
            End If
            FindTopLevelWindow = GetWindow(FindTopLevelWindow, GW_HWNDNEXT)
            
            i = i + 1
            If i Mod 10 = 0 Then DoEvents
        Loop
    End Function
    

    Edit:

    As requested in your comment, to use this code, just call the Sub I provided in your code:

    Sub VisuallyCursorMove()
        dim Driver as Selenium.ChromeDriver
        Set Driver = New Selenium.ChromeDriver
        Driver.Start
        Driver.Get "http://www.yahoo.com"
        
        'The following moves the mouse cursor to the position 57 pixels from the top
        'and 57 pixels from the left of your WebDriver window, so probably somewhere
        'around the top left corner of the window.
        MoveMouseToWebDriverPos(Driver, 57, 57)
    End Sub