结合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

posted @ 2019-08-12 21:50  F君君  阅读(1538)  评论(0编辑  收藏  举报