1. 程式人生 > 其它 >selenium的工作原理講解 selenium工作原理詳解

selenium的工作原理講解 selenium工作原理詳解

這是別人寫好的,我就是個搬運工 

網站原地址:

https://www.cnblogs.com/linuxchao/p/linux-selenium-webdriver.html

selenium工作原理詳解

 

selenium簡介

Selenium是一個用於Web應用程式自動化測試工具。Selenium測試直接執行在瀏覽器中,就像真正的使用者在操作一樣。支援的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。

主要功能包括:測試與瀏覽器的相容性——測試你的應用程式看是否能夠很好得工作在不同瀏覽器和作業系統之上。

測試系統功能——建立迴歸測試檢驗軟體功能和使用者需求。支援自動錄製動作和自動生成 .Net、Java、Perl等不同語言的測試指令碼(這裡主要是針對selenium ide)

selenium歷程

04年,誕生了Selenium Core,Selenium Core是基於瀏覽器並且採用JavaScript程式語言的測試工具,執行在瀏覽器的安全沙箱中,設計理念是將待測試產品、Selenium Core和測試指令碼均部署到同一臺伺服器上來完成自動化測試的工作。

05年,Selenium RC誕生,就是selenium1 ,這個時候,Selenium Core其實是Selenium RC的核心。

Selenium RC讓待測試產品、Selenium Core和測試指令碼三者分散在不同的伺服器上。(測試指令碼只關心將HTTP請求傳送到指定的URL上,selenium本身不需要關心HTTP請求由於什麼程式程式語言編寫而成)

Selenium RC包括兩部分:一個是Selenium RC Server,一個是提供各種程式語言的客戶端驅動來編寫測試指令碼

07年,Webdriver誕生,WebDriver的設計理念是將端到端測試與底層具體的測試工具分隔離,並採用設計模式Adapter介面卡來達到目標。WebDriver的API組織更多的是面向物件。

08/09年,selenium2誕生,selenium2其實是selenium rc和webdriver的合併,合併的根本原因是相互補充各自的缺點

09年,selenium3誕生,這個版本剔除了selenium rc , 主要由 selenium webdriver和selenium Grid組成, 我們日常使用的其實就是selenium webdriver,至於selenium grid是一個分散式實現自動化測試的工具

那麼今天我們就要說說selenium3(selenium webdriver)的工作原理,下面簡稱selenium(以上具體時間可能不太準確,我也是通過網路資料瞭解到的,拋磚引玉^-^)

selenium原理

我們使用Selenium實現自動化測試,主要需要3個東西

1.測試指令碼,可以是python,java編寫的指令碼程式(也可以叫做client端)

2.瀏覽器驅動, 這個驅動是根據不同的瀏覽器開發的,不同的瀏覽器使用不同的webdriver驅動程式且需要對應相應的瀏覽器版本,比如:geckodriver.exe(chrome)

3.瀏覽器,目前selenium支援市面上大多數瀏覽器,如:火狐,谷歌,IE等

selenium指令碼

先看一個簡單的程式碼

