Selenium 自動(dòng)化測(cè)試工具介紹
今天我們來(lái)介紹下 Selenium 自動(dòng)化測(cè)試工具并借助該工具完成一個(gè)簡(jiǎn)單的案例,這個(gè)案例和筆者的工作相關(guān),也算挺有意思的。
1. Selenuim 介紹與使用
Selenium 是一個(gè)用于 Web 應(yīng)用程序自動(dòng)化測(cè)試工具,提供一系列的測(cè)試函數(shù)。這些函數(shù)非常靈活,能夠完成界面元素定位、窗口跳轉(zhuǎn)、結(jié)果比較。Selenium 測(cè)試直接運(yùn)行在瀏覽器中,就像真正的用戶在操作一樣。該自動(dòng)化測(cè)試工具具有如下幾個(gè)鮮明的特點(diǎn):
- 支持多種瀏覽器:IE、Firefox、Safari、Chrome 等主流瀏覽器都是支持的;
- 支持多種變成語(yǔ)言:支持主流的編程語(yǔ)言,如 Java、Python、Ruby、PHP 等;
- 支持多種操作系統(tǒng):如 Windows、Linux、IOS 以及 Android 等;
- 開源免費(fèi):selenuim 在 github 上進(jìn)行了開源,star 和 fork 數(shù)都彰顯了該項(xiàng)目的流行度。Selenium框架由多個(gè)工具組成,包括:Selenium IDE,Selenium RC,Selenium WebDriver和SeleniumRC。
我們直接來(lái)在使用 Python 操作 Selenuim:
安裝 selenium 模塊:pip install selenium -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
;
接下來(lái),我們要使用 Google 瀏覽器完成 selenium 的自動(dòng)化操作,因此需要安裝相應(yīng)的 webdriver。對(duì)應(yīng) Google 瀏覽器的 webdriver 的下載地址為:下載。找到對(duì)應(yīng)的 Chrome driver 版本,下載即可:
下載了 webdriver 包后解壓得到 chromedriver.exe
,將其放到和 Chrome 瀏覽器相同的路徑下:
接下來(lái)將上面的這個(gè)路徑添加到系統(tǒng)變量 Path 中去,確保能找到 chromedriver.exe 這個(gè)文件:
?
接下來(lái),我們完成一段簡(jiǎn)單的 Selenium 自動(dòng)化測(cè)試代碼,幫助我們更好的理解 Selenium 這個(gè)工具:
"""
selenium工具測(cè)試
"""
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
# 請(qǐng)求百度頁(yè)面
driver.get("https://www.baidu.com")
# 找到輸入框位置
input = driver.find_element_by_id("kw")
# 輸入"慕課網(wǎng) wiki寶典"
input.send_keys("慕課網(wǎng) wiki寶典")
# 找到<百度一下>按鈕
button = driver.find_element_by_id("su")
# 點(diǎn)擊
button.click()
# 直到出現(xiàn)分頁(yè)元素
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "page"))
)
# 找到搜索內(nèi)容的第一條結(jié)果
wiki_page = driver.find_element_by_xpath('//div[@id="content_left"]/div[1]/h3/a')
# 點(diǎn)擊跳轉(zhuǎn)到慕課網(wǎng)的wiki頁(yè)面
wiki_page.click()
上面的代碼比較簡(jiǎn)單,我們只需要了解下 selenium 工具提供的基本函數(shù)就能夠理解,代碼中已經(jīng)做好了相關(guān)注釋,我們直接來(lái)運(yùn)行看看效果如何:
是不是有點(diǎn)意思?這段代碼幫我們自動(dòng)啟動(dòng) Chrome 瀏覽器,跳轉(zhuǎn)到百度頁(yè)面,然后再輸入框中輸入 “慕課網(wǎng) wiki”,然后點(diǎn)擊百度一下。在搜索的頁(yè)面中選擇第一個(gè)點(diǎn)擊,進(jìn)入到慕課網(wǎng)的 wiki 頁(yè)面中。這整個(gè)過(guò)程就是由代碼控制完成,沒(méi)有人為操作,這是不是就是我們想象的自動(dòng)化測(cè)試呢?自信點(diǎn),就是這樣的!
2. 基于 Selenium 完成發(fā)票認(rèn)證
接下來(lái),我們基于 Scrapy 和 Selenium 來(lái)完成筆者工作中的一個(gè)需求。我們每個(gè)月有一筆通信發(fā)票的報(bào)銷,需要使用對(duì)自己在營(yíng)業(yè)廳中買的發(fā)票進(jìn)行校驗(yàn),然后要截圖留存。報(bào)銷的時(shí)間有時(shí)候是3個(gè)月一次,有時(shí)候是半年,所以累積下來(lái)有不少發(fā)票,這些發(fā)票都需要校驗(yàn)和截圖才能報(bào)銷。
我們應(yīng)屆生剛工作的時(shí)候,我們大部分新員工都是手工截圖,非常笨拙,且耗時(shí)。由于是搞技術(shù)的公司,于是有前端人員寫了相關(guān)的前端插件來(lái)自動(dòng)化截圖和生成發(fā)票校驗(yàn)文檔,然后在公司內(nèi)廣泛應(yīng)用。我也寫了這樣一段基于 Selenium 的自動(dòng)化截圖代碼,不過(guò)代碼依賴 chrome 和 webdriver,所以組內(nèi)的部分人會(huì)把發(fā)票的起始編號(hào)和張數(shù)發(fā)給我,我運(yùn)行程序截好圖后將圖片打包發(fā)給他們自己放到 word 文檔中去。
來(lái)看看發(fā)票校驗(yàn)的網(wǎng)站的截圖如下:
驗(yàn)證的方式非常機(jī)械,正是因?yàn)闄C(jī)械操作,才給了我們自動(dòng)化的可能、看上圖中的四個(gè)輸入框,表示的含義分別為:
- 發(fā)票代碼:固定值;
- 發(fā)票號(hào)碼:通常而言,從移動(dòng)營(yíng)業(yè)廳拿的通信發(fā)票是連續(xù)的,這樣就可以用 for 循環(huán)實(shí)現(xiàn);
- 納稅人識(shí)別號(hào):這個(gè)是個(gè)固定值;
- 發(fā)票面額:固定50元。假設(shè)我買300元的卡,就會(huì)對(duì)應(yīng)6張發(fā)票;
我們填好每張發(fā)票的相應(yīng)信息,只有一個(gè)變量。在查詢之前,需要先拖動(dòng)滑塊進(jìn)行驗(yàn)證,驗(yàn)證通過(guò),再點(diǎn)擊查詢就可以進(jìn)行認(rèn)證。得到了認(rèn)證結(jié)果后,截張圖,如此進(jìn)行下去,直到所有的發(fā)票都校驗(yàn)截圖完畢。令人欣慰的是這里的滑塊驗(yàn)證只需要做一次,得到了相應(yīng)的認(rèn)證截圖后,每次調(diào)整下一張發(fā)票的發(fā)票號(hào)碼,在截一張圖即可。看下面的做法:
因此,我們得到了這樣的機(jī)械化動(dòng)作:
- 打開發(fā)票校驗(yàn)頁(yè)面;
- 輸入第一張發(fā)票的四個(gè)參數(shù),然后拖動(dòng)滑塊到最右端完成校驗(yàn);
- 截圖留存;
- 改動(dòng)第二個(gè)輸入框的發(fā)票號(hào)碼,為下一個(gè)發(fā)票編號(hào),然后直接截圖;重復(fù),直到最后一張發(fā)票截圖成功;
對(duì)應(yīng)這樣的動(dòng)作我們翻譯成相應(yīng)的 Selenium 自動(dòng)化代碼如下:
"""
測(cè)試 selenium 工具
"""
import time
import random
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
# 固定值
ticket_code = "144011690802"
identification_number = "91440101618652334F"
face_value = 50
# 起始編號(hào)
ticket_start_num = 15415104
# 發(fā)票總數(shù)
total_count = 10
def click_space(driver):
"""
點(diǎn)擊下空白處,使得輸入框失去焦點(diǎn)
"""
driver.find_elements_by_xpath('//div[@class="check-main"]/table/tbody/tr[1]/td[1]')[0].click()
def fill_input(driver, idx):
"""
填充輸入框
"""
input_value = [ticket_code, idx, identification_number, face_value]
table = driver.find_elements_by_xpath('//div[@class="check-main"]/table/tbody')[0]
for i in range(1, len(input_value) + 1):
input = table.find_elements_by_xpath(f'./tr[{i}]/td[2]/input')[0]
input.clear()
input.send_keys(input_value[i - 1])
click_space(driver)
def get_track(distance):
"""
參考文獻(xiàn)2代碼,模擬人移動(dòng)鼠標(biāo)
:distance為傳入的總距離
"""
# 移動(dòng)軌跡
track = []
# 當(dāng)前位移
current = 0
# 減速閾值
mid = distance * 3 / 5
# 計(jì)算間隔
t = 0.4
# 初速度
v = 1
while current < distance:
if current < mid:
# 加速度為一個(gè)隨機(jī)值
a = random.randint(2, 6)
else:
# 加速度為一個(gè)隨機(jī)負(fù)值
a = -1 * random.randint(1, 2)
v0 = v
# 當(dāng)前速度
v = v0 + a * t
# 移動(dòng)距離
move = v0 * t + 0.5 * a * t * t
# 當(dāng)前位移
current += move
# 加入軌跡
track.append(round(move))
return track
def move_to_gap(slider, tracks):
"""
參考文獻(xiàn)2代碼
:slider是要移動(dòng)的滑塊,tracks是要傳入的移動(dòng)軌跡
"""
action = ActionChains(driver)
action.click_and_hold(slider).perform()
for x in tracks:
ActionChains(driver).move_by_offset(xoffset=x,yoffset=0).perform()
time.sleep(0.1)
ActionChains(driver).release().perform()
# 發(fā)票校驗(yàn)地址
verify_address = "https://gs.etax-gd.gov.cn/gsyw/service/fpyw/fpcy/index"
# 想屏蔽selenium標(biāo)識(shí),避免被服務(wù)端檢測(cè)到,似乎沒(méi)起作用
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ['enable-automation'])
driver = webdriver.Chrome(options=options, executable_path="C:/Users/spyinx/AppData/Local/Google/Chrome/Application/chromedriver.exe")
driver.maximize_window()
# 第一步先去發(fā)票查詢頁(yè)面
driver.get(verify_address)
# 等待滑塊出現(xiàn)
WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, 'nc_1_n1z'))
)
# 填充輸入框
fill_input(driver, ticket_start_num)
# 等待幾秒后
driver.implicitly_wait(5)
# 找到滑動(dòng)按鈕,模擬鼠標(biāo)按下不松開
slider = driver.find_element_by_id('nc_1_n1z')
move_to_gap(slider, get_track(300))
driver.implicitly_wait(2)
driver.find_element_by_id("CxBtn").click()
# 查詢第一個(gè)需要滑動(dòng)滑塊
driver.get_screenshot_as_file(f"{ticket_start_num}.png")
for i in range(1, total_count):
ticket_num = ticket_start_num + i
fill_input(driver, ticket_num)
driver.get_screenshot_as_file(f"{ticket_num}.png")
driver.implicitly_wait(1)
上面的代碼已經(jīng)做好了詳細(xì)的注釋,請(qǐng)查找相關(guān)資料了解 Selenium 提供的相關(guān)方法,比如定位頁(yè)面元素的方法、對(duì)頁(yè)面元素點(diǎn)擊(click)、輸入框情況 (clear) 以及輸入元素(send_keys) 以及鼠標(biāo)的相關(guān)操作方法。下面我們直接來(lái)看代碼的演示效果,最后的截圖也全部得到。
注意:由于這個(gè)滑動(dòng)驗(yàn)證碼是接入的阿里滑動(dòng)驗(yàn)證碼插件,具備超強(qiáng)的反爬蟲能力,能識(shí)別出是瀏覽器否被 selenium 控制。本次測(cè)試重復(fù)了無(wú)數(shù)次,才終于有一次沒(méi)有出現(xiàn)滑塊校驗(yàn)錯(cuò)誤,才成功錄下此視頻。具體的可以搜索大神如何破解阿里的滑動(dòng)驗(yàn)證碼,不過(guò)大部分代碼已經(jīng)過(guò)時(shí),無(wú)法突破驗(yàn)證。
總而言之,上面的操作是不是一定程度上能幫助我們減少手工操作,節(jié)約了時(shí)間成本?如果大家感興趣的話,可以嘗試使用 Selenium 完成京東商城的自動(dòng)登錄操作,這里會(huì)有滑動(dòng)圖片缺口補(bǔ)全的校驗(yàn),會(huì)稍微有點(diǎn)復(fù)雜,對(duì)你們來(lái)說(shuō)也是一個(gè)不錯(cuò)的挑戰(zhàn)。
3. 小結(jié)
本小節(jié)中我們介紹了自動(dòng)化工具 Selenuim 的使用, 同時(shí)結(jié)合 Scrapy 框架了完成了一個(gè)簡(jiǎn)單的案例。這個(gè)案例來(lái)源于筆者公司的一個(gè)小需求,學(xué)習(xí)好技術(shù)有時(shí)能給我們工作和生活帶來(lái)許多意想不到的驚喜,這也是我們能不斷追求技術(shù)的一個(gè)動(dòng)力。