Python Selenium源码阅读以及基础知识的补充

Posted 测试那些事

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python Selenium源码阅读以及基础知识的补充相关的知识,希望对你有一定的参考价值。

最近,小编阅读selenium源码以及pytest的源码。在阅读过程中,重在理解selenium和pytest的工作原理,同时对薄弱部分的基础知识进行补充,在此进行记录。

python 基础知识

  • python 中的冒号和箭头

GET: str="get"defSum(num1: int, num2: int=200):

这时的冒号代表类型建议符,更多的是软件工程中使用方便第一次使用这个方法的人快速知道这里的参数应该传递什么类型。但是这里不会做强制的类型检查,即使你传递的和建议的不符合也不会报错。

defSum(num1: int, num2: int=200) ->int:

这里的箭头是对返回值的类型建议符

  • 箭头出现在函数后面,如下

  • 冒号比方函数参数中或者就是对变量定义赋值的时候出现,如下

  • isinstance(obj: object,class_or_tuple: _ClassInfo) 方法

  • 判断obj 是不是_class_or_tuple 类型,返回一个布尔类型

  • string.Template()/substitute()

template_str ='$this is $what's = Template(template_str)d = 'this': 'apple', 'what': 'red's.substitute(d)'apple is red'
  • 其中可以看到 template 用来根据给出的字符创造模板,substitute 负责将模板填充上实际的内容

  • string.Template() 用来定义一个字符串的模板,其中变量默认用 $ 符号标记,比方如下

  • 使用 f 修饰字符串

  • 最简单的用法就是源码中的用法 fstring1string2,这种打印出来就是 string1string2

  • f 的用法实际上的用法是 f字符串/变量:格式,比如 f3.1415926:2f,那么打印出来的就是 3.14

  • hasattr(): 用来判断 object 是否含有某个属性

classCoordinate:    
   x =10    
   y =-5   
   z =0point1 = Coordinate() print(hasattr(point1, 'x')) # Trueprint(hasattr(point1, 'y')) # Trueprint(hasattr(point1, 'z')) # Trueprint(hasattr(point1, 'no')) # False

Selenium 源码阅读/使用

  • 第一个需要考虑的问题就是 chromedriver.exe 的问题(其他浏览器也一样),我们需要选择和浏览器版本对应的 webdriver 否则是无法正常执行测试的。但是有一个现实的问题就是浏览器始终在升级,除非有特定的要求就是要在某个版本的浏览器进行测试,否则不建议一个个手动下载 webdriver.exe。这里可以选择第三方 library webdriver-manager,他会自动下载对应版本的 webdriver.exe

fromseleniumimport webdriverfromselenium.webdriver.chrome.serviceimport Servicefromwebdriver_manager.chromeimport ChromeDriverManagerservice = Service(executable_path=ChromeDriverManager().install())driver = webdriver.Chrome(service=service)
  • 我们运行这段代码就会发现会启动一个新的浏览器窗口,说明在上面这段代码中会将所有的初始化都做好,并且将我们需要的 session 建立好,主要得工作在下面这段代码中实现

driver = webdriver.Chrome(service=service)
  • 第一个关键点在于其继承类的初始化函数中的如下代码:

self.service.start()

跟踪这段代码,发现它执行了如下代码:

cmd = [self.path]        
    cmd.extend(self.command_line_args())        
    self.process = subprocess.Popen(cmd, env=self.env,                                               close_fds=system() !='Windows',                                        
        stdout=self.log_file,                                        
        stderr=self.log_file,                                        
        stdin=PIPE,                                        
        creationflags=self.creationflags)

上面这段代码开启了一个进程,这个进程启动了 chromewebdriver.exe 文件,说明他在后台启动了 webdriver 的进程

  • Chrome() 这个方法位于 webdriver.py 文件,经过一系列的变量初始化和类的初始化函数调用(这其中其实也会有一些比较关键的点,比方设置 localhost 的端口号等等,暂时不展开),最终会进入 remote/webdriver.py 中的init() 函数中,在如下代码中进行 session 的建立

self.start_session(capabilities, browser_profile)