"""
------------------------------------
@Time : 2019/6/29 8:16
@Auth : linux超
@File : seleniumWebdriver.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
from selenium import webdriver


dr = webdriver.Chrome()  # 開啟瀏覽器

執行上述程式碼,我們會發現程式打開了Chrome瀏覽器(前提:你已經正確配置了chrome的驅動和對應版本)

那麼selenium是如何實現這個過程的呢?ok,我們今天就通過分析原始碼的方式來理解selenium的工作原理

原始碼分析

檢視weddriver原始碼(按住Ctrl鍵,滑鼠點選Chrome)

C:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.py

 1 class WebDriver(RemoteWebDriver):
 2     """
 3     Controls the ChromeDriver and allows you to drive the browser.
 4 
 5     You will need to download the ChromeDriver executable from
 6     http://chromedriver.storage.googleapis.com/index.html
 7     """
 8 
 9     def __init__(self, executable_path="chromedriver", port=0,
10                  options=None, service_args=None,
11                  desired_capabilities=None, service_log_path=None,
12                  chrome_options=None, keep_alive=True):
13         """
14         Creates a new instance of the chrome driver.
15 
16         Starts the service and then creates new instance of chrome driver.
17 
18         :Args:
19          - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
20          - port - port you would like the service to run, if left as 0, a free port will be found.
21          - options - this takes an instance of ChromeOptions
22          - service_args - List of args to pass to the driver service
23          - desired_capabilities - Dictionary object with non-browser specific
24            capabilities only, such as "proxy" or "loggingPref".
25          - service_log_path - Where to log information from the driver.
26          - chrome_options - Deprecated argument for options
27          - keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
28         """
29         if chrome_options:
30             warnings.warn('use options instead of chrome_options',
31                           DeprecationWarning, stacklevel=2)
32             options = chrome_options
33 
34         if options is None:
35             # desired_capabilities stays as passed in
36             if desired_capabilities is None:
37                 desired_capabilities = self.create_options().to_capabilities()
38         else:
39             if desired_capabilities is None:
40                 desired_capabilities = options.to_capabilities()
41             else:
42                 desired_capabilities.update(options.to_capabilities())
43 
44         self.service = Service(
45             executable_path,
46             port=port,
47             service_args=service_args,
48             log_path=service_log_path)
49         self.service.start()
50 
51         try:
52             RemoteWebDriver.__init__(
53                 self,
54                 command_executor=ChromeRemoteConnection(
55                     remote_server_addr=self.service.service_url,
56                     keep_alive=keep_alive),
57                 desired_capabilities=desired_capabilities)
58         except Exception:
59             self.quit()
60             raise
61         self._is_remote = False

通過原始碼中的44-49行發現,初始化了一個service物件,然後呼叫了start()方法,那麼我們繼續看下一49行的start()方法到底實現了什麼功能?

C:\Python36\Lib\site-packages\selenium\webdriver\common\service.py

 1  def start(self):
 2         """
 3         Starts the Service.
 4 
 5         :Exceptions:
 6          - WebDriverException : Raised either when it can't start the service
 7            or when it can't connect to the service
 8         """
 9         try:
10             cmd = [self.path]
11             cmd.extend(self.command_line_args())
12             self.process = subprocess.Popen(cmd, env=self.env,
13                                             close_fds=platform.system() != 'Windows',
14                                             stdout=self.log_file,
15                                             stderr=self.log_file,
16                                             stdin=PIPE)
17         except TypeError:
18             raise
19         except OSError as err:
20             if err.errno == errno.ENOENT:
21                 raise WebDriverException(
22                     "'%s' executable needs to be in PATH. %s" % (
23                         os.path.basename(self.path), self.start_error_message)
24                 )
25             elif err.errno == errno.EACCES:
26                 raise WebDriverException(
27                     "'%s' executable may have wrong permissions. %s" % (
28                         os.path.basename(self.path), self.start_error_message)
29                 )
30             else:
31                 raise
32         except Exception as e:
33             raise WebDriverException(
34                 "The executable %s needs to be available in the path. %s\n%s" %
35                 (os.path.basename(self.path), self.start_error_message, str(e)))
36         count = 0
37         while True:
38             self.assert_process_still_running()
39             if self.is_connectable():
40                 break
41             count += 1
42             time.sleep(1)
43             if count == 30:
44                 raise WebDriverException("Can not connect to the Service %s" % self.path)

我們發現9-16行其實就是執行了一個cmd命令,命令的作用就是啟動了chromedriver.exeChrome瀏覽器的驅動程式

這裡我們需要注意一點: 下載的瀏覽器驅動一定要配置到環境變數中,或者放到python的根目錄下,便於程式在執行驅動的時候查詢

這個過程和我們手動啟動瀏覽器驅動是一樣的效果,類似下面的結果

啟動驅動程式後,繫結埠號9515,且只允許本地訪問這個服務,其實我們可以檢視一下我們本地電腦工作管理員,確實開啟了一個服務程序程式

第一步工作我們已經知道了執行測試指令碼webdriver.Chrome()會自動執行chromedriver.exe驅動程式,然後開啟一個程序

如何開啟瀏覽器

我們繼續看原始碼 C:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.py 的51-57行程式碼,呼叫了父類RemoteWebDriver 的初始化方法,我們看這個方法做了什麼事?

C:\Python36\Lib\site-packages\selenium\webdriver\remote\webdriver.py

 1 class WebDriver(object):
 2     """
 3     Controls a browser by sending commands to a remote server.
 4     This server is expected to be running the WebDriver wire protocol
 5     as defined at
 6     https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
 7 
 8     :Attributes:
 9      - session_id - String ID of the browser session started and controlled by this WebDriver.
10      - capabilities - Dictionaty of effective capabilities of this browser session as returned
11          by the remote server. See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities
12      - command_executor - remote_connection.RemoteConnection object used to execute commands.
13      - error_handler - errorhandler.ErrorHandler object used to handle errors.
14     """
15 
16     _web_element_cls = WebElement
17 
18     def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
19                  desired_capabilities=None, browser_profile=None, proxy=None,
20                  keep_alive=False, file_detector=None, options=None):
21         """
22         Create a new driver that will issue commands using the wire protocol.
23 
24         :Args:
25          - command_executor - Either a string representing URL of the remote server or a custom
26              remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
27          - desired_capabilities - A dictionary of capabilities to request when
28              starting the browser session. Required parameter.
29          - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object.
30              Only used if Firefox is requested. Optional.
31          - proxy - A selenium.webdriver.common.proxy.Proxy object. The browser session will
32              be started with given proxy settings, if possible. Optional.
33          - keep_alive - Whether to configure remote_connection.RemoteConnection to use
34              HTTP keep-alive. Defaults to False.
35          - file_detector - Pass custom file detector object during instantiation. If None,
36              then default LocalFileDetector() will be used.
37          - options - instance of a driver options.Options class
38         """
39         capabilities = {}
40         if options is not None:
41             capabilities = options.to_capabilities()
42         if desired_capabilities is not None:
43             if not isinstance(desired_capabilities, dict):
44                 raise WebDriverException("Desired Capabilities must be a dictionary")
45             else:
46                 capabilities.update(desired_capabilities)
47         if proxy is not None:
48             warnings.warn("Please use FirefoxOptions to set proxy",
49                           DeprecationWarning, stacklevel=2)
50             proxy.add_to_capabilities(capabilities)
51         self.command_executor = command_executor
52         if type(self.command_executor) is bytes or isinstance(self.command_executor, str):
53             self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
54         self._is_remote = True
55         self.session_id = None
56         self.capabilities = {}
57         self.error_handler = ErrorHandler()
58         self.start_client()
59         if browser_profile is not None:
60             warnings.warn("Please use FirefoxOptions to set browser profile",
61                           DeprecationWarning, stacklevel=2)
62         self.start_session(capabilities, browser_profile)
63         self._switch_to = SwitchTo(self)
64         self._mobile = Mobile(self)
65         self.file_detector = file_detector or LocalFileDetector()

 這裡有一行最重要的程式碼,62行self.start_session(capabilities, browser_profile) 這個方法,繼續看一下這個方法的原始碼做了什麼工作

 1     def start_session(self, capabilities, browser_profile=None):
 2         """
 3         Creates a new session with the desired capabilities.
 4 
 5         :Args:
 6          - browser_name - The name of the browser to request.
 7          - version - Which browser version to request.
 8          - platform - Which platform to request the browser on.
 9          - javascript_enabled - Whether the new session should support JavaScript.
