0

0

解决Selenium并发测试WebSocket应用中的端口占用问题

聖光之護

聖光之護

发布时间:2025-11-05 15:44:03

|

623人浏览过

|

来源于php中文网

原创

解决Selenium并发测试WebSocket应用中的端口占用问题

在selenium测试websocket应用的场景中,当多个测试用例并发执行时,可能会出现独立测试通过但批量执行失败的情况。这通常是由于websocket服务器实例在测试之间未正确关闭,导致端口被占用而无法为后续测试启动新的服务器。本文将深入分析此问题,并提供通过在测试清理阶段确保服务器资源释放的解决方案,以实现稳定可靠的自动化测试。

问题现象:Selenium测试WebSocket应用时的并发挑战

在使用Selenium对基于WebSocket的Web应用进行自动化测试时,开发者可能会遇到一个令人困惑的现象:单个测试用例(例如row41())能够成功执行并验证预期行为,但当多个测试用例(如row41()和row42())同时运行在一个测试套件中时,除了第一个测试用例外,后续的测试用例会失败。常见的错误信息是org.openqa.selenium.ElementNotInteractableException: element not interactable,这通常意味着页面上的元素(如“startButton”)未能正确加载或变得可交互。进一步观察发现,在并发执行时,只有第一个测试用例的WebSocket服务器会成功启动并打印“Server started!”,而后续测试用例对应的服务器则没有启动日志。这暗示了资源冲突的存在。

根本原因分析:资源未释放导致的端口冲突

此问题的核心在于WebSocket服务器实例的生命周期管理不当。在提供的测试配置中,每个测试用例都会在@BeforeEach方法中启动一个新的Server实例,并使其监听在固定的8800端口。然而,在@AfterEach方法中,虽然Selenium WebDriver(driver1)被正确关闭,但对应的WebSocket服务器实例(server)却没有被显式地停止或关闭。

当测试用例独立运行时,前一个测试完成后,JVM可能在短时间内释放了所有资源,包括被占用的端口,使得下一个独立运行的测试能够成功启动新的服务器。但当多个测试用例在同一个JVM进程中连续执行时,前一个测试用例的Server实例可能仍然在后台运行,或者操作系统尚未完全释放其占用的8800端口。当第二个测试用例尝试在@BeforeEach中启动一个新的Server实例并监听同一端口时,就会因为端口已被占用而失败。WebSocket服务器未能成功启动,导致客户端页面无法建立连接,进而使得依赖于WebSocket连接的页面元素(如startButton)无法进入预期状态,最终引发Selenium的ElementNotInteractableException。

解决方案:确保WebSocket服务器的正确关闭

要解决这个问题,关键是在每个测试用例执行完毕后,显式地停止WebSocket服务器并释放其占用的端口。这可以通过在@AfterEach方法中调用WebSocket服务器的关闭方法来实现。对于org.java_websocket.server.WebSocketServer,其关闭方法通常是stop()。

图可丽批量抠图
图可丽批量抠图

用AI技术提高数据生产力,让美好事物更容易被发现

下载

以下是修改后的测试配置示例:

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 MyWebSocketTest { // Renamed Test to MyWebSocketTest to avoid conflict
    WebDriver driver1;
    String path = "path/web.html"; // 确保路径正确
    Path sampleFile;
    Server server; // WebSocket服务器实例
    JavascriptExecutor js1;

    @BeforeAll
    static void setupClass() {
        WebDriverManager.chromedriver().setup();
    }

    @BeforeEach
    void setup() throws InterruptedException {
        driver1 = new ChromeDriver();
        js1 = (JavascriptExecutor) driver1;
        sampleFile = Paths.get(path);

        // 启动WebSocket服务器
        server = new Server(8800);
        server.start();
        // 确保服务器有足够时间启动并监听端口
        Thread.sleep(500); // 可以根据实际情况调整或使用更智能的等待机制
    }

    @AfterEach
    void teardown() throws InterruptedException {
        // 关闭Selenium WebDriver
        if (driver1 != null) {
            driver1.quit();
        }
        // 停止WebSocket服务器并释放端口
        if (server != null) {
            try {
                server.stop();
                // 给予操作系统一些时间来释放端口
                Thread.sleep(500);
            } catch (IOException | InterruptedException e) {
                System.err.println("Error stopping WebSocket server: " + e.getMessage());
                Thread.currentThread().interrupt(); // 重新中断当前线程
            }
        }
    }

    @Test
    void testCaseOne() throws InterruptedException {
        driver1.get(sampleFile.toUri().toString());
        // 确保页面加载完成,并且WebSocket连接已建立
        // 可以添加显式等待,例如等待某个元素可见或JavaScript变量就绪
        WebDriverWait wait = new WebDriverWait(driver1, Duration.ofSeconds(10));
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("startButton")));

        driver1.findElement(By.id("startButton")).click();

        // 模拟其他操作
        // assertEquals(2, server.getNextPlayer()); // 假设getNextPlayer方法存在
    }

    @Test
    void testCaseTwo() throws InterruptedException {
        driver1.get(sampleFile.toUri().toString());
        // 确保页面加载完成,并且WebSocket连接已建立
        WebDriverWait wait = new WebDriverWait(driver1, Duration.ofSeconds(10));
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("startButton")));

        driver1.findElement(By.id("startButton")).click();
        // 模拟其他操作
    }
}

