UI automation test: Selenium+PO mode + Pytest+Allure integration

Posted by bimo on Mon, 14 Feb 2022 12:52:35 +0100

At present, I haven't involved in webui automatic testing in my work, but in order to improve my technology, it's not harmful to learn more. There's no more nonsense. At present, the mainstream webui testing framework should still be selenium. Considering maintainability, expansibility and reusability, we use PO mode to write our script, This document also mainly integrates Selenium+PO mode + Pytest+Allure. Let's get to the point. Note: Github address is attached at the end of the article

Technical premise: basic knowledge of python, selenium and pytest

1. Project structure directory:

 

2. Introduction to Po mode

PO mode features:

  • Easy to maintain
  • High reusability
  • The script is easy to read and understand

PO mode elements:

1. In PO mode, it is abstractly encapsulated into a BasePage class, which should have an attribute that only implements webdriver instances

2. Each pag inherits the BasePage, manages the elements in the page through the driver, and encapsulates the operations in the page into methods one by one

3. TestCase relies on the page class to implement the corresponding test steps

 

3. BasePage page encapsulation

import logging
import os
import time
from datetime import datetime
from time import sleep
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from Utils.myLog import MyLog

"""
    This class encapsulates all operations, and all pages inherit this class
"""


class BasePage(object):

    def __init__(self, driver):
        self.logger = MyLog().getLog()
        self.driver = driver

    # Wait for elements to be visible
    def wait_eleVisible(self, loc, timeout=30, poll_frequency=0.5, model=None):
        """
        :param loc:Element location expression;Tuple type,Expression(Element location type,Element positioning method)
        :param timeout:Upper limit of waiting
        :param poll_frequency:Polling frequency
        :param model:When waiting fails,Screenshot operation,Function labels to be expressed in picture files
        :return:None
        """
        self.logger.info(f'wait for"{model}"element,Positioning mode:{loc}')
        try:
            start = datetime.now()
            WebDriverWait(self.driver, timeout, poll_frequency).until(EC.visibility_of_element_located(loc))
            end = datetime.now()
            self.logger.info(f'wait for"{model}"duration:{end - start}')
        except TimeoutException:
            self.logger.exception(f'wait for"{model}"Element failed,Positioning mode:{loc}')
            # screenshot
            self.save_webImgs(f"Wait element[{model}]An exception occurred")
            raise

    # Wait element not visible
    def wait_eleNoVisible(self, loc, timeout=30, poll_frequency=0.5, model=None):
        """
        :param loc:Element location expression;Tuple type,Expression(Element location type,Element positioning method)
        :param timeout:Upper limit of waiting
        :param poll_frequency:Polling frequency
        :param model:When waiting fails,Screenshot operation,Function labels to be expressed in picture files
        :return:None
        """
        logging.info(f'wait for"{model}"disappear,Element positioning:{loc}')
        try:
            start = datetime.now()
            WebDriverWait(self.driver, timeout, poll_frequency).until_not(EC.visibility_of_element_located(loc))
            end = datetime.now()
            self.logger.info(f'wait for"{model}"duration:{end - start}')
        except TimeoutException:
            self.logger.exception(f'wait for"{model}"Element failed,Positioning mode:{loc}')
            # screenshot
            self.save_webImgs(f"Wait element[{model}]Vanishing anomaly")
            raise

    # Find an element element
    def find_element(self, loc, model=None):
        self.logger.info(f'lookup"{model}"Element, element positioning:{loc}')
        try:
            return self.driver.find_element(*loc)
        except NoSuchElementException:
            self.logger.exception(f'lookup"{model}"Element failed,Positioning mode:{loc}')
            # screenshot
            self.save_webImgs(f"Find element[{model}]abnormal")
            raise

    # Find element elements
    def find_elements(self, loc, model=None):
        self.logger.info(f'lookup"{model}"Element set:{loc}')
        try:
            return self.driver.find_elements(*loc)
        except NoSuchElementException:
            self.logger.exception(f'lookup"{model}"Element set failed,Positioning mode:{loc}')
            # screenshot
            self.save_webImgs(f"Find element set[{model}]abnormal")
            raise

    # Input operation
    def input_text(self, loc, text, model=None):
        # Find element
        ele = self.find_element(loc, model)
        # Input operation
        self.logger.info(f'stay"{model}"input"{text}",Element positioning:{loc}')
        try:
            ele.send_keys(text)
        except:
            self.logger.exception(f'"{model}"Input operation failed!')
            # screenshot
            self.save_webImgs(f"[{model}]Input exception")
            raise

    # Clear operation
    def clean_inputText(self, loc, model=None):
        ele = self.find_element(loc, model)
        # Clear operation
        self.logger.info(f'eliminate"{model}",Element positioning:{loc}')
        try:
            ele.clear()
        except:
            self.logger.exception(f'"{model}"Clear operation failed')
            # screenshot
            self.save_webImgs(f"[{model}]Clear exception")
            raise

    # Click operation
    def click_element(self, loc, model=None):
        # Find the element first and click
        ele = self.find_element(loc, model)
        # Click operation
        self.logger.info(f'click"{model}",Element positioning:{loc}')
        try:
            ele.click()
        except:
            self.logger.exception(f'"{model}"Click failed')
            # screenshot
            self.save_webImgs(f"[{model}]Click exception")
            raise

    # Get text content
    def get_text(self, loc, model=None):
        # Find the element first and get the text content
        ele = self.find_element(loc, model)
        # Get text
        self.logger.info(f'obtain"{model}"Element text content, element positioning:{loc}')
        try:
            text = ele.text
            self.logger.info(f'obtain"{model}"The element text content is"{text}",Element positioning:{loc}')
            return text
        except:
            self.logger.exception(f'obtain"{model}"Element text content failed,Element positioning:{loc}')
            # screenshot
            self.save_webImgs(f"obtain[{model}]Text content exception")
            raise

    # Get property value
    def get_element_attribute(self, loc, name, model=None):
        # First find the element to get the attribute value
        ele = self.find_element(loc, model)
        # Get element attribute value
        self.logger.info(f'obtain"{model}"Element attribute:{loc}')
        try:
            ele_attribute = ele.get_attribute(name)
            self.logger.info(f'obtain"{model}"element"{name}"Property set is"{ele_attribute}",Element positioning:{loc}')
            return ele_attribute
        except:
            self.logger.exception(f'obtain"{model}"element"{name}"Property failed,Element positioning:{loc}')
            # screenshot
            self.save_webImgs(f"obtain[{model}]Attribute exception")
            raise

    # iframe switching
    def switch_iframe(self, frame_refer, timeout=30, poll_frequency=0.5, model=None):
        # Wait for iframe to exist
        self.logger.info('iframe Switching operation:')
        try:
            # Switch = = index\name\id\WebElement
            WebDriverWait(self.driver, timeout, poll_frequency).until(
                EC.frame_to_be_available_and_switch_to_it(frame_refer))
            sleep(0.5)
            self.logger.info('Switch successful')
        except:
            self.logger.exception('iframe Switching failed!!!')
            # screenshot
            self.save_webImgs(f"iframe Switching exception")
            raise

    # Window switching = if switching to a new window, new If it is to return to the default window, default
    def switch_window(self, name, cur_handles=None, timeout=20, poll_frequency=0.5, model=None):
        """
        Get before calling window_handles
        :param name: new Represents the latest open window. default Represents the first window. Other values are represented as the of the window handles
        :param cur_handles:
        :param timeout:Upper limit of waiting
        :param poll_frequency:Polling frequency
        :param model:When waiting fails,Screenshot operation,Function labels to be expressed in picture files
        :return:
        """
        try:
            if name == 'new':
                if cur_handles is not None:
                    self.logger.info('Switch to the latest open window')
                    WebDriverWait(self.driver, timeout, poll_frequency).until(EC.new_window_is_opened(cur_handles))
                    window_handles = self.driver.window_handles
                    self.driver.swich_to.window(window_handles[-1])
                else:
                    self.logger.exception('Switching failed,There is no information to switch windows!!!')
                    self.save_webImgs("Switching failed_There is no information to switch windows")
                    raise
            elif name == 'default':
                self.logger.info('Switch to default page')
                self.driver.switch_to.default()
            else:
                self.logger.info('Switch to handles Window of')
                self.driver.swich_to.window(name)
        except:
            self.logger.exception('Failed to switch windows!!!')
            # screenshot
            self.save_webImgs("Switching failed_There is no information to switch windows")
            raise

    # screenshot
    def save_webImgs(self, model=None):
        # filepath = refers to the image saving directory / model (page function name)_ The current time is seconds png
        # Screenshot saving directory
        # Splice log folder, if it does not exist, will be created automatically
        cur_path = os.path.dirname(os.path.realpath(__file__))
        now_date = time.strftime('%Y-%m-%d', time.localtime(time.time()))
        screenshot_path = os.path.join(os.path.dirname(cur_path), f'Screenshots\\{now_date}')
        if not os.path.exists(screenshot_path):
            os.mkdir(screenshot_path)
        # current time 
        dateNow = time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time()))
        # route
        filePath = '{}\\{}_{}.png'.format(screenshot_path, model, dateNow)
        try:
            self.driver.save_screenshot(filePath)
            self.logger.info(f"Screen capture successful,Picture path is{filePath}")
        except:
            self.logger.exception('Screen capture failed!')

    # sign out
    def get_driver(self):
        return self.driver

