#include-once
; standard UDF's
#include <File.au3> ; Needed For _WD_UpdateDriver
#include <InetConstants.au3>
#include <Misc.au3> ; Needed For _WD_UpdateDriver >> _VersionCompare
#include <WinAPIFiles.au3> ; Needed For _WD_UpdateDriver >> _WinAPI_GetBinaryType and _WD_DownloadFile >> _WinAPI_FileInUse

; WebDriver related UDF's
#include "wd_core.au3"

#Region Copyright
#cs
	* WD_Helper.au3
	*
	* MIT License
	*
	* Copyright (c) 2023 Dan Pollak (@Danp2)
	*
	* Permission is hereby granted, free of charge, to any person obtaining a copy
	* of this software and associated documentation files (the "Software"), to deal
	* in the Software without restriction, including without limitation the rights
	* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	* copies of the Software, and to permit persons to whom the Software is
	* furnished to do so, subject to the following conditions:
	*
	* The above copyright notice and this permission notice shall be included in all
	* copies or substantial portions of the Software.
	*
	* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	* SOFTWARE.
#ce
#EndRegion Copyright

#Region Many thanks to:
#cs
	- Jonathan Bennett (@Jon) and the AutoIt Team
	- Thorsten Willert (@Stilgar), author of FF.au3, which I've used as a model
	- MichaƂ Lipok (@mLipok) for all his contribution
#ce
#EndRegion Many thanks to:

#Tidy_Parameters=/tcb=-1

#Region Global Constants
Global Enum _
		$_WD_OPTION_None = 0, _
		$_WD_OPTION_Visible = 1, _
		$_WD_OPTION_Enabled = 2, _
		$_WD_OPTION_Element = 4, _
		$_WD_OPTION_NoMatch = 8, _
		$_WD_OPTION_Hidden = 16

Global Enum _
		$_WD_OPTION_Standard, _
		$_WD_OPTION_Advanced

Global Enum _
		$_WD_STATUS_Invalid, _
		$_WD_STATUS_Valid, _
		$_WD_STATUS_Reconnect

Global Enum _
		$_WD_TARGET_FirstTab, _
		$_WD_TARGET_LastTab

Global Enum _
		$_WD_BUTTON_Left = 0, _
		$_WD_BUTTON_Middle = 1, _
		$_WD_BUTTON_Right = 2

Global Enum _
		$_WD_STORAGE_Local = 0, _
		$_WD_STORAGE_Session = 1

Global Enum _ ; _WD_FrameList() , _WD_FrameListFindElement()
		$_WD_FRAMELIST_Absolute = 0, _
		$_WD_FRAMELIST_Relative = 1, _
		$_WD_FRAMELIST_Attributes = 2, _
		$_WD_FRAMELIST_URL = 3, _
		$_WD_FRAMELIST_BodyID = 4, _
		$_WD_FRAMELIST_FrameVisibility = 5, _
		$_WD_FRAMELIST_MatchedElements = 6, _ ; array of matched element from _WD_FrameListFindElement()
		$_WD_FRAMELIST__COUNTER

#Tidy_ILC_Pos=42
Global Enum _                            ; https://www.w3schools.com/jsref/prop_doc_readystate.asp
		$_WD_READYSTATE_Uninitialized, _ ; Has not started loading
		$_WD_READYSTATE_Loading, _       ; Is loading
		$_WD_READYSTATE_Loaded, _        ; Has been loaded
		$_WD_READYSTATE_Interactive, _   ; Has loaded enough to interact with
		$_WD_READYSTATE_Complete, _      ; Fully loaded
		$_WD_READYSTATE__COUNTER
#Tidy_ILC_Pos=0

Global Const $aWD_READYSTATE[$_WD_READYSTATE__COUNTER][2] = [ _
		["uninitialized", "Has not started loading"], _
		["loading", "Is loading"], _
		["loaded", "Has been loaded"], _
		["interactive", "Has loaded enough to interact with"], _
		["complete", "Fully loaded"] _
		]

Global Enum _ ; Column positions of $aWD_READYSTATE
		$_WD_READYSTATE_State, _
		$_WD_READYSTATE_Desc

#EndRegion Global Constants

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_NewTab
; Description ...: Create new tab in current browser session.
; Syntax ........: _WD_NewTab($sSession[, $bSwitch = Default[, $iTimeout = Default[, $sURL = Default[, $sFeatures = Default]]]])
; Parameters ....: $sSession  - Session ID from _WD_CreateSession
;                  $bSwitch   - [optional] Switch session context to new tab? Default is True
;                  $iTimeout  - [optional] Period of time (in milliseconds) to wait before exiting function
;                  $sURL      - [optional] URL to be loaded in new tab
;                  $sFeatures - [optional] Comma-separated list of requested features of the new tab
; Return values .: Success - String representing handle of new tab.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_Timeout
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......: Specifying any features other than noopener or noreferrer, also has the effect of requesting a popup.
;                  See the link below for further details and a list of available features.
; Related .......: _WD_Window, _WD_LastHTTPResult
; Link ..........: https://developer.mozilla.org/en-US/docs/Web/API/Window/open#window_features
; Example .......: No
; ===============================================================================================================================
Func _WD_NewTab($sSession, $bSwitch = Default, $iTimeout = Default, $sURL = Default, $sFeatures = Default)
	Local Const $sFuncName = "_WD_NewTab"
	Local Const $sParameters = 'Parameters:    Switch=' & $bSwitch & '    Timeout=' & $iTimeout & '    URL=' & $sURL & '    Features=' & $sFeatures
	Local $sTabHandle = '', $sLastTabHandle, $hWaitTimer, $iTabIndex, $aTemp

	If $bSwitch = Default Then $bSwitch = True
	If $iTimeout = Default Then $iTimeout = $_WD_DefaultTimeout
	If $sURL = Default Then $sURL = ''
	If $sFeatures = Default Then $sFeatures = ''

	; Get handle for current tab
	Local $sCurrentTabHandle = _WD_Window($sSession, 'window')

	If $sFeatures = '' Then
		$sTabHandle = _WD_Window($sSession, 'new', '{"type":"tab"}')

		If @error = $_WD_ERROR_Success Then
			_WD_Window($sSession, 'Switch', '{"handle":"' & $sTabHandle & '"}')

			If $sURL Then _WD_Navigate($sSession, $sURL)

			If Not $bSwitch Then _WD_Window($sSession, 'Switch', '{"handle":"' & $sCurrentTabHandle & '"}')
		Else
			Return SetError(__WD_Error($sFuncName, $_WD_ERROR_Exception, $sParameters), 0, $sTabHandle)
		EndIf
	Else
		Local $aHandles = _WD_Window($sSession, 'handles')

		If @error <> $_WD_ERROR_Success Or Not IsArray($aHandles) Then
			Return SetError(__WD_Error($sFuncName, $_WD_ERROR_Exception, $sParameters), 0, $sTabHandle)
		EndIf

		Local $iTabCount = UBound($aHandles)

		; Get handle to current last tab
		$sLastTabHandle = $aHandles[$iTabCount - 1]

		If $sCurrentTabHandle Then
			; Search for current tab handle in array of tab handles. If not found,
			; then make the current tab handle equal to the last tab
			$iTabIndex = _ArraySearch($aHandles, $sCurrentTabHandle)

			If @error Then
				$sCurrentTabHandle = $sLastTabHandle
				$iTabIndex = $iTabCount - 1
			EndIf
		Else
			_WD_Window($sSession, 'Switch', '{"handle":"' & $sLastTabHandle & '"}')
			$sCurrentTabHandle = $sLastTabHandle
			$iTabIndex = $iTabCount - 1
		EndIf

		_WD_ExecuteScript($sSession, "window.open(arguments[0], '', arguments[1])", '"' & $sURL & '","' & $sFeatures & '"')

		If @error <> $_WD_ERROR_Success Then
			Return SetError(__WD_Error($sFuncName, $_WD_ERROR_Exception, $sParameters), 0, $sTabHandle)
		EndIf

		$hWaitTimer = TimerInit()

		While 1
			$aTemp = _WD_Window($sSession, 'handles')

			If UBound($aTemp) > $iTabCount Then
				$sTabHandle = $aTemp[$iTabIndex + 1]
				ExitLoop
			EndIf

			If TimerDiff($hWaitTimer) > $iTimeout Then Return SetError(__WD_Error($sFuncName, $_WD_ERROR_Timeout, $sParameters), 0, $sTabHandle)

			__WD_Sleep(10)
			If @error Then Return SetError(__WD_Error($sFuncName, @error, $sParameters), 0, $sTabHandle)
		WEnd

		If $bSwitch Then
			_WD_Window($sSession, 'Switch', '{"handle":"' & $sTabHandle & '"}')
		Else
			_WD_Window($sSession, 'Switch', '{"handle":"' & $sCurrentTabHandle & '"}')
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $_WD_ERROR_Success, $sParameters), 0, $sTabHandle)
EndFunc   ;==>_WD_NewTab

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_Attach
; Description ...: Attach to existing browser tab.
; Syntax ........: _WD_Attach($sSession, $sSearch[, $sMode = Default])
; Parameters ....: $sSession - Session ID from _WD_CreateSession
;                  $sSearch  - String to search for
;                  $sMode    - [optional] One of the following search modes:
;                  |Title (default)
;                  |URL
;                  |HTML
; Return values .: Success - String representing handle of matching tab.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_InvalidDataType
;                  - $_WD_ERROR_NoMatch
;                  - $_WD_ERROR_GeneralError
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_Window, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_Attach($sSession, $sSearch, $sMode = Default)
	Local Const $sFuncName = "_WD_Attach"
	Local Const $sParameters = 'Parameters:    Search=' & $sSearch & '    Mode=' & $sMode
	Local $sTabHandle = '', $bFound = False, $sCurrentTab = '', $aHandles
	Local $iErr = $_WD_ERROR_Success

	If $sMode = Default Then $sMode = 'title'

	$aHandles = _WD_Window($sSession, 'handles')
	$iErr = @error

	If $iErr = $_WD_ERROR_Success Then
		$sCurrentTab = _WD_Window($sSession, 'window')

		For $sHandle In $aHandles

			_WD_Window($sSession, 'Switch', '{"handle":"' & $sHandle & '"}')

			Switch $sMode
				Case "title", "url"
					If StringInStr(_WD_Action($sSession, $sMode), $sSearch) > 0 Then
						$bFound = True
						$sTabHandle = $sHandle
						ExitLoop
					EndIf

				Case 'html'
					If StringInStr(_WD_GetSource($sSession), $sSearch) > 0 Then
						$bFound = True
						$sTabHandle = $sHandle
						ExitLoop
					EndIf

				Case Else
					Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(Title|URL|HTML) $sMode=>" & $sMode), 0, $sTabHandle)
			EndSwitch
		Next

		If Not $bFound Then
			; Restore prior active tab
			If $sCurrentTab <> '' Then
				_WD_Window($sSession, 'Switch', '{"handle":"' & $sCurrentTab & '"}')
			EndIf

			$iErr = $_WD_ERROR_NoMatch
		EndIf
	ElseIf Not $_WD_DetailedErrors Then
		$iErr = $_WD_ERROR_GeneralError
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sTabHandle)
EndFunc   ;==>_WD_Attach

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_LinkClickByText
; Description ...: Simulate a mouse click on a link with text matching the provided string.
; Syntax ........: _WD_LinkClickByText($sSession, $sText[, $bPartial = Default[, $sStartNodeID = Default]])
; Parameters ....: $sSession      - Session ID from _WD_CreateSession
;                  $sText         - Text to find in link
;                  $bPartial      - [optional] Search by partial text? Default is True
;                  $sStartNodeID  - [optional] Element ID to use as starting HTML node. Default is ""
; Return values .: Success - None.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NoMatch
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_FindElement, _WD_ElementAction, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_LinkClickByText($sSession, $sText, $bPartial = Default, $sStartNodeID = Default)
	Local Const $sFuncName = "_WD_LinkClickByText"
	Local Const $sParameters = 'Parameters:   Text=' & $sText & '   Partial=' & $bPartial & '   StartElement=' & $sStartNodeID

	If $bPartial = Default Then $bPartial = True
	If $sStartNodeID = Default Then $sStartNodeID = ""

	Local $sElement = _WD_FindElement($sSession, ($bPartial) ? $_WD_LOCATOR_ByPartialLinkText : $_WD_LOCATOR_ByLinkText, $sText, $sStartNodeID)
	Local $iErr = @error

	If $iErr = $_WD_ERROR_Success Then
		_WD_ElementAction($sSession, $sElement, 'click')
		$iErr = @error

		If $iErr <> $_WD_ERROR_Success And Not $_WD_DetailedErrors Then
			$iErr = $_WD_ERROR_Exception
		EndIf
	Else
		$iErr = $_WD_ERROR_NoMatch
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, "")
EndFunc   ;==>_WD_LinkClickByText

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_WaitElement
; Description ...: Wait for an element in the current tab before returning.
; Syntax ........: _WD_WaitElement($sSession, $sStrategy, $sSelector[, $iDelay = Default[, $iTimeout = Default[, $iOptions = Default]]])
; Parameters ....: $sSession  - Session ID from _WD_CreateSession
;                  $sStrategy - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector - Indicates how the WebDriver should traverse through the HTML DOM to locate the desired element(s).
;                  $iDelay    - [optional] Milliseconds to wait before initially checking status
;                  $iTimeout  - [optional] Period of time (in milliseconds) to wait before exiting function
;                  $iOptions  - [optional] Binary flags to perform additional actions:
;                  |$_WD_OPTION_None    (0) = No optional feature processing
;                  |$_WD_OPTION_Visible (1) = Confirm element is visible
;                  |$_WD_OPTION_Enabled (2) = Confirm element is enabled
;                  |$_WD_OPTION_NoMatch (8) = Confirm element is not found
;                  |$_WD_OPTION_Hidden (16) = Confirm element is not visible
; Return values .: Success - Element ID returned by web driver.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_InvalidArgue
;                  - $_WD_ERROR_Timeout
;                  - $_WD_ERROR_UserAbort
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......: _WD_FindElement, _WD_ElementAction, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_WaitElement($sSession, $sStrategy, $sSelector, $iDelay = Default, $iTimeout = Default, $iOptions = Default)
	Local Const $sFuncName = "_WD_WaitElement"
	Local Const $sParameters = 'Parameters:   Strategy=' & $sStrategy & '   Selector=' & $sSelector & '   Delay=' & $iDelay & '   Timeout=' & $iTimeout & '   Options=' & $iOptions
	Local $iErr, $sElement, $bIsVisible = True, $bIsEnabled = True
	$_WD_HTTPRESULT = 0
	$_WD_HTTPRESPONSE = ''

	If $iDelay = Default Then $iDelay = 0
	If $iTimeout = Default Then $iTimeout = $_WD_DefaultTimeout
	If $iOptions = Default Then $iOptions = $_WD_OPTION_None

	Local Const $bVisible = BitAND($iOptions, $_WD_OPTION_Visible)
	Local Const $bEnabled = BitAND($iOptions, $_WD_OPTION_Enabled)
	Local Const $bNoMatch = BitAND($iOptions, $_WD_OPTION_NoMatch)
	Local Const $bHidden = BitAND($iOptions, $_WD_OPTION_Hidden)

	; Other options aren't valid if No Match or Hidden option is supplied
	If ($bNoMatch And $iOptions <> $_WD_OPTION_NoMatch) Or _
			($bHidden And $iOptions <> $_WD_OPTION_Hidden) Then
		$iErr = $_WD_ERROR_InvalidArgue
	Else
		__WD_Sleep($iDelay)
		$iErr = @error

		; prevent multiple errors https://github.com/Danp2/au3WebDriver/pull/290#issuecomment-1100707095
		Local $_WD_DEBUG_Saved = $_WD_DEBUG ; save current DEBUG level

		; Prevent logging from _WD_FindElement if not in Full debug mode
		If $_WD_DEBUG <> $_WD_DEBUG_Full Then $_WD_DEBUG = $_WD_DEBUG_None

		Local $hWaitTimer = TimerInit()
		While 1
			If $iErr Then ExitLoop

			$sElement = _WD_FindElement($sSession, $sStrategy, $sSelector)
			$iErr = @error

			If $iErr <> $_WD_ERROR_Success And $iErr <> $_WD_ERROR_NoMatch Then
				; Exit loop if unexpected error occurs
				ExitLoop

			ElseIf $iErr = $_WD_ERROR_NoMatch And $bNoMatch Then
				; if element wasn't found and "no match" option is active
				; exit loop indicating success
				$iErr = $_WD_ERROR_Success
				ExitLoop

			ElseIf $iErr = $_WD_ERROR_Success And Not $bNoMatch Then
				; if element was found and "no match" option isn't active
				; check other options
				If $bVisible Or $bHidden Then
					$bIsVisible = _WD_ElementAction($sSession, $sElement, 'displayed')

					If @error Then
						$bIsVisible = False
					EndIf

				EndIf

				If $bEnabled Then
					$bIsEnabled = _WD_ElementAction($sSession, $sElement, 'enabled')

					If @error Then
						$bIsEnabled = False
					EndIf
				EndIf

				Select
					Case $bHidden
						If Not $bIsVisible Then ExitLoop

					Case $bIsVisible And $bIsEnabled
						ExitLoop

					Case Else
						$sElement = ''
				EndSelect
			EndIf

			If (TimerDiff($hWaitTimer) > $iTimeout) Then
				$iErr = $_WD_ERROR_Timeout
				ExitLoop
			EndIf

			__WD_Sleep(10)
			$iErr = @error
		WEnd
		$_WD_DEBUG = $_WD_DEBUG_Saved ; restore DEBUG level
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sElement)
EndFunc   ;==>_WD_WaitElement

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_WaitScript
; Description ...: Wait for a JavaScript snippet to return true.
; Syntax ........: _WD_WaitScript($sSession, $sJavaScript[, $iDelay = Default[, $iTimeout = Default[, $iOptions = Default]]])
; Parameters ....: $sSession  - Session ID from _WD_CreateSession
;                  $sJavaScript - JavaScript to run
;                  $iDelay    - [optional] Milliseconds to wait before initially checking status
;                  $iTimeout  - [optional] Period of time (in milliseconds) to wait before exiting function
; Return values .: Success - True
;                  Failure - False and sets @error to one of the following values:
;                  - $_WD_ERROR_InvalidArgue
;                  - $_WD_ERROR_RetValue
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_Timeout
;                  - $_WD_ERROR_UserAbort
; Author ........: yehiaserag
; Modified ......:
; Remarks .......: The Javascript needs to return either True or False.
; Related .......: _WD_ExecuteScript
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_WaitScript($sSession, $sJavaScript, $iDelay = Default, $iTimeout = Default)
	Local Const $sFuncName = "_WD_WaitScript"
	Local Const $sParameters = 'Parameters:   JavaScript=' & $sJavaScript & '   Delay=' & $iDelay & '   Timeout=' & $iTimeout
	Local $iErr
	Local $bValue = False

	If $iDelay = Default Then $iDelay = 0
	If $iTimeout = Default Then $iTimeout = $_WD_DefaultTimeout

	If StringLeft($sJavaScript, 6) <> "return" Then
		$iErr = $_WD_ERROR_InvalidArgue
	Else
		__WD_Sleep($iDelay)
		$iErr = @error

		; prevent multiple errors https://github.com/Danp2/au3WebDriver/pull/290#issuecomment-1100707095
		Local $_WD_DEBUG_Saved = $_WD_DEBUG ; save current DEBUG level

		; Prevent logging from _WD_ExecuteScript if not in Full debug mode
		If $_WD_DEBUG <> $_WD_DEBUG_Full Then $_WD_DEBUG = $_WD_DEBUG_None

		Local $hWaitTimer = TimerInit()
		While 1
			If $iErr Then ExitLoop

			$bValue = _WD_ExecuteScript($sSession, 'return !!((function(){' & $sJavaScript & '})())', Default, Default, $_WD_JSON_Value)
			$iErr = @error

			If $iErr <> $_WD_ERROR_Success Then
				; Exit loop if unexpected error occurs
				ExitLoop
			ElseIf $bValue = False Then
				If (TimerDiff($hWaitTimer) > $iTimeout) Then
					$iErr = $_WD_ERROR_Timeout
					ExitLoop
				EndIf

				__WD_Sleep(10)
				$iErr = @error
			Else
				$iErr = $_WD_ERROR_Success
				ExitLoop
			EndIf
		WEnd
		$_WD_DEBUG = $_WD_DEBUG_Saved ; restore DEBUG level
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $bValue)
EndFunc   ;==>_WD_WaitScript

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_DebugSwitch
; Description ...: Switch to new debug level or switch back to saved debug level
; Syntax ........: _WD_DebugSwitch([$vMode = Default])
; Parameters ....: $vMode - [optional] Set new $_WD_DEBUG level. When not specified (Default) restore saved debug level.
; Return values .: Success - current stack size
;                  Failure - negative values indicate an error
; Author ........: mLipok
; Modified ......:
; Remarks .......: @error and @extended values are preserved by this function and did not originate within it
; Related .......:
; Link ..........:
; Example .......: _WD_DebugSwitch($_WD_DEBUG_Full)
; ===============================================================================================================================
Func _WD_DebugSwitch($vMode = Default, $iErr = @error, $iExt = @extended)
	Local Const $sFuncName = "_WD_DebugSwitch"
	Local Static $a_WD_DEBUG_SavedStack[0] ; first usage - empty stack array
	Local $iStackSize = UBound($a_WD_DEBUG_SavedStack)
	Local $sMessage = ''

	If $vMode = Default Then ; restoring saved debug level
		If $iStackSize Then
			$_WD_DEBUG = $a_WD_DEBUG_SavedStack[$iStackSize - 1] ; restore previous debug level from last element on the stack
			$iStackSize -= 1 ; decrease stack size
			ReDim $a_WD_DEBUG_SavedStack[$iStackSize] ; trim array - stack last element
		Else
			$iStackSize = -1
			$sMessage = 'There are no saved debug levels'
		EndIf
	ElseIf IsInt($vMode) And $vMode >= $_WD_DEBUG_None And $vMode <= $_WD_DEBUG_Full Then ; setting new debug level
		$iStackSize += 1 ; increase stack size
		ReDim $a_WD_DEBUG_SavedStack[$iStackSize] ; resize array - add new position to the stack
		$a_WD_DEBUG_SavedStack[$iStackSize - 1] = $_WD_DEBUG ; store current debug level to the stack
		$_WD_DEBUG = $vMode ; set new debug level
	Else
		$iStackSize = -2
		$sMessage = 'Invalid argument in function-call'
	EndIf

	$sMessage &= " / " & (($iStackSize < 0) ? (" error code: ") : (" stack size: ")) & $iStackSize
	__WD_ConsoleWrite($sFuncName & ": " & $sMessage, $_WD_DEBUG_Info)
	Return SetError($iErr, $iExt, $iStackSize) ; do not use __WD_Error() here as $iErr and $iExt are preserved and not belongs to this function