10          - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested.
11         """
12         if not isinstance(capabilities, dict):
13             raise InvalidArgumentException("Capabilities must be a dictionary")
14         if browser_profile:
15             if "moz:firefoxOptions" in capabilities:
16                 capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded
17             else:
18                 capabilities.update({'firefox_profile': browser_profile.encoded})
19         w3c_caps = _make_w3c_caps(capabilities)
20         parameters = {"capabilities": w3c_caps,
21                       "desiredCapabilities": capabilities}
22         response = self.execute(Command.NEW_SESSION, parameters)
23         if 'sessionId' not in response:
24             response = response['value']
25         self.session_id = response['sessionId']
26         self.capabilities = response.get('value')
27 
28         # if capabilities is none we are probably speaking to
29         # a W3C endpoint
30         if self.capabilities is None:
31             self.capabilities = response.get('capabilities')
32 
33         # Double check to see if we have a W3C Compliant browser
34         self.w3c = response.get('status') is None
35         self.command_executor.w3c = self.w3c

分析這部分原始碼可以發現22行是向地址localhost:9515/session傳送了一個post請求,引數是json格式的,然後返回特定的響應資訊給程式(這裡主要就是新建了一個sessionid),最終打開了瀏覽器

ok,開啟瀏覽器的操作完成了

如何執行對應操作

檢視C:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.py原始碼(第一個原始碼中的51-57行)

51         try:
52             RemoteWebDriver.__init__(
53                 self,
54                 command_executor=ChromeRemoteConnection(
55                     remote_server_addr=self.service.service_url,
56                     keep_alive=keep_alive),
57                 desired_capabilities=desired_capabilities)

點選ChromeRemoteConnection檢視一下原始碼

 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)
 8         self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app')
 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')

第7行訪問的是localhost:9515/session地址,第8-11行,定義了一些和我們使用的瀏覽器(chrome)特有的介面地址,我們再看一下父類RemoteConnection裡面原始碼

C:\Python36\Lib\site-packages\selenium\webdriver\remote\remote_connection.py:RemoteConnection

  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         }

這個類裡面定義了所有的selenium操作需要的介面地址(這些介面地址全部封裝在瀏覽器驅動程式中),那麼所有的瀏覽器操作就是通過訪問這些介面來實現的

其中 Command.GET: ('POST', '/session/$sessionId/url') 這個地址就是實現訪問一個網址的url ,我們先記錄一下後面有用

ok,所有的操作對應介面地址我們知道了,那麼又怎樣執行這些介面來達到在瀏覽器上實現各種操作呢?繼續看緊接著介面地址定義下面的原始碼

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

可以看到主要是通過execute方法呼叫_request方法通過urilib3標準庫向伺服器傳送對應操作請求地址,進而實現了瀏覽器各種操作

有人會問開啟瀏覽器和操作瀏覽器實現各種動作是怎麼關聯的呢?

其實,開啟瀏覽器也是傳送請求,請求會返回一個sessionid,後面操作的各種介面地址,你也會發現介面地址中存在一個變數$sessionid,那麼不難猜測開啟瀏覽器和操作瀏覽器就是用過sessionid關聯到一起,達到在同一個瀏覽器中做操作

第二步在瀏覽其上實現各種操作原理也完成了

模擬selenium

現在我們可以通過下面的一段程式碼檢視一下開啟瀏覽器和訪問我的部落格首頁的請求引數是什麼樣子的

"""
------------------------------------
@Time : 2019/6/29 9:16
@Auth : linux超
@File : seleniumWebdriver.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
from selenium import webdriver
import logging


logging.basicConfig(level=logging.DEBUG)  # 列印原始碼中的日誌
dr = webdriver.Chrome() # 開啟瀏覽器
driver.get("https://www.cnblogs.com/linuxchao/") # 訪問我的部落格首頁

輸出日誌資訊

DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:55695/session 
{"capabilities": {"firstMatch": [{}], "alwaysMatch": {"browserName": "chrome", "platformName": "any", "goog:chromeOptions":
{"extensions": [], "args": []}}}, "desiredCapabilities": {"browserName": "chrome", "version": "", "platform": "ANY",
"goog:chromeOptions": {"extensions": [], "args": []}}} DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 127.0.0.1 DEBUG:urllib3.connectionpool:http://127.0.0.1:55695 "POST /session HTTP/1.1" 200 830 DEBUG:selenium.webdriver.remote.remote_connection:Finished Request DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:51006/session/09d52393b7dfcb45b8bb9101885ce206/url
{"url": "https://www.cnblogs.com/linuxchao/", "sessionId": "09d52393b7dfcb45b8bb9101885ce206"}
DEBUG:urllib3.connectionpool:http://127.0.0.1:51006 "POST /session/09d52393b7dfcb45b8bb9101885ce206/url HTTP/1.1" 200 72
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request Process finished with exit code 0

通過執行結果就很明顯明白selenium執行的過程了,程式告訴RemoteWebDriver開啟一個瀏覽器(傳送post請求,帶上請求引數),然後再向remote server傳送執行瀏覽器動作的請求

那麼為了更加深入理解selenium實現自動化測試的過程,我們可以自己編寫程式模擬一下開啟瀏覽器然後控制瀏覽器訪問我的部落格地址的操作過程

首先我們需要保持瀏覽器的驅動程式開啟狀態,然後編寫如下程式碼並執行

"""
------------------------------------
@Time : 2019/6/28 8:52
@Auth : linux超
@File : test.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
import requests


# 請求地址(開啟瀏覽器)
driver_url = 'http://localhost:9515/session'
# 開啟瀏覽器的請求引數
driver_value = {"capabilities":
                    {"firstMatch": [{}],
                     "alwaysMatch":
                         {"browserName":
                              "chrome",
                          "platformName": "any",
                          "goog:chromeOptions":
                              {"extensions": [], "args": []}}},
                "desiredCapabilities":
                    {"browserName":
                         "chrome",
                     "version": "",
                     "platform": "ANY",
                     "goog:chromeOptions": {"extensions": [],
                                            "args": []}}}
# 傳送求清
response_session = requests.post(driver_url, json = driver_value)
print(response_session.json())
# 訪問我的部落格的請求地址 (這個地址是我們上面記錄的地址)
url = 'http://localhost:9515/session/'+response_session.json()['sessionId']+'/url'
# 訪問我的部落格的請求引數
value = {"url": "https://www.cnblogs.com/linuxchao/", "sessionId": response_session.json()['sessionId']}
response_blog = requests.post(url = url,json = value)
print(response_blog.json())

執行結果

{'sessionId': '25144efef880dcce53e4e6f60c342e9d', 'status': 0, 'value': 
{'acceptInsecureCerts': False, 'acceptSslCerts': False, 'applicationCacheEnabled': False,
'browserConnectionEnabled': False, 'browserName': 'chrome', 'chrome':
{'chromedriverVersion': '2.39.562718 (9a2698cba08cf5a471a29d30c8b3e12becabb0e9)',
'userDataDir': 'C:\\Users\\v-xug\\AppData\\Local\\Temp\\scoped_dir9944_25238'},
'cssSelectorsEnabled': True, 'databaseEnabled': False, 'handlesAlerts': True,
'hasTouchScreen': False, 'javascriptEnabled': True, 'locationContextEnabled': True,
'mobileEmulationEnabled': False, 'nativeEvents': True, 'networkConnectionEnabled': False,
'pageLoadStrategy': 'normal', 'platform': 'Windows NT', 'rotatable': False, 'setWindowRect': True,
'takesHeapSnapshot': True, 'takesScreenshot': True, 'unexpectedAlertBehaviour': '', 'version': '75.0.3770.100', 'webStorageEnabled': True}} {'sessionId': '25144efef880dcce53e4e6f60c342e9d', 'status': 0, 'value': None} Process finished with exit code 0

上面的返回資訊中最重要的資訊是'sessionId': '25144efef880dcce53e4e6f60c342e9d',從程式碼中你也可以看到訪問我的部落格地址的url是使用這個引數拼接的,因為開啟瀏覽器後,後面所有的操作都是基於這個sessionid的

你還會看到Chrome瀏覽器被開啟,且打開了我的部落格地址https://www.cnblogs.com/linuxchao/,這就是selenium原理的一個過程了

最後

前面的程式碼你看不懂,也沒關係,我們再來敘述一下selenium工作的過程

1.selenium client(python等語言編寫的自動化測試指令碼)初始化一個service服務,通過Webdriver啟動瀏覽器驅動程式chromedriver.exe

2.通過RemoteWebDriver向瀏覽器驅動程式傳送HTTP請求,瀏覽器驅動程式解析請求,開啟瀏覽器,並獲得sessionid,如果再次對瀏覽器操作需攜帶此id

3.開啟瀏覽器,繫結特定的埠,把啟動後的瀏覽器作為webdriver的remote server

3.開啟瀏覽器後,所有的selenium的操作(訪問地址,查詢元素等)均通過RemoteConnection連結到remote server,然後使用execute方法呼叫_request方法通過urlib3向remote server傳送請求

4.瀏覽器通過請求的內容執行對應動作

5.瀏覽器再把執行的動作結果通過瀏覽器驅動程式返回給測試指令碼

這篇文章我寫了幾乎一天的時間,過程也沒那麼好理解,可能我自己理解的也不是那麼準確,如果你覺得哪裡有問題一定聯絡我修改,我不想對看到這篇文章的人產生錯誤的理解,那我就罪大了。

雖然用了很久的selenium 但是要細說他的工作原理還是感覺有點詞窮,可能就是理解的不透徹吧!

selenium簡介

Selenium是一個用於Web應用程式自動化測試工具。Selenium測試直接執行在瀏覽器中,就像真正的使用者在操作一樣。支援的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。

主要功能包括:測試與瀏覽器的相容性——測試你的應用程式看是否能夠很好得工作在不同瀏覽器和作業系統之上。

測試系統功能——建立迴歸測試檢驗軟體功能和使用者需求。支援自動錄製動作和自動生成 .Net、Java、Perl等不同語言的測試指令碼(這裡主要是針對selenium ide)

selenium歷程

04年,誕生了Selenium Core,Selenium Core是基於瀏覽器並且採用JavaScript程式語言的測試工具,執行在瀏覽器的安全沙箱中,設計理念是將待測試產品、Selenium Core和測試指令碼均部署到同一臺伺服器上來完成自動化測試的工作。

05年,Selenium RC誕生,就是selenium1 ,這個時候,Selenium Core其實是Selenium RC的核心。

Selenium RC讓待測試產品、Selenium Core和測試指令碼三者分散在不同的伺服器上。(測試指令碼只關心將HTTP請求傳送到指定的URL上,selenium本身不需要關心HTTP請求由於什麼程式程式語言編寫而成)

Selenium RC包括兩部分:一個是Selenium RC Server,一個是提供各種程式語言的客戶端驅動來編寫測試指令碼

07年,Webdriver誕生,WebDriver的設計理念是將端到端測試與底層具體的測試工具分隔離,並採用設計模式Adapter介面卡來達到目標。WebDriver的API組織更多的是面向物件。

08/09年,selenium2誕生,selenium2其實是selenium rc和webdriver的合併,合併的根本原因是相互補充各自的缺點

09年,selenium3誕生,這個版本剔除了selenium rc , 主要由 selenium webdriver和selenium Grid組成, 我們日常使用的其實就是selenium webdriver,至於selenium grid是一個分散式實現自動化測試的工具

那麼今天我們就要說說selenium3(selenium webdriver)的工作原理,下面簡稱selenium(以上具體時間可能不太準確,我也是通過網路資料瞭解到的,拋磚引玉^-^)

selenium原理

我們使用Selenium實現自動化測試,主要需要3個東西

1.測試指令碼,可以是python,java編寫的指令碼程式(也可以叫做client端)

2.瀏覽器驅動, 這個驅動是根據不同的瀏覽器開發的,不同的瀏覽器使用不同的webdriver驅動程式且需要對應相應的瀏覽器版本,比如:geckodriver.exe(chrome)

3.瀏覽器,目前selenium支援市面上大多數瀏覽器,如:火狐,谷歌,IE等

selenium指令碼

先看一個簡單的程式碼

"""
------------------------------------
@Time : 2019/6/29 8:16
@Auth : linux超
@File : seleniumWebdriver.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
from selenium import webdriver


