【点点点】selenium原理解析

参考:https://www.cnblogs.com/linuxchao/p/linux-selenium-webdriver.html、https://cloud.tencent.com/developer/article/1461359

1、selenium简介

Selenium是一个用于Web应用程序自动化测试工具。selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7,8,9,10,11),Mozilla Firfox,Safari,Google Chrome,Opera等。

包括功能:

1)测试与浏览器的兼容性  --测试应用程序看是否能够很好得工作在不同浏览器和操作系统上

2)测试系统功能 --创建回归测试检测软件功能和用户需求,支持自动录制和自动生成.Net、Java、Perl等不同语言的测试脚本

2、selenium原理

2.1 使用Selenium实现自动化测试,主要包括:

1)测试脚本,一般指脚本程序,也称client端

2)浏览器驱动,不同的浏览器使用不同的webdriver驱动程序且需要对应相应的浏览器版本,也称:服务端,如:geckodriver.exe(chrome)

3)浏览器,目前selenium支持大多数浏览器,如:火狐,谷歌,IE等

解析:

步骤1:对于每一个Selenium脚本,一个http请求会被创建并且发送给浏览器的驱动(测试人员执行测试脚本后,就创建了一个session, 通过http 请求向webservice发送了restfull的请求)

步骤2:浏览器驱动中包含一个HTTP Server,用来接收发送的http请求,HTTP Server接收到请求后根据请求来具体操控对应的浏览器(webservice翻译restfull的请求为浏览器能懂的脚本,然后接受脚本执行结果)

步骤3:浏览器将步骤执行结果返回给HTTP Server,HTTP Server又将结果返回给Selenium的脚本。若是错误,可在控制台看到对应的报错信息(webservice将结果进行封装--json 给到客户端client/测试脚本 ,然后client就知道操作是否成功,同时测试也可以进行校验了)

2.2 相关协议

1)HTTP协议是一个浏览器和Web服务器之间的通信的标准协议,几乎每一种编程语言都提供了丰富的http libraries,可方便的处理客户端Client和服务端Server之间的请求request及响应response。WebDriver的结构中是典型的C/S结构,WebDriver API相当于是客户端,浏览器驱动才是真正的服务端。

2)WebDriver协议:JSON Wire protocol,是在HTTP协议基础上,对HTTP请求及响应的body部分的数据进一步规范。常见HTTP请求及响应包括:http请求方法,http请求及响应内容body,http响应状态码等。

3)常见的http请求方法:GET(用来服务器获取信息,如:网页的标题信息)、POST(用来服务器发送操作请求,如:findElement,Click等)

4)HTTP 响应代码:在WebDriver中进行了明确的反馈信息,细化了HTTP响应的状态码,如:7:NoSuchElement,11:ElementNotVisible等

5)Body部分主要传送具体的数据,WebDriver中数据都是以JSON形式存在并进行传送(Selenium是将各个浏览器的API封装成,即The WebDriver Wire Protocol),称作:JSON Wire protocol的Webdriver API

6)打开chromedriver,可以看到开启一个Server,并开启端口:9515

3、selenium脚本-源码分析

\Lib\site-packages\selenium\webdriver\chrome\webdriver.py

class WebDriver(RemoteWebDriver):
"""
Controls the ChromeDriver and allows you to drive the browser.

You will need to download the ChromeDriver executable from
http://chromedriver.storage.googleapis.com/index.html
"""

def __init__(self, executable_path="chromedriver", port=0,
options=None, service_args=None,
desired_capabilities=None, service_log_path=None,
chrome_options=None, keep_alive=True):
"""
Creates a new instance of the chrome driver.

Starts the service and then creates new instance of chrome driver.

:Args:
- executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
- port - port you would like the service to run, if left as 0, a free port will be found.
- options - this takes an instance of ChromeOptions
- service_args - List of args to pass to the driver service
- desired_capabilities - Dictionary object with non-browser specific
capabilities only, such as "proxy" or "loggingPref".
- service_log_path - Where to log information from the driver.
- chrome_options - Deprecated argument for options
- keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
"""
if chrome_options:
warnings.warn('use options instead of chrome_options',
DeprecationWarning, stacklevel=2)
options = chrome_options

if options is None:
# desired_capabilities stays as passed in
if desired_capabilities is None:
desired_capabilities = self.create_options().to_capabilities()
else:
if desired_capabilities is None:
desired_capabilities = options.to_capabilities()
else:
desired_capabilities.update(options.to_capabilities())
self.service = Service( #第一步
executable_path,
port=port,
service_args=service_args,
log_path=service_log_path)
self.service.start()
try:
RemoteWebDriver.__init__(
self,
command_executor=ChromeRemoteConnection(
remote_server_addr=self.service.service_url,
keep_alive=keep_alive),
desired_capabilities=desired_capabilities)
except Exception:
self.quit()
raise
self._is_remote = False

