Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webdriver BiDi support #113

Open
Danp2 opened this issue Dec 26, 2021 · 10 comments
Open

Webdriver BiDi support #113

Danp2 opened this issue Dec 26, 2021 · 10 comments
Labels
enhancement New feature or request

Comments

@Danp2
Copy link
Owner

Danp2 commented Dec 26, 2021

@Danp2 Danp2 added the enhancement New feature or request label Dec 26, 2021
@Danp2
Copy link
Owner Author

Danp2 commented Jul 23, 2022

Chromium BiDi progress [Outdated]

Mozilla BiDi progress

Mozilla BiDi status wiki

@Danp2
Copy link
Owner Author

Danp2 commented Jul 24, 2022

Initial coding can be found here

@Danp2

This comment was marked as outdated.

@Danp2 Danp2 changed the title Future support for Webdriver BiDi? Webdriver BiDi support Jul 25, 2022
@Danp2
Copy link
Owner Author

Danp2 commented Aug 20, 2022

WPT tracker

@Danp2
Copy link
Owner Author

Danp2 commented Oct 3, 2022

Found that calls to WinHttpWebSocketReceive would cause the process to hang when there wasn't pending data to receive. Switched to using websocat to perform all websocket activity.

@Danp2

This comment was marked as outdated.

@Danp2
Copy link
Owner Author

Danp2 commented Nov 25, 2022

Here is the latest revision of code I'm using to test the new bidi commands --

CLICK ME

;~ #AutoIt3Wrapper_au3check_parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
;~ #AutoIt3Wrapper_AutoIt3Dir="C:\AutoitPortable\3.3.16.0"
#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiComboBoxEx.au3>
#include <GuiListView.au3>
#include <GuiEdit.au3>

#include "wd_core.au3"
#include "wd_helper.au3"
#include "wd_capabilities.au3"
#include "wd_bidi.au3"

Global $sSession

Global Const $aBrowsers[][2] = _
        [ _
        ["Firefox", SetupGecko], _
        ["Chrome", SetupChrome], _
        ["MSEdge", SetupEdge], _
        ["Opera", SetupOpera] _
        ]

Global Const $aCommands[][2] = _
        [ _
            ["browsingContext.create", '{ "type": "tab" }'], _
            ["browsingContext.close", '{"context": "<context id>"}'], _
            ["browsingContext.getTree", "{}"], _
            ["browsingContext.navigate", '{"context": "<context id>", "url": "https://google.com"}'], _
            ["session.status",  "{}"], _
            ["session.new", "{}"], _
            ["session.subscribe", '{"events":["log.entryAdded"]}'], _
            ["session.unsubscribe", '{"events":["log.entryAdded"]}'] _
        ]

Opt('TrayIconDebug',1)

Main()

Func Main()
    $_WD_Debug = $_WD_DEBUG_Full
    _WD_BidiSetClient('websocat')
    _WD_CapabilitiesDefine($_WD_KEYS__STANDARD_PRIMITIVE, 'webSocketUrl')
    _WD_CapabilitiesDefine($_WD_KEYS__STANDARD_PRIMITIVE, '"moz:debuggerAddress"')

    Local $hGUI = GUICreate("BiDi Tester", 615, 325, -1, -1)
    GUICtrlCreateLabel("Command", 15, 15)
    Local $idCommands = GUICtrlCreateCombo("", 75, 12, 200, 20, $CBS_DROPDOWNLIST)
    Local $sData = _ArrayToString($aCommands, Default, Default, Default, "|", 0, 0)
    GUICtrlSetData($idCommands, $sData)
    GUICtrlSetData($idCommands, $aCommands[0][0])
    Local $iCommand = GUICtrlCreateInput($aCommands[0][0] &amp; "|" &amp; $aCommands[0][1], 24, 40, 409, 21)
    Local $iSend = GUICtrlCreateButton("Send", 448, 39, 75, 25)
    Local $iAsync = GUICtrlCreateCheckbox("Async?", 530, 40)

    Local $iLaunch = GUICtrlCreateButton("Launch", 15, 75, 75, 25)
    Local $iMenu = GUICtrlCreateContextMenu($iLaunch)
    For $i = 0 To UBound($aBrowsers, 1) - 1
        GUICtrlCreateMenuItem($aBrowsers[$i][0], $iMenu, Default, 1)
    Next

    Local $iShow = GUICtrlCreateButton("Show Maps", 95, 75, 75, 25)
    Local $iMenu2 = GUICtrlCreateContextMenu($iShow)
    Local $iEvents = GUICtrlCreateMenuItem("Events", $iMenu2, Default, 1)
    Local $iResults = GUICtrlCreateMenuItem("Results", $iMenu2, Default, 1)
    GUICtrlSetState($iEvents, $GUI_CHECKED)
    Local $iGetResult = GUICtrlCreateButton("Get Result", 175, 75, 75, 25)

    Local $iEdit = GUICtrlCreateEdit("", 15, 110, 585, 200)

    Local $iBrowser = 0, $iStartPopup = $iMenu + 1, $iEndPopup = $iStartPopup + UBound($aBrowsers, 1)
    Local $iSelect, $aCmd, $sCmd, $sOpt, $bAsync, $sResult, $mMap, $nMsg, $iLastID = 0, $sAnswer, $aPos
    Local $iDiff = 0, $hTimer = TimerInit() 

    GUICtrlSetState($iStartPopup, $GUI_CHECKED)
    GUISetState(@SW_SHOW)

