是的,大体上就是这样。
理论
粗体和方框内是执行方,斜体和箭头表示使用的协议。
当你想与浏览器交互时,
-
您的代码使用您使用的语言(Java、Python、Ruby 等)的webdriver 客户端(通常是库,如
selenium) )。
- 该客户端与 webdriver 服务器 通信,按照 webdriver 协议 发送和接收数据;该协议封装在 http 中,以便于传输和控制。
-
webdriver 服务器 将其转换为 浏览器 的实际命令 - 因此它(浏览器)与页面交互,或从中获取数据。
流程始终是端到端的(例如,浏览器从不直接与您的代码通信:)),并且是双向的。失败/异常通常只会出现在您的代码的上游。
一些细节
该图中的“browser's webdriver”是二进制(程序) - Firefox 的“geckodriver”(在 Windows 上带有“.exe” )、“chromedriver”、“safaridriver”、“edgedriver.exe”(总是与“.exe”一起使用:))。它充当代理 - 一方面接受和理解 webdriver 协议中的命令,另一方面 - 知道如何与浏览器通信。
webdriver 始终是 HTTP 服务器 - 所有命令都封装在 HTTP 中,使用常用方法 get/post/delete/put (关闭,如果与常规 REST 不同)。它implements the webdriver protocol,所以客户端(selenium & co)有一个定义良好的 API 来与之通信。因此它也可以称为“webdriver 服务器”——它侦听命令,将它们代理到浏览器,并将响应返回给客户端。 (没有人这样称呼它:),但它更容易区分“webdriver the executable”和“webdriver the protocol”)
作为服务器,它在随机网络端口上绑定和侦听 - 在您的本地计算机或远程计算机上。如果您在本地运行,这就是它的二进制文件必须在您的 path 变量中的原因 - 在初始化时 Selenium 启动它(因此它必须能够找到它)并获取它正在侦听的网络端口 (对于进一步沟通)。如果您使用远程连接,您必须 a) 知道远程 webdriver 服务器的 IP:端口,或 b) 使用“Selenium Hub”,它在其域下跟踪此信息,并与您共享。
webdriver 服务器和浏览器之间的通信通常是二进制 rpc,并且非常特定于浏览器 - 它使用内部 API,webdriver 知道如何最好地控制这个特定的浏览器。因此,驱动程序由浏览器供应商提供。这始终是本地(在同一台机器/操作系统中) 通信(至少据我所知)。
如果您使用的是更高级别的框架,如 Robot Framework、Cucumber、JBehave 等,它位于该图中的“您的代码”之前,试图保护您免受某些 selenium 调用的影响。
实践中
“一张图抵千言”,那么代码一定是740之类的吧? :) 足够的理论,这是一个实际的例子:
from selenium import webdriver # importing selenium bindings
wd = webdriver.Firefox() # connect to the "webdriver server", a local one
element = wd.find_element_by_css_selector('#my-id') # locate an element
the_text = element.text # get is text
assert(text == 'My awesome text!') # verify it's the expected one
整个清单是第一部分中的您的代码 - 为完成工作而执行的不同指令、流程控制和检查。在第 1 行,python 的 selenium 库被导入以供进一步使用。
Selenium是最流行的实现webdriver协议的框架;它具有不同语言的实现(即绑定) - python 这里,java,ruby,javascript 等等。它努力做的是为所有这些提供一个统一的接口——Java 中的getText() 也可以在 Python 中作为.text 使用,等等。通过这个接口,它将客户端与实际的 webdriver 协议隔离开来——用户输入.text,并且不关心它是如何实际执行的,也不必在协议发生变化时更改他的代码。
在第 3 行,实例化了 webdriver 对象;因为这里是一个本地服务器,所以实例化过程通过前面描述的本地步骤——“webdriver 服务器”运行,它的端口现在是已知的(并存储在对象中),并且可以进行通信开始。
代码中的
第 4 行 使用selenium 方法来定位页面中的特定元素。在后台,该库向 webdriver 服务器发送一个 POST http 请求,以定位该元素。
为什么发布?因为一旦成功找到,服务端就会给它分配一个内部id,以后会用到;并将 id 返回给客户端,客户端将其存储为 element 对象的属性(*参见脚注)。
webdriver 服务器如何定位该元素?不怎么 - 它通过专有协议与浏览器通信,说“嘿,使用你的渲染和评估引擎,在 DOM 中找到一个与这个 CSS 选择器匹配的元素,并给我一个我们将来可以重用的参考。 " (即“魔法”:)。所以是浏览器完成工作,webdriver 服务器只是代理通信。
让我们进入细节 - 第 5 行 执行命令.text,显然返回元素的文本 (如果你不懂 python,不要惊慌为什么它是一个命令,但末尾没有 () - 这是一个语言怪癖,将方法别名为对象属性,一个非常方便的功能)。
此时会发生什么: selenium python binding 将此命令与其公共接口中的getElementText 匹配;然后它将它与webdriver protocol command 匹配(打开链接,这很有趣,我保证) - 它是 GET 类型,它的参数是这个和那个。
它打开到“localhost:the_know_port”的网络连接,到这个端点:
GET /session/2cce72b7-c748-48bc-b350-6dd6730b5a69/element/5/text
第一个“随机”字符串是会话 id - 一个 webdriver 服务器可以被许多客户端使用,你的服务器在第 3 行建立并存储。第二个参数 (“5”) 是元素的 id,在第 4 行建立。然后是“文本” - 您请求的子资源,元素支持的子资源之一。
这就是臭名昭著的 webdriver 协议/API——特定访问方案的知识(您可以在会话中获取已建立元素的“文本”)和流(您必须首先建立一个共享会话,然后是一个元素的引用,所以最终得到“文本”)。
之后,webdriver 服务器让浏览器从其 DOM ("the magic") 中获取信息,并通过网络将其发送回客户端(selenium 实例):
{"sessionId":"2cce72b7-c748-48bc-b350-6dd6730b5a69","status":0,"value":"My awesome text!"}
您的 selenium 实例正在等待响应,从负载中获取并解析信息,并将值返回给您的代码 - 变量 the_text 现在具有值“我的真棒文本!”。
并且 - 完成,循环 code -> webdriver client -> webdriver server -> browser -> webdriver server -> webdriver client -> code 现在完成了。
脚注:
(*) - 这就是可怕的StaleElementReferenceException 的真正原因:所有三个 - 客户端、webdriver 服务器和浏览器都持有对 DOM 中元素的引用。
但是在某个特定的时刻,第 3 方 - 在浏览器中运行的 javascript 代码,更改/删除了元素,幸福地没有意识到某些东西有一个引用,它现在使 无效(想想看,这是一个非常邪恶的行为: D).
下次客户端尝试通过 webdriver 服务器在浏览器中与引用交互时 - 元素不再存在。自然,交互失败,失败返回到客户端并出现异常;它的文本消息是“元素不再附加到 DOM”——希望这有点神秘现在很有意义。