首先,初始化一个service对象,然后调用start()方法,下面看下start()方法

\Lib\site-packages\selenium\webdriver\common\service.py

def start(self):
"""
Starts the Service.
:Exceptions:
- WebDriverException : Raised either when it can't start the service
or when it can't connect to the service
"""
try: #第二步
cmd = [self.path]
cmd.extend(self.command_line_args())
self.process = subprocess.Popen(cmd, env=self.env,
close_fds=platform.system() != 'Windows',
stdout=self.log_file,
stderr=self.log_file,
stdin=PIPE)
except TypeError:
raise
except OSError as err:
if err.errno == errno.ENOENT:
raise WebDriverException(
"'%s' executable needs to be in PATH. %s" % (
os.path.basename(self.path), self.start_error_message)
)
elif err.errno == errno.EACCES:
raise WebDriverException(
"'%s' executable may have wrong permissions. %s" % (
os.path.basename(self.path), self.start_error_message)
)
else:
raise
except Exception as e:
raise WebDriverException(
"The executable %s needs to be available in the path. %s\n%s" %
(os.path.basename(self.path), self.start_error_message, str(e)))
count = 0
while True:
self.assert_process_still_running()
if self.is_connectable():
break
count += 1
time.sleep(1)
if count == 30:
raise WebDriverException("Can not connect to the Service %s" % self.path)

其次,发现是执行了一个cmd命令,命令的作用就是启动了chromedriver.exeChrome浏览器的驱动程序(注:下载的浏览器驱动一定要配置到环境变量中,或放到python的根目录,偏于程序在执行驱动查找,类似于我们手动启动浏览器驱动一样)

 自此,了解执行脚本webdriver.Chrome() 会自动执行chromedriver.exe驱动程序,后开启一个进程。

接下来,看下如何打开浏览器

\Lib\site-packages\selenium\webdriver\chrome\webdriver.py --调用了父类RemoteWebDriver 的初始化方法

 

class WebDriver(RemoteWebDriver):  #第三步
"""
Controls the ChromeDriver and allows you to drive the browser.

You will need to download the ChromeDriver executable from
http://chromedriver.storage.googleapis.com/index.html
"""

def __init__(self, executable_path="chromedriver", port=0,
options=None, service_args=None,
desired_capabilities=None, service_log_path=None,
chrome_options=None, keep_alive=True):
"""
Creates a new instance of the chrome driver.

Starts the service and then creates new instance of chrome driver.

:Args:
- executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
- port - port you would like the service to run, if left as 0, a free port will be found.
- options - this takes an instance of ChromeOptions
- service_args - List of args to pass to the driver service
- desired_capabilities - Dictionary object with non-browser specific
capabilities only, such as "proxy" or "loggingPref".
- service_log_path - Where to log information from the driver.
- chrome_options - Deprecated argument for options
- keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
"""
if chrome_options:
warnings.warn('use options instead of chrome_options',
DeprecationWarning, stacklevel=2)
options = chrome_options

if options is None:
# desired_capabilities stays as passed in
if desired_capabilities is None:
desired_capabilities = self.create_options().to_capabilities()
else:
if desired_capabilities is None:
desired_capabilities = options.to_capabilities()
else:
desired_capabilities.update(options.to_capabilities())

self.service = Service(
executable_path,
port=port,
service_args=service_args,
log_path=service_log_path)
self.service.start()

\Lib\site-packages\selenium\webdriver\remote\webdriver.py

class WebDriver(object):
"""
Controls a browser by sending commands to a remote server.
This server is expected to be running the WebDriver wire protocol
as defined at
https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol

:Attributes:
- session_id - String ID of the browser session started and controlled by this WebDriver.
- capabilities - Dictionaty of effective capabilities of this browser session as returned
by the remote server. See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities
- command_executor - remote_connection.RemoteConnection object used to execute commands.
- error_handler - errorhandler.ErrorHandler object used to handle errors.
"""

_web_element_cls = WebElement

