其他分享
首页 > 其他分享> > Selenium Webdriver原理终于搞清楚了!

Selenium Webdriver原理终于搞清楚了!

作者:互联网

前言

测开系列Selenium Webdriver Python(20)--Webdriver运行原理

selenium1.x:这个时候的selenium,使用的是JavaScript注入技术与浏览器打交道。需要Selenium RC启动一个Server,将操作Web元素的API调用转化为一段段Javascript,在Selenium内核启动浏览器之后注入这段Javascript。Javascript可以获取并调用DOM的任何元素,自如的进行操作。由此才实现了Selenium的目的:自动化Web操作。

这种Javascript注入技术的缺点是速度不理想,而且稳定性大大依赖于Selenium内核对API翻译成的Javascript质量高低。

相比于selenium1.x,selenium2.x版本整合了webdriver以及原版selenium。两个项目合二为一,虽然名字还叫selenium,但也可以叫Webdriver。

这个版本的selenium是利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API。直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)。由于使用的是浏览器原生的API,速度大大提高,而且调用的稳定性交给了浏览器厂商本身,显然是更加科学。

然而带来的一些副作用就是,不同的浏览器厂商,对Web元素的操作和呈现多少会有一些差异,这就直接导致了Selenium WebDriver要分浏览器厂商不同,而提供不同的实现。

 

WebDriver协议

WebDriver是W3C的一个标准,由Selenium主持。具体的协议标准可以从官方网站查看。

从这个协议中我们可以看到,WebDriver之所以能够实现与浏览器进行交互,是因为浏览器实现了这些协议。这个协议是使用JOSN通过HTTP进行传输。

它的实现使用了经典的Client-Server模式。客户端发送一个requset,服务器端返回一个response。

官方网站文档描述如下:

The following table of endpoints lists the method and URI template for each endpoint node command. Extension commands are implicitly appended to this table.

MethodURI TemplateCommand
POST/sessionNew Session
DELETE/session/{session id}Delete Session
GET/statusStatus
GET/session/{session id}/timeoutsGet Timeouts
POST/session/{session id}/timeoutsSet Timeouts
POST/session/{session id}/urlNavigate To
GET/session/{session id}/urlGet Current URL
POST/session/{session id}/backBack
POST/session/{session id}/forwardForward
POST/session/{session id}/refreshRefresh
GET/session/{session id}/titleGet Title
GET/session/{session id}/windowGet Window Handle
DELETE/session/{session id}/windowClose Window
POST/session/{session id}/windowSwitch To Window
GET/session/{session id}/window/handlesGet Window Handles
POST/session/{session id}/window/newNew Window
POST/session/{session id}/frameSwitch To Frame
POST/session/{session id}/frame/parentSwitch To Parent Frame
GET/session/{session id}/window/rectGet Window Rect
POST/session/{session id}/window/rectSet Window Rect
POST/session/{session id}/window/maximizeMaximize Window
POST/session/{session id}/window/minimizeMinimize Window
POST/session/{session id}/window/fullscreenFullscreen Window
GET/session/{session id}/element/activeGet Active Element
POST/session/{session id}/elementFind Element
POST/session/{session id}/elementsFind Elements
POST/session/{session id}/element/{element id}/elementFind Element From Element
POST/session/{session id}/element/{element id}/elementsFind Elements From Element
GET/session/{session id}/element/{element id}/selectedIs Element Selected
GET/session/{session id}/element/{element id}/attribute/{name}Get Element Attribute
GET/session/{session id}/element/{element id}/property/{name}Get Element Property
GET/session/{session id}/element/{element id}/css/{property name}Get Element CSS Value
GET/session/{session id}/element/{element id}/textGet Element Text
GET/session/{session id}/element/{element id}/nameGet Element Tag Name
GET/session/{session id}/element/{element id}/rectGet Element Rect
GET/session/{session id}/element/{element id}/enabledIs Element Enabled
GET/session/{session id}/element/{element id}/computedroleGet Computed Role
GET/session/{session id}/element/{element id}/computedlabelGet Computed Label
POST/session/{session id}/element/{element id}/clickElement Click
POST/session/{session id}/element/{element id}/clearElement Clear
POST/session/{session id}/element/{element id}/valueElement Send Keys
GET/session/{session id}/sourceGet Page Source
POST/session/{session id}/execute/syncExecute Script
POST/session/{session id}/execute/asyncExecute Async Script
GET/session/{session id}/cookieGet All Cookies
GET/session/{session id}/cookie/{name}Get Named Cookie
POST/session/{session id}/cookieAdd Cookie
DELETE/session/{session id}/cookie/{name}Delete Cookie
DELETE/session/{session id}/cookieDelete All Cookies
POST/session/{session id}/actionsPerform Actions
DELETE/session/{session id}/actionsRelease Actions
POST/session/{session id}/alert/dismissDismiss Alert
POST/session/{session id}/alert/acceptAccept Alert
GET/session/{session id}/alert/textGet Alert Text
POST/session/{session id}/alert/textSend Alert Text
GET/session/{session id}/screenshotTake Screenshot
GET/session/{session id}/element/{element id}/screenshotTake Element Screenshot
POST/session/{session id}/printPrint Page

 

 

