Web automated testing python+selenium+data-driven

        The selenium library in python can be used for web page UI automated testing. There are many introductions to selenium methods on the Internet. I won’t introduce them one by one here. There are some methods that we don’t need at all. If you know JavaScript, you can complete many operations just by using the execute_script() method. Here I will focus on the encapsulation of selenium and how to use data-driven in the test framework.

selenium framework

        Installation instructions: pip3 install selenium

        After installing selenium, we can use selenium to create a webdriver to perform UI automated testing of web pages. But to create a webdriver, you also need a browser driver to complete the exchange of information between the python code and the browser. Usually we download the browser driver from the Internet ourselves according to the type and version of the browser, but now we have an easier way to complete the download of the browser driver.

Automatically match browser drivers

        The webdriver_manager library can automatically download the driver for the current browser, so we don't have to download it online based on the browser version.

        Installation instructions: pip3 install webdriver_manager

        Source project address: https://github.com/SergeyPirogov/webdriver_manager

        Use webdriver_manager to easily create browser drivers according to different browser types. The usage is as follows:

from webdriver_manager.microsoft import EdgeChromiumDriverManager  # 导入Edge浏览器管理
from selenium.webdriver.edge.service import Service
path = EdgeChromiumDriverManager().install()  # 使用Edge浏览器管理下载浏览器驱动程序并返回驱动程序路径
print(path)  # 打印出驱动程序路径
# selenium 4.0
driver = webdriver.Edge(service=Service(executable_path=path))  # 创建webdriver

webdriver_manager will query the current browser version by executing terminal commands, and then download the corresponding browser driver based on the browser version. However, there is a drawback to using this method, which is that it needs to be connected to the network. If it is run on the company's restricted network, there will be problems. Therefore, when we do web automation, it is best to use only one version of the browser, and we need to disable automatic browser upgrades.

Create webdriver

        The webdriver module in selenium can create webdriver objects according to different browsers. For example, if we want to create a webdriver object for the Edge browser, use the following code:

from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))

The version used here is selenium 4.0 or above, which is different from version 3.0. In 3.0, executable_path can be passed in directly. After 4.0, executable_path can be passed in in Serveice. In addition, we copied the browser driver downloaded by webdriver_manager above to the current directory and can use it directly. There is no need to download it every time when the browser is not updated.

open the Web page

        To access a web page using the get() method, you need to pass in the URL of the web page. For example, open a Baidu page:

from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')  # 打开百度网页

refresh page

        Use the refresh method to refresh the current page.

from selenium import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.edge.options import Options
import time


option = Options()  # 实例化Options
option.add_experimental_option('detach', True)  # 定义detach为True防止浏览器自动关闭
driver = webdriver.Edge(options=option, service=Service(executable_path='msedgedriver.exe'))  # 添加option
driver.get('http://www.baidu.com')  # 打开百度网页
time.sleep(5)  # 停止5秒钟
driver.refresh()  # 刷新当前页面

        If you want the browser not to be closed after the program ends, you need to pass in custom options when instantiating the webdriver, and the 'detach' of the options is True.

Close page

        Use the close method to close the current page. When we have multiple pages open at the same time, we can use close to close some pages. The premise is that we must first switch the window to the window that needs to be closed.

from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')  # 打开百度网页
driver.close()  # 关闭页面

 Close webdriver

        When we no longer want to use the webdriver, we can use the quit method to close the webdriver and release memory resources.

from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')  # 打开百度网页
driver.quit()  # 关闭driver

Switch page window

        When we want to switch from one page window to another, we can use the switch_to.window method. Use the switch_to method to get an instance of the SwitchTo class. The SwitchTo class is specially used to switch various windows. The window method in the SwitchTo class is used to switch page windows. If you want to switch windows, you must get the window handle. Use window_handles to get a handle list composed of all window handles.

from selenium import webdriver
from selenium.webdriver.edge.service import Service
import time


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')  # 打开百度网页
driver.execute_script('window.open("https://www.csdn.com")')  # 使用JavaScript在新窗口打开csdn网页
time.sleep(5)  # 添加延时利于观察
window_list = driver.window_handles  # 得到页面窗口句柄列表
driver.switch_to.window(window_list[1])  # 切换到第二个页面窗口
driver.close()  # 关闭第二个窗口
time.sleep(5)  # 添加延时利于观察