def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
desired_capabilities=None, browser_profile=None, proxy=None,
keep_alive=False, file_detector=None, options=None):
"""
Create a new driver that will issue commands using the wire protocol.

:Args:
- command_executor - Either a string representing URL of the remote server or a custom
remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
- desired_capabilities - A dictionary of capabilities to request when
starting the browser session. Required parameter.
- browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object.
Only used if Firefox is requested. Optional.
- proxy - A selenium.webdriver.common.proxy.Proxy object. The browser session will
be started with given proxy settings, if possible. Optional.
- keep_alive - Whether to configure remote_connection.RemoteConnection to use
HTTP keep-alive. Defaults to False.
- file_detector - Pass custom file detector object during instantiation. If None,
then default LocalFileDetector() will be used.
- options - instance of a driver options.Options class
"""
capabilities = {}
if options is not None:
capabilities = options.to_capabilities()
if desired_capabilities is not None:
if not isinstance(desired_capabilities, dict):
raise WebDriverException("Desired Capabilities must be a dictionary")
else:
capabilities.update(desired_capabilities)
if proxy is not None:
warnings.warn("Please use FirefoxOptions to set proxy",
DeprecationWarning, stacklevel=2)
proxy.add_to_capabilities(capabilities)
self.command_executor = command_executor
if type(self.command_executor) is bytes or isinstance(self.command_executor, str):
self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
self._is_remote = True
self.session_id = None
self.capabilities = {}
self.error_handler = ErrorHandler()
self.start_client()
if browser_profile is not None:
warnings.warn("Please use FirefoxOptions to set browser profile",
DeprecationWarning, stacklevel=2)
self.start_session(capabilities, browser_profile) #第四步
self._switch_to = SwitchTo(self)
self._mobile = Mobile(self)
self.file_detector = file_detector or LocalFileDetector()

def start_session(self, capabilities, browser_profile=None):
"""
Creates a new session with the desired capabilities.

:Args:
- browser_name - The name of the browser to request.
- version - Which browser version to request.
- platform - Which platform to request the browser on.
- javascript_enabled - Whether the new session should support JavaScript.
- browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested.
"""
if not isinstance(capabilities, dict):
raise InvalidArgumentException("Capabilities must be a dictionary")
if browser_profile:
if "moz:firefoxOptions" in capabilities:
capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded
else:
capabilities.update({'firefox_profile': browser_profile.encoded})
w3c_caps = _make_w3c_caps(capabilities)
parameters = {"capabilities": w3c_caps,
"desiredCapabilities": capabilities}
response = self.execute(Command.NEW_SESSION, parameters) #第五步,向地址 localhost:9515/session发送一个post请求,参数格式是json格式,后返回特定的响应信息给程序(新建sessionid),打开浏览器
if 'sessionId' not in response:
response = response['value']
self.session_id = response['sessionId']
self.capabilities = response.get('value')

# if capabilities is none we are probably speaking to
# a W3C endpoint
if self.capabilities is None:
self.capabilities = response.get('capabilities')

# Double check to see if we have a W3C Compliant browser
self.w3c = response.get('status') is None
self.command_executor.w3c = self.w3c

注:
我们可以构造一个http请求,如下:
 1 请求方式 :POST
 2 请求地址 :http://localhost:9515/session
 3 请求body :
 4 
 5 capabilities = {
 6     "capabilities": {
 7         "alwaysMatch": {
 8             "browserName": "chrome"
 9         },
10         "firstMatch": [
11             {}
12         ]
13     },
14     "desiredCapabilities": {
15         "platform": "ANY",
16         "browserName": "chrome",
17         "version": "",
18         "chromeOptions": {
19             "args": [],
20             "extensions": []
21         }
22     }
23 }
 1 import requests
 2 import json
 3 session_url = 'http://localhost:9515/session'
 4 session_pars = {"capabilities": {"firstMatch": [{}], \
 5                       "alwaysMatch": {"browserName": "chrome",\
 6                                       "platformName": "any", \
 7                                       "goog:chromeOptions": {"extensions": [], "args": []}}}, \
 8                 "desiredCapabilities": {"browserName": "chrome", \
 9                              "version": "", "platform": "ANY", "goog:chromeOptions": {"extensions": [], "args": []}}}
10 r_session = requests.post(session_url,json=session_pars)
11 print(json.dumps(r_session.json(),indent=2))
 1 {
 2   "sessionId": "44fdb7b1b048a76c0f625545b0d2567b",
 3   "status": 0,
 4   "value": {
 5     "acceptInsecureCerts": false,
 6     "acceptSslCerts": false,
 7     "applicationCacheEnabled": false,
 8     "browserConnectionEnabled": false,
 9     "browserName": "chrome",
10     "chrome": {
11       "chromedriverVersion": "2.40.565386 (45a059dc425e08165f9a10324bd1380cc13ca363)",
12       "userDataDir": "/var/folders/yd/dmwmz84x5rj354qkz9rwwzbc0000gn/T/.org.chromium.Chromium.RzlABs"
13     },
14     "cssSelectorsEnabled": true,
15     "databaseEnabled": false,
16     "handlesAlerts": true,
17     "hasTouchScreen": false,
18     "javascriptEnabled": true,
19     "locationContextEnabled": true,
20     "mobileEmulationEnabled": false,
21     "nativeEvents": true,
22     "networkConnectionEnabled": false,
23     "pageLoadStrategy": "normal",
24     "platform": "Mac OS X",
25     "rotatable": false,
26     "setWindowRect": true,
27     "takesHeapSnapshot": true,
28     "takesScreenshot": true,
29     "unexpectedAlertBehaviour": "",
30     "version": "71.0.3578.80",
31     "webStorageEnabled": true
32   }
33 }

 至此,我们获取到sessionId。

