Safari 10 中的 WebDriver 支持

随着 Web 内容变得越来越具交互性、响应性和复杂性,确保在多个平台和浏览器上提供良好的用户体验对于 Web 开发者和 QA 组织来说是一个巨大的挑战。Web 内容不仅必须正确加载、执行和渲染,而且用户还必须能够按照预期与内容进行交互,无论浏览器、平台、硬件、屏幕尺寸或其他条件如何。如果您的购物车应用程序布局简洁、排版美观,但由于生产环境配置错误或 CSS 规则不正确导致结账按钮在横向模式下被隐藏,那么您的用户就不会获得良好的体验。

通过自动化确保良好的用户体验之所以困难,部分原因在于每个浏览器都有自己解释键盘和鼠标输入、执行导航、组织窗口和标签页等的方式。当然,人工可以在部署更新到生产环境之前,在每个浏览器上手动测试其网站的每一次交互和工作流程,但这对于除最大公司之外的所有公司来说都是缓慢、昂贵且不切实际的。Selenium 开源项目的创建正是为了弥补浏览器自动化能力的这一空白,它提供了一个通用的自动化 API。WebDriver 是 Selenium 的跨平台、跨浏览器自动化 API。使用 WebDriver,开发者可以编写测试其 Web 内容的自动化测试,并针对任何拥有 WebDriver 兼容驱动程序的浏览器运行该测试。

Safari + WebDriver

我很高兴地宣布,Safari 现在提供了对 WebDriver API 的原生支持。从 OS X El Capitan 和 macOS Sierra 上的 Safari 10 开始,Safari 捆绑了一个由 Apple 的 Web 开发者体验团队维护的新驱动程序实现。Safari 的驱动程序可以通过 /usr/bin/safaridriver 可执行文件启动,并且 Selenium 提供的大多数客户端库将自动以这种方式启动驱动程序,无需进一步配置。

我将首先通过示例介绍 WebDriver,并解释一些关于其实现方式以及如何使用 Safari 新驱动程序实现的信息。我将介绍一些 Safari 驱动程序实现特有的重要保护措施,并讨论当您将现有的 WebDriver 测试套件重新定位到使用 Safari 驱动程序运行时,这些保护措施可能如何影响您。

通过示例学习 WebDriver

WebDriver 是一种浏览器自动化 API,它使人们能够使用 Python、Java、PHP、JavaScript 或任何其他具有理解事实标准 Selenium Wire Protocol 或即将到来的 W3C WebDriver 协议的库的语言编写其 Web 内容的自动化测试。

下面是用 Python 编写的针对WebKit 功能状态页面的 WebDriver 测试套件。第一个测试用例在功能状态页面中搜索“CSS”,并确保至少出现一个结果。第二个测试用例计算应用各种过滤器后可见结果的数量,以确保每个结果最多只出现在一个过滤器中。请注意,在 Python WebDriver 库中,每个方法调用都会同步阻塞,直到操作完成(例如导航或执行 JavaScript),但某些客户端库提供了更异步的 API。

from selenium.webdriver.common.by import By
from selenium import webdriver
import unittest

class WebKitFeatureStatusTest(unittest.TestCase):

    def test_feature_status_page_search(self):
        self.driver.get("https://webkit.ac.cn/status/")

        # Enter "CSS" into the search box.
        search_box = self.driver.find_element_by_id("search")
        search_box.send_keys("CSS")
        value = search_box.get_attribute("value")
        self.assertTrue(len(value) > 0)
        search_box.submit()

        # Count the results.
        feature_count = self.shown_feature_count()
        self.assertTrue(len(feature_count) > 0)

    def test_feature_status_page_filters(self):
        self.driver.get("https://webkit.ac.cn/status/")

        filters = self.driver.find_element(By.CSS_SELECTOR, "ul#status-filters li input[type=checkbox]")
        self.assertTrue(len(filters) is 7)

        # Make sure every filter is turned off.
        for checked_filter in filter(lambda f: f.is_selected(), filters):
            checked_filter.click()

        # Count up the number of items shown when each filter is checked.
        unfiltered_count = self.shown_feature_count()
        running_count = 0
        for filt in filters:
            filt.click()
            self.assertTrue(filt.is_selected())
            running_count += self.shown_feature_count()
            filt.click()

        self.assertTrue(running_count is unfiltered_count)

    def shown_feature_count(self):
        return len(self.driver.execute_script("return document.querySelectorAll('li.feature:not(.is-hidden)')"))

