1. 程式人生 > >通過源碼看原理之 selenium

通過源碼看原理之 selenium

tps 初學 med 獲取 rest red 類對象 fin 進行


# selenium的歷史
1. selenium1.x:這個時候的selenium,使用的是JavaScript註入技術與瀏覽器打交道,需要Selenium RC啟動一個Server,將操作Web元素的API調用轉化為一段段Javascript,在Selenium內核啟動瀏覽器之後註入這段Javascript。Javascript可以獲取並調用DOM的任何元素,自如的進行操作。由此才實現了Selenium的目的:自動化Web操作。這種Javascript註入技術的缺點是速度不理想,而且穩定性大大依賴於Selenium內核對API翻譯成的Javascript質量高低。
2. selenium2.x:相比於selenium1.x,2.x版本整合了webdriver以及原版selenium,兩個項目合二為一,雖然名字還叫selenium,但也可以叫Webdriver。這個版本的selenium是利用瀏覽器原生的API,封裝成一套更加面向對象的Selenium WebDriver API,直接操作瀏覽器頁面裏的元素,甚至操作瀏覽器本身(截屏,窗口大小,啟動,關閉,安裝插件,配置證書之類的)。由於使用的是瀏覽器原生的API,速度大大提高,而且調用的穩定性交給了瀏覽器廠商本身,顯然是更加科學。然而帶來的一些副作用就是,不同的瀏覽器廠商,對Web元素的操作和呈現多少會有一些差異,這就直接導致了Selenium WebDriver要分瀏覽器廠商不同,而提供不同的實現。