4. Page inheritance BasPage

from Common.basePage import BasePage
from selenium.webdriver.common.by import By
from time import sleep


class BaiduIndex(BasePage):
    """
    Page element
    """
    # Baidu link home page
    baidu_index_url = "https://www.baidu.com"
    # Search box
    search_input = (By.ID, "kw")
    # "Baidu click" button box
    search_button = (By.ID, "su")

    # Query operation
    def search_key(self, search_key):
        self.logger.info("[===Search operation===]")
        # Wait for the user name text box element to appear
        self.wait_eleVisible(self.search_input, model='Search box')
        # Input content
        self.input_text(self.search_input, "Atri", model="Search box")
        # Clear text box contents
        self.clean_inputText(self.search_input, model='Search box')
        # enter one user name
        self.input_text(self.search_input, text=search_key, model='Search box')
        # Wait for the search button to appear
        self.wait_eleVisible(self.search_button, model='"use Baidu Search"Search button')
        # Click the search button
        self.click_element(self.search_button, model='"use Baidu Search"Search button')
        # Wait for the interface to load after searching
        self.driver.implicitly_wait(10)
        sleep(3)

5. pytest+allure write test cases

Note: please refer to: https://www.cnblogs.com/huny/p/13752406.html

import os
import time
import pytest
import allure
from time import sleep
from selenium import webdriver
from PageObject.baiduIndex import BaiduIndex