EndFunc   ;==>_WD_DebugSwitch

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetMouseElement
; Description ...: Retrieves reference to element below mouse pointer.
; Syntax ........: _WD_GetMouseElement($sSession)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
; Return values .: Success - Element ID returned by web driver.
;                  Failure - Response from web driver and sets @error returned from _WD_ExecuteScript()
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......: _WD_ExecuteScript, _WD_LastHTTPResult
; Link ..........: https://stackoverflow.com/questions/24538450/get-element-currently-under-mouse-without-using-mouse-events
; Example .......: No
; ===============================================================================================================================
Func _WD_GetMouseElement($sSession)
	Local Const $sFuncName = "_WD_GetMouseElement"
	Local $sScript = "return Array.from(document.querySelectorAll(':hover')).pop()"
	Local $sElement = _WD_ExecuteScript($sSession, $sScript, '', Default, $_WD_JSON_Element)
	Local $iErr = @error

	Return SetError(__WD_Error($sFuncName, $iErr, $sElement), 0, $sElement)
EndFunc   ;==>_WD_GetMouseElement

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetElementFromPoint
; Description ...: Retrieves reference to element at specified point.
; Syntax ........: _WD_GetElementFromPoint($sSession, $iX, $iY)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
;                  $iX       - an integer value
;                  $iY       - an integer value
; Return values .: Success - Element ID returned by web driver.
;                  Failure - "" (empty string) and @error is set to one of the following values:
;                  - $_WD_ERROR_RetValue
;                  - $_WD_ERROR_InvalidArgue
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......: @extended is set to 1 if the browsing context changed during the function call
; Related .......: _WD_ExecuteScript, _WD_LastHTTPResult
; Link ..........: https://stackoverflow.com/questions/31910534/executing-javascript-elementfrompoint-through-selenium-driver/32574543#32574543
; Example .......: No
; ===============================================================================================================================
Func _WD_GetElementFromPoint($sSession, $iX, $iY)
	Local Const $sFuncName = "_WD_GetElementFromPoint"
	Local Const $sParameters = 'Parameters:    X=' & $iX & '    Y=' & $iY
	Local $sResponse, $oJSON, $sElement = ""
	Local $sTagName, $sParams, $aCoords, $iFrame = 0, $oERect
	Local $sScript1 = "return document.elementFromPoint(arguments[0], arguments[1]);"
	Local $sScript2 = "return new Array(window.pageXOffset, window.pageYOffset);"
	Local $iErr = $_WD_ERROR_Success, $sResult, $bIsNull

	; https://developer.mozilla.org/en-US/docs/Web/API/Document/elementFromPoint
	; If the specified point is outside the visible bounds of the document or either
	; coordinate is negative, the result is null
	If $iX < 0 Or $iY < 0 Then
		$iErr = $_WD_ERROR_InvalidArgue
	EndIf

	While $iErr = $_WD_ERROR_Success
		$sParams = $iX & ", " & $iY
		$sResponse = _WD_ExecuteScript($sSession, $sScript1, $sParams)
		If @error Then
			$iErr = $_WD_ERROR_RetValue
			ExitLoop
		EndIf

		$oJSON = Json_Decode($sResponse)
		$sElement = Json_Get($oJSON, $_WD_JSON_Element)

		If @error Then
			$sResult = Json_Get($oJSON, $_WD_JSON_Value)
			$bIsNull = (IsKeyword($sResult) = $KEYWORD_NULL)

			If Not $bIsNull Then
				$iErr = $_WD_ERROR_RetValue
			EndIf

			ExitLoop
		Else
			$sTagName = _WD_ElementAction($sSession, $sElement, "Name")
			If Not StringInStr($sTagName, "frame") Then ; check <iframe> and <frame> element
				ExitLoop
			EndIf

			$aCoords = _WD_ExecuteScript($sSession, $sScript2, $_WD_EmptyDict, Default, $_WD_JSON_Value)
			If @error Then
				$iErr = $_WD_ERROR_RetValue
				ExitLoop
			EndIf

			$oERect = _WD_ElementAction($sSession, $sElement, 'rect')

			; changing the coordinates in relation to left top corner of frame
			$iX -= ($oERect.Item('x') - Int($aCoords[0]))
			$iY -= ($oERect.Item('y') - Int($aCoords[1]))

			_WD_FrameEnter($sSession, $sElement)
			$iFrame = 1
		EndIf
	WEnd

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters, $iFrame), $iFrame, $sElement)
EndFunc   ;==>_WD_GetElementFromPoint

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetFrameCount
; Description ...: Returns the number of frames/iframes in the current document context.
; Syntax ........: _WD_GetFrameCount($sSession)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
; Return values .: Success - Number of frames
;                  Failure - 0 and sets @error to $_WD_ERROR_Exception
; Author ........: Decibel, Danp2
; Modified ......: mLipok
; Remarks .......: Nested frames are not included in the frame count
; Related .......: _WD_ExecuteScript, _WD_LastHTTPResult
; Link ..........: https://www.w3schools.com/jsref/prop_win_length.asp
; Example .......: No
; ===============================================================================================================================
Func _WD_GetFrameCount($sSession)
	Local Const $sFuncName = "_WD_GetFrameCount"
	Local $iValue = _WD_ExecuteScript($sSession, "return window.frames.length", Default, Default, $_WD_JSON_Value)
	Local $iErr = @error
	If $iErr Then $iValue = 0
	Return SetError(__WD_Error($sFuncName, $iErr), 0, Number($iValue))
EndFunc   ;==>_WD_GetFrameCount

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_IsWindowTop
; Description ...: Returns a boolean of the session being at the top level, or in a frame(s).
; Syntax ........: _WD_IsWindowTop($sSession)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
; Return values .: Success - Boolean response.
;                  Failure - Response from webdriver and sets @error returned from _WD_ExecuteScript()
; Author ........: Decibel
; Modified ......: mLipok
; Remarks .......:
; Related .......: _WD_ExecuteScript, _WD_LastHTTPResult
; Link ..........: https://www.w3schools.com/jsref/prop_win_top.asp
; Example .......: No
; ===============================================================================================================================
Func _WD_IsWindowTop($sSession)
	Local Const $sFuncName = "_WD_IsWindowTop"
	Local $blnResult = _WD_ExecuteScript($sSession, "return window.top == window.self", Default, Default, $_WD_JSON_Value)
	Local $iErr = @error
	Return SetError(__WD_Error($sFuncName, $iErr), 0, $blnResult)
EndFunc   ;==>_WD_IsWindowTop

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_FrameEnter
; Description ...: Enter the specified frame.
; Syntax ........: _WD_FrameEnter($sSession, $vIdentifier)
; Parameters ....: $sSession    - Session ID from _WD_CreateSession
;                  $vIdentifier - Target frame identifier. Can be any of the following:
;                  |Null    - Return to top-most browsing context
;                  |String  - Element ID from _WD_FindElement or path like 'null/2/0'
;                  |Integer - 0-based index of frames
; Return values .: Success - True.
;                  Failure - WD Response error message (E.g. "no such frame") and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
; Author ........: Decibel
; Modified ......: Danp2, mLipok, jchd
; Remarks .......: You can drill-down into nested frames by calling this function repeatedly or use identifier like 'null/2/0'
; Related .......: _WD_Window, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_FrameEnter($sSession, $vIdentifier)
	Local Const $sFuncName = "_WD_FrameEnter"
	If String($vIdentifier) = 'null' Then $vIdentifier = Null ; String must be used because checking 0 = 'null' is True
	Local Const $bIsIdentifierNull = (IsKeyword($vIdentifier) = $KEYWORD_NULL)
	Local Const $sParameters = 'Parameters:    Identifier=' & ($bIsIdentifierNull ? ("Null") : ($vIdentifier))
	Local $sValue, $sMessage = '', $sOption, $sResponse, $oJSON
	Local $iErr = $_WD_ERROR_Success

	; must start with null or digit, must have at least one slash (may have many slashes but should not be followed one per other), must end with digit
	Local Const $bIdentifierAsPath = StringRegExp($vIdentifier, "(?i)\A(?:Null|\d+)(?:\/\d+)+\Z", $STR_REGEXPMATCH)

	If $bIdentifierAsPath Then
		; will be processed below
	ElseIf $bIsIdentifierNull Then
		$sOption = '{"id":null}'
	ElseIf IsInt($vIdentifier) Then
		$sOption = '{"id":' & $vIdentifier & '}'
	Else
		$sOption = '{"id":' & __WD_JsonElement($vIdentifier) & '}'
	EndIf

	If Not $bIdentifierAsPath Then
		$sResponse = _WD_Window($sSession, "frame", $sOption)
		$iErr = @error
	Else
		Local $aIdentifiers = StringSplit($vIdentifier, '/')
		For $i = 1 To $aIdentifiers[0]
			If String($aIdentifiers[$i]) = 'null' Then
				$aIdentifiers[$i] = '{"id":null}'
			Else
				$aIdentifiers[$i] = '{"id":' & $aIdentifiers[$i] & '}'
			EndIf
			$sResponse = _WD_Window($sSession, "frame", $aIdentifiers[$i])
			If Not @error Then ContinueLoop

			$iErr = @error
			$sMessage = ' Error on ID#' & $i & ' > ' & $aIdentifiers[$i]
			ExitLoop
		Next
	EndIf

	If $iErr = $_WD_ERROR_Success Then
		$oJSON = Json_Decode($sResponse)
		$sValue = Json_Get($oJSON, $_WD_JSON_Value)

		;*** Evaluate the response
		If $sValue <> Null Then
			$sValue = Json_Get($oJSON, $_WD_JSON_Error)
		Else
			$sValue = True
		EndIf
	ElseIf Not $_WD_DetailedErrors Then
		$iErr = $_WD_ERROR_Exception
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters & $sMessage), 0, $sValue)
EndFunc   ;==>_WD_FrameEnter

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_FrameLeave
; Description ...: Leave the current frame, to its parent.
; Syntax ........: _WD_FrameLeave($sSession)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
; Return values .: Success - True.
;                  Failure - WD Response error message (E.g. "chrome not reachable") and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
; Author ........: Decibel
; Modified ......: Danp2
; Remarks .......:
; Related .......: _WD_Window, _WD_LastHTTPResult
; Link ..........: https://www.w3.org/TR/webdriver/#switch-to-parent-frame
; Example .......: No
; ===============================================================================================================================
Func _WD_FrameLeave($sSession)
	Local Const $sFuncName = "_WD_FrameLeave"
	Local $sValue, $oJSON, $sOption = '{}'

	Local $sResponse = _WD_Window($sSession, "parent", $sOption)
	Local $iErr = @error

	If $iErr = $_WD_ERROR_Success Then
		$oJSON = Json_Decode($sResponse)
		$sValue = Json_Get($oJSON, $_WD_JSON_Value)

		;*** Evaluate the response
		If $sValue <> Null Then
			$sValue = Json_Get($oJSON, $_WD_JSON_Error)
		Else
			$sValue = True
		EndIf
	ElseIf Not $_WD_DetailedErrors Then
		$iErr = $_WD_ERROR_Exception
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr), 0, $sValue)
EndFunc   ;==>_WD_FrameLeave

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_FrameList
; Description ...: Retrieves a detailed list of the main document and all associated frames
; Syntax ........: _WD_FrameList($sSession[, $bReturnAsArray = True[, $iDelay = 1000[, $iTimeout = Default]]])
; Parameters ....: $sSession            - Session ID from _WD_CreateSession
;                  $bReturnAsArray      - [optional] Return result as array? Default is True.
;                  $iDelay              - [optional] Single delay before checking first frame. Default is 1000 ms
;                  $iTimeout            - [optional] Timeout for _WD_LoadWait() calls for each frame. Default is $_WD_DefaultTimeout
; Return values .: Success - 2D array (with 7 cols) or string ( delimited with | and @CRLF ) @extended contains information about frame count
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_GeneralError
;                  - $_WD_ERROR_Timeout
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NotFound
;                  - $_WD_ERROR_RetValue
;                  - $_WD_ERROR_UserAbort
; Author ........: mLipok
; Modified ......: Danp2
; Remarks .......: The returned list of frames can depend on many factors, including geolocation, as well as problems with the local Internet
; Related .......: _WD_GetFrameCount, _WD_FrameEnter, _WD_FrameLeave
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_FrameList($sSession, $bReturnAsArray = True, $iDelay = 1000, $iTimeout = Default)
	Local Const $sFuncName = "_WD_FrameList"
	Local Const $sParameters = 'Parameters:    ReturnAsArray=' & $bReturnAsArray & '    iDelay=' & $iDelay & '    iTimeout=' & $iTimeout
	Local $a_Result[0][$_WD_FRAMELIST__COUNTER], $sStartLocation = '', $sMessage = ''
	Local $vResult = '', $iErr = $_WD_ERROR_Success, $iFrameCount = 0

	Local Const $sElement_CallingFrameBody = _WD_ExecuteScript($sSession, "return window.document.body;", Default, Default, $_WD_JSON_Element)
	If Not @error Then
		__WD_Sleep($iDelay)
	EndIf
	If Not @error Then
		$vResult = __WD_FrameList_Internal($sSession, 'null', '', False, $iTimeout)
	EndIf
	$iErr = @error
	#Region - post processing
	If $iErr = $_WD_ERROR_Success Then
		; Strip last @CRLF
		$vResult = StringTrimRight($vResult, 2)

		; create array of frames from string returned from __WD_FrameList_Internal
		_ArrayAdd($a_Result, $vResult)

		; check the results
		For $i = 0 To UBound($a_Result) - 1
			; find "calling frame" location - set $sStartLocation
			If $a_Result[$i][$_WD_FRAMELIST_BodyID] = $sElement_CallingFrameBody Then $sStartLocation = $a_Result[$i][$_WD_FRAMELIST_Absolute]

			; recalculate locations from absolute path on COL0 to relative path on COL1
			$a_Result[$i][$_WD_FRAMELIST_Relative] = StringRegExpReplace($a_Result[$i][$_WD_FRAMELIST_Absolute], '\A' & $sStartLocation & '\/?', '')
		Next

	ElseIf $iErr <> $_WD_ERROR_Timeout And $iErr <> $_WD_ERROR_UserAbort And Not $_WD_DetailedErrors Then
		$iErr = $_WD_ERROR_GeneralError
	EndIf

	$iFrameCount = UBound($a_Result, $UBOUND_ROWS)
	If $iFrameCount < 1 Then $sMessage &= 'List of frames is empty. '

	; select desired DataType for the $vResult - usually string is option for testing and asking support, thus Array is returned by default
	If $bReturnAsArray Then
		$vResult = $a_Result
	Else
		$vResult = _ArrayToString($a_Result) ; getting string with recalculated locations (relative path)
		If @error Then
			$iErr = $_WD_ERROR_RetValue
			$sMessage = 'ArrayToString conversion failed. '
			$vResult = ''
		EndIf
	EndIf

	If $sStartLocation Then ; Back to "calling frame"
		_WD_FrameEnter($sSession, $sStartLocation)
		$iErr = @error
		If $iErr Then
			$sMessage &= 'Was not able back to "calling frame".'
			If Not $_WD_DetailedErrors Then $iErr = $_WD_ERROR_Exception
		EndIf
	Else
		$sMessage &= 'Was not able to check "calling frame".'
		$iErr = $_WD_ERROR_NotFound
	EndIf

	#EndRegion - post processing

	$sMessage = ($sMessage And $_WD_DEBUG > $_WD_DEBUG_Error) ? ('	Information: ' & $sMessage) : ("")
	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters & $sMessage, $iFrameCount), $iFrameCount, $vResult)
