Python自动化测试:web自动化测试——selenium API、unittest框架的使用

发布于:2024-06-29 ⋅ 阅读:(14) ⋅ 点赞:(0)


前言:使用Selenium框架进行简单web端UI自动化测试,简单的以百度搜索为例,复杂的模拟访问12306登陆、购票操作。

1. 设计用例的方法——selenium API

1.1 基本元素定位

web端-浏览器右键-检查,可以查看各个元素的id、class、name、text、XPath等,需要唯一才可精准定位
在这里插入图片描述

适用selenium 4.6以上语法如下:

1)定位单个唯一元素

  • driver.find_element(By.XPATH,‘XPATH’)——XPath路径如上图获取,是唯一的;
  • driver.find_element(By.CLASS_NAME,‘CLASS_NAME’)——用Class名称查找;
  • driver.find_element(By.CSS_SELECTOR,‘CSS_SELECTOR’)——用CSS选择器查找;
  • driver.find_element(By.ID,‘ID’)——用ID查找;
  • driver.find_element(By.LINK_TEXT,‘LINK_TEXT’)——用永超链接查找;
  • driver.find_element(By.PARTIAL_LINK_TEXT,‘PARTIAL_LINK_TEXT’)——用部分超链接查找;
  • driver.find_element(By.TAG_NAME,‘TAG_NAME’)——用标签名查找;

2)定位一组元素

  • switch_to.frame("框架’')——定位到页面所有input框;
inputs=driver.find_elements(By.TAG_NAME, "input")
for input in inputs:
    # 遍历定位到的input,若元素为单选框,则点击选中
   if input.get_attribute('type')=='checkbox':
      input.click()

3)定位多窗口/多框架

  • switch_to.frame("框架id’')——定位到某层级内的框架;
  • switch_to.default_content()——返回默认/最外层界面;
    举例:
    在这里插入图片描述
import switch as switch

#转换层级
driver.switch_to.frame("f1")
driver.switch_to.frame("f2")
#要想从f2回到f1,要先回到默认界面
driver.switch_to.default_content()
driver.switch_to.frame("f1")

4)定位连续层级

备注:若要定位的元素需要进行一系列操作才展示,那需要我们一层层去定位;

driver.find_element(By.ID, "元素ID").find_element(By.ID, "下一层级才能看到的元素ID")

5)定位下拉框

在这里插入图片描述

元素类型为:< option value=“1”>一月< /option>
定位并选择十月份:
方法1:option[value]

#定位到下拉框,注意elements的复数形式
options = driver.find_element(By.CLASS_NAME,"整个日历月份下拉框class名").find_elements(By.TAG_NAME, "option")
for option in options:
    if option.get_attribute('value') == '10':
        option.click()
# 第二种方法option[10].click

6)定位div框

备注:若页面元素太多,利用元素无法精准定位,可以先定位到某div框,在从该div框里去定位:

先定位到DIV1这个模块,在对模块上的元素进行操作
div1=driver.find_element(By.CLASS_NAME, "class名")
div1.find_element(By.ID, "ID").click()
#如果这个模块上多个button,还可以使用这样的方法
div1=driver.find_element(By.CLASS_NAME, "class名")
buttons=div1.find_element(By.ID, "ID")
button[0].click()

1.2 基本操作

(1)点击按钮:.click()
举例:
在这里插入图片描述

# 通过元素ID 定位到“百度一下”的按钮,点击“百度一下”
driver = webdriver.Chrome()
driver.get('http://www.baidu.com/')
driver.find_element(By.ID, "su").click()

(2)模拟写入对象/元素的内容:.send_keys(“xxxx”)
举例:百度-搜索框输入“孙俪”-点击“百度一下”
在这里插入图片描述

driver.find_element(By.ID, "kw").clear()
driver.find_element(By.ID, "kw").send_keys("孙俪")
driver.find_element(By.ID, "su").click()

(3)模拟清空元素/对象的内容:.clear()

(4)提交表单:.submit()
备注:要求元素为表单类型才可使用
在这里插入图片描述
举例:也可通过表单方式提交.submit() == .click() == 点击“百度一下”

(5)用于获取元素的文本信息:.text ()

text=driver.find_element(By.XPATH,'//*[@id="s-top-left"]/a[1]').text
print(text)