接下来,看下如何执行对应的操作

\Lib\site-packages\selenium\webdriver\chrome\webdriver.py

class WebDriver(RemoteWebDriver):
"""
Controls the ChromeDriver and allows you to drive the browser.

You will need to download the ChromeDriver executable from
http://chromedriver.storage.googleapis.com/index.html
"""

def __init__(self, executable_path="chromedriver", port=0,
options=None, service_args=None,
desired_capabilities=None, service_log_path=None,
chrome_options=None, keep_alive=True):
"""
Creates a new instance of the chrome driver.

Starts the service and then creates new instance of chrome driver.

:Args:
- executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
- port - port you would like the service to run, if left as 0, a free port will be found.
- options - this takes an instance of ChromeOptions
- service_args - List of args to pass to the driver service
- desired_capabilities - Dictionary object with non-browser specific
capabilities only, such as "proxy" or "loggingPref".
- service_log_path - Where to log information from the driver.
- chrome_options - Deprecated argument for options
- keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
"""
if chrome_options:
warnings.warn('use options instead of chrome_options',
DeprecationWarning, stacklevel=2)
options = chrome_options

if options is None:
# desired_capabilities stays as passed in
if desired_capabilities is None:
desired_capabilities = self.create_options().to_capabilities()
else:
if desired_capabilities is None:
desired_capabilities = options.to_capabilities()
else:
desired_capabilities.update(options.to_capabilities())

self.service = Service(
executable_path,
port=port,
service_args=service_args,
log_path=service_log_path)
self.service.start()

try:
RemoteWebDriver.__init__( #
self,
command_executor=ChromeRemoteConnection( #第六步
remote_server_addr=self.service.service_url,
keep_alive=keep_alive),
desired_capabilities=desired_capabilities)
except Exception:
self.quit()
raise
self._is_remote = False

\Lib\site-packages\selenium\webdriver\chrome\remote_connection.py

 1 from selenium.webdriver.remote.remote_connection import RemoteConnection
 2 
 3 
 4 class ChromeRemoteConnection(RemoteConnection):
 5 
 6     def __init__(self, remote_server_addr, keep_alive=True):
 7         RemoteConnection.__init__(self, remote_server_addr, keep_alive)  #访问的是 localhost:9515/session
 8         self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app')  #定义和浏览器(chrome)的接口地址,可以在看下父类RemoteConnectiond定义
 9         self._commands["setNetworkConditions"] = ('POST', '/session/$sessionId/chromium/network_conditions')
10         self._commands["getNetworkConditions"] = ('GET', '/session/$sessionId/chromium/network_conditions')
11         self._commands['executeCdpCommand'] = ('POST', '/session/$sessionId/goog/cdp/execute')

\Lib\site-packages\selenium\webdriver\remote\remote_connection.py