#forceref $iResults

    While 1
        $nMsg = GUIGetMsg()
        Switch $nMsg
            Case $GUI_EVENT_CLOSE
                ExitLoop

            Case $iSend
                If _WD_BidiIsConnected() Then
                    $aCmd = StringSplit(GUICtrlRead($iCommand), '|')
                    $sCmd = $aCmd[0] ? $aCmd[1] : ""
                    $sOpt = ($aCmd[0] = 2) ? Json_Decode($aCmd[2]) : Json_ObjCreate()
                    $bAsync = (GUICtrlRead($iAsync) = $GUI_CHECKED)

                    $sResult = DoSend($sCmd, $sOpt, $bAsync)

                    If $bAsync Then
                        $iLastID = Number($sResult)
                        $sResult = "Request ID: " &amp; $sResult
                    EndIf
                Else
                    $sResult = "Not connected!"
                EndIf

                _GUICtrlEdit_AppendText($iEdit, $sResult &amp; @CRLF)

            Case $iLaunch
                DoLaunch($iBrowser)

            Case $idCommands
                $iSelect = _GUICtrlComboBox_GetCurSel($idCommands)
                GUICtrlSetData($iCommand,  $aCommands[$iSelect][0] &amp; "|" &amp; $aCommands[$iSelect][1])

            Case $iShow
                If BitAnd(GUICtrlRead($iEvents), $GUI_CHECKED) Then
                    $mMap = __WD_BidiActions('maps', 'event')
                    _Map_Display($mMap, 'events')
                Else
                    $mMap = __WD_BidiActions('maps', 'result')
                    _Map_Display($mMap, 'results')
                EndIf

            Case $iStartPopup To $iEndPopup
                $iBrowser = $nMsg - $iStartPopup

            Case $iGetResult
                $aPos = WinGetPos($hGUI)
                $sAnswer = InputBox("Result", "Enter the Request ID of the result to retrieve", $iLastID, Default, Default, Default, $aPos[0]+($aPos[2]/3), $aPos[1]+($aPos[3]/3))
                If @error = $_WD_ERROR_Success Then
                    $sResult = _WD_BidiGetResult(Number($sAnswer))

                    If @error Then _
                        $sResult = $aWD_ERROR_DESC[@error] &amp; " [" &amp; @error &amp; "]"

                    _GUICtrlEdit_AppendText($iEdit, $sResult &amp; @CRLF)
                EndIf
                
        EndSwitch


        If TimerDiff($hTimer) > $iDiff Then
            DoPoll($iEdit)
            $iDiff = TimerDiff($hTimer) + 500
        EndIf
    WEnd

    _WD_BidiDisconnect()
    GUIDelete($hGUI)
EndFunc

Func DoLaunch($iBrowser)
    Local $bHeadless = False
    Local $sURL = 'https://google.com/'

    ; Execute browser setup routine for user's browser selection
    Local $sCapabilities = Call($aBrowsers[$iBrowser][1], $bHeadless)

    _WD_Startup()
    $sSession = _WD_CreateSession($sCapabilities)

    If @error = $_WD_ERROR_Success Then
        _WD_Navigate($sSession, $sURL)
        _WD_BidiConnect($sSession)
    Endif
EndFunc

Func DoSend($sCommand, $oParams, $bAsync)
    Local $sResult = _WD_BidiExecute($sCommand, $oParams, $bAsync)

    Return $sResult
EndFunc

