Selenium in Python - tinasamson/QAautomation GitHub Wiki
Go to the folder were your project is and create a virtualenv running the following command:
python3 -m venv .venv
Activate the virtualenv:
source .venv/bin/activate
Then execute the following command to install selenium:
pip install selenium
Install selenium manager:
pip install webdriver-manager
Create a python file, for example: example.py.
In this file import this:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
You then create a new chrome instance by adding this:
driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
After that include all the methods and assertions you need.
Execute your test in the console like this:
python example.py
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("headless") #to run headless
chrome_options.add_argument("--ignore-certificate-errors") #To ignore all the certifications errors
chrome_options.add_argument("--start-maximized")
driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=chrome_options) #to add the options
maximize_window() : Is to maximize the window
title : To know the title of the page (tab)
current_url : To get the url
send_keys(): To type something in a input.
click(): To click on an element.
text: To get the text of an element.
clear(): To delete any text in an input
get_attribute() : get attribute of html tag. For example: -> get_attribute("name") = "email"
is_selected(): This returns a boolean, if a checkbox is selected or not.
is_displayed(): Returns boolean, if element is displayed or not. if you want to validate it is not displayed, use assert not.
driver.find_element(By.ID, "button") # in html id="button"
driver.find_element(By.NAME, "email") # in html name="email"
tagname[attribute='value'] for example input[data='element1'] for multiple elements you can use nth-child() for example: form div:nth-child(2) input
driver.find_element(By.CSS_SELECTOR, "input[data="element1"]) # in html <input data="element1"></input>
//tagname[@attribute='value'] for example //input[@data='element1'] for multiple element you can choose one by using the index: //input[@data='element1'][1] To find text: //buton[text()='Continue']
driver.find_element(By.XPATH, "//input[@data='element1']")
driver.find_element(By.XPATH, "//input[contains(@href='element1')]")
driver.find_element(By.CLASS_NAME, "alert-success") #in html: <div class="alert-success">
driver.find_element(By.LINK_TEXT, "Forgot password") #in html: <a href="">Forgot password</a>
You can use Select for static dropdowns and when the HTML has
for selenium.webdriver.support.select.Select import Select
dropdown = Select(driver.find_element(By.ID, "dropdown1"))
dropdown.select_by_index(1) # it is going to select the second element of the dropdown
dropdown.select_by_visible_text("Women") # To select any option based on the text
dropdown.select_by_value() # to select from a value attribute for example <option value="age"></option>
When you update value dynamicaly through script, you extract the text using get_attribute("value")
driver.find_element(By.ID, "autosuggest").send_keys("Arg")
sleep.time(2) # import time
countries = driver.find_elements(By.CLASS_NAME, "li.menu-items a") #To get all the elements in the dropdown
for country in countries:
if country.text == "Argentina":
country.click()
break
print(driver.find_element(By.ID, "autosuggest").get_attribute("value"))
checkboxes = driver.find_elements(By.CSS_SELECTOR, "[type='checkbox']")
for checkbox in checkboxes:
if checkbox.get_attribute("value") == "option2":
checkbox.click()
assert checkbox.is_selected() #Validates if checkbox is selected
break
radiobuttons = driver.find_elements(By.CSS_SELECTOR, ".radiobutton")
radiobuttons[2].click() # Do this if you know the position will not change
alert = driver.switch_to.alert
print(alert.text) # output would be the text of the alert
alert.accept() # clicks on the button to accept the alert
alert.dismiss() # clicks on the button to dismiss the alert
Similar to global timeout. If an element is not shown on page, it will wait a max of 2 seconds for that to show up. If it find the element faster, it is not going to wait that whole 2 seconds. It will not work for find_elements() method because it is plural and will give a list, empty list is also valid but this happens when elements are still loading. In this specific case use time.sleep()
driver.implicitly_wait(2)
It is to target a specific element and set exclusively on that element to wait
wait = WebDriverWait(driver, 10)
wait.until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, ".buttonInfo")))
always write on the end of your method perform()
action = ActionChains(driver)
action.click_and_hold(locator) # it will click on element and it keeps holding on like a long press, usually used in mobile
action.double_click(locator) # to double click
action.context_click(locator) # To right click
action.drag_and_drop()
action.move_to_element(locator).perform()
windowOpened = driver.window_handles #will get all the names of all the windows which are open into a list
driver.switch_to.window(windowOpened[1]) #switch to the window/tab with name of the tab
driver.closed() #only child window/tab would be closed
driver.switch_to.window(windowOpened[0]) # switch to parent window/tab
driver.switch_to.frame("idOfFrame")
driver.switch_to.default_content() #it goes back to default, were driver is originally initiated
driver.execute_script("window.scrollby(0, document.body.scrollHeight);") #To execute javascript methods to scroll down
driver.get_screenshot_as_file("screen.png") #To take a screenshot and save it into a file
It is important that the button has tag type="file"
file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
file_input.send_keys("/home/file.xlxs")
fruit_name = "Apple"
priceColumn = 4
driver.find_element(By.XPATH, "//div[text()='"+fruit_name+"']/parent::div/parent::div/div[@id='cell-"+priceColumn+"-undefined']").text
If you use Python's unittest module as framework to write test cases then you need to import:
import unittest
After import ing and creating the new chrome instance (see 2.how to creat first test)
We need to create the first test case and we are going to use the unittest module for that. The only way to tell the unittest module that this is a test case is using Class that is inherited from unittest.TestCase.
class PythonOrgSearch(unittest.TestCase):
Inside this class we are going to create a setUp method for initialization and this method will get called before every test function in this test case class.
def setUp(self):
self.driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
After this we create all the test cases we need, for example:
def test_example(self):
Here we include all the methods and assertions you need for this test case.
We finish by a tearDown method. This method will get called after every test method and it is a place to do cleanup actions. For example:
def tearDown(self):
self.drive.close
The final lines are some boiler plate code to run the test suite:
if __name__ == "__main__":
unittest.main()
Run the following command to install pytest:
pip install -U pytest
Create a directory called "tests", inside this folder create an init file. Here we are going to create our tests. We create a new file with a method that calls our tests cases and then execute the test case we want. This file name has to start with test_ and also our method name should start with test_ Example:
from tests.test_search import ExampleSearch
def test_example():
driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
example_search = ExampleSearch(driver)
example_search.test_search()
and another file that contains a class with our tests cases. Example:
class ExampleSearch:
def __init__(self, driver):
self.driver = driver
def test_search(self):
Execute your test in the console like this:
pytest # runs all tests
pytest -v #Runs all tests with more information, v is for verbose -> more info metadata
pytest -v -s # To see all the logs in output
pytest example.py # runs one test
pytest -k product -v -s #Will run method name execution
pytest -m smoke #Will run all the test with pytest mark
To mark(tag) some test to run for smoke or for regression. You can use pytest mark in the method:
@pytest.mark.smoke
def test_example():
print(This is test for smoke testing)
You have to create a pytest.ini file to add this smoke tag and avoid warnings in output:
[pytest] #important so that it is considered a pytest config file
asyncio_default_fixture_loop_scope = function #To eliminate pytestDeprecationWarning
markers = smoke: This are smoke test
To skip a test use: @pytest.mark.skip To run a test without reporting if it passes or fails: @pytest.mark.xfail
This can be use to run pre conditions before or after a method. To execute steps after your test method you have to use the yield keyword
@pytest.fixture()
def setup()
print("This will execute before my test starts")
yield
print("This will execute after my test")
def test_example(setup):
pass
To use it in class:
@pytest.mark.usefixtures("setup")
class Example:
def test_1(self):
pass
To run the fixture only before a class and after a class:
@pytest.fixture(scope="class")
def setup()
print("This will execute before my test starts")
yield
print("This will execute after my test")
@pytest.fixture()
def dataload()
return ["email", "name", "url"]
@pytest.mark.usefixtures("dataload")
class Example:
def test_1(self, dataload):
print(dataload)
You need to use the request instance when you need to have fixtures with some values available
@pytest.fixture(params=["chrome", "Firefox", "IE"])
def crossBrowser(request)
return request.param
def test_crossbroser(crossBrowser):
print(crossBrowser) #this will run 3 times and print the browser name
How to send multiple values in one run:
@pytest.fixture(params=[("chrome", "user1"), ("Firefox", "user2), ("IE", "user3")])
def crossBrowser(request)
return request.param
def test_crossbroser(crossBrowser):
print(crossBrowser[1]) #this will run 3 times and print all the users
In conftest file:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
# how to register environment variable - mandatory
def pytest_addoption(parser):
parser.addoption(
"--browser_name", action="store", default="chrome", help="default browser instance"
)
@pytest.fixture(scope="function")
def browserInstance(request):
browser_name = request.config.getoption("browser_name")
if browser_name == "chrome":
service_obj = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service_obj)
driver.implicitly_wait(5)
elseif browser_name == "firefox":
#add firefox driver
yield driver #returns driver
driver.close()
This is how you return the driver to your method:
def test_example(browserInstance):
driver = browserInstance
To send environment variable (browser_name like the fixture above) in your commands and run your test:
pytest test_example.py --browser_name chrome
Instead of repeating the same method in different file, you can create a conftest.py file so you can use generic fixture in all your tests.
You have to install
pip install pytest-html
To run it:
pytest --html=report.html -v -s
pytest --html reports/reports.html
Add this hook into de conftest file:
import pytest
driver = none # create a global driver variable
@pytest.fixture( scope="function" )
def browserInstance(request):
global driver # Use the global driver variable
@pytest.hookimpl( hookwrapper=True )
def pytest_runtest_makereport(item):
"""
Extends the PyTest Plugin to take and embed screenshot in html report, whenever test fails.
:param item:
"""
pytest_html = item.config.pluginmanager.getplugin( 'html' )
outcome = yield
report = outcome.get_result()
extra = getattr( report, 'extra', [] )
if report.when == 'call' or report.when == "setup":
xfail = hasattr( report, 'wasxfail' )
if (report.skipped and xfail) or (report.failed and not xfail):
reports_dir = os.path.join( os.path.dirname( __file__ ), 'reports' )
file_name = os.path.join( reports_dir, report.nodeid.replace( "::", "_" ) + ".png" )
print( "file name is " + file_name )
_capture_screenshot( file_name )
if file_name:
html = '<div><img src="%s" alt="screenshot" style="width:304px;height:228px;" ' \
'onclick="window.open(this.src)" align="right"/></div>' % file_name
extra.append( pytest_html.extras.html( html ) )
report.extras = extra
def _capture_screenshot(file_name):
driver.get_screenshot_as_file(file_name) # We are creating global driver variable for this
One of best practices is to use class for all your methods of a specific page. And in every class method you can return the next page object For example the loginPage:
class LoginPage:
def __init__(self, driver):
self.driver = driver
self.username_input = (By.ID, "username")
self.password_input = (By.Name, "password")
self.signin_button_input = (By.ID, "signInButton")
def login(self):
# It doesn´t expect a tuple but two argument,
# use * to break down the tuple and split into two parameters
self.driver.find_element(*self.username_input).send_keys("username")
self.driver.find_element(*self.password_input).send_keys("pass1234")
self.driver.find_element(*self.signin_button_input).click()
return ShopPage(self.driver) # return next page object
You have to call this into your test method:
def test_e2e():
driver = browserInstance()
driver.get("url.com")
login_page = LoginPage(driver)
shop_page = login_page.login() # Save the return page object to new variable
If there are class methods that are the same in different class, you can create a Utils folder, there create a new file and create a parent class
class BrowserUtils:
def __init__(self, driver):
self.driver = driver
def getTitle(self):
return self driver.title()
How to activate the driver into the parent class in your page objects is by using super() and then initialize it with the driver:
class LoginPage(BrowserUtils):
def __init__(self, driver):
super().__init__(driver)
self.driver = driver
def login(self):
pass
And then I can use the getTitle() method in the method of the test (see above test_e2e) using login_page.getTitle()
Create a new json file with this structure:
{
"data": [
{
"username" = "tester1"
},
{
"username" = "tester2"
}
]
}
To use the data in your test:
with open(test_data_path) as f:
test_data = json.load(f)
test_list = test_data["data"]
# This fixture expects a list and will run in this example 2 times, the first param is each item inside the list
@pytest.mark.parametrize("test_list_item", test_list)
def test_e2e(browserInstance, test_list_item):
LoginPage.login(test_list_item["username"])
Install this plugin:
pip install pytest-xdist
Then run this, to run 2 test in parallel:
pytest -n 2
Download the java you need to run jenkins. Then download jenkins. If you allready used an older version of jenkins, clean op everything by deleting everything inside your home/<user>/.jenkins
How to invoke jenkins in the terminal, in your terminal go were you downloaded your jenkins file:
java -jar jenkins.war --httpPort=9090
You will get a password, go to your localhost with port 9090 (can be another httpPort) and put the password. Then install all the necessary plugins. After that create first admin user. After completed the configuration.
Create a new item, here we will create our new job (enter job name). Select "freeStyle project" and click on "oke".
Our project is locally, so click on advanced and select custom workspace, then enter the path of your project.
If your project is on github, select source code management -> git.
Important: in the build steps section and select "execute shell", enter command:
cd filename
pytest --browser_name firefox --html=reports/report.html
After saving, go to the "build now" button in the menu to trigger.
To add parameters, go to the configuration and in the general section, select "this project is parameterized" and enter param name (example: browser) and param value (example: chrome, firefox). Go to the menu and select "build with parameters" and then you can select which param you are going to choose. Modify your command in the build steps:
cd filename
pytest --browser_name "$browser" --html=reports/report.html
In the configuration, go to the build trigger section here is were you can schecule your job. Choose "build periodically". We have to pass the schedule and this has to be a cron expression, for example:
0 6 * * *
were:
0 (minutes) - all minutes is 0
6 (hour) - at 6am
* (days of the month) - every day of the month
* (month) - every month
* (days of the week) - every day of the week