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

Appium+BrowserStack配置代理创建远程会话报隧道连接数超限错误

问题根因

org.openqa.selenium.SessionNotCreatedException: Unable to create new remote session. Original error: Too many tunnel connections attempted: 21是BrowserStack服务端返回的拦截错误,触发原因是账号下活跃的Local Testing隧道连接数超过了配额上限,和代码里的连接泄漏、配置缺陷直接相关:

  • 每次调用connectViaProxy都会新建独立的OkHttpClient实例与连接池,旧连接没有被回收,持续占用隧道配额
  • 历史运行残留的AndroidDriver实例没有主动执行quit()操作,对应的隧道连接被BrowserStack判定为活跃状态,累计到阈值后就会拒绝新建连接
  • 代理认证逻辑没有重试限制,代理返回407错误时会无限重试发起新连接,短时间内产生大量无效隧道计数
代码现存问题
  • OkHttpClient未做单例复用:OkHttpClient设计为全局单例使用,每次新建实例会创建独立连接池,闲置连接无法被统一回收
  • 缺少资源释放逻辑:代码仅做driver初始化,没有在用例结束、异常抛出时主动关闭会话释放隧道
  • 代理认证无重试拦截:Authenticator逻辑没有判断是否已经重试过认证,会无限发起重复请求
  • 未配置连接回收规则:闲置连接不会被主动关闭,长期占用配额
修复步骤

1. 释放现有残留连接

先登录BrowserStack控制台,手动终止所有处于活跃状态的Local Testing隧道,清空被占用的配额,否则新连接会持续被拦截。

2. 重构代码修复泄漏问题

将OkHttpClient调整为全局单例,增加认证重试限制,补充会话回收逻辑,参考实现如下:

import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.openqa.selenium.remote.internal.HttpClientFactory;
// 其他导入省略

// 全局单例OkHttpClient,全局复用连接池
private static final OkHttpClient BROWSERSTACK_OKHTTP_CLIENT;
static {
    String proxyHost = Config.getValue("proxy.host");
    int proxyPort = Config.getValueint("proxy.port");
    String proxyUserDomain = Config.getValue("proxy.user-domain");
    String proxyUser = Config.getValue("proxy.user");
    String proxyPassword = EncryptionUtil.passwordDecoder(Config.getValue("proxy.encrypted-password").getBytes());

    BROWSERSTACK_OKHTTP_CLIENT = new OkHttpClient.Builder()
            .connectTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            // 配置连接池,闲置5分钟的连接自动回收
            .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
            .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)))
            .proxyAuthenticator((route, response) -> {
                // 已经携带过认证头直接返回null,避免无限重试
                if (response.request().header("Proxy-Authorization") != null) {
                    return null;
                }
                String credential = Credentials.basic(proxyUserDomain + "\\" + proxyUser, proxyPassword);
                return response.request().newBuilder()
                        .header("Proxy-Authorization", credential)
                        .build();
            })
            .build();
}

public static AndroidDriver<AndroidElement> connectViaProxy(DesiredCapabilities caps) throws IOException {
    // 新建会话前先清理残留的driver实例,释放旧连接
    if (General.driver != null) {
        try {
            General.driver.quit();
        } catch (Exception e) {
            // 忽略旧实例关闭异常
        }
    }

    URL remoteUrl;
    try {
        remoteUrl = new URL(String.format("https://%s:%s@hub-cloud.browserstack.com/wd/hub",
                Config.getValue("BrowserStack.userName"),
                Config.getValue("BrowserStack.accessKey")));
    } catch (MalformedURLException e) {
        throw new RuntimeException("Invalid BrowserStack hub URL", e);
    }

    HttpClientFactory factory = new MyHttpClientFactory(new org.openqa.selenium.remote.internal.OkHttpClient(BROWSERSTACK_OKHTTP_CLIENT, remoteUrl));
    HttpCommandExecutor executor = new AppiumCommandExecutor(MobileCommand.commandRepository, remoteUrl, factory);
    AndroidDriver<AndroidElement> driver = new AndroidDriver<>(executor, caps);
    General.driver = driver;
    return driver;
}

// 测试执行完成必须调用该方法释放资源
public static void releaseDriver() {
    if (General.driver != null) {
        General.driver.quit();
        General.driver = null;
    }
}

3. 补充执行流程的资源保障

  • 所有测试用例执行完成后,必须显式调用releaseDriver()关闭会话,不要依赖JVM退出自动释放资源
  • 用例执行抛出异常时,在finally块中调用releaseDriver(),避免异常中断导致连接残留
注意事项
  • 如果是并行执行测试,需要提前确认BrowserStack账号的Local Tunnel最大并发配额,并行执行的用例数不要超过配额上限
  • 不要在短时间内高频反复调用connectViaProxy新建会话,每次新建前必须确认前一个会话已经正常关闭

内容的提问来源于stack exchange,提问作者Amruta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 03:36:56