dr = webdriver.Chrome()  # 開啟瀏覽器

執行上述程式碼,我們會發現程式打開了Chrome瀏覽器(前提:你已經正確配置了chrome的驅動和對應版本)

那麼selenium是如何實現這個過程的呢?ok,我們今天就通過分析原始碼的方式來理解selenium的工作原理

原始碼分析

檢視weddriver原始碼(按住Ctrl鍵,滑鼠點選Chrome)

C:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.py

 1 class WebDriver(RemoteWebDriver):
 2     """
 3     Controls the ChromeDriver and allows you to drive the browser.
 4 
 5     You will need to download the ChromeDriver executable from
 6     http://chromedriver.storage.googleapis.com/index.html
 7     """
 8 
 9     def __init__(self, executable_path="chromedriver", port=0,
10                  options=None, service_args=None,
11                  desired_capabilities=None, service_log_path=None,
12                  chrome_options=None, keep_alive=True):
13         """
14         Creates a new instance of the chrome driver.
15 
16         Starts the service and then creates new instance of chrome driver.
17 
18         :Args:
19          - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
20          - port - port you would like the service to run, if left as 0, a free port will be found.
21          - options - this takes an instance of ChromeOptions
22          - service_args - List of args to pass to the driver service
23          - desired_capabilities - Dictionary object with non-browser specific
24            capabilities only, such as "proxy" or "loggingPref".
25          - service_log_path - Where to log information from the driver.
26          - chrome_options - Deprecated argument for options
27          - keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
28         """
29         if chrome_options:
30             warnings.warn('use options instead of chrome_options',
31                           DeprecationWarning, stacklevel=2)
32             options = chrome_options
33 
34         if options is None:
35             # desired_capabilities stays as passed in
36             if desired_capabilities is None:
37                 desired_capabilities = self.create_options().to_capabilities()
38         else:
39             if desired_capabilities is None:
40                 desired_capabilities = options.to_capabilities()
41             else:
42                 desired_capabilities.update(options.to_capabilities())
43 
44         self.service = Service(
45             executable_path,
46             port=port,
47             service_args=service_args,
48             log_path=service_log_path)
49         self.service.start()
50 
51         try:
52             RemoteWebDriver.__init__(
53                 self,
54                 command_executor=ChromeRemoteConnection(
55                     remote_server_addr=self.service.service_url,
56                     keep_alive=keep_alive),
57                 desired_capabilities=desired_capabilities)
58         except Exception:
59             self.quit()
60             raise
61         self._is_remote = False

