QA/Execution/Web Testing/Docs/Automation/StyleGuide: Difference between revisions

no edit summary
No edit summary
Line 40: Line 40:
"""Logs in.
"""Logs in.
Clicks the login link and then waits for the home page to load."""
Clicks the login link and then waits for the home page to load."""
</pre>
</source>
* Indenting should be a soft tab (4 spaces) as common with in Python. Do not mix tabs and spaces!
* Indenting should be a soft tab (4 spaces) as common with in Python. Do not mix tabs and spaces!
* There should be no whitespace at the end of the file (as per PEP8).
* There should be no whitespace at the end of the file (as per PEP8).
Line 54: Line 54:


= Page Objects =
= Page Objects =
== General ==
== General ==
* All Page Objects should inherit from Page in page.py.
* All page objects should inherit from <code>Page</code> in page.py.
* Page Objects should not do asserts. This should be done within the test.
* Page objects should not do asserts. This should be done within the test.
* Each Page should be grouped within one module.
* Each page should be grouped within one module.
* If using mutliple words to describe a module separate them with underscores '_'
* If using multiple words to describe a module separate them with underscores '_'
     test_search.py
     test_search.py
* Timeout time should be taken from pytest-mozwebqa via page.py's timeout property.
* Timeout time should be taken from pytest-mozwebqa via <code>page.py's</code> timeout property.
* Single quotes (') should be used instead of double (") throughout.
* Single quotes (') should be used instead of double (") throughout.
* Methods should have a single purpose
* Methods should have a single purpose.


== Logic ==
== Logic ==
* 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.
* 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.
<pre class="brush:py;toolbar:false;">
 
    # Good
<source lang="python">
     def click_login(self)
# 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()
         self.selenium.find_element(*self._login_locator).click()
 
     else:
     # Bad
         pass
    def click_login(self)
</source>
        if not self.is_user_logged_in:
            self.selenium.find_element(*self._login_locator).click()
         else:
            pass
</pre>


== Locators ==
== Locators ==
* Locator variables should be prefixed with _ to show that it is "[http://docs.python.org/tutorial/classes.html#private-variables private]".
* Locator variables should be prefixed with <code>_</code> to show that it is "[http://docs.python.org/tutorial/classes.html#private-variables private]".
* Variables should be descriptive of the area and not clash with any properties.
* Variables should be descriptive of the area and not clash with any properties.
* Suffix of _locator.
* Should have a suffix of <code>_locator</code>.
* Accessing locators should be done through a property or method as this keeps the locator as readonly.
* Accessing locators should be done through a property or method as this keeps the locator as read-only.
<pre class="brush:py;toolbar:false;">
 
    @property
<source lang="python">
    def search_term(self):
@property
        return self.selenium.find_element(*self._search_box_locator).value
def search_term(self):
</pre>
    return self.selenium.find_element(*self._search_box_locator).value
</source>
 
* We should use locators in the following order of preference (there will be exceptions):
* We should use locators in the following order of preference (there will be exceptions):
** ID
** ID
Line 95: Line 99:
** CSS selector
** CSS selector
** XPath
** XPath
* For Selenium RC, locators should include the strategy instead of using implied strategies.
<pre class="brush:py;toolbar:false;">
    # Good
    _my_locator = "id=content"
   
    # Bad
    _my_locator = "content"
</pre>
* CSS locators should use whitespace for readability when using direct descendants.
* CSS locators should use whitespace for readability when using direct descendants.
<pre class="brush:py;toolbar:false;">
 
    # Good
<source lang="python">
    _my_locator = "css=#content > p > a"
# Good
_my_locator = "css=#content > p > a"
      
      
    # Bad
# Bad
    _my_locator = "css=#content>p>a"
_my_locator = "css=#content>p>a"
</pre>
</source>
* With WebDriver, use a Python tuple to define locators:
 
<pre class="brush:py;toolbar:false;">
* Use Python tuples to define locators:
    # Good
 
    _my_locator = (By.ID, "content")
<source lang="python">
</pre>
# Good
_my_locator = (By.ID, "content")
</source>


== Actions ==
== Actions ==
* Methods that perform actions on the page should indicate the action in the method name.
* Methods that perform actions on the page should indicate the action in the method name.
<pre class="brush:py;toolbar:false;">
    # Good
    def click_report_with_length(length)


    # Bad
<source lang="python">
    def report_length(length)
# Good
</pre>
def click_report_with_length(length)
 
# Bad
def report_length(length)
</source>
 
* Actions should wait for the appropriate action to complete. This could be an implicit or explicit wait. For example, clicking a login button might explicitly wait for a username field to be visible.
* Actions should wait for the appropriate action to complete. This could be an implicit or explicit wait. For example, clicking a login button might explicitly wait for a username field to be visible.


Line 132: Line 133:


A brief example:
A brief example:
<pre class="brush:py;toolbar:false;">
 
<source lang="python">
class BasePage(Page):
class BasePage(Page):


Line 145: Line 147:
         @def click_login(self):
         @def click_login(self):
             self.selenium.find_element(*self._login_link).click()
             self.selenium.find_element(*self._login_link).click()
</pre>
</source>


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:
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:
<pre class="brush:py;toolbar:false;">
<source lang="python">
    my_page.header.click_login()
my_page.header.click_login()
</pre>
</source>


Another example where this might be used is on a search results page, the page region being the search results element.
Another example where this might be used is on a search results page, the page region being the search results element.
Line 158: Line 160:
     test_search.py
     test_search.py
* Test method signature should include mozwebqa to use pytest-mozwebqa plugin.
* Test method signature should include mozwebqa to use pytest-mozwebqa plugin.
<pre class="brush:py;toolbar:false;">
 
    def test_example(self, mozwebqa):
<source lang="python">
</pre>
def test_example(self, mozwebqa):
</source>
 
* Test method names should always show the intent of the test case.
* Test method names should always show the intent of the test case.
<pre class="brush:py;toolbar:false;">
    # Good
    def test_that_advanced_search_does_not_find_item(self, mozwebqa):


    # Bad
<source lang="python">
    def test_advanced_search(self, mozwebqa):
# Good
</pre>
def test_that_advanced_search_does_not_find_item(self, mozwebqa):
 
# Bad
def test_advanced_search(self, mozwebqa):
</source>
 
* Tests should handle the asserts -- not the page objects.
* Tests should handle the asserts -- not the page objects.


Line 177: Line 183:
As we (and Selenium and automation) develop more knowledge some projects might fall behind the standards in this style guide. It can be tempting to want to fix all of the outdated style but in order to keep patches/pulls small (see above!) we are happy to have new and old standards of code sit side by side. As we regularly review and update tests the project will be brought completely up to our current standards.
As we (and Selenium and automation) develop more knowledge some projects might fall behind the standards in this style guide. It can be tempting to want to fix all of the outdated style but in order to keep patches/pulls small (see above!) we are happy to have new and old standards of code sit side by side. As we regularly review and update tests the project will be brought completely up to our current standards.


Or if you prefer, log a Github issue to have a section of code addressed separately to the job you are doing.
Or if you prefer, log a GitHub issue to have a section of code addressed separately to the job you are doing.
Confirmed users
2,197

edits