WebSocket相关Selenium测试用例单独通过,批量运行失败排查
问题:批量运行Selenium测试时WebSocket端口未释放导致元素不可交互
问题背景
使用HTML作为WebSocket客户端、Java实现WebSocket服务器(绑定端口8800),通过Selenium对页面进行自动化测试。现象如下:
- 单独运行任意一个测试用例均可通过
- 批量运行时,第一个测试通过,第二个测试抛出
ElementNotInteractableException,页面中startButton始终处于隐藏状态(正常连接服务器后按钮会显示) - 批量运行时,第一个测试的服务器会打印
Server started!日志,第二个测试无该日志 - 复制HTML页面并修改为连接其他端口(如8802)后,批量运行两个测试均通过
相关代码片段
客户端HTML代码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>something</title> </head> <body> <div ><button id="startButton" onclick="start()" style="visibility: hidden;">START</button></div> <script type="text/javascript"> var webSocket = new WebSocket("ws://localhost:8800"); var startButton = document.getElementById("startButton"); webSocket.onopen = function(event){ wsOpen(event);}; webSocket.onmessage = function(event){ wsGetMessage(event);}; webSocket.onclose = function(event){ wsClose(event);}; webSocket.onerror = function(event){ wsError(event);}; function start(){ wsSendMessage("start,"+player_id); startButton.innerText = "STARTED"; startButton.disabled="true"; } function wsSendMessage(message){ webSocket.send(message); } </script> </body> </html>
WebSocket服务器代码
import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; import java.io.IOException; import java.net.InetSocketAddress; import java.util.*; public class Server extends WebSocketServer { public Server(int port) { super(new InetSocketAddress(port)); } @Override public void onStart() { System.out.println("Server started!"); setConnectionLostTimeout(0); setConnectionLostTimeout(500); } // 其他逻辑 }
Selenium测试基础配置
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openqa.selenium.*; import org.openqa.selenium.chrome.ChromeDriver; import io.github.bonigarcia.wdm.WebDriverManager; import java.nio.file.Path; import java.nio.file.Paths; import static org.junit.jupiter.api.Assertions.*; public class Test { WebDriver driver1; String path = "path/web.html"; Path sampleFile; Server server; JavascriptExecutor js1; @BeforeAll static void setupClass() { WebDriverManager.chromedriver().setup(); } @BeforeEach void setup() throws InterruptedException { driver1 = new ChromeDriver(); js1 = (JavascriptExecutor) driver1; sampleFile = Paths.get(path); server = new Server(8800); server.start(); } @AfterEach void teardown() throws InterruptedException { driver1.quit(); } }
测试用例代码
@Test void row41() throws InterruptedException { driver1.get(sampleFile.toUri().toString()); driver1.findElement(By.id("startButton")).click(); // 其他操作 assertEquals(2, server.getNextPlayer()); } @Test void row42() throws InterruptedException { driver1.get(sampleFile.toUri().toString()); driver1.findElement(By.id("startButton")).click(); // 其他操作 }
异常信息
org.openqa.selenium.ElementNotInteractableException: element not interactable (Session info: chrome=107.0.5304.121) at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.createException(W3CHttpResponseCodec.java:200) at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:133) at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:53) at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:184) at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:167) at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:142) at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:567) at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:251) at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:77) at project.Test.row42
根因分析
批量运行时,第一个测试结束后WebSocket服务器未彻底关闭,端口8800处于TIME_WAIT状态或被残留连接占用,导致第二个测试的服务器实例无法正常绑定端口启动。客户端因无法连接到服务器,startButton始终保持隐藏状态,最终引发Selenium的元素不可交互异常。
解决方法
1. 测试销毁阶段彻底关闭WebSocket服务器
修改AfterEach方法,添加服务器关闭逻辑,确保端口被释放:
@AfterEach void teardown() throws InterruptedException, IOException { driver1.quit(); // 关闭服务器并等待连接断开、端口释放 if (server != null) { server.stop(1000); // 传入超时时间,确保资源清理完成 server = null; } }
stop(long timeout)方法会等待所有客户端连接关闭,再释放服务器占用的端口,避免残留占用。
2. 测试启动前检查并等待端口释放
在BeforeEach中添加端口占用检查,确保8800端口可用后再启动服务器:
// 新增端口检查方法 private boolean isPortInUse(int port) { try (Socket socket = new Socket("localhost", port)) { return true; } catch (IOException e) { return false; } } @BeforeEach void setup() throws InterruptedException { driver1 = new ChromeDriver(); js1 = (JavascriptExecutor) driver1; sampleFile = Paths.get(path); // 重试等待端口释放,最多等待5秒 int retryCount = 0; while (isPortInUse(8800) && retryCount < 5) { Thread.sleep(1000); retryCount++; } server = new Server(8800); server.start(); // 等待服务器完全启动 Thread.sleep(500); }
该逻辑会避免在端口处于TIME_WAIT状态时启动新服务器,确保服务器能正常绑定端口。
3. 为每个测试分配独立动态端口
放弃固定端口,为每个测试动态分配可用端口,并通过JS注入修改客户端的WebSocket连接地址:
@BeforeEach void setup() throws InterruptedException, IOException { driver1 = new ChromeDriver(); js1 = (JavascriptExecutor) driver1; sampleFile = Paths.get(path); // 动态获取可用端口 ServerSocket tempSocket = new ServerSocket(0); int freePort = tempSocket.getLocalPort(); tempSocket.close(); // 启动服务器 server = new Server(freePort); server.start(); Thread.sleep(500); // 打开页面并修改WebSocket连接地址 driver1.get(sampleFile.toUri().toString()); js1.executeScript("webSocket = new WebSocket('ws://localhost:" + freePort + "');"); }
每个测试使用独立端口,彻底避免端口冲突问题,无需复制多个HTML文件。
内容的提问来源于stack exchange,提问作者zxcisnoias
相关产品推荐
相关产品推荐

