「Selenium Grid 3」- 使用 Java / Groovy 语言 @20210308

我们更多的是在 Jenkins Pipeline 中使用 Selenium 框架,因此需要使用 Groovy 类库。由于没有与之对应的 Groovy 类库,因此只能使用 Java 类库。

还有另外种做法:使用 Python 实现,然后在 Groovy 中命令行调用。但是我们无法使用该方法,因为 Selenime 的自动化测试过程中需要交互、判断,而这种方法无法获取状态,只能输入执行然后等待输出。

相关链接

Maven Repository: org.seleniumhq.selenium » selenium-java(我们使用 Selenium Grid 3 版本)
下载页面:Downloads
接口文档:https://www.selenium.dev/selenium/docs/api/java/index.html

Selenium with Java: Best Practices

连接 Selenium Hub 节点

import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.Platform;

import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.WebDriver;
import java.net.URL;

DesiredCapabilities desiredCapabilities = DesiredCapabilities.chrome();
desiredCapabilities.setBrowserName("chrome");
desiredCapabilities.setPlatform(Platform.LINUX);

String seleniumHubUrl = "http://ip-address:port-number/wd/hub";
WebDriver webDriver = new RemoteWebDriver(new URL(seleniumHubUrl), desiredCapabilities);

设置窗口大小及位置

python - How do I set browser width and height in Selenium WebDriver? - Stack Overflow
Selenium Waits: Implicit, Explicit, Fluent And Sleep

import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;

webDriver.manage().window().setPosition(new Point(0, 0));
webDriver.manage().window().setSize(new Dimension(1366, 768));

// 窗口最大化
webDriver.manage().window().maximize();

在页面中,选择 HTML 元素

Find Element and FindElements in Selenium WebDriver

// 最常规的用法:通过 ID 选择元素
webDriver.findElement(By.id("buttoncheck"))

// 通过 XPath 选择元素
webDriver.findElement(By.xpath("//div[@id='writeArticleWrapper']//form//input[@type='text' and @name='title']"));

定位元素的方法有很多,比如 ID、Name、Class Name、Tag Name、Link Text、Partial Link Text、XPATH 等等

在页面中,点击按钮(或元素)

Test Automation With Selenium Click Button Method(Examples)
Check if element is clickable in Selenium Java - Stack Overflow

通常点击元素使用 click() 方法即可:

// 选择元素并进行点击
webDriver.findElement(By.id("buttoncheck")).click()

// 等待元素可以点击
 new WebDriverWait(webDriver, 10).until(ExpectedConditions.elementToBeClickable(By.xpath("xpath-query"))).click()

复杂的点击操作(长按、右键、双击),可以参考 Test Automation With Selenium Click Button Method(Examples) 页面

关于点击需要注意的事项

现实世界是复杂的,我们将在该部分中记录我们遇到的问题(与点击相关)。

# 10/03/2020 某些元素,当在浏览器窗口(viewport)中可见时,才会被绑定点击事件。因此,如果没有滚动到该元素使其可见,点击动作是无效的。在 Firefox 80.0.1 (64-bit) 中,滚动至元素可见,会发现在 Inspector 中该元素的后面后显示 event 标签。使用如下代码进行滚动及滚动完成的检查:

// 下面是与滚动至元素可见的代码的参考文献
// https://www.guru99.com/scroll-up-down-selenium-webdriver.html
// https://stackoverflow.com/questions/42982950/how-to-scroll-down-the-page-till-bottomend-page-in-the-selenium-webdriver

// 第一步、执行滚动
((JavascriptExecutor) webDriver).executeScript('arguments[0].scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});', bodyElement);

// 理解 scrollIntoView 需要:
// 阅读:https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
// 在我们的场景中,block: "end" 是关键设置,否则会出现“没有显示元素,而下面滚动已经提示完成”,导致事件没有绑定到元素上便触发点击事件。

// 等待滚动完成
new WebDriverWait(webDriver, 30).until(new Function<WebDriver, Boolean>() {
    @Override
    public Boolean apply(WebDriver tmpWebDriver) {
    	String isVisibleJavascript =  "return (arguments[0].getBoundingClientRect().top >= 0) && (arguments[0].getBoundingClientRect().bottom <= window.innerHeight);"
		Object pageReady = ((JavascriptExecutor) tmpWebDriver).executeScript(isVisibleJavascript, bodyElement);
		Boolean complete = pageReady.toString().equals("true");
		System.out.println("检查滚动是否完成:${complete}"); // Groovy
		return complete;
    }
});

// 要理解 等待滚动完成 代码,需要阅读以下文档(依序):
// https://stackoverflow.com/questions/487073/how-to-check-if-element-is-visible-after-scrolling
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
// https://developer.mozilla.org/en-US/docs/Web/API/Window/innerHeight