EndFunc   ;==>_WD_FrameList

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __WD_FrameList_Internal
; Description ...: function that is used internally in _WD_FrameList, even recursively when nested frames are available
; Syntax ........: __WD_FrameList_Internal($sSession, $sLevel, $sFrameAttributes, $bIsHidden, $iTimeout)
; Parameters ....: $sSession            - Session ID from _WD_CreateSession
;                  $sLevel              - frame location level path
;                  $sFrameAttributes    - frame attributes in HTML format
;                  $bIsHidden           - information about visibility of frame - taken by WebDriver
;                  $iTimeout            - Timeout for _WD_LoadWait() calls for each frame
; Return values .: Success - string
;                  Failure - "" (empty string) and sets @error returned from related functions
; Author ........: mLipok
; Modified ......: Danp2
; Remarks .......:
; Related .......: _WD_FrameEnter, _WD_LoadWait, _WD_ExecuteScript, _WD_GetFrameCount, _WD_ElementAction, _WD_FrameLeave
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func __WD_FrameList_Internal($sSession, $sLevel, $sFrameAttributes, $bIsHidden, $iTimeout)
	Local Const $sFuncName = "__WD_FrameList_Internal"
	Local Const $sParameters = 'Parameters:    Level=' & $sLevel & '    IsHidden=' & $bIsHidden & '    Timeout=' & $iTimeout ; intentionally $sFrameAttributes is not listed here to not put too many data into the log
	Local $iErr = $_WD_ERROR_Success, $sMessage = '', $vResult = ''
	Local $s_URL = '', $sCurrentBody_ElementID = ''

	#Region ; this region is prevented from redundant logging if not in Full debug mode - https://github.com/Danp2/au3WebDriver/pull/362#issuecomment-1220962556
	Local Static $_WD_DEBUG_Saved = Null ; this is static because this function will be run recurrently and we need to keep outer debug level
	If $_WD_DEBUG_Saved = Null Then
		$_WD_DEBUG_Saved = $_WD_DEBUG ; save current DEBUG level
		If $_WD_DEBUG_Saved <> $_WD_DEBUG_Full Then $_WD_DEBUG = $_WD_DEBUG_None ; Prevent logging multiple errors from __WD_FrameList_Internal
	EndIf

	_WD_FrameEnter($sSession, $sLevel)
	$iErr = @error
	If $iErr Then
		$sMessage = 'Error occurred on "' & $sLevel & '" level when trying to entering frame'
	Else
		_WD_LoadWait($sSession, 0, $iTimeout, Default, $_WD_READYSTATE_Complete) ; wait until current frame is fully loaded
		$iErr = @error
		If $iErr And $iErr <> $_WD_ERROR_Timeout Then
			$sMessage = 'Error occurred on "' & $sLevel & '" level when waiting for a browser page load to complete'
		Else
			$sCurrentBody_ElementID = _WD_ExecuteScript($sSession, "return window.document.body;", Default, Default, $_WD_JSON_Element)
			$iErr = @error
			If $iErr Then
				$sMessage = 'Error occurred on "' & $sLevel & '" level when checking "document.body" ElementID'
			Else
				$s_URL = _WD_ExecuteScript($sSession, "return window.location.href", Default, Default, $_WD_JSON_Value)
				$iErr = @error
				If $iErr Then
					$sMessage = 'Error occurred on "' & $sLevel & '" level when checking URL'
				EndIf
			EndIf
		EndIf
	EndIf
	$vResult = $sLevel & '|' & $sLevel & '|' & $sFrameAttributes & '|' & $s_URL & '|' & $sCurrentBody_ElementID & '|' & $bIsHidden & '|' & @CRLF

	If Not $iErr Then
		Local $iFrameCount = _WD_GetFrameCount($sSession)
		$iErr = @error
		If $iErr Then
			$sMessage = 'Error occurred on "' & $sLevel & '" level when trying to check frames count'
		Else
			Local $sFrameElementID
			Local Const $sJavaScript_FrameAttributes = "function FrameAttributes(FrameIDX) { let nodes = document.querySelectorAll('iframe');    if (nodes.length)   { return nodes[FrameIDX].outerHTML; }   else   { return window.frames[FrameIDX].frameElement.outerHTML; } }; return FrameAttributes(%s);"
			Local Const $sJavaScript_FrameElementID = "function FrameElementID(FrameIDX) { let nodes = document.querySelectorAll('iframe');    if (nodes.length)   { return nodes[FrameIDX]; }   else   { return document.querySelectorAll('frame')[FrameIDX]; } }; return FrameElementID(%s);"
			For $iFrame = 0 To $iFrameCount - 1
				If $sMessage Or $iErr Then ; message from last subframe is logged in the end of this function - not within For To Next loop
					$_WD_DEBUG = $_WD_DEBUG_Saved ; turn off prevention for a moment
					__WD_Error($sFuncName, $iErr, $sParameters & '	Information: ' & $sMessage) ; log messages which comes from loop processing
					If $_WD_DEBUG <> $_WD_DEBUG_Full Then $_WD_DEBUG = $_WD_DEBUG_None ; again turn on prevention
				EndIf
				$sMessage = '' ; clear recent/previous message
				$sFrameAttributes = _WD_ExecuteScript($sSession, StringFormat($sJavaScript_FrameAttributes, $iFrame), Default, Default, $_WD_JSON_Value)
				$iErr = @error
				If $iErr Then
					$sMessage = 'Error occurred on "' & $sLevel & '" level when trying to check attributes of subframe "' & $sLevel & '/' & $iFrame & '"'
					ContinueLoop
				Else
					$sFrameAttributes = StringRegExpReplace($sFrameAttributes, '\R', '')
					$sFrameElementID = _WD_ExecuteScript($sSession, StringFormat($sJavaScript_FrameElementID, $iFrame), Default, Default, $_WD_JSON_Element)
					$iErr = @error
					If $iErr Then
						$sMessage = 'Error occurred on "' & $sLevel & '" level when trying to get ElementID of subframe "' & $sLevel & '/' & $iFrame & '"'
						ContinueLoop
					Else
						$bIsHidden = Not (_WD_ElementAction($sSession, $sFrameElementID, 'DISPLAYED'))
						$iErr = @error
						If $iErr Then
							$sMessage = 'Error occurred on "' & $sLevel & '" level when trying to check visibility of subframe "' & $sLevel & '/' & $iFrame & '"'
							ContinueLoop
						Else
							$vResult &= __WD_FrameList_Internal($sSession, $sLevel & '/' & $iFrame, $sFrameAttributes, $bIsHidden, $iTimeout)
							$iErr = @error
							If $iErr Then
								$sMessage = 'Error occurred on "' & $sLevel & '" level after processing subframe "' & $sLevel & '/' & $iFrame & '"'
								ContinueLoop
							Else
								_WD_FrameLeave($sSession)
								$iErr = @error
								If $iErr Then
									$sMessage = 'Error occurred on "' & $sLevel & '" level when trying to leave subframe "' & $sLevel & '/' & $iFrame & '"'
									ExitLoop
								EndIf
							EndIf
						EndIf
					EndIf
				EndIf
			Next
		EndIf
	EndIf

	If $sLevel = 'null' Then ; checking if exiting main (top level) __WD_FrameList_Internal() call
		$_WD_DEBUG = $_WD_DEBUG_Saved ; restore DEBUG level
		$_WD_DEBUG_Saved = Null ; reset staticly defined saved debug level in order to get new one when function will be called from user script
	EndIf
	#EndRegion ; this region is prevented from redundant logging if not in Full debug mode - https://github.com/Danp2/au3WebDriver/pull/362#issuecomment-1220962556

	$sMessage = ($sMessage And $_WD_DEBUG > $_WD_DEBUG_Error) ? ('	Information: ' & $sMessage) : ("")
	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters & $sMessage), 0, $vResult)
EndFunc   ;==>__WD_FrameList_Internal

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_HighlightElements
; Description ...: Highlights the specified elements.
; Syntax ........: _WD_HighlightElements($sSession, $vElements[, $iMethod = Default])
; Parameters ....: $sSession  - Session ID from _WD_CreateSession
;                  $vElements - Element ID from _WD_FindElement (single element as string; multiple elements as array)
;                  $iMethod   - [optional] an integer value to set the style (default = 1)
;                  0 - Remove highlight
;                  1 - Highlight border dotted red
;                  2 - Highlight yellow rounded box
;                  3 - Highlight yellow rounded box + border  dotted red
; Return values .: Success - True
;                  Failure - False and sets @error to _WD_ERROR_InvalidArgue or the error code from _WD_ExecuteScript()
; Author ........: Danyfirex
; Modified ......: mLipok, Danp2
; Remarks .......:
; Related .......: _WD_LastHTTPResult
; Link ..........: https://www.autoitscript.com/forum/topic/192730-webdriver-udf-help-support/?do=findComment&comment=1396643
; Example .......: No
; ===============================================================================================================================
Func _WD_HighlightElements($sSession, $vElements, $iMethod = Default)
	Local Const $sFuncName = "_WD_HighlightElements"
	Local Const $sParameters = 'Parameters:    Element=' & (IsArray($vElements) ? "<array>" : $vElements) & '    Method=' & $iMethod
	Local Const $aMethod[] = _
			[ _
			"border: 0px;", _
			"border: 2px dotted red;", _
			"background: #FFFF66; border-radius: 5px; padding-left: 3px;", _
			"border: 2px dotted red; background: #FFFF66; border-radius: 5px; padding-left: 3px;" _
			]
	Local $sScript, $iErr, $sElements
	$_WD_HTTPRESULT = 0
	$_WD_HTTPRESPONSE = ''

	If $iMethod = Default Then $iMethod = 1
	If $iMethod < 0 Or $iMethod > 3 Then $iMethod = 1

	If IsString($vElements) Then
		$sScript = "arguments[0].style='" & $aMethod[$iMethod] & "'; return true;"
		_WD_ExecuteScript($sSession, $sScript, __WD_JsonElement($vElements), Default, $_WD_JSON_Value)
		$iErr = @error

	ElseIf IsArray($vElements) And UBound($vElements) > 0 Then
		For $i = 0 To UBound($vElements) - 1
			$vElements[$i] = __WD_JsonElement($vElements[$i])
		Next

		$sElements = "[" & _ArrayToString($vElements, ",") & "]"
		$sScript = "for (var i = 0, max = arguments[0].length; i < max; i++) { arguments[0][i].style = '" & $aMethod[$iMethod] & "'; }; return true;"
		_WD_ExecuteScript($sSession, $sScript, $sElements, Default, $_WD_JSON_Value)
		$iErr = @error
	Else
		$iErr = $_WD_ERROR_InvalidArgue
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, ($iErr = $_WD_ERROR_Success))
EndFunc   ;==>_WD_HighlightElements

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_LoadWait
; Description ...: Wait for a browser page load to complete before returning.
; Syntax ........: _WD_LoadWait($sSession[, $iDelay = Default[, $iTimeout = Default[, $sElement = Default[, $iState = Default]]]])
; Parameters ....: $sSession - Session ID from _WD_CreateSession
;                  $iDelay   - [optional] Milliseconds to wait before initially checking status
;                  $iTimeout - [optional] Period of time (in milliseconds) to wait before exiting function
;                  $sElement - [optional] Element ID (from _WD_FindElement or _WD_WaitElement) to confirm DOM invalidation
;                  $iState   - [optional] Minimal desired ReadyState that is expected. Default is $_WD_READYSTATE_Complete.
; Return values .: Success - 1.
;                  Failure - 0 and sets @error to one of the following values:
;                  - $_WD_ERROR_ContextInvalid
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_RetValue
;                  - $_WD_ERROR_Timeout
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......: Only the current document context is checked (frames must be checked individually)
; Related .......: _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_LoadWait($sSession, $iDelay = Default, $iTimeout = Default, $sElement = Default, $iState = Default)
	Local Const $sFuncName = "_WD_LoadWait"
	If $iState = Default Then $iState = $_WD_READYSTATE_Complete ; Fully loaded
	If Not (IsInt($iState) And $iState > 0 And $iState < $_WD_READYSTATE__COUNTER) Then $iState = $_WD_READYSTATE_Complete  ; Fully loaded
	Local Const $sDesiredState = _ArrayToString($aWD_READYSTATE, '', $iState, $_WD_READYSTATE__COUNTER - 1, '|', $_WD_READYSTATE_State, $_WD_READYSTATE_State)
	Local Const $sParameters = 'Parameters:    Delay=' & $iDelay & '    Timeout=' & $iTimeout & '    Element=' & $sElement & '    DesiredState=' & $sDesiredState
	Local $iErr, $iExt = 0, $sReadyState, $iIndex = -1
	$_WD_HTTPRESULT = 0
	$_WD_HTTPRESPONSE = ''

	If $iDelay = Default Then $iDelay = 0
	If $iTimeout = Default Then $iTimeout = $_WD_DefaultTimeout
	If $sElement = Default Then $sElement = ""

	__WD_Sleep($iDelay)
	$iErr = @error

	Local $hLoadWaitTimer = TimerInit()

	Local $_WD_DEBUG_Saved = $_WD_DEBUG ; save current DEBUG level to prevent multiple errors
	If $_WD_DEBUG <> $_WD_DEBUG_Full Then $_WD_DEBUG = $_WD_DEBUG_None ; Prevent logging from _WD_ElementAction if not in Full debug mode

	While True
		If $iErr Then ExitLoop

		If $sElement <> '' Then
			_WD_ElementAction($sSession, $sElement, 'name')

			Switch @error
				Case $_WD_ERROR_NoMatch
					$sElement = ''

				Case $_WD_ERROR_ContextInvalid
					$iErr = @error
					ExitLoop

				Case $_WD_ERROR_Success

				Case Else
					$iErr = $_WD_ERROR_Exception
					ExitLoop
			EndSwitch

			If $_WD_HTTPRESULT = $HTTP_STATUS_NOT_FOUND Then $sElement = ''
		Else
			$sReadyState = _WD_ExecuteScript($sSession, 'return document.readyState', '', Default, $_WD_JSON_Value)
			$iErr = @error

			If $iErr Then
				If $iErr <> $_WD_ERROR_ContextInvalid Then
					$iErr = $_WD_ERROR_Exception
				EndIf

				$sReadyState = ''
				ExitLoop
			EndIf

			If StringInStr($sDesiredState, $sReadyState) Then
				ExitLoop
			EndIf
		EndIf

		If (TimerDiff($hLoadWaitTimer) > $iTimeout) Then
			$iErr = $_WD_ERROR_Timeout
			ExitLoop
		EndIf

		__WD_Sleep(10)
		$iErr = @error
	WEnd
	$_WD_DEBUG = $_WD_DEBUG_Saved ; restore DEBUG level

	If $sReadyState Then
		$iIndex = _ArraySearch($aWD_READYSTATE, $sReadyState, Default, Default, Default, Default, Default, $_WD_READYSTATE_State)
		If @error Then
			$iErr = $_WD_ERROR_RetValue
		Else
			$iExt = $iIndex
			$sReadyState &= ' (' & $aWD_READYSTATE[$iIndex][$_WD_READYSTATE_Desc] & ')'
		EndIf
	EndIf

	Local $iReturn = ($iErr) ? (0) : (1)
	Local $sMessage = $sParameters & '    : ReadyState= ' & $sReadyState
	Return SetError(__WD_Error($sFuncName, $iErr, $sMessage, $iExt), $iExt, $iReturn)
EndFunc   ;==>_WD_LoadWait

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_FrameListFindElement
; Description ...: Search the current document (including frames) and return locations of matching elements
; Syntax ........: _WD_FrameListFindElement($sSession, $sStrategy, $sSelector)
; Parameters ....: $sSession     - Session ID from _WD_CreateSession
;                  $sStrategy    - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector    - $sSelector - Indicates how the WebDriver should traverse through the HTML DOM to locate the desired element(s).
; Return values .: Success - array of matching frames (format like in _WD_FrameList)
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_GeneralError
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NoMatch
; Author ........: mLipok
; Modified ......:
; Remarks .......: Returned location (path like 'null/2/0') can be used with _WD_FrameEnter before _WD_FindElement or _WD_WaitElement will be used.
;                  In case when $_WD_ERROR_Exception is set returned location is valid, but was not able back to calling frame,
;                  	or some frames have become inaccessible during processing
; Related .......: _Wd_FrameList, _WD_FindElement
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_FrameListFindElement($sSession, $sStrategy, $sSelector)
	Local Const $sFuncName = "_WD_FrameListFindElement"
	Local Const $sParameters = 'Parameters:   Strategy=' & $sStrategy & '   Selector=' & $sSelector
	Local $iErr = $_WD_ERROR_Success
	Local $sStartLocation = '', $sMessage = ''

	Local $aFrameList = _WD_FrameList($sSession, True)
	$iErr = @error
	If $iErr Then
		If Not $_WD_DetailedErrors Then $iErr = $_WD_ERROR_GeneralError
		$sMessage = ' > Issue with getting list of frames'
	Else
		Local $iFrameCount = UBound($aFrameList, $UBOUND_ROWS)
		For $i = 0 To $iFrameCount - 1
			If $aFrameList[$i][$_WD_FRAMELIST_Relative] = '' Then $sStartLocation = $aFrameList[$i][$_WD_FRAMELIST_Absolute]
		Next

		#Region ; this region is prevented from redundant logging ( _WD_FrameEnter and _WD_FindElement ) if not in Full debug mode > https://github.com/Danp2/au3WebDriver/pull/290#issuecomment-1100707095
		Local $_WD_DEBUG_Saved = $_WD_DEBUG ; save current DEBUG level
		If $_WD_DEBUG <> $_WD_DEBUG_Full Then $_WD_DEBUG = $_WD_DEBUG_None

		For $i = $iFrameCount - 1 To 0 Step -1
			_WD_FrameEnter($sSession, $aFrameList[$i][$_WD_FRAMELIST_Absolute])
			$iErr = @error
			If $iErr Then
				If Not $_WD_DetailedErrors Then $iErr = $_WD_ERROR_Exception
				$sMessage = ' > Issue with entering frame=' & $aFrameList[$i][$_WD_FRAMELIST_Absolute] & '  URL=' & $aFrameList[$i][$_WD_FRAMELIST_URL]
				ExitLoop
			Else
				$aFrameList[$i][$_WD_FRAMELIST_MatchedElements] = _WD_FindElement($sSession, $sStrategy, $sSelector, Default, True, Default)
				$iErr = @error
				If $iErr = $_WD_ERROR_Success Then
					ContinueLoop ; keep the frame in the list and continue searching in next frame
				ElseIf $iErr = $_WD_ERROR_NoMatch Then ; element was not found on location: ' & $aFrameList[$i][$_WD_FRAMELIST_Absolute]
					_ArrayDelete($aFrameList, $i) ; delete frame from the list because the searched element do not exist within the frame
					ContinueLoop
				Else
					If Not $_WD_DetailedErrors Then $iErr = $_WD_ERROR_Exception
					$sMessage = ' > Issue with finding element in frame=' & $aFrameList[$i][$_WD_FRAMELIST_Absolute] & '  URL=' & $aFrameList[$i][$_WD_FRAMELIST_URL]
					$aFrameList[$i][$_WD_FRAMELIST_MatchedElements] = ''
					ExitLoop
				EndIf
			EndIf
		Next

		If $i = -1 Then ; all frames was checked
			If UBound($aFrameList) Then
				$iErr = $_WD_ERROR_Success
			Else
				$iErr = $_WD_ERROR_NoMatch
			EndIf
		EndIf

		If $sStartLocation Then ; Back to "calling frame"
			_WD_FrameEnter($sSession, $sStartLocation)
			$iErr = @error
			If $iErr Then
				$sMessage &= ' > Was not able to back to "calling frame" : StartLocation=' & $sStartLocation
				If Not $_WD_DetailedErrors Then $iErr = $_WD_ERROR_Exception
			EndIf
		EndIf

		$_WD_DEBUG = $_WD_DEBUG_Saved ; restore DEBUG level
		$sMessage = $sParameters & $sMessage
		#EndRegion ; this region is prevented from redundant logging ( _WD_FrameEnter and _WD_FindElement ) if not in Full debug mode > https://github.com/Danp2/au3WebDriver/pull/290#issuecomment-1100707095
	EndIf

	Local $iExt = UBound($aFrameList, $UBOUND_ROWS)
	If $iErr Or $iExt = 0 Then $aFrameList = ''
	Return SetError(__WD_Error($sFuncName, $iErr, $sMessage, $iExt), $iExt, $aFrameList)
