Skip to content
Browse files

Updating test templates to modernize them

Tests now run against quality.mozilla.org
  • Loading branch information...
1 parent aaa0d9a commit d9ef128dad6b3ae9720caf022405b1f1dc94e121 @bobsilverberg bobsilverberg committed Mar 5, 2013
Showing with 337 additions and 167 deletions.
  1. +1 −1 mozwebqa.cfg
  2. +27 −8 pages/base.py
  3. +94 −0 pages/home.py
  4. +114 −14 pages/page.py
  5. +0 −55 pages/page_object.py
  6. +3 −2 requirements.txt
  7. +0 −25 tests/test_file.py
  8. +98 −0 tests/test_home_page.py
  9. +0 −30 tests/test_nondestructive.py
  10. +0 −32 tests/test_xfail.py
View
2 mozwebqa.cfg
@@ -1,4 +1,4 @@
[DEFAULT]
api = webdriver
-baseurl = https://www.mozilla.org
+baseurl = https://quality.mozilla.org
tags = mozwebqa-template
View
35 pages/base.py
@@ -3,18 +3,37 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
-from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.common.by import By
from pages.page import Page
class Base(Page):
- '''
+ """
Base class for global project specific functions
- '''
+ """
+
@property
- def page_title(self):
- WebDriverWait(self.selenium, 10).until(lambda s: self.selenium.title)
- return self.selenium.title
+ def footer(self):
+ """Return the common Footer region."""
+ return self.Footer(self.testsetup)
+
+ class Footer(Page):
+ """The common Footer region that is present on every page."""
- def go_to_home_page(self):
- self.selenium.get(self.base_url)
+ # The locators in this list contain examples of positional locators, a unique css locator,
+ # and a link text locator
+ copyright_links_list = [
+ {
+ 'locator': (By.CSS_SELECTOR, '#copyright a:nth-of-type(1)'),
+ 'url_suffix': 'http://www.mozilla.org/privacy-policy.html',
+ }, {
+ 'locator': (By.CSS_SELECTOR, '#copyright a:nth-of-type(2)'),
+ 'url_suffix': 'http://www.mozilla.org/about/legal.html'
+ }, {
+ 'locator': (By.CSS_SELECTOR, '#site-info a.licence'),
+ 'url_suffix': 'http://www.mozilla.org/foundation/licensing/website-content.html'
+ }, {
+ 'locator': (By.LINK_TEXT, 'About'),
+ 'url_suffix': '/about/'
+ }
+ ]
View
94 pages/home.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import time
+import datetime
+
+from selenium.webdriver.common.by import By
+
+from base import Base
+from page import PageRegion
+
+
+class HomePage(Base):
+ """This Page Object models the QMO Home Page (https://quality.mozilla.org/)."""
+
+ # The title of this page, which is used by is_the_current_page() in page.py
+ _page_title = u'QMO \u2013 quality.mozilla.org | The Home of Mozilla QA'
+
+ # Locators for the home page
+ _tagline_locator = (By.ID, 'tagline')
+ _news_items_locator = (By.TAG_NAME, 'article')
+
+ # Link locators, which can be used for checking visibility, accuracy and validity of links
+ team_links_list = [
+ {
+ 'locator': (By.CSS_SELECTOR, 'ul.teams-list > li:nth-of-type(1) > a'),
+ 'url_suffix': '/teams/web-qa/',
+ }, {
+ 'locator': (By.CSS_SELECTOR, 'ul.teams-list > li:nth-of-type(2) > a'),
+ 'url_suffix': '/teams/browser-technologies/',
+ }, {
+ 'locator': (By.CSS_SELECTOR, 'ul.teams-list > li:nth-of-type(3) > a'),
+ 'url_suffix': '/teams/thunderbird/',
+ }, {
+ 'locator': (By.CSS_SELECTOR, 'ul.teams-list > li:nth-of-type(4) > a'),
+ 'url_suffix': '/teams/automation/',
+ }, {
+ 'locator': (By.CSS_SELECTOR, 'ul.teams-list > li:nth-of-type(5) > a'),
+ 'url_suffix': '/teams/desktop-firefox/',
+ }
+ ]
+
+ def go_to_page(self):
+ """Open the home page."""
+ self.open('/')
+
+ @property
+ def tagline(self):
+ """Return the text of the tagline."""
+ return self.find_element(*self._tagline_locator).text
+
+ @property
+ def news_items_count(self):
+ """Return the number of news items on the home page."""
+ return len(self.find_elements(*self._news_items_locator))
+
+ @property
+ def news_items(self):
+ """Return a list of new items, each of which is a single news item from the home page."""
+ return [self.NewsItem(self.testsetup, web_element)
+ for web_element in self.find_elements(*self._news_items_locator)]
+
+ class NewsItem(PageRegion):
+ """Allows each news item on the home page to be treated as a separate object."""
+
+ _title_locator = (By.CSS_SELECTOR, 'h1 > a')
+ _entry_posted_locator = (By.CSS_SELECTOR, 'p.entry-posted')
+ _month_posted_locator = (By.CLASS_NAME, 'posted-month')
+ _day_posted_locator = (By.CLASS_NAME, 'posted-date')
+ _year_posted_locator = (By.CLASS_NAME, 'posted-year')
+
+ @property
+ def title(self):
+ """Return the title of the news item."""
+ return self.find_element(*self._title_locator).text
+
+ @property
+ def is_post(self):
+ """Return True if the item is a post (as opposed to an event)."""
+ return self.is_element_present(*self._entry_posted_locator)
+
+ @property
+ def date_posted(self):
+ """Return the date posted as a Python date."""
+ year = self.find_element(*self._year_posted_locator).text
+ month = self.find_element(*self._month_posted_locator).text
+ day = self.find_element(*self._day_posted_locator).text
+ time_struct = time.strptime(
+ '%s %s %s' % (year, month, day),
+ '%Y %b %d'
+ )
+ return datetime.date.fromtimestamp(time.mktime(time_struct))
View
128 pages/page.py
@@ -3,50 +3,150 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
-from unittestzero import Assert
+import time
+
+import requests
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import ElementNotVisibleException
+from selenium.common.exceptions import TimeoutException
+from unittestzero import Assert
class Page(object):
- '''
- Base class for all Pages
- '''
+ """Base class for all Pages"""
def __init__(self, testsetup):
- '''
- Constructor
- '''
+ """Constructor"""
+
self.testsetup = testsetup
self.base_url = testsetup.base_url
self.selenium = testsetup.selenium
+ self.timeout = testsetup.timeout
+ self._selenium_root = hasattr(self, '_root_element') and self._root_element or self.selenium
+
+ def open(self, url_fragment):
+ """Open the specified url_fragment, which is relative to the base_url, in the current window."""
+ self.selenium.get(self.base_url + url_fragment)
+ self.is_the_current_page
+
+ @property
+ def page_title(self):
+ """Return the page title from Selenium."""
+ WebDriverWait(self.selenium, self.timeout).until(lambda s: self.selenium.title)
+ return self.selenium.title
@property
def is_the_current_page(self):
+ """Return true if the actual page title matches the expected title stored in _page_title."""
if self._page_title:
- WebDriverWait(self.selenium, 10).until(lambda s: self.selenium.title)
-
- Assert.equal(self.selenium.title, self._page_title,
- "Expected page title: %s. Actual page title: %s" % (self._page_title, self.selenium.title))
+ Assert.equal(self.page_title, self._page_title,
+ "Expected page title: %s. Actual page title: %s" % (self._page_title, self.page_title))
return True
def is_element_present(self, *locator):
+ """
+ Return true if the element at the specified locator is present in the DOM.
+ Note: It returns false immediately if the element is not found.
+ """
self.selenium.implicitly_wait(0)
try:
- self.selenium.find_element(*locator)
+ self._selenium_root.find_element(*locator)
return True
except NoSuchElementException:
return False
finally:
- # set back to where you once belonged
+ # set the implicit wait back
self.selenium.implicitly_wait(self.testsetup.default_implicit_wait)
def is_element_visible(self, *locator):
+ """
+ Return true if the element at the specified locator is visible in the browser.
+ Note: It uses an implicit wait if it cannot find the element immediately.
+ """
try:
- return self.selenium.find_element(*locator).is_displayed()
+ return self._selenium_root.find_element(*locator).is_displayed()
except (NoSuchElementException, ElementNotVisibleException):
return False
+ def is_element_not_visible(self, *locator):
+ """
+ Return true if the element at the specified locator is not visible in the browser.
+ Note: It returns true immediately if the element is not found.
+ """
+ self.selenium.implicitly_wait(0)
+ try:
+ return not self._selenium_root.find_element(*locator).is_displayed()
+ except (NoSuchElementException, ElementNotVisibleException):
+ return True
+ finally:
+ # set the implicit wait back
+ self.selenium.implicitly_wait(self.testsetup.default_implicit_wait)
+
+ def wait_for_element_present(self, *locator):
+ """Wait for the element at the specified locator to be present in the DOM."""
+ count = 0
+ while not self.is_element_present(*locator):
+ time.sleep(1)
+ count += 1
+ if count == self.timeout:
+ raise Exception(*locator + ' has not loaded')
+
+ def wait_for_element_visible(self, *locator):
+ """Wait for the element at the specified locator to be visible in the browser."""
+ count = 0
+ while not self.is_element_visible(*locator):
+ time.sleep(1)
+ count += 1
+ if count == self.timeout:
+ raise Exception(*locator + " is not visible")
+
+ def wait_for_element_not_present(self, *locator):
+ """Wait for the element at the specified locator to be not present in the DOM."""
+ self.selenium.implicitly_wait(0)
+ try:
+ WebDriverWait(self.selenium, self.timeout).until(lambda s: len(self.find_elements(*locator)) < 1)
+ return True
+ except TimeoutException:
+ Assert.fail(TimeoutException)
+ finally:
+ self.selenium.implicitly_wait(self.testsetup.default_implicit_wait)
+
def get_url_current_page(self):
+ """Return the url for the current page."""
return(self.selenium.current_url)
+
+ def find_element(self, *locator):
+ """Return the element at the specified locator."""
+ return self._selenium_root.find_element(*locator)
+
+ def find_elements(self, *locator):
+ """Return a list of elements at the specified locator."""
+ return self._selenium_root.find_elements(*locator)
+
+ def get_response_code(self, url):
+ """Return the response code for a get request to the specified url."""
+ requests_config = {'max_retries': 5}
+ try:
+ r = requests.get(url, verify=False, allow_redirects=True, config=requests_config, timeout=self.timeout)
+ return r.status_code
+ except requests.Timeout:
+ return 408
+
+ def link_destination(self, locator):
+ """Return the href attribute of the element at the specified locator."""
+ link = self.find_element(*locator)
+ return link.get_attribute('href')
+
+ def image_source(self, locator):
+ """Return the src attribute of the element at the specified locator."""
+ link = self.find_element(*locator)
+ return link.get_attribute('src')
+
+
+class PageRegion(Page):
+ """Base class for a page region (generally an element in a list of elements)."""
+
+ def __init__(self, testsetup, element):
+ self._root_element = element
+ Page.__init__(self, testsetup)
View
55 pages/page_object.py
@@ -1,55 +0,0 @@
-#!/usr/bin/env python
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-from selenium.webdriver.common.by import By
-
-from base import Base
-from page import Page
-
-
-class MySiteHomePage(Base):
-
- _some_locator_by_id = (By.ID, 'someLocator')
- _some_locator_by_css = (By.CSS_SELECTOR, '#someLocator')
- _some_locator_by_xpath = (By.XPATH, "//div[@id='someLocator']")
- _some_elements_locator = (By.CSS_SELECTOR, 'li .someElementsLocator')
-
- # Demo locators
- _page_title = u"Mozilla \u2014 Home of the Mozilla Project \u2014 mozilla.org"
- _header_locator = (By.CSS_SELECTOR, 'h1')
-
- @property
- def header_text(self):
- return self.selenium.find_element(*self._header_locator).text
-
- @property
- def element_attribute(self):
- return self.selenium.find_element(*self._some_locator_by_id).get_attribute('someAttribute')
-
- @property
- def elements_count(self):
- return len(self.selenium.find_elements(*self._some_locator_by_id))
-
- def click_on_element(self):
- self.selenium.find_element(*self._some_locator_by_id).click()
-
- def news_item(self, lookup):
- return self.NewsList(self.testsetup, lookup)
-
- class NewsList(Page):
- _home_news_locator = (By.CSS_SELECTOR, '#home-news-list li')
- _link_locator = (By.CSS_SELECTOR, 'a')
-
- def __init__(self, testsetup, lookup):
- Page.__init__(self, testsetup)
- self._root_element = self.selenium.find_element(By.CSS_SELECTOR,
- "%s:nth-child(%s)" % (self._home_news_locator[1], lookup))
-
- @property
- def name(self):
- return self._root_element.text
-
- def click_link(self):
- self._root_element.find_element(*self._link_locator).click()
View
5 requirements.txt
@@ -1,10 +1,11 @@
# This pulls in all the libraries needed to run Selenium tests
# on Mozilla WebQA projects
-PyYAML==3.10
-UnittestZero
+BeautifulSoup==3.2.0 # Only required for doing link checking without Selenium
py==1.4.9
pytest==2.2.4
pytest-mozwebqa==1.0
+PyYAML==3.10
requests==0.13.3
selenium
+UnittestZero
View
25 tests/test_file.py
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import pytest
-
-from unittestzero import Assert
-from pages.page_object import MySiteHomePage
-
-
-class TestTemplate():
-
- @pytest.mark.nondestructive
- def test_load_baseurl_and_assert_header(self, mozwebqa):
- '''
- Demo Test - Verify Header is correct on Amo Page
- '''
- home_page = MySiteHomePage(mozwebqa)
- home_page.go_to_home_page()
- Assert.equal(home_page.header_text, 'We are')
-
- @pytest.mark.nondestructive
- def test_that_we_do_something_to_find_a_bug(self, mozwebqa):
- pass
View
98 tests/test_home_page.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import datetime
+
+from BeautifulSoup import BeautifulStoneSoup # Only required for the test that doesn't use Selenium
+import pytest
+import requests
+from unittestzero import Assert
+
+from pages.home import HomePage
+
+
+class TestHomePage:
+
+ @pytest.mark.nondestructive
+ def test_that_page_has_correct_tagline(self, mozwebqa):
+ home_page = HomePage(mozwebqa)
+ home_page.go_to_page()
+ Assert.equal('The home of Mozilla QA', home_page.tagline)
+
+ @pytest.mark.nondestructive
+ def test_that_page_has_news_items(self, mozwebqa):
+ home_page = HomePage(mozwebqa)
+ home_page.go_to_page()
+ Assert.greater(home_page.news_items_count, 0)
+
+ @pytest.mark.nondestructive
+ def test_that_news_items_are_sorrted_in_reverse_chronological_order(self, mozwebqa):
+ home_page = HomePage(mozwebqa)
+ home_page.go_to_page()
+ news_items = home_page.news_items
+ most_recent_date = datetime.date.today()
+ for news_item in news_items:
+ if news_item.is_post:
+ news_item_date = news_item.date_posted
+ Assert.greater_equal(most_recent_date, news_item_date, 'News items are out of sequence. %s is not after %s.' % (most_recent_date, news_item_date))
+ most_recent_date = news_item_date
+
+ # The following 3 tests check for visibilty, accuracy and validity of the team links on the home page
+ @pytest.mark.nondestructive
+ def test_that_team_links_are_visible(self, mozwebqa):
+ home_page = HomePage(mozwebqa)
+ home_page.go_to_page()
+ bad_links = []
+ for link in home_page.team_links_list:
+ if not home_page.is_element_visible(*link.get('locator')):
+ bad_links.append('The link at %s is not visible' % link.get('locator')[1:])
+ Assert.equal(0, len(bad_links), '%s bad links found: ' % len(bad_links) + ', '.join(bad_links))
+
+ @pytest.mark.nondestructive
+ def test_that_team_link_destinations_are_correct(self, mozwebqa):
+ home_page = HomePage(mozwebqa)
+ home_page.go_to_page()
+ bad_links = []
+ for link in home_page.team_links_list:
+ url = home_page.link_destination(link.get('locator'))
+ if not url.endswith(link.get('url_suffix')):
+ bad_links.append('%s does not end with %s' % (url, link.get('url_suffix')))
+ Assert.equal(0, len(bad_links), '%s bad links found: ' % len(bad_links) + ', '.join(bad_links))
+
+ @pytest.mark.nondestructive
+ def test_that_team_link_urls_are_valid(self, mozwebqa):
+ home_page = HomePage(mozwebqa)
+ home_page.go_to_page()
+ bad_links = []
+ for link in home_page.team_links_list:
+ url = home_page.link_destination(link.get('locator'))
+ response_code = home_page.get_response_code(url)
+ if response_code != requests.codes.ok:
+ bad_links.append('%s is not a valid url - status code: %s.' % (url, response_code))
+ Assert.equal(0, len(bad_links), '%s bad urls found: ' % len(bad_links) + ', '.join(bad_links))
+
+ # This test checks the validity of all links on the page, and doesn't use Selenium
+ # Note: this is just an example and will take a long time to run if you run it against quality.mozilla.org
+ @pytest.mark.skip_selenium
+ @pytest.mark.nondestructive
+ def test_that_checks_the_validity_of_all_links_on_the_page(self, mozwebqa):
+ home_page = HomePage(mozwebqa)
+ url = mozwebqa.base_url
+ page_response = requests.get(url, verify=False)
+ html = BeautifulStoneSoup(page_response.content)
+ bad_links = []
+ links = html.findAll('a')
+ for link in links:
+ url = self.make_absolute(link['href'], mozwebqa.base_url)
+ response_code = home_page.get_response_code(url)
+ if response_code != requests.codes.ok:
+ bad_links.append('%s is not a valid url - status code: %s.' % (url, response_code))
+ Assert.equal(0, len(bad_links), '%s bad urls found: ' % len(bad_links) + ', '.join(bad_links))
+
+ def make_absolute(self, url, base_url):
+ if url.startswith('http'):
+ return url
+ return base_url + url
View
30 tests/test_nondestructive.py
@@ -1,30 +0,0 @@
-#!/usr/bin/env python
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import pytest
-
-from unittestzero import Assert
-from pages.page_object import MySiteHomePage
-
-
-class TestNonDestructive():
-
- @pytest.mark.nondestructive
- def test_load_baseurl_nondestructive(self, mozwebqa):
- """
- This test is nondestructive - it does not write
- to the database or leave a mark on the website
- """
- home_page = MySiteHomePage(mozwebqa)
- home_page.go_to_home_page()
- Assert.true(home_page.is_the_current_page)
-
- def test_load_baseurl_destructive(self, mozwebqa):
- """
- This test is *not* marked as nondestructive and
- will be automatically skipped when run against
- a sensitive (ie Production) URL
- """
- pass
View
32 tests/test_xfail.py
@@ -1,32 +0,0 @@
-#!/usr/bin/env python
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import pytest
-
-from unittestzero import Assert
-from pages.page_object import MySiteHomePage
-
-
-class TestXfail():
-
- @pytest.mark.nondestructive
- @pytest.mark.xfail(reason='Header text is incorrect')
- def test_load_baseurl_xfail(self, mozwebqa):
- '''
- This test will fail but we have marked it with XFail
- The test result will be XFail
- '''
- home_page = MySiteHomePage(mozwebqa)
- home_page.go_to_home_page()
- Assert.equal(home_page.header_text, 'BlaBla')
-
- @pytest.mark.nondestructive
- @pytest.mark.xfail
- def test_that_we_do_something_to_find_a_bug(self, mozwebqa):
- '''
- This test is marked with XFail but it will pass.
- The test result will be XPassed
- '''
- pass

0 comments on commit d9ef128

Please sign in to comment.
Something went wrong with that request. Please try again.