Selenium 是一个在 Web 浏览器中进行自动化测试的强大工具。虽然 Selenium Web 驱动程序支持所有主流的浏览器,但你并不总是希望在真正的浏览器中进行测试。
本文要点
Selenium是一个在 Web 浏览器中进行自动化测试的强大工具。虽然 Selenium Web 驱动程序支持所有的主流浏览器,但你并不总是希望在真正的浏览器中进行测试。Selenium 来营救!本文中的示例来自GitHub库。所有示例都使用 JUnit 5 和 Maven 运行。每个示例都有 Java 11 和 Java 8 支持说明。
使用 Selenium 有什么好处?
Selenium 在运行时没有用户界面(UI)。使用 Selenium 进行测试的一大好处是性能——因为 Selenium 没有 UI,所以它们比真正的浏览器快。
一些 Selenium 还有另外一个优势——依赖。当在像 Jenkins 这样的持续集成服务器上进行测试时,机器可能没有安装真正的浏览器。根据你的环境,你可能没有权限安装一个。
另一方面,需要安装“真正的”浏览器的 Selenium 浏览器非常适合开发。例如,Chrome 和 Firefox 都可以在无头模式下运行。在调试 Selenium 脚本时,临时关闭无头模式并观看程序运行非常有用。这样你就可以直观地看到哪里出了问题。
HtmlUnitDriver——最初的 Selenium 驱动程序
过去,Selenium 带有一个内置的 Selenium 驱动程序 HtmlUnitDriver。虽然这个驱动程序仍然受支持,但它现在是一个单独的依赖项,并且不出所料地使用了 Html Unit 框架。在单页应用程序和主要基于 AJAX 的页面出现之前,这个驱动程序是一个非常好的选择。你可以选择是否运行页面 JavaScript,而且它在内存中运行并且非常快。对于包含大量 HTML 数据的 Web 页面来说,这仍然是一个不错的选择。
下面的代码展示了如何在 HtmlUnitDriver 中使用 Selenium 运行基本的测试。它之所以有效,是因为 InfoQ 的主页设计成了没有 JavaScript 也能正常工作。这个例子在 GitHub 库里,有Java 8和Java 11版本。
package com.infoq.selenium;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import java.util.Set;
import java.util.stream.Collectors;
public class HtmlUnitSeleniumIT {
protected WebDriver driver;
// ----------------------------------------------------
@BeforeEach
public final void connect() {
driver = new HtmlUnitDriver();
//driver.setJavascriptEnabled(true);
}
@AfterEach
public final void closeDriver() {
if (driver != null) {
driver.quit();
}
}
@Test
void qconDates() {
driver.get(“https://www.infoq.com“);
Set<String> newYorkCity = driver.findElements(By.className(“qcon”))
.stream()
.map(element -> element.getAttribute(“innerText”))
.filter(city -> city.trim().startsWith(“New York”))
.collect(Collectors.toSet());
assertEquals(1, newYorkCity.size(), “New York is an upcoming city”);
}
}
复制代码
然而,取消启用 JavaScript 这行代码的注释就是另外一回事了。它会在抛出一堆 JavaScript 警告之后失败并报错:EcmaError: TypeError:无法调用未定义方法“then”。
由于许多页面在没有 JavaScript 的情况下根本无法加载,因此需要一个能更好地支持 JavaScript 的 Selenium 驱动程序。
PhantomJS
多年来,PhantomJS是一个很好的选择。它轻量级、无头,并且有很好的 JavaScript 支持。然而,2017 年 4 月,维护者退出,2018 年 3 月,该项目被正式放弃。我想念它。
通过阅读声明和评论可以发现,其意图显然是转移到 Chrome 驱动程序。对于任何新东西,我都不建议使用 PhantomJS。除了不受支持之外,我还将两个项目切换到 Chrome 驱动程序,因为对于当前大部分 JavaScript 库,PhantomJS 都不能很好地处理其中的 JavaScript。因为 Chrome 驱动程序使用的是真正的浏览器,所以这不是问题。
Chrome 驱动程序
Chrome 提供了一种无头模式,总体效果很好。最大的缺点是你需要能够安装 Chrome。你不需要 UI,但是并不一定能够安装软件。
Chrome 驱动程序也需要下载一个可执行文件。我在这里用了一点小技巧。我将可执行文件保存在与项目相同的目录中(或者保存在二进制存储库里,并将其复制到工作区中)。然后,我让 Java 测试本身设置权限。类似地,我让 Java 测试将 Java 流程中的系统属性设置为该位置。我知道这有点像作弊,但它确实让我几乎可以控制所有事情。“几乎”是因为它仍然需要安装 Chrome 本身。对我的个人项目,这非常有效。不过,当与他人共享代码时,它会崩溃,因为他们也需要下载可执行文件。
GitHub 库里有Java 8 和Java 11两个版本。两者都要求你下载可执行文件并将其替换到 chrome-driver 目录中。
package com.infoq.selenium;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import static org.junit.jupiter.api.Assertions.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
import java.util.stream.Collectors;
public class ChromeSeleniumIT {
private static final boolean HEADLESS = true;
private static final String CHROME_DRIVER_DIRECTORY = “chrome-driver”;
protected WebDriver driver;
// ----------------------------------------------------
@BeforeEach
public final void connect() {
Path chrome = Paths.get(CHROME_DRIVER_DIRECTORY + “/chromedriver”);
chrome.toFile().setExecutable(true);
System.setProperty(“webdriver.chrome.driver”, chrome.toAbsolutePath().toString());
ChromeOptions chromeOptions = new ChromeOptions();
if (HEADLESS) {
chromeOptions.addArguments(“--headless”);
}
driver = new ChromeDriver(chromeOptions);
// https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/27
((JavascriptExecutor) driver).executeScript(“window.alert = function(msg) { }“);
((JavascriptExecutor) driver).executeScript(“window.confirm = function(msg) { }“);
}
@AfterEach
public final void closeDriver() {
if (driver != null) {
driver.quit();
}
}
@Test
void qconDates() {
driver.get(“https://www.infoq.com”);
Set<String> newYorkCity = driver.findElements(By.className(“qcon”))
.stream()
.map(element -> element.getAttribute(“innerText”))
.filter(city -> city.trim().startsWith(“New York”))
.collect(Collectors.toSet());
assertEquals(1, newYorkCity.size(), “New York is an upcoming city”);
}
复制代码
这两行执行脚本用于解决我正在测试的另一个应用程序中的提示问题。我已经告诉驱动程序忽略它们。(虽然这里不需要它,但我发现我在任何使用 Chrome 驱动程序的地方都使用了它,所以我永远都不需要解决这个问题,这已经够痛苦的了!)
使用 Chrome 驱动程序的另一个缺点是它需要定期更新以支持 Chrome 的后续版本。
Gecko 驱动程序
Chrome 是第一个进行无头浏览器测试的,所以这是我最熟悉的一个。然而,Firefox 也有无头模式。就像 Chrome 一样。你可以下载Gecko驱动程序,并在你的 pom.xml 中使用 selenium-firefox-driver。
JBrowser 驱动程序
虽然我喜欢 Chrome 驱动程序,但它确实需要安装 Chrome。我有一个项目,我每天运行一个检查,看看 Oracle 认证目标是否有变化。在我运行这项检查的服务器上没有安装 Chrome。最近,Oracle 更新了他们的网站,更多地使用了 AJAX。PhantomJS 不再满足我的需求,所以我开始寻找一个更现代的驱动程序。
我找到了JBrowser驱动程序。该项目去年一直有定期的提交,包括针对 Selenium 版本的更新。它有良好的许可协议(Apache 2)。1.0.0 版本刚刚在 2018 年夏天发布。不过,1.0 之前的版本已经发布近 3 年了。
JBrowser 驱动程序最大的缺点是目前只支持 Oracle JDK Java 8。这个版本的 Java 将分别在 2019 年 1 月和 2020 年 12 月停止为企业用户和个人用户提供补丁。
关于 Java FX,请注意:
在 Java 7 中,Java FX 是单独下载的;
在 Java 8 中,Java FX 是 Oracle JDK 的组成部分,但不是 Open JDK 的组成部分;
在 Java 11 中,Java FX 可以通过 Maven 依赖使用OpenJFX免费获得;
同样,在 Java 11 中,Robot.java 类所在的包从com.sun.glass.ui变成了javafx.scene.robot。这意味着你不能只在 Open JDK 11 中使用 OpenJFX 的 JavaFX 版本,就期望 JBrowser 驱动程序能够正常工作。
代码很简单,Java 8版本在 GitHub 库中。
import com.machinepublishers.jbrowserdriver.JBrowserDriver;
import com.machinepublishers.jbrowserdriver.Settings;
import com.machinepublishers.jbrowserdriver.Timezone;
import com.machinepublishers.jbrowserdriver.UserAgent;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JBrowserSeleniumIT {
protected WebDriver driver;
// ----------------------------------------------------
@BeforeEach
public final void connect() {
driver = new JBrowserDriver(Settings.builder()
.timezone(Timezone.AMERICA_NEWYORK)
.userAgent(UserAgent.CHROME).build());
// says 120 but is really 0
driver.manage().timeouts().pageLoadTimeout(120, TimeUnit.SECONDS);
}
@AfterEach
public final void closeDriver() {
if (driver != null) {
driver.quit();
}
}
@Test
void qconDates() {
driver.get(“https://www.infoq.com“);
Set<String> newYorkCity = driver.findElements(By.className(“qcon”))
.stream()
.map(element -> element.getAttribute(“innerText”))
.filter(city -> city.trim().startsWith(“New York”))
.collect(Collectors.toSet());
assertEquals(1, newYorkCity.size(), “New York is an upcoming city”);
}
}
复制代码
小结——可选驱动对比
使用 Java 和 Selenium 进行无头浏览器测试有很多选择。就像任何良好的工程问题一样,需要在它们之间进行权衡。这张表列出了你的主要选项。我的经验是,如果你需要在没有安装真正浏览器的情况下运行无头 Selenium 驱动程序,那么你需要暂时使用 Java 8。
关于作者
Jeanne Boyarsky 是一名 Java 开发人员和兼职 ScrumMaster。她与人合著了 Wiley 出版的 OCA/OCP 8 认证书籍,并将针对认证的下一个版本进行更新。除了在 CodeRanch 做志愿者外,她还在一个高中机器人团队中指导程序员,并获得了导师奖。Jeanne 曾在 JavaOne、QCon、DevNexus 和 SpringOne 等会议上演讲。
查看英文原文:https://www.infoq.com/articles/headless-selenium-browsers
评论