2024年最详细的web自动化测试框架(从封装开始)

Daniel Hao(找工作中) 2024-10-03 08:33:00 阅读 89

本文使用Page Objects设计模式,Page Objects模式是Selenium中的一种测试设计模式,主要是将每一个页面设计为一个Class,其中包含页面中需要测试的元素(按钮,输入框,标题 等),这样在页面中可以通过调用页面类来获取页面元素,这样巧妙的避免了当页面元素id或者位置变化时,在UI级的自动化测试中,对象设计模式表示测试正在交互的web应用,程序用户界面中的一个区域,这个是减少了代码的重复,也就是说,如果用户界面发生了改变,只需要在一个地方修改程序就可以了。

PO 模式的设计思想与面向对象相似,能让测试代码变得可读性更好,可维护性高,复用性高。

PO 模式可以把一个页面分为三个层级:对象库层、操作层、业务层。

对象库层:封装定位元素的方法。操作层:封装对元素的操作。业务层:将一个或多个操作组合起来完成一个业务功能。

安装 selenium 包

安装 parameterized 包添加 HTMLTestRunner

一、base:基础层,主要编写底层定位元素的类,它是一个包。

二、page:对象层,编写具体的业务逻辑,把页面每一个操作行为单独的写一个方法或者是函数。

三、test:测试层,里面主要是测试模块,也可以说是每个测试的场景的代码。

四、run:运行层:整个自动化测试的运行目录。

五、report:测试报告目录,主要用来存放测试报告。

六、data:存储测试使用到测试数据。

七、config:配置文件存储目录。

八、common:公共类,里面编写公共使用到的方法。

九、utils:工具类,存放工具,如文件处理、说明文档等。

页面对象设计模式

一、Base基础层

在该层中主要编写了基础代码。在该层主要定义了类WebUI,在这个类中编写了单个元素和多个元素定位的方法。

新建Base项目,在该项目的下面新建base_page.py,这个类主要是用来将selenium的操作进行二次封装,方便page层的项目调用。

<code>from telnetlib import EC

from selenium.webdriver.support.select import Select

from selenium.webdriver.support.wait import WebDriverWait

class BasePage:

def __int__(self,driver):

self.driver = driver

# 打开页面

def into_testin(self, url):

self.driver.get(url)

# 定位元素

def locator_element(self,loc):

return self.driver.find_element(*loc)

# 定位一组元素

def locate_eles(self, loc):

return self.driver.find_elements(*loc)

# 设置值的关键字

def send_keys(self, loc, value):

self.locator_element(loc).send_keys(value)

# 点击的关键字操作

def click(self, loc):

self.locator_element(loc).click()

# 清理输入框

def clear(self, loc):

self.locator_element(loc).clear()

# 封装选中下拉框关键字

def choice_select(self, loc, value):

sel = Select(self.locator_element(loc))

sel.select_by_value(value)

# 智能等待

def wait_for_element(self, locator, timeout=10):

"""

等待元素出现

:param locator: 定位器元组,例如 (By.ID, 'element_id')

:param timeout: 超时时间(秒),默认为10秒

:return: 找到的WebElement对象

"""

try:

element = WebDriverWait(self.driver, timeout).until(

EC.presence_of_element_located(locator)

)

return element

except Exception as e:

print(f"等待元素时出错: {e}")

return None

在Base该项目的下面新建base_util.py,由于我们每次都要用到 Chrome 的驱动,因此我们可以把它封装成一个工具类:

from selenium import webdriver

import unittest

class BaseUtil(unittest.TestCase):

# 在每个测试用例执行之前都会调用。它用于设置测试环境。

def setUp(self) -> None:

# 声明一个全局变量driver,这样在类的其他方法中也可以访问这个变量。

global driver

# 创建一个Chrome浏览器实例,并将其赋值给self.driver,这样所有测试用例都可以使用这个浏览器实例。

self.driver = webdriver.Chrome()

driver = self.driver

driver.get('https://www.luffycity.com/')

# 设置隐式等待时间为5秒,这意味着在查找页面元素时,Selenium会等待最多5秒。

self.driver.implicitly_wait(5)

self.driver.maximize_window()

# 在每个测试用例执行之后都会调用。它用于清理测试环境。当前这个方法是空的,没有执行任何操作。

def tearDown(self) -> None:

pass

二、page对象层

在这一层的类直接继承了基础层的类,以类属性的方法指明每个操作元素属性的值,然后依据操作步骤编写对应的方法。

比如关于登录的操作:输入用户名、输入密码,点击登录,获取文本的信息操作会在实例中实现的登录操作,然后把每个登录操作封装成一个方法,这样实现登录测试用例直接调用。