这个类中定义了所有的selenium操作需要的接口地址(接口地址均封装在浏览器驱动中),所有的浏览器均是通过该接口实现的,如:Command.GET: ('POST', '/session/$sessionId/url') 

  1      self._commands = {
  2             Command.STATUS: ('GET', '/status'),
  3             Command.NEW_SESSION: ('POST', '/session'),
  4             Command.GET_ALL_SESSIONS: ('GET', '/sessions'),
  5             Command.QUIT: ('DELETE', '/session/$sessionId'),
  6             Command.GET_CURRENT_WINDOW_HANDLE:
  7                 ('GET', '/session/$sessionId/window_handle'),
  8             Command.W3C_GET_CURRENT_WINDOW_HANDLE:
  9                 ('GET', '/session/$sessionId/window'),
 10             Command.GET_WINDOW_HANDLES:
 11                 ('GET', '/session/$sessionId/window_handles'),
 12             Command.W3C_GET_WINDOW_HANDLES:
 13                 ('GET', '/session/$sessionId/window/handles'),
 14             Command.GET: ('POST', '/session/$sessionId/url'),
 15             Command.GO_FORWARD: ('POST', '/session/$sessionId/forward'),
 16             Command.GO_BACK: ('POST', '/session/$sessionId/back'),
 17             Command.REFRESH: ('POST', '/session/$sessionId/refresh'),
 18             Command.EXECUTE_SCRIPT: ('POST', '/session/$sessionId/execute'),
 19             Command.W3C_EXECUTE_SCRIPT:
 20                 ('POST', '/session/$sessionId/execute/sync'),
 21             Command.W3C_EXECUTE_SCRIPT_ASYNC:
 22                 ('POST', '/session/$sessionId/execute/async'),
 23             Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'),
 24             Command.GET_TITLE: ('GET', '/session/$sessionId/title'),
 25             Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'),
 26             Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'),
 27             Command.ELEMENT_SCREENSHOT: ('GET', '/session/$sessionId/element/$id/screenshot'),
 28             Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'),
 29             Command.FIND_ELEMENTS: ('POST', '/session/$sessionId/elements'),
 30             Command.W3C_GET_ACTIVE_ELEMENT: ('GET', '/session/$sessionId/element/active'),
 31             Command.GET_ACTIVE_ELEMENT:
 32                 ('POST', '/session/$sessionId/element/active'),
 33             Command.FIND_CHILD_ELEMENT:
 34                 ('POST', '/session/$sessionId/element/$id/element'),
 35             Command.FIND_CHILD_ELEMENTS:
 36                 ('POST', '/session/$sessionId/element/$id/elements'),
 37             Command.CLICK_ELEMENT: ('POST', '/session/$sessionId/element/$id/click'),
 38             Command.CLEAR_ELEMENT: ('POST', '/session/$sessionId/element/$id/clear'),
 39             Command.SUBMIT_ELEMENT: ('POST', '/session/$sessionId/element/$id/submit'),
 40             Command.GET_ELEMENT_TEXT: ('GET', '/session/$sessionId/element/$id/text'),
 41             Command.SEND_KEYS_TO_ELEMENT:
 42                 ('POST', '/session/$sessionId/element/$id/value'),
 43             Command.SEND_KEYS_TO_ACTIVE_ELEMENT:
 44                 ('POST', '/session/$sessionId/keys'),
 45             Command.UPLOAD_FILE: ('POST', "/session/$sessionId/file"),
 46             Command.GET_ELEMENT_VALUE:
 47                 ('GET', '/session/$sessionId/element/$id/value'),
 48             Command.GET_ELEMENT_TAG_NAME:
 49                 ('GET', '/session/$sessionId/element/$id/name'),
 50             Command.IS_ELEMENT_SELECTED:
 51                 ('GET', '/session/$sessionId/element/$id/selected'),
 52             Command.SET_ELEMENT_SELECTED:
 53                 ('POST', '/session/$sessionId/element/$id/selected'),
 54             Command.IS_ELEMENT_ENABLED:
 55                 ('GET', '/session/$sessionId/element/$id/enabled'),
 56             Command.IS_ELEMENT_DISPLAYED:
 57                 ('GET', '/session/$sessionId/element/$id/displayed'),
 58             Command.GET_ELEMENT_LOCATION:
 59                 ('GET', '/session/$sessionId/element/$id/location'),
 60             Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW:
 61                 ('GET', '/session/$sessionId/element/$id/location_in_view'),
 62             Command.GET_ELEMENT_SIZE:
 63                 ('GET', '/session/$sessionId/element/$id/size'),
 64             Command.GET_ELEMENT_RECT:
 65                 ('GET', '/session/$sessionId/element/$id/rect'),
 66             Command.GET_ELEMENT_ATTRIBUTE:
 67                 ('GET', '/session/$sessionId/element/$id/attribute/$name'),
 68             Command.GET_ELEMENT_PROPERTY:
 69                 ('GET', '/session/$sessionId/element/$id/property/$name'),
 70             Command.GET_ALL_COOKIES: ('GET', '/session/$sessionId/cookie'),
 71             Command.ADD_COOKIE: ('POST', '/session/$sessionId/cookie'),
 72             Command.GET_COOKIE: ('GET', '/session/$sessionId/cookie/$name'),
 73             Command.DELETE_ALL_COOKIES:
 74                 ('DELETE', '/session/$sessionId/cookie'),
 75             Command.DELETE_COOKIE:
 76                 ('DELETE', '/session/$sessionId/cookie/$name'),
 77             Command.SWITCH_TO_FRAME: ('POST', '/session/$sessionId/frame'),
 78             Command.SWITCH_TO_PARENT_FRAME: ('POST', '/session/$sessionId/frame/parent'),
 79             Command.SWITCH_TO_WINDOW: ('POST', '/session/$sessionId/window'),
 80             Command.CLOSE: ('DELETE', '/session/$sessionId/window'),
 81             Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY:
 82                 ('GET', '/session/$sessionId/element/$id/css/$propertyName'),
 83             Command.IMPLICIT_WAIT:
 84                 ('POST', '/session/$sessionId/timeouts/implicit_wait'),
 85             Command.EXECUTE_ASYNC_SCRIPT: ('POST', '/session/$sessionId/execute_async'),
 86             Command.SET_SCRIPT_TIMEOUT:
 87                 ('POST', '/session/$sessionId/timeouts/async_script'),
 88             Command.SET_TIMEOUTS:
 89                 ('POST', '/session/$sessionId/timeouts'),
 90             Command.DISMISS_ALERT:
 91                 ('POST', '/session/$sessionId/dismiss_alert'),
 92             Command.W3C_DISMISS_ALERT:
 93                 ('POST', '/session/$sessionId/alert/dismiss'),
 94             Command.ACCEPT_ALERT:
 95                 ('POST', '/session/$sessionId/accept_alert'),
 96             Command.W3C_ACCEPT_ALERT:
 97                 ('POST', '/session/$sessionId/alert/accept'),
 98             Command.SET_ALERT_VALUE:
 99                 ('POST', '/session/$sessionId/alert_text'),
100             Command.W3C_SET_ALERT_VALUE:
101                 ('POST', '/session/$sessionId/alert/text'),
102             Command.GET_ALERT_TEXT:
103                 ('GET', '/session/$sessionId/alert_text'),
104             Command.W3C_GET_ALERT_TEXT:
105                 ('GET', '/session/$sessionId/alert/text'),
106             Command.SET_ALERT_CREDENTIALS:
107                 ('POST', '/session/$sessionId/alert/credentials'),
108             Command.CLICK:
109                 ('POST', '/session/$sessionId/click'),
110             Command.W3C_ACTIONS:
111                 ('POST', '/session/$sessionId/actions'),
112             Command.W3C_CLEAR_ACTIONS:
113                 ('DELETE', '/session/$sessionId/actions'),
114             Command.DOUBLE_CLICK:
115                 ('POST', '/session/$sessionId/doubleclick'),
116             Command.MOUSE_DOWN:
117                 ('POST', '/session/$sessionId/buttondown'),
118             Command.MOUSE_UP:
119                 ('POST', '/session/$sessionId/buttonup'),
120             Command.MOVE_TO:
121                 ('POST', '/session/$sessionId/moveto'),
122             Command.GET_WINDOW_SIZE:
123                 ('GET', '/session/$sessionId/window/$windowHandle/size'),
124             Command.SET_WINDOW_SIZE:
125                 ('POST', '/session/$sessionId/window/$windowHandle/size'),
126             Command.GET_WINDOW_POSITION:
127                 ('GET', '/session/$sessionId/window/$windowHandle/position'),
128             Command.SET_WINDOW_POSITION:
129                 ('POST', '/session/$sessionId/window/$windowHandle/position'),
130             Command.SET_WINDOW_RECT:
131                 ('POST', '/session/$sessionId/window/rect'),
132             Command.GET_WINDOW_RECT:
133                 ('GET', '/session/$sessionId/window/rect'),
134             Command.MAXIMIZE_WINDOW:
135                 ('POST', '/session/$sessionId/window/$windowHandle/maximize'),
136             Command.W3C_MAXIMIZE_WINDOW:
137                 ('POST', '/session/$sessionId/window/maximize'),
138             Command.SET_SCREEN_ORIENTATION:
139                 ('POST', '/session/$sessionId/orientation'),
140             Command.GET_SCREEN_ORIENTATION:
141                 ('GET', '/session/$sessionId/orientation'),
142             Command.SINGLE_TAP:
143                 ('POST', '/session/$sessionId/touch/click'),
144             Command.TOUCH_DOWN:
145                 ('POST', '/session/$sessionId/touch/down'),
146             Command.TOUCH_UP:
147                 ('POST', '/session/$sessionId/touch/up'),
148             Command.TOUCH_MOVE:
149                 ('POST', '/session/$sessionId/touch/move'),
150             Command.TOUCH_SCROLL:
151                 ('POST', '/session/$sessionId/touch/scroll'),
152             Command.DOUBLE_TAP:
153                 ('POST', '/session/$sessionId/touch/doubleclick'),
154             Command.LONG_PRESS:
155                 ('POST', '/session/$sessionId/touch/longclick'),
156             Command.FLICK:
157                 ('POST', '/session/$sessionId/touch/flick'),
158             Command.EXECUTE_SQL:
159                 ('POST', '/session/$sessionId/execute_sql'),
160             Command.GET_LOCATION:
161                 ('GET', '/session/$sessionId/location'),
162             Command.SET_LOCATION:
163                 ('POST', '/session/$sessionId/location'),
164             Command.GET_APP_CACHE:
165                 ('GET', '/session/$sessionId/application_cache'),
166             Command.GET_APP_CACHE_STATUS:
167                 ('GET', '/session/$sessionId/application_cache/status'),
168             Command.CLEAR_APP_CACHE:
169                 ('DELETE', '/session/$sessionId/application_cache/clear'),
170             Command.GET_NETWORK_CONNECTION:
171                 ('GET', '/session/$sessionId/network_connection'),
172             Command.SET_NETWORK_CONNECTION:
173                 ('POST', '/session/$sessionId/network_connection'),
174             Command.GET_LOCAL_STORAGE_ITEM:
175                 ('GET', '/session/$sessionId/local_storage/key/$key'),
176             Command.REMOVE_LOCAL_STORAGE_ITEM:
177                 ('DELETE', '/session/$sessionId/local_storage/key/$key'),
178             Command.GET_LOCAL_STORAGE_KEYS:
179                 ('GET', '/session/$sessionId/local_storage'),
180             Command.SET_LOCAL_STORAGE_ITEM:
181                 ('POST', '/session/$sessionId/local_storage'),
182             Command.CLEAR_LOCAL_STORAGE:
183                 ('DELETE', '/session/$sessionId/local_storage'),
184             Command.GET_LOCAL_STORAGE_SIZE:
185                 ('GET', '/session/$sessionId/local_storage/size'),
186             Command.GET_SESSION_STORAGE_ITEM:
187                 ('GET', '/session/$sessionId/session_storage/key/$key'),
188             Command.REMOVE_SESSION_STORAGE_ITEM:
189                 ('DELETE', '/session/$sessionId/session_storage/key/$key'),
190             Command.GET_SESSION_STORAGE_KEYS:
191                 ('GET', '/session/$sessionId/session_storage'),
192             Command.SET_SESSION_STORAGE_ITEM:
193                 ('POST', '/session/$sessionId/session_storage'),
194             Command.CLEAR_SESSION_STORAGE:
195                 ('DELETE', '/session/$sessionId/session_storage'),
196             Command.GET_SESSION_STORAGE_SIZE:
197                 ('GET', '/session/$sessionId/session_storage/size'),
198             Command.GET_LOG:
199                 ('POST', '/session/$sessionId/log'),
200             Command.GET_AVAILABLE_LOG_TYPES:
201                 ('GET', '/session/$sessionId/log/types'),
202             Command.CURRENT_CONTEXT_HANDLE:
203                 ('GET', '/session/$sessionId/context'),
204             Command.CONTEXT_HANDLES:
205                 ('GET', '/session/$sessionId/contexts'),
206             Command.SWITCH_TO_CONTEXT:
207                 ('POST', '/session/$sessionId/context'),
208             Command.FULLSCREEN_WINDOW:
209                 ('POST', '/session/$sessionId/window/fullscreen'),
210             Command.MINIMIZE_WINDOW:
211                 ('POST', '/session/$sessionId/window/minimize')
212         }