View element properties

        We can open the browser development tool by pressing F12 on the keyboard in the browser, click the arrow to select the element in the upper left corner of the development tool, and then select an element on the web page, and the properties of the element will be displayed in the development tool interface.

positioned element

        There are many ways to locate elements, but the most suitable for data-driven use are find_element() and find_elements().

find_element method

        The find_element method receives two parameters. The first parameter is the way to find page elements (id, name, class name, tag name, link text, partial link text, xpath, css selector). The second parameter is the method for each search method. Corresponding values, id corresponds to the element's id attribute value, name corresponds to the element's name attribute value, class name corresponds to the element's class attribute value, tag name corresponds to the element's tag, link text, and partial link text correspond to the element's Text content and xpath correspond to the absolute path of the element in the interface, and css selector corresponds to the relative path of the element in the interface.

from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
element = driver.find_element(By.ID, 'su')
print(element)

find_elements method

        The find_elements method is used the same as the find_element method. The difference is that find_elements returns a list of elements, which contains one or more elements with the same attributes. For example, elements whose name attributes are all editor, elements whose class is both el-form, and elements whose tag name is both a. Usually we use the find_elements method when we want to find multiple elements with the same attribute.

from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
elements = driver.find_elements(By.ID, 'su')
print(elements)

await element

        Wait elements can be divided into implicit wait, explicit wait and forced wait.

implicit wait

        The implicit wait for finding elements sets a global waiting time through the implicitly_wait method of the webdriver instance. After setting, all element search operations will be based on this waiting time. If the element is found within the waiting time, the following code will be executed immediately. If the element is not found after the waiting time, an error will be thrown. How to use it:

from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.implicitly_wait(5)  # 设置查找元素隐式等待为5秒

         The implicit waiting for page loading sets a global waiting time through the set_page_load_timeout method of the webdriver instance. After setting, all page loads will be based on this waiting time. If the page is loaded successfully during the waiting time, the following code will be executed immediately. If the page is not loaded after the waiting time, an error will be thrown. How to use it:

from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.set_page_load_timeout(5)  # 设置页面加载隐式等待为5秒

        Asynchronous execution of JavaScript scripts implicitly waits to set a global waiting time through the set_script_timeout method of the webdriver instance. After setting, all asynchronous execution of JavaScript scripts will be based on this waiting time. If the wait time is exceeded and the script execution is not executed, an error is thrown. How to use it:

from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.set_script_timeout(5)  # 设置JavaScript脚本异步执行隐式等待为5秒(execute_async_script)

show wait 

        To display the appearance or disappearance of waiting elements, we usually use an instance of the WebDriverWait class. WebDriverWait is a very simple class with only 4 methods, an __init__ method for initialization, a __repr__ method for outputting debugging information, an until method for waiting for elements to appear, and a method for waiting for elements. Disappeared until_not method. The logic of until and until_not is also very simple and clear, because they are mainly used to implement function callbacks. WebDriverWait can receive 4 parameters when instantiated. The first parameter driver is webdriver; the second parameter timeout is the time to wait for the element to appear or disappear. If the element does not appear or disappear after the time is exceeded, a timeout error will be thrown; the third parameter poll_frequency is the frequency of finding elements. How long does it take to check whether elements appear or disappear? The default is 0.5 seconds; the fourth parameter ignored_exceptions is the error type to be captured when executing the function to find elements. You can pass in a list or tuple. Serializable data, defaulting to NoSuchElementException. If you think WebDriverWait is too troublesome, we can also not use WebDriverWait. It is also very simple to define the waiting function yourself. (In fact, the entire selenium is indeed a bit bloated and redundant)

until method

        The until method is mainly used to wait for the appearance of elements and can receive two parameters. The first parameter is the function used to find the element, which will become a callback function in the until while loop; the second parameter is the error description to be thrown after the search element times out, telling others why the error is thrown. How to use it:

