Jump to content

WebDriver UDF (W3C compliant version) - 2024/09/21


Danp2
 Share

Recommended Posts

I've been using this with SalesForce and have a decent amount of luck with it.

 

I needed to make a couple changes to two functions so far

_WD_FrameEnter()  SalesForce has many nested Frames and I needed to remove the $_WD_ELEMENT_ID in order to access any frame.

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_FrameEnter
; Description ...: This will enter the specified frame for subsequent WebDriver operations.
; Syntax ........: _WD_FrameEnter($sIndexOrID)
; Parameters ....:
; Return values .: Success      - True
;                  Failure      - WD Response error message (E.g. "no such frame")
; Author ........: Decibel
; Modified ......: 2018-04-27
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_FrameEnter($sSession, $sIndexOrID)
    Local $sOption
    Local $sResponse, $sJSON
    Local $sValue

    ;*** Encapsulate the value if it's an integer, assuming that it's supposed to be an Index, not ID attrib value.
    If IsInt($sIndexOrID) = True Then
        $sOption = '{"id":' & $sIndexOrID & '}'
    Else
;~      $sOption = '{"id":{"' & $_WD_ELEMENT_ID & '":"' & $sIndexOrID & '"}}'   ;This won't work with Salesforce for some reason
        $sOption = '{"id":"' & $sIndexOrID & '"}'
    EndIf

    $sResponse = _WD_Window($sSession, "frame", $sOption)
    $sJSON = Json_Decode($sResponse)
    $sValue = Json_Get($sJSON, "[value]")

    ;*** Evaluate the response
    If $sValue <> Null Then
        $sValue = Json_Get($sJSON, "[value][error]")
    Else
        $sValue = True
    EndIf

    Return $sValue
EndFunc ;==>_WD_FrameEnter

 

 

Since SalesForce can take a long time to load some pages and controls, I'm using _WD_WaitElement a lot, so I updated it to return the Element(s) once it finally shows up.

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_WaitElement
; Description ...: Wait for a element to be found  in the current tab before returning
; Syntax ........: _WD_WaitElement($sSession, $sElement, $sStrategy, $sSelector[, $iDelay = 0[, $iTimeout = -1]])
; Parameters ....: $sSession            - Session ID from _WDCreateSession
;                  $sStrategy           - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector           - Value to find
;                  $sStartElement       - [optional] a string value. Default is "".
;                  $lMultiple           - [optional] True returns Array of Element ID's. Default is False.
;                  $iDelay              - [optional] Milliseconds to wait before checking status
;                  $iTimeout            - [optional] Period of time to wait before exiting function
; Return values .: Success      - Element ID(s) returned by web driver
;                  Failure      - 0 and sets the @error flag to non-zero
;                  @error       - $_WD_ERROR_Success
;                               - $_WD_ERROR_Timeout
; Author ........: Dan Pollak
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_WaitElement($sSession, $sStrategy, $sSelector, $sStartElement = "", $lMultiple = False, $iDelay = 0, $iTimeout = -1)
    Local Const $sFuncName = "_WD_WaitElement"
    Local $bAbort = False, $iErr, $iResult

    If $iTimeout = -1 Then $iTimeout = $_WD_DefaultTimeout

    Sleep($iDelay)

    Local $hWaitTimer = TimerInit()

    While 1
        $FindRet = _WD_FindElement($sSession, $sStrategy, $sSelector, $sStartElement, $lMultiple)
        $iErr = @error

        If $iErr = $_WD_ERROR_Success Then
            $iResult = 1
            ExitLoop

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

        Sleep(1000)
    WEnd

    Return SetError(__WD_Error($sFuncName, $iErr), $iResult, $FindRet)
EndFunc

 

Link to comment
Share on other sites

@BigDaddyO Based on my limited testing, I believe the issue lies with Chromedriver not conforming to the W3C specs. Firefox works fine with the UDF as-is, but fails when I implement your change. Chrome failed for me either way, returning