EndFunc   ;==>_WD_FrameListFindElement

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_Screenshot
; Description ...: Takes a screenshot of the Window or Element.
; Syntax ........: _WD_Screenshot($sSession[, $sElement = Default[, $iOutputType = Default]])
; Parameters ....: $sSession    - Session ID from _WD_CreateSession
;                  $sElement    - [optional] Element ID from _WD_FindElement
;                  $iOutputType - [optional] One of the following output types:
;                  |1 - String (Default)
;                  |2 - Binary
;                  |3 - Base64
; Return values .: Success - Output of specified type (PNG format).
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_NoMatch
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_GeneralError
;                  - $_WD_ERROR_InvalidDataType
;                  - $_WD_ERROR_InvalidExpression
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......: _WD_Window, _WD_ElementAction, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_Screenshot($sSession, $sElement = Default, $iOutputType = Default)
	Local Const $sFuncName = "_WD_Screenshot"
	Local Const $sParameters = 'Parameters:    Element=' & $sElement & '    OutputType=' & $iOutputType
	Local $sResponse, $vResult = "", $iErr, $dBinary

	If $sElement = Default Then $sElement = ""
	If $iOutputType = Default Then $iOutputType = 1

	If $sElement = '' Then
		$sResponse = _WD_Window($sSession, 'Screenshot')
	Else
		$sResponse = _WD_ElementAction($sSession, $sElement, 'Screenshot')
	EndIf
	$iErr = @error

	If $iErr = $_WD_ERROR_Success Then
		If $iOutputType < 3 Then
			$dBinary = __WD_Base64Decode($sResponse)
			If @error Then $iErr = $_WD_ERROR_GeneralError
		EndIf
		If $iErr = $_WD_ERROR_Success Then ; Recheck after __WD_Base64Decode() usage
			Switch $iOutputType
				Case 1 ; String
					$vResult = BinaryToString($dBinary)
				Case 2 ; Binary
					$vResult = $dBinary
				Case 3 ; Base64
					$vResult = $sResponse
			EndSwitch
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $vResult)
EndFunc   ;==>_WD_Screenshot

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_PrintToPDF
; Description ...: Print the current tab in paginated PDF format.
; Syntax ........: _WD_PrintToPDF($sSession[, $sOptions = Default]])
; Parameters ....: $sSession - Session ID from _WD_CreateSession
;                  $sOptions - [optional] JSON string of formatting directives
; Return values .: Success - String containing PDF contents.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_InvalidDataType
; Author ........: Danp2
; Modified ......:
; Remarks .......: Chromedriver currently requires headless mode (https://bugs.chromium.org/p/chromedriver/issues/detail?id=3517).
; Related .......: _WD_Window, _WD_LastHTTPResult
; Link ..........: https://www.w3.org/TR/webdriver/#print-page
; Example .......: No
; ===============================================================================================================================
Func _WD_PrintToPDF($sSession, $sOptions = Default)
	Local Const $sFuncName = "_WD_PrintToPDF"
	Local Const $sParameters = 'Parameters:    Options=' & $sOptions
	Local $sResponse, $sResult, $iErr

	If $sOptions = Default Then $sOptions = $_WD_EmptyDict

	$sResponse = _WD_Window($sSession, 'print', $sOptions)
	$iErr = @error

	If $iErr = $_WD_ERROR_Success Then
		$sResult = __WD_Base64Decode($sResponse)
	Else
		$sResult = ''
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sResult)
EndFunc   ;==>_WD_PrintToPDF

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_jQuerify
; Description ...: Inject jQuery library into current session.
; Syntax ........: _WD_jQuerify($sSession[, $sjQueryFile = Default[, $iTimeout = Default]])
; Parameters ....: $sSession    - Session ID from _WD_CreateSession
;                  $sjQueryFile - [optional] Path or URL to jQuery source file
;                  $iTimeout    - [optional] Period of time (in milliseconds) to wait before exiting function
; Return values .: Success - None.
;                  Failure - None and sets @error to one of the following values:
;                  - $_WD_ERROR_Timeout
;                  - $_WD_ERROR_GeneralError
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......: _WD_ExecuteScript, _WD_LastHTTPResult
; Link ..........: https://sqa.stackexchange.com/questions/2921/webdriver-can-i-inject-a-jquery-script-for-a-page-that-isnt-using-jquery
; Example .......: No
; ===============================================================================================================================
Func _WD_jQuerify($sSession, $sjQueryFile = Default, $iTimeout = Default)
	Local Const $sFuncName = "_WD_jQuerify"
	Local Const $sParameters = 'Parameters:    File=' & $sjQueryFile & '    Timeout=' & $iTimeout

	If $sjQueryFile = Default Then
		$sjQueryFile = ""
	Else
		$sjQueryFile = '"' & StringReplace($sjQueryFile, "\", "/") & '"' ; wrap in double quotes and replace backslashes
	EndIf

	If $iTimeout = Default Then $iTimeout = $_WD_DefaultTimeout

	Local $jQueryLoader = _
			"(function(jqueryUrl, callback) {" & _
			"    if (typeof jqueryUrl != 'string') {" & _
			"        jqueryUrl = 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js';" & _
			"    }" & _
			"    if (typeof jQuery == 'undefined') {" & _
			"        var script = document.createElement('script');" & _
			"        var head = document.getElementsByTagName('head')[0];" & _
			"        var done = false;" & _
			"        script.onload = script.onreadystatechange = (function() {" & _
			"            if (!done && (!this.readyState || this.readyState == 'loaded' " & _
			"                    || this.readyState == 'complete')) {" & _
			"                done = true;" & _
			"                script.onload = script.onreadystatechange = null;" & _
			"                head.removeChild(script);" & _
			"                callback();" & _
			"            }" & _
			"        });" & _
			"        script.src = jqueryUrl;" & _
			"        head.appendChild(script);" & _
			"    }" & _
			"    else {" & _
			"        jQuery.noConflict();" & _
			"        callback();" & _
			"    }" & _
			"})(arguments[0], arguments[arguments.length - 1]);"

	_WD_ExecuteScript($sSession, $jQueryLoader, $sjQueryFile, True)

	If @error = $_WD_ERROR_Success Then
		Local $hWaitTimer = TimerInit()

		Do
			__WD_Sleep(10)
			If @error Then ExitLoop

			If TimerDiff($hWaitTimer) > $iTimeout Then
				SetError($_WD_ERROR_Timeout)
				ExitLoop
			EndIf

			_WD_ExecuteScript($sSession, "jQuery")
		Until @error = $_WD_ERROR_Success
	EndIf

	Local $iErr = @error

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters))
EndFunc   ;==>_WD_jQuerify

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_ElementOptionSelect
; Description ...: Find and click on an option from a Select element.
; Syntax ........: _WD_ElementOptionSelect($sSession, $sStrategy, $sSelector[, $sStartNodeID = Default])
; Parameters ....: $sSession      - Session ID from _WD_CreateSession
;                  $sStrategy     - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector     - Indicates how the WebDriver should traverse through the HTML DOM to locate the desired element(s).  Should point to <option> in element of type '<select>'
;                  $sStartNodeID  - [optional] Element ID to use as starting HTML node. Default is ""
; Return values .: Success - None.
;                  Failure - None and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NoMatch
;                  - $_WD_ERROR_InvalidDataType
;                  - $_WD_ERROR_InvalidExpression
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_FindElement, _WD_ElementAction, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_ElementOptionSelect($sSession, $sStrategy, $sSelector, $sStartNodeID = Default)
	Local Const $sFuncName = "_WD_ElementOptionSelect"
	Local Const $sParameters = 'Parameters:    Strategy=' & $sStrategy & '    Selector=' & $sSelector & '    StartElement=' & $sStartNodeID
	If $sStartNodeID = Default Then $sStartNodeID = ""

	Local $sElement = _WD_FindElement($sSession, $sStrategy, $sSelector, $sStartNodeID)

	If @error = $_WD_ERROR_Success Then
		_WD_ElementAction($sSession, $sElement, 'click')
	EndIf

	Return SetError(__WD_Error($sFuncName, @error, $sParameters))
EndFunc   ;==>_WD_ElementOptionSelect

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_ElementSelectAction
; Description ...: Perform action on designated <select> element.
; Syntax ........: _WD_ElementSelectAction($sSession, $sSelectElement, $sCommand[, $vLabels = Default])
; Parameters ....: $sSession       - Session ID from _WD_CreateSession
;                  $sSelectElement - Element ID of <select> element from _WD_FindElement
;                  $sCommand       - Action to be performed. Can be one of the following:
;                  |DESELECTALL    - Clear all selections
;                  |MULTISELECT    - Select <option> elements given in 1D array of labels
;                  |OPTIONS        - Retrieves all <option> elements as 2D array
;                  |SELECTALL      - Select all <option> elements
;                  |SELECTEDINDEX  - Retrieves 0-based index of the first selected <option> element
;                  |SELECTEDLABELS - Retrieves labels of selected <option> elements as 1D array
;                  |SELECTEDOPTIONS- Retrieves selected <option> elements as 2D array
;                  |SINGLESELECT   - Select <option> element given as string and deselect all others
;                  |VALUE          - Retrieves value of the first selected <option> element
;                  $vLabels        - [optional] List of labels (depending on chosen $sCommand)
; Return values .: Success - Requested data returned by web driver.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_ElementIssue
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_GeneralError
;                  - $_WD_ERROR_InvalidArgue
;                  - $_WD_ERROR_InvalidDataType
;                  - $_WD_ERROR_InvalidExpression
;                  - $_WD_ERROR_NoMatch
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......: If no option is selected, SELECTEDINDEX will return -1.
; Related .......: _WD_FindElement, _WD_ExecuteScript
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_ElementSelectAction($sSession, $sSelectElement, $sCommand, $vLabels = Default)
	Local Const $sFuncName = "_WD_ElementSelectAction"
	Local $sLabelsTemp = ($_WD_DEBUG = $_WD_DEBUG_Full) ? ($vLabels) : ("(string)")
	Local Const $sParameters = 'Parameters:    Command=' & $sCommand & '    Labels=' & ((IsArray($vLabels)) ? ("(array)") : ($sLabelsTemp))
	Local $vResult, $sScript
	Local Static $sScript_MultiSelectTemplate = StringReplace( _ ; it is declared as static to optimize AutoIt processing speed - this line will be processed once per script run
			"function MultiSelectOption(SelectElement, LabelsToSelect, AllowMultiple) {" & _
			"	if (AllowMultiple && SelectElement.multiple == false) {" & _
			"		return '';" & _
			"	}" & _
			"	const LabelsUpperCased = LabelsToSelect.map( function(value) { return value.toUpperCase(); } );" & _ ; https://stackoverflow.com/a/24718430/5314940
			"	const options = SelectElement.options;" & _
			"	let result = false;" & _
			"	for (let i = 0, o, IsDisabled, IsHidden, Matching; i < options.length; i++) {" & _
			"		o = options[i];" & _
			"		Matching = ( LabelsUpperCased.indexOf( o.label.toUpperCase() ) != -1 );" & _
			"		if (Matching) {" & _
			"			IsDisabled =	( o.disabled	|| (o.parentNode.nodeName == 'OPTGROUP' && o.parentNode.disabled) );" & _
			"			IsHidden =		( o.hidden		|| (o.parentNode.nodeName == 'OPTGROUP' && o.parentNode.hidden) );" & _
			"			if (AllowMultiple) {" & _
			"				if (!(IsDisabled || IsHidden)) {" & _
			"					o.selected = true;" & _
			"					result = true;" & _
			"				}" & _
			"			} else {" & _
			"				if (IsDisabled || IsHidden) {" & _
			"					result = '';" & _
			"				} else {" & _
			"					SelectElement.selectedIndex = -1;" & _
			"					o.selected = true;" & _
			"					result = true;" & _
			"				}" & _
			"				break;" & _
			"			}" & _
			"		}" & _
			"	}" & _
			"	if (result == true) {" & _
			"		SelectElement.dispatchEvent(new Event('change', {bubbles: true}));" & _
			"	}" & _
			"	return result;" & _
			"};" & _
			"var SelectElement = arguments[0];" & _
			"var LabelsToSplit = arguments[1];" & _ ; Label1||Label2
			"var LabelsToSelect = LabelsToSplit.split('||');" & _ ; ['Label1', 'Label2']
			"var AllowMultiple = arguments[2];" & _ ; true or false
			"return MultiSelectOption(SelectElement, LabelsToSelect, AllowMultiple);" & _
			"", @TAB, '')

	; Save current debug level and set to none to reduce excessive logging
	Local $WDDebugSave = $_WD_DEBUG
	If $_WD_DEBUG <> $_WD_DEBUG_Full Then $_WD_DEBUG = $_WD_DEBUG_None

	Local $sNodeName = _WD_ElementAction($sSession, $sSelectElement, 'property', 'nodeName')
	Local $iErr = @error, $iExt = 0

	If $iErr <> $_WD_ERROR_Success Then
		$iErr = $_WD_ERROR_GeneralError
	Else
		If $sNodeName = 'select' Then ; check if designated element is <select> element
			Switch $sCommand
				Case 'deselectAll'
					$sScript = _
							"var SelectElement = arguments[0];" & _
							"SelectElement.selectedIndex = -1;" & _
							"SelectElement.dispatchEvent(new Event('change', {bubbles: true}));" & _
							"return true;"
					$vResult = _WD_ExecuteScript($sSession, $sScript, __WD_JsonElement($sSelectElement), Default, $_WD_JSON_Value)
					$iErr = @error

				Case 'multiSelect' ; https://stackoverflow.com/a/1296068/5314940
					; Should be a single dimensional, non-empty array
					If UBound($vLabels, $UBOUND_DIMENSIONS) <> 1 Or UBound($vLabels, $UBOUND_ROWS) = 0 Then
						$iErr = $_WD_ERROR_InvalidArgue
						$iExt = 41 ; $iExt from 41 to 49 are related to _WD_ElementSelectAction()
					Else
						$vLabels = StringReplace(_ArrayToString($vLabels, "||"), '"', '\"') ; labels can contains double quotation marks
						$vLabels = __WD_JsonElement($sSelectElement) & ',"' & $vLabels & '", true'
						$vResult = _WD_ExecuteScript($sSession, $sScript_MultiSelectTemplate, $vLabels, Default, $_WD_JSON_Value)
						$iErr = @error
						If Not @error Then
							If $vResult == '' Then
								$iErr = $_WD_ERROR_ElementIssue
							ElseIf $vResult = False Then
								$iErr = $_WD_ERROR_NoMatch
							EndIf
						EndIf
					EndIf

				Case 'singleSelect'
					; Should be a non empty string
					If Not (IsString($vLabels) And StringLen($vLabels)) Then
						$iErr = $_WD_ERROR_InvalidArgue
						$iExt = 42 ; $iExt from 41 to 49 are related to _WD_ElementSelectAction()
					Else
						$vLabels = StringReplace($vLabels, '"', '\"') ; labels can contains double quotation marks
						$vLabels = __WD_JsonElement($sSelectElement) & ',"' & $vLabels & '", false'
						$vResult = _WD_ExecuteScript($sSession, $sScript_MultiSelectTemplate, $vLabels, Default, $_WD_JSON_Value)
						$iErr = @error
						If Not @error Then
							If $vResult == '' Then
								$iErr = $_WD_ERROR_ElementIssue
							ElseIf $vResult = False Then
								$iErr = $_WD_ERROR_NoMatch
							EndIf
						EndIf
					EndIf

				Case 'options' ; 7 columns (value, label, index, selected status, disabled status, hidden status and group name)
					Local Static $sScript_OptionsTemplate = StringReplace( _
							"function GetOptions(SelectElement) {" & _
							"	let result ='';" & _
							"	const options = SelectElement.options;" & _
							"	for (let i = 0, o, IsDisabled, IsHidden, GroupName; i < options.length; i++) {" & _
							"		o = options[i];" & _
							"		IsDisabled =	( o.disabled	|| (o.parentNode.nodeName == 'OPTGROUP' && o.parentNode.disabled) );" & _
							"		IsHidden =		( o.hidden		|| (o.parentNode.nodeName == 'OPTGROUP' && o.parentNode.hidden) );" & _
							"		GroupName = (o.parentNode.nodeName == 'OPTGROUP' ? o.parentNode.label : '');" & _
							"		result += o.value + '|' + o.label + '|' + o.index + '|' + o.selected + '|' + IsDisabled + '|' + IsHidden + '|' + GroupName + '\n';" & _
							"	}" & _
							"	return result;" & _
							"}" & _
							"var SelectElement = arguments[0];" & _
							"return GetOptions(SelectElement);" & _
							"", @TAB, '')

					$vResult = _WD_ExecuteScript($sSession, $sScript_OptionsTemplate, __WD_JsonElement($sSelectElement), Default, $_WD_JSON_Value)
					$iErr = @error

					If $iErr = $_WD_ERROR_Success Then
						Local $aAllOptions[0][7]
						_ArrayAdd($aAllOptions, StringStripWS($vResult, $STR_STRIPTRAILING), 0, Default, @LF, $ARRAYFILL_FORCE_SINGLEITEM)
						$vResult = $aAllOptions
					EndIf

				Case 'selectAll'
					Local Static $sScript_SelectAllTemplate = StringReplace( _
							"function SelectAll(SelectElement) {" & _
							"	if (SelectElement.multiple == false) {" & _
							"		return '';" & _
							"	};" & _
							"	const options = SelectElement.options;" & _
							"	let waschanged = false;" & _
							"	for (let i = 0, o, IsDisabled, IsHidden; i < options.length; i++) {" & _
							"		o = options[i];" & _
							"		IsDisabled =	( o.disabled	|| (o.parentNode.nodeName == 'OPTGROUP' && o.parentNode.disabled) );" & _
							"		IsHidden =		( o.hidden		|| (o.parentNode.nodeName == 'OPTGROUP' && o.parentNode.hidden) );" & _
							"		if ( !(IsDisabled || IsHidden || o.selected) ) {" & _
							"			o.selected = true;" & _
							"			waschanged = true;" & _
							"		};" & _
							"	};" & _
							"	if (waschanged==true) {" & _
							"		SelectElement.dispatchEvent(new Event('change', {bubbles: true}));" & _
							"	};" & _
							"	return waschanged;" & _
							"};" & _
							"var SelectElement = arguments[0];" & _
							"return SelectAll(SelectElement);" & _
							"", @TAB, '')
					$vResult = _WD_ExecuteScript($sSession, $sScript_SelectAllTemplate, __WD_JsonElement($sSelectElement), Default, $_WD_JSON_Value)
					$iErr = @error
					If Not @error And $vResult == '' Then
						$iErr = $_WD_ERROR_ElementIssue
					ElseIf $vResult = False Then
						$iErr = $_WD_ERROR_NoMatch
					EndIf

				Case 'selectedIndex'
					$sScript = "return arguments[0].selectedIndex"
					$vResult = _WD_ExecuteScript($sSession, $sScript, __WD_JsonElement($sSelectElement), Default, $_WD_JSON_Value)
					$iErr = @error

				Case 'selectedLabels'
					Local Static $sScript_SelectedLabelsTemplate = StringReplace( _
							"function GetSelecteLabels(SelectElement) {" & _
							"	let result ='';" & _
							"	const options = SelectElement.selectedOptions;" & _
							"	for (let i = 0, o; i < options.length; i++)	{" & _
							"		o = options[i];" & _
							"		result += o.label + '\n';" & _
							"	};" & _
							"	return result;" & _
							"};" & _
							"var SelectElement = arguments[0];" & _
							"return GetSelecteLabels(SelectElement);" & _
							"", @TAB, '')
					$vResult = _WD_ExecuteScript($sSession, $sScript_SelectedLabelsTemplate, __WD_JsonElement($sSelectElement), Default, $_WD_JSON_Value)
					$iErr = @error

					If $iErr = $_WD_ERROR_Success Then
						Local $aSelectedLabels[0]
						_ArrayAdd($aSelectedLabels, StringStripWS($vResult, $STR_STRIPTRAILING), 0, @LF, "", $ARRAYFILL_FORCE_DEFAULT)
						$vResult = $aSelectedLabels
					EndIf

				Case 'selectedOptions' ; 4 columns (value, label, index and group name)
					Local Static $sScript_SelectedOptionsTemplate = StringReplace( _
							"function GetSelectedOptions(SelectElement) {" & _
							"	let result ='';" & _
							"	const options = SelectElement.selectedOptions;" & _
							"	for (let i = 0, o, GroupName; i < options.length; i++) {" & _
							"		o = options[i];" & _
							"		GroupName = (o.parentNode.nodeName == 'OPTGROUP' ? o.parentNode.label : '');" & _
							"		result += o.value + '|' + o.label + '|' + o.index + '|' + GroupName + '\n';" & _
							"	};" & _
							"	return result;" & _
							"}" & _
							"var SelectElement = arguments[0];" & _
							"return GetSelectedOptions(SelectElement);" & _
							"", @TAB, '')
					$vResult = _WD_ExecuteScript($sSession, $sScript_SelectedOptionsTemplate, __WD_JsonElement($sSelectElement), Default, $_WD_JSON_Value)
					$iErr = @error

					If $iErr = $_WD_ERROR_Success Then
						Local $aSelectedOptions[0][4]
						_ArrayAdd($aSelectedOptions, StringStripWS($vResult, $STR_STRIPTRAILING), 0, Default, @LF, $ARRAYFILL_FORCE_SINGLEITEM)
						$vResult = $aSelectedOptions
					EndIf

				Case 'value'
					$sScript = "return arguments[0].value"
					$vResult = _WD_ExecuteScript($sSession, $sScript, __WD_JsonElement($sSelectElement), Default, $_WD_JSON_Value)
					$iErr = @error

				Case Else
					Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(deselectAll|multiSelect|options|selectAll|selectedIndex|selectedLabels|selectedOptions|singleSelect|value) $sCommand=>" & $sCommand), 0, "")

			EndSwitch
		Else
			$iErr = $_WD_ERROR_InvalidArgue
			$iExt = 49 ; $iExt from 41 to 49 are related to _WD_ElementSelectAction()
		EndIf
	EndIf

	; Restore prior setting
	$_WD_DEBUG = $WDDebugSave

	Local $sMessage = $sParameters & '    : Result = ' & ((IsArray($vResult)) ? ("(array)") : ($vResult))
	Return SetError(__WD_Error($sFuncName, $iErr, $sMessage, $iExt), $iExt, $vResult)