保存 Cookie 信息(保留登录状态)

我们没有找到保存 Cookie 的专有方法,所以我们采用自己的方案:1)将 Cookie 对象保存到文件,2)启动时再载入 Cookie 对象

将 Cookie 保存到文件:

private void cookieWriteToFile(WebDriver webDriver) throws Exception {
	ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/path/to/cookie.bin"));
	objectOutputStream.writeObject(webDriver.manage().getCookies());
}

从文件中读取 Cookie:

private void cookieWriteToFile(WebDriver webDriver) throws Exception {
	ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/path/to/cookie.bin"));
	Set<Cookie> cookies = (Set<Cookie>) objectInputStream.readObject();
	for (Cookie cookie : cookies) {
	    webDriver.manage().addCookie(cookie);
	}
}

该方法的本质是:保存二进制对象到文件,之后从文件恢复二进制对象

注意事项,该方法适用于 Java 语言,而 Groovy 语言存在其他问题,参考 Write an Object to File and Read it 笔记。

正确的休眠(等待)方法

Using Thread.sleep() in Selenium WebDriver - Make Selenium Easy

当我们加载页面后,可能需要等待页面渲染,等待某个 HTML 元素加载完成。我们经常使用 Thread.sleep() 进行等待,但是具有以下缺点:
1)等待时间过长,而页面已经加载完成;等待时间过短,而页面还未加载完成;
2)我们无法确定要等待的具体时间。如果使用 while 循环检查,程序会显得“不整洁”;
3)每个查找元素的地方都需要等待;
4)必须等待特定时间后,即 Thread.sleep() 设置的时间,才能继续执行后续程序;

我们可以使用 Selenium 提供的等待方法:
1)Implicit wait – Provided by Selenium WebDriver
2)Explicit wait (WebDriverWait & FluentWait) Provided by Selenium WebDriver
3)Fluent Wait

Implicit Wait

如下是演示代码(只包含关键部分),我们通过演示代码进行讲解:

WebDriver driver=new ChromeDriver();

driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);

driver.get("https://www.easemytrip.com/");
driver.findElement(By.id("FromSector_show")).sendKeys("Delhi", Keys.ENTER);
driver.findElement(By.id("Editbox13_show")).sendKeys("Mumbai", Keys.ENTER);

如上示例,使用 implicitlyWait 最多 30s 等待,具有以下优势:
1)在 findElement 时,最多 30s 等待,只要找元素就立即向下执行;
2)如果在 30s 内没有找到,则返回 ElementNotVisibleException 异常;
3)全局设置(只需要设置一次,无需在每次查找元素时进行设置);

但是我们会遇到另外场景,比如:虽然 HTML 元素已经找到,但是在页面元素是否可见、是否可以点击,这些会影响自动化测试的进行。针对这个问题,我们可以使用 Explicit wait 等待。

Explicit wait

如下是演示代码(只包含关键部分),我们通过演示代码进行讲解:

WebDriver driver = new ChromeDriver();
driver.get("https://www.rentomojo.com/");

// 等待页面元素可见
WebDriverWait wait = new WebDriverWait(driver, 120);
wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.xpath("//div[@class='Campaign__innerWrapper']/button"))));
driver.findElement(By.xpath("//div[@class='Campaign__innerWrapper']/button")).click();

// 等待 body 中出现内容
// https://stackoverflow.com/questions/15656252/wait-till-text-present-in-text-field/15657053
new WebDriverWait(driver, 120).until(new ExpectedCondition<Boolean>() {
	@Override
	public Boolean apply(WebDriver input) {
		WebElement bodyElement = input.findElement(By.xpath("html/body"));
		return !"".equals(bodyElement.getAttribute("innerHTML").trim());
	}
});

如上程序,通过 visibilityOf 方法等待,直到特定元素可见。通过该方法可以判断某些 HTML 元素是否已经处于特定状态。还有很多其他状态,参考 ExpectedConditions 文档。

Fluent Wait

类似与 Explicit wait 等待,但是更加灵活,可以自定义等待时间粒度、忽略异常等等:

Wait<WebDriver> fluentWait = new FluentWait<WebDriver>(driver)
		.withTimeout(60, TimeUnit.SECONDS) // // this defines the polling frequency
		.pollingEvery(2, TimeUnit.SECONDS)
		.ignoring(NoSuchElementException.class); // this defines the exception to ignore

WebElement foo = fluentWait.until(new Function<WebDriver, WebElement>() {
	// in this method defined your own subjected conditions for which
	// we need to wait for
	public WebElement apply(WebDriver driver) {
		return driver.findElement(By.id("foo"));
	}
});