这一行会启动一个 session 用来执行接下来的测试,其中的 session_id 是贯穿始终的一个值,用来标注是是与这个 client 进行交互

  • 接下来我们进入到 start_session() 中看看发生了什么

  • 在 start_session() 中,最关键的是下面这一行,字面意思看他通过执行某些命令,启动了一个新的 session,那我们接下来看看,执行了什么命令,又是如何启动 session 的

response =self.execute(Command.NEW_SESSION, parameters)
  • 进入这个 execute() 方法,这里面最关键的一行的代码是下面这一行,而这一行代码基本上也就是贯穿了 selenium 基本上所有的方法

response =self.command_executor.execute(driver_command, params)
  • execute() 方法中,先是对 driver_command 和 params 参数进行了一些处理,最终以下面这段代码作为响应返回值返回

returnself._request(command_info[0], url, body=data)

这段代码中,command_info[0] 定义了 method(POST,GET...), url 为 RemotewebDriver 的 url,body 是需要传输给 webdriver 的数据,内容在上面被格式化了成 json 格式。所以通俗来讲就是,通过_request() 方法,将操作 (data) 发送给 webdriver 去执行并获取返回值。如果不继续看底层代码的话,这里就结束了 selenium 的工作原理。

  • 在_request() 方法中,首先处理了一下 url 和 header 信息,然后对 KeepAlive 参数进行了判断,由于大部分时间这个参数都是 True,所以我们也跟着 True 的情况去继续看

response =self._conn.request(method, url, body=body, headers=headers)
  • 继续上面可以看到调用了一个 request() 方法,我们用跟进的方式跟进去是一个 pyi 的文件,只是提供了一些没有实现的接口,这个时候需要使用 F11 去跟进,发现 request 方法是在 urllib3/request.py 下的方法,这个方法其实也是一个过度方法,对参数变量进行了一些大小写之类的处理,然后调入相同文件的 request_encode_body() 方法

  • request_encode_body() 方法最关键的是调用 urlopen() 方法,这个 urlopen() 方法的实现处于 urllib3/poolmanager.py 中,这个 urlopen() 的方法是一个回调函数,他一直在确定是不是重定向的最后一层,如果不是就继续调用本方法去寻找,如果是的话,调用类似下列函数来获取响应

response = conn.urlopen()
  • urlopen() 最终调用的是 urllib3/connectionpool.py 中的方法,这个方法是最底层的调用,所以细化了一个 httprequest 所需的所有细节的内容。这个方法中最终发送 request 的方法是如下代码:

httplib_response =self._make_request(                
        conn,                
        method,                
        url,               
        timeout=timeout_obj,                
        body=body,                
        headers=headers,                
        chunked=chunked,            
    )
  • 继续跟进,在_make_request() 中调用了 urllib3/connection.py 中的 request() 方法

  • request() 方法调用了 http/client.py 中的 request()->_send_request() 方法

  • 在 client.py 中经过一系列的 class 内部函数调用,最终将整个待发送的 data 整合好,在 send() 方法中依靠下列代码和 server 建立连接,同时发送:

self.sock.sendall(data)
  • 至此结束,其实也没有结束。这个 sendall 最终走入了 ssl.py 的 write 方法中,奈何我无法继续跟进。如果有大佬看到麻烦指正,很想知道底层到底是如何将一个 request 发送出去。

其实,这里只看了一个建立 session 的过程,但是大部分的操作(比方说打开浏览器,get(url), find_element())都是使用的相同的方式,只不过是使用了维护的字典中不一样的信息,如下:

self._commands = 
            Command.NEW_SESSION: ('POST', '/session'),
            Command.QUIT: ('DELETE', '/session/$sessionId'),
            Command.W3C_GET_CURRENT_WINDOW_HANDLE:
                ('GET', '/session/$sessionId/window'),
            Command.W3C_GET_WINDOW_HANDLES:
                ('GET', '/session/$sessionId/window/handles'),
            Command.GET: ('POST', '/session/$sessionId/url'),
            Command.GO_FORWARD: ('POST', '/session/$sessionId/forward'),
            Command.GO_BACK: ('POST', '/session/$sessionId/back'),
            Command.REFRESH: ('POST', '/session/$sessionId/refresh'),
            Command.W3C_EXECUTE_SCRIPT:
                ('POST', '/session/$sessionId/execute/sync'),
            Command.W3C_EXECUTE_SCRIPT_ASYNC:
                ('POST', '/session/$sessionId/execute/async'),
            Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'),
            Command.GET_TITLE: ('GET', '/session/$sessionId/title'),
            Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'),
            Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'),
            Command.ELEMENT_SCREENSHOT: ('GET', '/session/$sessionId/element/$id/screenshot'),
            Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'),
            Command.FIND_ELEMENTS: ('POST', '/session/$sessionId/elements'),
            Command.W3C_GET_ACTIVE_ELEMENT: ('GET', '/session/$sessionId/element/active'),
            Command.FIND_CHILD_ELEMENT:
                ('POST', '/session/$sessionId/element/$id/element'),
            Command.FIND_CHILD_ELEMENTS:
                ('POST', '/session/$sessionId/element/$id/elements'),
            Command.CLICK_ELEMENT: ('POST', '/session/$sessionId/element/$id/click'),
            Command.CLEAR_ELEMENT: ('POST', '/session/$sessionId/element/$id/clear'),
            Command.GET_ELEMENT_TEXT: ('GET', '/session/$sessionId/element/$id/text'),
            Command.SEND_KEYS_TO_ELEMENT:
                ('POST', '/session/$sessionId/element/$id/value'),
            Command.UPLOAD_FILE: ('POST', "/session/$sessionId/se/file"),
            Command.GET_ELEMENT_TAG_NAME:
                ('GET', '/session/$sessionId/element/$id/name'),
            Command.IS_ELEMENT_SELECTED:
                ('GET', '/session/$sessionId/element/$id/selected'),
            Command.IS_ELEMENT_ENABLED:
                ('GET', '/session/$sessionId/element/$id/enabled'),
            Command.GET_ELEMENT_RECT:
                ('GET', '/session/$sessionId/element/$id/rect'),
            Command.GET_ELEMENT_ATTRIBUTE:
                ('GET', '/session/$sessionId/element/$id/attribute/$name'),
            Command.GET_ELEMENT_PROPERTY:
                ('GET', '/session/$sessionId/element/$id/property/$name'),
            Command.GET_ELEMENT_ARIA_ROLE:
                ('GET', '/session/$sessionId/element/$id/computedrole'),
            Command.GET_ELEMENT_ARIA_LABEL:
                ('GET', '/session/$sessionId/element/$id/computedlabel'),
            Command.GET_SHADOW_ROOT:
                ('GET', '/session/$sessionId/element/$id/shadow'),
            Command.FIND_ELEMENT_FROM_SHADOW_ROOT:
                ('POST', '/session/$sessionId/shadow/$shadowId/element'),
            Command.FIND_ELEMENTS_FROM_SHADOW_ROOT:
                ('POST', '/session/$sessionId/shadow/$shadowId/elements'),
            Command.GET_ALL_COOKIES: ('GET', '/session/$sessionId/cookie'),
            Command.ADD_COOKIE: ('POST', '/session/$sessionId/cookie'),
            Command.GET_COOKIE: ('GET', '/session/$sessionId/cookie/$name'),
            Command.DELETE_ALL_COOKIES:
                ('DELETE', '/session/$sessionId/cookie'),
            Command.DELETE_COOKIE:
                ('DELETE', '/session/$sessionId/cookie/$name'),
            Command.SWITCH_TO_FRAME: ('POST', '/session/$sessionId/frame'),
            Command.SWITCH_TO_PARENT_FRAME: ('POST', '/session/$sessionId/frame/parent'),
            Command.SWITCH_TO_WINDOW: ('POST', '/session/$sessionId/window'),
            Command.NEW_WINDOW: ('POST', '/session/$sessionId/window/new'),
            Command.CLOSE: ('DELETE', '/session/$sessionId/window'),
            Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY:
                ('GET', '/session/$sessionId/element/$id/css/$propertyName'),
            Command.EXECUTE_ASYNC_SCRIPT: ('POST', '/session/$sessionId/execute_async'),
            Command.SET_TIMEOUTS:
                ('POST', '/session/$sessionId/timeouts'),
            Command.GET_TIMEOUTS:
                ('GET', '/session/$sessionId/timeouts'),
            Command.W3C_DISMISS_ALERT:
                ('POST', '/session/$sessionId/alert/dismiss'),
            Command.W3C_ACCEPT_ALERT:
                ('POST', '/session/$sessionId/alert/accept'),
            Command.W3C_SET_ALERT_VALUE:
                ('POST', '/session/$sessionId/alert/text'),
            Command.W3C_GET_ALERT_TEXT:
                ('GET', '/session/$sessionId/alert/text'),
            Command.W3C_ACTIONS:
                ('POST', '/session/$sessionId/actions'),
            Command.W3C_CLEAR_ACTIONS:
                ('DELETE', '/session/$sessionId/actions'),
            Command.SET_WINDOW_RECT:
                ('POST', '/session/$sessionId/window/rect'),
            Command.GET_WINDOW_RECT:
                ('GET', '/session/$sessionId/window/rect'),
            Command.W3C_MAXIMIZE_WINDOW:
                ('POST', '/session/$sessionId/window/maximize'),
            Command.SET_SCREEN_ORIENTATION:
                ('POST', '/session/$sessionId/orientation'),
            Command.GET_SCREEN_ORIENTATION:
                ('GET', '/session/$sessionId/orientation'),
            Command.GET_NETWORK_CONNECTION:
                ('GET', '/session/$sessionId/network_connection'),
            Command.SET_NETWORK_CONNECTION:
                ('POST', '/session/$sessionId/network_connection'),
            Command.GET_LOG:
                ('POST', '/session/$sessionId/se/log'),
            Command.GET_AVAILABLE_LOG_TYPES:
                ('GET', '/session/$sessionId/se/log/types'),
            Command.CURRENT_CONTEXT_HANDLE:
                ('GET', '/session/$sessionId/context'),
            Command.CONTEXT_HANDLES:
                ('GET', '/session/$sessionId/contexts'),
            Command.SWITCH_TO_CONTEXT:
                ('POST', '/session/$sessionId/context'),
            Command.FULLSCREEN_WINDOW:
                ('POST', '/session/$sessionId/window/fullscreen'),
            Command.MINIMIZE_WINDOW:
                ('POST', '/session/$sessionId/window/minimize'),
            Command.PRINT_PAGE:
                ('POST', '/session/$sessionId/print'),
            Command.ADD_VIRTUAL_AUTHENTICATOR:
                ('POST', '/session/$sessionId/webauthn/authenticator'),
            Command.REMOVE_VIRTUAL_AUTHENTICATOR:
                ('DELETE', '/session/$sessionId/webauthn/authenticator/$authenticatorId'),
            Command.ADD_CREDENTIAL:
                ('POST', '/session/$sessionId/webauthn/authenticator/$authenticatorId/credential'),
            Command.GET_CREDENTIALS:
                ('GET', '/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials'),
            Command.REMOVE_CREDENTIAL:
                ('DELETE', '/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials/$credentialId'),
            Command.REMOVE_ALL_CREDENTIALS:
                ('DELETE', '/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials'),
            Command.SET_USER_VERIFIED:
                ('POST', '/session/$sessionId/webauthn/authenticator/$authenticatorId/uv'),
        

接下来还会继续去看源码,找一找有没有其他的用得是不一样的方式来触发一条请求的发送。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

以上是关于Python Selenium源码阅读以及基础知识的补充的主要内容,如果未能解决你的问题,请参考以下文章

python爬虫--selenium的理解以及使用

零基础掌握Selenium Webdriver Python

Selenium自动化测试Python二:WebDriver基础

《python3自动化接口+selenium》10月13号开学!(2个月2000,包教会)

通过阅读python subprocess源码尝试实现非阻塞读取stdout以及非阻塞wait

百度面试题——iOS开发