Selenium工作原理详解

发表于:2019-7-05 16:11

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:linux超    来源:博客园

  这个类里面定义了所有的selenium操作需要的接口地址(这些接口地址全部封装在浏览器驱动程序中),那么所有的浏览器操作就是通过访问这些接口来实现的
  其中 Command.GET: ('POST', '/session/$sessionId/url') 这个地址就是实现访问一个网址的url ,我们先记录一下后面有用
  ok,所有的操作对应接口地址我们知道了,那么又怎样执行这些接口来达到在浏览器上实现各种操作呢?继续看紧接着接口地址定义下面的源码
   1     def execute(self, command, params):
  2         """
  3         Send a command to the remote server.
  4
  5         Any path subtitutions required for the URL mapped to the command should be
  6         included in the command parameters.
  7
  8         :Args:
  9          - command - A string specifying the command to execute.
  10          - params - A dictionary of named parameters to send with the command as
  11            its JSON payload.
  12         """
  13         command_info = self._commands[command]
  14         assert command_info is not None, 'Unrecognised command %s' % command
  15         path = string.Template(command_info[1]).substitute(params)
  16         if hasattr(self, 'w3c') and self.w3c and isinstance(params, dict) and 'sessionId' in params:
  17             del params['sessionId']
  18         data = utils.dump_json(params)
  19         url = '%s%s' % (self._url, path)
  20         return self._request(command_info[0], url, body=data)
  21
  22     def _request(self, method, url, body=None):
  23         """
  24         Send an HTTP request to the remote server.
  25
  26         :Args:
  27          - method - A string for the HTTP method to send the request with.
  28          - url - A string for the URL to send the request to.
  29          - body - A string for request body. Ignored unless method is POST or PUT.
  30
  31         :Returns:
  32           A dictionary with the server's parsed JSON response.
  33         """
  34         LOGGER.debug('%s %s %s' % (method, url, body))
  35
  36         parsed_url = parse.urlparse(url)
  37         headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
  38         resp = None
  39         if body and method != 'POST' and method != 'PUT':
  40             body = None
  41
  42         if self.keep_alive:
  43             resp = self._conn.request(method, url, body=body, headers=headers)
  44
  45             statuscode = resp.status
  46         else:
  47             http = urllib3.PoolManager(timeout=self._timeout)
  48             resp = http.request(method, url, body=body, headers=headers)
  49
  50             statuscode = resp.status
  51             if not hasattr(resp, 'getheader'):
  52                 if hasattr(resp.headers, 'getheader'):
  53                     resp.getheader = lambda x: resp.headers.getheader(x)
  54                 elif hasattr(resp.headers, 'get'):
  55                     resp.getheader = lambda x: resp.headers.get(x)
  56
  57         data = resp.data.decode('UTF-8')
  58         try:
  59             if 300 <= statuscode < 304:
  60                 return self._request('GET', resp.getheader('location'))
  61             if 399 < statuscode <= 500:
  62                 return {'status': statuscode, 'value': data}
  63             content_type = []
  64             if resp.getheader('Content-Type') is not None:
  65                 content_type = resp.getheader('Content-Type').split(';')
  66             if not any([x.startswith('image/png') for x in content_type]):
  67
  68                 try:
  69                     data = utils.load_json(data.strip())
  70                 except ValueError:
  71                     if 199 < statuscode < 300:
  72                         status = ErrorCode.SUCCESS
  73                     else:
  74                         status = ErrorCode.UNKNOWN_ERROR
  75                     return {'status': status, 'value': data.strip()}
  76
  77                 # Some of the drivers incorrectly return a response
  78                 # with no 'value' field when they should return null.
  79                 if 'value' not in data:
  80                     data['value'] = None
  81                 return data
  82             else:
  83                 data = {'status': 0, 'value': data}
  84                 return data
  85         finally:
  86             LOGGER.debug("Finished Request")
  87             resp.close()
  可以看到主要是通过execute方法调用_request方法通过urilib3标准库向服务器发送对应操作请求地址,进而实现了浏览器各种操作
  有人会问打开浏览器和操作浏览器实现各种动作是怎么关联的呢?
  其实,打开浏览器也是发送请求,请求会返回一个sessionid,后面操作的各种接口地址,你也会发现接口地址中存在一个变量$sessionid,那么不难猜测打开浏览器和操作浏览器就是用过sessionid关联到一起,达到在同一个浏览器中做操作
  第二步在浏览其上实现各种操作原理也完成了
  模拟selenium
  现在我们可以通过下面的一段代码查看一下打开浏览器和访问我的博客首页的请求参数是什么样子的
   """
  ------------------------------------
  @Time : 2019/6/29 9:16
  @Auth : linux超
  @File : seleniumWebdriver.py
  @IDE  : PyCharm
  @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
  @QQ   : 28174043@qq.com
  @GROUP: 878565760
  ------------------------------------
  """
  from selenium import webdriver
  import logging
  logging.basicConfig(level=logging.DEBUG)  # 打印源码中的日志
  dr = webdriver.Chrome() # 打开浏览器
  driver.get("https://www.cnblogs.com/linuxchao/") # 访问我的博客首页
  输出日志信息
   DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:55695/session
  {"capabilities": {"firstMatch": [{}], "alwaysMatch": {"browserName": "chrome", "platformName": "any", "goog:chromeOptions":
  {"extensions": [], "args": []}}}, "desiredCapabilities": {"browserName": "chrome", "version": "", "platform": "ANY",
  "goog:chromeOptions": {"extensions": [], "args": []}}}
  DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 127.0.0.1
  DEBUG:urllib3.connectionpool:http://127.0.0.1:55695 "POST /session HTTP/1.1" 200 830
  DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
  DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:51006/session/09d52393b7dfcb45b8bb9101885ce206/url
  {"url": "https://www.cnblogs.com/linuxchao/", "sessionId": "09d52393b7dfcb45b8bb9101885ce206"}
  DEBUG:urllib3.connectionpool:http://127.0.0.1:51006 "POST /session/09d52393b7dfcb45b8bb9101885ce206/url HTTP/1.1" 200 72
  DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
  Process finished with exit code 0
  通过执行结果就很明显明白selenium执行的过程了,程序告诉RemoteWebDriver打开一个浏览器(发送post请求,带上请求参数),然后再向remote server发送执行浏览器动作的请求
  那么为了更加深入理解selenium实现自动化测试的过程,我们可以自己编写程序模拟一下打开浏览器然后控制浏览器访问我的博客地址的操作过程
  首先我们需要保持浏览器的驱动程序打开状态,然后编写如下代码并执行
   """
  ------------------------------------
  @Time : 2019/6/28 8:52
  @Auth : linux超
  @File : test.py
  @IDE  : PyCharm
  @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
  @QQ   : 28174043@qq.com
  @GROUP: 878565760
  ------------------------------------
  """
  import requests
  # 请求地址(打开浏览器)
  driver_url = 'http://localhost:9515/session'
  # 打开浏览器的请求参数
  driver_value = {"capabilities":
  {"firstMatch": [{}],
  "alwaysMatch":
  {"browserName":
  "chrome",
  "platformName": "any",
  "goog:chromeOptions":
  {"extensions": [], "args": []}}},
  "desiredCapabilities":
  {"browserName":
  "chrome",
  "version": "",
  "platform": "ANY",
  "goog:chromeOptions": {"extensions": [],
  "args": []}}}
  # 发送求清
  response_session = requests.post(driver_url, json = driver_value)
  print(response_session.json())
  # 访问我的博客的请求地址 (这个地址是我们上面记录的地址)
  url = 'http://localhost:9515/session/'+response_session.json()['sessionId']+'/url'
  # 访问我的博客的请求参数
  value = {"url": "https://www.cnblogs.com/linuxchao/", "sessionId": response_session.json()['sessionId']}
  response_blog = requests.post(url = url,json = value)
  print(response_blog.json())
     执行结果
  {'sessionId': '25144efef880dcce53e4e6f60c342e9d', 'status': 0, 'value':
  {'acceptInsecureCerts': False, 'acceptSslCerts': False, 'applicationCacheEnabled': False,
  'browserConnectionEnabled': False, 'browserName': 'chrome', 'chrome':
  {'chromedriverVersion': '2.39.562718 (9a2698cba08cf5a471a29d30c8b3e12becabb0e9)',
  'userDataDir': 'C:\\Users\\v-xug\\AppData\\Local\\Temp\\scoped_dir9944_25238'},
  'cssSelectorsEnabled': True, 'databaseEnabled': False, 'handlesAlerts': True,
  'hasTouchScreen': False, 'javascriptEnabled': True, 'locationContextEnabled': True,
  'mobileEmulationEnabled': False, 'nativeEvents': True, 'networkConnectionEnabled': False,
  'pageLoadStrategy': 'normal', 'platform': 'Windows NT', 'rotatable': False, 'setWindowRect': True,
  'takesHeapSnapshot': True, 'takesScreenshot': True, 'unexpectedAlertBehaviour': '', 'version': '75.0.3770.100', 'webStorageEnabled': True}}
  {'sessionId': '25144efef880dcce53e4e6f60c342e9d', 'status': 0, 'value': None}
  Process finished with exit code 0
  上面的返回信息中最重要的信息是'sessionId': '25144efef880dcce53e4e6f60c342e9d',从代码中你也可以看到访问我的博客地址的url是使用这个参数拼接的,因为打开浏览器后,后面所有的操作都是基于这个sessionid的
  你还会看到Chrome浏览器被打开,且打开了我的博客地址https://www.cnblogs.com/linuxchao/,这就是selenium原理的一个过程了
  最后
  前面的代码你看不懂,也没关系,我们再来叙述一下selenium工作的过程
  1.selenium client(python等语言编写的自动化测试脚本)初始化一个service服务,通过Webdriver启动浏览器驱动程序chromedriver.exe
  2.通过RemoteWebDriver向浏览器驱动程序发送HTTP请求,浏览器驱动程序解析请求,打开浏览器,并获得sessionid,如果再次对浏览器操作需携带此id
  3.打开浏览器,绑定特定的端口,把启动后的浏览器作为webdriver的remote server
  3.打开浏览器后,所有的selenium的操作(访问地址,查找元素等)均通过RemoteConnection链接到remote server,然后使用execute方法调用_request方法通过urlib3向remote server发送请求
  4.浏览器通过请求的内容执行对应动作
  5.浏览器再把执行的动作结果通过浏览器驱动程序返回给测试脚本

      上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号