Func DoPoll($iEdit)
    Local $sResult
    Local $_WD_DEBUG_Saved = $_WD_DEBUG ; save current DEBUG level
    $_WD_DEBUG = $_WD_DEBUG_None

    If _WD_BidiIsConnected() Then
        $sResult = _WD_BidiGetEvent()
        If $sResult Then
            _GUICtrlEdit_AppendText($iEdit, $sResult &amp; @CRLF)
        EndIf

    EndIf

    $_WD_DEBUG = $_WD_DEBUG_Saved ; restore DEBUG level
EndFunc

Func SetupGecko($bHeadless)
    _WD_Option('Driver', 'geckodriver.exe')
    ;~ _WD_Option('DriverParams', '--log trace --websocket-port=60000 --allow-hosts 127.0.0.1 --allow-origins http://127.0.0.1:60000 http://localhost:60000')
    _WD_Option('DriverParams', '--log trace')
    _WD_Option('Port', 4444)

    _WD_CapabilitiesStartup()
    _WD_CapabilitiesAdd('alwaysMatch', 'firefox')
    _WD_CapabilitiesAdd('browserName', 'firefox')
    _WD_CapabilitiesAdd('acceptInsecureCerts', True)
    _WD_CapabilitiesAdd('webSocketUrl', True)
    _WD_CapabilitiesAdd('"moz:debuggerAddress"', True)

    ; enable experimental BiDi commands
    _WD_CapabilitiesAdd('prefs', 'remote.experimental.enabled', true)

    ;~ _WD_CapabilitiesAdd('prefs', 'remote.hosts.allowed', "127.0.0.1")
    ;~ _WD_CapabilitiesAdd('prefs', 'remote.origins.allowed', "http://127.0.0.1")

    If $bHeadless Then _WD_CapabilitiesAdd('args', '--headless')
    Local $sCapabilities = _WD_CapabilitiesGet()

    Return $sCapabilities
EndFunc   ;==>SetupGecko

Func SetupChrome($bHeadless)
    _WD_Option('Driver', 'chromedriver.exe')
    _WD_Option('Port', 9515)
    _WD_Option('DriverParams', '--verbose --log-path="' &amp; @ScriptDir &amp; '\chrome.log"')

    _WD_CapabilitiesStartup()
    _WD_CapabilitiesAdd('alwaysMatch', 'chrome')
    _WD_CapabilitiesAdd('w3c', True)
    _WD_CapabilitiesAdd('webSocketUrl', True)
    _WD_CapabilitiesAdd('excludeSwitches', 'enable-automation')
    If $bHeadless Then _WD_CapabilitiesAdd('args', '--headless')
    Local $sCapabilities = _WD_CapabilitiesGet()
    Return $sCapabilities
EndFunc   ;==>SetupChrome

Func SetupEdge($bHeadless)
    _WD_Option('Driver', 'msedgedriver.exe')
    _WD_Option('Port', 9515)
    _WD_Option('DriverParams', '--verbose --log-path="' &amp; @ScriptDir &amp; '\msedge.log"')

    _WD_CapabilitiesStartup()
    _WD_CapabilitiesAdd('alwaysMatch', 'msedge')
    _WD_CapabilitiesAdd('webSocketUrl', True)
    _WD_CapabilitiesAdd('excludeSwitches', 'enable-automation')
    If $bHeadless Then _WD_CapabilitiesAdd('args', '--headless')
    Local $sCapabilities = _WD_CapabilitiesGet()
    Return $sCapabilities
EndFunc   ;==>SetupEdge

Func SetupOpera($bHeadless)
    _WD_Option('Driver', 'operadriver.exe')
    _WD_Option('Port', 9515)
    _WD_Option('DriverParams', '--verbose --log-path="' &amp; @ScriptDir &amp; '\opera.log"')

    _WD_CapabilitiesStartup()
    _WD_CapabilitiesAdd('alwaysMatch', 'opera')
    _WD_CapabilitiesAdd('w3c', True)
    _WD_CapabilitiesAdd('webSocketUrl', True)
    _WD_CapabilitiesAdd('excludeSwitches', 'enable-automation')
    ; REMARK
    ; using 32bit operadriver.exe requires to set 'binary' capabilities,
    ; using 64bit operadriver.exe dosen't require to set this capability, but at the same time setting is not affecting the script
    ; So this is good habit to setup for any case.
    _WD_CapabilitiesAdd('binary', _WD_GetBrowserPath("opera"))
    ConsoleWrite("wd_demo.au3: _WD_GetBrowserPath() > " &amp; _WD_GetBrowserPath("opera") &amp; @CRLF)

    If $bHeadless Then _WD_CapabilitiesAdd('args', '--headless')
    Local $sCapabilities = _WD_CapabilitiesGet()
    Return $sCapabilities
EndFunc   ;==>SetupOpera