selenium驱动浏览器原理

在我们new一个WebDriver的过程中,Selenium首先会确认浏览器的native component是否存在可用而且版本匹配。接着就在目标浏览器里启动一整套Web Service,这套Web Service使用了Selenium自己设计定义的协议,名字叫做The WebDriver Wire Protocol。这套协议非常之强大,几乎可以操作浏览器做任何事情,包括打开、关闭、最大化、最小化、元素定位、元素点击、上传文件等等等等。

WebDriver Wire协议是通用的,也就是说不管是FirefoxDriver还是ChromeDriver,启动之后都会在某一个端口启动基于这套协议的Web Service。例如FirefoxDriver初始化成功之后,默认会从http://localhost:7055开始,而ChromeDriver则大概是http://localhost:46350之类的。接下来,我们调用WebDriver的任何API,都需要借助一个ComandExecutor发送一个命令,实际上是一个HTTP request给监听端口上的Web Service。在我们的HTTP request的body中,会以WebDriver Wire协议规定的JSON格式的字符串来告诉Selenium我们希望浏览器接下来做社么事情。

 

这里笔者初步画了一个图来表示各种WebDriver的工作原理:

从上图中我们可以看出,不同浏览器的WebDriver子类,都需要依赖特定的浏览器原生组件,例如Firefox就需要一个add-on名字叫webdriver.xpi。而IE的话就需要用到一个dll文件来转化Web Service的命令为浏览器native的调用。另外,图中还标明了WebDriver Wire协议是一套基于RESTful的Web Service。如果不明白什么是RESTful的,可以参见一篇介绍REST的blog(http://blog.csdn.net/ant_yan/article/details/7963517)

关于WebDriver Wire协议的细节,比如希望了解这套Web Service能够做哪些事情,可以阅读Selenium官方的协议文档, 在Selenium的源码中,我们可以找到一个HttpCommandExecutor这个类,里面维护了一个Map<String, CommandInfo>,它负责将一个个代表命令的简单字符串key,转化为相应的URL,因为REST的理念是将所有的操作视作一个个状态,每一个状态对应一个URI。所以当我们以特定的URL发送HTTP request给这个RESTful Web Service之后,它就能解析出需要执行的操作。截取一段源码如下:

nameToUrl = ImmutableMap.<String, CommandInfo>builder()
        .put(NEW_SESSION, post("/session"))
        .put(QUIT, delete("/session/:sessionId"))
        .put(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle"))
        .put(GET_WINDOW_HANDLES, get("/session/:sessionId/window_handles"))
        .put(GET, post("/session/:sessionId/url"))
 
            // The Alert API is still experimental and should not be used.
        .put(GET_ALERT, get("/session/:sessionId/alert"))
        .put(DISMISS_ALERT, post("/session/:sessionId/dismiss_alert"))
        .put(ACCEPT_ALERT, post("/session/:sessionId/accept_alert"))
        .put(GET_ALERT_TEXT, get("/session/:sessionId/alert_text"))
        .put(SET_ALERT_VALUE, post("/session/:sessionId/alert_text"))

可以看到实际发送的URL都是相对路径,后缀多以/session/:sessionId开头,这也意味着WebDriver每次启动浏览器都会分配一个独立的sessionId,多线程并行的时候彼此之间不会有冲突和干扰。例如我们最常用的一个WebDriver的API,getWebElement在这里就会转化为/session/:sessionId/element这个URL,然后在发出的HTTP request body内再附上具体的参数比如by ID还是CSS还是Xpath,各自的值又是什么。收到并执行了这个操作之后,也会回复一个HTTP response。内容也是JSON,会返回找到的WebElement的各种细节,比如text、CSS selector、tag name、class name等等。以下是解析我们说的HTTP response的代码片段:

try {
        response = new JsonToBeanConverter().convert(Response.class, responseAsText);
      } catch (ClassCastException e) {
        if (responseAsText != null && "".equals(responseAsText)) {
          // The remote server has died, but has already set some headers.
          // Normally this occurs when the final window of the firefox driver
          // is closed on OS X. Return null, as the return value _should_ be
          // being ignored. This is not an elegant solution.
          return null;
        }
        throw new WebDriverException("Cannot convert text to response: " + responseAsText, e);
      } //...

 

 

 

 

 

 

 

 

 

 

标签:Webdriver,搞清楚,WebDriver,Selenium,element,session,浏览器,id
来源: https://blog.csdn.net/lovedingd/article/details/110929187