web自動化原理揭祕

做過兩年自動化測試的小夥伴說web自動化測試真的不難,無非就是一些瀏覽器操作,頁面元素操作,常規的情況很容易處理,再學一學特殊元素的處理,基本就能應付項目的測試了。

  這個話倒沒錯,但是真正要學好自動化測試,深入自動化,並不是那麼簡單。首先你得懂原理吧,原理不懂,你就不知道怎麼解決一些異常情況,也無法完成拓展。其次你得學會寫自己的測試框架吧,一個項目寫了100個測試類,都是零散的腳本,沒有任何設計而言,都是純粹的業務代碼,那我可以說,換了項目你這些腳本就成了垃圾,因此,我們要做自動化,要成爲自動化大牛,就一定要花時間去要搞清楚自動化實現的原理,並且學會自己去實現自動化測試框架,乃至於自動化測試平臺。

  下面一段代碼實現了一個很簡單的功能:

  1.打開瀏覽器

  2.訪問頁面“http://ke.qq.com

  3.定位到頁面的搜索框

  4.輸入查詢數據

  5.定位搜索按鈕

  6.點擊搜索按鈕,完成搜索

  代碼如下圖:

  

package web.demo;import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.WebElement;import org.openqa.selenium.firefox.FirefoxDriver;import org.testng.annotations.Test;public class Demo1 { @Test public void test(){ //設置可執行的驅動文件路徑 System.setProperty("webdriver.gecko.driver", "src/test/resources/geckodriver.exe"); //創建火狐驅動對象 WebDriver driver = new FirefoxDriver(); //訪問騰訊課堂首頁 driver.get("https://ke.qq.com/"); //定位頁面的搜索框 WebElement searchInputBox = driver.findElement(By.id("js_keyword")); //往搜索框輸入數據 searchInputBox.sendKeys("檸檬班"); //定位到搜索按鈕 WebElement searchButton = driver.findElement(By.id("js_search")); //點擊搜索 searchButton.click(); }}

  驅動文件位置:

  

  

  需求很簡單,代碼也很簡單。但是你知道代碼中的這些瀏覽器操作,元素操作,是如何完成的嗎?

  比如,瀏覽器啓動完成後,再調用:driver.get("https://ke.qq.com/");就能在導航欄中訪問到指定的這個頁面,這個裏面發生了什麼,到底是客戶端腳本直接操作瀏覽器還是通過某些中間件來完成??

  底層原理如下:

  1.在自動化測試過程中,存在三部分組件:客戶端腳本+驅動+瀏覽器終端。

  2.驅動文件,以geckodriver.exe爲例,這個可執行的驅動文件啓動後,相當於一個暴露了一系列接口的服務器,監聽某一端口,例如:89890。

  3.客戶端的操作(訪問頁面,定位元素,輸入數據,點擊按鈕等)都是封裝成了接口請求(eg:/session/xx/yy),然後提交到驅動服務器。

  4.驅動服務器接收到客戶端的請求後,再跟終端瀏覽器交互。

  5.終端瀏覽器做出相應操作。

    下圖描述了整個交互過程:

  

  以定位元素爲例,定位搜索框,我們來看底下這行代碼在執行的時候底層到底經歷了些什麼:

WebElement searchInputBox = driver.findElement(By.id("js_keyword"));

  

實際,底層請求時,每個請求會被封裝爲一個command,然後根據不同的commannd封裝得到不同的HttpRequest對象:

根據此命令,得到接口地址:

拿到此接口地址封裝爲一個HttpRequest請求。

client.execute(httpRequest,true),執行接口調用:

  至於其他操作:往輸入框數據數據,點擊按鈕等,都是對應一個接口地址,通過調用接口,請求驅動來處理,最後驅動同瀏覽器進行交互,瀏覽器按照指示做出對應操作。

  Selenium有一個類AbstractHttpCommandCodec,此類中維護了衆多自動化操作對應的接口地址:

  

public AbstractHttpCommandCodec() {
defineCommand(STATUS, get("/status"));

defineCommand(GET_ALL_SESSIONS, get("/sessions"));
defineCommand(NEW_SESSION, post("/session"));
defineCommand(GET_CAPABILITIES, get("/session/:sessionId"));
defineCommand(QUIT, delete("/session/:sessionId"));

defineCommand(GET_SESSION_LOGS, post("/logs"));
defineCommand(GET_LOG, post("/session/:sessionId/log"));
defineCommand(GET_AVAILABLE_LOG_TYPES, get("/session/:sessionId/log/types"));

defineCommand(SWITCH_TO_FRAME, post("/session/:sessionId/frame"));
defineCommand(SWITCH_TO_PARENT_FRAME, post("/session/:sessionId/frame/parent"));

defineCommand(CLOSE, delete("/session/:sessionId/window"));
defineCommand(SWITCH_TO_WINDOW, post("/session/:sessionId/window"));

defineCommand(FULLSCREEN_CURRENT_WINDOW, post("/session/:sessionId/window/fullscreen"));

defineCommand(GET_CURRENT_URL, get("/session/:sessionId/url"));
defineCommand(GET, post("/session/:sessionId/url"));
defineCommand(GO_BACK, post("/session/:sessionId/back"));
defineCommand(GO_FORWARD, post("/session/:sessionId/forward"));
defineCommand(REFRESH, post("/session/:sessionId/refresh"));

defineCommand(SET_ALERT_CREDENTIALS, post("/session/:sessionId/alert/credentials"));

defineCommand(UPLOAD_FILE, post("/session/:sessionId/file"));
defineCommand(SCREENSHOT, get("/session/:sessionId/screenshot"));
defineCommand(ELEMENT_SCREENSHOT, get("/session/:sessionId/screenshot/:id"));
defineCommand(GET_TITLE, get("/session/:sessionId/title"));

defineCommand(FIND_ELEMENT, post("/session/:sessionId/element"));
defineCommand(FIND_ELEMENTS, post("/session/:sessionId/elements"));
defineCommand(GET_ELEMENT_PROPERTY, get("/session/:sessionId/element/:id/property/:name"));
defineCommand(CLICK_ELEMENT, post("/session/:sessionId/element/:id/click"));
defineCommand(CLEAR_ELEMENT, post("/session/:sessionId/element/:id/clear"));
defineCommand(
    GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
    get("/session/:sessionId/element/:id/css/:propertyName"));
defineCommand(FIND_CHILD_ELEMENT, post("/session/:sessionId/element/:id/element"));
defineCommand(FIND_CHILD_ELEMENTS, post("/session/:sessionId/element/:id/elements"));
defineCommand(IS_ELEMENT_ENABLED, get("/session/:sessionId/element/:id/enabled"));
defineCommand(ELEMENT_EQUALS, get("/session/:sessionId/element/:id/equals/:other"));
defineCommand(GET_ELEMENT_RECT, get("/session/:sessionId/element/:id/rect"));
defineCommand(GET_ELEMENT_LOCATION, get("/session/:sessionId/element/:id/location"));
defineCommand(GET_ELEMENT_TAG_NAME, get("/session/:sessionId/element/:id/name"));
defineCommand(IS_ELEMENT_SELECTED, get("/session/:sessionId/element/:id/selected"));
defineCommand(GET_ELEMENT_SIZE, get("/session/:sessionId/element/:id/size"));
defineCommand(GET_ELEMENT_TEXT, get("/session/:sessionId/element/:id/text"));
defineCommand(SEND_KEYS_TO_ELEMENT, post("/session/:sessionId/element/:id/value"));

defineCommand(GET_ALL_COOKIES, get("/session/:sessionId/cookie"));
defineCommand(GET_COOKIE, get("/session/:sessionId/cookie/:name"));
defineCommand(ADD_COOKIE, post("/session/:sessionId/cookie"));
defineCommand(DELETE_ALL_COOKIES, delete("/session/:sessionId/cookie"));
defineCommand(DELETE_COOKIE, delete("/session/:sessionId/cookie/:name"));

defineCommand(SET_TIMEOUT, post("/session/:sessionId/timeouts"));
defineCommand(SET_SCRIPT_TIMEOUT, post("/session/:sessionId/timeouts/async_script"));
defineCommand(IMPLICITLY_WAIT, post("/session/:sessionId/timeouts/implicit_wait"));

defineCommand(GET_APP_CACHE_STATUS, get("/session/:sessionId/application_cache/status"));
defineCommand(IS_BROWSER_ONLINE, get("/session/:sessionId/browser_connection"));
defineCommand(SET_BROWSER_ONLINE, post("/session/:sessionId/browser_connection"));
defineCommand(GET_LOCATION, get("/session/:sessionId/location"));
defineCommand(SET_LOCATION, post("/session/:sessionId/location"));

defineCommand(GET_SCREEN_ORIENTATION, get("/session/:sessionId/orientation"));
defineCommand(SET_SCREEN_ORIENTATION, post("/session/:sessionId/orientation"));
defineCommand(GET_SCREEN_ROTATION, get("/session/:sessionId/rotation"));
defineCommand(SET_SCREEN_ROTATION, post("/session/:sessionId/rotation"));

defineCommand(IME_GET_AVAILABLE_ENGINES, get("/session/:sessionId/ime/available_engines"));
defineCommand(IME_GET_ACTIVE_ENGINE, get("/session/:sessionId/ime/active_engine"));
defineCommand(IME_IS_ACTIVATED, get("/session/:sessionId/ime/activated"));
defineCommand(IME_DEACTIVATE, post("/session/:sessionId/ime/deactivate"));
defineCommand(IME_ACTIVATE_ENGINE, post("/session/:sessionId/ime/activate"));

// Mobile Spec
defineCommand(GET_NETWORK_CONNECTION, get("/session/:sessionId/network_connection"));
defineCommand(SET_NETWORK_CONNECTION, post("/session/:sessionId/network_connection"));
defineCommand(SWITCH_TO_CONTEXT, post("/session/:sessionId/context"));
defineCommand(GET_CURRENT_CONTEXT_HANDLE, get("/session/:sessionId/context"));
defineCommand(GET_CONTEXT_HANDLES, get("/session/:sessionId/contexts"));

}

  另外,可能會有人好奇,驅動服務器是何時啓動的服務。其實是在執行下面這行代碼的時候啓動的,大家可執行去debug調試selenium的底層代碼:

  

//創建火狐驅動對象
WebDriver driver = new FirefoxDriver();

  當上面這行代碼執行完,可以發現eclipse的控制檯顯示瞭如下信息:

1531911173760 geckodriver INFO geckodriver 0.19.0
1531911173772 geckodriver INFO Listening on 127.0.0.1:21984

  說明此驅動服務器成功啓動了,並且監聽了本機的21984端口,等待客戶端發起請求,並處理。

  至於驅動跟瀏覽器之間是如何交互的,在後面的文章中會擇機介紹,請大家守候。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章