from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
element = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "su"), '查找元素超时')
until_not method

        The until_not method is mainly used to wait for the element to disappear and can receive two parameters. The first parameter is the function used to find the element, which will become a callback function in the until while loop; the second parameter is the error description to be thrown after the search element times out, telling others why the error is thrown. How to use it:

from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
state = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).until_not(lambda x: x.find_element(By.ID, "su").is_displayed(), '元素未消失')
Custom method

        In addition to the above two methods, we can also customize the method of waiting for elements.

def wait(driver: webdriver, by: str, value: str, timeout: float, *, frequency=0.5, display=True):
    """
    等待元素出现或消失
    :param driver: webdriver
    :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
    :param value: 跟by对应的属性值
    :param timeout: 查找元素的时间
    :param frequency: 查找元素的频率
    :param display: 出现或消失 -> True: 出现, False: 消失
    :return: 元素或bool值
    """
    end_time = time.monotonic() + timeout  # 得到查找元素的截至时间
    while True:
        if time.monotonic() > end_time:  # 判断当前时间是否大于截至时间
            break
        try:
            element = driver.find_element(by, value)  # 查找元素
            if element.is_displayed() == display:  # 判断元素是否出现或消失
                return element if display is True else True  # 返回元素或bool值
        except Exception:  # 捕获大多数错误
            if not display:
                return True
        time.sleep(frequency)
    raise TimeoutError(f'元素{by}={value}在{timeout}秒内未{"出现" if display else "消失"}')

 forced wait

        Forced waiting is to use the sleep function in time to delay the execution of the following code.

operating elements

        After we locate an element, we can do some operations on this element. For example, click, drag, enter text, double-click, etc.

click method

        The click method is used to click on an element when we locate a clickable element. You can use the click method on it to make it produce the effect of being clicked with the left mouse button. How to use it:

from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
driver.find_element(By.ID, 's-top-loginbtn').click()

send_keys method

        The send_keys method is used to simulate keyboard input when we locate an input box. You can use the send_keys method on it and enter text into this input box. The input text is typed one by one by simulating keyboard keys. Of course, we can also use the send_keys method to simulate key events, such as pressing the Enter key, Ctrl+C, Ctrl+V, etc. The Keys class in selenium provides us with key values ​​that cannot be represented by text. How to use it:

from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium import webdriver
from selenium.webdriver.edge.service import Service
import time


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
driver.find_element(By.ID, 'kw').send_keys('hello world')  # 输入文本
time.sleep(1)  # 延迟执行便于观察
driver.find_element(By.ID, 'kw').send_keys(Keys.CONTROL, 'a')  # 按Ctrl+A全选
time.sleep(1)  # 延迟执行便于观察
driver.find_element(By.ID, 'kw').send_keys(Keys.CONTROL, 'c')  # 按Ctrl+C复制
time.sleep(1)  # 延迟执行便于观察
driver.find_element(By.ID, 'kw').send_keys(Keys.CONTROL, 'v')  # 按Ctrl+V粘贴

 Clear text content

        The clear method is used to clear the text content in the input box. When we locate an input box, we can use the clear method to clear the content in the input box. How to use it:

from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.edge.service import Service
import time


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
driver.find_element('id', 'kw').send_keys('hello world')  # 输入文本
time.sleep(1)  # 延迟执行便于观察
driver.find_element(By.ID, 'kw').clear()  # 清除文本

Get text content

        The text method can get the text description of the element, such as <a>Click to jump</a>. Using the text method on this element, you can get the click to jump text. How to use it:

from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
text = driver.find_elements(By.ID, 's-top-loginbtn').text
print(text)

 Move mouse

        We usually use the move_to_element method in the ActionChains class to move the mouse over an element. The ActionChains class provides many methods for operating the mouse. I will only list a few commonly used ones here. How to use it:

from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
element = driver.find_elements(By.ID, 's-top-loginbtn')
ActionChains(driver).move_to_element(element).perform()  # perform方法用于执行鼠标的动作

 Right-click

        The context_click method in the ActionChains class can realize right-clicking an element with the mouse. The usage method is as follows:

from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
element = driver.find_elements(By.ID, 's-top-loginbtn')
ActionChains(driver).context_click(element).perform()

double click

         The double_click method in the ActionChains class can double-click an element with the mouse. The usage is as follows:

