【点点点】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})