QA SoftVision Team/Mobile/Writing tests
Contents
- 1 Getting Started
- 2 How to start when writing a new test
- 3 Basic structure of the test
- 4 APIs
- 5 Usable IDs
- 6 Gecko Events you can wait for
- 7 Basic code sequences to do different actions
- 7.1 Load page
- 7.2 Check Title
- 7.3 Open the app menu (Custom Menu)
- 7.4 Open Menu item
- 7.5 Use the Back key
- 7.6 Set up listeners to catch the page load
- 7.7 Wait for the new tab and page to load
- 7.8 Wait for Gecko to load
- 7.9 Open the Bookmarks tab - method
- 7.10 Open Bookmark context menu
- 7.11 Open a new tab and check the tab count
- 8 Logging info in the Android Logs to help debug
- 9 Debugging tests
- 10 Substituting mSolo.sleep
- 11 Updating your test directory
- 12 Creating a diff patch file
- 12.1 Create a new patch in the Mercurial Queue
- 12.2 Adding new files to be tracked by Mercurial
- 12.3 Refresh the repository
- 12.4 Poping the patch
- 12.5 Pushing the patch
- 12.6 Viewing the diff file
- 12.7 Exporting the patch
- 12.8 Remove files from the current patch
- 12.9 See a list of all patches in the queue and changed files outside of patches
- 12.10 Apply a certain patch/patch tree
- 13 Adding a patch to the tryserver
Getting Started
- You can find information about setting up the enviroment for running the tests here
- Please make sure to look over this page for some general information about Robocop tests.
- Robocop is a Java API based on the Robotium API used for automation testing on the Android OS
- The existing test cases can be found in the sorce directory under the folder mobile/android/base/tests
- The class that will cover the text will have to be named the same as the test file
- The test file name should be test[Feature_to_be_tested].java.in
- The test must be included in the same package as all other Robocop tests
How to start when writing a new test
- Setup the enviroment. Follow the instructions here.
- Download sources. More info can be found here about how to do it.
- If you already have the sources downloaded update to the lates version by running the command in the sources directory:
hg pull -u
- Make the build by running:
make -f client.mk
- Create a new file named "test" followed by the test name and with the extention "java.in" : for e.g. testBookmarks.java.in
- Copy the basic structure of the test presented in the next section to the test making sure to match the test name and the class and constructure names.
- Create a new mercurial queue patch on the repository by running in the source folder the command:
hg qnew <patch_name>
- Add the new test file to the queue by running the command:
hg add mobile/android/base/tests/<test_file_complete_name>
- Open robocop.ini from mobile/android/base/tests and add after the last Robocop test a new line with the test name between sqare brackets ( "[" and "]")
- Write the test, compile robocop (here is how) and run the test in order to test that it works
- After the test works follow the instruction to create a patch so the test can be uploaded to a bug and integrated in mozilla.
- Before updating the sources or after the patch is completed make sure to return the sources to the unmodified version by running the command in the source folder until there are no patches in the queue:
hg qpop
Basic structure of the test
#filter substitution package @ANDROID_PACKAGE_NAME@.tests; import @ANDROID_PACKAGE_NAME@.*; /* Insert info about the test here - a basic description of what the test does */ public class test[Feature_to_be_tested] extends BaseTest { @Override // This method is needed in the BaseTest class protected int getTestType() { return TEST_MOCHITEST; // Test type should remain Mochitest } /* * This is the class constructor * This method will contatin the test that will be ran */ public void testLoad() { } }
- This is a basic structure of a Robocop test
- Each test class must have three methods:
- protected void setUp() // Starts Fennec and sets up commonly used member variables. This is usually very similar for every test class
- public void test[YourFeature]() // Your test code goes here. Use the Robocop API to access Fennec elements, click, send keys, and assert conditions
- public void tearDown() // Clean up
- If your tests extends BaseTest then you will not need to add the methods setUp() and tearDown() as they are already defined in BaseTest
- If you need to do extra clean-up, like removing entries you added in a database for e.g., the tearDown() method can be overwrittern but make sure to call super.tearDown() as needed in the method. The same is applicable for setUp().
APIs
Robotium itself provides a rich API through the Solo class - javadocs for Solo are available at [1].
"Robocop" provides an additional API to make common tasks easier. The main interfaces are Actions, Elements, and Driver.
Actions provides commonly used non-element specific actions that can be taken on the application, such as dragging and sending key events.
Actions //This will cause this process to spin until the gecko fires a specific JSON event, such as DOMContentLoaded void waitForGeckoEvent(String geckoEvent); //Clicks the given Key (Actions.SpecialKey.[DOWN|UP|LEFT|RIGHT|ENTER]) void sendSpecialKey(SpecialKey button) //Sends a string of characters to the system. (most have been implemented but not all) void sendKeys(String keysToSend); //Sends a drag action across the screen void drag(int startingX, int endingX, int startingY, int endingY) // Run a sql query on the specified database public Cursor querySql(String dbPath, String sql);
Element represents each of the available UI objects in Fennec including the Awesomebar, the 'tabs' button, and different lists and menus.
Element //To click the element. void click() //Returns true if the element is currently displayed boolean isDisplayed(); //Returns the text currently displayed on the element, or direct sub-elements. String getText();
Driver finds elements and provides info about the UI.
Driver //This is used to find elements given their id's name. Element findElement(String name); //This is used for getting information on scrolls. NOTE: It must be used for the next three methods to return useful information void setupScrollHandling(); int getPageHeight(); //The total height of the page. int getScrollHeight(); //How far down the screen the client has scrolled. int getHeight(); //The height of the client's view. //The following are used to give information on the graphical location of the Gecko view on the screen. int getGeckoTop(); int getGeckoLeft(); int getGeckoHeight(); int getGeckoWidth();
Finally, an evolving set of test base classes - BaseTest, PixelTest, etc - can be leveraged for some types of tests.
Usable IDs
The following is a list of ids that can be used with Driver.findElement(). Most of the following ids have not been tested, so might have unexpeced results, or require increased APIs for them. To know how a given object is used, in mobile/android/base, grep R.id.[id-name] * (from Objdir/mobile/android/base/R.java#id)
abouthome_content add_tab addons address_bar agent_mode all_pages_list awesome_bar awesome_screen awesomebar_button awesomebar_tabs awesomebar_text background bookmark bookmark_icon bookmark_title bookmark_url bookmarks_list browser_toolbar close container doorhanger_choices doorhanger_container doorhanger_title favicon forward gecko_layout grid history_list info list main_layout notification_image notification_progressbar notification_text notification_title outline plugin_container preferences quit recommended_addon_list reload save_as_pdf screenshot select_list share site_security stop tabs tabs_count title url
Gecko Events you can wait for
- There are a set of messages sent from Gecko to Java to signal different actions
- Also Java can register observers to catch changes to deferent actions
- Here is a list of Gecko Events on which you can create EventExpectors and block until the message is received
Addons:Change Addons:All - shown when sending all addons to java as a json Addons:FetchAll - get the state of all addons Browser:Quit Browser:ZoomToRect - zoom to element (viewport if the element is to large) Browser:ZoomToPageWidth Campaign:Set - Update the prefs for this session - related to distribution CharEncoding:Data CharEncoding:Get CharEncoding:Set CharEncoding:State Content:LoadError Content:PageShow Content:ReaderEnabled Content:StateChange - change in network state Content:LocationChange - change of location Content:SecurityChange DesktopMode:Change Distribution:Set Distribution:Set:OK DOMFullScreen:Start DOMFullScreen:Stop DOMContentLoaded DOMLinkAdded DOMMetaAdded DOMModalDialogClosed DOMUpdatePageReport DOMTitleChanged DOMWindowClose DOMWillOpenModalDialog Doorhanger:Add Doorhanger:Remove Doorhanger:Reply Feedback:LastUrl Feedback:OpenPlayStore Feedback:MaybeLater FilePicker:Show FormAssist:Hide - shown when hide request given FormAssist:AutoComplete - shown when autocomplete is displayed FormAssist:Blocklisted FormAssist:Hidden - shown when the form assist has been hidden FormAssist:ValidationMessage - shown when a vaildation message is displayed FormHistory:Init - initialize form history db on update FullScreen:Exit Gecko:Ready Gesture:CancelTouch Gesture:DoubleTap Gesture:LongPress Gesture:Scroll Gesture:ScrollAck Gesture:SingleTap Link:Favicon Link:Feed Menu:Add Menu:Clicked Menu:Update Menu:Remove MozApplicationManifest MozMagnifyGesture MozScrolledAreaChanged nsPref:changed OrderedBroadcast:Send pageshow Panning:Override Panning:CancelOverride PanZoom:StateChange Passwords:Init - initialize passwords db on update Permissions:Data Prompt:Show PluginBindingAttached Pref:Change Preferences:Get Preferences:Set Preferences:Observe Preferences:RemoveObservers PrivateBrowsing:Data Reader:Add Reader:Added Reader:FaviconRequest Reader:GoToReadingList Reader:Remove Reader:Share robocop:scroll SaveAs:PDF Sanitize:ClearData Sanitize:ClearHistory Sanitize:Finished scroll ScrollTo:FocusedInput SearchEngines:Data SearchEngines:Get Session:Back Session:Forward Session:Reload Session:RestoreEnd Session:ShowHistory Session:StatePurged Session:Stop SessionHistory Share:Text SharedPreferences:Set SharedPreferences:Observe Shortcut:Remove Tab:Close Tab:Closed Tab:HasTouchListener Tab:Load Tab:Select Tab:Selected Tab:ViewportMetadata Telemetry:Add Telemetry:Gather TextSelection:HideHandles TextSelection:PositionHandles TextSelection:ShowHandles Toast:Show ToggleChrome:Focus ToggleChrome:Show ToggleChrome:Hide Viewport:Change Viewport:Flush Viewport:FixedMarginsChanged Wallpaper:Set WebApps:Open WebApps:PreInstall WebApps:PostInstall Window:Resize
Basic code sequences to do different actions
Load page
- Note that the tests need to use local pages in order to run on the mozilla setup. Outside pages are not accessible
- Using an URL
String url = "<insert_link_here>"; loadURL(url);
- Using the robocop tests blank page
String url = getAbsoluteUrl("/robocop/robocop_blank_01.html"); loadUrl(url);
Check Title
Element awesomebar = mDriver.findElement(getActivity(), "awesome_bar_title"); // search the awesomebar title mAsserter.isnot(awesomebar, null, "Got the awesomebar"); // log "Got the awesomebar" in the logs assertMatches(awesomebar.getText(), "<insert_title_here", "page title match"); // do a match between expected and actual title
mActions.sendSpecialKey(Actions.SpecialKey.MENU); //Open the More menu for devices with old style menues if (mSolo.waitForText("^More$")) { mSolo.clickOnText("^More$"); }
Open Menu item
mSolo.waitForText("^Settings$"); mSolo.clickOnText("^Settings$");
Use the Back key
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
Set up listeners to catch the page load
Actions.EventExpecter tabEventExpecter = mActions.expectGeckoEvent("Tab:Added"); Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
Wait for the new tab and page to load
tabEventExpecter.blockForEvent(); contentEventExpecter.blockForEvent();
Wait for Gecko to load
mActions.expectGeckoEvent("Gecko:Ready").blockForEvent();
Open the Bookmarks tab - method
private ListView openBookmarksList() { Activity awesomeBarActivity = clickOnAwesomeBar(); // Click the "Bookmarks" tab to switch to bookmarks list mSolo.clickOnText("Bookmarks"); TabHost tabHost = (TabHost)mSolo.getView(TabHost.class, 0); return (ListView)tabHost.getCurrentView(); }
- Similarly you can get the History and Top Sites tabs
View child = list.getChildAt(<nr>); //bookmark nr- numbering starts at 0 mSolo.clickLongOnView(child); //long tap to open context menu
- Similarly you can open the context menu for History and Top Sites items
Open a new tab and check the tab count
expectedTabCount = <nr_of_expected_tabs>; tabCountText = tabCount.getText(); tabCountInt = Integer.parseInt(tabCountText); mAsserter.is(tabCountInt, expectedTabCount, "Number of tabs increased");
Logging info in the Android Logs to help debug
- The Assert class can be found in the source folder in the subfolder /obj-android/build/mobile/robocop
- Please see the java code for the Assert methods for more info about how they work
Test an object is not the same as a second object
- Method signature:
public void isnot(Object a, Object b, String name)
- Example for checking that there is a title set for the current page:
Element awesomebar = mDriver.findElement(getActivity(), "awesome_bar_title"); mAsserter.isnot(awesomebar, null, "Got the awesomebar"); // Checks that the awesomebar title is not null and posts in the logs
Test an object is the same as a second object
- Method signature:
public void is(Object a, Object b, String name)
- Example for checking the default bookmarks:
ListView list = getAllPagesList(url); mSolo.waitForText(url); mAsserter.is(list.getChildCount(), 5, "all pages list has 5 children (the default bookmarks)"); // Checks that there are 5 entries in the list and posts in the logs
Write text in logs
- Method signature:
public void dumpLog(String message) // writes text in the logs
Log info about test progression
- Method signature:
public void ok(boolean condition, String name, String diag)
- Example
ListView list = getAllPagesList(url); mSolo.waitForText(url); mAsserter.ok(list != null, "checking that all pages list exists", list.toString()); // tests if the list is null and if it isn't it prints the list after printing the message of what the test does
Debugging tests
See the screenshot from the fail in the try run
- Open the link to the tryrun and view the full logs of the failed run
- Go the the position the fail you are interested in has happened
- Search for "base64" and copy the hole line from the log
- Remove the "SCREENSHOT" from the beginning of the string and paste it in the URL bar of the browser
- You can see the screenshot of the output the moment the test failed
Debugging using logs
- You can use mAsserter.dumpLog(Sting) or mAsserter.ok(true, Sting, String) to print any string or value of any variable
- Note that you will need to cast the variable to String in order to print it: String.valueOf(Variable)
Substituting mSolo.sleep
- Don't use sleep unless absolutely necessary
- It's ok to use sleep while creating the test but at the end try and substitute it
- Here are some alternatives:
- waitForText(String waitText)
- waitForEnabledText(String waitText)
- blockForEvent/ blockForEventData - gecko event expecters
- waitForCondition
- waitForTest
- waitForPaint
- waitForView(View view)
- waitForView(View view, int timeout, boolean scroll)
- waitForDialogToOpen()
- waitForDialogToClose()
- waitForView(int id)
- waitForView(int id, int minimumNumberOfMatches, int timeout)
- waitForView(int id, int minimumNumberOfMatches, int timeout, boolean scroll)
- waitForActivity(name)
- waitForActivity(Class<? extends Activity> activityClass)
- waitForActivity(Class<? extends Activity> activityClass, int timeout)
Updating your test directory
It is not always necessary to rebuild Fennec to use new tests. If you are updating the build with your own test, ensure that your test has been added to the manifest in /PATH/TO/FENNEC/SOURCE/mobile/android/base/tests/robocop.ini .
Otherwise, you can retrieve tests from the repository by executing:
hg pull hg update
Now you can build the robocop part of Fennec by executing:
cd objdir make -C build/mobile/robocop/ make package
Creating a diff patch file
All commands need to be run on the main root source folder.
Steps to perform for new patches:
- Create a new patch
- Add or modify any files
- Refresh the repository
- Pop the patch to have a clean repository
- Push the patch back
- Export the patch
- Pop the patch to clean up the repository
Steps to perform for existing patches:
- Push the patch to the repository
- Add or modify any files
- Refresh the repository
- Pop the patch to have a clean repository
- Push the patch back
- Export the patch
- Pop the patch to clean up the repository
Create a new patch in the Mercurial Queue
Merurial can create patch queue (patch stack) so the user can keep his repository clean and can add or work on multiple patches on the same repository
hg pull -u hg qnew <patch name>
You need to do hg pull to have the repository up to date in order to create a correct diff patch
Adding new files to be tracked by Mercurial
You need to add new files to be tracked otherwise the diff will be created only for the existing changed files Run the following command for each new file
hg add mobile/android/base/tests/<test_file>
Refresh the repository
When first creating a patch:
hg qrefresh -m "Commit message"
When editing patches:
hg qrefresh
The commit message has the general form: "Bug <number> - <Description>". The commit message should be set for a new patch and will appear in the patch file.
Poping the patch
Removes the patch from the patch stack (Queue)
hg qpop
Pushing the patch
Adds the last patch to the top of the queue. Alternatively you can specify a patch name to add.
hg push -v [<patch_name>]
Viewing the diff file
hg qdiff
Exporting the patch
Exports the top patch in the stack to a file that can be submitted for review
hg export tip > <patch_name.patch>
Remove files from the current patch
In case you added an unwanted file to the patch it can be removed by running:
hg rm -f mobile/android/base/tests/<test_file>
See a list of all patches in the queue and changed files outside of patches
- If there are files displayed that means that the file has been changed outside of any patch
hg status
Apply a certain patch/patch tree
- This command will apply the patch or the sequence of patches until the selected patch
hg qgoto <patch_name>
Adding a patch to the tryserver
- In order to add a patch to the tryserver you need to get Commit access level 1
- Follow the steps listed here to get the access
- All patches must contain a trychooser patch on top of it in order to limit the tests only to what it is needed. Build the statement for it using this page
Create the try patch over the existing patch queue
hg qnew -m "try: -b o -p android -u robocop-1,robocop-2 -t none" try
Push the changes to the tryserver
hg push -f -rtip ssh://<user>@hg.mozilla.org/try/
- This will push the changes and will give you a Treeherder link to your push
- When the building process for the test build will start you will also receive an email with the push info
Unlock the patch queue
- From Mercurial 2.0 the queue will be locked if you pushed to try
- You can unlock the queue in order to remove the patches or change them
hg phase -f --draft qbase:tip
Removing the try patch from the queue
- After you are done with the push you can delete the try patch so you can add it to other patches for push
hg qremove try