源碼解讀:webdriver client的原理 (面試自動化:如果你認為知道18種定位方式就算會自動化,那就太low了)


前言

又到年底了,群里很多朋友說要開始備戰2020金三銀四,其實,我建議是,如果你不是技術大牛,就不要去湊熱鬧。

其實,現在(11,12月份)就是最佳換工作的時候,因為很多人想等着拿了年終再走,雖然招聘的公司不多,但是找工作的更少,所以,可以去試試有沒有好機會。

很多人說要去面試自動化,問我有沒有面試題,而好點的公司都喜歡問底層原理。

想必用過selenium的測試朋友都知道18種定位方式吧?

  

示例腳本准備

如果你去面試自動化,面試官估計會問你webdriver client的原理,因為這樣才能看出你自動化的深度,這需要去研究下源碼。

懵逼?別急,下面我就結合源碼給大家簡單介紹下(看主要源碼即可,本來想畫個調用流程圖,但是着實精力有限)

我用的selenium版本是__version__ = '3.0.1'

寫一個簡單的樣例

from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.cnblogs.com/uncleyong/')
driver.close()  # 關閉當前窗口,但是沒結束進程
driver.quit()

 

 

 

運行示例,成功打開目標頁面

 

源碼分析 

打開selenium的目錄結構,webdriver下有各種客戶端瀏覽器,比如chrome,firefox,ie等

 

那webdriver client的原理是什么呢?回到下面的示例,點擊下圖第2行Chrome

 

下面第20行可以看到,Chrome是一個別名

 

點擊上圖第20行WebDriver

下圖可以看到,WebDriver類繼承了RemoteWebDriver類,RemoteWebDriver類是as的別名,是另外一個py文件中WebDriver類的別名

下面這個WebDriver類是在seleniumwebdriver.chrome下的webdriver.py中

這個類的注釋:Controls the ChromeDriver and allows you to drive the browser,含義是:控制ChromeDriver並允許驅動瀏覽器。

 

實例化WebDriver(也就是執行webdriver.Chrome())的時候,過程中會先啟動chromedriver,然后調用父類RemoteWebDriver的__init方法

 

上圖57行實例化Service類,點擊這個類

 

點擊上圖35行free_port 

下面是獲取到一個可用的端口

 

執行下圖62行self.service.start()后,啟動了chromedriver

 

點擊上圖62行start

可用看到,subprocess調用命令行打開webdriver.exe

 

 

也就是selenium啟動了chromedriver,可以看到對應的進程

 

我們把獲取到的端口打印出來

 

 

 

要執行了self.service.start()后,啟動了chromedriver,下面命令才有結果

 

我們來看下RemoteWebDriver這個父類,點擊下圖65行RemoteWebDriver

 

名稱還是WebDriver,注意,只要不在同一個py文件,類名是可以相同的,但是如果其中一個要引用另外一個,另外一個要as取別名,如上圖中from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver

這個類的注釋:Controls a browser by sending commands to a remote server,含義是:通過向遠程服務器發送命令來控制瀏覽器。 

 

來看其__init__方法,

下圖58行__init__中參數command_executor的默認值是http://127.0.0.1:4444/wd/hubremote,

下圖86行創建RemoteConnection類對象也需要參數command_executor,說明是連接remote server的URL,RemoteConnection類是負責與Remote WebDriver server連接的類

 

點擊上圖92行start_session,

可以看到,下圖180行生成sessionId

 

點擊上圖179行execute

 

點擊上圖234行execute

command表示執行的命令

 

點擊上圖407行command_info

 

點擊上圖402行_commands,打開self._commands這個dict,下圖199行就是我們前面的發送請求的命令Command.NEW_SESSION,發送一個路徑為/session的post請求,生成sessionId,返回響應

 

回到下圖,點擊407行_request

 

在_request方法中,try下面第一行將打開目標瀏覽器,並綁定到指定端口,綁定完成后,該啟動的瀏覽器實例就做為WebDriver的remote server

resp是一個返回的對象

 

后面執行resp.read(),獲取到對象內的data數據,debug模式下,可用看到,data中包括已經生成的sessionId

 

另外,WebDriver類是在selenium.webdriver.remote下的webdriver.py文件中

 

 

回到我們的示例腳本

 

 

點擊上方第3行get

 

上圖248行url是我們傳的參數,點擊上圖248行GET,通過下圖可知Command是一個類,GET是類屬性

 