(6)获取输入框元素内容/值:.get_attribute(‘value’)

driver.find_element(By.ID,"kw").send_keys("selenium")
qq=driver.find_element(By.ID,'kw').get_attribute('value')
print(qq)

结果:打印 输入框的值-输出 selenium

1.3 等待

(1)强制等待:time.sleep(2)——休眠2s
(2)智能等待:driver.implicitly_wait(5) ——智能等待最长5s

import time
from selenium import webdriver
driver = webdriver.Firefox()
driver.implicitly_wait(5)
time.sleep(2)

1.4 浏览器操作

(1)浏览器最大化:driver.maximize_window()
(2)设置浏览器高、宽:driver.set_window_size(500,500)
(3)浏览器后退:driver.back()
(4)浏览器前进:driver.forward()
(5)浏览器滚动条置顶与置底:(借助执行JS语句,如下示例所示)

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get('http://www.baidu.com/')
driver.maximize_window()
#设置浏览器窗口为(500,500)
driver.set_window_size(500,500)
time.sleep(1)
#设置浏览器窗口最大化
driver.maximize_window()
driver.implicitly_wait(2)
driver.find_element(By.ID,"kw").send_keys("selenium")
driver.find_element(By.ID,"su").click()
driver.implicitly_wait(3)
#将页面滚动条拖到底部
js = "var q=document.documentElement.scrollTop=10000"
driver.execute_script(js)
time.sleep(3)
#将页面滚动条拖到顶部
jjs="var q=document.documentElement.scrollTop=0"
driver.execute_script(jjs)
time.sleep(3)

1.5 鼠标事件

from selenium.webdriver.common.action_chains import ActionChains
(1)context_click() 右击
(2)double_click() 双击
(3)drag_and_drop() 拖动
(4)move_to_element() 移动

  • ActionChains(driver)
    生成用户的行为。所有的行动都存储在actionchains 对象。通过perform()存储的行为。
  • move_to_element(menu)
    移动鼠标到一个元素中,menu 上面已经定义了他所指向的哪一个元素
  • perform()
    执行所有存储的行为
qq=driver.find_element(By.ID,"kw")
ActionChains(driver).context_click(qq).perform() #右键
ActionChains(driver).double_click(qq).perform() #双击

1.6 键盘事件

from selenium.webdriver import Keys
(1)快捷回车-enter键:.send_keys(Keys.ENTER) 等价于点击按钮

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import Keys
driver = webdriver.Firefox()
driver.get('http://www.baidu.com/')
driver.maximize_window()
driver.find_element(By.ID, "kw").clear()
driver.find_element(By.ID, "kw").send_keys("孙俪")
driver.find_element(By.ID, "su").send_keys(Keys.ENTER)

(2)切换焦点-Tab键:.send_keys(Keys.TAB)
作用:将元素定位位置由当前元素切换至下一个元素
在这里插入图片描述
(3)输入框内容-全选与剪切-组合键:全选、剪切、复制、粘贴

#ctrl+a 全选输入框内容
send_keys(Keys.CONTROL,'a')
#ctrl+x 剪切输入框内容
send_keys(Keys.CONTROL,'x')
#ctrl+c 复制输入框内容
.send_keys(Keys.CONTROL,'c')
#ctrl+v 粘贴输入框内容
.send_keys(Keys.CONTROL,'v')

(4)输入空格:.send_keys(Keys.SPACE)
(5)单个删除:.send_keys(Keys.BACK_SPACE)

1.7 弹窗处理

(1)Alert弹窗:只有信息及确认按钮
(2)Confirm弹窗:在Alert弹窗基础上增加了取消按钮
(3)Prompt类型弹框:在Confirm的基础上增加了可输入文本内容的功能

driver.switch_to.alert.accept() #确定、同意;三种弹窗都可使用
driver.switch_to.alert.dismiss() #取消、不同意;confirm和prompt弹窗中使用
title = driver.switch_to.alert.text #打印弹窗信息
alert = driver.switch_to.alert #获取alert对象
alert.send_keys() #Prompt弹窗中输入内容

1.8 上传文件操作

driver.find_element(By.CLASS_NAME, "class名").send_keys("文件路径")

