QTP: Synchronization for AJAX Applications

Ajax has been extremely popular amongst developers these days and is being integrated with hundreds of websites. We have also been recently working on a light-weight Ajax application – and its not being very easy, especially when the time it takes for all elements to load is high. I have also read on several forums about automation developers facing issue with Ajax synchronization, and obviously its quite challenging.

There are several ways this class can be used to synchronize with Ajax applications. It can be used to synchronize with either changes in WebTables or with changes in the number of objects existing at any given time (WebElement, WebCheckBox etc). I wanted to record a video of this class in action, but because of other obligations, this has not been possible. However, below you will find a textual representation of how this class will work to synchronize Ajax applications:

ObjectSync: Set this to ensure that the object exists on the page before verifying if its Ajax elements have completed loading.

'Setting the object sync:
AjaxUtil.ObjectSync = 20

StatusSync: Set this to ensure that after the object exists, code waits for StatusSync max seconds for all elements to load. The class will keep on waiting until no changes in the application occur for StatusSync seconds.

'Setting the status sync:
AjaxUtil.StatusSync = 20

Loading Objects: You can load the objects initially (or right before you need to execute them). This makes it easy to call your objects anywhere in the script. Also, it is mandatory to add the objects where synchronization occurs (only the WebTables and the Browsers need to be added here, since that’s where all the action takes place). When you’re checking if any elements are added or removed from the page, you only have to add the Browser object in the collection and use AjaxUtil.SyncObjects to sync with the changes. However, if you’re synchronizing your script with (text) changes in the WebTable, and would like to see what text changed, you would have to add the WebTable in the collection also (AjaxUtil.SyncWebTable). But, please note that if the only concern is to check for changes in the WebTable, you can simply add the browser and check for changes in the WebElements.

' Page 1
AjaxUtil.AddObject "MyBrowser1", Browser("title:=Browser1")
' Page 2
AjaxUtil.AddObject "MyBrowser2", Browser("title:=Browser2")
' Synchronize with changes in WebTable text in Page 3
AjaxUtil.AddObject "MyTable1", Browser("title:=Browser3").WebTable("innertext:=WebTable")
' Synchronize with changes in another WebTable text in Page 4
AjaxUtil.AddObject "MyTable2", Browser("title:=Browser4").WebTable("innertext:=WebTable")

How To: Synchronize with changes in object count

AjaxUtil.SetBrowser "MyBrowser1"   ' Mandatory step; must set the browser each time.
AjaxUtil.SyncObjects               ' This step will synchronize with WebElements in "MyBrowser1"

'Synchronize with changes in Page 2
AjaxUtil.SetBrowser "MyBrowser2"
AjaxUtil.SyncObjects

How To: Synchronize changes in WebTable

'Synchronize with "MyTable1" in Page 3
AjaxUtil.SyncWebTable "MyTable1"
 
'Synchronize with "MyTable2" in Page 4
AjaxUtil.SyncWebTable "MyTable2"

Simple! Isn’t it?

Working example:

SystemUtil.Run "iexplore.exe", "www.kayak.com"
 
If Not Browser("title:=Cheap Flights.*").Exist(10) Then ExitTest
 
AjaxUtil.ObjectSync = 30  ' Check if the added objects exist before we synchronize
AjaxUtil.StatusSync = 10    ' Wait for a max 10 seconds if there is no status change

'Add Objects to collection (Its mandatory that you add objects to the class)
AjaxUtil.AddObject "MyBrowser1", Browser("title:=Cheap Flights.*")
AjaxUtil.AddObject "MyTable", Browser("title:=Kayak.com Search Results")_
    .WebTable("innertext:=.*of.*roundtrips shown.*|.*forget all.*", "index:=0")
AjaxUtil.AddObject "MyBrowser2", Browser("title:=Kayak.com Search Results")
 
Browser("title:=Cheap Flights.*").WebEdit("html id:=destination")_
    .Set "Atlanta, GA - Hartsfield-Jackson (ATL)"
 
' Its mandatory that you set a browser for which you will run the test for changes in elements
AjaxUtil.SetBrowser "MyBrowser1"        ' Required Step

' Synchronize for changes in objects:
AjaxUtil.SyncObjects "WebElement"
 
Browser("title:=Cheap Flights.*").WebButton("html id:=fdimgbutton").Click
 
'You can modify the ObjectSync and StatusSync times here for the next operations.
'Example: StatusSync is increased
AjaxUtil.StatusSync = 15       ' increase the StatusSync

'Synchronize with the flights table
AjaxUtil.SyncWebTable "MyTable"
 
'Test Complete - release dictionary
AjaxUtil.DestroyDict

Notice the results below. When StatusSync was 10, the changes in Page verified was 10 times. Similarly, in the case of the WebTable, the statusSync was 15, thus, the changes were verified 15 times over a course of 15 seconds.

How its done:

You can download the code or copy it to your clipboard using the code below:

Public oGlobalDict
 
'—————————————————————————————————————————————
''
' Class clsAjaxUtil
'
' Methods:
'    AddObject
'    DestroyDict
'    RemoveObject
'    SetBrowser
'    SyncObjects *
'    SyncWebTable *
'
' Author: Anshoo Arora
'
' Version: v1.0
''
'—————————————————————————————————————————————
Class clsAjaxSync
'—————————————————————————————————————————————
 
    Public StatusSync       ' Synchronization with changes in application
    Public ObjectSync       ' To synchronize while the object loads  
    Public oBrowser         ' Reference to the target browser: SetBrowser "MyBrowser"
    Public oTable           ' Reference to the target table: SyncWebTable "MyTable"
    Public intVisCnt
 
    Private sClass          ' Class used in SyncObjects
    Private intObjectCount  ' Changes in Object Counts in SyncObjects
    Private oLocalDict      ' Dictionary

 
    '—————————————————————————————————————————
    ' Name: Class_Initialize (Private)
    ' 
    ' Purpose: Scripting.Dictionary Singleton
    ' 
    ' Parameters: 
    ' 
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Private Sub Class_Initialize
    '—————————————————————————————————————————
        Dim bInit 'As Boolean
    
        bInit = False
 
        'Singleton Pattern (see references in remarks above)
        If IsObject(oGlobalDict) = True Then
            If Not oGlobalDict is Nothing Then
                bInit = True
            End If
        End If
 
        'Only create new object if needed
        If bInit = False Then Set oGlobalDict = CreateObject( "Scripting.Dictionary" )
 
        'Set the local class excel reference to the global Singleton object
        Set oLocalDict = oGlobalDict
    End Sub 
 
    '—————————————————————————————————————————
    ' Name: AddObject (Public)
    ' 
    ' Purpose: Add object to collection
    ' 
    ' Parameters:
    '    sObjectName: Name of the object stored as refereces
    '    oObject: Reference of the object
    ' 
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Public Sub AddObject( sObjectName, ByRef oObject )
    '—————————————————————————————————————————
        If oLocalDict.Exists(sObjectName) Then
            RemoveObject sObjectName
        End If
 
        oLocalDict.Add sObjectName, oObject    
    End Sub 
 
    '—————————————————————————————————————————
    ' Name: SyncWebTable (Public)
    ' 
    ' Purpose: Synchronize the table for StatusSync seconds to make sure no text has changed.
    ' 
    ' Parameters:
    ' 
    ' Author: 
    ' 
    ' Date: 
    '
    ' Remarks:
    '    Uses recursion
    '—————————————————————————————————————————
    Public Sub SyncWebTable( sTableName )
    '—————————————————————————————————————————
        If IsObject(Me.oTable) Then Set Me.oTable = Nothing
        Set Me.oTable = oLocalDict.Item( sTableName )
 
        If ObjectSync = "" Then ObjectSync = 1
 
        ' Check if the Table exists
        If oTable.Exist(ObjectSync) Then
            ' Make sure the table is visible
            If oTable.GetROProperty( "x" ) <> 0 Then
                ' Sync for changes in Table
                SyncText
            Else
                Reporter.ReportEvent micWarning, sTableName, "Object not visible."
                Me.intVisCnt = Me.intVisCnt + 1
                ' Maybe the table is still loading its elements, use recursion and check again.
                If Not Me.intVisCnt >= 2 Then SyncWebTable sTableName
            End If
        Else
            Reporter.ReportEvent micWarning, sTableName, "Object not found."
        End If
    End Sub
 
    '—————————————————————————————————————————
    ' Name: SyncText
    ' 
    ' Purpose: Verify if the table text has changed. If the text
    ' changes, the original text string is reset. The loop runs again until it 
    ' reaches its max: StatusSync.
    ' 
    ' Parameters:
    ' 
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Private Sub SyncText
    '—————————————————————————————————————————
        Dim intWait, sNewText, sReportChanges
        Dim sDivider: sDivider = "================================"
        Dim sRefText: sRefText = GetTableText
 
        If StatusSync = 0 Then StatusSync = 1
 
        Do
            ' Get the updated innerText from the Table
            sNewText = GetTableText
 
            sReportChanges = sRefText &vbLf&sDivider& sNewText &vbLf&sDivider& sReportChanges
 
            ' Verify if there were changes
            If sRefText <> sNewText Then
                intWait = 0
                sRefText = sNewText
            Else
                intWait = intWait + 1
            End If
            Wait(1)
        Loop Until intWait = StatusSync
 
        Reporter.ReportEvent micInfo, "WebTable Sync", sReportChanges
    End Sub 
 
    '—————————————————————————————————————————
    ' Name:SyncObjects (Public)
    ' 
    ' Purpose: Checks whether the object count has changed.
    ' 
    ' Parameters:
    '  
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Public Sub SyncObjects( ClassName )
    '—————————————————————————————————————————
        Dim intWait, intNewCount, sReportChanges
        Dim sDivider: sDivider = "================================"
 
        sClass = ClassName
        Dim intObjectCount: intObjectCount = CountObjects
 
        If StatusSync = 0 Then StatusSync = 1
 
        Do 
            ' Get the current number of objects count for ClassName
            intNewCount = CountObjects
 
            sReportChanges = "Old: " & intObjectCount &vbLf& "New: " &_
                            intNewCount &vbLf&sDivider&vbLf& sReportChanges &vbLf
 
            ' Verify if there were any changes in the count for ClassName
            If intObjectCount <> intNewCount Then
                intWait = 0
                intObjectCount = intNewCount
            Else
                intWait = intWait + 1
            End If
            Wait(1)
        Loop Until intWait = StatusSync
 
        Reporter.ReportEvent micInfo, "Sync Objects", sReportChanges
    End Sub    
 
    '—————————————————————————————————————————
    ' Name: SetBrowser (Public)
    ' 
    ' Purpose: Set the browser in which the objects count is being searched for
    ' 
    ' Parameters:
    ' 
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Public Sub SetBrowser( sBrowserName )
    '—————————————————————————————————————————
        Set Me.oBrowser = oLocalDict.Item( sBrowserName )
    End Sub    
 
    '—————————————————————————————————————————
    ' Name: RemoveObject (Public)
    ' 
    ' Purpose: Remove object from collection
    ' 
    ' Parameters:
    ' 
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Public Sub RemoveObject( sObjectName )
    '—————————————————————————————————————————
        If oLocalDict.Exists(sObjectName) Then
            oLocalDict.Remove(sObjectName)
        End If
    End Sub
 
    '—————————————————————————————————————————
    ' Name: DestroyDict (Public)
    ' 
    ' Purpose: Releases Dictionary
    ' 
    ' Parameters:
    ' 
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Public Sub DestroyDict
    '—————————————————————————————————————————
        oLocalDict.RemoveAll
        Set oLocalDict = Nothing
        Set oGlobalDict = Nothing
    End Sub
 
    '—————————————————————————————————————————
    ' Name: CountObjects (Private)
    ' 
    ' Purpose: Internal Function to retrieve visible object count
    ' 
    ' Parameters: 
    ' 
    ' Return:
    '    Integer
    '   
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Private Function CountObjects
    '—————————————————————————————————————————
        Dim pDesc: Set pDesc = Description.Create
        pDesc( "micclass" ).Value = sClass
 
        '* Get all objects with micClass = sClass
        CountObjects = oBrowser.Page("micclass:=Page").ChildObjects(pDesc).Count
 
        '* Get only visible objects
        pDesc( "x" ).Value = 0
        CountObjects = CountObjects - oBrowser.Page("micclass:=Page").ChildObjects(pDesc).Count
    End Function
 
    '—————————————————————————————————————————
    ' Name: GetTableText (Private)
    ' 
    ' Purpose: Retrieves the innertext of the table
    ' 
    ' Parameters:
    ' 
    ' Return:
    '    String
    '
    ' Author: 
    ' 
    ' Date: 
    '—————————————————————————————————————————
    Private Function GetTableText
    '—————————————————————————————————————————
        Reporter.Filter = rfDisableAll
        On Error Resume Next
            oTable.Init
            GetTableText = oTable.object.innerText
 
            If Err.Number <> 0 Then
                Err.Clear
            End If
        On Error Goto 0
        Reporter.Filter = rfEnableAll
    End Function
 
End Class
 
Set Ajax = New clsAjaxSync
 
Public Function AjaxUtil
    Set AjaxUtil = Ajax
End Function

Leave a Comment

Scroll to Top