結合pychrom與selenium實現頁面自動登錄


緣起

一直在瀏覽器里用Katalon插件錄制一些常用的流程,以減少重復操作,也就自然而然想自己搞搞自動化測試,但無奈登錄一關跨不過去,就無法串起來。(不想讓開發添加萬能驗證碼的功能)首先想到的是識別驗證碼。用selenium模擬登錄時,驗證碼一關實在過不了。無論怎么處理驗證碼圖片,tesseract識別率還是太低,完全不可用。看到有機器學習提高驗證碼識別率的例子,但覺得實在太麻煩,就沒有研究,擱置了一段時間。最近發現在登陸時,后台發起了2個請求。先是Get_VerifyCode,獲得verifyCode和backEndKey,然后是Post_Authenticate,並傳入用戶名,md5加密后的密碼,verifyCode和backEndKey。登錄成功后,后台會返回token。登錄后訪問頁面,請求頭中就都有Authorization字段,后台會根據id和authorization發給權限。

我就想如果能先用Get_VerifyCode獲取verifyCode和backEndKey,然后把加上其它信息,對Post_Authenticate發起請求,這樣不就能登錄成功了嗎?先寫好用到的方法,然后再發起用requests發起請求吧。結果是不成功。驗證碼雖然能獲得,但Post_Authenticate總是返回500錯誤。原因是什么呢?第一次是猜測,是不是參數不對,用response.url查看響應的url,怎么參數沒在里面?查詢后知道,要給requests.post()傳參數,得用params(keyword argument),不能用data。修改后再嘗試,還是不行。再查詢,可能是請求頭不對,就用Chrome dev tools查看請求頭。在請求Post_Authenticate時,給加上了幾個。結果不行。再查,有人說一定要把請求頭給加全了,一個都別缺,下面也有人回復有用。難道不能偷懶,不能只在請求頭里加user-agent?補齊請求頭后,還是不行。容我想想。是不是在獲取驗證碼時就得加上,不然拿到的backEndKey也是無效的,試了,不行。觀察下請求頭吧。獲取驗證碼時,有個cookie字段,是不是應該拿到cookie,然后放到2個請求的請求頭中?怎么獲取cookie呢?我發現打開登錄頁面后,發起的請求里就有cookie字段了。我想到的第一個辦法是,用selenium模擬登錄,用get_cookies()方法,試了,可以,但Post_Authenticate還是不通。第二個辦法是,用requests發起請求后,也能在返回值里取到cookie,試了,可以,但最后還是一樣的問題。請求走不通,難道是requests庫的問題,於是又試了下urllib庫。一樣的結果。這么一圈下來還是解決不了,想想能能不能換個思路。能不能在頁面上登錄,但又用Get_VerifyCode獲取驗證碼呢?試了,不行,根本就不是一次請求,獲得了驗證碼也不可用。因為backendky不一致。那能不能selenium登錄瀏覽器后,執行js腳本發起請求,就好像在頁面上點擊了驗證碼,也能更新驗證碼呢?奈何行不通。還是因為backendkey不一致。

在selenium模擬登錄后,頁面上點擊驗證碼圖片,在去監控網絡請求,拿到Get_verifyCode的返回值,這樣也能解決問題。於是就去搜索selenium capture net traffic,翻來揀去,總算是看到相關的。可以讓selenium在啟動后,打開一個dev tools,不過主要能用來觀測瀏覽器性能。又看到chrome devtool protocol,似乎可行,就安裝了python的對它的實現pychrome,想執行例子試試,但有報錯。按最后給出的錯誤信息,有說關閉防火牆,有說把把代理設置改為自動,有說用cmd的命令行執行,有說是pycharm的問題,一一試了試,無一可行。

錯誤信息往前追溯,看到是urllib3報錯,搜索后還是沒解決。再往前看是socket問題,還是不行。
讓我想想,之前有人遇到的類似問題是,服務端沒有開啟監聽,所以客戶端訪問時,就會提示目標計算機拒絕了請求。
由於pychrome庫,實例化Browser對象后,監聽的是9222端口,會不會是9222端口的服務端問題呢,也就是瀏覽器設置的問題呢?這會用baidu吧,搜索“9222端口”,第一個就是
設置Chrome遠程調試接口,是給前端打斷點調試用的。試試看吧,總算可行,pychrome庫能用了。

又了解了下chrome dev protocol,嘗試了pychrome里的各種方法,試圖捕捉到Get_verifyCode請求的返回值
試了多個方法,看了github倉庫中提供的例子,總算弄好了
接下來的問題就變成了,怎么把selenium和pychrome結和起來,這樣既能讓selenium來做自動化操作,
又能使用pychrome監控請求。分開使用時,兩個庫都是會生成各自的瀏覽器實例,互不相干,

莫名其妙,執行寫好的代碼就不能用了,會提示chrome unreachable。