2. unittest介绍

  • unittest是Python自带的一个单元测试框架, 它可以做单元测试,提供了去创建测试用例的方法,并能用于编写和运行重复的测试工作;
  • 可以利用unittest创建一个类,该类集成unittest的TestCase,其中每个case作为一个最小单元,由测试容器组织起来,统一执行并引入测试报告输出结果;
  1. test fixture:初始化与清理测试环境。如创建临时的数据库、文件/目录,其中如setUp()用于启动浏览器驱动、setDown()用于关闭游览器驱动等统一操作;
  2. test case:单元测试用例,在类TestCase中设计编写测试用例;
  3. test suite:单元测试用例集合,将不同的测试用例封装至类TestSuite中; test runner:执行单元测试用例;
  4. test report:生成测试报告
    在这里插入图片描述

3. unittest 框架的使用示例

  • 前置:资源导入
import csv #用于解析data数据
import sys #用于访问本地资源路径
from selenium import webdriver
import time
import os  #用于输出
import unittest   #用于使用unittest资源
from ddt import ddt, data, file_data, unpack  #用于data数据读取与输入操作
from unittest import TestLoader   #用于输出测试报告

1)测试固件的编写

  • 每个测试用例都需要包含测试固件:包含基本的setUp()、tearDown()等统一操作;
    def setUp(self):
        print("------setUp")
        self.driver=webdriver.Firefox()
        self.url="https://www.baidu.com"
        self.driver.maximize_window()
        time.sleep(3)

    def tearDown(self):
        print("-------tearDown")
        self.driver.quit()

2)单元测试用例——多场景设计测试用例

  • 单元测试:可以是同一个场景设计多个测试用例;

示例:该单元测试场景——模拟每次进入百度首页-点击进入不同标签页面

from selenium import webdriver
import os
import time
import unittest

class Test1(unittest.TestCase):
    #绑定浏览器驱动,设置url
    def setUp(self):
        print("-----setUp")
        self.driver=webdriver.Firefox()
        self.url="https://www.baidu.com"
        self.driver.maximize_window()
        time.sleep(3)

    #测试用例执行结束进行清理,关闭浏览器驱动
    def tearDown(self):
        print("-----tearDown")
        self.driver.quit()

     #编写测试用例 def test_xxx:
    def test_baidu1(self):
        driver=self.driver
        url=self.url
        driver.get(url)
        driver.find_element_by_link_text("hao123").click()
        time.sleep(2)

    def test_baidu2(self):
        driver=self.driver
        url=self.url
        driver.get(url)
        driver.find_element_by_link_text("图片").click()
        time.sleep(2)

    def test_baidu3(self):
        driver=self.driver
        url=self.url
        driver.get(url)
        driver.find_element_by_link_text("地图").click()
        time.sleep(3)

    if __name__ == "__main__":
        unittest.main()

示例:该单元测试场景——模拟百度搜索功能(对于不同数据类型)

测试数据导入

@data(不带的列表)会将整个列表作为参数传入
@data(带的列表)会将整个列表的子元素作为参数逐个传入,可将二维列表的元素逐个传入,一个元素一个case
@unpack 将要传入的元素解包后传入,将二维列表的元素逐个传入,便于一个测试用例中使用一组数据中的多个不同变量;

首先需要了解数据导入的方法:
在这里插入图片描述
备注:数据导入可以选择txt文件、json文件格式、或直接导入数据数组;其中dataTest.py为某单元测试文件;

1)读取txt文件

  • 需要引入data数据包-并要设置读取方法;
def getTxT(file_name):
    rows = []
    path = sys.path[0]
    with open(path + '/data/' + file_name, 'rt',encoding='UTF-8') as f:
        readers = csv.reader(f, delimiter=',', quotechar='|')
        next(readers, None)
        for row in readers:
            temprow = []
            for i in row:
                temprow.append(i)
            rows.append(temprow)
        return rows
  • 给该单元测试的类和某测试用例设置@ddt与@data修饰器
@data(*getTxT("baidu_data.txt"))

2)读取json文件

  • 格式如下:
[
  "hao123",
  "图片",
  "地图"
]

3)直接在某用例上输入数组

  • 格式如下:
@data(["hao123", "hao123"], [u"视频", u"视频_百度搜索"])

跳过某用例

想跳过某单元用例中的某用例,则添加如下代码至某用例方法上即可:

@unittest.skip("skipping")  #表示跳过该测试用例

测试用例断言

unittest单元测试框架提供了一整套内置的断言方法:
1)如果断言失败,抛出AssertionError的错误,case为失败状态
2)如果断言成功,会标识case为成功状态

方法 检查 描述
assertEqual(a, b) a == b 验证a是否等于b
assertNotEqual(a, b) a != b 验证a是否不等于b
assertTrue(x) bool(x) is True 验证x是否为ture
assertFalse(x) bool(x) is False 验证x是否为flase
assertIs(a, b) a is b 验证a,b是否为同一个对象
assertIsNot(a, b) a is not b 验证a,b不是同一个对象
assertIsNone(x) x is None 验证x是否是None
assertIsNotNone(x) x is not None 验证x是否非None
assertIn(a, b) a in b 验证a是否是b的子串
assertNotIn(a, b) a not in b 验证a是否非b的子串
assertIsInstance(a, b) isinstance(a, b) 验证a是否是b的实例
assertNotIsInstance(a, b) not isinstance(a, b) 验证a是否不是b的实例

举例:百度搜索某字段,判断标题是否一致,不一致则断言失败,打印结果:标题不相等!

    @data(["hao123", "hao123"], [u"视频", u"视频_百度搜索"])
    def test_baidu7(self, value, title):
        driver = self.driver
        url = self.url
        driver.get(url)
        driver.find_element_by_id("kw").send_keys(value)
        driver.find_element_by_id("su").submit()
        time.sleep(5)
        print(driver.title)
        self.assertEqual(title, driver.title, msg="标题不相等!")

测试结果截图

可以设置保存截图的方法,根据断言结果调用截图方法并保存截图:
1)设置截图保存方法

    def savescreenshot(self, driver, file_name):
        if not os.path.exists('./image'):
            os.makedirs('./image')

        now = time.strftime("%Y%m%d-%H%M%S", time.localtime(time.time()))
        # 截图保存
        driver.get_screenshot_as_file('./image/' + now + '-' + file_name)
        time.sleep(1)

2)根据断言抛出异常,调用截图方法

    def test_baidu4(self):
        driver=self.driver
        url=self.url
        driver.get(url)
        driver.find_element_by_id("kw").clear()
        driver.find_element_by_id("kw").send_keys("孙俪")
        driver.find_element_by_id("su").click()
        time.sleep(3)
        print(driver.title)
        try:
            self.assertEqual("孙俪_百度搜索", driver.title, msg=None)
            self.assertNotEqual("孙俪_百度搜索",driver.title,msg=None)
        except:
            self.savescreenshot(driver,"sunli.png")

3)截图:百度搜索“孙俪”
结果保存至项目文件的/image文件夹中
在这里插入图片描述

3)测试套件组合与执行

套件组合-借助装载器:defaultTestLoader、TestLoader

将单元测试文件中的某类测试用例塞入测试套件中,创建一个测试用例组合套件的方法:

def createSuit():
    # 添加不同测试用例到套件里
    testSuit=unittest.defaultTestLoader.discover("../py2Unittest",pattern="test*.py",top_level_dir=None)
    return testSuit

注:此方法可以把一个文件夹下面所有的满足test*.py命名规则的测试脚本中的测试用例放入测试套件

套件组合-借助addTest函数

可以将某单元测试文件中的某个用例塞入测试套件中:
注:这样放需要在当前测试套件脚本中引入此处的单元测试脚本,否则addTest()会报错

import test1
import test2

def creatSuit():
    #要把不同的测试脚本的类中的需要执行的方法放在一个测试套件中
    suit = unittest.TestSuite()
    suit.addTest(test1.Test1("test_baidu1")) # test_baidu1为某测试用例方法的名称
    suit.addTest(test2.Test2("test_baidu3"))
    return suit

套件组合-借助makeSuit

利用makeSuit不需要导入单元测试用例文件

def creatSuit():
   #如果我需要把一个测试脚本中所有的测试用例都添加到suit中-实际将整个单元测试脚本中的类都加入套件中实现
   # makeSuit
   suit = unittest.TestSuite()
   suit.addTest(unittest.makeSuite(test1.Test1))
   suit.addTest(unittest.makeSuite(test2.Test2))
   return suit

套件执行-借助TextTestRunner