注意事项,我们没有使用过 Fluent Wait 等待,这里只是简单整理,详细方法需要参考官方文档。

获取标签内的 HTML 代码(dom.innerHTML)

How to get HTML source of a Web Element in Selenium WebDriver | BrowserStack

element.getAttribute("innerHTML");

向标签内填充 HTML 代码

Modify innerHTML using Selenium - Stack Overflow

WebElement element = ...
((JavascriptExecutor)driver).executeScript("arguments[0].innerHTML = '<h1>H1</h1>';", element);

对于复杂的 HTML 代码填充

java - put a string with html/Javascript into selenium webdriver - Stack Overflow

在我们的 HTML 内容中,经常会包含复杂的内容,比如单引号、双引号,会破坏 Javascript 语法,导致代码无法执行。

解决方法如下(如下是 Groovy 代码):

@Grab(group='commons-lang', module='commons-lang', version='2.6') // 正好的 Jenkins 所依赖的版本一致
import org.apache.commons.lang.StringEscapeUtils;

String htmlContent = StringEscapeUtils.escapeJavaScript(postInfo.content)
((JavascriptExecutor) webDriver).executeScript("arguments[0].innerHTML = '${htmlContent}';", bodyElement);

判断页面是否加载完成

java - Wait for page load in Selenium - Stack Overflow
java - Selenium -- How to wait until page is completely loaded - Stack Overflow

new WebDriverWait(webDriver, 30).until(new Function<WebDriver, Boolean>() {
	@Override
	public Boolean apply(WebDriver tmpWebDriver) {
		Object pageReady = ((JavascriptExecutor) tmpWebDriver).executeScript("return document.readyState;");
		return "complete".equals(pageReady.toString());
	}
});

切换标签 / 关闭标签

selenium - Is there a way to close a tab in WebDriver or Protractor? - Stack Overflow
java - Clicking links in newly opened tab using WebDriver - Stack Overflow
testing - Switch tabs using Selenium WebDriver with Java - Stack Overflow

我们在点击 a 标签之后,可能会打开新的标签,那么如何切换到新的标签页呢:

// 获取当前标签页句柄
String oldTab = driver.getWindowHandle();

// 获取所有标签句柄
ArrayList<String> handles = new ArrayList<String>(driver.getWindowHandles());

// 切换到新的标签页,
// handles.get(0) 是最开始的标签(即是 oldTab 变量),
// 而 handles.get(1) 新的标签页;
driver.switchTo().window(handles.get(1));

// 切换会原有标签
driver.switchTo().window(oldTab); // driver.switchTo().window(handles.get(0));

// 关闭当前标签页(按照如上代码顺序,这里关闭 oldTab 标签)
driver.close() // 注意区分 quit() 与 close() 方法,这里不再赘述

// 注意事项:因为我们清楚 a 标签打开页面的行为,即在新标签中打开,因此才这样编写代码。如果 a 标签在
// 当前页面中打开,则无需进行切换。
// 如果不清楚 a 标签的行为,则需要我们自己进行判断,比如根据 target 属性。

参考文献

Selenium Grid Tutorial: Hub & Node (with Example)
Selenium Java / API / Overview

