|
|
| Line 13: |
Line 13: |
|
| |
|
| = Use of Libraries = | | = Use of Libraries = |
| == Limited 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.
| |
|
| |
| <source lang="python">
| |
| # 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
| |
| </source>
| |
|
| |
| == Locators ==
| |
| TBD - jlorenzo
| |
| * 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.
| |
| * Should have a suffix of <code>_locator</code>.
| |
| * Accessing locators should be done through a property or method as this keeps the locator as read-only.
| |
|
| |
| <source lang="python">
| |
| @property
| |
| def search_term(self):
| |
| 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):
| |
| ** ID
| |
| ** Name
| |
| ** Class name
| |
| ** CSS selector
| |
| ** XPath
| |
| * CSS locators should use whitespace for readability when using direct descendants.
| |
|
| |
| <source lang="python">
| |
| # Good
| |
| _my_locator = "css=#content > p > a"
| |
|
| |
| # Bad
| |
| _my_locator = "css=#content>p>a"
| |
| </source>
| |
|
| |
| * Use Python tuples to define locators:
| |
|
| |
| <source lang="python">
| |
| # Good
| |
| _my_locator = (By.ID, "content")
| |
| </source>
| |
|
| |
| == 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:
| |
|
| |
| <source lang="python">
| |
| 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()
| |
| </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:
| |
| <source lang="python">
| |
| my_page.header.click_login()
| |
| </source>
| |
|
| |
| 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 [https://docs.python.org/2/reference/simple_stmts.html#the-assert-statement assert] statement.
| |
| * When doing equivalency assertions, put the expected value first, followed by the actual value, for example:
| |
| <source lang="python">
| |
| # Good
| |
| a = some_function()
| |
| assert 'expected result' == a
| |
|
| |
| # Bad
| |
| a = some_function()
| |
| assert a == 'expected result'
| |
| </source>
| |
| == How to use GaiaHeader and GaiaBinaryControl ==
| |
| TBD - jlorenzo
| |
|
| |
|
| = Submitting and Reviewing Patches = | | = Submitting and Reviewing Patches = |