一、 前言

该文档描述一次基于python的webUI自动化框架搭建过程及简单的使用。

框架构成:python + selenium + unittest

二、准备及编写条件

准备:

  • IDEA工具:pycharm(社区版即可)
  • Python3.9
  • Webdriver.exe文件下载好,将该文件放在本地python的lib文件夹下,并将其配置到环境变量(实际上放到python的任意文件夹下都可以,但必须将其配置到环境变量,以便于运行时能找到这个文件)。或者在代码中指定驱动的路径也可以。如:driver
    = webdriver.Chrome(executable_path=‘driver/chromedriver.exe’)
  • Chrome浏览器

注意:Webdriver.exe的版本需要和浏览器版本一致,不一致则会报错,浏览器版本可通过浏览器 “设置” –> “关于Chrome” 查看

Webdriver.exe下载地址:http://chromedriver.storage.googleapis.com/index.html

编写条件:

需掌握python基础、元素定位、三种等待方式、鼠标键盘事件、窗口切换等(多多益善)

三、初体验

1、实现用户登录:

 1 from time import sleep 2 from selenium import webdriver 3 from selenium.webdriver.common.by import By 45 driver = webdriver.Chrome() 6 driver.get(r'https://xxx')# 打开浏览器并访问该链接,这里的链接不便展示哈 7 driver.maximize_window() 89 # 定位元素并操作10 driver.find_element(By.NAME, 'username').send_keys('luoyang')11 driver.find_element(By.NAME, 'password').send_keys('123456')12 driver.find_element(By.XPATH, '//*[@id="app"]/div/div[2]/div/form/button').click()13 sleep(10)14 15 # 关闭并退出浏览器16 driver.quit()17 1819 20 # 关于close()和quit():close()只是关闭浏览器当前窗口,并不会退出浏览器21 22 # 当浏览器只有一个窗口时,使用close()虽然退出了浏览器,但驱动还在运行23 24 # 而quit()则会关闭所有窗口,清除session,并结束驱动运行

2、进一步实现,引入unittest框架

 1 from time import sleep 2 from selenium import webdriver 3 import unittest 4 from selenium.webdriver.common.by import By 567 class Login(unittest.TestCase): 89 def setUp(self) -> None:10 self.driver = webdriver.Chrome()11 self.url = r'https://xxx'12 self.driver.maximize_window()# 最大化窗口13 self.driver.get(self.url)14 15 def test_login(self, username='luoyang', password='123456'):16 self.driver.find_element(By.NAME, 'username').send_keys(username)17 self.driver.find_element(By.NAME, 'password').send_keys(password)18 self.driver.find_element(By.XPATH, '//*[@id="app"]/div/div[2]/div/form/button').click()19 20 def tearDown(self) -> None:21 sleep(5)22 self.driver.quit()23 24 25 if __name__ == '__main__':26 unittest.main() # 执行测试

关于unittest框架详细学了之后再在其他篇章中发表…

四、POM设计模式

1、POM:

即page object model,页面对象模型,顾名思义,就是将每个页面当做一个对象来看待,将页面中需要操作的元素提取到这个对象中,此后每当要用到这些元素时,调用该对象即可。让我们来具体使用一下吧!

首先,我们先创建好结构:

all_case_run.py –模块,用于执行所有的测试类

|–common – 包,用于存放公用的工具类

|–universal_method.py – 通用工具类

|–case – 包,用于存放所有的测试类

|–test_login.py – 登录测试用例类

|–pages – 包,用于存放页面类及页面基类(basePage)

|–base_page.py – 所有页面对象都需继承该类,该类里封装了元素的定位、操作等方法

|–login_page.py – 登录页面类,该类包含了登录页面的元素、元素定位及操作逻辑等

|–data – 包,用于存放元素定位路径文件

|–login.yaml – yaml数据文件,用于存放登录页面的元素定位路径数据

|–report – 包,用于存放测试报告文件及日志文件(自动生成)

至此,一个简便的结构就创建好了。

2、 all_case.run.py内容:

 1 import time 23 from BeautifulReport import BeautifulReport 45 from GAD_webUI.commen.universal_method import UniversalMethod 67 from GAD_webUI.commen.send_email import SendEmail 89 10 11 if __name__ == '__main__':12 13 14 print('用例开始执行-------------------')15 16 now = time.strftime('%Y-%m-%d_%H_%M_%S', time.localtime(time.time()))17 18 filename = 'D:\\testStudy\gitstudy\gitrepository\pythonstudy\pythonworkspace\GAD_webUI\\report'19 20 UniversalMethod.del_report(filename, 3)# 测试报告生成之前删除冗余的测试报告(最多只剩3个测试报告),这里的 del_report() 方法是自定义的21 22 23 24 result = BeautifulReport(UniversalMethod.createSuite()) # 创建测试套件容器,这里的 createSuite() 方法也是自定义的25 26 result.report(filename=now+'GAD_smoke', description='GAD冒烟测试', report_dir=filename) # 生成测试报告,这里采用的是 BeautifulReport

3、 login.yaml内容:

1 username: //*[@id="app"]/div/div[2]/div/form/div[2]/div/div/input2 3 password: //*[@id="app"]/div/div[2]/div/form/div[3]/div/div/input4 5 login_btn: //*[@id="app"]/div/div[2]/div/form/button# 登录按钮 xpath路径6 7 login_error: /html/body/div[3]/div# 用户登录失败出现的元素

这里还需要优化一下,因为我全部采用的是xpath定位

Python获取yaml文件的内容:

 1 # 读取yaml文件并返回一个数据集,返回的是一个字典 23 @staticmethod 45 def get_yaml_info( 67 yaml_path=r'D:\G_webUI\data\advertisingId_page.yaml'): 89 yaml_file = open(yaml_path, 'r', encoding='utf-8')10 11 content = yaml.load(yaml_file, Loader=yaml.FullLoader)12 13 return content

能读取yaml文件的内容,能干什么不用多说了吧

4、 basePage.py内容:

1 """2 3 所有页面类都需继承该类,该类封装了Selenium 基本方法(元素定位、元素等待、鼠标事件等)4 5 """6 7 import random8 91011 from selenium.common.exceptions import NoSuchElementException, TimeoutException 1213 from selenium.webdriver import Keys 1415 from selenium.webdriver.common.by import By 1617 from selenium.webdriver.support.wait import WebDriverWait 18192021 from selenium.webdriver.support import expected_conditions as EC 22232425 from GAD_webUI.commen.universal_method import UniversalMethod 2627 from selenium.webdriver.common.action_chains import ActionChains 282930313233 class BasePage(object): 3435 def __init__(self, driver, path='https://xxx'): 3637 self.driver = driver 3839 self.url = path 4041 self.driver.implicitly_wait(30)# 隐式等待,设置一次全局有效 4243 self.driver.maximize_window() 4445 self.navigation_els = UniversalMethod.get_yaml_info( 4647 r'D:\G_webUI\data\navigation.yaml') 48495051 def open_page(self): 5253 self.driver.get(self.url) # 打开浏览器 54555657 # 单个元素的定位方法1 5859 def find_element(self, *args): 6061 try: 6263 return self.driver.find_element(*args) 6465 except NoSuchElementException: 6667 print("未找到该元素:" + str(args)) 68697071 # 单个元素的定位方法2 7273 def find_element_v(self, *args): 7475 try: 7677 return WebDriverWait(self.driver, 5, 0.5).until(EC.visibility_of_element_located(*args)) 7879 except (NoSuchElementException, TimeoutException): 8081 print("超过元素定位等待时长,无法获取到该元素,请检查定位路径") 82838485 # 单个元素的定位方法3 8687 def find_element_p(self, *args): 8889 try: 9091 return WebDriverWait(self.driver, 5, 0.5).until(EC.presence_of_element_located(*args)) 9293 except (NoSuchElementException, TimeoutException): 9495 print("超过元素定位等待时长,无法获取到该元素,请检查定位路径") 96979899 # 多个元素的定位方法1100 101 def find_elements(self, *loc):102 103 try:104 105 return self.driver.find_elements(*loc)106 107 except (NoSuchElementException, TimeoutException):108 109 print("未找到该元素:" + str(loc))110 111 112 113 # 多个元素的定位方法2114 115 def find_elements_v(self, *loc):116 117 try:118 119 return WebDriverWait(self.driver, 5, 0.5).until(EC.visibility_of_any_elements_located(*loc))120 121 except (NoSuchElementException, TimeoutException):122 123 print("超过元素定位等待时长,无法获取到该元素,请检查定位路径")124 125 126 127 # 多个元素的定位方法3128 129 def find_elements_p(self, *loc):130 131 try:132 133 return WebDriverWait(self.driver, 5, 0.5).until(EC.presence_of_all_elements_located(*loc))134 135 except (NoSuchElementException, TimeoutException):136 137 print("超过元素定位等待时长,无法获取到该元素,请检查定位路径")138 139 140 141 # 点击元素,以JS脚本的方式142 143 def click_JS(self, element):144 145 self.driver.execute_script('arguments[0].click();', element)146 147 148 149 # 点击元素,普通方式150 151 def click(self, element_xp):152 153 try:154 155 self.find_element_p((By.XPATH, element_xp)).click()156 157 except AttributeError:158 159 print('元素获得为空,无属性可用')160 161 162 163 # 清除输入框164 165 def clear_input(self, element_xp):166 167 try:168 169 self.find_element_p((By.XPATH, element_xp)).clear()170 171 except AttributeError:172 173 print('元素获得为空,无属性可用')174 175 176 177 # 输入框输入值178 179 def send_kw(self, element_xp, kw):180 181 try:182 183 self.find_element_p((By.XPATH, element_xp)).send_keys(kw)184 185 except AttributeError:186 187 print('元素获得为空,无属性可用')188 189 190 191 # 模拟键盘向页面发送end指令(滑动到页面底部)192 193 def page_end(self, table_xp):194 195 try:196 197 self.find_element_p((By.XPATH, table_xp)).send_keys(Keys.END)198 199 except AttributeError:200 201 print('元素获得为空,无属性可用')202 203 204 205 # 鼠标移动到指定元素上206 207 def move_element(self, element_xp):208 209 move = self.find_element_p((By.XPATH, element_xp))210 211 ActionChains(self.driver).move_to_element(move).perform()212 213 214 215 # 双击元素216 217 def double_click(self, element):218 219 ActionChains(self.driver).double_click(element).perform()220 221 222 223 # 切换到指定窗口224 225 def switch_window(self, num):226 227 handles = self.driver.window_handles# 获取当前窗口句柄集合228 229 try:230 231 self.driver.switch_to.window(handles[num])# 切换到指定窗口232 233 except Exception:234 235 raise

5、 loginPage.py内容:

 1 from GAD_webUI.commen.universal_method import UniversalMethod 5 from GAD_webUI.pages.base_page import BasePage10 11 class LoginPage(BasePage):12 13 login_els = UniversalMethod.get_yaml_info(14 15 r'D:\G_webUI\data\login.yaml')# login_els是个字典16 17 18 19 def login_GAD(self, username, password):20 21 self.open_page()# 打开浏览器22 23 self.send_kw(self.login_els['username'], username)# 输入用户名24 25 self.send_kw(self.login_els['password'], password)# 输入密码26 27 self.click(self.login_els['login_btn'])# 点击登录28 29 sleep(2)30 31 32 33 error_el = self.find_element_p((By.XPATH, self.login_els['login_error']))34 35 if error_el:36 37 return error_el.text38 39 else:40 41 print('登录成功')

6、 test_login.py(测试类)内容:

 1 import unittest 23 from time import sleep 45 from selenium import webdriver 67 from GAD_webUI.pages.login_page import LoginPage 89 10 11 class Login(unittest.TestCase):12 13 driver = webdriver.Chrome()14 15 16 17 @classmethod18 19 def setUpClass(cls, ) -> None:20 21 cls.login_page = LoginPage(cls.driver)22 23 24 25 def test_login(self, username='v-luoyang', password='123456'):26 27 error_text = self.login_page.login_GAD(username, password)28 29 self.assertFalse(error_text is not None, msg=error_text)# 如果错误信息存在,则登录失败,输出错误提示信息30 31 32 33 @classmethod34 35 def tearDownClass(cls) -> None:36 37 sleep(5)38 39 cls.driver.quit()40 41 42 43 if __name__ == '__main__':# 执行all_test_run.py 时,需将该段注释掉44 45 unittest.main() 

7、 引入测试报告

在小节2中已经实现了。

result = BeautifulReport(UniversalMethod.createSuite()) # 创建测试套件容器
    result.report(filename=now+‘G_smoke’, description=‘G冒烟测试’, report_dir=filename) # 生成测试报告

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

1 import os2 3 4 5 """6 7 这个文件主要是配置发送邮件的主题、正文等,将测试报告发送并抄送到相关人邮箱的逻辑。8 9 """ 1011 import smtplib 1213 from email.mime.text import MIMEText 1415 from email.mime.multipart import MIMEMultipart 16171819 class SendEmail(object): 2021 def __init__(self, username, passwd, recv, title, content, 2223file_path=None, ssl=False, 2425email_host='smtp.163.com', port=25, ssl_port=465): 2627 self.username = username# 用户名 2829 self.passwd = passwd# 密码 3031 self.recv = recv# 收件人,多个要传list ['a@qq.com','b@qq.com] 3233 self.title = title# 邮件标题 3435 self.content = content# 邮件正文 3637 self.file_path = file_path# 附件路径,如果不在当前目录下,要写绝对路径 3839 self.email_host = email_host# smtp服务器地址 4041 self.port = port# 普通端口 4243 self.ssl = ssl# 是否安全链接 4445 self.ssl_port = ssl_port# 安全链接端口 46474849 # 发送邮件 5051 def send_email(self): 5253 msg = MIMEMultipart() 5455 msg.attach(MIMEText(self.content))# 邮件正文的内容 56575859 # 构造附件 6061 for f_path, file_dirs, files in os.walk(self.file_path): 6263 for file in files: 6465 msg.attach(self._att_html(os.path.join(f_path, file))) 66676869 msg['Subject'] = self.title# 邮件主题 7071 msg['From'] = self.username# 发送者账号 7273 msg['To'] = ','.join(self.recv)# 接收者账号列表 7475 if self.ssl: 7677 self.smtp = smtplib.SMTP_SSL(self.email_host, port=self.ssl_port) 7879 else: 8081 self.smtp = smtplib.SMTP(self.email_host, port=self.port) 8283 # 发送邮件服务器的对象 8485 self.smtp.login(self.username, self.passwd) 8687 try: 8889 self.smtp.sendmail(self.username, self.recv, msg.as_string()) 9091 pass 9293 except Exception as e: 9495 print('出错了。。', e) 9697 else: 9899 print('发送成功!')100 101 self.smtp.quit()102 103 104 105 # 构造邮件附件106 107 @staticmethod108 109 def _att_html(filename):110 111 # 构造附件112 113 atthtml = MIMEText(open(filename, 'rb').read(), 'base64',114 115'utf-8')# 文件放在同一路径,不放在同一路径改一下比如'D:/test/report.html116 117 atthtml["Content-Type"] = 'application/octet-stream'118 119 atthtml["Content-Disposition"] = 'attachment;filename = "GAD_Smoke_report.html"'120 121 return atthtml122 123 124 125 126 # 调用并发送邮件127 if __name__ == '__main__':128 129 m = SendEmail(130 131 username='cicada_luo@163.com',  # 这里填发送者邮箱132 133 passwd='TCBXOAOF...',    # 授权码还是什么忘记了134 135 recv=['1761885773@qq.com'],  # 接收者邮箱136 137 title='G——smoke',138 139 content='G——smoke测试报告',140 141 file_path='D:\\G_webUI\\report',  # 发送的文件142 143 email_host='smtp.163.com',144 145 ssl_port=465,146 147 ssl=True,148 149 )150 151 m.send_email()  # 发送邮件