from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
element = driver.find_elements(By.ID, 's-top-loginbtn')
ActionChains(driver).double_click(element).perform()

drag

        The drag_and_drop method in the ActionChains class can drag an element from its current position to the position of another element. How to use it:

from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.edge.service import Service


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://xxx.com')
source = driver.find_elements(By.ID, 'xxx')
target = driver.find_elements(By.ID, 'xxx')
ActionChains(driver).drag_and_drop(source, target).perform()

I didn't find any draggable elements on Baidu. You can find a webpage and try it yourself. 

scroll page

        The scroll_to_element method in the ActionChains class can implement mouse wheel scrolling, making the current page scroll to the position of an element. How to use it:

from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
from selenium.webdriver.edge.service import Service
import time


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('https://blog.csdn.net/qq_40148262/article/details/132191795')
element = driver.find_element('name', 't23')
ActionChains(driver).scroll_to_element(element).perform()  # 滚动到完整代码位置
time.sleep(5)

Execute JavaScript script

        Use the execute_script method to execute JavaScript scripts, and use the execute_async_script method to execute JavaScript scripts asynchronously. If you know JavaScript, many of the above methods can be replaced by JavaScript scripts. The advantage of using JavaScript is that when we position elements, we do not need the elements to be displayed on the interface (not visible on the computer monitor). As long as the HTML text of the web page is loaded, we can operate any element in the interface. JavaScript scripts can also help us achieve more Multiple modes of operation.

from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.edge.service import Service
import time


driver = webdriver.Edge(service=Service(executable_path='msedgedriver.exe'))
driver.get('http://www.baidu.com')
driver.execute_script("var input = document.getElementById('kw');if(kw != null) {input.value = 'hello world';}")
time.sleep(5)

Encapsulate basic test classes

        Encapsulate a basic test class according to the method introduced above.

base.py

import time
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains


class WebBase:

    def __init__(self, web_driver: webdriver.Firefox):
        self.driver = web_driver
        self.wait_time = 10

    def into_url(self, url: str):
        """
        通过url访问网页\n
        :param url: 网页的url
        :return:
        """
        self.driver.get(url)

    def locate_element(self, by: str, value: str):
        """
        定位一个元素\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return: 一个网页元素(WebElement)
        """
        element = self.wait(by, value)
        return element

    def locate_elements(self, by: str, value: str):
        """
        定位一组元素\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return: 一组网页元素(list[WebElement])
        """
        return self.driver.find_elements(by, value)

    def input_key(self, by: str, value: str, *args):
        """
        给可输入的元素输入值\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :param args: 要输入的字符
        :return:
        """
        element = self.locate_element(by, value)
        element.send_keys(*args)

    def wait(self, by: str, value: str, *, timeout=None, frequency=0.5, display=True):
        """
        等待元素出现或消失\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :param timeout: 查找元素的时间
        :param frequency: 查找元素的频率
        :param display: 出现或消失 -> True: 出现, False: 消失
        :return: 元素或bool值
        """
        if timeout is None:
            timeout = self.wait_time
        end_time = time.monotonic() + timeout
        while True:
            if time.monotonic() > end_time:
                break
            try:
                element = self.driver.find_element(by, value)
                if element.is_displayed() == display:
                    return element if display is True else True
            except Exception:
                if not display:
                    return True
            time.sleep(frequency)
        raise TimeoutError(f'元素{by}={value}在{timeout}秒内未{"出现" if display else "消失"}')
    
    def wait_gone(self, by: str, value: str, timeout=None):
        """
        等待元素消失\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :param timeout: 查找元素的时间
        :return: 
        """
        try:
            return self.wait(by, value, timeout=timeout, frequency=0.5, display=False)
        except TimeoutError:
            return False

    def click_left(self, by: str, value: str):
        """
        鼠标左键单击元素\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return:
        """
        element = self.locate_element(by, value)
        element.click()

    def click_right(self, by: str, value: str):
        """
        鼠标右键单击元素\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return:
        """
        element = self.locate_element(by, value)
        ActionChains(self.driver).context_click(element).perform()

    def double_click(self, by: str, value: str):
        """
        鼠标双击元素\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return:
        """
        element = self.locate_element(by, value)
        ActionChains(self.driver).double_click(element).perform()

    def move_mouse_to(self, by: str, value: str):
        """
        移动鼠标到某个元素上方\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return:
        """
        element = self.locate_element(by, value)
        ActionChains(self.driver).move_to_element(element).perform()

    def drag_element(self, by1: str, value1: str, by2: str, value2: str):
        """
        把第一个元素拖拽到第二个元素的位置\n
        :param by1: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value1: 跟by1对应的属性值
        :param by2: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value2: 跟by2对应的属性值
        :return:
        """
        source = self.locate_element(by1, value1)
        target = self.locate_element(by2, value2)
        ActionChains(self.driver).drag_and_drop(source, target).perform()

    def swipe(self, by: str, value: str):
        """
        滑动界面到某个元素\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return:
        """
        element = self.locate_element(by, value)
        self.driver.execute_script("arguments[0].scrollIntoView();", element)

    def clear_text(self, by: str, value: str):
        """
        清除文本内容\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return:
        """
        element = self.locate_element(by, value)
        element.clear()

    def get_text(self, by: str, value: str):
        """
        获取文本内容\n
        :param by: ["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"]
        :param value: 跟by对应的属性值
        :return: 文本内容(str)
        """
        element = self.locate_element(by, value)
        return element.text

    def script(self, js):
        """
        执行js脚本\n
        :param js:
        :return:
        """
        return self.driver.execute_script(js)

    def refresh(self):
        """
        刷新页面\n
        :return:
        """
        self.driver.refresh()

    def switch_window(self, index: int):
        """
        切换页面窗口\n
        :param index: 窗口索引
        :return:
        """
        window_list = self.driver.window_handles
        self.driver.switch_to.window(window_list[index])

    def close(self):
        """
        关闭当前窗口\n
        :return:
        """
        self.driver.close()

    def quit(self):
        """
        关闭webdriver\n
        :return:
        """
        self.driver.quit()


if __name__ == '__main__':
    pass

Use data-driven

        In the test case file we can use data-driven to simplify the test cases. When there are multiple test cases with the same operation steps and different test parameters, we can write only one test method to execute multiple test cases. We can put the test data into excel, json, py and other files, and then use the corresponding method to read the data in the file. If it is placed in excel, you can use openpyxl or pandas to read the data. If it is placed in json, You can use json to read data. If you put it in a py file, just import the py file directly.

        Suppose we store the user data (username, password) of three login websites in the json file. Now we use data-driven to implement a test method to execute three use cases.

login.json

{
    "element": {
        "name": [
            "id",
            "username"
        ],
        "password": [
            "id",
            "password"
        ],
        "login": [
            "id",
            "login"
        ],
        "logout": [
            "id",
            "logout"
        ]
    },
    "data": [
        [
            "xiaoming",
            "123456"
        ],
        [
            "baoxiaowa",
            "666666"
        ],
        [
            "limei",
            "654321"
        ]
    ]
}

DDT principle analysis

        The ddt library in python is specially used to implement the data driver of the unittest.TestCase class. Use the ddt function in the ddt library to decorate the subclass of unittest.TestCase. The ddt function will traverse and check all methods in the decorated class. If a method has the %values ​​attribute, the ddt function will traverse the values ​​corresponding to the %values ​​attribute. During the traversal process, use the logic of this method to create multiple test methods with the same logic and different names, and pass the traversed data into the created method, and finally add the created method to the decorated class. If there is a %file_path attribute in the method, the ddt function will open the file corresponding to the file path in the %file_path attribute. If the file is a deserialized file, yaml will be used to read the file. If the file is a json file, json will be used directly. Read the file, read the data and then traverse the data. During the traversal process, use the logic of this method to create multiple test methods with the same logic and different names, and pass the traversed data into the created method. Finally, Add the created method to the decorated class.

import unittest
import ddt


@ddt.ddt
class TestCase(unittest.TestCase):
    ...

        The data function in the ddt library is used to decorate the test case method in the unittest.TestCase subclass. Test data needs to be passed into the data function. The data function will use Python's built-in setattr() function to set the incoming test data to the %values ​​attribute of the decorated method. setattr(decorated function, '%values', test data)