接下来,有了接口地址,看下如何执行
 1  def execute(self, command, params):   #通过excute方法调用request方法中urllib3标准库向服务器发送对应操作请求地址,进而实现浏览器各种操作
 2 
 3         """
 4         Send a command to the remote server.
 5 
 6         Any path subtitutions required for the URL mapped to the command should be  
 7         included in the command parameters.
 8 
 9         :Args:
10          - command - A string specifying the command to execute.
11          - params - A dictionary of named parameters to send with the command as
12            its JSON payload.
13         """
14 
15 
16         command_info = self._commands[command]
17         # print(command)
18         print(command_info)
19         print(params)
20 
21         assert command_info is not None, 'Unrecognised command %s' % command
22         path = string.Template(command_info[1]).substitute(params)
23         if hasattr(self, 'w3c') and self.w3c and isinstance(params, dict) and 'sessionId' in params:
24             del params['sessionId']
25         data = utils.dump_json(params)
26         url = '%s%s' % (self._url, path)
27         print(url)
28         return self._request(command_info[0], url, body=data)
29 
30     def _request(self, method, url, body=None):
31         """
32         Send an HTTP request to the remote server.
33 
34         :Args:
35          - method - A string for the HTTP method to send the request with.
36          - url - A string for the URL to send the request to.
37          - body - A string for request body. Ignored unless method is POST or PUT.
38 
39         :Returns:
40           A dictionary with the server's parsed JSON response.
41         """
42         LOGGER.debug('%s %s %s' % (method, url, body))
43 
44         parsed_url = parse.urlparse(url)
45         headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
46         resp = None
47         if body and method != 'POST' and method != 'PUT':
48             body = None
49 
50         if self.keep_alive:
51             resp = self._conn.request(method, url, body=body, headers=headers)
52 
53             statuscode = resp.status
54         else:
55             http = urllib3.PoolManager(timeout=self._timeout)
56             resp = http.request(method, url, body=body, headers=headers)
57 
58             statuscode = resp.status
59             if not hasattr(resp, 'getheader'):
60                 if hasattr(resp.headers, 'getheader'):
61                     resp.getheader = lambda x: resp.headers.getheader(x)
62                 elif hasattr(resp.headers, 'get'):
63                     resp.getheader = lambda x: resp.headers.get(x)
64 
65         data = resp.data.decode('UTF-8')
66         try:
67             if 300 <= statuscode < 304:
68                 return self._request('GET', resp.getheader('location'))
69             if 399 < statuscode <= 500:
70                 return {'status': statuscode, 'value': data}
71             content_type = []
72             if resp.getheader('Content-Type') is not None:
73                 content_type = resp.getheader('Content-Type').split(';')
74             if not any([x.startswith('image/png') for x in content_type]):
75 
76                 try:
77                     data = utils.load_json(data.strip())
78                 except ValueError:
79                     if 199 < statuscode < 300:
80                         status = ErrorCode.SUCCESS
81                     else:
82                         status = ErrorCode.UNKNOWN_ERROR
83                     return {'status': status, 'value': data.strip()}
84 
85                 # Some of the drivers incorrectly return a response
86                 # with no 'value' field when they should return null.
87                 if 'value' not in data:
88                     data['value'] = None
89                 return data
90             else:
91                 data = {'status': 0, 'value': data}
92                 return data
93         finally:
94             LOGGER.debug("Finished Request")
95             resp.close()

