Node.js下如何阻止Selenium WebDriver打开多个Firefox实例?
解决Cucumber+Selenium WebDriver打开多个Firefox实例的问题
问题详情
在Node.js环境中使用Selenium WebDriver结合Cucumber编写自动化测试时,测试功能正常,但运行单个测试会打开4个Firefox实例,仅其中一个窗口执行并完成测试,其余实例会残留,需要限制测试时打开的Firefox实例数量。
现有代码
const { Then, Given, When, After } = require('@cucumber/cucumber'); const assert = require('assert'); const { Builder, By, until, Key } = require('selenium-webdriver'); const firefox = require('selenium-webdriver/firefox'); let options = new firefox.Options(); const driver = new Builder() .forBrowser('firefox') .setFirefoxOptions(options) .build(); Given('I am on a page that needs to be tested', async function () { driver.wait(until.elementLocated(By.tagName('h1'))); await driver.get('https://www.awebsite.com'); }); When('I do the thing', async function() { //.....tests, etc
控制台输出
Selenium Manager binary found at /me/automated-testing/node_modules/selenium-webdriver/bin/macos/selenium-manager Driver path: /me/.cache/selenium/geckodriver/mac-arm64/0.34.0/geckodriver Browser path: /Applications/Firefox.app/Contents/MacOS/firefox Driver path: /me/.cache/selenium/geckodriver/mac-arm64/0.34.0/geckodriver Browser path: /Applications/Firefox.app/Contents/MacOS/firefox Driver path: /me/.cache/selenium/geckodriver/mac-arm64/0.34.0/geckodriver Browser path: /Applications/Firefox.app/Contents/MacOS/firefox Driver path: /me/.cache/selenium/geckodriver/mac-arm64/0.34.0/geckodriver Browser path: /Applications/Firefox.app/Contents/MacOS/firefox ..... 1 scenario (1 passed) 3 steps (3 passed) 0m04.525s (executing steps: 0m04.517s)
问题原因
当前代码在模块顶层直接创建driver实例,而Cucumber会在加载每个步骤定义时执行模块顶层代码。由于你有3个步骤(Given/When/Then)加上Cucumber内部的加载逻辑,导致模块被执行了4次,每次都创建一个新的Firefox实例,最终出现4个浏览器窗口。同时,代码未在测试结束后调用driver.quit(),导致未使用的实例残留。
解决方案
通过Cucumber的Before和After钩子管理driver的生命周期,确保每个测试场景只创建一个driver实例,并且测试结束后销毁实例:
修改后的代码
const { Then, Given, When, Before, After } = require('@cucumber/cucumber'); const assert = require('assert'); const { Builder, By, until, Key } = require('selenium-webdriver'); const firefox = require('selenium-webdriver/firefox'); // 定义Firefox选项(可根据需求配置) const options = new firefox.Options(); // 在每个场景开始前创建driver实例,并绑定到上下文 Before(async function () { this.driver = await new Builder() .forBrowser('firefox') .setFirefoxOptions(options) .build(); }); // 在每个场景结束后关闭driver,避免残留实例 After(async function () { await this.driver.quit(); }); Given('I am on a page that needs to be tested', async function () { // 使用上下文里的driver,并且wait需要加await保证异步执行完成 await this.driver.wait(until.elementLocated(By.tagName('h1')), 10000); await this.driver.get('https://www.awebsite.com'); }); When('I do the thing', async function() { // 使用this.driver执行测试操作,例如: // await this.driver.findElement(By.id('some-id')).click(); }); // 补充Then步骤示例 Then('I should see the expected result', async function() { const title = await this.driver.getTitle(); assert.strictEqual(title, 'Expected Title'); });
关键修改点
- 移除全局driver初始化:不再在模块顶层创建driver,而是通过
Before钩子在每个场景开始前创建实例,确保每个场景只有一个driver。 - 绑定driver到Cucumber上下文:将driver存储在
this对象中,让所有步骤都能访问同一个实例。 - 添加After钩子销毁实例:测试结束后调用
driver.quit(),强制关闭浏览器和驱动进程,避免残留。 - 修复异步等待问题:原代码中
driver.wait没有加await,会导致后续代码提前执行,现在补上await确保等待完成后再执行下一步。
额外优化(可选)
如果希望所有场景共享同一个浏览器实例(而不是每个场景都重新打开),可以修改钩子作用域为BeforeAll和AfterAll:
const { BeforeAll, AfterAll } = require('@cucumber/cucumber'); let driver; BeforeAll(async function () { driver = await new Builder() .forBrowser('firefox') .setFirefoxOptions(options) .build(); }); AfterAll(async function () { await driver.quit(); }); // 步骤中直接使用全局的driver变量 Given('I am on a page that needs to be tested', async function () { await driver.wait(until.elementLocated(By.tagName('h1')), 10000); await driver.get('https://www.awebsite.com'); });
注意:共享实例可能导致场景间的状态污染(比如Cookie、缓存),如果你的场景需要独立环境,建议还是使用每个场景创建一个实例的方式。
内容的提问来源于stack exchange,提问作者MeltingDog
相关产品推荐
相关产品推荐