import unittest
import ddt


@ddt.ddt
class TestCase(unittest.TestCase):

    @ddt.data(1, 2, 3)
    def test_1(self, data):
        print(data)


if __name__ == '__main__':
    suit = unittest.TestSuite()
    suit.addTest(unittest.TestLoader().loadTestsFromTestCase(TestCase))
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suit)

 The execution results are as follows:

From the execution results, we can find that 3 test methods were executed. ddt.data changed one test method into 3 test methods, and passed 1, 2, and 3 in the data to the first and second test methods respectively. , in the third method.

        The file_data function in the ddt library is used to decorate the test case method in the unittest.TestCase subclass. A json file path for storing test data needs to be passed into the file_data function. The file_data function uses python's built-in setattr() function to set the incoming json file path to the %file_path attribute of the decorated method. setattr(decorated function, '%file_path', json file path)

import unittest
import ddt


@ddt.ddt
class TestCase(unittest.TestCase):

    @ddt.file_data('login.json')
    def test_1(self, *args, **kwargs):
        for i in args:
            print(i)
        for key, value in kwargs.items():
            print(f'{key}: {value}')
        print('-'*100)


if __name__ == '__main__':
    suit = unittest.TestSuite()
    suit.addTest(unittest.TestLoader().loadTestsFromTestCase(TestCase))
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suit)

The execution results are as follows:

From the execution results, we can find that ddt.file_data reads the data in the login.json file and turns one test method into two methods according to the dictionary keys. The dictionary keys can be seen from the method names test_1_1_element and test_1_2_data. element and data are integrated into the test method name, and the value corresponding to the key is placed in the test method as test data.

Use ddt

        According to the working principle of ddt, we can use ddt in the unittest.TestCase subclass to achieve data-driven purposes.

login_test.py

import json
from selenium import webdriver
import unittest
from base import WebBase
import ddt


with open('login.json', 'r') as f:
    data = json.load(f)


@ddt.ddt
class LoginTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        driver = webdriver.Firefox(executable_path="geckodriver")
        cls.web = WebBase(driver)

    def setUp(self) -> None:
        self.web.into_url('https://xxx.com')

    def tearDown(self) -> None:
        self.web.close()

    @classmethod
    def tearDownClass(cls) -> None:
        cls.web.quit()

    @ddt.data(*data['data'])
    def test_login(self, n):
        """登录xxx网站"""
        name = data['element']['name']
        password = data['element']['password']
        login = data['element']['login']
        logout = data['element']['logout']
        self.web.input_key(*name, n[0])
        self.web.input_key(*password, n[1])
        self.web.click_left(*login)
        self.web.click_left(*logout)

In this way, we can separate the test data from the test method, and only use one method to implement no matter how much data there is. At the same time, elements and methods are also separated. No matter how the attributes of the elements are modified later, we only need to update the content in the json file.

Complete separation of test cases and test methods

        If we encounter test cases whose operation steps change frequently, we can also put all test methods into json files to completely separate the test methods from the test cases.

data.json

{
    "login": [
        [
            "input_key",
            "id",
            "username",
            "xiaoming"
        ],
        [
            "input_key",
            "id",
            "password",
            "123456"
        ],
        [
            "click_left",
            "id",
            "login"
        ],
        [
            "click_left",
            "id",
            "logout"
        ]
    ]
}

login_test.py

from selenium import webdriver
import unittest
from base import WebBase


class LoginTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        driver = webdriver.Firefox(executable_path="geckodriver")
        cls.web = WebBase(driver)

    def setUp(self) -> None:
        self.web.into_url('https://xxx.com')

    def tearDown(self) -> None:
        self.web.close()

    @classmethod
    def tearDownClass(cls) -> None:
        cls.web.quit()

    @ddt.file_data('data.json')
    def test_login(self, data):
        """登录xxx网站"""
        for i in data:
            getattr(self.web, i[0])(*i[1:])

In this way, we can achieve complete separation of test cases and test methods. No matter how the test steps change or the elements change, we only need to modify the json file.

Guess you like

Origin blog.csdn.net/qq_40148262/article/details/132325517