def setup_module(module):
    WebKitFeatureStatusTest.driver = webdriver.Safari()

def teardown_module(module):
    WebKitFeatureStatusTest.driver.quit()

if __name__ == '__main__':
    unittest.main()

请注意,此代码示例仅用于说明目的。此测试与特定版本的功能状态页面同时存在,并使用页面特定的 DOM 类和选择器。因此,它可能无法在后续版本的功能状态页面上工作。与任何测试一样,它可能需要修改才能与被测试软件的后续版本一起工作。

WebDriver 的内部原理

WebDriver 规范采用 REST API 形式;Safari 的驱动程序提供了自己的本地 Web 服务器,接受 REST 风格的 HTTP 请求。在底层,Python Selenium 库self.driver 上的每个方法调用转换为 REST API 命令,并将相应的 HTTP 请求发送到由 Safari 驱动程序托管的本地 Web 服务器。驱动程序验证请求的内容,并将命令转发到适当的浏览器实例。通常,执行大多数这些命令的底层细节会委托给 WebKit 的 UIProcess 和 WebProcess 层中的 WebAutomationSession 及相关类。当命令执行完成后,驱动程序最终为 REST API 命令发送一个 HTTP 响应。Python 客户端库解释 HTTP 响应并将结果返回给测试代码。

在 Safari 中运行示例

要使用 Safari 运行此 WebDriver 测试,首先需要获取 Selenium 开源项目的最新版本。Selenium 的 Java 和 Python 客户端库从 3.0.0-beta1 版本开始支持 Safari 的原生驱动程序实现。请注意,Apple 开发的驱动程序与 Selenium 项目中提到的旧版 SafariDriver 无关。旧版 SafariDriver 实现已不再维护,不应使用。除了 Safari 10 之外,您无需下载任何其他内容即可获得 Apple 开发的驱动程序。

获取、安装并配置好测试以使用正确的 Selenium 库版本后,您需要配置 Safari 以允许自动化。作为面向开发者的功能,Safari 的 WebDriver 支持默认处于关闭状态。要开启 WebDriver 支持,请执行以下操作:

  • 确保“开发”菜单可用。您可以通过打开 Safari 偏好设置(菜单栏中的“Safari”>“偏好设置”),转到“高级”标签页,并确保勾选“在菜单栏中显示‘开发’菜单’”复选框来将其打开。
  • 在“开发”菜单中启用远程自动化。这可以通过菜单栏中的“开发”>“允许远程自动化”进行切换。
  • 授权 safaridriver 启动托管本地 Web 服务器的 webdriverd 服务。要允许此操作,请手动运行一次 /usr/bin/safaridriver 并完成身份验证提示。

启用远程自动化后,将测试复制并保存为 test_webkit.py。然后运行以下命令:

python test_webkit.py

Safari 独有的保护措施

虽然能够在 Safari 中编写自动化测试非常棒,但用于浏览器自动化的 REST API 也引入了恶意软件的潜在攻击向量。为了在不牺牲用户隐私或安全的情况下支持有价值的自动化 API,Safari 的驱动程序实现引入了额外保护措施,这是迄今为止其他浏览器或驱动程序所未实现的。通过提供增强的测试隔离,这些保护措施也作为防止“不稳定测试” 的额外保障。下面描述了一些这些保护措施。

在 Safari 中运行 WebDriver 测试时,测试执行被限制在特殊的自动化窗口中,这些窗口与正常浏览窗口、用户设置和偏好设置隔离。自动化窗口很容易通过其橙色的智能搜索栏进行识别。与在“无痕浏览”窗口中浏览类似,在自动化窗口中运行的 WebDriver 测试总是从全新的状态开始,无法访问 Safari 的正常浏览历史记录、自动填充数据或其他敏感信息。除了明显的隐私好处之外,此限制还有助于确保测试不受之前测试会话的持久状态(例如本地存储或 Cookie)的影响。