通過原始碼中的44-49行發現,初始化了一個service物件,然後呼叫了start()方法,那麼我們繼續看下一49行的start()方法到底實現了什麼功能?

C:\Python36\Lib\site-packages\selenium\webdriver\common\service.py

 1  def start(self):
 2         """
 3         Starts the Service.
 4 
 5         :Exceptions:
 6          - WebDriverException : Raised either when it can't start the service
 7            or when it can't connect to the service
 8         """
 9         try:
10             cmd = [self.path]
11             cmd.extend(self.command_line_args())
12             self.process = subprocess.Popen(cmd, env=self.env,
13                                             close_fds=platform.system() != 'Windows',
14                                             stdout=self.log_file,
15                                             stderr=self.log_file,
16                                             stdin=PIPE)
17         except TypeError:
18             raise
19         except OSError as err:
20             if err.errno == errno.ENOENT:
21                 raise WebDriverException(
22                     "'%s' executable needs to be in PATH. %s" % (
23                         os.path.basename(self.path), self.start_error_message)
24                 )
25             elif err.errno == errno.EACCES:
26                 raise WebDriverException(
27                     "'%s' executable may have wrong permissions. %s" % (
28                         os.path.basename(self.path), self.start_error_message)
29                 )
30             else:
31                 raise
32         except Exception as e:
33             raise WebDriverException(
34                 "The executable %s needs to be available in the path. %s\n%s" %
35                 (os.path.basename(self.path), self.start_error_message, str(e)))
36         count = 0
37         while True:
38             self.assert_process_still_running()
39             if self.is_connectable():
40                 break
41             count += 1
42             time.sleep(1)
43             if count == 30:
44                 raise WebDriverException("Can not connect to the Service %s" % self.path)

我們發現9-16行其實就是執行了一個cmd命令,命令的作用就是啟動了chromedriver.exeChrome瀏覽器的驅動程式

這裡我們需要注意一點: 下載的瀏覽器驅動一定要配置到環境變數中,或者放到python的根目錄下,便於程式在執行驅動的時候查詢

這個過程和我們手動啟動瀏覽器驅動是一樣的效果,類似下面的結果

啟動驅動程式後,繫結埠號9515,且只允許本地訪問這個服務,其實我們可以檢視一下我們本地電腦工作管理員,確實開啟了一個服務程序程式

第一步工作我們已經知道了執行測試指令碼webdriver.Chrome()會自動執行chromedriver.exe驅動程式,然後開啟一個程序

如何開啟瀏覽器

我們繼續看原始碼 C:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.py 的51-57行程式碼,呼叫了父類RemoteWebDriver 的初始化方法,我們看這個方法做了什麼事?

C:\Python36\Lib\site-packages\selenium\webdriver\remote\webdriver.py

 1 class WebDriver(object):
 2     """
 3     Controls a browser by sending commands to a remote server.
 4     This server is expected to be running the WebDriver wire protocol
 5     as defined at
 6     https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
 7 
 8     :Attributes:
 9      - session_id - String ID of the browser session started and controlled by this WebDriver.
10      - capabilities - Dictionaty of effective capabilities of this browser session as returned
11          by the remote server. See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities
12      - command_executor - remote_connection.RemoteConnection object used to execute commands.
13      - error_handler - errorhandler.ErrorHandler object used to handle errors.
14     """
15 
16     _web_element_cls = WebElement
17 
18     def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
19                  desired_capabilities=None, browser_profile=None, proxy=None,
20                  keep_alive=False, file_detector=None, options=None):
21         """
22         Create a new driver that will issue commands using the wire protocol.
23 
24         :Args:
25          - command_executor - Either a string representing URL of the remote server or a custom
26              remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
27          - desired_capabilities - A dictionary of capabilities to request when
28              starting the browser session. Required parameter.
29          - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object.
30              Only used if Firefox is requested. Optional.
31          - proxy - A selenium.webdriver.common.proxy.Proxy object. The browser session will
32              be started with given proxy settings, if possible. Optional.
33          - keep_alive - Whether to configure remote_connection.RemoteConnection to use
34              HTTP keep-alive. Defaults to False.
35          - file_detector - Pass custom file detector object during instantiation. If None,
36              then default LocalFileDetector() will be used.
37          - options - instance of a driver options.Options class
38         """
39         capabilities = {}
40         if options is not None:
41             capabilities = options.to_capabilities()
42         if desired_capabilities is not None:
43             if not isinstance(desired_capabilities, dict):
44                 raise WebDriverException("Desired Capabilities must be a dictionary")
45             else:
46                 capabilities.update(desired_capabilities)
47         if proxy is not None:
48             warnings.warn("Please use FirefoxOptions to set proxy",
49                           DeprecationWarning, stacklevel=2)
50             proxy.add_to_capabilities(capabilities)
51         self.command_executor = command_executor
52         if type(self.command_executor) is bytes or isinstance(self.command_executor, str):
53             self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
54         self._is_remote = True
55         self.session_id = None
56         self.capabilities = {}
57         self.error_handler = ErrorHandler()
58         self.start_client()
59         if browser_profile is not None:
60             warnings.warn("Please use FirefoxOptions to set browser profile",
61                           DeprecationWarning, stacklevel=2)
62         self.start_session(capabilities, browser_profile)
63         self._switch_to = SwitchTo(self)
64         self._mobile = Mobile(self)
65         self.file_detector = file_detector or LocalFileDetector()

 這裡有一行最重要的程式碼,62行self.start_session(capabilities, browser_profile) 這個方法,繼續看一下這個方法的原始碼做了什麼工作

 1     def start_session(self, capabilities, browser_profile=None):
 2         """
 3         Creates a new session with the desired capabilities.
 4 
 5         :Args:
 6          - browser_name - The name of the browser to request.
 7          - version - Which browser version to request.
 8          - platform - Which platform to request the browser on.
 9          - javascript_enabled - Whether the new session should support JavaScript.
10          - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested.
11         """
12         if not isinstance(capabilities, dict):
13             raise InvalidArgumentException("Capabilities must be a dictionary")
14         if browser_profile:
15             if "moz:firefoxOptions" in capabilities:
16                 capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded
17             else:
18                 capabilities.update({'firefox_profile': browser_profile.encoded})
19         w3c_caps = _make_w3c_caps(capabilities)
20         parameters = {"capabilities": w3c_caps,
21                       "desiredCapabilities": capabilities}
22         response = self.execute(Command.NEW_SESSION, parameters)
23         if 'sessionId' not in response:
24             response = response['value']
25         self.session_id = response['sessionId']
26         self.capabilities = response.get('value')
27 
28         # if capabilities is none we are probably speaking to
29         # a W3C endpoint
30         if self.capabilities is None:
31             self.capabilities = response.get('capabilities')
32 
33         # Double check to see if we have a W3C Compliant browser
34         self.w3c = response.get('status') is None
35         self.command_executor.w3c = self.w3c

