Selenium in Python - tinasamson/QAautomation GitHub Wiki

1. How to start: Instalation and drivers

1.1. Instalation

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

1.2. Drivers:

Install selenium manager:

pip install webdriver-manager

2. How to create first file

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

2.1. Chromeoptions

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

3. Basic webdriver methods

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.

3.1. Locators:

3.1.1. ID

driver.find_element(By.ID, "button") # in html id="button"

3.1.2. Name

driver.find_element(By.NAME, "email") # in html name="email"

3.1.3. CSS selector

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>

3.1.4. Xpath

//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')]")

3.1.5. Classname

driver.find_element(By.CLASS_NAME, "alert-success")  #in html: <div class="alert-success">

3.1.6. Linktext

driver.find_element(By.LINK_TEXT, "Forgot password")  #in html: <a href="">Forgot password</a>

3.2 Dropdown

3.2.1. Static dropdown

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>

3.2.2. Dynamic dropdown

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"))

3.3. Checkbox

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

3.4. Radio button

radiobuttons = driver.find_elements(By.CSS_SELECTOR, ".radiobutton")
radiobuttons[2].click()   # Do this if you know the position will not change

3.5. Handling alert pop-ups

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

3.6. Waits

3.6.1 Implicit waits

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)

3.6.1 Explicit waits

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")))

3.7. Action chains

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()

3.8. Handling child windows/tabs

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

3.9. Frames

driver.switch_to.frame("idOfFrame")
driver.switch_to.default_content()  #it goes back to default, were driver is originally initiated

3.10. Execute javascript

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

3.11. Upload files

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")

3.12. Generate Xpath for query table data dynamically

fruit_name = "Apple"
priceColumn = 4
driver.find_element(By.XPATH, "//div[text()='"+fruit_name+"']/parent::div/parent::div/div[@id='cell-"+priceColumn+"-undefined']").text

4. Frameworks

4.1. Unittests

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()

4.2. Pytest

4.2.1. Instalation

Run the following command to install pytest:

pip install -U pytest

4.2.2. Integration

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

4.2.3. 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

4.2.4. Fixtures

4.2.4.1. Method/class fixtures

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")

4.2.4.2. Data driven fixtures

@pytest.fixture()
def dataload()
  return ["email", "name", "url"]

@pytest.mark.usefixtures("dataload")
class Example:
  def test_1(self, dataload):
    print(dataload)

4.2.4.3. Data driven parameterization set fixtures

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

4.2.4.4. Browser instance fixture

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 

4.2.5. Conftest file

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.

4.2.6. HTML report

You have to install

pip install pytest-html

To run it:

pytest --html=report.html -v -s

pytest --html reports/reports.html
4.2.6.1. Generate screenshots when test fail into html report

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

4.2.7. Page object pattern

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()

4.2.8. Writing test data as json format

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"])

4.2.9. Run test in parallel

Install this plugin:

pip install pytest-xdist

Then run this, to run 2 test in parallel:

pytest -n 2

4.2.10. Jenkins

4.2.10.1. Instalation and create first job

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

4.2.10.2 schedule ci/cd pipeline in jenkins

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

⚠️ **GitHub.com Fallback** ⚠️