9、 引入DDT

① 安装DDT,打开cmd,输入pip install ddt

② 在测试类上写上@ddt,表示该用例类需要进行数据驱动

③ 在测试方法上写上@file_data(file_path),表示引入外部文件进行数据驱动。

④ 如果步骤③传入的文件是yaml格式,那么用例方法参数需要用**args来接收文件的内容(表示接收文件的所有内容到该参数中) ;如果传入的文件是其他的格式,那么用一个参数接收即可(接收的是json数据格式的值)

 1 import unittest 2345 from ddt import file_data, ddt 67 from selenium import webdriver 89 from GAD_webUI.pages.login_page import LoginPage10 11 12 13 14 15 @ddt16 17 class test_Login(unittest.TestCase):18 19 20 21 def setUp(self) -> None:22 23 self.driver = webdriver.Chrome()24 25 self.login_page = LoginPage(self.driver)26 27 28 29 def tearDown(self) -> None:30 31 self.driver.quit()32 33 34 35 @file_data(r'D:\G_webUI\data\user_login.yaml')36 37 def test_login(self, **kwargs):38 39 print(kwargs)40 41 error_text = self.login_page.login_GAD(kwargs['username'], kwargs['password'])42 43 self.assertFalse(error_text is not None, msg=error_text)# 如果错误信息存在,则登录失败,输出错误提示信息44 45 print("------------------------------------")46 47 48 49 50 51 if __name__ == '__main__':# 执行all_test_run.py 时,需将该段注释掉52 53 unittest.main()

五、所遇问题及解决思路

1、元素定位不到怎么办

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

解决思路:

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

② 添加等待。

有时,程序执行过快,导致程序已经执行完了,而元素还未加载出来,那么就会抛出异常,

我们添加等待时间即可。最粗暴的做法就是 sleep(3)—强制等待3秒,这样做使得程序运行时间较长,一般少用。最常用的是使用显示等待(搭配 until()方法、expected_conditions 类来使用)。例:

1 from selenium.common.exceptions import NoSuchElementException, TimeoutException 2 from selenium.webdriver.support import expected_conditions as EC 3 45 # 单个元素的定位方法3 67 def find_element_p(self, *args): 89 try:10 11 return WebDriverWait(self.driver, 5, 0.5).until(EC.presence_of_element_located(*args))12 13 except (NoSuchElementException, TimeoutException):14 15 print("超过元素定位等待时长,无法获取到该元素,请检查定位路径")

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

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

2、元素无法交互

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

解决思路:

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

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

1 # 鼠标移动到指定元素上2 3 def move_element(self, element_xp):4 5 move = self.find_element_p((By.XPATH, element_xp))6 7 ActionChains(self.driver).move_to_element(move).perform()      

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

以上脚本虽然较为粗糙,但也五脏俱全,后面熟悉了再回来补充,欢迎各位前来指正。

最后: 可以在公众号:伤心的辣条 ! 自行领取一份216页软件测试工程师面试宝典文档资料【免费的】。以及相对应的视频学习教程免费分享!,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。

学习不要孤军奋战,最好是能抱团取暖,相互成就一起成长,群众效应的效果是非常强大的,大家一起学习,一起打卡,会更有学习动力,也更能坚持下去。你可以加入我们的测试技术交流扣扣群:914172719(里面有各种软件测试资源和技术讨论)

喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一 键三连哦!