分析這部分原始碼可以發現22行是向地址localhost:9515/session傳送了一個post請求,引數是json格式的,然後返回特定的響應資訊給程式(這裡主要就是新建了一個sessionid),最終打開了瀏覽器

ok,開啟瀏覽器的操作完成了

如何執行對應操作

檢視C:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.py原始碼(第一個原始碼中的51-57行)

51         try:
52             RemoteWebDriver.__init__(
53                 self,
54                 command_executor=ChromeRemoteConnection(
55                     remote_server_addr=self.service.service_url,
56                     keep_alive=keep_alive),
57                 desired_capabilities=desired_capabilities)

點選ChromeRemoteConnection檢視一下原始碼

 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)
 8         self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app')
 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')

第7行訪問的是localhost:9515/session地址,第8-11行,定義了一些和我們使用的瀏覽器(chrome)特有的介面地址,我們再看一下父類RemoteConnection裡面原始碼

C:\Python36\Lib\site-packages\selenium\webdriver\remote\remote_connection.py:RemoteConnection

  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         }

這個類裡面定義了所有的selenium操作需要的介面地址(這些介面地址全部封裝在瀏覽器驅動程式中),那麼所有的瀏覽器操作就是通過訪問這些介面來實現的

其中 Command.GET: ('POST', '/session/$sessionId/url') 這個地址就是實現訪問一個網址的url ,我們先記錄一下後面有用

ok,所有的操作對應介面地址我們知道了,那麼又怎樣執行這些介面來達到在瀏覽器上實現各種操作呢?繼續看緊接著介面地址定義下面的原始碼

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

可以看到主要是通過execute方法呼叫_request方法通過urilib3標準庫向伺服器傳送對應操作請求地址,進而實現了瀏覽器各種操作

有人會問開啟瀏覽器和操作瀏覽器實現各種動作是怎麼關聯的呢?

其實,開啟瀏覽器也是傳送請求,請求會返回一個sessionid,後面操作的各種介面地址,你也會發現介面地址中存在一個變數$sessionid,那麼不難猜測開啟瀏覽器和操作瀏覽器就是用過sessionid關聯到一起,達到在同一個瀏覽器中做操作

第二步在瀏覽其上實現各種操作原理也完成了

模擬selenium

現在我們可以通過下面的一段程式碼檢視一下開啟瀏覽器和訪問我的部落格首頁的請求引數是什麼樣子的

