赞
踩
该文档描述一次基于python的webUI自动化框架搭建过程及简单的使用。
框架构成:python + selenium + unittest
注意:Webdriver.exe的版本需要和浏览器版本一致,不一致则会报错,浏览器版本可通过浏览器 “设置” --> “关于Chrome” 查看
Webdriver.exe下载地址:http://chromedriver.storage.googleapis.com/index.html
需掌握python基础、元素定位、三种等待方式、鼠标键盘事件、窗口切换等(多多益善)
- 1 from time import sleep
- 2 from selenium import webdriver
- 3 from selenium.webdriver.common.by import By
- 4
- 5 driver = webdriver.Chrome()
- 6 driver.get(r'https://xxx') # 打开浏览器并访问该链接,这里的链接不便展示哈
- 7 driver.maximize_window()
- 8
- 9 # 定位元素并操作
- 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
- 18
- 19
- 20 # 关于close()和quit():close()只是关闭浏览器当前窗口,并不会退出浏览器
- 21
- 22 # 当浏览器只有一个窗口时,使用close()虽然退出了浏览器,但驱动还在运行
- 23
- 24 # 而quit()则会关闭所有窗口,清除session,并结束驱动运行
- 1 from time import sleep
- 2 from selenium import webdriver
- 3 import unittest
- 4 from selenium.webdriver.common.by import By
- 5
- 6
- 7 class Login(unittest.TestCase):
- 8
- 9 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框架详细学了之后再在其他篇章中发表...
即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 -- 包,用于存放测试报告文件及日志文件(自动生成)
至此,一个简便的结构就创建好了。
- 1 import time
- 2
- 3 from BeautifulReport import BeautifulReport
- 4
- 5 from GAD_webUI.commen.universal_method import UniversalMethod
- 6
- 7 from GAD_webUI.commen.send_email import SendEmail
- 8
- 9
- 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
- 1 username: //*[@id="app"]/div/div[2]/div/form/div[2]/div/div/input
- 2
- 3 password: //*[@id="app"]/div/div[2]/div/form/div[3]/div/div/input
- 4
- 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文件并返回一个数据集,返回的是一个字典
- 2
- 3 @staticmethod
- 4
- 5 def get_yaml_info(
- 6
- 7 yaml_path=r'D:\G_webUI\data\advertisingId_page.yaml'):
- 8
- 9 yaml_file = open(yaml_path, 'r', encoding='utf-8')
- 10
- 11 content = yaml.load(yaml_file, Loader=yaml.FullLoader)
- 12
- 13 return content
能读取yaml文件的内容,能干什么不用多说了吧
- 1 """
- 2
- 3 所有页面类都需继承该类,该类封装了Selenium 基本方法(元素定位、元素等待、鼠标事件等)
- 4
- 5 """
- 6
- 7 import random
- 8
- 9
- 10
- 11 from selenium.common.exceptions import NoSuchElementException, TimeoutException
- 12
- 13 from selenium.webdriver import Keys
- 14
- 15 from selenium.webdriver.common.by import By
- 16
- 17 from selenium.webdriver.support.wait import WebDriverWait
- 18
- 19
- 20
- 21 from selenium.webdriver.support import expected_conditions as EC
- 22
- 23
- 24
- 25 from GAD_webUI.commen.universal_method import UniversalMethod
- 26
- 27 from selenium.webdriver.common.action_chains import ActionChains
- 28
- 29
- 30
- 31
- 32
- 33 class BasePage(object):
- 34
- 35 def __init__(self, driver, path='https://xxx'):
- 36
- 37 self.driver = driver
- 38
- 39 self.url = path
- 40
- 41 self.driver.implicitly_wait(30) # 隐式等待,设置一次全局有效
- 42
- 43 self.driver.maximize_window()
- 44
- 45 self.navigation_els = UniversalMethod.get_yaml_info(
- 46
- 47 r'D:\G_webUI\data\navigation.yaml')
- 48
- 49
- 50
- 51 def open_page(self):
- 52
- 53 self.driver.get(self.url) # 打开浏览器
- 54
- 55
- 56
- 57 # 单个元素的定位方法1
- 58
- 59 def find_element(self, *args):
- 60
- 61 try:
- 62
- 63 return self.driver.find_element(*args)
- 64
- 65 except NoSuchElementException:
- 66
- 67 print("未找到该元素:" + str(args))
- 68
- 69
- 70
- 71 # 单个元素的定位方法2
- 72
- 73 def find_element_v(self, *args):
- 74
- 75 try:
- 76
- 77 return WebDriverWait(self.driver, 5, 0.5).until(EC.visibility_of_element_located(*args))
- 78
- 79 except (NoSuchElementException, TimeoutException):
- 80
- 81 print("超过元素定位等待时长,无法获取到该元素,请检查定位路径")
- 82
- 83
- 84
- 85 # 单个元素的定位方法3
- 86
- 87 def find_element_p(self, *args):
- 88
- 89 try:
- 90
- 91 return WebDriverWait(self.driver, 5, 0.5).until(EC.presence_of_element_located(*args))
- 92
- 93 except (NoSuchElementException, TimeoutException):
- 94
- 95 print("超过元素定位等待时长,无法获取到该元素,请检查定位路径")
- 96
- 97
- 98
- 99 # 多个元素的定位方法1
- 100
- 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 # 多个元素的定位方法2
- 114
- 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 # 多个元素的定位方法3
- 128
- 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
- 1 from GAD_webUI.commen.universal_method import UniversalMethod
- 5 from GAD_webUI.pages.base_page import BasePage
- 10
-
- 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.text
- 38
- 39 else:
- 40
- 41 print('登录成功')
- 1 import unittest
- 2
- 3 from time import sleep
- 4
- 5 from selenium import webdriver
- 6
- 7 from GAD_webUI.pages.login_page import LoginPage
- 8
- 9
- 10
- 11 class Login(unittest.TestCase):
- 12
- 13 driver = webdriver.Chrome()
- 14
- 15
- 16
- 17 @classmethod
- 18
- 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 @classmethod
- 34
- 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()
在小节2中已经实现了。
result = BeautifulReport(UniversalMethod.createSuite()) # 创建测试套件容器
result.report(filename=now+'G_smoke', description='G冒烟测试', report_dir=filename) # 生成测试报告
- 1 import os
- 2
- 3
- 4
- 5 """
- 6
- 7 这个文件主要是配置发送邮件的主题、正文等,将测试报告发送并抄送到相关人邮箱的逻辑。
- 8
- 9 """
- 10
- 11 import smtplib
- 12
- 13 from email.mime.text import MIMEText
- 14
- 15 from email.mime.multipart import MIMEMultipart
- 16
- 17
- 18
- 19 class SendEmail(object):
- 20
- 21 def __init__(self, username, passwd, recv, title, content,
- 22
- 23 file_path=None, ssl=False,
- 24
- 25 email_host='smtp.163.com', port=25, ssl_port=465):
- 26
- 27 self.username = username # 用户名
- 28
- 29 self.passwd = passwd # 密码
- 30
- 31 self.recv = recv # 收件人,多个要传list ['a@qq.com','b@qq.com]
- 32
- 33 self.title = title # 邮件标题
- 34
- 35 self.content = content # 邮件正文
- 36
- 37 self.file_path = file_path # 附件路径,如果不在当前目录下,要写绝对路径
- 38
- 39 self.email_host = email_host # smtp服务器地址
- 40
- 41 self.port = port # 普通端口
- 42
- 43 self.ssl = ssl # 是否安全链接
- 44
- 45 self.ssl_port = ssl_port # 安全链接端口
- 46
- 47
- 48
- 49 # 发送邮件
- 50
- 51 def send_email(self):
- 52
- 53 msg = MIMEMultipart()
- 54
- 55 msg.attach(MIMEText(self.content)) # 邮件正文的内容
- 56
- 57
- 58
- 59 # 构造附件
- 60
- 61 for f_path, file_dirs, files in os.walk(self.file_path):
- 62
- 63 for file in files:
- 64
- 65 msg.attach(self._att_html(os.path.join(f_path, file)))
- 66
- 67
- 68
- 69 msg['Subject'] = self.title # 邮件主题
- 70
- 71 msg['From'] = self.username # 发送者账号
- 72
- 73 msg['To'] = ','.join(self.recv) # 接收者账号列表
- 74
- 75 if self.ssl:
- 76
- 77 self.smtp = smtplib.SMTP_SSL(self.email_host, port=self.ssl_port)
- 78
- 79 else:
- 80
- 81 self.smtp = smtplib.SMTP(self.email_host, port=self.port)
- 82
- 83 # 发送邮件服务器的对象
- 84
- 85 self.smtp.login(self.username, self.passwd)
- 86
- 87 try:
- 88
- 89 self.smtp.sendmail(self.username, self.recv, msg.as_string())
- 90
- 91 pass
- 92
- 93 except Exception as e:
- 94
- 95 print('出错了。。', e)
- 96
- 97 else:
- 98
- 99 print('发送成功!')
- 100
- 101 self.smtp.quit()
- 102
- 103
- 104
- 105 # 构造邮件附件
- 106
- 107 @staticmethod
- 108
- 109 def _att_html(filename):
- 110
- 111 # 构造附件
- 112
- 113 atthtml = MIMEText(open(filename, 'rb').read(), 'base64',
- 114
- 115 'utf-8') # 文件放在同一路径,不放在同一路径改一下比如'D:/test/report.html
- 116
- 117 atthtml["Content-Type"] = 'application/octet-stream'
- 118
- 119 atthtml["Content-Disposition"] = 'attachment;filename = "GAD_Smoke_report.html"'
- 120
- 121 return atthtml
- 122
- 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() # 发送邮件
① 安装DDT,打开cmd,输入pip install ddt
② 在测试类上写上@ddt,表示该用例类需要进行数据驱动
③ 在测试方法上写上@file_data(file_path),表示引入外部文件进行数据驱动。
④ 如果步骤③传入的文件是yaml格式,那么用例方法参数需要用**args来接收文件的内容(表示接收文件的所有内容到该参数中) ;如果传入的文件是其他的格式,那么用一个参数接收即可(接收的是json数据格式的值)
- 1 import unittest
- 2
- 3
- 4
- 5 from ddt import file_data, ddt
- 6
- 7 from selenium import webdriver
- 8
- 9 from GAD_webUI.pages.login_page import LoginPage
- 10
- 11
- 12
- 13
- 14
- 15 @ddt
- 16
- 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()
表现形式为:程序抛出 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
- 4
- 5 # 单个元素的定位方法3
- 6
- 7 def find_element_p(self, *args):
- 8
- 9 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路径不一致,从而导致定位元素失败。如某些元素需要点击才能出现,但你的脚本程序未进行点击操作,自然就不可能定位得到该元素了。
- 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()
③检查元素是否被其他元素遮挡。当元素被其他元素所遮盖,那么也无法对该元素进行操作。解决办法就是移除其他元素的遮盖(通过脚本操作遮盖元素移开被遮盖的元素)。
以上脚本虽然较为粗糙,但也五脏俱全,后面熟悉了再回来补充,欢迎各位前来指正。
最后,绵薄之力
感谢每一个认真阅读我文章的人,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。