摘要:最近針對這個問題看了不少了文章和書籍,在加上一點自己的思考和整理,與大家一起分享,一起學習。本文將以為例進行說明。這個值表示的是訪問的。以為例可以看到,指令的部分包含了幾個組成部分請求方法。這一部分用來表示具體的指令。
作為一名使用Selenium開發UI自動化多年的工程師,一直都對Selenium Webdriver的實現原理感覺不是很清楚。怎么就通過腳本控制瀏覽器進行各種操作了呢?相信很多Selenium的使用者也會有類似的疑惑。最近針對這個問題看了不少了文章和書籍,在加上一點自己的思考和整理,與大家一起分享,一起學習。文章中如果有不準確的地方,希望大家給予指正。
結構想要使用Selenium實現自動化測試,主要需要三個東西。
測試代碼
Webdriver
瀏覽器
測試代碼測試代碼就是程序員利用不同的語言和相應的selenium API庫完成的代碼。本文將以python為例進行說明。
WebdriverWebdriver是針對不同的瀏覽器開發的,不同的瀏覽器有不同的webdriver。例如針對Chrome使用的chromedriver。
瀏覽器瀏覽器和相應的Webdriver對應。
首先我們來看一下這三個部分的關系。
對于三個部分的關系模型,可以用一個日常生活中常見的例子來類比。
對于打的這個行為來說,乘客和出租車司機進行交互,告訴出租車想去的目的地,出租車司機駕駛汽車把乘客送到目的地,這樣乘客就乘坐出租車到達了自己想去的地方。
這和Webdriver的實現原理是類似的,測試代碼中包含了各種期望的對瀏覽器界面的操作,例如點擊。測試代碼通過給Webdriver發送指令,讓Webdriver知道想要做的操作,而Webdriver根據這些操作在瀏覽器界面上進行控制,由此測試代碼達到了在瀏覽器界面上操作的目的。
理清了Selenium自動化測試三個重要組成之間的關系,接下來我們來具體分析其中一個最重要的關系。
接下來我會以獲取界面元素這個基本的操作為例來分析兩者之間的關系。
在測試代碼中,我們第一步要做的是新建一個webdriver類的對象:
from selenium import webdriver driver = webdriver.Chrome()
這里新建的driver對象是一個webdriver.Chrome()類的對象,而webdriver.Chrome()類的本質是
from .chrome.webdriver import WebDriver as Chrome
也就是一個來自chrome的WebDriver類。這個.chrome.webdriver.WebDriver是繼承了selenium.webdriver.remote.webdriver.WebDriver
from selenium.webdriver.remote.webdriver import WebDriver as 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, chrome_options=None, service_args=None, desired_capabilities=None, service_log_path=None): ...
以python為例,在selenium庫中,通過ID獲取界面元素的方法是這樣的:
from selenium import webdriver driver = webdriver.Chrome() 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()的實例方法的。
通過Source code可以看到:
def find_element(self, by=By.ID, value=None): """ "Private" method used by the find_element_by_* methods. :Usage: Use the corresponding find_element_by_* instead of this. :rtype: WebElement """ if self.w3c: ... return self.execute(Command.FIND_ELEMENT, { "using": by, "value": value})["value"]
這個方法最后call了一個execute方法,方法的定義如下:
def execute(self, driver_command, params=None): """ Sends a command to be executed by a 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}
正如注釋中提到的一樣,其中的關鍵在于
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類的類注釋來看:
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):
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。
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可以看到,self._commands存儲了selenium.webdriver.remote.command.Command類里的常量指令和WebDriver wire protocol中定義的指令的對應關系。
self._commands = { Command.STATUS: ("GET", "/status"), Command.NEW_SESSION: ("POST", "/session"), Command.GET_ALL_SESSIONS: ("GET", "/sessions"), Command.QUIT: ("DELETE", "/session/$sessionId"), ... Command.FIND_ELEMENT: ("POST", "/session/$sessionId/element"),
以FIND_ELEMENT為例可以看到,指令的URL部分包含了幾個組成部分:
HTTP請求方法。WebDriver wire protocol中定義的指令是符合RESTful規范的,通過不同請求方法對應不同的指令操作。
sessionId。Session的概念是這么定義的:
The server should maintain one browser per session. Commands sent to a session will be directed to the corresponding browser.
也就是說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返回給測試代碼,測試代碼經過解析處理后得到想要的數據。
這一部分屬于各個瀏覽器開發者和Webdriver開發者的范疇,所以我們不需要太關注,我們所關心的主要還是測試代碼和Webdriver的關系,就好像出租車駕駛員如何駕駛汽車我們不需要關心一樣。
總結
最后通過這個關系圖來簡單的描述Selenium三個組成部分的關系。通過對python selenium庫的分析,希望能夠幫助大家對selenium和webdriver的實現原理有更進一步的了解,在日常的自動化腳本開發中更加快捷的定位問題和解決問題。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44786.html
摘要:不同目標的自動化測試有不同的測試工具,但是任何工具都無不例外的需要編程的過程,實現源代碼,也可以稱之為測試腳本。 寫在最前面:目前自動化測試并不屬于新鮮的事物,或者說自動化測試的各種方法論已經層出不窮,但是,能夠在項目中持之以恒的實踐自動化測試的團隊,卻依舊不是非常多。有的團隊知道怎么做,做的還不夠好;有的團隊還正在探索和摸索怎么做,甚至還有一些多方面的技術上和非技術上的舊系統需要重構……...
摘要:目標通過模擬瀏覽器抓取淘寶商品美食信息,并存儲到數據庫中。流程框架淘寶頁面比較復雜,含有各種請求參數和加密參數,如果直接請求或者分析將會非常繁瑣。 目標 通過Selenium模擬瀏覽器抓取淘寶商品美食信息,并存儲到MongoDB數據庫中。 流程框架 淘寶頁面比較復雜,含有各種請求參數和加密參數,如果直接請求或者分析Ajax將會非常繁瑣。Selenium是一個自動化測試工具,可以驅動瀏覽...
摘要:然而讓蟲師們垂涎的并不是以上的種種,而是其通過驅動瀏覽器獲得的解析的能力。所以說這貨在動態爬取方面簡直是掛逼級別的存在,相較于手動分析更簡單易用,節省分析打碼時間。一旦設置了隱式等待時間,它的作用范圍就是對象實例的整個生命周期。 selenium——自動化測試工具,專門為Web應用程序編寫的一個驗收測試工具,測試其兼容性,功能什么的。然而讓蟲師們垂涎的并不是以上的種種,而是其通過驅動瀏...
摘要:自動化這一專欄,將以目的為導向,以簡化或自動化完成工作任務為目標,將運用于實踐中,解決實際問題,以激發讀者對這門腳本語言的學習興趣。 Python 自動化 這一專欄...
閱讀 1923·2021-11-19 09:40
閱讀 2132·2021-10-09 09:43
閱讀 3293·2021-09-06 15:00
閱讀 2809·2019-08-29 13:04
閱讀 2766·2019-08-26 11:53
閱讀 3512·2019-08-26 11:46
閱讀 2319·2019-08-26 11:38
閱讀 390·2019-08-26 11:27