"""
------------------------------------
@Time : 2019/6/29 9:16
@Auth : linux超
@File : seleniumWebdriver.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
from selenium import webdriver
import logging


logging.basicConfig(level=logging.DEBUG)  # 列印原始碼中的日誌
dr = webdriver.Chrome() # 開啟瀏覽器
driver.get("https://www.cnblogs.com/linuxchao/") # 訪問我的部落格首頁

輸出日誌資訊

DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:55695/session 
{"capabilities": {"firstMatch": [{}], "alwaysMatch": {"browserName": "chrome", "platformName": "any", "goog:chromeOptions":
{"extensions": [], "args": []}}}, "desiredCapabilities": {"browserName": "chrome", "version": "", "platform": "ANY",
"goog:chromeOptions": {"extensions": [], "args": []}}} DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 127.0.0.1 DEBUG:urllib3.connectionpool:http://127.0.0.1:55695 "POST /session HTTP/1.1" 200 830 DEBUG:selenium.webdriver.remote.remote_connection:Finished Request DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:51006/session/09d52393b7dfcb45b8bb9101885ce206/url
{"url": "https://www.cnblogs.com/linuxchao/", "sessionId": "09d52393b7dfcb45b8bb9101885ce206"}
DEBUG:urllib3.connectionpool:http://127.0.0.1:51006 "POST /session/09d52393b7dfcb45b8bb9101885ce206/url HTTP/1.1" 200 72
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request Process finished with exit code 0

通過執行結果就很明顯明白selenium執行的過程了,程式告訴RemoteWebDriver開啟一個瀏覽器(傳送post請求,帶上請求引數),然後再向remote server傳送執行瀏覽器動作的請求

那麼為了更加深入理解selenium實現自動化測試的過程,我們可以自己編寫程式模擬一下開啟瀏覽器然後控制瀏覽器訪問我的部落格地址的操作過程

首先我們需要保持瀏覽器的驅動程式開啟狀態,然後編寫如下程式碼並執行

"""
------------------------------------
@Time : 2019/6/28 8:52
@Auth : linux超
@File : test.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
import requests


# 請求地址(開啟瀏覽器)
driver_url = 'http://localhost:9515/session'
# 開啟瀏覽器的請求引數
driver_value = {"capabilities":
                    {"firstMatch": [{}],
                     "alwaysMatch":
                         {"browserName":
                              "chrome",
                          "platformName": "any",
                          "goog:chromeOptions":
                              {"extensions": [], "args": []}}},
                "desiredCapabilities":
                    {"browserName":
                         "chrome",
                     "version": "",
                     "platform": "ANY",
                     "goog:chromeOptions": {"extensions": [],
                                            "args": []}}}
# 傳送求清
response_session = requests.post(driver_url, json = driver_value)
print(response_session.json())
# 訪問我的部落格的請求地址 (這個地址是我們上面記錄的地址)
url = 'http://localhost:9515/session/'+response_session.json()['sessionId']+'/url'
# 訪問我的部落格的請求引數
value = {"url": "https://www.cnblogs.com/linuxchao/", "sessionId": response_session.json()['sessionId']}
response_blog = requests.post(url = url,json = value)
print(response_blog.json())

執行結果

{'sessionId': '25144efef880dcce53e4e6f60c342e9d', 'status': 0, 'value': 
{'acceptInsecureCerts': False, 'acceptSslCerts': False, 'applicationCacheEnabled': False,
'browserConnectionEnabled': False, 'browserName': 'chrome', 'chrome':
{'chromedriverVersion': '2.39.562718 (9a2698cba08cf5a471a29d30c8b3e12becabb0e9)',
'userDataDir': 'C:\\Users\\v-xug\\AppData\\Local\\Temp\\scoped_dir9944_25238'},
'cssSelectorsEnabled': True, 'databaseEnabled': False, 'handlesAlerts': True,
'hasTouchScreen': False, 'javascriptEnabled': True, 'locationContextEnabled': True,
'mobileEmulationEnabled': False, 'nativeEvents': True, 'networkConnectionEnabled': False,
'pageLoadStrategy': 'normal', 'platform': 'Windows NT', 'rotatable': False, 'setWindowRect': True,
'takesHeapSnapshot': True, 'takesScreenshot': True, 'unexpectedAlertBehaviour': '', 'version': '75.0.3770.100', 'webStorageEnabled': True}} {'sessionId': '25144efef880dcce53e4e6f60c342e9d', 'status': 0, 'value': None} Process finished with exit code 0

上面的返回資訊中最重要的資訊是'sessionId': '25144efef880dcce53e4e6f60c342e9d',從程式碼中你也可以看到訪問我的部落格地址的url是使用這個引數拼接的,因為開啟瀏覽器後,後面所有的操作都是基於這個sessionid的

你還會看到Chrome瀏覽器被開啟,且打開了我的部落格地址https://www.cnblogs.com/linuxchao/,這就是selenium原理的一個過程了

最後

前面的程式碼你看不懂,也沒關係,我們再來敘述一下selenium工作的過程

1.selenium client(python等語言編寫的自動化測試指令碼)初始化一個service服務,通過Webdriver啟動瀏覽器驅動程式chromedriver.exe

2.通過RemoteWebDriver向瀏覽器驅動程式傳送HTTP請求,瀏覽器驅動程式解析請求,開啟瀏覽器,並獲得sessionid,如果再次對瀏覽器操作需攜帶此id

3.開啟瀏覽器,繫結特定的埠,把啟動後的瀏覽器作為webdriver的remote server

3.開啟瀏覽器後,所有的selenium的操作(訪問地址,查詢元素等)均通過RemoteConnection連結到remote server,然後使用execute方法呼叫_request方法通過urlib3向remote server傳送請求

4.瀏覽器通過請求的內容執行對應動作

5.瀏覽器再把執行的動作結果通過瀏覽器驅動程式返回給測試指令碼

這篇文章我寫了幾乎一天的時間,過程也沒那麼好理解,可能我自己理解的也不是那麼準確,如果你覺得哪裡有問題一定聯絡我修改,我不想對看到這篇文章的人產生錯誤的理解,那我就罪大了。

雖然用了很久的selenium 但是要細說他的工作原理還是感覺有點詞窮,可能就是理解的不透徹吧!