You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 03:55:22