__WD_Post: StatusCode=500; ResponseText={"value":{"error":"unknown error","message":"missing 'ELEMENT'(Session infochrome=67.0.3396.99)","stacktrace":"Backtrace:\n\tOrdinal0 [0x00C0DF70+778096]\n\tOrdinal0 [0x00BBB42D+439341]\n\tOrdinal0 [0x00B9807F+295039]\n\tOrdinal0 [0x00B80619+198169]\n\tOrdinal0 [0x00B78349+164681]\n\tOrdinal0 [0x00B7F96B+194923]\n\tOrdinal0 [0x00B7824F+164431]\n\tOrdinal0 [0x00B61B45+72517]\n\tOrdinal0 [0x00B62F2A+77610]\n\tOrdinal0 [0x00B62ECC+77516]\n\tGetHandleVerifier [0x00CA9936+3478]\n\tOrdinal0 [0x00C188C3+821443]\n\tOrdinal0 [0x00BC7066+487526]\n\tOrdinal0 [0x00BC7393+488339]\n\tOrdinal0 [0x00BC74A3+488611]\n\tOrdinal0 [0x00C1AA67+830055]\n\tOrdinal0 [0x00BC6DAF+486831]\n\tOrdinal0 [0x00BD13FE+529406]\n\tOrdinal0 [0x00BDC57B+574843]\n\tOrdinal0 [0x00BDC6CD+575181]\n\tOrdinal0 [0x00BDB92B+571691]\n\tBaseThreadInitThunk [0x75FA8484+36]\n\tRtlValidSecurityDescriptor [0x77362FEA+282]\n\tRtlValidSecurityDescriptor [0x77362FBA+234]\n"}}

in one case and

__WD_Post: StatusCode=404; ResponseText={"value":{"error":"no such frame","message":"(Session infochrome=67.0.3396.99)","stacktrace":"Backtrace:\n\tOrdinal0 [0x00C0DF70+778096]\n\tOrdinal0 [0x00BBB42D+439341]\n\tOrdinal0 [0x00B97E1D+294429]\n\tOrdinal0 [0x00B9A2C1+303809]\n\tOrdinal0 [0x00B80787+198535]\n\tOrdinal0 [0x00B78349+164681]\n\tOrdinal0 [0x00B7F96B+194923]\n\tOrdinal0 [0x00B7824F+164431]\n\tOrdinal0 [0x00B61B45+72517]\n\tOrdinal0 [0x00B62F2A+77610]\n\tOrdinal0 [0x00B62ECC+77516]\n\tGetHandleVerifier [0x00CA9936+3478]\n\tOrdinal0 [0x00C188C3+821443]\n\tOrdinal0 [0x00BC7066+487526]\n\tOrdinal0 [0x00BC7393+488339]\n\tOrdinal0 [0x00BC74A3+488611]\n\tOrdinal0 [0x00C1AA67+830055]\n\tOrdinal0 [0x00BC6DAF+486831]\n\tOrdinal0 [0x00BD13FE+529406]\n\tOrdinal0 [0x00BDC57B+574843]\n\tOrdinal0 [0x00BDC6CD+575181]\n\tOrdinal0 [0x00BDB92B+571691]\n\tBaseThreadInitThunk [0x75FA8484+36]\n\tRtlValidSecurityDescriptor [0x77362FEA+282]\n\tRtlValidSecurityDescriptor [0x77362FBA+234]\n"}}

in the other.

Edit: Seems they are already aware of the issue.

Edited by Danp2
Link to comment
Share on other sites

@BigDaddyO Regarding the enhancements to _WD_WaitElement, I understand the addition of the optional parameters to take full advantage of _WD_FindElement.

Not sure I see the real benefit of returning the found elements. Please elaborate on how you are using this and why you this change is more desirable than making another call to _WD_FindElement.

Link to comment
Share on other sites

8 hours ago, Danp2 said:

@BigDaddyO Regarding the enhancements to _WD_WaitElement, I understand the addition of the optional parameters to take full advantage of _WD_FindElement.

Not sure I see the real benefit of returning the found elements. Please elaborate on how you are using this and why you this change is more desirable than making another call to _WD_FindElement.

I guess because I'm lazy and don't want to write another line when the _WD_WaitElement just performed the _WD_FindElement command. 