EndFunc   ;==>_WD_ElementSelectAction

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_ElementStyle
; Description ...: Set/Get element style CSSProperty
; Syntax ........: _WD_ElementStyle($sSession, $sElement, $sCSSProperty, $sValue)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
;                  $sElement - Element ID from _WD_FindElement
;                  $sCSSProperty - Style property name to be set or retrieved
;                  $sValue - New value to be set; current value will be retrieved when not supplied (Default)
; Return values .: Success - Requested style value(s) returned by the webdriver
;                  Failure - Response from webdriver and sets @error returned from _WD_ExecuteScript() or $_WD_ERROR_NotSupported or $_WD_ERROR_NoMatch
; Author ........: mLipok
; Modified ......: Danp2
; Remarks .......: An array of current styles and their values will be returned when $sCSSProperty is not defined (Default)
; Related .......:
; Link ..........:
; Example .......: _WD_ElementStyle($sSession, $sElement, 'fontFamily', '"Lucida Console", "Courier New", monospace')
; ===============================================================================================================================
Func _WD_ElementStyle($sSession, $sElement, $sCSSProperty = Default, $sValue = Default)
	Local Const $sFuncName = "_WD_ElementStyle"
	Local $vResult, $iErr = $_WD_ERROR_Success
	Local $sJavaScript = ''

	If IsString($sCSSProperty) And $sValue <> Default Then ; set property value
		$sJavaScript = _
				"var element = arguments[0];" & _
				"element.style." & $sCSSProperty & " = '" & $sValue & "';"
		$vResult = _WD_ExecuteScript($sSession, $sJavaScript, __WD_JsonElement($sElement), Default, Default)
		$iErr = @error
	ElseIf IsString($sCSSProperty) And $sValue = Default Then ; get specific property value
		$sJavaScript = _
				"var myelement = arguments[0];" & _
				"return GetPropertyValue(myelement);" & _
				"" & _
				"function GetPropertyValue(element) {" & _
				"   var search = '" & $sCSSProperty & "';" & _
				"   var propertyname = '';" & _
				"   for (let i = 0; i < element.style.length; i++) {" & _
				"      propertyname = element.style.item(i);" & _
				"      if (propertyname == search) {return element.style.getPropertyValue(propertyname);}" & _
				"   }" & _
				"   return '';" & _
				"}"
		$vResult = _WD_ExecuteScript($sSession, $sJavaScript, __WD_JsonElement($sElement), Default, $_WD_JSON_Value)
		$iErr = @error
	ElseIf $sCSSProperty = Default And $sValue = Default Then ; get list of properties and their values
		$sJavaScript = _
				"var myelement = arguments[0];" & _
				"return GetProperties(myelement);" & _
				"" & _
				"function GetProperties(element) {" & _
				"   var result = '';" & _
				"   var propertyname = '';" & _
				"   for (let i = 0; i < element.style.length; i++) {" & _
				"      propertyname = element.style.item(i);" & _
				"      result += propertyname + ':' + element.style.getPropertyValue(propertyname) + ';'" & _
				"   }" & _
				"   return result.slice(0, result.length -1);" & _
				"}"
		$sJavaScript = StringReplace($sJavaScript, @TAB, '')
		$vResult = _WD_ExecuteScript($sSession, $sJavaScript, __WD_JsonElement($sElement), Default, $_WD_JSON_Value)
		$iErr = @error
		If $iErr = $_WD_ERROR_Success And $vResult == '' Then
			$iErr = $_WD_ERROR_NoMatch
		ElseIf $iErr = $_WD_ERROR_Success Then
			Local $aProperties[0][2]
			_ArrayAdd($aProperties, StringStripWS($vResult, $STR_STRIPTRAILING), 0, ':', ';', $ARRAYFILL_FORCE_SINGLEITEM)
			$vResult = $aProperties
		EndIf
	Else
		$iErr = $_WD_ERROR_NotSupported
	EndIf
	Return SetError(__WD_Error($sFuncName, $iErr), 0, $vResult)
EndFunc   ;==>_WD_ElementStyle

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_ConsoleVisible
; Description ...: Control visibility of the webdriver console app.
; Syntax ........: _WD_ConsoleVisible([$bVisible = Default])
; Parameters ....: $bVisible - [optional] Set to true to show the console. Default is False.
; Return values .: Success - None
;                  Failure - None
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: _WD_ConsoleVisible(False)
; ===============================================================================================================================
Func _WD_ConsoleVisible($bVisible = Default)
	Local $sFile = __WD_StripPath($_WD_DRIVER)
	Local $pid, $pid2, $hWnd = 0, $aWinList

	If $bVisible = Default Then $bVisible = False

	$pid = ProcessExists($sFile)

	If $pid Then
		$aWinList = WinList("[CLASS:ConsoleWindowClass]")

		For $i = 1 To $aWinList[0][0]
			$pid2 = WinGetProcess($aWinList[$i][1])

			If $pid2 = $pid Then
				$hWnd = $aWinList[$i][1]
				ExitLoop
			EndIf
		Next

		If $hWnd <> 0 Then
			WinSetState($hWnd, "", $bVisible ? @SW_SHOW : @SW_HIDE)
		EndIf
	EndIf

EndFunc   ;==>_WD_ConsoleVisible

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetShadowRoot
; Description ...: Retrieves the shadow root of an element.
; Syntax ........: _WD_GetShadowRoot($sSession, $sStrategy, $sSelector[, $sStartNodeID = Default])
; Parameters ....: $sSession      - Session ID from _WD_CreateSession
;                  $sStrategy     - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector     - Indicates how the WebDriver should traverse through the HTML DOM to locate the desired element(s).
;                  $sStartNodeID  - [optional] Element ID to use as starting HTML node. Default is ""
; Return values .: Success - Element ID returned by web driver.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NoMatch
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_FindElement, _WD_ElementAction, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_GetShadowRoot($sSession, $sStrategy, $sSelector, $sStartNodeID = Default)
	Local Const $sFuncName = "_WD_GetShadowRoot"
	Local Const $sParameters = 'Parameters:    Strategy=' & $sStrategy & '    Selector=' & $sSelector & '    StartElement=' & $sStartNodeID
	Local $sResponse, $sResult = "", $oJSON

	If $sStartNodeID = Default Then $sStartNodeID = ""

	Local $sElement = _WD_FindElement($sSession, $sStrategy, $sSelector, $sStartNodeID)
	Local $iErr = @error

	If $iErr = $_WD_ERROR_Success Then
		$sResponse = _WD_ElementAction($sSession, $sElement, 'shadow')
		$iErr = @error

		If $iErr = $_WD_ERROR_Success Then
			$oJSON = Json_Decode($sResponse)
			$sResult = Json_Get($oJSON, $_WD_JSON_Shadow)
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sResult)
EndFunc   ;==>_WD_GetShadowRoot

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_SelectFiles
; Description ...: Select files for uploading to a website.
; Syntax ........: _WD_SelectFiles($sSession, $sStrategy, $sSelector, $sFilename)
; Parameters ....: $sSession  - Session ID from _WD_CreateSession
;                  $sStrategy - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector - Indicates how the WebDriver should traverse through the HTML DOM to locate the desired element(s). Should point to element of type '< input type="file" >'.
;                  $sFilename - Full path of file(s) to upload (use newline character [@LF] to separate files)
; Return values .: Success - Number of selected files.
;                  Failure - "0" and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NoMatch
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......: If $sFilename is empty, then prior selection is cleared.
; Related .......: _WD_FindElement, _WD_ElementAction, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_SelectFiles($sSession, $sStrategy, $sSelector, $sFilename)
	Local Const $sFuncName = "_WD_SelectFiles"
	Local Const $sParameters = 'Parameters:    Strategy=' & $sStrategy & '    Selector=' & $sSelector & '    Filename=' & $sFilename
	Local $sResult = "0"
	Local $sElement = _WD_FindElement($sSession, $sStrategy, $sSelector)
	Local $iErr = @error

	If $iErr = $_WD_ERROR_Success Then
		If $sFilename <> "" Then
			_WD_ElementAction($sSession, $sElement, 'value', $sFilename)
			$iErr = @error
		Else
			_WD_ElementAction($sSession, $sElement, 'clear')
			$iErr = @error
		EndIf

		If $iErr = $_WD_ERROR_Success Then
			$sResult = _WD_ExecuteScript($sSession, "return arguments[0].files.length", __WD_JsonElement($sElement), Default, $_WD_JSON_Value)
			$iErr = @error
			If $iErr Then $sResult = "0"
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sResult)
EndFunc   ;==>_WD_SelectFiles

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_IsLatestRelease
; Description ...: Compares local UDF version to latest release on Github.
; Syntax ........: _WD_IsLatestRelease()
; Parameters ....: None
; Return values .: Success - True if the local UDF version is the latest, otherwise False
;                  Failure - Null and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_GeneralError
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_IsLatestRelease()
	Local Const $sFuncName = "_WD_IsLatestRelease"
	Local Const $sGitURL = "https://github.com/Danp2/au3WebDriver/releases/latest"
	Local $bResult = Null
	Local $iErr = $_WD_ERROR_Success
	Local $sRegex = '<a.*href="\/Danp2\/au3WebDriver\/releases\/tag\/(.*?)"'

	Local $sResult = InetRead($sGitURL, $INET_FORCERELOAD)
	If @error Then $iErr = $_WD_ERROR_GeneralError

	If $iErr = $_WD_ERROR_Success Then
		Local $aLatestWDVersion = StringRegExp(BinaryToString($sResult), $sRegex, $STR_REGEXPARRAYMATCH)

		If Not @error Then
			Local $sLatestWDVersion = $aLatestWDVersion[0]
			Local $nStatus = _VersionCompare($__WDVERSION, $sLatestWDVersion)  ; 0 - Both versions equal ; 1 - Version1 greater ; -1 - Version2 greater
			$bResult = ($nStatus >= 0)
		Else
			$iErr = $_WD_ERROR_Exception
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, String($bResult)), 0, $bResult)
EndFunc   ;==>_WD_IsLatestRelease

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_UpdateDriver
; Description ...: Replace web driver with newer version, if available.
; Syntax ........: _WD_UpdateDriver($sBrowser[, $sInstallDir = Default[, $bFlag64 = Default[, $bForce = Default[, $bDowngrade = Default[, $sBrowserVersion = Default]]]]])
; Parameters ....: $sBrowser        - Browser name or full path to browser executable
;                  $sInstallDir     - [optional] Install directory. Default is @ScriptDir
;                  $bFlag64         - [optional] Install 64bit version? Default is current driver architecture or False
;                  $bForce          - [optional] Force update? Default is False
;                  $bDowngrade      - [optional] Downgrade to match browser version if needed? Default is False
;                  $sBrowserVersion - [optional] Force desired browser version
; Return values .: Success - True (Driver was updated).
;                  Failure - False (Driver was not updated) and sets @error to one of the following values:
;                  - $_WD_ERROR_FileIssue
;                  - $_WD_ERROR_GeneralError
;                  - $_WD_ERROR_InvalidValue
;                  - $_WD_ERROR_Mismatch
;                  - $_WD_ERROR_NotFound
;                  - $_WD_ERROR_NotSupported
;                  - $_WD_ERROR_UserAbort
; Author ........: Danp2, CyCho
; Modified ......: mLipok
; Remarks .......: When $bForce = Null, then the function will check for an updated webdriver without actually performing the update.
;                  This can be used in conjunction with $bDowngrade to determine if the existing webdriver is too new for the browser.
;                  In this scenario, the return value indicates if an update / downgrade is available.
;                  Using $sBrowserVersion you can provide desired browser version from computer not connected to internet saved earlier by _WD_GetBrowserVersion()
; Related .......: _WD_GetBrowserVersion, _WD_GetWebDriverVersion
; Link ..........:
; Example .......: Local $bResult = _WD_UpdateDriver('FireFox')
; ===============================================================================================================================
Func _WD_UpdateDriver($sBrowser, $sInstallDir = Default, $bFlag64 = Default, $bForce = Default, $bDowngrade = Default, $sBrowserVersion = Default)
	Local Const $sFuncName = "_WD_UpdateDriver"
	Local $iErr = $_WD_ERROR_Success, $iExt = 0, $sDriverEXE, $bResult = False
	Local $sDriverCurrent, $sDriverLatest, $sURLNewDriver
	Local $sTempFile
	Local $bKeepArch = False

	If $sInstallDir = Default Then $sInstallDir = @ScriptDir
	If $bForce = Default Then $bForce = False
	If $bFlag64 = Default Then
		$bFlag64 = False
		$bKeepArch = True
	EndIf
	If $bDowngrade = Default Then $bDowngrade = False

	$sInstallDir = StringRegExpReplace($sInstallDir, '(?i)(\\)\Z', '') & '\' ; prevent double \\ on the end of directory
	Local Const $bNoUpdate = (IsKeyword($bForce) = $KEYWORD_NULL) ; Flag to track if updates should be performed

	; If the Install directory doesn't exist and it can't be created, then set error
	If (Not FileExists($sInstallDir)) And (Not DirCreate($sInstallDir)) Then
		$iErr = $_WD_ERROR_InvalidValue
	Else
		; Save current debug level and set to none
		Local $WDDebugSave = $_WD_DEBUG
		If $_WD_DEBUG <> $_WD_DEBUG_Full Then $_WD_DEBUG = $_WD_DEBUG_None

		If $sBrowserVersion = Default Then
			$sBrowserVersion = _WD_GetBrowserVersion($sBrowser)
			$iErr = @error
			$iExt = @extended
		EndIf

		If $iErr = $_WD_ERROR_Success Then
			Local $iIndex = @extended
			; Match exe file name in list of supported browsers
			$sDriverEXE = $_WD_SupportedBrowsers[$iIndex][$_WD_BROWSER_DriverName]

			; Determine current local webdriver Architecture
			If FileExists($sInstallDir & $sDriverEXE) Then
				_WinAPI_GetBinaryType($sInstallDir & $sDriverEXE)
				Local $bDriverIs64Bit = (@extended = $SCS_64BIT_BINARY)
				If $bKeepArch Then $bFlag64 = $bDriverIs64Bit
				If $_WD_SupportedBrowsers[$iIndex][$_WD_BROWSER_64Bit] And $bDriverIs64Bit <> $bFlag64 Then
					$bForce = True
				EndIf
			EndIf

			$sDriverCurrent = _WD_GetWebDriverVersion($sInstallDir, $sDriverEXE)
			; Determine latest available webdriver version for the designated browser
			Local $aBrowser = _ArrayExtract($_WD_SupportedBrowsers, $iIndex, $iIndex)
			Local $aDriverInfo = __WD_GetLatestWebdriverInfo($aBrowser, $sBrowserVersion, $bFlag64)
			$iErr = @error
			$iExt = @extended
			$sDriverLatest = $aDriverInfo[1]
			$sURLNewDriver = $aDriverInfo[0]

			If $iErr = $_WD_ERROR_Success Then
				Local $nStatus = _VersionCompare($sDriverCurrent, $sDriverLatest)  ; 0 - Both versions equal ; 1 - Version1 greater ; -1 - Version2 greater
				Local $bUpdateAvail = ($nStatus < 0)
				Local $bDowngradable = ($nStatus > 0)

				If $bNoUpdate Then
					; Set return value to indicate if newer / downgradable driver is available
					$bResult = ($bDowngrade) ? $bDowngradable : $bUpdateAvail

				ElseIf $bUpdateAvail Or $bForce Or ($bDowngrade And $bDowngradable) Then
					; @TempDir should be used to avoid potential AV problems, for example by downloading stuff to @DesktopDir
					$sTempFile = _TempFile(@TempDir, "webdriver_", ".zip")
					_WD_DownloadFile($sURLNewDriver, $sTempFile)
					If @error Then
						$iErr = @error
						$iExt = @extended
					Else
						; Close any instances of webdriver
						__WD_CloseDriver($sDriverEXE)

						; Extract
						__WD_UpdateExtractor($sTempFile, $sInstallDir, $sDriverEXE)
						$iErr = @error
						$iExt = @extended
						If Not @error Then $bResult = True
					EndIf
					FileDelete($sTempFile)

				ElseIf $bDowngradable Then
					$iErr = $_WD_ERROR_Mismatch
				EndIf
			EndIf
		EndIf

		; Restore prior setting
		$_WD_DEBUG = $WDDebugSave
	EndIf

	Local $sMessage = 'DriverCurrent = ' & $sDriverCurrent & ' : DriverLatest = ' & $sDriverLatest & ' : $sInstallDir = ' & $sInstallDir
	Return SetError(__WD_Error($sFuncName, $iErr, $sMessage, $iExt), $iExt, $bResult)