driver = webdriver.Chrome()
baidu_index = BaiduIndex(driver)


@pytest.fixture(scope="class")
def init():
    # Open the browser to access the login page
    baidu_index.logger.info("\nWebDriver Initializing...")
    driver.get(baidu_index.baidu_index_url)
    baidu_index.logger.info(f"Open link: {baidu_index.baidu_index_url}...")
    # window maximizing
    driver.maximize_window()
    # Implicit waiting
    driver.implicitly_wait(10)
    baidu_index.logger.info("WebDriver Initialization complete!")
    yield
    driver.quit()
    baidu_index.logger.info("WebDriver Exit successfully...")


@allure.feature("Baidu search")
class TestBaiduSearch:

    @allure.story("Search for specified keywords")
    @pytest.mark.baidu_search
    @pytest.mark.parametrize("key_word", [
        "ha-ha",
        "ha-ha",
    ], )
    def test_search(self, init, key_word):
        # @pytest. mark. Parameterize parameterization
        baidu_index.search_key(key_word)
        web_title = driver.title
        assert "ha-ha_Baidu search" == web_title

6. Generate Allure test report

Github address: https://github.com/Zimo6/Selenium_Demo

The following is the supporting materials. For friends who do [software testing], it should be the most comprehensive and complete war preparation warehouse. This warehouse has also accompanied me through the most difficult journey. I hope it can also help you!

Finally, it can be in the official account: programmer Hao! Get a 216 page interview document of Software Test Engineer for free. And the corresponding video learning tutorials for free!, It includes basic knowledge, Linux essentials, Shell, Internet program principles, Mysql database, special topics of packet capture tools, interface test tools, test advanced Python programming, Web automation test, APP automation test, interface automation test, advanced continuous integration of test, test architecture, development test framework, performance test, security test, etc.

If my blog is helpful to you and you like my blog content, please click "like", "comment" and "collect" for three times! Friends who like software testing can join our testing technology exchange group: 779450660 (there are various software testing resources and technical discussions)

Topics: Selenium software testing UI