Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Updating test templates to modernize them

Tests now run against quality.mozilla.org
  • Loading branch information...
commit d9ef128dad6b3ae9720caf022405b1f1dc94e121 1 parent aaa0d9a
@bobsilverberg bobsilverberg authored
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
Please sign in to comment.
Something went wrong with that request. Please try again.