EndFunc   ;==>_WD_UpdateDriver

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __WD_UpdateExtractor
; Description ...: Extract webdriver executable from zip file
; Syntax ........: __WD_UpdateExtractor($sTempFile, $sInstallDir, $sDriverEXE[, $sSubDir = ""])
; Parameters ....: $sTempFile           - Full path to zip file.
;                  $sInstallDir         - Directory where extracted files are placed
;                  $sDriverEXE          - Name of webdriver executable
;                  $sSubDir             - [optional] Directory containing files to extract.
; Return values .: None
; Return values .: Success - None
;                  Failure - None and sets @error to one of the following values:
;                  - $_WD_ERROR_GeneralError
;                  - $_WD_ERROR_FileIssue
;                  - $_WD_ERROR_NotFound
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func __WD_UpdateExtractor($sTempFile, $sInstallDir, $sDriverEXE, $sSubDir = "")
	Local Const $sFuncName = "__WD_UpdateExtractor"
	Local $iErr = $_WD_ERROR_Success, $iExt = 0

	; Handle COM Errors
	Local $oErr = ObjEvent("AutoIt.Error", __WD_ErrHnd)
	#forceref $oErr

	Local $oShell = ObjCreate("Shell.Application")
	If @error Then
		$iErr = $_WD_ERROR_GeneralError
	ElseIf FileGetSize($sTempFile) = 0 Then
		$iErr = $_WD_ERROR_FileIssue
		$iExt = 11 ; $iExt from 11 to 19 are related to __WD_UpdateExtractor()
	ElseIf IsObj($oShell.NameSpace($sTempFile)) = 0 Then
		$iErr = $_WD_ERROR_FileIssue
		$iExt = 12
	ElseIf IsObj($oShell.NameSpace($sInstallDir)) = 0 Then
		$iErr = $_WD_ERROR_FileIssue
		$iExt = 13
	Else
		Local $oNameSpace_Temp = $oShell.NameSpace($sTempFile & $sSubDir)
		Local $FilesInZip = $oNameSpace_Temp.items
		If @error Then
			$iErr = $_WD_ERROR_GeneralError
			$iExt = 14
		Else
			Local $oNameSpace_Install = $oShell.NameSpace($sInstallDir)
			Local $bEXEWasFound = False
			For $FileItem In $FilesInZip     ; Check the files in the archive separately
				; https://docs.microsoft.com/en-us/windows/win32/shell/folderitem

				If $FileItem.IsFolder Then
					; try to Extract subdir content
					__WD_UpdateExtractor($sTempFile, $sInstallDir, $sDriverEXE, '\' & $FileItem.Name)
					If Not @error Then
						$bEXEWasFound = True
						ExitLoop
					EndIf
				Else
					If StringRight($FileItem.Name, 4) = ".exe" Or StringRight($FileItem.Path, 4) = ".exe" Then     ; extract only EXE files
						$bEXEWasFound = True
						; delete webdriver from disk before unpacking to avoid potential problems
						FileDelete($sInstallDir & $sDriverEXE)
						$oNameSpace_Install.CopyHere($FileItem, 20)     ; 20 = (4) Do not display a progress dialog box. + (16) Respond with "Yes to All" for any dialog box that is displayed.
						ExitLoop
					EndIf
				EndIf
			Next
			If @error Then
				$iErr = $_WD_ERROR_GeneralError
				$iExt = 15
			ElseIf Not $bEXEWasFound Then
				$iErr = $_WD_ERROR_NotFound
				$iExt = 19 ; $iExt from 11 to 19 are related to __WD_UpdateExtractor()
			Else
				$iErr = $_WD_ERROR_Success
			EndIf
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, Default, $iExt), $iExt)
EndFunc   ;==>__WD_UpdateExtractor

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetBrowserVersion
; Description ...: Get version number of specified browser.
; Syntax ........: _WD_GetBrowserVersion($sBrowser)
; Parameters ....: $sBrowser - Browser name or full path to browser executable
; Return values .: Success - Version number ("#.#.#.#" format) and sets @extended to index of $_WD_SupportedBrowsers
;                  Failure - "0" and sets @error to one of the following values:
;                  - $_WD_ERROR_FileIssue
;                  - $_WD_ERROR_NotSupported
;                  - $_WD_ERROR_NotFound
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......: _WD_GetBrowserPath, _WD_UpdateDriver
; Link ..........:
; Example .......: MsgBox(0, "", _WD_GetBrowserVersion('chrome'))
; ===============================================================================================================================
Func _WD_GetBrowserVersion($sBrowser)
	Local Const $sFuncName = "_WD_GetBrowserVersion"
	Local Const $sParameters = 'Parameters:    Browser=' & $sBrowser
	Local $iErr = $_WD_ERROR_Success, $iExt = 0
	Local $sBrowserVersion = "0"

	Local $sPath = _WD_GetBrowserPath($sBrowser)
	$iErr = @error
	$iExt = @extended
	If $iErr Then
		; as registry checks fails, now checking if file exist
		If FileExists($sBrowser) Then
			; Resetting as we are now checking file instead registry entries
			$iErr = $_WD_ERROR_Success
			$iExt = 0

			; Extract filename and confirm match in list of supported browsers
			Local $sBrowserName = StringRegExpReplace($sBrowser, "^.*\\|\..*$", "")
			Local $iIndex = _ArraySearch($_WD_SupportedBrowsers, $sBrowserName, Default, Default, Default, Default, Default, $_WD_BROWSER_Name)
			If @error Then
				$iErr = $_WD_ERROR_NotSupported
			Else
				$iExt = $iIndex
				$sPath = $sBrowser
			EndIf
		EndIf
	EndIf

	If $iErr = $_WD_ERROR_Success Then
		If _WinAPI_GetBinaryType($sPath) = 0 Then ; check if file is executable
			$iErr = $_WD_ERROR_FileIssue
			$iExt = 31 ; $iExt from 31 to 39 are related to _WD_GetBrowserVersion()
		Else
			$sBrowserVersion = FileGetVersion($sPath)
			If @error Then
				$iErr = $_WD_ERROR_FileIssue
				$iExt = 32
			EndIf
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters, $iExt), $iExt, $sBrowserVersion)
EndFunc   ;==>_WD_GetBrowserVersion

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetBrowserPath
; Description ...: Retrieve path to browser executable from registry
; Syntax ........: _WD_GetBrowserPath($sBrowser)
; Parameters ....: $sBrowser - Name of browser
; Return values .: Success - Full path to browser executable and sets @extended to index of $_WD_SupportedBrowsers
;                  Failure - "" and sets @error to one of the following values:
;                  - $_WD_ERROR_NotSupported
;                  - $_WD_ERROR_NotFound
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......: Browser names are defined in $_WD_SupportedBrowsers
; Related .......: _WD_GetBrowserVersion, _WD_UpdateDriver
; Link ..........:
; Example .......: MsgBox(0, "", _WD_GetBrowserPath('firefox'))
; ===============================================================================================================================
Func _WD_GetBrowserPath($sBrowser)
	Local Const $sFuncName = "_WD_GetBrowserPath"
	Local Const $sParameters = 'Parameters:    Browser=' & $sBrowser
	Local Const $sRegKeyCommon = '\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\'
	Local $iErr = $_WD_ERROR_Success, $iExt = 0
	Local $sEXE, $sPath = ""

	; Confirm match in list of supported browsers
	Local $iIndex = _ArraySearch($_WD_SupportedBrowsers, $sBrowser, Default, Default, Default, Default, Default, $_WD_BROWSER_Name)
	If @error Then
		$iErr = $_WD_ERROR_NotSupported
		$iExt = 21 ; $iExt from 21 to 29 are related to _WD_GetBrowserPath()
	Else
		$sEXE = $_WD_SupportedBrowsers[$iIndex][$_WD_BROWSER_ExeName]

		; check HKLM or in case of error HKCU
		$sPath = RegRead("HKLM" & $sRegKeyCommon & $sEXE, "")
		If @error Then $sPath = RegRead("HKCU" & $sRegKeyCommon & $sEXE, "")

		; Generate $_WD_ERROR_NotFound if neither key is found
		If @error Then
			$iErr = $_WD_ERROR_NotFound
			$iExt = 22
		Else
			$sPath = StringRegExpReplace($sPath, '["'']', '') ; Remove quotation marks
			$sPath = StringRegExpReplace($sPath, '(.+\\)(.*exe)', '$1' & $sEXE) ; Registry entries can contain "Launcher.exe" instead "opera.exe"
			$iExt = $iIndex
		EndIf
	EndIf
	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters, $iExt), $iExt, $sPath)
EndFunc   ;==>_WD_GetBrowserPath

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetWebDriverVersion
; Description ...: Get version number of specifed webdriver.
; Syntax ........: _WD_GetWebDriverVersion($sInstallDir, $sDriverEXE)
; Parameters ....: $sInstallDir - Directory where $sDriverEXE is located
;                  $sDriverEXE  - File name of "WebDriver.exe"
; Return values .: Success - The value you get when you call WebDriver with the --version parameter
;                  Failure - "0" and sets @error to one of the following values:
;                  - $_WD_ERROR_NotFound
;                  - $_WD_ERROR_GeneralError
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......: _WD_UpdateDriver
; Link ..........:
; Example .......: MsgBox(0, "", _WD_GetWebDriverVersion(@ScriptDir,'chromedriver.exe'))
; ===============================================================================================================================
Func _WD_GetWebDriverVersion($sInstallDir, $sDriverEXE)
	Local Const $sFuncName = "_WD_GetWebDriverVersion"
	Local Const $sParameters = 'Parameters:    Dir=' & $sInstallDir & '    EXE=' & $sDriverEXE
	Local $sDriverVersion = "0"
	Local $iErr = $_WD_ERROR_Success
	Local $iExt = 0

	$sInstallDir = StringRegExpReplace($sInstallDir, '(?i)(\\)\Z', '') & '\' ; prevent double \\ on the end of directory
	If Not FileExists($sInstallDir & $sDriverEXE) Then
		$iErr = $_WD_ERROR_NotFound
	Else
		Local $sCmd = $sInstallDir & $sDriverEXE & " --version"
		Local $iPID = Run($sCmd, $sInstallDir, @SW_HIDE, $STDOUT_CHILD)
		If @error Then
			$iErr = $_WD_ERROR_GeneralError
			$iExt = 1
		EndIf

		If $iPID Then
			ProcessWaitClose($iPID)
			Local $sOutput = StdoutRead($iPID)
			Local $aMatches = StringRegExp($sOutput, "\d+(?:\.\d+){2,}", 1)
			If @error Then
				$iErr = $_WD_ERROR_GeneralError
				$iExt = 2
			Else
				$sDriverVersion = $aMatches[0]
			EndIf
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters, $iExt), $iExt, $sDriverVersion)
EndFunc   ;==>_WD_GetWebDriverVersion

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_DownloadFile
; Description ...: Download file and save to disk.
; Syntax ........: _WD_DownloadFile($sURL, $sDest[, $iOptions = Default])
; Parameters ....: $sURL     - URL representing file to be downloaded
;                  $sDest    - Full path, including filename, of destination file
;                  $iOptions - [optional] Download options
; Return values .: Success - True (Download succeeded).
;                  Failure - False (Download failed) and sets @error to one of the following values:
;                  - $_WD_ERROR_NotFound
;                  - $_WD_ERROR_FileIssue
;                  - $_WD_ERROR_Timeout
;                  - $_WD_ERROR_GeneralError
;                  - $_WD_ERROR_UserAbort
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_DownloadFile($sURL, $sDest, $iOptions = Default)
	Local Const $sFuncName = "_WD_DownloadFile"
	Local Const $sParameters = 'Parameters:    URL=' & $sURL & '    Dest=' & $sDest & '    Options=' & $iOptions
	Local $bResult = False, $hWaitTimer
	Local $iErr = $_WD_ERROR_Success, $iExt = 0

	If $iOptions = Default Then $iOptions = $INET_FORCERELOAD + $INET_IGNORESSL + $INET_BINARYTRANSFER

	Local $sData = InetRead($sURL, $iOptions)
	If @error Then $iErr = $_WD_ERROR_NotFound

	If $iErr = $_WD_ERROR_Success Then
		Local $hFile = FileOpen($sDest, $FO_OVERWRITE + $FO_BINARY)

		If $hFile <> -1 Then
			FileWrite($hFile, $sData)
			FileClose($hFile)

			$hWaitTimer = TimerInit()
			; make sure that file is not used after download, for example by AV software scanning procedure
			While 1
				__WD_Sleep(100)
				If @error Then
					$iErr = @error
					ExitLoop
				ElseIf Not _WinAPI_FileInUse($sDest) Then
					If @error Then
						$iErr = $_WD_ERROR_FileIssue
						$iExt = 1 ; $iExt from 1 to 9 are related to _WD_DownloadFile()
					Else
						$bResult = True
					EndIf
					ExitLoop
				ElseIf TimerDiff($hWaitTimer) > $_WD_DefaultTimeout Then
					$iErr = $_WD_ERROR_FileIssue
					$iExt = 2
					ExitLoop
				EndIf
			WEnd
		Else
			$iErr = $_WD_ERROR_GeneralError
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters, $iExt), $iExt, $bResult)
EndFunc   ;==>_WD_DownloadFile

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_SetTimeouts
; Description ...: User friendly function to set webdriver session timeouts.
; Syntax ........: _WD_SetTimeouts($sSession[, $iPageLoad = Default[, $iScript = Default[, $iImplicitWait = Default]]])
; Parameters ....: $sSession      - Session ID from _WD_CreateSession
;                  $iPageLoad     - [optional] Page load timeout in milliseconds
;                  $iScript       - [optional] Script timeout in milliseconds
;                  $iImplicitWait - [optional] Implicit wait timeout in milliseconds
; Return values .: Success - Return value from web driver in JSON format.
;                  Failure - 0 and sets @error to one of the following values:
;                  - $_WD_ERROR_InvalidArgue
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_InvalidDataType
; Author ........: Danp2
; Modified ......:
; Remarks .......: $iScript parameter can be null, implies that scripts should never be interrupted, but instead run indefinitely
;                  When setting page load timeout, WinHTTP receive timeout is automatically adjusted as well
; Related .......: _WD_Timeouts, _WD_LastHTTPResult
; Link ..........: https://www.w3.org/TR/webdriver/#set-timeouts
; Example .......: _WD_SetTimeouts($sSession, 50000)
; ===============================================================================================================================
Func _WD_SetTimeouts($sSession, $iPageLoad = Default, $iScript = Default, $iImplicitWait = Default)
	Local Const $sFuncName = "_WD_SetTimeouts"
	Local Const $bIsNull = (IsKeyword($iScript) = $KEYWORD_NULL)
	Local Const $sParameters = 'Parameters:    PageLoad=' & $iPageLoad & '    Script=' & ($bIsNull ? "Null" : $iScript) & '    Implicit=' & $iImplicitWait
	Local $sTimeouts = '', $sResult = 0, $iErr
	$_WD_HTTPRESULT = 0
	$_WD_HTTPRESPONSE = ''

	; Build string to pass to _WD_Timeouts
	If $iPageLoad <> Default Then
		If Not IsInt($iPageLoad) Then
			Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(int) $vValue: " & $iPageLoad), 0, 0)
		EndIf

		$sTimeouts &= '"pageLoad":' & $iPageLoad
	EndIf

	If $iScript <> Default Then
		If Not IsInt($iScript) And Not $bIsNull Then
			Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(int) $vValue: " & $iScript), 0, 0)
		EndIf

		If StringLen($sTimeouts) Then $sTimeouts &= ", "
		$sTimeouts &= '"script":'
		$sTimeouts &= ($bIsNull) ? "null" : $iScript
	EndIf

	If $iImplicitWait <> Default Then
		If Not IsInt($iImplicitWait) Then
			Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(int) $vValue: " & $iImplicitWait), 0, 0)
		EndIf

		If StringLen($sTimeouts) Then $sTimeouts &= ", "
		$sTimeouts &= '"implicit":' & $iImplicitWait
	EndIf

	If StringLen($sTimeouts) Then
		$sTimeouts = "{" & $sTimeouts & "}"

		; Set webdriver timeouts
		$sResult = _WD_Timeouts($sSession, $sTimeouts)
		$iErr = @error

		If $iErr = $_WD_ERROR_Success And $iPageLoad <> Default Then
			; Adjust WinHTTP receive timeouts to prevent send/recv errors
			$_WD_HTTPTimeOuts[3] = $iPageLoad + 1000
		EndIf
	Else
		$iErr = $_WD_ERROR_InvalidArgue
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sResult)
EndFunc   ;==>_WD_SetTimeouts

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetElementById
; Description ...: Locate element by id.
; Syntax ........: _WD_GetElementById($sSession, $sID)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
;                  $sID      - ID of desired element
; Return values .: Success - Element ID returned by web driver.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NoMatch
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_FindElement, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_GetElementById($sSession, $sID)
	Local Const $sFuncName = "_WD_GetElementById"
	Local Const $sParameters = 'Parameters:    ID=' & $sID

	Local $sXpath = '//*[@id="' & $sID & '"]'
	Local $sElement = _WD_FindElement($sSession, $_WD_LOCATOR_ByXPath, $sXpath)
	Local $iErr = @error

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sElement)
EndFunc   ;==>_WD_GetElementById

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetElementByName
; Description ...: Locate element by name.
; Syntax ........: _WD_GetElementByName($sSession, $sName)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
;                  $sName    - Name of desired element
; Return values .: Success - Element ID returned by web driver
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NoMatch
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_FindElement, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_GetElementByName($sSession, $sName)
	Local Const $sFuncName = "_WD_GetElementByName"
	Local Const $sParameters = 'Parameters:    Name=' & $sName

	Local $sXpath = '//*[@name="' & $sName & '"]'
	Local $sElement = _WD_FindElement($sSession, $_WD_LOCATOR_ByXPath, $sXpath)
	Local $iErr = @error

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sElement)
EndFunc   ;==>_WD_GetElementByName

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_SetElementValue
; Description ...: Set value of designated element.
; Syntax ........: _WD_SetElementValue($sSession, $sElement, $sValue[, $iStyle = Default])
; Parameters ....: $sSession - Session ID from _WD_CreateSession
;                  $sElement - Element ID from _WD_FindElement
;                  $sValue   - New value for element
;                  $iStyle   - [optional] Update style. Default is $_WD_OPTION_Standard
;                  |$_WD_OPTION_Standard (0) = Set value using _WD_ElementAction
;                  |$_WD_OPTION_Advanced (1) = Set value using _WD_ExecuteScript
; Return values .: Success - Requested data returned by web driver
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_NoMatch
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_InvalidDataType
;                  - $_WD_ERROR_InvalidExpression
; Author ........: Danp2
; Modified ......: mLipok, TheDcoder
; Remarks .......: When using Advanced mode, translations or string encoding should occur prior to
;                  calling this function because the supplied value is used without modification.
; Related .......: _WD_ElementAction, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_SetElementValue($sSession, $sElement, $sValue, $iStyle = Default)
	Local Const $sFuncName = "_WD_SetElementValue"
	Local Const $bParameters_Value = (($_WD_DEBUG = $_WD_DEBUG_Full) ? ($sValue) : ("<masked>"))
	Local Const $sParameters = 'Parameters:    Element=' & $sElement & '    Value=' & $bParameters_Value & '    Style=' & $iStyle
	Local $sResult, $iErr, $sScript

	If $iStyle = Default Then $iStyle = $_WD_OPTION_Standard
	If $iStyle < $_WD_OPTION_Standard Or $iStyle > $_WD_OPTION_Advanced Then $iStyle = $_WD_OPTION_Standard

	Switch $iStyle
		Case $_WD_OPTION_Standard
			$sResult = _WD_ElementAction($sSession, $sElement, 'value', $sValue)
			$iErr = @error

		Case $_WD_OPTION_Advanced
			$sScript = _
					"Object.getOwnPropertyDescriptor(arguments[0].__proto__, 'value').set.call(arguments[0], arguments[1]);" & _
					"arguments[0].dispatchEvent(new Event('input', {bubbles: true}));" & _
					"arguments[0].dispatchEvent(new Event('change', {bubbles: true}));" & _
					""
			$sResult = _WD_ExecuteScript($sSession, $sScript, __WD_JsonElement($sElement) & ',"' & $sValue & '"')
			$iErr = @error

	EndSwitch

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sResult)
EndFunc   ;==>_WD_SetElementValue

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_ElementActionEx
; Description ...: Perform advanced action on designated element.
; Syntax ........: _WD_ElementActionEx($sSession, $sElement, $sCommand[, $iXOffset = Default[, $iYOffset = Default[, $iButton = Default[, $iHoldDelay = Default[, $sModifier = Default[, $bScrollView = Default]]]]]])
; Parameters ....: $sSession   - Session ID from _WD_CreateSession
;                  $sElement   - Element ID from _WD_FindElement
;                  $sCommand   - one of the following actions:
;                  |
;                  |CHECK - Checks a checkbox input element
;                  |CHILDCOUNT - Returns the number of child elements
;                  |CLICK - Clicks on the target element
;                  |CLICKANDHOLD - Clicks on the target element and holds the button down for the designated timeframe ($iHoldDelay)
;                  |DOUBLECLICK - Do a double click on the selected element
;                  |HIDE - Change the element's style to 'display: none' to hide the element
;                  |HOVER - Move the mouse pointer so that it is located above the target element
;                  |MODIFIERCLICK - Holds down a modifier key on the keyboard before clicking on the target element. This can be used to perform advanced actions such as shift-clicking an element
;                  |RIGHTCLICK - Do a rightclick on the selected element
;                  |SHOW - Change the element's style to 'display: normal' to unhide/show the element
;                  |UNCHECK - Unchecks a checkbox input element
;                  |REMOVE - Removes the element from the DOM
;                  $iXOffset    - [optional] X Offset. Default is 0
;                  $iYOffset    - [optional] Y Offset. Default is 0
;                  $iButton     - [optional] Mouse button. Default is $_WD_BUTTON_Left
;                  $iHoldDelay  - [optional] Hold time in ms. Default is 1000
;                  $sModifier   - [optional] Modifier key. Default is "\uE008" (shift key)
;                  $bScrollView - [optional] Forcibly scroll element into view? Default is True
; Return values .: Success - Return value from web driver (could be an empty string)
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_InvalidDataType
; Author ........: Danp2
; Modified ......: TheDcoder, mLipok
; Remarks .......: Moving the mouse pointer above the target element is the first thing to occur for every $sCommand before it gets executed.
;                  There are examples in DemoElements() function in wd_demo.au3
; Related .......: _WD_ElementAction, _WD_Action, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_ElementActionEx($sSession, $sElement, $sCommand, $iXOffset = Default, $iYOffset = Default, $iButton = Default, $iHoldDelay = Default, $sModifier = Default, $bScrollView = Default)
	Local Const $sFuncName = "_WD_ElementActionEx"
	Local Const $sParameters = 'Parameters:    Element=' & $sElement & '    Command=' & $sCommand & '    XOffset=' & $iXOffset & '    YOffset=' & $iYOffset & '    Button=' & $iButton & '    HoldDelay=' & $iHoldDelay & '    Modifier=' & $sModifier & '    ScrollView=' & $bScrollView
	Local $sAction, $sJavaScript, $iErr, $sResult, $iActionType = 1
	$_WD_HTTPRESULT = 0
	$_WD_HTTPRESPONSE = ''

	If $iXOffset = Default Then $iXOffset = 0
	If $iYOffset = Default Then $iYOffset = 0
	If $iButton = Default Then $iButton = $_WD_BUTTON_Left
	If $iHoldDelay = Default Then $iHoldDelay = 1000
	If $sModifier = Default Then $sModifier = "\uE008" ; shift
	If $bScrollView = Default Then $bScrollView = True

	If Not IsInt($iXOffset) Then
		Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(int) $iXOffset: " & $iXOffset), 0, "")
	EndIf

	If Not IsInt($iButton) Then
		Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(int) $iButton: " & $iButton), 0, "")
	EndIf

	If Not IsInt($iYOffset) Then
		Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(int) $iYOffset: " & $iYOffset), 0, "")
	EndIf

	If Not IsInt($iHoldDelay) Then
		Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(int) $iHoldDelay: " & $iHoldDelay), 0, "")
	EndIf

	Local $sPreAction = '', $sPostAction = '', $sPostHoverAction = ''

	Switch $sCommand
		Case 'hover'
			; No additional actions required for hover functionality

		Case 'click'
			$sPostHoverAction = _
					',' & _WD_JsonActionPointer("pointerDown", $iButton) & _
					',' & _WD_JsonActionPointer("pointerUp", $iButton) & _
					''

		Case 'doubleclick'
			$sPostHoverAction = _
					',' & _WD_JsonActionPointer("pointerDown", $iButton) & _
					',' & _WD_JsonActionPointer("pointerUp", $iButton) & _
					',' & _WD_JsonActionPointer("pointerDown", $iButton) & _
					',' & _WD_JsonActionPointer("pointerUp", $iButton) & _
					''

		Case 'rightclick'
			$sPostHoverAction = _
					',' & _WD_JsonActionPointer("pointerDown", $_WD_BUTTON_Right) & _
					',' & _WD_JsonActionPointer("pointerUp", $_WD_BUTTON_Right) & _
					''

		Case 'clickandhold'
			$sPostHoverAction = _
					',' & _WD_JsonActionPointer("pointerDown", $iButton) & _
					',' & _WD_JsonActionPause($iHoldDelay) & _
					',' & _WD_JsonActionPointer("pointerUp", $iButton) & _
					''

		Case 'modifierclick'
			; Hold modifier key down
			$sPreAction = _
					_WD_JsonActionKey("keyDown", $sModifier) & _
					','

			; Perform click
			$sPostHoverAction = _
					',' & _WD_JsonActionPointer("pointerDown", $iButton) & _
					',' & _WD_JsonActionPointer("pointerUp", $iButton) & _
					''

			; Release modifier key
			$sPostAction = _
					',' & _WD_JsonActionKey("keyUp", $sModifier, 2) & _
					''

		Case 'hide'
			$iActionType = 2
			$sJavaScript = "arguments[0].style='display: none'; return true;"

		Case 'show'
			$iActionType = 2
			$sJavaScript = "arguments[0].style='display: normal'; return true;"

		Case 'childcount'
			$iActionType = 2
			$sJavaScript = "return arguments[0].children.length;"

		Case 'check', 'uncheck'
			$iActionType = 2
			$sJavaScript = "Object.getOwnPropertyDescriptor(arguments[0].__proto__, 'checked').set.call(arguments[0], " & ($sCommand = "check" ? 'true' : 'false') & ");arguments[0].dispatchEvent(new Event('change', { bubbles: true }));"

		Case 'remove'
			$iActionType = 2
			$sJavaScript = "arguments[0].remove();"

		Case Else
			Return SetError(__WD_Error($sFuncName, $_WD_ERROR_InvalidDataType, "(Hover|RightClick|DoubleClick|Click|ClickAndHold|Hide|Show|ChildCount|ModifierClick|Check|Uncheck|Remove) $sCommand=>" & $sCommand), 0, "")

	EndSwitch

	#Region - JSON builder
	; $sActionTemplate declaration is outside the switch to not pollute simplicity of the >Switch ... EndSwitch< - for better code maintenance
	; StringFormat() usage is significantly faster than building JSON string each time from scratch
	; StringReplace() removes all possible @TAB's because they are used only for indentation and are not needed in JSON string
	; This line in compilation process will be linearized, and will be processed once, thus next usage will be significantly faster
	Local Static $sActionTemplate = StringReplace( _
			'{' & _
			'	"actions":[' & _ ; Open main action
			'		%s' & _ ; %s > $sPreAction
			'		{' & _ ; Start of default "hover" action
			'			"id":"hover"' & _
			'			,"type":"pointer"' & _
			'			,"parameters":{"pointerType":"mouse"}' & _
			'			,"actions":[' & _ ; Open mouse actions
			'				{' & _
			'					"type":"pointerMove"' & _
			'					,"duration":100' & _
			'					,"x":%s' & _ ; %s > $iXOffset
			'					,"y":%s' & _ ; %s > $iYOffset
			'					,"origin":{' & _
			'						"ELEMENT":"%s"' & _ ; %s > $sElement
			'						,"' & $_WD_ELEMENT_ID & '":"%s"' & _ ; %s > $sElement
			'					}' & _
			'				}' & _
			'				%s' & _ ; %s > $sPostHoverAction
			'			]' & _ ; Close mouse actions
			'		}' & _ ; End of default 'hover' action
			'		%s' & _ ; %s > $sPostAction
			'	]' & _ ; Close main action
			'}', @TAB, '')
	#EndRegion - JSON builder

	If $bScrollView Then
		_WD_ExecuteScript($sSession, "arguments[0].scrollIntoView(false);", __WD_JsonElement($sElement))
		Sleep(500) ; short Sleep() outside of the loop so no need to use __WD_Sleep()
	EndIf

	Switch $iActionType
		Case 1
			$sAction = StringFormat($sActionTemplate, $sPreAction, $iXOffset, $iYOffset, $sElement, $sElement, $sPostHoverAction, $sPostAction)
			$sResult = _WD_Action($sSession, 'actions', $sAction)
			$iErr = @error

		Case 2
			$sResult = _WD_ExecuteScript($sSession, $sJavaScript, __WD_JsonElement($sElement), Default, $_WD_JSON_Value)
			$iErr = @error
	EndSwitch

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $sResult)
EndFunc   ;==>_WD_ElementActionEx

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_DispatchEvent
; Description ...: Create and dispatch events
; Syntax ........: _WD_DispatchEvent($sSession,  $sElement,  $sEvent[,  $sOptions = Default])
; Parameters ....: $sSession - Session ID from _WD_CreateSession.
;                  $sElement - Element ID from _WD_FindElement.
;                  $sEvent   - The event type.
;                  $sOptions  - [optional] Event options in JSON format. Default is "{bubbles: true}".
; Return values .: None
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_ExecuteScript
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_DispatchEvent($sSession, $sElement, $sEvent, $sOptions = Default)
	Local Const $sFuncName = "_WD_DispatchEvent"
	Local $sScript, $sJsonElement, $sParameters

	If $sOptions = Default Or Not IsString($sOptions) Then $sOptions = "{bubbles: true}"

	$sScript = "arguments[0].dispatchEvent(new Event(arguments[1], arguments[2]));"
	$sJsonElement = __WD_JsonElement($sElement)
	$sParameters = '"' & $sJsonElement & '","' & $sEvent & '","' & $sOptions & '"'
	_WD_ExecuteScript($sSession, $sScript, $sParameters)

	Return SetError(__WD_Error($sFuncName, @error))
