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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM