├── .gitignore ├── LICENSE ├── README.md ├── pytest.pptx ├── requirements.txt ├── test_custom_markers.py ├── test_fixtures.py ├── test_parameters.py ├── test_simple_assert.py ├── test_skip.py └── test_xfail.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *.log 4 | *.cache 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 bnx05 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytest + selenium: demo tests 2 | 3 | ## Setup instructions (this assumes that you already have Python and pip installed) 4 | 5 | - If you clone this repo, you can do `pip install -r requirements.txt` to install both pytest and 6 | selenium at once. Otherwise, you can install them one by one. Note that you will still need to install geckodriver, since Selenium 3 does not support default Firefox: 7 | 8 | 9 | ### pytest 10 | 11 | - Install [pytest](http://doc.pytest.org/en/latest/getting-started.html) via pip or easy_install: 12 | ``` 13 | pip install -U pytest or easy_install -U pytest 14 | ``` 15 | - After installation, you can check the installed version by running: 16 | ``` 17 | pytest --version 18 | ``` 19 | 20 | ### selenium webdriver 21 | 22 | - Install [selenium](http://selenium-python.readthedocs.io/installation.html) via pip: 23 | ``` 24 | pip install -U selenium 25 | ``` 26 | 27 | ### geckodriver 28 | 29 | #### Unix 30 | 31 | - Download tar file from [geckodriver](https://github.com/mozilla/geckodriver/releases) 32 | - Go to download location and extract the file: 33 | ``` 34 | tar -xvzf geckodriver 35 | ``` 36 | - Make geckodriver executable: 37 | ``` 38 | chmod +x geckodriver 39 | ``` 40 | - Add geckodriver executable to your `PATH`. The default `PATH` is set in the `/etc/environment` 41 | file. Add the location there if it is not already set, e.g. 42 | ``` 43 | PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:" 44 | ``` 45 | - Alternatively, you can move the executable to the location/s specified in your `PATH` variable 46 | 47 | #### Windows 48 | 49 | - Download and extract zip file from [geckodriver](https://github.com/mozilla/geckodriver/releases) 50 | - Get the executable file's location and add it to your Environment Variables. Instructions on 51 | setting the path can be found [here](http://www.computerhope.com/issues/ch000549.htm) 52 | 53 | ## How to run the tests 54 | 55 | - To run all tests inside the test directory, simply run this command: 56 | ``` 57 | pytest 58 | or python -m pytest 59 | ``` 60 | This will open multiple Firefox browsers, since each module is calling a separate Firefox instance. 61 | - To run a test module, simply do 62 | ``` 63 | pytest 64 | e.g. pytest test_markers.py 65 | ``` 66 | - To run a specific test, do 67 | ``` 68 | pytest 69 | e.g. pytest test_markers.py::TestSignupPage::test_assert_empty_signup_form 70 | ``` 71 | - It is recommended to run the tests with verbosity by invoking `-v` during the test run. 72 | - If there are `print` statements you wish to see on the console when the tests run, add `-s` to disable stdout 73 | capturing. 74 | -------------------------------------------------------------------------------- /pytest.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnx05/pytest-selenium/edd7b595a47d8853d3bfe94b36bbeb841552c7df/pytest.pptx -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | selenium 3 | -------------------------------------------------------------------------------- /test_custom_markers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import pytest 4 | from selenium import webdriver 5 | 6 | 7 | browser = webdriver.Firefox() 8 | browser.maximize_window() 9 | 10 | 11 | # This test will run when 'login_test' is called when invoking -m, 12 | # e.g. 'pytest test_custom_markers.py -m "login_test"'. 13 | # This test will run when 'empty_form_test' is called when invoking -m, 14 | # e.g. 'pytest test_custom_markers.py -m "empty_form_test"'. 15 | @pytest.mark.login_test 16 | @pytest.mark.empty_form_test 17 | def test_assert_empty_login_form(): 18 | browser.get("https://start.engagespark.com/sign-in/") 19 | assert browser.find_element_by_name("login").get_attribute("value") == "" 20 | assert browser.find_element_by_name("password").get_attribute("value") == "" 21 | 22 | 23 | # This test will run when 'login_test' is called when invoking -m, 24 | # e.g. 'pytest test_custom_markers.py -m "login_test"'. 25 | # This test will run when 'empty_form_test' is called when invoking -m, 26 | # e.g. 'pytest test_custom_markers.py -m "empty_form_test"'. 27 | @pytest.mark.login_test 28 | @pytest.mark.input_attribute_test 29 | def test_assert_login_input_field_type(): 30 | browser.get("https://start.engagespark.com/sign-in/") 31 | assert browser.find_element_by_name("login").get_attribute("type") == "email" 32 | assert browser.find_element_by_name("password").get_attribute("type") == "password" 33 | 34 | 35 | # The tests in this class will run when 'signup_test' is called when invoking -m, 36 | # e.g. 'pytest test_custom_markers.py -m "signup_test"'. 37 | @pytest.mark.signup_test 38 | class TestSignupPage: 39 | 40 | # This test will run when 'empty_form_test' is called when invoking -m, 41 | # e.g. 'pytest test_custom_markers.py -m "empty_form_test"'. 42 | @pytest.mark.empty_form_test 43 | def test_assert_empty_signup_form(self): 44 | browser.get("https://start.engagespark.com/sign-up/") 45 | assert browser.find_element_by_name("email").get_attribute("value") == "" 46 | assert browser.find_element_by_name("password1").get_attribute("value") == "" 47 | 48 | # This test will run when 'input_attribute_test' is called when invoking -m, 49 | # e.g. 'pytest test_custom_markers.py -m "input_attribute_test"'. 50 | @pytest.mark.input_attribute_test 51 | def test_assert_signup_input_field_type(self): 52 | browser.get("https://start.engagespark.com/sign-up/") 53 | assert browser.find_element_by_name("email").get_attribute("type") == "email" 54 | assert browser.find_element_by_name("password1").get_attribute("type") == "password" 55 | -------------------------------------------------------------------------------- /test_fixtures.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import pytest 5 | 6 | from selenium import webdriver 7 | from selenium.webdriver.common.by import By 8 | from selenium.webdriver.support import expected_conditions as EC 9 | from selenium.webdriver.support.ui import WebDriverWait 10 | 11 | 12 | # Prior to running this module, set the environment variable "URL" first. 13 | # Set the value to "https://start.engagespark.com/sign-in/" for the Login test. 14 | # For the Signup test, set the value to https://start.engagespark.com/sign-up/". 15 | 16 | 17 | class Base: 18 | 19 | # This fixture contains the set up and tear down code for each test. 20 | @pytest.fixture(autouse=True) 21 | def browser_setup_and_teardown(self): 22 | self.browser = webdriver.Firefox() 23 | self.browser.maximize_window() 24 | self.wait = WebDriverWait(self.browser, 30) 25 | yield 26 | self.browser.close() 27 | self.browser.quit() 28 | 29 | # This fixture opens the URL set in the environment variable and waits 30 | # until a form is visible before proceeding. 31 | @pytest.fixture() 32 | def open_url(self): 33 | self.browser.get(os.getenv("URL")) 34 | self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "form"))) 35 | 36 | def enter_credentials(self, input_field, credential): 37 | self.browser.find_element_by_name(input_field).click() 38 | self.browser.find_element_by_name(input_field).send_keys(credential) 39 | 40 | 41 | class TestSignup(Base): 42 | 43 | # When this test runs, it will use the 'browser_setup_and_teardown' fixture first, followed by 44 | # the 'open_url' fixture before running the test itself. 45 | @pytest.mark.usefixtures("open_url") 46 | def test_signup_without_captcha(self): 47 | print("Setting email for sign up") 48 | self.enter_credentials(input_field="email", credential="sample_email@blah.org") 49 | print("Setting password for sign up") 50 | self.enter_credentials(input_field="password1", credential="password1") 51 | self.browser.find_element_by_css_selector("input[type='checkbox']").click() 52 | assert not self.browser.find_element_by_css_selector("#signupform button").is_enabled() 53 | 54 | 55 | class TestLogin(Base): 56 | 57 | # When this test runs, it will use the 'browser_setup_and_teardown' fixture first, followed by 58 | # the 'open_url' fixture before running the test itself. 59 | @pytest.mark.usefixtures("open_url") 60 | def test_login_with_inexistent_accounts(self): 61 | print("Setting email for sign up") 62 | self.enter_credentials(input_field="login", credential="another_imaginary_email@blah.org") 63 | print("Setting password for sign up") 64 | self.enter_credentials(input_field="password", credential="password2") 65 | self.browser.find_element_by_css_selector("#loginform button").click() 66 | print("Error notification should appear") 67 | self.wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, "notification-message"), "The e-mail address and/or password you specified are not correct.")) 68 | -------------------------------------------------------------------------------- /test_parameters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import pytest 4 | import time 5 | 6 | from selenium import webdriver 7 | 8 | 9 | sample_email_address = "demo@engagespark.com" 10 | sample_password = "Password123" 11 | email_addresses = ["invalid_email", "another_invalid_email@", "not_another_invalid_email@blah"] 12 | passwords = ["weak_password", "generic_password", "bleep_password"] 13 | 14 | 15 | browser = webdriver.Firefox() 16 | browser.maximize_window() 17 | 18 | 19 | # this test checks the maxlength attribute of the login and password fields 20 | @pytest.mark.parametrize("field_name, maxlength", [ 21 | ("login", "75"), 22 | ("password", "128"), 23 | ]) 24 | def test_assert_field_maxlength(field_name, maxlength): 25 | browser.get("https://start.engagespark.com/sign-in/") 26 | time.sleep(5) 27 | browser.find_element_by_name(field_name).get_attribute("maxlength") == maxlength 28 | 29 | 30 | # this test asserts the string length of values entered in the login and 31 | # password fields 32 | @pytest.mark.parametrize("field_name, sample_string, string_length", [ 33 | ("login", sample_email_address, 20), 34 | ("password", sample_password, 11), 35 | ]) 36 | def test_assert_email_and_password_length(field_name, sample_string, string_length): 37 | browser.get("https://start.engagespark.com/sign-in/") 38 | time.sleep(5) 39 | browser.find_element_by_name(field_name).click() 40 | browser.find_element_by_name(field_name).send_keys(sample_string) 41 | assert len(browser.find_element_by_name(field_name).get_attribute("value")) == string_length 42 | 43 | 44 | # this test checks if the login button is enabled after entering different 45 | # combinations of invalid values in the email and password fields 46 | @pytest.mark.parametrize("email", email_addresses) 47 | @pytest.mark.parametrize("password", passwords) 48 | def test_assert_login_button_enabled(email, password): 49 | browser.get("https://start.engagespark.com/sign-in/") 50 | time.sleep(5) 51 | browser.find_element_by_name("login").click() 52 | browser.find_element_by_name("login").send_keys(email) 53 | browser.find_element_by_name("password").click() 54 | browser.find_element_by_name("password").send_keys(password) 55 | assert browser.find_element_by_xpath("//button[contains(text(), 'Login')]").is_enabled() 56 | 57 | 58 | # this test checks if the values entered into the email field contain '@' 59 | @pytest.mark.parametrize("email", [ 60 | "123@abc.org", 61 | "info@engagespark.com", 62 | "blah", 63 | ]) 64 | def test_assert_valid_email_entry(email): 65 | browser.get("https://start.engagespark.com/sign-in/") 66 | time.sleep(5) 67 | browser.find_element_by_name("login").click() 68 | browser.find_element_by_name("login").send_keys(email) 69 | assert "@" in browser.find_element_by_name("login").get_attribute("value") 70 | -------------------------------------------------------------------------------- /test_simple_assert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import time 4 | 5 | from selenium import webdriver 6 | 7 | 8 | browser = webdriver.Firefox() 9 | browser.maximize_window() 10 | 11 | 12 | def test_assert_title_of_homepage(): 13 | browser.get("https://www.engagespark.com/") 14 | assert browser.title == "Home - Send and Receive Automated Call and SMS Text Campaigns." 15 | 16 | 17 | def test_assert_presence_of_login_button(): 18 | browser.get("https://www.engagespark.com/") 19 | assert browser.find_element_by_link_text("login") 20 | 21 | 22 | def test_assert_availability_of_login_page(): 23 | browser.get("https://www.engagespark.com/") 24 | browser.find_element_by_link_text("login").click() 25 | time.sleep(10) 26 | assert browser.title == "engageSPARK - Sign In" 27 | assert browser.find_element_by_id("loginform") 28 | -------------------------------------------------------------------------------- /test_skip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import pytest 4 | import time 5 | 6 | from selenium import webdriver 7 | 8 | 9 | browser = webdriver.Firefox() 10 | browser.maximize_window() 11 | 12 | 13 | # This test will not run since it has been marked 'skip' 14 | @pytest.mark.skip(reason="reset password unavailable from navbar") 15 | def test_assert_reset_password_in_navbar(): 16 | browser.get("https://start.engagespark.com/sign-in/") 17 | assert browser.find_element_by_css_selector("ul[class*='navbar-right'] a[href='/reset-password/']") 18 | 19 | 20 | # The tests in this class will not run if the browser instance uses PhantomJS 21 | @pytest.mark.skipif( 22 | browser == webdriver.PhantomJS, reason="headless browser not suitable for demo") 23 | class TestSignupPage: 24 | 25 | def test_assert_terms_of_service_link_in_form(self): 26 | browser.get("https://start.engagespark.com/sign-up/") 27 | time.sleep(10) 28 | assert browser.find_element_by_link_text("Terms of Service") 29 | -------------------------------------------------------------------------------- /test_xfail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import pytest 4 | 5 | from selenium import webdriver 6 | 7 | 8 | browser = webdriver.Firefox() 9 | browser.maximize_window() 10 | 11 | 12 | # This test will run but will be marked as 'xfail' if it fails; however, it will 13 | # be marked as 'xpass' if it passes. 14 | @pytest.mark.xfail(reason="button is disabled") 15 | def test_assert_login_with_empty_fields(): 16 | browser.get("https://start.engagespark.com/sign-in/") 17 | browser.find_element_by_xpath("//button[contains(text(), 'Login')]").click() 18 | 19 | 20 | class TestSignupPage: 21 | 22 | # This test will run but will be marked as 'xfail' if it fails; however, it 23 | # will be marked as 'xpass' if it passes. 24 | @pytest.mark.xfail(strict=True, reason="button is disabled") 25 | def test_signup_with_empty_fields(self): 26 | browser.get("https://start.engagespark.com/sign-up/") 27 | browser.find_element_by_xpath("//button[contains(text(), 'Sign Up')]").click() 28 | --------------------------------------------------------------------------------