Python Selenium 测试教程(三)
十一、页面对象
在前面的所有章节中,您学习了为网页中的 web 元素编写各种测试条件。通过组合这些条件来构建测试用例。构建一个成功的测试用例需要一个模型,在这个模型中,所有基本的功能和方法都集成到一个测试用例或脚本中。当测试用例被捆绑到一个单一的脚本中时,测试脚本的复杂性增加了,这使得阅读、修改或维护变得困难。页面对象用于解决网页上混乱的测试脚本问题。
本章从讨论页面对象开始。后面的章节涵盖了自动化测试最广泛使用的模型。本章还描述了该模型在当今环境中的必要性。此外,您将学习基于页面对象构建模型的完整过程,这是一种广泛使用的测试模型。这个模型在本章的一个经典例子中进行了定义。对模型的 Selenium 支持也有助于封装测试用例。现在让我们从页面对象概述开始。
页面对象概述
页面对象是为网页初始化的类(Python 中面向对象的概念)。页面对象中定义了 web 元素,就像 Python 语言中的属性一样。page object 类充当存储 web 元素及其动作和与 web 页面/应用相关联的验证的媒介。使用页面对象可以很容易地访问 Web 元素。根据需求,可以在 Python 测试脚本中定义多个页面对象。
页面对象的基本概念是隐藏定位 web 元素的逻辑(例如,用户 ID、XPath、CSS 选择器等)。)在输入值时与网页上的这些元素进行交互,从而将逻辑字段和值字段分开。
Note
页面对象最适合具有多个页面的 web 应用。
现在让我们看一个使用页面对象构建的模型。
页面对象模型(POM)概述
在测试自动化中,出现了一种设计模式或模型,它允许更多的功能、可维护性、可读性和灵活性。这种基于页面对象的设计模型/模式被称为页面对象模型(POM)或页面对象模式。
(这些术语可以互换使用,但本书只使用 POM。)
页面对象比非结构化的 Selenium 测试脚本更好,非常适合简单的测试用例/场景。随着测试用例在多个网页中的增加,测试脚本的复杂性也增加了。为了将测试用例绑定在一起并简化测试脚本,开发了使用页面对象的设计模型或模式。
Selenium WebDriver 测试领域中的 POM 是由 Simon Steward 提出的。页面对象模型的第一个实现是在 Java 中完成的,后来被 C#、Ruby 和 Python 采用,因为它们都支持面向对象的概念。
POM 是自动化 Python 测试脚本的一种有组织的方式。它包含与用户操作或交互相关联的 web 元素,并且经过测试,这意味着 POM 为页面对象提供了封装。
最初,页面对象是为每个 web 页面定义的,但是由于 web 元素的动态特性,对象也可以被定义为 Python 测试用例中的类或函数。近年来,在 Selenium 的自动化测试中,页面对象的使用越来越多。
Note
页面对象也用于单个网页或应用中可用的重要 web 元素,尽管有术语页面对象。
对页面对象模型的需求
当每个网页或专门的 web 元素被表示为一个类时,那么 page 对象就充当 page 和 AUT 之间的接口媒介。类中描述的方法是执行时与网页用户界面交互的测试用例。
使用 POM 的一个主要原因是网页 UI 的变化不会影响测试用例。更改是通过修改与之相关的页面对象来处理的,这样可以节省编写新测试的时间和精力。
接下来解释实现该模型的过程方式。
创建页面对象
页面对象模型被分成不同的模块。这些测试模块包含一个链接到其他模块文件的主测试页面。这些模块与指定测试用例的元素、定位器和页面相关。
接下来提供了一个 POM 测试用例示例,其中 test.py 是主测试用例文件,后面是附属文件:page.py、elements.py 和 locators.py。
要执行 POM,需要在命令提示符下运行 test.py,每个附属文件在被调用时都会被逐一执行。
现在,让我们从 test.py 文件开始,创建构成 POM 所需的每个页面。
Test.py
test.py 是一个使用 POM 架构开发的测试用例。test.py 页面包含测试特定网页或应用所需的所有测试用例。在这个例子中,POM 被设计用来测试在 Apress 网站上搜索一个关键字,然后获得匹配的结果。test.py 需要导入相关的模块,称为 page.py 文件,以完成测试用例。
将所有四个文件保存在一个目录中。不要单独运行它们,因为它是页面对象模型,类似于 Java 中的页面对象模式。
import unittest
from selenium import webdriver
import page
class ApressSearch(unittest.TestCase):
#Sample test case using Page Object Model
def setUp(self):
self.driver = webdriver.Firefox(executable_path=r'')
self.driver.get("https://www.apress.com")
def test_apress_search(self):
#Visits aprèss.com
home_page = page.HomePage(self.driver)
#Searches "Python Selenium" keyword
home_page.search_text ="Python Selenium"
home_page.click_submit_button()
search_results_page = page.ResultPage(self.driver)
#Checks if page is not empty
assert search_results_page.check_search_results(), "No results found."
def tearDown(self):
self.driver.close()
if __name__ =="__main__":
unittest.main()
test.py 文件访问指定的网站(Apress.com),然后通过搜索框搜索“Python Selenium”。无论结果页面是否为空,都会用断言进行验证。
Note
test.py 文件只包含测试用例。
Page.py
page.py 文件是为应用中可用的每个网页创建的。在这个文件中,定义了关于测试用例的操作。这一页从技术方面将测试代码和它的实现分开。该页面应该导入元素和定位器文件以及标准的 Selenium 库。
from elements import BasePageElement
from locators import HomePageLocators
class SearchText(BasePageElement):
#The locator for search box where search string is entered
locator ='query'
class BasePage(object):
def__init__(self, driver):
self.driver = driver
class HomePage(BasePage):
#Actions items for Home Page
#Variable containing retrieved text
search_text = SearchText()
def click_submit_button(self):
#Search is initialized
element=self.driver.find_element(*HomePageLocators.SUBMIT_BUTTON)
element.click()
classResultPage(BasePage):
#Actions items for result page
def check_search_results(self):
# Checks the result for specified text if found or not
return "No results found." not in self.driver.page_source
提交按钮单击操作在此页面中定义。它验证搜索页面结果是否可用。
Elements.py
elements.py 包含一个为每个页面初始化的类。此页面查找与执行操作的功能相关联的必要 web 元素。它还可以设置定位 web 元素的条件。
from selenium.webdriver.support.ui import WebDriverWait
class BasePageElement(object):
#Used in every page
def__set__(self, obj, value):
#Contains specified text
driver = obj.driver
WebDriverWait(driver, 100).until(lambda driver: driver.find_element_by_name(self.locator))
driver.find_element_by_name(self.locator).clear()
driver.find_element_by_name(self.locator).send_keys(value)
def__get__(self, obj, owner):
"""Gets the text of the specified object"""
driver = obj.driver
WebDriverWait(driver, 100).until(
lambda driver: driver.find_element_by_name(self.locator))
element = driver.find_element_by_name(self.locator)
return element.get_attribute("value")
在这个页面中,web 元素在等待定义的时间后被定位,然后通过 page.py 文件提供给 test.py。
Locators.py
locators.py 页面包含需要定位的 web 元素的名称。这将页面与元素和定位符分开。定位器值的任何更改都在此页面中完成。
from selenium.webdriver.common.by import By
class HomePageLocators(object):
#Should contain all locators from main page
SUBMIT_BUTTON = (By.CLASS_NAME, 'search__submit')
class ResultPageLocators(object):
#It should contain locators from result page
pass
初始化的定位器被传递给 elements.py,它在那里定位指定页面的 web 元素。POM 有时被认为是使用 Python 部署 Selenium 测试用例的框架。一个被称为 unittest 的测试用例框架与 POM 相结合。在第十二章中有描述。
页面对象模型的优点
这一部分主要关注用页面对象创建的测试用例的优点,如下所示。
-
**减少代码重复:**大部分进行测试的网页所需的基本测试脚本都需要重写,这就造成了代码的重复。这种代码重复可以通过使用页面对象来避免,因为相同的测试脚本具有为各种 web 元素指定的页面对象,并且可以用于其他页面。
-
增强代码维护:页面类的创建有助于分离 web 元素(数据)和测试用例。这使您能够在不断变化的环境中避免不稳定的测试套件条件。测试用例中异常的处理意味着更易维护的测试脚本。
-
**更短的测试用例:**使用 POM 对测试脚本进行了优化,因此减少了测试用例的长度。
-
**增加代码可重用性:**使用页面对象的代码可以在多个测试用例中使用。
-
可读性:为任何 web 应用编写的测试脚本,对于一个新的测试人员或者在同一个项目中工作的多个测试团队来说,应该是可以理解的。这是通过使用 page 对象来完成的,它将 web 元素或 web 页面分成几个部分,这使得测试脚本更具可读性。
页面对象模型的局限性
现在让我们看看在测试用例中使用页面对象的局限性。
-
**时间和精力:**当一个网页或应用有大量页面时,使用页面对象会有很大的风险,因为即使是应用中很小的变化,测试模型也需要修改。因此,POM 应该与 web 应用/页面的开发并行使用。
-
**高级技能:**要调试或编写符合 POM 的测试用例,您必须具备技术知识和经验,因为复杂性随着组合页面对象架构的增加而增加。
-
**固定模型:**用 POM 开发的测试不能在其他应用上使用(即 POM 不是通用模型)。
摘要
本章提供了构建模型的页面对象的完整概述。页面对象是将网页分成更小的部分的类,这些部分区分了其中可用的多个 web 元素。这是创建模型的基本构件。
您了解了页面对象模型的概念和技术实现方面。POM 的创建包括一个文件结构,该文件结构包含一个主 Python 文件和驻留在单个目录中的附属 Python 文件,如示例中所示。
您还了解了 POM 的优点和局限性。
本书的最后一章包括使用不同函数编写的各种测试用例。
十二、使用测试用例和截图
Selenium 上的 Python 程序测试 web 应用或 web 页面上的指定元素,称为测试用例。Selenium with Python 是测试 web 应用或网页的流行选择,因为实现它的学习曲线较低。Python 是一种易于理解的语言。测试要求您为 web 应用生成简单、可理解的测试报告,这可以通过 Python 和 Selenium 的结合来完成。
本章讨论了用于截图的各种测试用例。在前一章中,您学习了 POM 如何创建测试用例。
现在让我们研究一下 Selenium 中 Python 支持的最广泛使用的框架。还讨论了作为网页测试用例的框架实现。首先,让我们快速回顾一下测试结果。
测试结果
测试代码有三种结果:通过、失败或错误。这些结果定义了需要在测试用例中提供的变更。下面提供了一个快速描述。
通过/合格
通过/通过是开发人员通常期望的结果。这意味着应用在功能和客户满意度方面是理想的,没有错误。这是通过严格测试网页或应用来实现的。当测试通过时,应用不需要任何更改或错误修复。
失败
当相应的 web 元素或其行为不满足测试条件时,测试失败。当测试用例失败时,系统会引发 AssertionError 异常。向开发人员说明此错误,以便进行必要的更改,这些更改也是经过测试的。
错误
引发异常时会发生错误。该错误可能是由逻辑、语法或语义引起的,可以通过引发的异常进行跟踪。此异常不是 AssertionError。
现在让我们看几个测试案例,在这些案例中,使用多种方法实现了相同的目标。
测试用例 1:截图
这个测试用例获取网页的图片(即屏幕截图)。通过指定高度和宽度来确定屏幕截图的大小。这对于 bug 分析报告、查看测试用例流程以及恢复失败的测试用例非常有用。
有三种方法可以用来在 Selenium 中捕获屏幕图像。每一个都在下面描述。
save_screenshot('文件名称')
在这种方法中,屏幕截图是在测试用例的执行过程中拍摄的。该屏幕截图的扩展名为. png。下面是它的 Python 代码。
from selenium import webdriver
new_driver=webdriver.Firefox()
new_driver.get('https://apress.com')
new_driver.save_screenshot('screenshot1.png')
get _ snapshot _ as _ file('文件名称')
这种方法直接将截图保存为图像。png 格式。除了以外的任何其他图像扩展。png 抛出一个错误。下面是它的 Python 代码。
#get_screenshot_as_file()
from selenium import webdriver
driver=webdriver.Firefox()
driver.get('https://apress.com')
driver.get_screenshot_as_file('screenshot2.png')
#Error Occurs when image extension is changed
#driver.get_screenshot_as_file('screenshot2.jpg')
get_screenshot_as_png()
在此方法类型中,屏幕截图以二进制形式捕获,然后转换为图像。保存的图像存储在中。png 格式。下面是它的 Python 代码。
#Get method
from selenium import webdriver
from PIL import Image
from io import BytesIO
driver= webdriver.Firefox(executable_path=r'C:\Users\ADMIN\Desktop\geckodriver.exe')
driver.get('http://apress.com/')
#Logo Image extraction
element=driver.find_element_by_class_name('brand')
location=element.location
#saves logo image as screenshot
size=element.size
png=driver.get_screenshot_as_png()
driver.quit()
#PIL library is used to open image in memory
image=Image.open(BytesIO(png))
#Logo size extraction
left= location['x']
top= location['y']
right= location['x'] + size['width']
bottom= location['y'] + size['height']
#Saving Logo for extracted dimension size
image=image.crop((left, top, right, bottom))
image.save('C:\\Users\\ADMIN\Desktop\\python books\\#Go Lang\\Done\\final\Apress\python testing\\codes\\screenshot3.png')
Python 图像库(PIL)用于转换二进制图像。它有 Python 版本。在实现这个函数之前,指定图像的大小。
Note
在 Selenium 上的 Python 中,save_screenshot()和 get_screenshot_as_file()方法只能将图像存储在。png 格式,而 get_screenshot_as_png()方法可以存储多种格式的图像。
现在我们来看看测试框架和 Selenium 实现中使用的最流行的测试框架。
测试框架
一个测试用例由一套规则和规章管理,这是由一个框架来完成的。该框架用于测试,因此被称为测试框架。测试框架使得测试用例更加有效和健壮。该框架包括处理测试数据、测试标准、测试存储库、页面对象等特性。
几个 Python 框架可以和 Selenium 一起使用。最常用的框架之一是 unittest。接下来描述 unittest 框架,包括它的模块和功能。
单元测试框架
unittest 框架是一个 Python 测试框架,它基于 Kent Beck 和 Erich Gamma 开发的 XUnit 框架设计模式。它也被称为 PyUnit。unittest 框架通常用于基于自动化测试用例生成报告。
在这个框架中,测试用例是在小的独立单元中测试的(比如函数、方法、类等等)。)来确定 web 元素或网页测试的正确性。通过将类定义为方法并使用断言函数,这些测试以小块或小单元的形式进行。
在开始编写代码之前,您需要了解 unittest 框架支持的以下组件。
-
一个测试夹具执行一个或多个测试用例以及相关的清理动作。创建代理或临时数据库,在服务器上启动一个进程,或者创建新的目录都是文本设备的例子。
-
在测试用例模块中,测试小的单个单元。这些单元与要测试的 web 元素相关。unittest 框架包含一个基类和用于创建新测试用例的测试用例。
-
一个测试套件集合了多个测试用例。它也可以是测试套件的组合。测试套件的执行与测试用例相同。
-
一个测试运行器帮助运行所有的测试用例及其相关的结果。runner 可以与接口集成,比如一个图形、文本或者一个指示测试用例返回值的特殊值。
-
一个测试报告是一个测试用例的生成结果或输出(即是否通过或失败)。测试用例的执行时间以一种系统的方式被安排来生成报告。这些细节在给客户的报告中总结。
测试用例 2:单元测试
首先,需要使用 Python 中的 pip 下载 unittest 库。unittest 框架代码有几个实例可以合并到一个测试用例中。
设置( )
setUp()方法初始化运行测试用例所需的设置。它在测试用例的开始运行。测试用例的数量决定了要执行的setUp()方法。例如,如果有十个测试用例,那么setUp()方法在任何测试用例之前被执行十次。此方法配置测试用例的初始阶段,例如设置 web 驱动程序及其相关组件。
import unittest
from selenium import webdriver
class Test(unittest.TestCase):
#setUp Class method
def setUp(self):
self.driver=webdriver.Firefox(executable_path=r'C:\Users\ADMIN\Desktop\geckodriver.exe')
print("Firefox Browser Opened")
def test_apress(self):
self.driver.get("https://apress.com")
print("Apress")
def test_google(self):
self.driver.get("https://google.com")
print("Google")
def test_facebook(self):
self.driver.get("https://facebook.com")
print("Facebook")
def test_twitter(self):
self.driver.get("https://twitter.com")
print("Twitter")
if __name__ =="__main__":
unittest.main()
当同一个 web 应用或 web 页面有多个测试用例时,setUp()方法是有益的。这减少了测试前所需的初始配置或设置。
拆卸( )
在每个测试用例执行之后,执行tearDown()方法。它是 unittest 中只陈述一次的类方法。它为 unittest 程序中可用的每个测试用例执行。
import unittest
from selenium import webdriver
class Test(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox(executable_path=r'C:\Users\ADMIN\Desktop\geckodriver.exe')
print("Opened Firefox Browser")
def test_apress(self):
self.driver.get("https://apress.com")
print("Apress")
def tearDown(self):
self.driver.quit()
print("Quit Browser")
if __name__ =="__main__":
unittest.main()
这个方法包含了一些动作,比如关闭 web 驱动或者浏览器,注销或者任何与测试用例相关的关闭动作。无论测试用例结果如何(即通过/失败),一旦执行了setUp()方法,就执行tearDown()方法。
Note
在 unittest 的同一个测试类中,一个测试用例可以同时包含 setUp()和 tearDown()方法。
setUpClass
setUpClass是 unittest 中的一个类,它在任何setUp或tearDown函数或同一测试类中出现的任何测试用例之前执行。包含此方法的类只执行它一次,并且它作为单个参数传递。setUpClass有一个名为@classmethod的类,在setUp()函数之前已经声明。
import unittest
from selenium import webdriver
#Class Setup
class Test(unittest.TestCase):
@classmethod
def setUpClass(suc):
suc.driver=webdriver.Firefox(executable_path=r'C:\Users\ADMIN\Desktop\geckodriver.exe')
print("Open Browser using setUpClass method")
def test_apress(self):
self.driver.get("https://apress.com")
print("Open Apress Site")
if __name__ =="__main__":
unittest.main()
在setUpClass中setUp()功能不是强制的。如果这个类执行失败,它会抛出一个异常,并且其中的测试用例or tearDown()函数不会执行。
tearDownClass
在setUpClass之后执行tearDownClass类,所有相关的测试用例出现在同一个测试类中。这个类在退出程序之前执行一次。
import unittest
from selenium import webdriver
#Class Setup
class Test(unittest.TestCase):
@classmethod
def setUpClass(suc):
suc.driver=webdriver.Firefox(executable_path=r'C:\Users\ADMIN\Desktop\geckodriver.exe')
print("Open Browser using setUpClass method")
def test_apress(self):
self.driver.get("https://apress.com")
print("Open Apress Site")
@classmethod
def tearDownClass(suc):
suc.driver.quit()
print("Close Browser using tearDownClass method")
if __name__ =="__main__":
unittest.main()
不管setUpClass方法如何,都可以用这个方法。
下面的简单测试案例展示了所有类和函数的一个例子。
setUpModuleandtearDownModule
在 Selenium 上有两个可用的 Python 模块。setUpModule的执行发生在开始,teardownModule的执行发生在结束。这两个模块是最近添加到 unittest 框架中的。
import unittest
from selenium import webdriver
class Test(unittest.TestCase):
#Class setup
@classmethod
def setUpClass(suc):
suc.driver=webdriver.Firefox(executable_path=r'C:\Users\ADMIN\Desktop\geckodriver.exe')
print("Open Browser using setUpClass method")
def setUp(self):
print("Execute setUp")
#test case
def test_apress(self):
self.driver.get("https://apress.com")
print("Executing Test Case: Open Apress Site")
def tearDown(self):
print("Execute tearDown")
@classmethod
def tearDownClass(suc):
suc.driver.quit()
print("Close Browser using tearDownClass method")
#Two Modules
def setUpModule():
print("Executes setUpModule")
def tearDownModule():
print("Executes tearDownModule")
if __name__ =="__main__":
unittest.main()
在程序中,首先打印setUpModule()中的打印语句,然后打印setUpClass()、setUp()、test_apress()、tearDown()、tearDownClass(),最后打印tearDownModule()。
测试执行顺序
由于定义了不同的模块、类、函数和测试用例,所以了解 unittest 框架中的执行顺序非常重要。执行顺序是由模块、类和函数的优先级决定的,它们的定义与程序中的位置无关。一个测试用例的整个执行流程如图 12-1 所示。
图 12-1
单元测试框架中的执行顺序
在 unittest 中,测试用例的执行顺序默认为字母顺序。为了区分优先级,您可以为所有的测试用例定义相同的名称,然后为每个用例定义一个顺序号;例如,test1、test2、test3、test4 等等。
Note
如果有多个测试用例,那么它们将按字母顺序执行。
测试工具比较
许多测试工具可以与 Selenium 上的 Python 一起使用,以生成报告和测试 web 应用。其中一些在表 12-1 中有所介绍。
表 12-1
Python 中的测试工具
|测试工具
|
种类
|
许可证类型
|
描述
| | --- | --- | --- | --- | | DocTest | 单元测试 | 免费软件 | 基于 Python 解释器的输出生成测试用例 | | Nose2 | 单元测试 | 免费软件 | nose 测试框架的继承者扩展了鼻子框架以外的功能;非常容易理解 | | Pytest | 单元测试 | 免费软件 | 一个初学者友好的工具,支持与 unittest 和 nose 的集成易于构建的小型测试可以扩展到复杂的功能测试 | | 机器人 | 验收测试 | 免费软件 | 验收测试驱动开发(ATDD)和机器人过程自动化(RPA)的通用框架提供简单的语法;易于与其他框架一起实现 | | 作证 | 单元测试 | 免费软件 | 基于 Python 的测试插件,提供更多与报告相关的功能和模块提供了 unittest 和 nose 之外的功能 |
摘要
这最后一章解释了使用不同方法得到相同结果的测试用例。unitttest 框架也被 Google 用来自动化测试用例。该框架详细阐述了基本术语和模块。每个模块都有一个与之相关的测试示例。当所有的模块被组合在一个测试用例中时,每个模块执行的顺序形成了一个流动的模式。我们用 Python Selenium 支持的不同测试工具的比较结束了这一章。