Granted, the extra two options "", True in the function are a little annoying, perhaps putting those to the end since someone would most likely use the Delay and Wait options more often.

Link to comment
Share on other sites

  • 3 weeks later...

@Danp2 I created a _WD_WaitElementVisible() function based off your _WD_WaitElement() function.

For most elements I don't need this, but I came across a popup that seems to get created and is hidden in the background after logging into the website so the _WD_WaitElement() returned right away.  I needed to create this so it will wait for the element to exist AND be visible.  I included the link to where I found the code which is apparently what the JQuery code .is(':visible') uses.

OLD  updated function is 2 posts below!

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_WaitElementVisible
; Description ...: Wait for a element to be found in the current tab and that its visible before returning
; Syntax ........: _WD_WaitElementVisible($sSession, $sElement, $sStrategy, $sSelector[, $iDelay = 0[, $iTimeout = -1]])
; Parameters ....: $sSession            - Session ID from _WDCreateSession
;                  $sStrategy           - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector           - Value to find
;                  $iDelay              - [optional] Milliseconds to wait before checking status
;                  $iTimeout            - [optional] Period of time to wait before exiting function
; Return values .: Success      - 1
;                  Failure      - 0 and sets the @error flag to non-zero
;                  @error       - $_WD_ERROR_Success
;                               - $_WD_ERROR_Timeout
; Author ........: Dan Pollak, BigDaddyO added visible
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........: https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom/28933648
; Example .......: No
; ===============================================================================================================================
Func _WD_WaitElementVisible($sSession, $sStrategy, $sSelector, $iDelay = 0, $iTimeout = -1)
    Local Const $sFuncName = "_WD_WaitElement"
    Local $bAbort = False, $iErr, $iResult

    If $iTimeout = -1 Then $iTimeout = $_WD_DefaultTimeout

    Sleep($iDelay)

    Local $hWaitTimer = TimerInit()

    While 1
        $FindRet = _WD_FindElement($sSession, $sStrategy, $sSelector)
        $iErr = @error

        If $iErr = $_WD_ERROR_Success Then
            Local $sJsonElement = '{"' & $_WD_ELEMENT_ID & '":"' & $FindRet & '"}'
            $sResponse = _WD_ExecuteScript($sSession, "return arguments[0].offsetWidth || arguments[0].offsetHeight || arguments[0].getClientRects().length", $sJsonElement)
            ;Not visible returns {"value":0}
            ;Visible returns a # for example {"value":115}
            $sJSON = Json_Decode($sResponse)            ;Use Json_Decode to pull just the value from the response
            $isVisible = Json_Get($sJSON, "[value]")
            If $isVisible > 0 Then
                $iResult = 1
                ExitLoop
            EndIf

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

        Sleep(1000)
    WEnd

    Return SetError(__WD_Error($sFuncName, $iErr), $iResult)
EndFunc

 

Edited by BigDaddyO
Danp2 found a better option
Link to comment
Share on other sites

3 hours ago, Danp2 said:

@BigDaddyO I haven't tested this, but the w3c specs show that you can check the element's visibility like this --

/session/{session id}/element/{element id}/displayed

 

 

