Author: AutomatedQA Corp.
Last updated: November 26, 2009
Applied to: TestComplete 7
Introduction
All recent versions of web browsers allow opening web pages in separate tabs. In general, there can be several browser instances launched, each having several tabs opened. So, you have to keep this in mind when web testing with TestComplete.
This article describes how the page tabs are implemented in most popular browsers (Internet Explorer 7-8, Firefox 2-3), and provides code snippets that demonstrate how to perform the following typical tab-related tasks:
- Open new tab.
- Close the specified tab.
- Activate the tab that contains the desired web page.
- List pages opened in all tabs of all browser copies.
- Search for a specified URL in any of the opened browsers.
Understanding Tabbed Browsers for Web Testing
All browsers implement the “tab” functionality differently based on the manufacturer. The following sub sections below describe how each browser implements this functionality:
Mozilla Firefox ver. 2 and later.
Mozilla browsers always have one instance of the firefox.exe process running. When you launch another copy of a browser, another executable is not started. Instead, Mozilla creates a new instance of the MozillaUIWindowClass window that contains the UI elements of a browser’s main window and hosts the pages opened in that copy of a browser.
In TestComplete the pages opened in Mozilla Firefox are represented as child Page objects of the firefox.exe process. And the browser’s UI elements are represented by UIPage objects. These objects reside as grandchildren of the MozillaUIWindowClass window. When the “Make Page object a child of the browser process” project property is enabled, the Page and UIPage objects also become available as immediate children of the firefox process.
For example, suppose we have launched two copies of Firefox and opened 2 tabs in the first one and 3 tabs in the second one. Exploring the object hierarchy in TestComplete’s Object Browser we will see one instance of firefox.exe that hosts 5 Page objects that relate to pages opened in tabs and 2 UIPage objects that relate to the browser copy’s main windows.

Internet Explorer ver. 7
This version of Internet Explorer creates a separate iexplore.exe process instance for each of the launched copies, and each copy hosts its own set of opened pages. The browser’s main window is represented as IEFrame object and contains the Page objects that correspond to opened tabs. When the “Make Page object a child of the browser process” project property is enabled, the Page objects become available as immediate children of the iexplore process.
Getting back to the same example, if you launch two copies of IE7 and open 2 tabs in the first one and 3 tabs in the second one, then there will be two instances of iexplore.exe which have, 2 and 3 Page objects, and two instances of the IEFrame object.

Internet Explorer ver. 8
This browser has the most peculiar behavior of all. The reason for this behavior is Microsoft’s Loosely Coupled IE (LCIE) concept. According to it, each tab should have its own process so that the rest of the tabs and the browser’s main window would not be affected when a single tab-process crashes. So, upon start, IE8 launches two instances of iexplore.exe, the first one for the main application’s window, I will call this the master-process, and the second one for the opened tab, which I will call the tab-process. Opening of any subsequent tab typically launches a new instance of a tab-process.
The word “typically” is emphasized because the actual algorithm is more sophisticated and depends on a number of factors and parameters. Therefore, a tab-process may eventually hold more than one web page. The following blog contains more information about this: http://blogs.msdn.com/askie/archive/2009/03/09/opening-a-new-tab-may-launch-a-new-process-with-internet-explorer-8-0.aspx.
Another “tricky” aspect of the Loosely Coupled IE concept, is the manner the popup windows and dialogs are treated. Typically the popup windows and dialogs are created in the same instance of the tab-process that hosts the page which invoked the popup or dialog. However, if the popup or dialog affects the entire browser, for example, a warning shown in the Internet Explorer Information bar, then the corresponding object is created in the master-process.
TestComplete reflects the structure of IE8 processes in the following way:
The master-processes are represented as iexplore processes that contain the IEFrame object, which, in turn, contain the Page objects that correspond to all pages opened in the browser copy. When the “Make Page object a child of the browser process” project property is enabled, the Page objects become available as children of the master-process. The iexplore instances that correspond to tab-processes do not have the IEFrame object, but contain one or more Page objects as immediate children (no matter whether the “Make Page object a child of the browser process” property is enabled or not).
That is, the very same web page displayed in IE8 may have up to three duplicate Page objects that describe it – one belonging to a tab-process, and one or two (depending on “Make Page object a child of the browser process” project property) belonging to the master-process. These duplicates are equivalent and you can use whichever copy you like when you need to address the elements of the web page.
However, when you need to address the browser’s UI, dialogs, popup windows and other elements that do not belong to a web page, then you should refer to the appropriate iexplore process:
- To simulate actions over the browser’s main window, you should refer to the master-process.
- To simulate actions over the browser-related popup windows and dialogs (Information bar and the like), you also should refer to the master-process.
- To simulate actions over the page-related popup windows and dialogs, you should refer to the tab-process.
Returning to the same example, if you launch two copies of IE8 and open 2 tabs in the first one and 3 tabs in the second one, then there would be several (minimum 4, maximum 7) instances of iexplore.exe, two instances of IEFrame object and at least 10 instances of Page objects. Now, this may sound scary, but don’t be afraid, it’s just the LCIE from Microsoft.
Two instances of iexplore.exe are master-processes that contain IEFrame objects. The first master-process hosts 2 Page objects, whereas the second one hosts 3 Page objects. The other iexplore copies are tab-processes; they contain another 5 instances of the Page object.