至此,从打开浏览器开始发送请求,请求会返回一个sessionid,后面操作各种接口地址。且每个接口地址中均存在变量$sessionid,执行操作均关联sessionid,达到在一个浏览器中操作
注:
我们可以构造一个http请求,如下:
1)如打开一个网页,如:driver.get(url)
请求方式 :POST
请求地址 :http://localhost:9515/session/:sessionId/url

注意:上述地址中的 ":sessionId"
要用启动浏览器的请求返回结果中的sessionId的值
例如:我刚刚发送请求,启动浏览器,返回结果中"sessionId": "44fdb7b1b048a76c0f625545b0d2567b"  
然后请求的URL地址
请求地址:http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/url

请求body :{"url": "https://www.baidu.com", "sessionId": "44fdb7b1b048a76c0f625545b0d2567b"}
1 import requests
2 url = 'http://localhost:9515/session/44fdb7b1b048a76c0f625545b0d2567b/url'
3 pars = {"url": "https://www.baidu.com", "sessionId": "44fdb7b1b048a76c0f625545b0d2567b"}
4 r = requests.post(url,json=pars)
5 print(r.json())
2)如定位元素,类似driver.find_element_by_××:
 1 请求方式 :POST
 2 请求地址 :http://localhost:9515/session/:sessionId/element
 3 
 4 注意:上述地址中的 ":sessionId"
 5 要用启动浏览器的请求返回结果中的sessionId的值
 6 例如:我刚刚发送请求,启动浏览器,返回结果中"sessionId": "b2801b5dc58b15e76d0d3295b04d295c"  
 7 然后我构造 查找页面元素的请求地址
 8 请求地址:http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element
 9 