That worked! at least in Chrome.  I did have to update the _WD_ElementAction() Function to include 'displayed' as a valid command

    Switch $sCommand
        Case 'name', 'rect', 'text', 'selected', 'enabled', 'displayed'
 

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_WaitElementVisible
; Description ...: Wait for a element to be found in the current tab and that its visible before returning
; Syntax ........: _WD_WaitElementVisible($sSession, $sElement, $sStrategy, $sSelector[, $iDelay = 0[, $iTimeout = -1]])
; Parameters ....: $sSession            - Session ID from _WDCreateSession
;                  $sStrategy           - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector           - Value to find
;                  $iDelay              - [optional] Milliseconds to wait before checking status
;                  $iTimeout            - [optional] Period of time to wait before exiting function
; Return values .: Success      - 1
;                  Failure      - 0 and sets the @error flag to non-zero
;                  @error       - $_WD_ERROR_Success
;                               - $_WD_ERROR_Timeout
; Author ........: Dan Pollak
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_WaitElementVisible($sSession, $sStrategy, $sSelector, $iDelay = 0, $iTimeout = -1)
    Local Const $sFuncName = "_WD_WaitElementVisible"
    Local $bAbort = False, $iErr, $iResult, $isVisible

    If $iTimeout = -1 Then $iTimeout = $_WD_DefaultTimeout

    Sleep($iDelay)

    Local $hWaitTimer = TimerInit()

    While 1
        $FindRet = _WD_FindElement($sSession, $sStrategy, $sSelector)
        $iErr = @error

        If $iErr = $_WD_ERROR_Success Then
            $isVisible = _WD_ElementAction($sSession, $FindRet, 'displayed')
            if $isVisible = True Then
                $iResult = 1
                ExitLoop
            EndIf

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

        Sleep(1000)
    WEnd

    Return SetError(__WD_Error($sFuncName, $iErr), $iResult)
EndFunc

 

Edited by BigDaddyO
change needed to _WD_ElementAction()
Link to comment
Share on other sites

Yeah, that would make sense.

I'm not sure yet if it should default to IsVisible or not.  I'm still using my updated _WD_WaitElement that returns an elementID but it gets errors occasionally about stale elements so I have been meaning to change my code back to your Standard, it's just going to be a lot of work.

 

Also, It seems that the function can use = True instead of = "true" so I'm going to update the function in the above post

Link to comment
Share on other sites

Here's the revised _WD_WaitElement with the optional visibility check. Seems to work well in my testing. Let me know if you encounter any issues.

; #FUNCTION# ====================================================================================================================
; Name ..........: _WD_WaitElement
; Description ...: Wait for a element to be found  in the current tab before returning
; Syntax ........: _WD_WaitElement($sSession, $sStrategy, $sSelector[, $iDelay = 0[, $iTimeout = -1[, $lVisible = False]]])
; Parameters ....: $sSession            - Session ID from _WDCreateSession
;                  $sStrategy           - Locator strategy. See defined constant $_WD_LOCATOR_* for allowed values
;                  $sSelector           - Value to find
;                  $iDelay              - [optional] Milliseconds to wait before checking status
;                  $iTimeout            - [optional] Period of time to wait before exiting function
;                  $lVisible            - [optional] Check visibility of element?
; Return values .: Success      - 1
;                  Failure      - 0 and sets the @error flag to non-zero
;                  @error       - $_WD_ERROR_Success
;                               - $_WD_ERROR_Timeout
; Author ........: Dan Pollak
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _WD_WaitElement($sSession, $sStrategy, $sSelector, $iDelay = 0, $iTimeout = -1, $lVisible = False)
    Local Const $sFuncName = "_WD_WaitElement"
    Local $bAbort = False, $iErr, $iResult, $sElement, $lIsVisible = True

    If $iTimeout = -1 Then $iTimeout = $_WD_DefaultTimeout

    Sleep($iDelay)

    Local $hWaitTimer = TimerInit()

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

        If $iErr = $_WD_ERROR_Success Then
            If $lVisible Then
                $lIsVisible = _WD_ElementAction($sSession, $sElement, 'displayed')
            EndIf

            If $lIsVisible = True Then
                $iResult = 1
                ExitLoop
            EndIf

        ElseIf $iErr <> $_WD_ERROR_NoMatch Then
            ExitLoop
        EndIf

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

        Sleep(1000)
    WEnd

    Return SetError(__WD_Error($sFuncName, $iErr), $iResult)
EndFunc

 

Link to comment
Share on other sites

Looks like the Return isn't setup properly in the _WD_WaitElement() 

It is returning the $iResult in @Extended.  I think it should be something like this:  Return SetError(__WD_Error($sFuncName, $iErr), 0, $iResult)

 

Also, in the Local line, should $iResult be preset to 0 to return 0 if it errors?

Edited by BigDaddyO
Link to comment
Share on other sites

  • Danp2 changed the title to WebDriver UDF (W3C compliant version) - 2024/09/21
  • Melba23 pinned this topic

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...