Using Tabbed Browsers in Web Testing
Now that you understand how the tab functionality and the hierarchy of browsers, we can move on to the code examples for web testing. The code samples are in JScript, but this can be implemented in any other scripting language supported by TestComplete.
To store information about a tabbed page we will use a custom TabbedPage object, which will hold references to a web page, to the browser instance that hosts the web page (for IE 8 both the master-process and the tab-process) and some other properties and methods. In JScript custom objects are created by special constructor functions.
The TabbedPage object constructor accepts two parameters – browserInstance and pageObj. The first one is a reference to a browser instance and can be either a window object (for Firefoxes), or a process object (for both Internet Explorer versions). For IE8 the browserInstance parameter should correspond to the master-process. The second one is a reference to a web page, the Page object. The object constructor code is as follows:
01.// Constructor for a TabbedPage object
02.// TabbedPage object holds references to page and the browser in which it is opened
03.function TabbedPage(browserInstance, pageObj)
04.{
05. // Store reference to browser instance
06. // In Firefox the browser instance corresponds to MozillaUIWindowClass window
07. // In IE7 and IE8 the browser instance corresponds to iexplore process
08. // For IE8 browser this field stores master-process
09. this.browserInstance = browserInstance;
10. // Store reference to Page object
11. this.pageObj = pageObj;
12. // Calculate browser ID -
13. // a helper property that can be either "IE7", "IE8" or "FF"
14. if (aqString.Find(browserInstance.FullName, 'Process("firefox"', 0, false) !=-1)
15. this.browserID = "FF";
16. else
17. this.browserID = "IE"+browserInstance.FileVersionInfo.MajorPart;
18. // For IE8 browser calculate and store reference to corresponding tab-process
19. if (this.browserID=="IE8")
20. this.tabInstance = FindTabProcess(browserInstance, pageObj)
21. else
22. this.tabInstance = null;
23. // Assign object methods
24. // Display tab information
25. this.toString = TabbedPageToString;
26. // Navigate to a specified URL
27. this.ToUrl = TabbedPageToURL;
28. // Activate the tab
29. this.Activate = TabbedPageActivate;
30. // Close the tab
31. this.Close = TabbedPageClose;
32.}
Getting master-processes and tab-processes of Internet Explorer 8
To differentiate between master-processes and tab-processes, the following criteria can be used:
- The master-process has the IEFrame object as a child, whereas the tab-process does not.
- The command line of the master-process does not contain arguments other than the parameters specified by you.
- The command line of a tab-process always contains additional SCODEF and CREDAT arguments. For example, "C:\Program Files\Internet Explorer\iexplore.exe" SCODEF:2636 CREDAT:14348.
The four-digit number after the SCODEF argument in the tab-process’s command-line is the Process Identifier (PID) of the parent master-process.
The following IE developer blog contains more information about this: http://blogs.msdn.com/askie/archive/2009/03/20/how-to-i-determine-which-ie-tabs-go-to-which-iexplore-exe-process-when-using-internet-explorer-8.aspx
The FindMasterProcesses routine returns an array of master-processes. It iterates through all iexplore processes and verifies whether they contain the IEFrame window.
01.function FindMasterProcesses()
02.{
03.var p, MPArray;
04. MPArray = new Array();
05. for (var i = 0; i < Sys.ChildCount; i++)
06. {
07. p=Sys.Child(i);
08. // If the process is iexplore.exe
09. if (p.ProcessName=="iexplore")
10. {
11. // Search for IEFrame window
12. w=p.WaitChild("IEFrame*", 100);
13. if (w.Exists)
14. // If found append process reference to resulting array
15. MPArray.push(p);
16. }
17. }
18. return MPArray
19.}
The FindTabProcess routine searches for the process object that corresponds to a tab-process displaying the page, and returns this object as a routine result. The routine uses the FindChild method to find the copy of a desired page among the child tab-process of the given master-process.
01.function FindTabProcess(masterProcess, pageObj)
02.{
03. var PropArray, ValuesArray, ConvertedPropArray, ConvertedValuesArray, PageCopy;
04. // Create arrays of property names and values
05. PropArray = new Array("Parent.CommandLine", "Name");
06. ValuesArray = new Array("*iexplore.exe\" SCODEF:"+masterProcess.Id+"*", "Page("+pageObj.URL+")");
07. // Convert JScript arrays to format adopted in TestComplete
08. ConvertedPropArray = ConvertJScriptArray(PropArray);
09. ConvertedValuesArray = ConvertJScriptArray(ValuesArray);
10. PageCopy = Sys.FindChild(ConvertedPropArray, ConvertedValuesArray, 3);
11. if (PageCopy.Exists)
12. return PageCopy.Parent;
13. else
14. {
15. Log.Error("The tab-process for "+pageObj.URL+" was not found.");
16. return null;
17. }
18.}
19.// Helper function
20.// used to convert JScript arrays to format adopted in TestComplete
21.function ConvertJScriptArray(AArray)
22.{
23. // Uses the Dictionary object to convert a JScript array
24. var objDict = Sys.OleObject("Scripting.Dictionary");
25. objDict.RemoveAll();
26. for (var j in AArray)
27. objDict.Add(j, AArray[j]);
28. return objDict.Items();
29.}
Displaying information about the tabbed page
01.// Method of TabbedPage object that returns a tab description string
02.function TabbedPageToString()
03.{
04.var str ="Browser Instance: " + this.browserInstance.FullName + "\n" +
05. "Browser ID: " + this.browserID + "\n" +
06. "Page: " + this.pageObj.FullName;
07.if (this.browserID=="IE8") str = str + "\nTab Instance: "+ this.tabInstance.FullName;
08.return str;
09.}
Navigating to another location
1.// Method of TabbedPage object that navigates to a specified URL
2.function TabbedPageToURL(newUrl)
3.{
4. this.pageObj=this.pageObj.ToUrl(newUrl);
5. Log.Event("Navigated to "+ newUrl);
6.}
Activating the tabbed page
To simulate user actions over page controls, you need to activate the tab that contains the desired web page. Otherwise TestComplete won’t be able to interact with the controls because of another overlapping tab.
To activate a certain tab we need to perform a click over the corresponding tab button in the browser’s main window. The names of the tab buttons represent the titles of the web pages.
01.// Method of TabbedPage object that activates the tab
02.function TabbedPageActivate()
03.{
04.var BrowserUI;
05. switch (this.browserID)
06. {
07. case "FF":
08. // Get Firefox UI window
09. BrowserUI = this.browserInstance.Window("MozillaWindowClass", "", 1).UIPage("*");
10. BrowserUI.TabBand.TabButton(this.pageObj.contentDocument.title).Click();
11. Log.Event("Tab \"" + this.pageObj.contentDocument.title + "\" was activated.");
12. break;
13. case "IE7": case "IE8":
14. // Get Internet Explorer UI window
15. BrowserUI = this.browserInstance.IEFrame(0);
16. BrowserUI.CommandBar.TabBand.TabButton(this.pageObj.LocationName).Click();
17. Log.Event("Tab \"" + this.pageObj.LocationName + "\" was activated.");
18. break;
19. }
20.}
Closing the tabbed page
To close a tabbed page, you also need to interact with the tab buttons in the browser’s main window. Calling the Close() method for an object that represents a tab button, closes the related tab.
01.function TabbedPageClose()
02.{
03.var BrowserUI;
04. switch (this.browserID)
05. {
06. case "FF":
07. // Get Firefox UI window
08. BrowserUI = this.browserInstance.Window("MozillaWindowClass", "", 1).UIPage("*");
09. // Activate the tab
10. BrowserUI.TabBand.TabButton(this.pageObj.contentDocument.title).Click();
11. // Close the tab
12. Log.Event("Closing tab \"" + this.pageObj.contentDocument.title + "\"");
13. BrowserUI.TabBand.TabButton(this.pageObj.contentDocument.title).Close();
14. // Update object hierarchy
15. Sys.Refresh();
16. break;
17. case "IE7": case "IE8":
18. // Get Internet Explorer UI window
19. BrowserUI = this.browserInstance.IEFrame(0);
20. // Activate the tab
21. BrowserUI.CommandBar.TabBand.TabButton(this.pageObj.LocationName).Click();
22. // Close the tab
23. Log.Event("Closing tab \"" + this.pageObj.LocationName + "\"");
24. BrowserUI.CommandBar.TabBand.TabButton(this.pageObj.LocationName).Close();
25. // Update object hierarchy
26. Sys.Refresh();
27. break;
28. }
29.}
Now let me demonstrate how to perform the following typical tab-related tasks:
- Open a new tab.
- List all tabbed pages.
- Retrieve a tabbed page with the specified URL.
- Handle dialogs and notification messages.
Opening a new tabbed page
There are several ways to open a new tab from the browser’s user interface: select the respective command from the main menu, press Ctrl+T, press the “New Tab” button on the tab bar or simply double-click the tab bar. Any of these actions can easily be simulated from TestComplete.
The OpenNewTab function accepts a browserInstance as input parameter and on success returns the TabbedPage object that holds a newly opened tab. As mentioned above, the browserInstance is a reference to a browser copy and can be either a window object (for Firefoxes), or a process object (for Internet Explorers). For IE8 the browserInstance parameter should correspond to the master-process.
01.function OpenNewTab(browserInstance)
02.{
03.var BrowserUI;
04.// Determine the browser vendor
05.if (aqString.Find(browserInstance.FullName, 'Process("firefox"', 0, false) !=-1)
06. {
07. // Get Firefox UI window
08. BrowserUI = browserInstance.Window("MozillaWindowClass", "", 1).UIPage("*");
09. // Select File|New Tab from the main menu
10. BrowserUI.toolbar("toolbar_menubar").toolbaritem("menubar_items").menubar("main_menubar").ClickItem("File|New Tab");
11. // Search for a newly opened page - "about:blank"
12. PageObj=browserInstance.FindChild("Name", "*about:blank*", 10);
13. if (PageObj.Exists) return new TabbedPage(browserInstance, PageObj);
14. }
15. else
16. {
17. // Get Firefox UI window
18. BrowserUI = browserInstance.IEFrame(0);
19. // Press "New Tab" button on the tab bar
20. BrowserUI.CommandBar.TabBand.TabButton("New Tab*").Click();
21. // Search for a newly opened page - "about:Tabs"
22. PageObj=browserInstance.FindChild("Name", "*about:Tabs*", 10);
23. if (PageObj.Exists) return new TabbedPage(browserInstance, PageObj);
24. }
25. Log.Error("Failed to open a new tab.");
26. return null;
27.}
Listing pages opened in all tabs of all browser copies
The routines provided below iterate through all running browser instances and return an array of TabbedPage objects that contain data about every opened tabbed page. Since the object hierarchy differs for Firefox and Internet Explorer browsers, there are separate versions of the routine – GetFFTabs() and GetIETabs(). To list the pages opened in both of the browsers, we need to concatenate the resulting arrays.
01.function ListAllTabs()
02.{
03. // Get the list of pages opened in Firefox
04. var FFTabsArr = GetFFTabs();
05. // Get the list of pages opened in Internet Explorer
06. var IETabsArr = GetIETabs();
07. // Join the lists
08. var AllTabsArr=FFTabsArr.concat(IETabsArr);
09. Log.Message("There are "+AllTabsArr.length + " tabs opened in all browsers")
10. for (var i = 0; i < AllTabsArr.length; i++) Log.Message(AllTabsArr[i]);
11.}
01.function GetFFTabs()
02.{
03.var firefox, Tabs, BrowserInstances, Pages;
04. // Refresh list of running processes
05. Sys.Refresh();
06. Tabs = new Array();
07. firefox = Sys.WaitProcess("firefox");
08. if (firefox.Exists)
09. {
10. // Search for browser copies
11. // Same firefox.exe process but different instances of MozillaUIWindowClass
12. BrowserInstances = firefox.FindAllChildren("WndClass", "MozillaUIWindowClass");
13. // Convert to JScript's Array type
14. BrowserInstances = VBArray(BrowserInstances).toArray();
15. if (BrowserInstances.length > 0)
16. {
17. Log.Message("Total number of Firefox instances: " + BrowserInstances.length);
18. // Search for web pages opened in each instance of a browser
19. for (var i = 0; i < BrowserInstances.length; i++)
20. {
21. // Pages opened in a certain copy of FF are grandchildren of MozillaUIWindowClass
22. Pages = BrowserInstances[i].FindAllChildren("Name", "Page(*)",10);
23. // Convert to JScript's Array type
24. Pages = VBArray(Pages).toArray();
25. if (Pages.length > 0){
26. Log.Message(Pages.length+" tabs opened in instance "+(i+1));
27. for (var j = 0; j < Pages.length; j++)
28. // Create a TabbedPage object and append it to Tabs array
29. Tabs.push(new TabbedPage(BrowserInstances[i], Pages[j]));
30. }
31. else
32. Log.Warning("No pages opened in Firefox instance "+(i+1));
33. }
34. }
35. }
36. else Log.Error("No running instances of Firefox were found.");
37.return Tabs;
38.}
01.function GetIETabs()
02.{
03.var iexplore, Tabs, BrowserInstances, Pages;
04. // Refresh list of running processes
05. Sys.Refresh();
06. Tabs = new Array();
07. // Search for browser copies
08. // (Different instances of iexplore.exe process having the IEFrame window)
09. BrowserInstances = FindMasterProcesses();
10. if (BrowserInstances.length > 0)
11. {
12. Log.Message("Total number of Internet Explorer instances: " + BrowserInstances.length);
13. // Search for web pages opened in each instance of a browser
14. for (var i = 0; i < BrowserInstances.length; i++)
15. {
16. // Pages opened in a certain copy of IE7 are grandchildren of its IEFrame
17. // Pages opened in a certain copy of IE8 appear as:
18. // grandchildren of master-process's IEFrame
19. // children of helper tab-processes
20. // (Here we'll search for those of a master-process)
21. Pages = BrowserInstances[i].IEFrame(0).FindAllChildren("Name", "Page(*)",10);
22. // Convert to JScript's Array type
23. Pages = VBArray(Pages).toArray();
24. if (Pages.length > 0){
25. Log.Message(Pages.length+" tabs opened in instance "+(i+1));
26. for (var j = 0; j < Pages.length; j++)
27. // Create a TabbedPage object and append it to Tabs array
28. Tabs.push(new TabbedPage(BrowserInstances[i], Pages[j]));
29. }
30. else
31. Log.Warning("No pages opened in Internet Explorer instance "+(i+1));
32. }
33. }
34. else Log.Error("No running instances of Internet Explorer were found.");
35.return Tabs;
36.}
Retrieving a tabbed page with the specified URL
Sometimes it may be convenient to check whether a browser displays a page with the specified URL, and get this tab on success. The routine below iterates through all running processes, finds Firefox and IE and searches for the Page object with the given URL. Once the page is found, the routine returns a TabbedPage object holding references to the found page and browser. If the page was not found, then the routine returns null.
01.function GetTabbedPage(pageURL)
02.{
03. var PageObj;
04. var PgName="Page(" + pageURL + ")";
05. // Refresh the process list
06. Sys.Refresh();
07. // Enumerate through processes in order to find browsers
08. for (var i = 0; i < Sys.ChildCount; i++)
09. {
10. p=Sys.Child(i);
11. // If the process is Firefox
12. if (p.ProcessName=="firefox")
13. {
14. // Enumerate through windows in order to find MozillaUIWindowClass windows
15. for (var j = 0; j < p.ChildCount; j++)
16. {
17. w=p.Child(j);
18. if (w.WndClass=="MozillaUIWindowClass")
19. {
20. // Once found search for the Page object that has the specified URL
21. PageObj=w.FindChild("Name", PgName, 10);
22. if (PageObj.Exists)
23. {
24. // Return object that describes both the browser instance and the page
25. // In Firefox the browser instance corresponds to MozillaUIWindowClass window
26. return new TabbedPage(w, PageObj);
27. }
28. }
29. }
30. }
31. // If the process is IExplore
32. if (p.ProcessName=="iexplore")
33. {
34. // Search for IEFrame window
35. w=p.WaitChild("IEFrame(0)", 1000);
36. if (w.Exists)
37. {
38. // Once found search for the Page object that has the specified URL
39. PageObj=w.FindChild("Name", PgName, 10);
40. if (PageObj.Exists)
41. {
42. // Return the object that describes both the browser instance and the page
43. // In IE the browser instance corresponds to a Process that has the IEFrame window
44. // In IE8 this is a master-process, not a tab-process
45. return new TabbedPage(p, PageObj);
46. }
47. }
48. }
49. }
50. Log.Error("The Page(" + pageURL + ") was not found.");
51. return null;
52.}
Handling dialogs and notification messages
The code below demonstrates the specifics of handling dialogs and popup windows displayed in Internet Explorer 8. The routine searches for the notification message that appears in the Information bar when a page contains active content and permits the browser to use it. The routine works both for Internet Explorer 7 and for Internet Explorer 8.
01.//Enables the active content which is blocked by default
02.function AllowBlockedContent(TabbedPageObj)
03.{
04. var InfoBar, ConfirmDlg;
05. // Search for the Internet Explorer Information bar
06. // (It always belongs to the master instance of IE)
07. InfoBar=TabbedPageObj.browserInstance.FindChild("WndCaption","To help protect your security*",15,true);
08. if (InfoBar.Exists)
09. if (InfoBar.Visible)
10. {
11. InfoBar.Click();
12. InfoBar.PopupMenu.Click("Allow Blocked Content...");
13. // Search for the confirmation dialog
14. // (It belongs to the IE instance displaying the blocked page)
15. if (TabbedPageObj.browserID=="IE8")
16. // In IE8 it is child tab-process
17. ConfirmDlg=TabbedPageObj.tabInstance.FindChild("WndCaption","Security Warning",15);
18. else
19. // In IE7 it is the same process instance
20. ConfirmDlg=TabbedPageObj.browserInstance.FindChild("WndCaption","Security Warning",15);
21. ConfirmDlg.Window("Button", "&Yes").ClickButton();
22. }
23.}
Demo
The code snippet below, demonstrates how to use all of the given methods and routines. Yuo can find this and the other routines in the supplied TestComplete project.
01.function TabsDemo()
02.{
03.var firefox, iexplore, Tab1, Tab2;
04. // Launch Internet Explorer browser
05. iexplore = TestedApps.iexplore.Run(1, true);
07. // Search for the tab where the Google page was opened
09. // Display tab information
10. Log.Message(Tab1);
11. // Open empty tab in Internet Explorer
12. Tab2=OpenNewTab(iexplore);
13. Log.Message(Tab2);
14. // Navigate to another URL
16. Log.Message(Tab2);
17. // Launch Firefox browser
18. firefox = TestedApps.firefox.Run(1, true);
19. // Open empty tab in Firefox
20. Tab3=OpenNewTab(firefox.Window("MozillaUIWindowClass", "*", 1));
21. Log.Message(Tab3);
22. // Navigate to another URL
24. Log.Message(Tab3);
25. // Activate first tab
26. Tab1.Activate();
27. // List tabs opened in all browsers
28. ListAllTabs();
29. // Close first tab
30. Tab1.Close();
31. Log.Message(Tab1);
32.}
Conclusion
Tabbed browsing is a lot more convenient for users, that is why all leading browsers implemented page tab technology. If you have tried the recent versions of Firefox or Internet Explorer, then you already know what page tabs are and have seen their advantages.
When creating automated web tests to be executed in these browsers, you should take into account the tab specifics. This article demonstrated how to work with page tabs in automated tests created with TestComplete. We hope this information will help you create reliable and flexible web tests. If you are interested in testing your site, server or web application with TestComplete, download and try it today.