**發展史請看下圖:**
![Alt](https://testerhome.com/uploads/photo/2017/548944bd-b5f6-4243-b74b-d2cd8dac7412.png!large)

# 以下進入正題
## 結構
要通過selenium實現自動化測試,最最主要是需要三種東西。
- 測試需要用的代碼
- webdriver
- 瀏覽器
今天想要分享的也是這三者關系。

### 代碼
selenium支持多種語言(java/c#/python/ruby)。測試工程師通過編程語言,調用瀏覽器對應API實現需要的功能。
### webdriver
webdriver,就像是一個媒介,代碼驅動webdriver。上文提過,不同瀏覽器有不同的webdriver,例如火狐的FirefoxDriver,谷歌的 ChromeDriver.

### 瀏覽器

不同的瀏覽器對應不同的webdriver。

![](/uploads/photo/2019/6fe3febe-fb16-4417-9d54-921a703e15d6.png!large)

從上圖,測試代碼輸入操作給webdriver,webdriver再去控制瀏覽器,最終達到的效果就是代碼實現對瀏覽器的操作。

## 代碼與webdriver的交互
以下python為例

```python
from selenium import webdriver
driver = webdriver.Chrome()
```

這裏driver是webdriver.Chrome()的對象,我們查看webdriver.Chrome()的源碼,發現本質是

**from .chrome.webdriver import WebDriver as Chrome** 從目錄名可知這來自chrome的webdriver,再次對這個**WebDriver**溯源,發現它是繼承了一個**RemoteWebDriver**類,註釋的含義是:控制ChromeDriver並允許驅動瀏覽器。

![](/uploads/photo/2019/71df64b3-7bd5-4b95-a857-a3e940695839.png!large)

再次對繼承的**RemoteWebDriver**類溯源,發現其繼承了**selenium.webdriver.remote.webdriver.WebDriver**

![](/uploads/photo/2019/6816dc54-8320-40b3-8ae7-8dc40566ebcf.png!large)
註釋的含義是:通過向遠程服務器發送命令來控制瀏覽器。 該服務器應該運行WebDriver有線協議。這裏先停一下,等會我們會再回來繼續了解這個類。

以python為例,我們在在selenium庫中,通過ID獲取界面元素的方法是這樣的:
```python
ele = driver.find_element_by_id(‘id‘)
```
以上同方法,對代碼溯源,**find_elements_by_id**是**selenium.webdriver.remote.webdriver.WebDriver**類的實例方法。在代碼中,我們直接使用的其實不是**selenium.webdriver.remote.webdriver.WebDriver**這個類,而是針對各個瀏覽器的webdriver類,例如webdriver.Chrome()。所以說在測試代碼中執行各種瀏覽器操作的方法其實都是**selenium.webdriver.remote.webdriver.WebDriver**類的實例方法。接下來我們再深入**selenium.webdriver.remote.webdriver.WebDriver**類來看看具體是如何實現例如**find_element_by_id()**的實例方法的。

![](/uploads/photo/2019/cd6a3798-21dd-4c6e-a6f5-ad45abf92b14.png!large)

這個find_element方法最後調用了一個excute方法,我們再來看看這個excute方法:
```python
def execute(self, driver_command, params=None):
"""
Sends a command to be executed by a command.CommandExecutor.
發送一個命令由command.CommandExecutor執行。

:Args:
- driver_command: The name of the command to execute as a string.
- params: A dictionary of named parameters to send with the command.

:Returns:
The command‘s JSON response loaded into a dictionary object.
"""
if self.session_id is not None:
if not params:
params = {‘sessionId‘: self.session_id}
elif ‘sessionId‘ not in params:
params[‘sessionId‘] = self.session_id

params = self._wrap_value(params)
response = self.command_executor.execute(driver_command, params)
if response:
self.error_handler.check_response(response)
response[‘value‘] = self._unwrap_value(
response.get(‘value‘, None))
return response
# If the server doesn‘t send a response, assume the command was
# a success
return {‘success‘: 0, ‘value‘: None, ‘sessionId‘: self.session_id}
```
正如註釋中提到的一樣,其中的關鍵在於
```python
response = self.command_executor.execute(driver_command, params)
```
一個名為**command_executor**的對象執行了**execute**方法。
名為**command_executor**的對象是**RemoteConnection**類的對象,並且這個對象是在新建**selenium.webdriver.remote.webdriver.WebDriver**類對象的時候就完成賦值的**self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)**。
結合**selenium.webdriver.remote.webdriver.WebDriver**類的類註釋來看:**WebDriver**類的功能是通過給一個**remote server**發送指令來控制瀏覽器。而這個**remote server**是一個運行**WebDriver wire protocol**的**server**。而**RemoteConnection**類就是負責與**Remote WebDriver server**的連接的類。
可以註意到有這麽一個新建WebDriver類的對象時候的參數command_executor,默認值=‘http://127.0.0.1:4444/wd/hub‘。這個值表示的是訪問remote server的URL。因此這個值作為了**RemoteConnection**類的構造方法的參數,因為要連接**remote server**,URL是必須的。
現在再來看**RemoteConnection**類的實例方法**execute**。
```python
def execute(self, command, params):
"""
Send a command to the remote server.

Any path subtitutions required for the URL mapped to the command should be
included in the command parameters.

:Args:
- command - A string specifying the command to execute.
- params - A dictionary of named parameters to send with the command as
its JSON payload.
"""
command_info = self._commands[command]
assert command_info is not None, ‘Unrecognised command %s‘ % command
data = utils.dump_json(params)
path = string.Template(command_info[1]).substitute(params)
url = ‘%s%s‘ % (self._url, path)
return self._request(command_info[0], url, body=data)
```
這個方法有兩個參數
- command
- params

**command**表示期望執行的指令的名字。打開**self._commands**這個dict,查看Command.FIND_ELEMENT的value.
指令的URL部分包含了幾個組成部分:
- HTTP請求方法。WebDriver wire protocol中定義的指令是符合RESTful規範的,通過不同請求方法對應不同的指令操作。
- sessionId. sessionId表示了remote server和瀏覽器的一個會話,指令通過這個會話變成對於瀏覽器的一個操作。
- element 這一部分用來表示具體的指令。

而**selenium.webdriver.remote.command.Command**類裏的常量指令又在各個具體的類似**find_elements**的實例方法中作為execute方法的參數來使用,這樣就實現了**selenium.webdriver.remote.webdriver.WebDriver**類中實現各種操作的實例方法與WebDriver wire protocol中定義的指令的一一對應。
而**selenium.webdriver.remote.webelement.WebElement**中各種在WebElement上的操作也是用類似的原理實現的。

實例方法**execute**的另一個參數params則是用來保存指令的參數的,這個參數將轉化為JSON格式,作為HTTP請求的**body**發送到**remote server**。
**remote server**在執行完對瀏覽器的操作後得到的數據將作為**HTTP Response**的**body**返回給測試代碼,測試代碼經過解析處理後得到想要的數據。

## 總結
![](/uploads/photo/2019/ef6dc9df-864a-4ab3-b718-c9ad573bdc1b.png!large)

**初學者文中難免可能有疏漏之處,希望各位大佬指正**

通過源碼看原理之 selenium