zoukankan      html  css  js  c++  java
  • 结合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:Python36chromedriver.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

  • 相关阅读:
    《Effective Java》 读书笔记(三) 使用私有构造方法或枚举实现单例类
    《Effective Java》 读书笔记(二) 在构造参数过多的时候优先考虑使用构造器
    读书笔记-《Maven实战》-2018/5/3
    读书笔记-《Maven实战》-关于Maven依赖传递的思考 2018/4/26
    MySQL基础篇(07):用户和权限管理,日志体系简介
    SpringCloud微服务:Sentinel哨兵组件,管理服务限流和降级
    MySQL基础篇(06):事务管理,锁机制案例详解
    Java并发编程(02):线程核心机制,基础概念扩展
    SpringBoot2 整合ElasticJob框架,定制化管理流程
    Java基础篇(02):特殊的String类,和相关扩展API
  • 原文地址:https://www.cnblogs.com/yifeixu/p/11342864.html
Copyright © 2011-2022 走看看