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