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
相关产品推荐
相关产品推荐

