Selenium可用于编写UI自动化测试脚本,其可以结合TestNG一起使用。熟悉TestNG的人都知道,@BeforeMethod和@AfterMethod是当前测试类中每个测试用例方法运行之前运行和每个测试用例方法运行结束之后运行
按照之前测试类的方法,经常是一个测试类中只有一个测试方法,所以会把打开浏览器请求页面的方法写在@BeforeMethod中,同时关闭浏览器的方法写在@AferMethod中,直到今天尝试在一个测试类中写两个测试方法时,突然暴露出了问题,大致的代码如下
public class TestBeforeAndAfter {
public WebDriver webDriver = new ChromeDriver();
String baseUrl = "https://www.sogou.com/";
@BeforeClass
public void setUp() {
System.setProperty("webdriver.chrome.driver", "/usr/local/bin/chromedriver");
}
@BeforeMethod
public void openBrowser() {
webDriver.get(baseUrl);
}
@AfterMethod
public void closeBrowser() {
webDriver.quit();
}
@Test
public void testOne() {
webDriver.findElement(By.id("query"));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testTwo() {
webDriver.findElement(By.id("query"));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
当执行该测试类时,抛出如下异常
Starting ChromeDriver 101.0.4951.41 (93c720db8323b3ec10d056025ab95c23a31997c9-refs/branch-heads/4951@{#904}) on port 18231
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
5月 28, 2022 6:50:09 下午 org.openqa.selenium.remote.ProtocolHandshake createSession
信息: Detected dialect: W3C
org.openqa.selenium.NoSuchSessionException: Session ID is null. Using WebDriver after calling quit()?
Build info: version: '4.0.0-alpha-1', revision: 'd1d3728cae', time: '2019-04-24T13:42:21'
System info: XXXX
Driver info: driver.version: RemoteWebDriver
at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:128)
at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:83)
根据异常信息,简单的翻译一下就是session Id为null,也就是没有会话状态了
通过阅读源码(RemoteWebDriver类)可以发现,关闭浏览器的quit方法在执行的时候,会将当前WebDriver对象中的Session id置为null,而WebDriver对象中在打开浏览器的get方法中,需要使用session Id来new一个Command对象
public class RemoteWebDriver implements WebDriver, JavascriptExecutor, FindsById, FindsByClassName, FindsByLinkText, FindsByName, FindsByCssSelector, FindsByTagName, FindsByXPath, HasInputDevices, HasCapabilities, Interactive, TakesScreenshot {
public void quit() {
if (this.sessionId != null) {
try {
this.execute("quit");
} finally {
this.sessionId = null; // 最后会被为null
}
}
}
...
Response execute(CommandPayload payload) {
Command command = new Command(this.sessionId, payload); // webdriver.get(xx)将会执行到这里
long start = System.currentTimeMillis();
String currentName = Thread.currentThread().getName();
Thread.currentThread().setName(String.format("Forwarding %s on session %s to remote", command.getName(), this.sessionId));
...
}
}
...
在上例中,testOne测试方法首先执行@BeforeMethod来创建一个Command对象,并且打开浏览器,此时使用的session Id是类第一次初始化WebDriver对象(这边别名为 1号WebDriver对象)时产生的session Id,即public WebDriver webDriver = new ChromeDriver();该代码生成的session Id,在测试方法结束后,执行@AfterMethod将 1号WebDriver对象 中的session Id给置为null
testTwo方法在执行时,依旧首先执行@BeforeMethod来创建一个Command对象,此时使用的session Id依旧为 1号WebDriver对象 中的,但此时的session Id已经为null,从而导致后面在执行get方法时,报最上面的错误最终抛异常的类为HttpCommandExecutor
public class HttpCommandExecutor implements CommandExecutor, NeedsLocalLogs {
...
public Response execute(Command command) throws IOException {
if (command.getSessionId() == null) {
if ("quit".equals(command.getName())) {
return new Response();
}
if (!"getAllSessions".equals(command.getName()) && !"newSession".equals(command.getName())) {
throw new NoSuchSessionException("Session ID is null. Using WebDriver after calling quit()?");
}
}
...
}
...
}
其实最主要的问题出在
- 在上述代码中WebDriver这个对象被设定为实例变量,并且直接在定义这个实例变量的时候便将其new了一个新的对象public WebDriver webDriver = new ChromeDriver();,也就是说我整个测试类中的所有测试方法都将公用一个对象,testOne方法和testTwo方法都只想了同一个对象
- @AfterMethod方法中的quit方法,将浏览器进行了关闭
定位到了问题,也就可以很快速的解决我们的问题,只要将testOne测试方法和testTwo测试方法指向的WebDriver对象不是同一个即可,将new方法写到@BeforeMethod方法中
public class TestBeforeAndAfter {
public WebDriver webDriver;
String baseUrl = "https://www.sogou.com/";
@BeforeClass
public void setUp() {
System.setProperty("webdriver.chrome.driver", "/usr/local/bin/chromedriver");
}
@BeforeMethod
public void openBrowser() {
webDriver = new ChromeDriver();
webDriver.get(baseUrl);
}
...
}
执行测试类,此时已可正常执行
当然不使用@BeforeMethod和@AfterMethod来编写测试类,不会出现上述问题