點擊上上圖248行execute

我們直接看response后面的代碼,self對象后面是一個名為command_executor的對象,這個對象執行了execute方法。

 

點擊上圖234行command_executor

下圖86行可以看到,對象command_executor是RemoteConnection類的實例,

這個實例是在__init__方法中,說明是在創建selenium.webdriver.remote.webdriver.WebDriver類對象的時候也完成了command_executor對象的創建

 

點擊上圖86行RemoteConnection類

RemoteConnection類就是負責與Remote WebDriver server連接的類

 

再回到下圖,來看RemoteConnection類的方法execute

 

點擊上圖234行execute

command表示執行的命令

 

點擊上圖407行command_info

 

點擊上圖402行_commands,打開self._commands這個dict,

下圖206行就是我們前面的發送請求的命令Command.GET,通過sessionId確定請求的瀏覽器,也就是說sessionId表示了remote server和對應瀏覽器的一個會話,指令通過這個會話變成對瀏覽器的一個操作,

remote server在執行完對瀏覽器的操作后得到的數據將作為響應的body返回給測試代碼。

另外,216行是常用的

 

手工模擬 

為了更加清晰查看請求過程中的path、params、method、url、body值,我們先自己寫一個mylogger文件

修改源文件remote_connection.py

 

點擊上圖411行_request,添加如下內容

 

運行示例,控制台打印出日志

 

完整版日志:

[2019-11-12 22:49:05,821] [DEBUG] [C:\Python36\lib\site-packages\selenium\webdriver\remote\remote_connection.py : execute:408 , 【path】:/session ----- 【params】:{'desiredCapabilities': {'browserName': 'chrome', 'version': '', 'platform': 'ANY', 'javascriptEnabled': True, 'chromeOptions': {'extensions': [], 'args': []}}, 'requiredCapabilities': {}}

[2019-11-12 22:49:05,821] [DEBUG] [C:\Python36\lib\site-packages\selenium\webdriver\remote\remote_connection.py : _request:428 , 【method】:POST +++++ 【url】:http://127.0.0.1:21753/session +++++ 【body】:{"desiredCapabilities": {"browserName": "chrome", "version": "", "platform": "ANY", "javascriptEnabled": true, "chromeOptions": {"extensions": [], "args": []}}, "requiredCapabilities": {}}

[2019-11-12 22:49:07,882] [DEBUG] [C:\Python36\lib\site-packages\selenium\webdriver\remote\remote_connection.py : execute:408 , 【path】:/session/80c0d4efaeea62ae67c11a9ae3c656fd/url ----- 【params】:{'url': 'https://www.cnblogs.com/uncleyong/', 'sessionId': '80c0d4efaeea62ae67c11a9ae3c656fd'}

[2019-11-12 22:49:07,882] [DEBUG] [C:\Python36\lib\site-packages\selenium\webdriver\remote\remote_connection.py : _request:428 , 【method】:POST +++++ 【url】:http://127.0.0.1:21753/session/80c0d4efaeea62ae67c11a9ae3c656fd/url +++++ 【body】:{"url": "https://www.cnblogs.com/uncleyong/", "sessionId": "80c0d4efaeea62ae67c11a9ae3c656fd"}

 

通過debug調試可知, 執行2-5行代碼時,driver_command的值分別為:newSession、get、close、quit

 

上面日志中,

前兩行是執行response = self.execute(Command.NEW_SESSION, capabilities)產生的日志

后兩行是執行self.execute(Command.GET, {'url': url})產生的日志

 

還原上面第2-3行過程

手動執行chromedriver.exe,模擬selenium先啟動了chromedriver(不要手動關閉)

 

模擬上面日志中第一個post請求,browserName是chrome,表示告訴remote driver打開chrome瀏覽器

打開了瀏覽器(不要手動關閉) 

 

  

 響應中,返回了sessionId,后面和瀏覽器的交互都是基於這個sessionId進行的

 

 

 

模擬上面日志第二個post請求,sessionId替換為上面返回的值:8832915b0bc0151aab6573ab33b4be93

 

瀏覽器展示了我們的目標url頁面內容

 

響應status等於0,即表示命令執行成功

 

結論 

看懂上面的分析,你應該知道結論了吧?

如果還是迷糊,那請關注公眾號“全棧測試筆記”,回復“原理”獲取結論。

 


免責聲明!

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



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