关键改进点:

  1. server.stop(): 在@AfterEach方法中添加server.stop(),确保每个测试用例结束后WebSocket服务器都被正确关闭。
  2. 延迟等待: 在server.start()和server.stop()之后都添加了Thread.sleep(500)。虽然Thread.sleep不是最佳实践,但在测试环境中可以作为一种快速解决端口释放时间问题的手段。更健壮的方法是等待服务器的onStop()回调或者通过检查端口是否真正空闲。
  3. 显式等待: 在测试用例内部,添加WebDriverWait来等待页面元素(如startButton)变得可见和可交互。这有助于处理页面加载和WebSocket连接建立的异步性,避免因元素未就绪而导致的ElementNotInteractableException。
  4. 异常处理: 在server.stop()周围添加try-catch块,以优雅地处理服务器关闭过程中可能出现的异常。

最佳实践与注意事项

  1. 资源管理的重要性: 自动化测试中对外部资源(如数据库连接、文件句柄、网络端口、外部服务)的生命周期管理至关重要。任何未正确释放的资源都可能导致后续测试的失败,尤其是在并发或连续执行的场景中。
  2. 动态端口分配: 如果可能,考虑为每个测试用例或测试套件使用一个动态分配的端口,而不是硬编码固定端口。这可以进一步避免端口冲突,尤其是在大型测试套件或并行测试环境中。例如,可以使用ServerSocket来查找一个可用的随机端口。
  3. 服务器状态检查: 在@BeforeEach中启动服务器后,可以添加逻辑来确认服务器确实已启动并正在监听端口,而不是简单地依赖Thread.sleep。例如,可以尝试连接该端口,或者通过服务器内部状态标志来确认。
  4. 测试隔离: 确保每个测试用例都是独立的,不依赖于其他测试用例的执行状态。这有助于提高测试的稳定性和可维护性。
  5. 异步操作的处理: WebSocket通信本质上是异步的。在Selenium测试中,对于涉及WebSocket消息发送和接收的场景,应使用显式等待(WebDriverWait)来等待预期的UI变化或数据更新,而不是使用固定的Thread.sleep。

总结

当Selenium测试WebSocket应用在批量运行时出现失败,而单独运行时通过时,最常见的原因是WebSocket服务器实例在测试之间未正确关闭,导致端口冲突。通过在@AfterEach清理方法中显式调用server.stop()来停止WebSocket服务器,并结合适当的等待机制,可以有效解决此问题,确保测试用例的隔离性和稳定性。良好的资源管理是构建健壮自动化测试套件的关键。

相关专题

更多
Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

53

2025.12.01

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

324

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2066

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

346

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

250

2023.09.05

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

315

2023.10.09

数据库对象名无效怎么解决
数据库对象名无效怎么解决

数据库对象名无效解决办法:1、检查使用的对象名是否正确,确保没有拼写错误;2、检查数据库中是否已存在具有相同名称的对象,如果是,请更改对象名为一个不同的名称,然后重新创建;3、确保在连接数据库时使用了正确的用户名、密码和数据库名称;4、尝试重启数据库服务,然后再次尝试创建或使用对象;5、尝试更新驱动程序,然后再次尝试创建或使用对象。

400

2023.10.16

vb连接access数据库的方法
vb连接access数据库的方法

vb连接access数据库方法:1、使用ADO连接,首先导入System.Data.OleDb模块,然后定义一个连接字符串,接着创建一个OleDbConnection对象并使用Open() 方法打开连接;2、使用DAO连接,首先导入 Microsoft.Jet.OLEDB模块,然后定义一个连接字符串,接着创建一个JetConnection对象并使用Open()方法打开连接即可。

363

2023.10.16

苹果官网入口直接访问
苹果官网入口直接访问

苹果官网直接访问入口是https://www.apple.com/cn/,该页面具备0.8秒首屏渲染、HTTP/3与Brotli加速、WebP+AVIF双格式图片、免登录浏览全参数等特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

10

2025.12.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 2.9万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 1.7万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号