Initial commit

This commit is contained in:
Mat Hare 2021-07-10 17:58:17 +01:00
commit bb2eefe470
9 changed files with 584 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__/
.pytest_cache/

16
Pipfile Normal file
View File

@ -0,0 +1,16 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
selenium = "*"
pytest = "*"
pytest-bdd = "*"
webdriver-manager = "*"
sttable = "*"
[dev-packages]
[requires]
python_version = "3.8"

262
Pipfile.lock generated Normal file
View File

@ -0,0 +1,262 @@
{
"_meta": {
"hash": {
"sha256": "ffac70183633b651a15be3d779fb1d1017f5de3e4647b2f78ebe0f213954fe05"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"atomicwrites": {
"hashes": [
"sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197",
"sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"
],
"markers": "sys_platform == 'win32'",
"version": "==1.4.0"
},
"attrs": {
"hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"certifi": {
"hashes": [
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
],
"version": "==2021.5.30"
},
"chardet": {
"hashes": [
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
},
"colorama": {
"hashes": [
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
],
"markers": "sys_platform == 'win32'",
"version": "==0.4.4"
},
"configparser": {
"hashes": [
"sha256:85d5de102cfe6d14a5172676f09d19c465ce63d6019cf0a4ef13385fc535e828",
"sha256:af59f2cdd7efbdd5d111c1976ecd0b82db9066653362f0962d7bf1d3ab89a1fa"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.2"
},
"crayons": {
"hashes": [
"sha256:bd33b7547800f2cfbd26b38431f9e64b487a7de74a947b0fafc89b45a601813f",
"sha256:e73ad105c78935d71fe454dd4b85c5c437ba199294e7ffd3341842bc683654b1"
],
"version": "==0.4.0"
},
"glob2": {
"hashes": [
"sha256:85c3dbd07c8aa26d63d7aacee34fa86e9a91a3873bc30bf62ec46e531f92ab8c"
],
"version": "==0.7"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"mako": {
"hashes": [
"sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab",
"sha256:aea166356da44b9b830c8023cd9b557fa856bd8b4035d6de771ca027dfc5cc6e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.4"
},
"markupsafe": {
"hashes": [
"sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
"sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.1"
},
"packaging": {
"hashes": [
"sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
"sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
],
"markers": "python_version >= '3.6'",
"version": "==21.0"
},
"parse": {
"hashes": [
"sha256:9ff82852bcb65d139813e2a5197627a94966245c897796760a3a2a8eb66f020b"
],
"version": "==1.19.0"
},
"parse-type": {
"hashes": [
"sha256:089a471b06327103865dfec2dd844230c3c658a4a1b5b4c8b6c16c8f77577f9e",
"sha256:7f690b18d35048c15438d6d0571f9045cffbec5907e0b1ccf006f889e3a38c0b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.5.2"
},
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"py": {
"hashes": [
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.10.0"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
],
"index": "pypi",
"version": "==6.2.4"
},
"pytest-bdd": {
"hashes": [
"sha256:304cd2b09923b838d0c2f08331d1f4236a14ef3594efa94e3bdae0f384d3fa5d",
"sha256:7c5221680cec9a97630e1fae6132f4a97c2f86a90914206ee06a55ae1a409fe5"
],
"index": "pypi",
"version": "==4.1.0"
},
"requests": {
"hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1"
},
"selenium": {
"hashes": [
"sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c",
"sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"
],
"index": "pypi",
"version": "==3.141.0"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sttable": {
"hashes": [
"sha256:305d75b48e409993a766f3c1322bb7244c9c3729a377b98d21aba8047a0f669c",
"sha256:46653f283fc0482f338db54d2d8f5d2c4d558eafd565a155acb5e3fbcf3e7b33"
],
"index": "pypi",
"version": "==0.0.1"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"urllib3": {
"hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.6"
},
"webdriver-manager": {
"hashes": [
"sha256:50a6e174106542f5335cacc387cec7ada26812babc1aeca61c208a1bab2ac2c5",
"sha256:c6d81590aae6fc0fb10cf7dd20c8c1b9bb043501f9cf62c316a854a0de841e32"
],
"index": "pypi",
"version": "==3.4.2"
}
},
"develop": {}
}

5
config.json Normal file
View File

@ -0,0 +1,5 @@
{
"browser": "Chrome",
"headless": true,
"implicit_wait": 10
}

117
conftest.py Normal file
View File

@ -0,0 +1,117 @@
import pytest
import selenium.webdriver
import json
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
from pytest_bdd import given, then, parsers
from pages.base import BasePage
@pytest.fixture
def config(scope='session'):
BROWSERS = ['Chrome', 'Firefox']
# Read config file
with open('config.json') as config_file:
config = json.load(config_file)
# Assert values are acceptable
assert config['browser'] in BROWSERS
assert isinstance(config['headless'], bool)
assert isinstance(config['implicit_wait'], int)
assert config['implicit_wait'] > 0
# Return config so it can be used
return config
@pytest.fixture
def browser(config):
# Initialize the WebDriver instance
if config['browser'] == 'Chrome':
opts = webdriver.ChromeOptions()
if config['headless']:
opts.add_argument('headless')
b = webdriver.Chrome(ChromeDriverManager().install(), options=opts)
elif config['browser'] == 'Firefox':
opts = webdriver.FirefoxOptions()
if config['headless']:
opts.headless = True
b = webdriver.Firefox(
executable_path=GeckoDriverManager().install(), options=opts)
else:
raise Exception(f'Browser "{config["browser"]}" is not supported')
# Make call wait up to 10 seconds for elements to appear
b.implicitly_wait(config['implicit_wait'])
# Return the WebDriver instance for the setup
yield b
# Quit the WebDriver instance for the teardown
b.quit()
@pytest.fixture()
def datatable():
return DataTable()
class DataTable(object):
def __init__(self):
pass
def __str__(self):
dt_str = ''
for field, value in self.__dict__.items():
dt_str = f'{dt_str}\n{field} = {value}'
return dt_str
def __repr__(self) -> str:
return self.__str__()
@given(parsers.parse('I have navigated to the \'the-internet\' "{page_name}" page'), target_fixture='navigate_to')
def navigate_to(browser, page_name):
BASE_URL = "https://the-internet.herokuapp.com"
PAGE_URLS = {
"home": BASE_URL + "/",
"checkboxes": BASE_URL + "/checkboxes",
"dropdown": BASE_URL + "/dropdown",
"dynamic controls": BASE_URL + "/dynamic_controls",
"form authentication": BASE_URL + "/login",
"inputs": BASE_URL + "/inputs",
"secure area": BASE_URL + "/secure"
}
browser.get(PAGE_URLS.get(page_name.lower()))
@then(parsers.parse('a "{text}" banner is displayed in the top-right corner of the page'))
def verify_banner_text(browser, text):
url = 'https://github.com/tourdedave/the-internet'
assert text == BasePage(browser).get_github_fork_banner_text()
assert url == BasePage(browser).get_github_fork_banner_link()
styleAttrs = BasePage(browser).get_github_fork_banner_position().split(";")
for attr in styleAttrs:
if attr.startswith("position"):
assert "absolute" == attr.split(": ")[1]
if attr.startswith("top"):
assert "0px" == attr.split(": ")[1]
if attr.startswith("right"):
assert "0px" == attr.split(": ")[1]
if attr.startswith("border"):
assert "0px" == attr.split(": ")[1]
@then(parsers.parse('the page has a footer containing "{text}"'))
def verify_footer_text(browser, text):
pass
@then(parsers.parse('the link in the page footer goes to "{url}"'))
def verify_footer_link_url(browser, url):
pass

View File

@ -0,0 +1,69 @@
Feature: Home Page
Tests for the 'https://the-internet.herokuapp.com/' home page
Background: Open home page
Given I have navigated to the 'the-internet' "home" page
@focus
Scenario: Verify home page contents are correct
Then the page title is "Welcome to the-internet"
And the sub-header text is "Available Examples"
And a list of the following sub-pages is displayed
| name |
| A/B Testing |
| Add/Remove Elements |
| Basic Auth |
| Broken Images |
| Challenging DOM |
| Checkboxes |
| Context Menu |
| Digest Authentication |
| Disappearing Elements |
| Drag and Drop |
| Dropdown |
| Dynamic Content |
| Dynamic Controls |
| Dynamic Loading |
| Entry Ad |
| Exit Intent |
| File Download |
| File Upload |
| Floating Menu |
| Forgot Password |
| Form Authentication |
| Frames |
| Geolocation |
| Horizontal Slider |
| Hovers |
| Infinite Scroll |
| Inputs |
| JQuery UI Menus |
| JavaScript Alerts |
| JavaScript onload event error |
| Key Presses |
| Large & Deep DOM |
| Multiple Windows |
| Nested Frames |
| Notification Messages |
| Redirect Link |
| Secure File Download |
| Shadow DOM |
| Shifting Content |
| Slow Resources |
| Sortable Data Tables |
| Status Codes |
| Typos |
| WYSIWYG Editor |
And a "Fork me on GitHub" banner is displayed in the top-right corner of the page
And the page has a footer containing "Powered by Elemental Selenium"
And the link in the page footer goes to "http://elementalselenium.com/"
# Scenario Outline: Open <page> page
# When I click on the "<page>" link
# Then the "<page>" page opens
# Examples:
# | page |
# | Checkboxes |
# | Dropdown |
# | Dynamic Controls |
# | Form Authentication |
# | Inputs |

49
pages/base.py Normal file
View File

@ -0,0 +1,49 @@
from abc import ABC, abstractmethod
from selenium.webdriver.common.by import By
class BasePage():
# public static WebDriver driver;
# BASE_URL = "https://the-internet.herokuapp.com";
# PAGE_URLS = {
# "home": BASE_URL + "/",
# "checkboxes": BASE_URL + "/checkboxes",
# "dropdown": BASE_URL + "/dropdown",
# "dynamic controls": BASE_URL + "/dynamic_controls",
# "form authentication": BASE_URL + "/login",
# "inputs": BASE_URL + "/inputs",
# "secure area": BASE_URL + "/secure"
# }
@property
@abstractmethod
def PAGE_TITLE(self):
pass
@abstractmethod
def get_page_title_text(self):
pass
FORK_LINK = (By.XPATH, "/html/body/div[2]/a")
FORK_LINK_IMG = (By.XPATH, "/html/body/div[2]/a/img")
FOOTER = (By.ID, "page-footer")
def __init__(self, browser):
self.browser = browser
def get_github_fork_banner_text(self):
return self.browser.find_element(*self.FORK_LINK_IMG).get_attribute("alt")
def get_github_fork_banner_link(self):
return self.browser.find_element(*self.FORK_LINK).get_attribute("href")
def get_github_fork_banner_position(self):
return self.browser.find_element(*self.FORK_LINK_IMG).get_attribute("style")
def get_page_footer_text(self):
return self.browser.find_element(*self.FOOTER).text
def get_page_footer_link_url(self):
return self.browser.find_element(*self.FOOTER).get_attribute("href")

42
pages/home.py Normal file
View File

@ -0,0 +1,42 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from pages.base import BasePage
class HomePage(BasePage):
@property
def PAGE_TITLE(self):
return (By.TAG_NAME, 'h1')
SUBHEADER = (By.TAG_NAME, 'h2')
SUBPAGE_LINKS = (By.XPATH, '//*[@id="content"]/ul/li')
def __init__(self, browser):
self.browser = browser
def get_page_title_text(self):
return self.browser.find_element(*self.PAGE_TITLE).text
def get_subheader_text(self):
return self.browser.find_element(*self.SUBHEADER).text
def get_subpage_list(self):
links = self.browser.find_elements(*self.SUBPAGE_LINKS)
titles = [link.text.split(" (")[0] for link in links]
return titles
# URL = "https://www.duckduckgo.com"
# SEARCH_INPUT = (By.ID, 'search_form_input_homepage')
# def __init__(self, browser):
# self.browser = browser
# def load(self):
# self.browser.get(self.URL)
# def search(self, phrase):
# search_input = self.browser.find_element(*self.SEARCH_INPUT)
# search_input.send_keys(phrase + Keys.RETURN)

View File

@ -0,0 +1,22 @@
from pytest_bdd import scenarios, given, when, then, parsers
from pages.home import HomePage as page
from sttable import parse_str_table
scenarios('../features/home_page.feature')
@then(parsers.parse('the page title is "{title}"'))
def verify_page_title(browser, title):
assert title == page(browser).get_page_title_text()
@then(parsers.parse('the sub-header text is "{text}"'))
def verify_subheader_text(browser, text):
assert text == page(browser).get_subheader_text()
@then(parsers.parse('a list of the following sub-pages is displayed\n{subpages}'))
def verify_subpage_list(browser, datatable, subpages):
expected = parse_str_table(subpages)
for field in expected.fields:
assert expected.columns[field] == page(browser).get_subpage_list()