以為是chrome自動升級了,導致webriver不匹配,下載了新的webdriver,不行。
更新chrome時發現,原來自己早把chrome自動更新給停了,就更新到了75,又換了chromedriver,不行。
再查,有人說是hosts的問題,沒有填入127.0.0.1和localhost的映射,打開hosts查看,已經有了,不是這個問題。
再查,有人說是代碼中的webdriver配置問題,改了后不行。中間還遇到常用的js代理走不通,谷歌訪問助手不能用的情況,又調了下這個問題。
再查,有人說防火牆問題,仍然不行。
停下倆想想吧,是不是瀏覽器的問題呢?以remote-debugging模式打開chrome,訪問不到9222端口。用telnet local 9222命令,也是不通。
可能是瀏覽器問題吧。實在是查不到相關信息了,只能想到瀏覽器重裝大法。查詢中,記得看到過chrome canary的信息,就查了下,原來是類似nightly的版本,
就裝了下,是77版。以遠程調試模式,打開chrome canary后,發現可以訪問到9222端口。再打開chrome stable版,發現居然也可以了。
試着卸載了canary版本,又不行了,只得再裝回來。可是網絡突然又不好了,無論如何都下載不成功。第二天到了公司,重裝了下,總算原來的代碼可以用了
我真的很困惑,問題不知道為什么就發生了,也不知道為什么就解決了。
卡在某處,就想一次性解決,不拖到下次。可是總會遇到各種各樣的問題。要解決一個問題,查詢后發現得先解決另一個問題,預置了前提,而前提又未必一定導向
原本的問題,於是就有了兩個問題。以此類推,問題像鎖鏈一樣,一環套一環。卸掉一環,未必就能離原本的問題近一點,但卻只能如此。

 

代碼

  1 from selenium import webdriver
  2 import pychrome
  3 import json
  4 import subprocess
  5 import time
  6 from data import *
  7 from commons import EventHandler, Task
  8 import os
  9 from action import Action
 10 from action import *
 11 
 12 
 13 class ChromeClient(object):
 14     options = webdriver.ChromeOptions()
 15     options._debugger_address = "localhost:9222"
 16     # options.add_argument('--remote-debugging-port=9222')
 17 
 18     def __init__(self):
 19         # 原來用os.popen時不時就報錯,打開的chrome一直無響應,改為用subprocess后就沒報錯了
 20        21         # os.popen(CHROME_CMD)
 22 
 23         subprocess.Popen(CHROME_CMD)
 24         self.browser = pychrome.Browser()
 25         self.driver = webdriver.Chrome(executable_path=r'C:\Python36\chromedriver.exe', chrome_options=ChromeClient.options)
 26         self.driver.implicitly_wait(30)
 27         self.tab = None
 28         self.event = None
 29         self.token = None
 30         
 31         # self.driver.get('about:blank')
 32 
 33     def monitor(self):
 34         self.tab = self.browser.list_tab()[0]
 35         self.tab.start()
 36         self.tab.Page.enable()
 37         self.tab.Network.enable()
 38         # self.tab.Page.enable()
 39         self.event = EventHandler()
 40         self.tab.Network.requestWillBeSent = self.event.on_request_will_be_sent
 41         self.tab.Network.responseReceived =  self.event.on_response_received
 42         print('----Requests are being monitored....')
 43         return True
 44 
 45     def naivigate(self, url):
 46         self.tab.Page.navigate(url=url)
 47         time.sleep(2.5)
 48         print('----Navigate to ',  url)
 49 
 50     def get_code_from_request(self, requestId):
 51         try:
 52             _response = self.tab.Network.getResponseBody(requestId=requestId)
 53             _response_content = _response.get('body')
 54             _response_dict = json.loads(json.loads(_response_content))
 55             _verifyCode_str = _response_dict.get('data').get('verifyCode')
 56             print("----Verifycode ", _verifyCode_str)
 57             return _verifyCode_str
 58         except Exception as e:
 59             return False
 60 
 61     def get_token_from_request(self, requestId):
 62         time.sleep(2)
 63         _response = self.tab.Network.getResponseBody(requestId=requestId)
 64         print(_response)
 65         _response_content = _response.get('body')
 66         _response_dict = json.loads(json.loads(_response_content))
 67         _token = _response_dict.get('data').get('tokenID')
 68         self.token = ''.join(['BasicAuth ', _token])
 69         print("----Token ", self.token)
 70         return self.token
 71 
 72     def login(self, username, password, verifycode):
 73         _driver = self.driver
 74         _username_input = _driver.find_element_by_name("username")
 75         _password_input = _driver.find_element_by_name("password")
 76         _verifycode_input = _driver.find_element_by_name("verificationCode")
 77 
 78         Task.delete_text(_username_input)
 79         _username_input.send_keys(username)
 80         Task.delete_text(_password_input)
 81         _password_input.send_keys(password)
 82         _verifycode_input.send_keys(verifycode)
 83 
 84         for _element in _driver.find_elements_by_css_selector('span'):
 85             if "登錄" in _element.text:
 86                 _driver.execute_script("arguments[0].click();", _element)
 87         # self.tab.wait(2.5)
 88         # _driver.execute_script('alert(1);')
 89         # _driver.execute_script("arguments[0].click();", login_span)
 90 
 91 
 92 if __name__ == '__main__':
 93     host = 'ppm-test'
 94     role = 'la'
 95     no = 0
 96 
 97     client = ChromeClient()
 98     client.monitor()
 99     client.naivigate(url=LOGIN_URLS[host])
100     verifycode = client.get_code_from_request(client.event.request_id)
101     client.login(username=USERS[role][no][0], password=USERS[role][no][1], verifycode=verifycode)
102     token = client.get_token_from_request(client.event.request_id)

 

感想

即便是花了很多時間把登錄搞好了,之后各類流程卻又是個問題。各個業務用到的接口數據,復雜繁多,而且有諸多限制。況且錄制的代碼,常常無法直接變成selenium代碼,嵌在項目里,往往還有經過多輪調試,還不一定能用。UI自動化測試收益果然不高。瞎折騰實在是費心費力。

 

參考文章

https://div.io/topic/1464

https://testerhome.com/topics/16526

https://testerhome.com/topics/16222

https://testerhome.com/topics/15817

https://blog.csdn.net/crisschan/article/details/79970813


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM