「Selenium Grid 3」- 使用 Java 语言

更新日期:2020年09月25日

我们更多的是在 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 等等

保存 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 Grid Tutorial: Hub & Node (with Example)
Selenium Java / API / Overview


ToC

相关链接

连接 Selenium Hub 节点

设置窗口大小及位置

在页面中,选择 HTML 元素

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

正确的休眠(等待)方法

Implicit Wait

Explicit wait

Fluent Wait

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

向标签内填充 HTML 代码

对于复杂的 HTML 代码填充

判断页面是否加载完成

参考文献