B2G/QA/Automation/Style Guide/Best Practices

From MozillaWiki
< B2G‎ | QA‎ | Automation‎ | Style Guide
Jump to navigation Jump to search

Use External Parameters

Using test variable file when running gaiatest can avoid defining variables inside the script. The testvars template is located here.
Make sure to fill in the appropriate section if you're planning to use it, and supply the name and location of the .json file as the parameter to the gaiatest command with --testvars= option.

If you want to access the varable value defined in the .json file, you can do as the following example:

test_phone_number = self.testvars['remote_phone_number']

If you need to access the sub-variable, consider below example as well:

self.testvars['plivo']['auth_id'],
self.testvars['plivo']['auth_token'],
self.testvars['plivo']['phone_number']

Make sure that you are not including your testvars.json file in your PR request, as the testvars file for jenkins is managed separately.

Try to Avoid sleep() Calls

Sleep methods can be used as follows:

import time
time.sleep(seconds)

However, sleep() calls should be used very sparingly, only when there is no other way to delay the action of Marionette. Using sleep() when Wait() can accomplish the same thing would be bad for following reasons:

  • Sleep() does not care about the UI changes in app. If you're using sleep() to just 'wait enough', you'll run into problems when the app behavior changes and requires more/less time to wait.
  • Sleep() does not care about the phone performance. If the speed of the execution changes because of the changes in memory allocation or running on a newer/older devices, it will still wait for specified time.

When you have to use the sleep() call, make sure to put in the comment explaining why other methods won't work.

Limit the Use of Conditionals

  • Methods should not contain logic that depends on properties of the page. The logic and expectations should be within the test, and adding this to the page object could guard your tests against genuine failures.
# Good
def click_login(self)
    self.selenium.find_element(*self._login_locator).click()

# Bad
def click_login(self)
    if not self.is_user_logged_in:
        self.selenium.find_element(*self._login_locator).click()
    else:
        pass

PageRegions

TBD - jlorenzo

  • How to deal with stale root_elements

In some circumstances, for example where a header/navigation is common across the website, we will use a page region. The page region is a child class of the base Page object, which is inherited by all page objects. This means that the navigation can be reached from any page object and herein lies the DRY!

A brief example:

class BasePage(Page):

    @property
    def header(self):
        return BasePage.HeaderRegion(self.testsetup)
    
    class HeaderRegion(Page):

        _login_link = (By.ID, "home")

        @def click_login(self):
            self.selenium.find_element(*self._login_link).click()

Referring to this page region with a property makes it very readable and concise from within the test. Clicking login during a test would be performed like this:

my_page.header.click_login()

Another example where this might be used is on a search results page, the page region being the search results element.

Assertions

TBD - jlorenzo

  • Tests should handle the asserts -- not the page objects.
  • Tests should use Python's native assert statement.
  • When doing equivalency assertions, put the expected value first, followed by the actual value, for example:
# Good
a = some_function()
assert 'expected result' == a

# Bad
a = some_function()
assert a == 'expected result'

How to use GaiaHeader and GaiaBinaryControl

TBD - jlorenzo

app.py and regions/helper.py

In gaiatest/apps folder, each subfolder contains the helper python method files. The rule of thumb is as follows:

  • app.py: Contains the class for the main screen when the app is instantiated. There should be a method that instantiates the subpage objects.
  • regions/*.py: Contains the classes for the each subpage of the app.

Meaningful custom Assert() messages

Put a custom error message in Assert() call only when it provides more information than the one given by default. `self.asserEqual()` by itself is a nice assertion method: if it fails, it'll give you the expected value and the actual.

For example, self.assertEqual(displayed_phone_number, expected_phone_number), will fail with:

 AssertionError: u'+34...' != u'+33...'
 Stacktrace:

But if you provide an error message as follows: self.assertEqual(displayed_phone_number, expected_phone_number, msg='Phone numbers are not the same'), then you'll have something like:

 AssertionError: Phone numbers are not the same
 Stacktrace:

In other words, by adding more context, you might actually remove some useful debug data.

Clean up afterwards

TBD - njpark

Simulate end-user check

TBD - jlorenzo

  • Check what an end-user would check.
  • Make sure manifest.ini is updated with right flags