10 请求body :{"using": "css selector", "value": ".postTitle a", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"}
1 import requests
2 url = 'http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element'
3 pars = {"using": "css selector", "value": ".postTitle a", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"}
4 r = requests.post(url,json=pars)
5 print(r.json())
3)如定位元素,类似click():
 1 请求方式 :POST
 2 请求地址 :http://localhost:9515/session/:sessionId/element/:id/click
 3 
 4 注意:上述地址中的 ":sessionId"
 5 要用启动浏览器的请求返回结果中的sessionId的值
 6 :id 要用元素定位请求后返回ELEMENT的值
 7 
 8 例如:我刚刚发送请求,启动浏览器,返回结果中"sessionId": "b2801b5dc58b15e76d0d3295b04d295c"  
 9 元素定位,返回ELEMENT的值"0.11402119390850629-1"
10 
11 然后我构造 点击页面元素的请求地址
12 请求地址:http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element/0.11402119390850629-1/click
13 
14 请求body :{"id": "0.11402119390850629-1", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"}
1 import requests
2 url = 'http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element/0.11402119390850629-1/click'
3 pars ={"id": "0.5930642995574296-1", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"}
4 r = requests.post(url,json=pars)
5 print(r.json())

从上面可以看出来,UI自动化,其实也可以写成API自动化。只是太繁琐,没有封装的webdriver指令好用,如下完整的代码:

 1 import requests
 2 import time
 3 
 4 capabilities = {
 5     "capabilities": {
 6         "alwaysMatch": {
 7             "browserName": "chrome"
 8         },
 9         "firstMatch": [
10             {}
11         ]
12     },
13     "desiredCapabilities": {
14         "platform": "ANY",
15         "browserName": "chrome",
16         "version": "",
17         "chromeOptions": {
18             "args": [],
19             "extensions": []
20         }
21     }
22 }
23 
24 # 打开浏览器 http://127.0.0.1:9515/session
25 res = requests.post('http://127.0.0.1:9515/session', json=capabilities).json()
26 session_id = res['sessionId']
27 
28 # 打开百度
29 requests.post('http://127.0.0.1:9515/session/%s/url' % session_id,
30               json={"url": "http://www.baidu.com", "sessionId": session_id})
31 
32 time.sleep(3)
33 
34 # 关闭浏览器,删除session
35 requests.delete('http://127.0.0.1:9515/session/%s' % session_id, json={"sessionId": session_id})
posted @ 2022-02-26 20:04  站在围墙上的白白  阅读(2877)  评论(0编辑  收藏  举报