EndFunc   ;==>_WD_DispatchEvent

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetTable
; Description ...: Retrieve text from all matching elements of a table.
; Syntax ........: _WD_GetTable($sSession, $sStrategy, $sSelector[, $sRowsSelector = Default[, $sColsSelector = Default]])
; Parameters ....: $sSession      - Session ID from _WD_CreateSession
;                  $sStrategy     - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector     - Indicates how the WebDriver should traverse through the HTML DOM to locate the desired <table> element.
;                  $sRowsSelector - [optional] Rows CSS selector. Default is "tr".
;                  $sColsSelector - [optional] Columns CSS selector. Default is "td, th".
; Return values .: Success - 2D array.
;                  Failure - "" (empty string) and sets @error to one of the following values:
;                  - $_WD_ERROR_Exception
;                  - $_WD_ERROR_NoMatch
; Author ........: danylarson
; Modified ......: water, danp2, mLipok
; Remarks .......: The CSS selectors can be overridden to control the included elements. For example, a modified $sRowsSelector of ":scope > tbody > tr" can be used to bypass nested tables.
; Related .......: _WD_FindElement, _WD_ElementAction, _WD_LastHTTPResult
; Link ..........: https://www.autoitscript.com/forum/topic/191990-webdriver-udf-w3c-compliant-version-01182020/page/18/?tab=comments#comment-1415164
; Example .......: No
; ===============================================================================================================================
Func _WD_GetTable($sSession, $sStrategy, $sSelector, $sRowsSelector = Default, $sColsSelector = Default)
	Local Const $sFuncName = "_WD_GetTable"
	Local Const $sParameters = 'Parameters:   Strategy=' & $sStrategy & '   Selector=' & $sSelector & '   RowsSelector=' & $sRowsSelector & '   ColsSelector=' & $sColsSelector
	Local $sElement, $aTable = ''
	$_WD_HTTPRESULT = 0
	$_WD_HTTPRESPONSE = ''

	If $sRowsSelector = Default Then $sRowsSelector = "tr"
	If $sColsSelector = Default Then $sColsSelector = "td, th"

	; Get the table element
	$sElement = _WD_FindElement($sSession, $sStrategy, $sSelector)
	Local $iErr = @error

	If $iErr = $_WD_ERROR_Success Then
		; https://stackoverflow.com/questions/64842157
		Local $sScript = "return [...arguments[0].querySelectorAll(arguments[1])]" & _
				".map(row => [...row.querySelectorAll(arguments[2])]" & _
				".map(cell => cell.textContent.trim()));"
		Local $sArgs = __WD_JsonElement($sElement) & ', "' & $sRowsSelector & '", "' & $sColsSelector & '"'
		Local $sResult = _WD_ExecuteScript($sSession, $sScript, $sArgs)
		$iErr = @error

		If $iErr = $_WD_ERROR_Success Then
			; Extract target data from results and convert to array
			Local $sStr = StringMid($sResult, 10, StringLen($sResult) - 10)
			$aTable = __WD_Make2Array($sStr)
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $aTable)
EndFunc   ;==>_WD_GetTable

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_IsFullScreen
; Description ...: Return a boolean indicating if the session is in full screen mode.
; Syntax ........: _WD_IsFullScreen($sSession)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
; Return values .: Success - True or False.
;                  Failure - Response from webdriver and sets @error returned from _WD_ExecuteScript()
; Author ........: Danp2
; Modified ......: mLipok
; Remarks .......:
; Related .......: _WD_LastHTTPResult
; Link ..........: https://www.autoitscript.com/forum/topic/205553-webdriver-udf-help-support-iii/?do=findComment&comment=1480527
; Example .......: No
; ===============================================================================================================================
Func _WD_IsFullScreen($sSession)
	Local Const $sFuncName = "_WD_IsFullScreen"
	Local $bResult = _WD_ExecuteScript($sSession, 'return screen.width == window.innerWidth and screen.height == window.innerHeight;', Default, Default, $_WD_JSON_Value)
	Local $iErr = @error
	Return SetError(__WD_Error($sFuncName, $iErr), 0, $bResult)
EndFunc   ;==>_WD_IsFullScreen

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetDevicePixelRatio
; Description ...: Returns an integer indicating the DevicePixelRatio
; Syntax ........: _WD_GetDevicePixelRatio($sSession)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
; Return values .: Success - DevicePixelRatio
;                  Failure - Response from webdriver and sets @error returned from _WD_ExecuteScript()
; Author ........: mLipok
; Modified ......:
; Remarks .......:
; Related .......: _WD_LastHTTPResult
; Link ..........: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
; Example .......: No
; ===============================================================================================================================
Func _WD_GetDevicePixelRatio($sSession)
	Local Const $sFuncName = "_WD_GetDevicePixelRatio"
	Local $sResponse = _WD_ExecuteScript($sSession, "return window.devicePixelRatio", Default, Default, $_WD_JSON_Value)
	Local $iErr = @error
	Return SetError(__WD_Error($sFuncName, $iErr), 0, $sResponse)
EndFunc   ;==>_WD_GetDevicePixelRatio

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_CheckContext
; Description ...: Check if browser context is still valid.
; Syntax ........: _WD_CheckContext($sSession[, $bReconnect = Default[, $vTarget = Default]])
; Parameters ....: $sSession   - Session ID from _WD_CreateSession
;                  $bReconnect - [optional] Auto reconnect? Default is True
;                  $vTarget    - [optional] Tab to target in reconnect attempt. Default is $_WD_TARGET_FirstTab. This can be the handle for an existing tab if known
; Return values .: Success - Returns one of the following values:
;                  |$_WD_STATUS_Valid (1) - Current browser context is valid
;                  |$_WD_STATUS_Reconnect (2) - Context was invalid; Successfully reconnected to existing tab
;                  Failure - $_WD_STATUS_Invalid (0) and sets @error to $_WD_ERROR_Exception
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_Action, _WD_Window, _WD_LastHTTPResult
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_CheckContext($sSession, $bReconnect = Default, $vTarget = Default)
	Local Const $sFuncName = "_WD_CheckContext"
	Local $iResult = $_WD_STATUS_Invalid

	If $bReconnect = Default Then $bReconnect = True
	If $vTarget = Default Then $vTarget = $_WD_TARGET_FirstTab

	_WD_Action($sSession, 'url')
	Local $iErr = @error

	Switch $iErr
		Case $_WD_ERROR_Success
			$iResult = $_WD_STATUS_Valid

		Case $_WD_ERROR_Exception, $_WD_ERROR_ContextInvalid
			If $bReconnect Then
				If IsInt($vTarget) Then
					; To recover, get an array of window handles and use one
					Local $aHandles = _WD_Window($sSession, "handles")

					If @error = $_WD_ERROR_Success And IsArray($aHandles) Then
						Select
							Case $vTarget = $_WD_TARGET_FirstTab
								$vTarget = $aHandles[0]

							Case $vTarget = $_WD_TARGET_LastTab
								$vTarget = $aHandles[UBound($aHandles) - 1]

						EndSelect
					EndIf
				EndIf

				_WD_Window($sSession, "switch", '{"handle":"' & $vTarget & '"}')

				If @error = $_WD_ERROR_Success Then
					$iResult = $_WD_STATUS_Reconnect
				EndIf
			EndIf
	EndSwitch

	Return SetError(__WD_Error($sFuncName, ($iResult) ? $_WD_ERROR_Success : $_WD_ERROR_Exception), 0, $iResult)
EndFunc   ;==>_WD_CheckContext

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetContext
; Description ...: Retrieve the element ID of the current browsing context
; Syntax ........: _WD_GetContext($sSession)
; Parameters ....: $sSession - Session ID from _WD_CreateSession
; Return values .: Success - Element ID of current frame / document
;                  Failure - "" and sets @error to value returned from _WD_ExecuteScript()
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_GetContext($sSession)
	Local Const $sFuncName = "_WD_GetContext"
	Local $sElement = _WD_ExecuteScript($sSession, "return window.document.body;", Default, Default, $_WD_JSON_Element)
	Local $iErr = @error

	If $iErr Then $sElement = ""
	Return SetError(__WD_Error($sFuncName, $iErr), 0, $sElement)
EndFunc   ;==>_WD_GetContext

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetElementByRegEx
; Description ...: Find element by matching attributes values using Javascript regular expression
; Syntax ........: _WD_GetElementByRegEx($sSession, $sMode, $sRegExPattern[, $sRegExFlags = ""[, $bAll = False]])
; Parameters ....: $sSession            - Session ID from _WD_CreateSession
;                  $sMode               - Attribute of the element which should be matched, e.g. `id`, `style`, `class` etc.
;                  $sRegExPattern       - JavaScript compatible regular expression
;                  $sRegExFlags         - [optional] RegEx Flags. Default is "".
;                  $bAll                - [optional] Return multiple matching elements? Default is False
; Return values .: Success - Matching Element ID(s)
;                  Failure - @error set to $_WD_ERROR_NoMatch if there are no matches OR
;                            Response from _WD_ExecuteScript() and sets @error to value returned from _WD_ExecuteScript()
; Author ........: TheDcoder
; Modified ......: mLipok, Danp2
; Remarks .......: The RegEx matching is done by the browser's JavaScript engine so AutoIt's RegEx rules may not accurately work
;                  in this function. You may refer to the following resources for further information:
;                  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet
;                  https://regex101.com with FLAVOR set to: ECMAScript (JavaScript) to validate your RegEx
; Related .......:
; Link ..........:
; Example .......: _WD_GetElementByRegEx($sSession, 'class', 'button-[0-9]', 'i', True)
; ===============================================================================================================================
Func _WD_GetElementByRegEx($sSession, $sMode, $sRegExPattern, $sRegExFlags = "", $bAll = False)
	Local Const $sFuncName = "_WD_GetElementByRegEx"
	Local $iRow = 0, $iErr = 0, $vResult = ''
	Local Static $sJS_Static = _
			"return _JS_GetElementByRegEx('%s', '%s', '%s', %s) || '';" & _
			"" & _
			"function _JS_GetElementByRegEx(mode, pattern, flags = '', all = false) {" & _
			"   var regex = new RegExp(pattern, flags);" & _
			"   var elements;" & _
			"   elements = document.querySelectorAll(`[${mode}]`);" & _
			"   return Array.prototype[all ? 'filter' : 'find'].call(elements, x => regex.test(x.getAttribute(mode)));" & _
			"}" & _
			""

	Local $sJavaScript = StringFormat($sJS_Static, $sMode, $sRegExPattern, $sRegExFlags, StringLower($bAll))
	Local $oValues = _WD_ExecuteScript($sSession, $sJavaScript, Default, False, $_WD_JSON_Value)
	$iErr = @error
	If Not @error Then
		Local $sKey = "[" & $_WD_ELEMENT_ID & "]"

		If $bAll Then
			Local $aElements[UBound($oValues)]
			If UBound($aElements) < 1 Then
				$iErr = $_WD_ERROR_NoMatch
			Else
				For $oValue In $oValues
					$aElements[$iRow] = Json_Get($oValue, $sKey)
					$iRow += 1
				Next

				$vResult = $aElements
			EndIf
		Else
			$vResult = Json_Get($oValues, $sKey)
			If @error Then
				$iErr = $_WD_ERROR_NoMatch
			Else
				$iRow = 1
			EndIf
		EndIf
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr), $iRow, $vResult)
EndFunc   ;==>_WD_GetElementByRegEx

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_Storage
; Description ...: Provide access to the browser's localStorage and sessionStorage objects
; Syntax ........: _WD_Storage($sSession,  $sKey[,  $vValue = Default[,  $nType = Default]])
; Parameters ....: $sSession            - Session ID from _WD_CreateSession
;                  $vKey                - Key to manipulate.
;                  $vValue              - [optional] Value to store.
;                  $nType               - [optional] Storage type. Default is $_WD_STORAGE_Local
; Return values .: Success - Response from _WD_ExecuteScript() and sets @error to $_WD_ERROR_Success
;                  Failure - @error set to $_WD_ERROR_InvalidArgue if there are no matches OR
;                            Response from _WD_ExecuteScript() and sets @error to value returned from _WD_ExecuteScript()
; Author ........: Danp2
; Modified ......:
; Remarks .......:	Data is stored and retrieved without modification. Translations or string
;					encoding / decoding should occur outside of this function.
;
;					See below for special conditions --
;
;					| Parameter | Condition | Action                     |
;					|-----------|-----------|----------------------------|
;					| $vKey     | Numeric   | Return name of the Nth key |
;					| $vKey     | Null      | Clear storage              |
;					| $vValue   | Null      | Remove key from storage    |
;
; Related .......:
; Link ..........: https://developer.mozilla.org/en-US/docs/Web/API/Storage
; Example .......: No
; ===============================================================================================================================
Func _WD_Storage($sSession, $vKey, $vValue = Default, $nType = Default)
	Local Const $sFuncName = "_WD_Storage"
	Local $sParams, $vResult = '', $iErr = $_WD_ERROR_Success
	Local Const $bIsKeyNull = (IsKeyword($vKey) = $KEYWORD_NULL), $bIsValueNull = (IsKeyword($vValue) = $KEYWORD_NULL)
	Local Const $sParameters = 'Parameters:   Key=' & ($bIsKeyNull ? "Null" : $vKey) & '   Value=' & ($bIsValueNull ? "Null" : $vValue) & '   Type=' & $nType

	If $nType = Default Or $nType < $_WD_STORAGE_Local Or $nType > $_WD_STORAGE_Session Then $nType = $_WD_STORAGE_Local

	Local $sTarget = ($nType = $_WD_STORAGE_Local) ? "window.localStorage" : "window.sessionStorage"
	Local $sJavaScript = 'return ' & $sTarget

	Select
		Case $bIsKeyNull ; Empty storage
			If $vValue = Default Then
				$sJavaScript &= '.clear()'
				$sParams = $_WD_EmptyDict
			Else
				$iErr = $_WD_ERROR_InvalidArgue
			EndIf

		Case $vValue = Default ; Retrieve key
			If IsNumber($vKey) Then
				$sJavaScript &= '.key(arguments[0])'
				$sParams = String($vKey)
			Else
				$sJavaScript &= '.getItem(arguments[0])'
				$sParams = '"' & $vKey & '"'
			EndIf

		Case $bIsValueNull ; Remove key
			$sJavaScript &= '.removeItem(arguments[0])'
			$sParams = '"' & $vKey & '"'

		Case $vKey And $vValue ; Set key
			$sJavaScript &= '.setItem(arguments[0], arguments[1])'
			$sParams = '"' & $vKey & '","' & $vValue & '"'
	EndSelect

	If $iErr = $_WD_ERROR_Success Then
		$vResult = _WD_ExecuteScript($sSession, $sJavaScript, $sParams, Default, $_WD_JSON_Value)
		$iErr = @error
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters), 0, $vResult)
EndFunc   ;==>_WD_Storage

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_JsonActionKey
; Description ...: Formats keyboard "action" strings for use in _WD_Action
; Syntax ........: _WD_JsonActionKey($sType, $sKey[, $iSuffix = 1])
; Parameters ....: $sType      - Type of action (Ex: keyDown, keyUp)
;                  $sKey       - Keystroke to simulate
;                  $iSuffix  - [optional] Value to append to the "id" property. Default is 1.
; Return values .: Requested JSON string
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_Action
; Link ..........: https://www.w3.org/TR/webdriver/#actions
; Example .......: No
; ===============================================================================================================================
Func _WD_JsonActionKey($sType, $sKey, $iSuffix = Default)
	Local Const $sFuncName = "_WD_JsonActionKey"

	If $iSuffix = Default Then $iSuffix = 1

	Local $vData = Json_ObjCreate()
	Json_Put($vData, '.type', 'key')
	Json_Put($vData, '.id', 'keyboard_' & $iSuffix)
	Json_Put($vData, '.actions[0].type', $sType)
	Json_Put($vData, '.actions[0].value', $sKey)
	Local $sJSON = Json_Encode($vData)

	; Don't encode backslash of Unicode character
	If StringLeft($sKey, 2) = '\u' Then
		$sJSON = StringReplace($sJSON, "\\u", "\u")
	EndIf

	Return SetError(__WD_Error($sFuncName, 0, $sJSON), 0, $sJSON)
EndFunc   ;==>_WD_JsonActionKey


; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_GetFreePort
; Description ...:  Locate and return an available TCP port within a defined range
; Syntax ........: _WD_GetFreePort([$iMinPort = Default[,  $iMaxPort = Default]])
; Parameters ....: $iMinPort - [optional] Starting port number. Default is 64000
;                  $iMaxPort - [optional] Ending port number. Default is $iMinPort or 65000
; Return values .: Success - Available TCP port number
;                  Failure - Value from $iMinPort and sets @error to one of the following values:
;                  - $_WD_ERROR_NotFound
;                  - $_WD_ERROR_GeneralError
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_GetFreePort($iMinPort = Default, $iMaxPort = Default)
	Local Const $sFuncName = "_WD_GetFreePort"
	Local Const $sParameters = 'Parameters:   MinPort=' & $iMinPort & '   MaxPort=' & $iMaxPort
	Local $sMessage = ' > No available ports found'

	If $iMaxPort = Default Then $iMaxPort = ($iMinPort = Default) ? 65000 : $iMinPort
	If $iMinPort = Default Then $iMinPort = 64000
	Local $iResult = $iMinPort, $iErr = $_WD_ERROR_NotFound
	Local $aPorts = __WinAPI_GetTcpTable()

	If @error Then
		$iErr = $_WD_ERROR_GeneralError
		$sMessage = ' > Error occurred in __WinAPI_GetTcpTable'
	Else
		For $iPort = $iMinPort To $iMaxPort
			_ArraySearch($aPorts, $iPort, Default, Default, Default, Default, Default, 3)
			If @error = 6 Then
				$iResult = $iPort
				$iErr = $_WD_ERROR_Success
				$sMessage = ''
				ExitLoop
			EndIf
		Next
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, $sParameters & $sMessage, $iResult), 0, $iResult)
EndFunc   ;==>_WD_GetFreePort

Func __WinAPI_GetTcpTable()
	;funkey 2012.12.14
	;https://www.autoitscript.com/forum/topic/146671-getextendedtcptable-get-netstat-information/?tab=comments#comment-1038649
	Local Const $aConnState[12] = ["CLOSED", "LISTENING", "SYN_SENT", "SYN_RCVD", "ESTABLISHED", "FIN_WAIT1", _
			"FIN_WAIT2", "CLOSE_WAIT", "CLOSING", "LAST_ACK", "TIME_WAIT", "DELETE_TCB"]

	Local $tMIB_TCPTABLE = DllStructCreate("dword[6]")
	Local $aRet = DllCall("Iphlpapi.dll", "DWORD", "GetTcpTable", "struct*", $tMIB_TCPTABLE, "DWORD*", 0, "BOOL", True)
	Local $dwSize = $aRet[2]
	$tMIB_TCPTABLE = DllStructCreate("DWORD[" & $dwSize / 4 & "]")

	$aRet = DllCall("Iphlpapi.dll", "DWORD", "GetTcpTable", "struct*", $tMIB_TCPTABLE, "DWORD*", $dwSize, "BOOL", True)
	If $aRet[0] <> 0 Then Return SetError(1)
	Local $iNumEntries = DllStructGetData($tMIB_TCPTABLE, 1, 1)
	Local $aRes[$iNumEntries][6]

	For $i = 0 To $iNumEntries - 1
		$aRes[$i][0] = DllStructGetData($tMIB_TCPTABLE, 1, 2 + $i * 5 + 0)
		$aRes[$i][1] = $aConnState[$aRes[$i][0] - 1]
		$aRet = DllCall("ws2_32.dll", "str", "inet_ntoa", "uint", DllStructGetData($tMIB_TCPTABLE, 1, 2 + $i * 5 + 1)) ; local IP / translate
		$aRes[$i][2] = $aRet[0]
		$aRet = DllCall("ws2_32.dll", "ushort", "ntohs", "uint", DllStructGetData($tMIB_TCPTABLE, 1, 2 + $i * 5 + 2)) ; local port / translate
		$aRes[$i][3] = $aRet[0]
		$aRet = DllCall("ws2_32.dll", "str", "inet_ntoa", "uint", DllStructGetData($tMIB_TCPTABLE, 1, 2 + $i * 5 + 3)) ; remote IP / translate
		$aRes[$i][4] = $aRet[0]
		If $aRes[$i][0] <= 2 Then
			$aRes[$i][5] = 0
		Else
			$aRet = DllCall("ws2_32.dll", "ushort", "ntohs", "uint", DllStructGetData($tMIB_TCPTABLE, 1, 2 + $i * 5 + 4)) ; remote port / translate
			$aRes[$i][5] = $aRet[0]
		EndIf
	Next

	Return $aRes
EndFunc   ;==>__WinAPI_GetTcpTable

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_JsonActionPointer
; Description ...: Formats pointer "action" strings for use in _WD_Action
; Syntax ........: _WD_JsonActionPointer($sType[, $iButton = Default[, $sOrigin = Default[, $iXOffset = Default[, $iYOffset = Default[,
;                  $iDuration = Default]]]]])
; Parameters ....: $sType     - Type of action (Ex: pointerDown, pointerUp, pointerMove)
;                  $iButton   - [optional] Mouse button to simulate. Default is $_WD_BUTTON_Left.
;                  $sOrigin   - [optional] Starting location. ('pointer', 'viewport', or Element ID). Default is 'viewport'.
;                  $iXOffset  - [optional] X offset. Default is 0.
;                  $iYOffset  - [optional] Y offset. Default is 0.
;                  $iDuration - [optional] Duration in ticks. Default is 100.
; Return values .: Requested JSON string
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_Action
; Link ..........: https://www.w3.org/TR/webdriver/#actions
; Example .......: No
; ===============================================================================================================================
Func _WD_JsonActionPointer($sType, $iButton = Default, $sOrigin = Default, $iXOffset = Default, $iYOffset = Default, $iDuration = Default)
	Local Const $sFuncName = "_WD_JsonActionPointer"

	If $iButton = Default Then $iButton = $_WD_BUTTON_Left
	If $sOrigin = Default Then $sOrigin = 'viewport'
	If $iXOffset = Default Then $iXOffset = 0
	If $iYOffset = Default Then $iYOffset = 0
	If $iDuration = Default Then $iDuration = 100

	Local $vData = Json_ObjCreate()
	Json_Put($vData, '.type', $sType)

	Switch $sType
		Case 'pointerDown', 'pointerUp'
			Json_Put($vData, '.button', $iButton)

		Case 'pointerMove'
			Json_Put($vData, '.duration', $iDuration)

			Switch $sOrigin
				Case 'viewport', 'pointer'
					Json_Put($vData, '.origin', $sOrigin)
				Case Else
					Json_Put($vData, '.origin.ELEMENT', $sOrigin)
					Json_Put($vData, '.origin.' & $_WD_ELEMENT_ID, $sOrigin)
			EndSwitch

			Json_Put($vData, '.x', $iXOffset)
			Json_Put($vData, '.y', $iYOffset)
	EndSwitch

	Local $sJSON = Json_Encode($vData)
	Return SetError(__WD_Error($sFuncName, 0, $sJSON), 0, $sJSON)
EndFunc   ;==>_WD_JsonActionPointer

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_JsonActionPause
; Description ...: Formats pause "action" strings for use in _WD_Action
; Syntax ........: _WD_JsonActionPause($iDuration)
; Parameters ....: $iDuration - length of time to pause in ticks
; Return values .: Requested JSON string
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......: _WD_Action
; Link ..........: https://www.w3.org/TR/webdriver/#actions, https://www.w3.org/TR/webdriver/#ticks
; Example .......: No
; ===============================================================================================================================
Func _WD_JsonActionPause($iDuration)
	Local Const $sFuncName = "_WD_JsonActionPause"

	Local $vData = Json_ObjCreate()
	Json_Put($vData, '.type', 'pause')
	Json_Put($vData, '.duration', $iDuration)

	Local $sJSON = Json_Encode($vData)
	Return SetError(__WD_Error($sFuncName, 0, $sJSON), 0, $sJSON)
EndFunc   ;==>_WD_JsonActionPause

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_JsonCookie
; Description ...: Formats "cookie" strings for use in _WD_Cookies
; Syntax ........: _WD_JsonCookie($sName, $sValue[, $sPath = Default[, $sDomain = Default[, $bSecure = Default [,
;                  $bHTTPOnly = Default[, $iExpiryTime = Default[, $sSameSite = Default]]]]]])
; Parameters ....: $sName               - The name of the cookie.
;                  $sValue              - The cookie value.
;                  $sPath               - [optional] This defines the cookie path.
;                  $sDomain             - [optional] This defines the domain the cookie is visible to.
;                  $bSecure             - [optional] This defines whether the cookie is a secure cookie.
;                  $bHTTPOnly           - [optional] This defines whether the cookie is an HTTP only cookie.
;                  $iExpiryTime         - [optional] This defines when the cookie expires, specified in seconds since Unix Epoch.
;                  $sSameSite           - [optional] This defines whether the cookie applies to a SameSite policy. One of the following modes can be used:
;                  |None
;                  |Lax
;                  |Strict
; Return values .: Cookie as formatted JSON strings
; Author ........: mLipok
; Modified ......:
; Remarks .......:
; Related .......: _WD_Cookies
; Link ..........: https://www.w3.org/TR/webdriver/#dfn-table-for-cookie-conversion
; Example .......: No
; ===============================================================================================================================
Func _WD_JsonCookie($sName, $sValue, $sPath = Default, $sDomain = Default, $bSecure = Default, $bHTTPOnly = Default, $iExpiryTime = Default, $sSameSite = Default)
	Local Const $sFuncName = "_WD_JsonCookie"

	; Create JSON
	Local $vData = Json_ObjCreate()
	Json_Put($vData, '.cookie.name', $sName)
	Json_Put($vData, '.cookie.value', $sValue)
	If $sPath <> Default Then Json_Put($vData, '.cookie.path', $sPath)
	If $sDomain <> Default Then Json_Put($vData, '.cookie.domain', $sDomain)
	If $bSecure <> Default Then Json_Put($vData, '.cookie.secure', $bSecure)
	If $bHTTPOnly <> Default Then Json_Put($vData, '.cookie.httponly', $bHTTPOnly)
	If $iExpiryTime <> Default Then Json_Put($vData, '.cookie.expiry', $iExpiryTime)
	If $sSameSite <> Default Then Json_Put($vData, '.cookie.sameSite', $sSameSite)

	Local $sJSON = Json_Encode($vData)
	Return SetError(__WD_Error($sFuncName, 0, $sJSON), 0, $sJSON)
EndFunc   ;==>_WD_JsonCookie

; #INTERNAL_USE_ONLY# ====================================================================================================================
; Name ..........: __WD_Base64Decode
; Description ...: Decodes Base64 strings into binary.
; Syntax ........: __WD_Base64Decode($input_string)
; Parameters ....: $input_string - string to be decoded
; Return values .: Decoded string
; Author ........: trancexx
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........: https://www.autoitscript.com/forum/topic/81332-_base64encode-_base64decode/
; Example .......: No
; ===============================================================================================================================
Func __WD_Base64Decode($input_string)

	Local $struct = DllStructCreate("int")

	Local $a_Call = DllCall("Crypt32.dll", "int", "CryptStringToBinary", _
			"str", $input_string, _
			"int", 0, _
			"int", 1, _
			"ptr", 0, _
			"ptr", DllStructGetPtr($struct, 1), _
			"ptr", 0, _
			"ptr", 0)

	If @error Or Not $a_Call[0] Then
		Return SetError(1, 0, "") ; error calculating the length of the buffer needed
	EndIf

	Local $a = DllStructCreate("byte[" & DllStructGetData($struct, 1) & "]")

	$a_Call = DllCall("Crypt32.dll", "int", "CryptStringToBinary", _
			"str", $input_string, _
			"int", 0, _
			"int", 1, _
			"ptr", DllStructGetPtr($a), _
			"ptr", DllStructGetPtr($struct, 1), _
			"ptr", 0, _
			"ptr", 0)

	If @error Or Not $a_Call[0] Then
		Return SetError(2, 0, "") ; error decoding
	EndIf

	Return DllStructGetData($a, 1)

EndFunc   ;==>__WD_Base64Decode

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __WD_ErrHnd
; Description ...: COM Error handler
; Syntax ........: __WD_ErrHnd($oError)
; Parameters ....: $oError              - Error object.
; Return values .: None
; Author ........: mLipok
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func __WD_ErrHnd($oError)
	__WD_Error($oError.source, $_WD_ERROR_GeneralError, " err.number: " & $oError.number & " err.windescription: " & $oError.windescription & " err.description is: " & $oError.description, $oError.scriptline)
EndFunc   ;==>__WD_ErrHnd

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __WD_JsonElement
; Description ...: Convert Element ID into JSON string
; Syntax ........: __WD_JsonElement($sElement)
; Parameters ....: $sElement - Element ID from _WD_FindElement
; Return values .: Formatted JSON string
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func __WD_JsonElement($sElement)
	Return '{"' & $_WD_ELEMENT_ID & '":"' & $sElement & '"}'
EndFunc   ;==>__WD_JsonElement

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __WD_GetLatestWebdriverInfo
; Description ...: Generates URL for downloading latest matching webdriver version
; Syntax ........: __WD_GetLatestWebdriverInfo($aBrowser, $sBrowserVersion, $bFlag64)
; Parameters ....: $aBrowser        - Row extracted from $_WD_SupportedBrowsers.
;                  $sBrowserVersion - Current browser version.
;                  $bFlag64         - Install 64bit version?
; Return values .: Success - Array containing [0] URL for downloading requested webdriver & [1] matching webdriver version
;                  Failure - Empty array and sets @error to $_WD_ERROR_GeneralError
; Author ........: Danp2
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func __WD_GetLatestWebdriverInfo($aBrowser, $sBrowserVersion, $bFlag64)
	Local Const $sFuncName = "__WD_GetLatestWebdriverInfo"
	Local $iStartPos, $iConversion, $iErr = $_WD_ERROR_Success, $iExt = 0
	Local $aInfo[2] = ["", ""]
	Local $sURL = $aBrowser[0][$_WD_BROWSER_LatestReleaseURL]
	Local $sRegex = $aBrowser[0][$_WD_BROWSER_LatestReleaseRegex]
	Local $sNewURL = $aBrowser[0][$_WD_BROWSER_NewDriverURL]
	#forceref $sBrowserVersion, $bFlag64

	If StringRegExp($sURL, '["'']') Then
		$sURL = Execute($sURL)
	EndIf

	Local $sDriverLatest = InetRead($sURL, $INET_FORCERELOAD)

	If @error = $_WD_ERROR_Success Then
		Select
			Case BinaryMid($sDriverLatest, 1, 4) = '0x0000FEFF'                   ; UTF-32 BE
				$iStartPos = 5
				$iConversion = $SB_UTF16LE
			Case BinaryMid($sDriverLatest, 1, 4) = '0xFFFE0000'                   ; UTF-32 LE
				$iStartPos = 5
				$iConversion = $SB_UTF16LE
			Case BinaryMid($sDriverLatest, 1, 2) = '0xFEFF'                       ; UTF-16 BE
				$iStartPos = 3
				$iConversion = $SB_UTF16BE
			Case BinaryMid($sDriverLatest, 1, 2) = '0xFFFE'                       ; UTF-16 LE
				$iStartPos = 3
				$iConversion = $SB_UTF16LE
			Case BinaryMid($sDriverLatest, 1, 3) = '0xEFBBBF'                     ; UTF-8
				$iStartPos = 4
				$iConversion = $SB_UTF8
			Case Else
				$iStartPos = 1
				$iConversion = $SB_ANSI
		EndSelect

		$sDriverLatest = StringStripWS(BinaryToString(BinaryMid($sDriverLatest, $iStartPos), $iConversion), $STR_STRIPTRAILING)

		If StringLen($sRegex) Then
			; Incorporate major version number into regex
			$sRegex = StringFormat($sRegex, StringLeft($sBrowserVersion, StringInStr($sBrowserVersion, '.') - 1))
			Local $aResults = StringRegExp($sDriverLatest, $sRegex, $STR_REGEXPARRAYMATCH)

			If @error Then
				$iErr = $_WD_ERROR_GeneralError
				$iExt = 1
			Else
				$sDriverLatest = $aResults[0]
			EndIf
		EndIf

		If Not $iErr Then
			$aInfo[0] = Execute($sNewURL)
			$aInfo[1] = $sDriverLatest
		EndIf
	Else
		$iErr = $_WD_ERROR_GeneralError
		$iExt = 2
	EndIf

	Return SetError(__WD_Error($sFuncName, $iErr, Default, $iExt), $iExt, $aInfo)
EndFunc   ;==>__WD_GetLatestWebdriverInfo

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __WD_Make2Array
; Description ...: Parse string to array
; Syntax ........: __WD_Make2Array($s)
; Parameters ....: $s - String to be parsed
; Return values .: Generated array
; Author ........: jguinch
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........: https://www.autoitscript.com/forum/topic/179113-is-there-a-easy-way-to-parse-string-to-array
; Example .......: No
; ===============================================================================================================================
Func __WD_Make2Array($s)
	Local $aLines = StringRegExp($s, "(?<=[\[,])\s*\[(.*?)\]\s*[,\]]", 3), $iCountCols = 0
	For $i = 0 To UBound($aLines) - 1
		$aLines[$i] = StringRegExp($aLines[$i], "(?:^|,)\s*(?|'([^']*)'|""([^""]*)""|(.*?))(?=\s*(?:,|$))", 3)
		If UBound($aLines[$i]) > $iCountCols Then $iCountCols = UBound($aLines[$i])
	Next
	Local $aRet[UBound($aLines)][$iCountCols]
	For $y = 0 To UBound($aLines) - 1
		For $x = 0 To UBound($aLines[$y]) - 1
			$aRet[$y][$x] = ($aLines[$y])[$x]
		Next
	Next
	Return $aRet
EndFunc   ;==>__WD_Make2Array