The Smart Search field in Automation windows is bright orange to warn users that the window is not meant for normal browsing.

当 WebDriver 测试在自动化窗口中执行时,任何试图与窗口或 Web 内容交互的行为都可能通过意外改变页面状态来干扰测试。为了防止这种情况发生,Safari 在测试运行时会在自动化窗口上方安装一个“玻璃面板”。这会阻止任何意外的交互(鼠标、键盘、调整大小等)影响自动化窗口。如果正在运行的测试卡住或失败,可以通过“打破”玻璃面板来中断正在运行的测试。当自动化会话被中断时,测试与浏览器的连接将永久断开,自动化窗口保持打开状态,直到手动关闭。

If a user interacts with an active Automation Window, a dialog will appear that allows the user to end the automation session and interact with the test page.

除了能够与停止的测试进行交互外,Safari 的驱动程序实现还允许在 WebDriver 测试执行期间和之后充分使用 Web Inspector。这在尝试找出测试挂起或提供意外结果的原因时非常有用。safaridriver 支持两个专门用于调试的特殊WebDriver 能力(capabilities)automaticInspection 能力将在后台预加载 Web Inspector 和 JavaScript 调试器;要暂停测试执行并调出 Web Inspector 的“调试器”标签页,您只需在测试页面中评估一个 debugger; 语句即可。automaticProfiling 能力将在后台预加载 Web Inspector 并开始时间线记录;如果您以后想查看测试期间发生的详细情况,可以在 Web Inspector 中打开“时间线”标签页以查看捕获的完整时间线记录。

Safari 的驱动程序限制了活动 WebDriver 会话的数量。任何给定时间只能运行一个 Safari 浏览器实例,并且一次只能有一个 WebDriver 会话附加到该浏览器实例。这确保了模拟的用户行为(鼠标、键盘、触摸等)与用户在 macOS 窗口环境中实际使用真实硬件执行的行为密切匹配。例如,系统的文本插入光标一次只能在一个窗口和一个表单控件中处于活动状态;如果 Safari 的驱动程序允许在不同的窗口或浏览器中同时运行多个测试,那么文本插入行为(以及由此产生的 DOM focus/blur 事件)将无法代表用户自己可以执行的操作。

总结

随着 Safari 10 中引入原生 WebDriver 支持,现在可以在所有主流浏览器上平等地运行相同的 Web 内容自动化测试,而无需编写任何浏览器特定的代码。我希望 WebDriver 支持能帮助 Web 开发者更快地捕获其 Web 内容中用户可见的回归问题,并减少人工测试。Safari 的支持带来了新的、独有的保护措施,既能同时保护用户安全和隐私,也能帮助您编写更稳定、更一致的测试。您今天就可以通过安装 macOS SierraSafari 10 的测试版来试用 Safari 的 WebDriver 支持。

这是我们首次为 Safari 实现 WebDriver。如果您在使用 Safari 的 WebDriver 支持时遇到意外行为,或有其他改进建议,请在 https://bugreport.apple.com/ 提交错误报告。对于快速问题或反馈,请发送电子邮件至 web-evangelist@apple.com 或在 Twitter 上发推文给 @webkit@brrian。测试愉快!

附录

本文发布后,我们收到了用户大量有用的反馈。本节包含一些常见问题及其解决方法的信息。

在 El Capitan 上运行 Safari 10 时,safaridriver 启动后立即退出

一些用户报告称,他们在 El Capitan 上安装 Safari 10 后无法正常运行 safaridriver。此问题的主要症状是,当通过命令行手动启动 safaridriver 时,它会立即退出。发生这种情况是因为 safaridriver 使用的 launchd plist 未自动加载。解决方法如下:

# Check the status of the com.apple.webdriverd launchd plist:
launchctl list | grep webdriverd

# If it's not loaded, use this command to load the launchd plist:
launchctl load /System/Library/LaunchAgents/com.apple.webdriverd.plist

此问题仅在通过应用程序更新而不是 OS 更新安装较新的 Safari 时出现在 El Capitan 上。在 macOS Sierra 上运行 safaridriver 的用户应该不受影响。

注意:请参阅Web Inspector Reference 文档,了解有关 Web Inspector 的更多信息。