; #FUNCTION# ====================================================================================================================
; Name ..........: _Map_Display
; Description ...: Displays the Key &amp; Value pair in a ListView.
; Syntax ........: _Map_Display(Byref $mMap[, $sTitle = "Map Display"])
; Parameters ....: $mMap                - [in/out] Map to display.
;                  $sTitle              - [optional] Title for the Display window. Default is "Map Display".
; Return values .: Success: True
;                  Failure: False &amp; @error is set to non-zero
; Author ........: Damon Harris (TheDcoder)
; Modified ......:
; Remarks .......: This function is intended to be used for Debugging. Its still in development, so it does not provide flexible
;                  functionality like _ArrayDisplay
; Related .......: _ArrayDisplay
; Link ..........:
; Example .......: Yes
; ===============================================================================================================================
Func _Map_Display(ByRef $mMap, $sTitle = "Map Display")
    If Not IsMap($mMap) Then
        Return SetError(1, 0, False)
    EndIf
    Local Const $esDataSepChar = Opt("GUIDataSeparatorChar") ; Get the GUIDataSeparatorChar
    Local $hGUI = GUICreate($sTitle, 300, 300, -1, -1, BitOR($WS_MINIMIZEBOX, $WS_CAPTION, $WS_POPUP, $WS_SYSMENU, $WS_SIZEBOX, $WS_MAXIMIZEBOX)) ; Create the $hGUI
    Local $idListView = GUICtrlCreateListView("Key|Value", 0, 0, 300, 264) ; Create the $idListView
    Local $idCopyButton = GUICtrlCreateButton("Copy Text", 3, 268, 146, 30) ; Create the $idCopyButton
    GUICtrlSetResizing(-1, $GUI_DOCKAUTO) ; Set Resizing
    Local $idExitButton = GUICtrlCreateButton("Exit Script", 149, 268, 148, 30) ; Create the $idExitButton
    GUICtrlSetResizing(-1, $GUI_DOCKAUTO) ; Set Resizing

    Local $aKeys = MapKeys($mMap) ; Get the $aKeys in the map
    ;Local $iKeyCount = UBound($mMap) ; Get the $iKeyCount
    Local $sContents = "" ; Declare the variable in which the contents will be stored

    For $vKey In $aKeys ; Loop...
        $sContents = $vKey &amp; $esDataSepChar
        Switch VarGetType($mMap[$vKey]) ; Switch to the Var Type
            Case "Map"
                $sContents &amp;= '{Map}' ; Store the contents

            Case "Array"
                $sContents &amp;= '{Array}' ; Store the contents

            Case "Object"
                $sContents &amp;= '{Object}' ; Store the contents

            Case "DLLStruct"
                $sContents &amp;= '{Struct}' ; Store the contents

            Case "Function"
                $sContents &amp;= '{Function}' ; Store the contents

            Case "UserFunction"
                $sContents &amp;= '{UDF}' ; Store the contents

            Case Else
                $sContents &amp;= $mMap[$vKey] ; Store the contents
        EndSwitch
        GUICtrlCreateListViewItem($sContents, $idListView) ; Create the ListViewItem in $idListView
    Next

    _GUICtrlListView_SetColumnWidth($idListView, 0, $LVSCW_AUTOSIZE) ; Autosize the $idListView to fit the text
    GUISetState() ; Display the GUI
    Local $nMsg = 0

    While 1
        $nMsg = GUIGetMsg()
        Switch $nMsg
            Case $GUI_EVENT_CLOSE
                GUIDelete($hGUI) ; Delete the GUI
                Return True ; Return True

            Case $idExitButton
                Exit ; Exit

            Case $idCopyButton
                $sContents = GUICtrlRead(GUICtrlRead($idListView)) ; Copy the $sContents of the ListViewItem
                ClipPut($sContents) ; ClipPut them!
                MsgBox($MB_ICONINFORMATION, $sTitle, "The following has been copied to your clipboard: " &amp; @CRLF &amp; @CRLF &amp; $sContents) ; Display a message
        EndSwitch
    WEnd
EndFunc   ;==>_Map_Display

Note that you can right click on the Launch and Show Maps buttons to alter their functionality.

@Danp2
Copy link
Owner Author

Danp2 commented Apr 8, 2023

@mlipok
Copy link
Contributor

mlipok commented Apr 25, 2023

@Danp2
Copy link
Owner Author

Danp2 commented Oct 23, 2023

I have been successful at creating a BiDi session in Firefox without the need of geckodriver. I don't believe that this feature is currently supported by Chrome or Edge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants