一、? 前言
該文檔描述一次基于python的webUI自動化框架搭建過程及簡單的使用。
框架構成:python + selenium + unittest
二、準備及編寫條件
準備:
-
- IDEA工具:pycharm(社區(qū)版即可)
- Python3.9
- Webdriver.exe文件下載好,將該文件放在本地python的lib文件夾下,并將其配置到環(huán)境變量(實際上放到python的任意文件夾下都可以,但必須將其配置到環(huán)境變量,以便于運行時能找到這個文件)?;蛘咴诖a中指定驅動的路徑也可以。如:driver = webdriver.Chrome(executable_path='driver/chromedriver.exe')
-
- Chrome瀏覽器
注意:Webdriver.exe的版本需要和瀏覽器版本一致,不一致則會報錯,瀏覽器版本可通過瀏覽器 “設置” --> “關于Chrome” 查看
Webdriver.exe下載地址:http://chromedriver.storage.googleapis.com/index.html
編寫條件:
需掌握python基礎、元素定位、三種等待方式、鼠標鍵盤事件、窗口切換等(多多益善)
三、初體驗
?1、實現(xiàn)用戶登錄:
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,并結束驅動運行
2、進一步實現(xiàn),引入unittest框架
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() # 執(zhí)行測試
關于unittest框架詳細學了之后再在其他篇章中發(fā)表...
四、POM設計模式
1、POM:
即page object model,頁面對象模型,顧名思義,就是將每個頁面當做一個對象來看待,將頁面中需要操作的元素提取到這個對象中,此后每當要用到這些元素時,調用該對象即可。讓我們來具體使用一下吧!
首先,我們先創(chuàng)建好結構:
all_case_run.py?? --模塊,用于執(zhí)行所有的測試類
|--common???? -- 包,用于存放公用的工具類
?|--universal_method.py????? -- 通用工具類
|--case?????? -- 包,用于存放所有的測試類
|--test_login.py???? -- 登錄測試用例類
|--pages????? -- 包,用于存放頁面類及頁面基類(basePage)
|--base_page.py????? -- 所有頁面對象都需繼承該類,該類里封裝了元素的定位、操作等方法
|--login_page.py???? -- 登錄頁面類,該類包含了登錄頁面的元素、元素定位及操作邏輯等
|--data?????? -- 包,用于存放元素定位路徑文件
|--login.yaml???? -- yaml數(shù)據(jù)文件,用于存放登錄頁面的元素定位路徑數(shù)據(jù)
|--report???? -- 包,用于存放測試報告文件及日志文件(自動生成)
???
? ? 至此,一個簡便的結構就創(chuàng)建好了。
2、 all_case.run.py內容:
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('用例開始執(zhí)行-------------------')
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()) # 創(chuàng)建測試套件容器,這里的 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/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 # 用戶登錄失敗出現(xiàn)的元素
這里還需要優(yōu)化一下,因為我全部采用的是xpath定位
Python獲取yaml文件的內容:
1 # 讀取yaml文件并返回一個數(shù)據(jù)集,返回的是一個字典
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文件的內容,能干什么不用多說了吧
4、 basePage.py內容:
?
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 # 模擬鍵盤向頁面發(fā)送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 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('登錄成功')
6、 test_login.py(測試類)內容:
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__': # 執(zhí)行all_test_run.py 時,需將該段注釋掉
44
45 unittest.main()
7、 引入測試報告
在小節(jié)2中已經(jīng)實現(xiàn)了。
result = BeautifulReport(UniversalMethod.createSuite())???? # 創(chuàng)建測試套件容器
result.report(filename=now+'G_smoke', description='G冒煙測試', report_dir=filename)???? # 生成測試報告
8、 執(zhí)行完測試自動發(fā)送郵件(經(jīng)嘗試可用)
1 import os
2
3
4
5 """
6
7 這個文件主要是配置發(fā)送郵件的主題、正文等,將測試報告發(fā)送并抄送到相關人郵箱的邏輯。
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 # 發(fā)送郵件
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 # 發(fā)送者賬號
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 # 發(fā)送郵件服務器的對象
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('發(fā)送成功!')
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 # 調用并發(fā)送郵件
127 if __name__ == '__main__':
128
129 m = SendEmail(
130
131 username='cicada_luo@163.com', # 這里填發(fā)送者郵箱
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', # 發(fā)送的文件
142
143 email_host='smtp.163.com',
144
145 ssl_port=465,
146
147 ssl=True,
148
149 )
150
151 m.send_email() # 發(fā)送郵件
9、 引入DDT
?、? 安裝DDT,打開cmd,輸入pip install ddt
?、? 在測試類上寫上@ddt,表示該用例類需要進行數(shù)據(jù)驅動
?、? 在測試方法上寫上@file_data(file_path),表示引入外部文件進行數(shù)據(jù)驅動。
?、? 如果步驟③傳入的文件是yaml格式,那么用例方法參數(shù)需要用**args來接收文件的內容(表示接收文件的所有內容到該參數(shù)中) ;如果傳入的文件是其他的格式,那么用一個參數(shù)接收即可(接收的是json數(shù)據(jù)格式的值)
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__': # 執(zhí)行all_test_run.py 時,需將該段注釋掉
52
53 unittest.main()
五、所遇問題及解決思路
1、元素定位不到怎么辦
?????? 表現(xiàn)形式為:程序拋出?NoSuchElementException?異常
??? 解決思路:
?、? 檢查元素定位屬性值是否寫錯,很多時候錯誤都是因為粗心導致的。
?、? 添加等待。
有時,程序執(zhí)行過快,導致程序已經(jīng)執(zhí)行完了,而元素還未加載出來,那么就會拋出異常,
我們添加等待時間即可。最粗暴的做法就是 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路徑不一致,從而導致定位元素失敗。如某些元素需要點擊才能出現(xiàn),但你的腳本程序未進行點擊操作,自然就不可能定位得到該元素了。
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()
?、蹤z查元素是否被其他元素遮擋。當元素被其他元素所遮蓋,那么也無法對該元素進行操作。解決辦法就是移除其他元素的遮蓋(通過腳本操作遮蓋元素移開被遮蓋的元素)。
? 以上腳本雖然較為粗糙,但也五臟俱全,后面熟悉了再回來補充,歡迎各位前來指正。
最后,綿薄之力
感謝每一個認真閱讀我文章的人,雖然不是什么很值錢的東西,如果你用得到的話可以直接拿走:
?這些資料,對于【軟件測試】的朋友來說應該是最全面最完整的備戰(zhàn)倉庫,這個倉庫也陪伴上萬個測試工程師們走過最艱難的路程,希望也能幫助到你!
文章來源:http://www.zghlxwxcb.cn/news/detail-787033.html
?文章來源地址http://www.zghlxwxcb.cn/news/detail-787033.html
到了這里,關于webUI自動化之基本框架搭建(python + selenium + unittest)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!