from base.base_page import BasePage

from selenium.webdriver.common.by import By

class LoginPage(BasePage):

zhuce = (By.XPATH,'//*[@id="__layout"]/div/header/div/div/div[2]/div[2]/span')code>

shouji = (By.XPATH,'//*[@id="__layout"]/div/div[2]/div/div/div[1]/div[1]/input')code>

mima = (By.XPATH,'//*[@id="__layout"]/div/div[2]/div/div/div[1]/div[2]/input')code>

denglu = (By.XPATH,'//*[@id="__layout"]/div/div[2]/div/div/button')code>

def __init__(self, driver):

self.driver = driver

def loginpage(self,username,password):

self.click(LoginPage.zhuce)

self.send_keys(LoginPage.shouji,username)

self.send_keys(LoginPage.mima,password)

self.click(LoginPage.denglu)

其他page类,样例:

from base.base_page import BasePage

from selenium.webdriver.common.by import By

class BlackClassPage(BasePage):

blackclass = (By.XPATH,'//*[@id="__layout"]/div/header/div/div/nav/a[3]/li')code>

cesiclass = (By.XPATH,"/html/body/div[3]/div/div/div[1]/main/div[2]/div[1]/div/article/span[12]")

cesifirsrclass = (By.XPATH,'//*[@id="__layout"]/div/div[1]/main/div[2]/div[2]/div[1]/a/dl/dd/div[3]/button')code>

def blackclasspage(self):

self.wait_for_element(BlackClassPage.blackclass)

self.click(BlackClassPage.blackclass)

self.click(BlackClassPage.cesiclass)

self.click(BlackClassPage.cesifirsrclass)

三、test测试层

在这里首先需要导入对象层中的类和unittest单元测试框架,在测试类中,继承了unittest.TestCase和对象层中的类,TestCase是由于在编写自动化测试的用例中,用到的测试固件、测试断言和测试执行都是需要它中的方法,而对象层中的类包含对象层中的测试操作步骤的方法,继承后可以直接进行调用。

注意:

在编写用例的时候需要添加备注信息,明确表示该用例是测试的哪个点,验证的哪个场景;

测试模块都是以test_开头,测试方法也是以test_开头的;

在test层下创建test_login类,实例代码如下:

from time import sleep

from base.base_util import BaseUtil

from pageobject.login import LoginPage

class TestLogin(BaseUtil):

def test_login(self):

'''

登录路非学堂

:return:

'''

lf = LoginPage(self.driver)

lf.loginpage("13375383415","Hao20001018")

sleep(3)

四、run运行层

运行层,主要是运行测试用例的目录,我们可以根据测试模块来运行,也可以运行所有的模块,该层的内容也适用于所有场景(适用的前提是po设计模式的目录结构如上所示)

import unittest

from HTMLTestRunner_cn import HTMLTestRunner

import time

if __name__ == '__main__':

suite = unittest.defaultTestLoader.discover("testcase", "test_login.py")

now = time.strftime("%Y-%m-%d %H_%M_%S") # 报告生成时间

test_reports_address = './report' # 测试报告存放位置

filename = './report/' + now + 'report.html' # 设置报告文件名

fp = open(filename, 'wb')

# 生成一个htmlTestRunenr运行对象

runner = HTMLTestRunner(stream=fp, title="路非学堂自动化测试报告", description="报告详情如下")code>

runner.run(suite)

fp.close()

# # 向指定邮箱发送测试报告的html文件

# time.sleep(3)

# # 查找最新生成的测试报告地址

# new_report_addr = SendEmail().acquire_report_address(test_reports_address)

# # 自动发送邮件

# SendEmail().send_email(new_report_addr)

# 测试用例均使用"test_"开头命名

# suites = unittest.defaultTestLoader.discover(dirpath, 'test_*.py', top_level_dir=dirpath)

# for suite in suites:

# test_cases.addTests(suite)

# return test_cases

# 生成html报告文件

# report_file = open("./report/report.html", "wb")

# 通过运行器运行测试用例

# runner.run(suite)

五、report测试报告目录

主要用于存放测试报告(使用HTMLTestRunner生成测试报告)

 执行完测试自动发送邮件(经尝试可用)

<code>import os

import smtplib

from email.mime.text import MIMEText

from email.mime.multipart import MIMEMultipart

class SendEmail(object):

def __init__(self, username, passwd, recv, title, content,

file_path=None, ssl=False,

email_host='smtp.163.com', port=25, ssl_port=465):code>

self.username = username # 用户名

self.passwd = passwd # 密码

self.recv = recv # 收件人,多个要传list ['a@qq.com','b@qq.com]

self.title = title # 邮件标题

self.content = content # 邮件正文

self.file_path = file_path # 附件路径,如果不在当前目录下,要写绝对路径

self.email_host = email_host # smtp服务器地址

self.port = port # 普通端口

self.ssl = ssl # 是否安全链接

self.ssl_port = ssl_port # 安全链接端口

# 发送邮件

def send_email(self):

msg = MIMEMultipart()

msg.attach(MIMEText(self.content)) # 邮件正文的内容

# 构造附件

for f_path, file_dirs, files in os.walk(self.file_path):

for file in files:

msg.attach(self._att_html(os.path.join(f_path, file)))

msg['Subject'] = self.title # 邮件主题

msg['From'] = self.username # 发送者账号

msg['To'] = ','.join(self.recv) # 接收者账号列表

if self.ssl:

self.smtp = smtplib.SMTP_SSL(self.email_host, port=self.ssl_port)

else:

self.smtp = smtplib.SMTP(self.email_host, port=self.port)

# 发送邮件服务器的对象

self.smtp.login(self.username, self.passwd)

try:

self.smtp.sendmail(self.username, self.recv, msg.as_string())

pass

except Exception as e:

print('出错了。。', e)

else:

print('发送成功!')

self.smtp.quit()

# 构造邮件附件

@ staticmethod

def _att_html(filename):

# 构造附件

atthtml = MIMEText(open(filename, 'rb').read(), 'base64',

'utf-8') # 文件放在同一路径,不放在同一路径改一下比如'D:/test/report.html

atthtml["Content-Type"] = 'application/octet-stream'

atthtml["Content-Disposition"] = 'attachment;filename = "GAD_Smoke_report.html"'

return atthtml

# 调用并发送邮件

if __name__ == '__main__':

m = SendEmail(

username = 'cicada_luo@163.com', # 这里填发送者邮箱

passwd = 'TCBXOAOF...', # 授权码还是什么忘记了

recv = ['1761885773@qq.com'], # 接收者邮箱

title = 'G——smoke',

content = 'G——smoke测试报告',

file_path = 'D:\\G_webUI\\report', # 发送的文件

email_host = 'smtp.163.com',

ssl_port =465,

ssl = True,

)

m.send_email() # 发送邮件

六、data:存储测试

存放数据文件

七、config配置文件目录

放置HTMLTestRunner等包。

常见问题

1、元素定位不到怎么办

表现形式为:程序抛出 NoSuchElementException 异常

解决思路:

① 检查元素定位属性值是否写错,很多时候错误都是因为粗心导致的。

② 添加等待。

有时,程序执行过快,导致程序已经执行完了,而元素还未加载出来,那么就会抛出异常。(我们添加等待时间即可。最粗暴的做法就是 sleep(3)—强制等待3秒,这样做使得程序运行时间较长,一般少用。最常用的是使用显示等待(搭配 until()方法、expected_conditions 类来使用))

def find_element_p(self, *args):

try:

return WebDriverWait(self.driver, 5, 0.5).until(EC.presence_of_element_located(*args))

except (NoSuchElementException, TimeoutException):

print("超过元素定位等待时长,无法获取到该元素,请检查定位路径")

③ 以上方法不行时,那么就再尝试使用其他方式进行元素定位(常见的元素定位方式可是有八种之多)

④ 当使用xpath定位时,有可能,定位元素之前进行了某些操作,但程序逻辑没有进行这些操作,那么就可能导致定位元素的xpath路径不一致,从而导致定位元素失败。如某些元素需要点击才能出现,但你的脚本程序未进行点击操作,自然就不可能定位得到该元素了。

2、元素无法交互

表现形式为:程序抛出异常:ElementNotInteractableException: Message: element not interactable

解决思路

① 检查进行交互的元素是否唯一,元素不唯一时也会出现此类错误

② 检查元素是否被隐藏。如果元素被隐藏起来,也无法进行交互。常见案例有:某按钮需要鼠标悬停在该元素上才能进行交互操作,此时就需要用到 ActionChains 类。例:

# 鼠标移动到指定元素上

def move_element(self, element_xp):

move = self.find_element_p((By.XPATH, element_xp))

ActionChains(self.driver).move_to_element(move).perform()

③检查元素是否被其他元素遮挡。当元素被其他元素所遮盖,那么也无法对该元素进行操作。解决办法就是移除其他元素的遮盖(通过脚本操作遮盖元素移开被遮盖的元素)。 



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。