举例:
将测试套件执行并打印测试结果:

import unittest
from unittest import TestLoader

def creatSuit():
    #TestLoader
    suit1 = unittest.TestLoader().loadTestsFromTestCase(test1.Test1)
    suit2 = unittest.TestLoader().loadTestsFromTestCase(test2.Test2)
    suit = unittest.TestSuite([suit1, suit2])
    return suit

if __name__ == "__main__":
    suit = creatSuit()
    # verbersity= 0, 1, 2
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suit)

注:unittest.TextTestRunner的verbosity参数用于控制测试运行时的详细程度。它可以接受的值如下:
0: 静默模式,不输出任何信息。
1: 默认模式,输出每个测试方法的简要摘要和总体摘要(通过、失败、错误等)。
2: 详细模式,输出每个测试方法的详细执行结果,包括测试方法的名称、运行时间、状态等信息。

4)测试报告输出

测试套件&执行&测试报告输出脚本程序入口的设置:
if name ==“main”:
1.设置测试结果文件输出路径
2.设置结果文件名
3.创建并打开文件,执行测试套件,写入测试结果,保存文件

举例:
单元测试脚本:

import csv
import sys
# -*- coding: utf-8 -*-
from selenium import webdriver
import os
import time
import unittest
from ddt import ddt, data, file_data, unpack
from selenium.webdriver.common.by import By

def getTxT(file_name):
    rows = []
    path = sys.path[0]
    with open(path + '/data/' + file_name, 'rt',encoding='UTF-8') as f:
        readers = csv.reader(f, delimiter=',', quotechar='|')
        next(readers, None)
        for row in readers:
            temprow = []
            for i in row:
                temprow.append(i)
            rows.append(temprow)
        return rows

@ddt
class Test4(unittest.TestCase):
    # 绑定浏览器驱动,设置url
    def setUp(self):
        print("-----setUp")
        self.driver = webdriver.Firefox()
        self.url = "https://www.baidu.com"
        self.driver.maximize_window()
        time.sleep(3)

    # 测试用例执行结束进行清理,关闭浏览器驱动
    def tearDown(self):
        print("-----tearDown")
        self.driver.quit()

    # @unittest.skip("skipping")  # 注释表示跳过该测试用例
    @file_data("data_baidu.json")
    def test_baidu6(self, value):
        driver = self.driver
        url = self.url
        driver.get(url)
        driver.find_element(By.LINK_TEXT,value).click()
        time.sleep(3)

    # @unittest.skip("skipping")
    @unpack
    @data(*getTxT("baidu_data.txt"))
    # @data(["hao123", "hao123"], [u"视频", u"视频_百度搜索"])
    def test_baidu7(self, value, title):
        driver = self.driver
        url = self.url
        driver.get(url)
        driver.find_element(By.ID,"kw").send_keys(value)
        driver.find_element(By.ID,"su").submit()
        time.sleep(5)
        print(driver.title)
        self.assertEqual(title, driver.title, msg="标题不相等!")

套件组合与执行&报告输出脚本:

import HTMLTestRunner  # 基于html文件的测试执行与报告输出资源模块
import os
import sys
import time
import unittest

def createSuit():
    # 添加不同测试用例到套件里
    testSuit=unittest.defaultTestLoader.discover("../py2Unittest",pattern="dataTest.py",top_level_dir=None)
    return testSuit

if __name__ =="__main__":
    if not os.path.exists("./result"):
        os.makedirs("./result")

    now = time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time()))
    print(now)

    fileName="./result/"+now+"result.html"

    with open(fileName, "w") as fp:
        runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u"测试报告", description=u"测试用例执行情况",verbosity=2)
        suit = createSuit()
        runner.run(suit)

若文件读写时 with open(fileName, “wb”) as fp:可能会报错
在这里插入图片描述
不要慌,可以查看文件读取与写入规则,主要是使用write函数时报错,检查open函数,发现参数写为‘wb’,即按二进制write,所以后面出现TypeError: a bytes-like object is required, not 'str’的报错,将此次的参数修改为w后,可以按照字符串输入,编译通过,write的结果正确(w+具有读写属性,写的时候如果文件存在,会被清空,从头开始写)

结果截图:
在这里插入图片描述
测试报告:
在这里插入图片描述