Aug 15, 2025 2:21:21 PM com.kms.katalon.core.logging.KeywordLogger startTest INFO: -------------------- Aug 15, 2025 2:21:22 PM com.kms.katalon.core.logging.KeywordLogger startTest INFO: START Test Cases/try Aug 15, 2025 2:21:24 PM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1 WARNING: error sending request for url (https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json) Aug 15, 2025 2:21:24 PM com.kms.katalon.core.logging.KeywordLogger log SEVERE: ❌ Test Cases/try FAILED. Reason: org.openqa.selenium.remote.NoSuchDriverException: Unable to obtain: chromedriver, error Command failed with code: 65, executed: [--browser, chrome, --language-binding, java, --output, json] error sending request for url (https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json) Build info: version: &#39;4.22.0&#39;, revision: &#39;c5f3146703&#39; System info: os.name: &#39;Windows 11&#39;, os.arch: &#39;amd64&#39;, os.version: &#39;10.0&#39;, java.version: &#39;17.0.14&#39; Driver info: driver.version: ChromeDriver at org.openqa.selenium.remote.service.DriverFinder.getBinaryPaths(DriverFinder.java:121) at org.openqa.selenium.remote.service.DriverFinder.getDriverPath(DriverFinder.java:55) at org.openqa.selenium.chrome.ChromeDriver.generateExecutor(ChromeDriver.java:99) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:88) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:83) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:51) at try.run(try:25) at com.kms.katalon.core.main.ScriptEngine.run(ScriptEngine.java:194) at com.kms.katalon.core.main.ScriptEngine.runScriptAsRawText(ScriptEngine.java:119) at com.kms.katalon.core.main.TestCaseExecutor.runScript(TestCaseExecutor.java:448) at com.kms.katalon.core.main.TestCaseExecutor.doExecute(TestCaseExecutor.java:439) at com.kms.katalon.core.main.TestCaseExecutor.processExecutionPhase(TestCaseExecutor.java:418) at com.kms.katalon.core.main.TestCaseExecutor.accessMainPhase(TestCaseExecutor.java:410) at com.kms.katalon.core.main.TestCaseExecutor.execute(TestCaseExecutor.java:285) at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:137) at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:125) at TempTestCase1755238875241.run(TempTestCase1755238875241.groovy:25) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) Caused by: org.openqa.selenium.WebDriverException: Command failed with code: 65, executed: [--browser, chrome, --language-binding, java, --output, json] error sending request for url (https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json) Build info: version: &#39;4.22.0&#39;, revision: &#39;c5f3146703&#39; System info: os.name: &#39;Windows 11&#39;, os.arch: &#39;amd64&#39;, os.version: &#39;10.0&#39;, java.version: &#39;17.0.14&#39; Driver info: driver.version: ChromeDriver at org.openqa.selenium.manager.SeleniumManager.runCommand(SeleniumManager.java:170) at org.openqa.selenium.manager.SeleniumManager.getBinaryPaths(SeleniumManager.java:247) at org.openqa.selenium.remote.service.DriverFinder.getBinaryPaths(DriverFinder.java:102) at org.openqa.selenium.remote.service.DriverFinder.getDriverPath(DriverFinder.java:55) at org.openqa.selenium.chrome.ChromeDriver.generateExecutor(ChromeDriver.java:99) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:88) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:83) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:51) at Script1755238419595.run(Script1755238419595.groovy:25) ... 13 more Aug 15, 2025 2:21:24 PM com.kms.katalon.core.logging.KeywordLogger log SEVERE: ❌ Test Cases/try FAILED. Reason: org.openqa.selenium.remote.NoSuchDriverException: Unable to obtain: chromedriver, error Command failed with code: 65, executed: [--browser, chrome, --language-binding, java, --output, json] error sending request for url (https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json) Build info: version: &#39;4.22.0&#39;, revision: &#39;c5f3146703&#39; System info: os.name: &#39;Windows 11&#39;, os.arch: &#39;amd64&#39;, os.version: &#39;10.0&#39;, java.version: &#39;17.0.14&#39; Driver info: driver.version: ChromeDriver at org.openqa.selenium.remote.service.DriverFinder.getBinaryPaths(DriverFinder.java:121) at org.openqa.selenium.remote.service.DriverFinder.getDriverPath(DriverFinder.java:55) at org.openqa.selenium.chrome.ChromeDriver.generateExecutor(ChromeDriver.java:99) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:88) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:83) at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:51) at try.run(try:25) at com.kms.katalon.core.main.ScriptEngine.run(ScriptEngine.java:194) at com.kms.katalon.core.main.ScriptEngine.runScriptAsRawText(ScriptEngine.java:119) at com.kms.katalon.core.main.TestCaseExecutor.runScript(TestCaseExecutor.java:448) at com.kms.katalon.core.main.TestCaseExecutor.doExecute(TestCaseExecutor.java:439) at com.kms.katalon.core.main.TestCaseExecutor.processExecutionPhase(TestCaseExecutor.java:418) at com.kms.katalon.core.main.TestCaseExecutor.accessMainPhase(TestCaseExecutor.java:410) at com.kms.katalon.core.main.TestCaseExecutor.execute(TestCaseExecutor.java:285) at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:137) at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:125) at TempTestCase1755238875241.run(TempTestCase1755238875241.groovy:25) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) Caused by: org.openqa.selenium.WebDriverException: Command failed with code: 65, executed: [--browser, chrome, --language-binding, java, --output, json] error sending request for url (https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json) Build info: version: &#39;4.22.0&#39;, revision: &#39;c5f3146703&#39; System info: os.name: &#39;Windows 11&#39;, os.arch: &#39;amd64&#39;, os.version: &#39;10.0&#39;, java.version: &#39;17.0.14&#39; Driver info: driver.version: ChromeDriver at org.openqa.selenium.manager.SeleniumManager.runCommand(SeleniumManager.java:170) at org.openqa.selenium.manager.SeleniumManager.getBinaryPaths(SeleniumManager.java:247) at org.openqa.selenium.remote.service.DriverFinder.getBinaryPaths(DriverFinder.java:102) ... 19 more Aug 15, 2025 2:21:24 PM com.kms.katalon.core.logging.KeywordLogger endTest INFO: END Test Cases/try
最新发布
08-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值