?
?文章來源:http://www.zghlxwxcb.cn/news/detail-778534.html
??作者簡介:博主是一位測試管理者,同時也是一名對外企業(yè)兼職講師。
??主頁地址:【Austin_zhai】
??目的與景愿:旨在于能幫助更多的測試行業(yè)人員提升軟硬技能,分享行業(yè)相關(guān)最新信息。
??聲明:博主日常工作較為繁忙,文章會不定期更新,各類行業(yè)或職場問題歡迎大家私信,有空必回。

?
?
1. 目的
??web自動化測試作為軟件自動化測試領(lǐng)域中繞不過去的一個“香餑餑”,通常都會作為廣大測試從業(yè)者的首選學(xué)習(xí)對象,相較于C/S架構(gòu)的自動化來說,B/S有著其無法忽視的諸多優(yōu)勢,從行業(yè)發(fā)展趨、研發(fā)模式特點、測試工具支持,其整體的完整生態(tài)已經(jīng)遠(yuǎn)遠(yuǎn)超過了C/S架構(gòu)方面的測試價值。
??接上一篇文章,我們將繼續(xù)深入探討在python中如何將Selenium的一些方法進(jìn)行封裝和二開,以便我們更高效的在自己的測試項目中靈活運用。
?
?
2. 封裝
??既然選擇了python這門語言來實現(xiàn)web自動化,那我們就不得不講到一個重要的概念,那就是面向?qū)ο缶幊汤砟钪械姆庋b。就字面意思來理解的話,就像是把各種物品放入一個箱子內(nèi),日后需要使用的話就必須從這個箱子里拿才可以獲取那個放入的物品。而這個比喻內(nèi)的箱子就是python內(nèi)的類,而各類物品則是我們自己根據(jù)需要自定義的各種屬性和數(shù)據(jù)、方法,后續(xù)需要使用這些屬性、數(shù)據(jù)、方法時,只要引入對應(yīng)的類并實例化即可。
??那這時一定會有人要問,封裝的好處是什么呢?其實這里面的優(yōu)點也是顯而易見的。第一,封裝完的類你完全不需要關(guān)心里面的功能實現(xiàn)邏輯(除非你要二開),就比如time這個內(nèi)置模塊,你日常測開工作中正常使用其中的內(nèi)置函數(shù)即可,完全不需要搞明白里面的邏輯是如何實現(xiàn)的。第二,方便復(fù)用,面向?qū)ο缶幊叹褪侨绱?,萬物皆對象,他不像面向過程,每一個流程都必須實現(xiàn)。只要是任何可以重復(fù)實現(xiàn)的邏輯都可以封裝形成獨立的類或方法,方便復(fù)用。第三,既然已經(jīng)有復(fù)用了,那維護(hù)性的優(yōu)勢自然就不用再多提了,日常的業(yè)務(wù)需求修改、頁面修改,原本多到數(shù)不勝數(shù)的維護(hù)工作會讓你慶幸自己的腳本最初遵循了面向?qū)ο蟮睦砟?。第四,如果你的代碼封裝完善并且比較健壯且無高耦合,其實用來單獨給開發(fā)做單測也是一個不錯的選擇,當(dāng)然這里更多的還是指接口測試,眾所周知基本很少有開發(fā)會在緊迫的項目時間內(nèi)再給自己的程序設(shè)計一套單測代碼,那么對于測試的同學(xué)來說,在實現(xiàn)現(xiàn)有代碼的同時,是否可以將代碼提供給開發(fā)做單模塊或功能的單測就顯得十分的重要了,這也是測試左移中比較典型的一個例子。
?
2.1 基礎(chǔ)功能封裝
??我們就先從最基本的瀏覽器操作開始,這里會遵循一些簡單的日常業(yè)務(wù)操作進(jìn)行介紹,并且對類內(nèi)的方法進(jìn)行拆解,逐一介紹。文中的代碼會比較簡單,也是方便大家可以更順利的根據(jù)這些功能封裝進(jìn)行二開,無論是健壯性加強或者業(yè)務(wù)判斷都可以自由添加其中。
我們定義的類名為:BrowserDriver,構(gòu)造函數(shù)傳入browser。
class BrowserDriver:
def __init__(self, browser):
self.driver = self.open_browser(browser)
?
2.1.1 開啟瀏覽器
def open_browser(self, browser):
if browser == 'chrome':
options = webdriver.ChromeOptions()
exclude = ['enable-automation']
options.add_experimental_option('excludeSwitches', exclude)
driver = webdriver.Chrome(chrome_options=options)
elif browser == 'firefox':
profile = webdriver.FirefoxProfile()
profile.set_preference('browser.download.dir', 'E:\FireFox_DL\\')
profile.set_preference('browser.download.folderList', 2)
profile.set_preference('browser.helperApps.neverAsk.saveToDisk', 'application/zip')
driver = webdriver.Firefox(firefox_profile=profile)
elif browser == 'ie':
driver = webdriver.Ie()
else:
driver = webdriver.Edge()
return driver
??這里解釋一下,ChromeOptions()這個方法是chrome瀏覽器的參數(shù)對象,用來配置瀏覽器啟動是的一些參數(shù)與屬性,這里添加的是瀏覽器啟動后不顯示“正受到自動測試軟件的控制”的提示,用法比較簡單,add_experimental_option這邊是添加試驗性質(zhì)的參數(shù),另外比較常用的還有add_argument,add_extension(添加啟動項、添加擴展)等方法。
??FirefoxProfile()這個是用來指定火狐瀏覽器內(nèi)用戶設(shè)定檔案,一般可以開啟或關(guān)閉某些瀏覽器內(nèi)的功能來達(dá)到我們的測試業(yè)務(wù)需求,如果你用selenium啟動火狐的話都會默認(rèn)新建一個這樣的檔案,那在代碼中的話你可以指定檔案的保存路徑并在后續(xù)對其指定功能進(jìn)行開啟或關(guān)閉。
?
2.1.2 檢查URL
??封裝的功能比較簡單的,這里檢查URL內(nèi)是否含有http,大家可以根據(jù)自己的需求將判斷邏輯這塊加強,將錯誤之后拋異常的動作實現(xiàn)成自動添加http或https至url的開頭處等都可。
def get_url(self, url):
if self.driver != None:
sleep(1)
if 'http' in url:
self.driver.get(url)
else:
session.add(err_message)
session.commit()
else:
session.add(fail_message)
session.commit()
??如果你的UI測試中不需要將用例的結(jié)果進(jìn)行數(shù)據(jù)持久化,可以替換兩個判斷分支中的業(yè)務(wù)操作,打印到后臺還是寫入文件根據(jù)自己的測試流程要求來自定義即可。這里的數(shù)據(jù)庫操作使用了sqlalchemy模塊,我們定義db創(chuàng)建一個ORM基類,拿博主的腳本舉例,我的ORM模型名(表名)為TestCaseResult,將各個測試場景(URL格式檢查與瀏覽器對象檢查)下的錯誤場景報錯信息寫入。我們的基本信息如下,執(zhí)行后插入一條數(shù)據(jù),包含錯誤代碼,結(jié)果具體信息與一個復(fù)合用例的標(biāo)識判斷。err_message = TestCaseResult(status='100200', result="URL格式有誤", is_composite='False')
可以看出錯誤信息的內(nèi)容還是比較簡單的。我們只需在執(zhí)行過后檢查對應(yīng)的自動化平臺結(jié)果頁面即可看到對應(yīng)報錯信息。
??另外使用sqlalchemy操作數(shù)據(jù)庫前記得創(chuàng)建對應(yīng)的數(shù)據(jù)庫對象。
engine = create_engine("mysql://root:dEDsofe@19admv@172.20.30.241/rtz_fund_trade?charset=uft8",
echo=True,
pool_size=8,
pool_recycle=60*30
)
這里在連接參數(shù)后有三個選項,分別為:
echo: 當(dāng)設(shè)置為True時會將orm語句轉(zhuǎn)化為sql語句打印,一般debug的時候可用
pool_size: 連接池的大小,默認(rèn)為5個,設(shè)置為0時表示連接無限制
pool_recycle: 設(shè)置時間以限制數(shù)據(jù)庫多久沒連接自動斷開
當(dāng)然我們本著易維護(hù)的思想理念,還是將數(shù)據(jù)庫接連的動作進(jìn)行常量設(shè)定。
HOST = '172.20.30.241'
PORT = '3306'
DATABASE = 'rtz_fund_trade'
UNAME = 'root'
PASSWD = 'dEDsofe@19admv'
DB_URL = 'mysql+pymysql://{username}:{pwd}@{host}:{port}/{db}?charset=utf8' \
.format(username=UNAME, pwd=PASSWD, host=HOST, port=PORT, db=DATABASE)
engine = create_engine(DB_URL)
??接下去使用engine = create_engine(DB_URI,echo=True)
進(jìn)行數(shù)據(jù)庫的連接,因為操作數(shù)據(jù)庫必須創(chuàng)建會話來進(jìn)行控制,所以我們還需要使用session = sessionmaker(engine)()
創(chuàng)建一個會話。之后就如最初的代碼中所進(jìn)行的操作來進(jìn)行數(shù)據(jù)庫的數(shù)據(jù)寫入。當(dāng)然以上說的這些操作大家應(yīng)該將其也封裝為一個或者多個類。
?
2.1.3 瀏覽器窗口操作
??窗口操作也是比較常用的基礎(chǔ)功能之一,以下將基本的最大化、最小化、前進(jìn)、后退、刷新、設(shè)定尺寸大小封裝起來。之后會判斷可變參數(shù)的長度,根據(jù)傳入的長度不同進(jìn)行對應(yīng)的窗口操作。
def browser_handle(self, *args):
param = len(args)
if param == 1:
if args[0] == 'max':
self.driver.maximize_window()
elif args[0] == 'min':
self.driver.minimize_window()
elif args[0] == 'forward':
self.driver.forward()
elif args[0] == 'back':
self.driver.back()
else:
self.driver.refresh()
elif param == 2:
self.driver.set_window_size(args[0], args[1])
else:
session.add(fail_message)
session.commit()
sleep(2)
?
2.1.4 切換窗口
??另一個日常較為頻繁的業(yè)務(wù)操作就是切換窗口,也就是我們的標(biāo)簽頁,我們可以使用遍歷的方式獲得一個當(dāng)前所有的窗口列表,通過傳遞默認(rèn)參數(shù)title來進(jìn)行當(dāng)前窗口的切換,直到匹配到與title相同的窗口。
def switch_windows(self, title=None):
windows_list = self.driver.window_handles
current_window = self.driver.current_window_handle
for i in windows_list:
if i != current_window:
time.sleep(1)
self.driver.switch_to.window(i)
if self.assert_title(title):
break
?
2.1.5 獲取頁面元素
??元素定位自然不必多說了,web自動化中的基礎(chǔ)操作,也是日常接觸的最多的功能,封裝的功能只需傳兩個參數(shù),定位方式與元素對應(yīng)的屬性值。這里可以改造的地方還是有很多的,比如不手動指定,通過持久化或者文件指定對應(yīng)要查找的元素,需要定位的元素屬性也可以通過其他方式進(jìn)行抽出,總之二開的話大家可以根據(jù)業(yè)務(wù)需求進(jìn)行靈活多變的定制。另外elements的定位就不演示了,大家舉一反三即可。
def get_element(self, by, ele):
element = None
try:
if by == 'id':
element = self.driver.find_element(By.ID, ele)
elif by == 'name':
element = self.driver.find_element(By.NAME, ele)
elif by == 'css':
element = self.driver.find_element(By.CSS_SELECTOR, ele)
elif by == 'class':
element = self.driver.find_element(By.CLASS_NAME, ele)
else:
element = self.driver.find_element(By.XPATH, ele)
except:
session.add(ele_err_msg)
session.commit()
return element
?
2.1.6 層級元素定位
??層級元素定位的實現(xiàn)邏輯其實就是根據(jù)鏈?zhǔn)綄懛óa(chǎn)生的,原生的find_element()方法是可以從當(dāng)前捕捉到的元素層級開始往下再次定位的,我們就利用這一特性,先使用上一個定位元素的方法get_element()來再一次進(jìn)行find_element()方法,這樣就可以實現(xiàn)層級元素的定位操作。這里有一點需要注意的是,雖然原理如此,但切不可偷懶,調(diào)用兩次get_element(),因為這個方法本身含有driver對象,兩次調(diào)用會使程序無法識別具體使用的是哪個對象,從而導(dǎo)致報錯。
def get_level_element(self, by, ele, ch_by, ch_ele):
element = self.get_element(by, ele)
ch_element = None
try:
if ch_by == 'id':
ch_element = element.find_element(By.ID, ch_ele)
elif ch_by == 'name':
ch_element = element.find_element(By.NAME, ch_ele)
elif ch_by == 'css':
ch_element = element.find_element(By.CSS_SELECTOR, ch_ele)
elif ch_by == 'class':
ch_element = element.find_element(By.CLASS_NAME, ch_ele)
else:
ch_element = element.find_element(By.XPATH, ch_ele)
except:
session.add(ele_err_msg)
session.commit()
return ch_element
?
2.1.7 信息輸入操作
??輸入操作的封裝也是相對比較直白的,定位元素并傳值即可。需要注意的點就是如果定位的元素本身出了問題的話,我們可以利用判斷條件來規(guī)避一些異常的情況。
def send_info(self, by, ele, info):
element = self.get_element(by, ele)
if element is not None:
element.send_keys(info)
else:
session.add(null_key_msg)
session.commit()
?
2.1.8 點擊操作
??邏輯同信息輸入操作,大家自行體會。
def click_element(self, by, ele):
element = self.get_element(by, ele)
if element is not None:
element.click()
else:
session.add(null_key_msg)
session.commit()
?
2.1.9 控件操作
??控件的種類還是比較多的,這里就舉個比較典型的例子。下面封裝的是一個復(fù)選框(勾選框),這里的傳參前兩個就不介紹了,最后一個表示復(fù)選框目前的勾選狀態(tài),我這里定義的0為未勾選,1為已勾選狀態(tài)。這里的實現(xiàn)邏輯大致為:判斷對象是否為勾選狀態(tài),再判斷是否需要勾選,結(jié)合兩種狀態(tài)一般就是有4個結(jié)果,勾選狀態(tài)下勾選和不勾選、未勾選狀態(tài)下勾選和不勾選。大家可以根據(jù)以下的判斷邏輯解讀一下。
def check_box(self, by, ele, selected=None):
element = self.get_element(by, ele)
flag = element.is_selected()
if flag == True:
if selected == 0:
self.click_element(by, ele)
else:
if selected == 1:
self.click_element(by, ele)
?
2.1.10 元素可見性操作
??對于頁面上的某些元素是否可見,我們也可以封裝一個方法,用來增強整體的元素定位方法的健壯性,該方法可以直接在元素定位時進(jìn)行調(diào)用,將原有的返回對象進(jìn)行預(yù)先判斷。
def ele_display(self, ele):
flag = ele.is_displayed()
if flag == True:
return ele
else:
session.add(ele_dis_msg)
session.commit()
??以上就是一些日常工作中使用的比較頻繁的selenium內(nèi)置方法的封裝示例。
?
?
3. 一些題外話
??博主最近也是比較的繁忙,各類測試項目加上宣講了幾場外部企業(yè)培訓(xùn),實在是分身乏術(shù),忙碌之余還在感慨2023年能有一份工作已是萬幸??。平時只能抽空回復(fù)回復(fù)大家的問題和一些簡單互動,文章也是每天抽空寫那么一點點,這里再次向大家說一聲抱歉。后續(xù)有空的話會在原計劃的基礎(chǔ)上加更幾篇關(guān)于selenium的自動化UI測試框架中涉及到的一些進(jìn)階知識點與編碼實例、技巧。如果大家有任何想看的內(nèi)容也可以私信給我,有時間就會穿插的安排上。文章來源地址http://www.zghlxwxcb.cn/news/detail-778534.html
到了這里,關(guān)于web自動化測試入門篇04——selenium+python基礎(chǔ)方法封裝的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!