3. 浏览器驱动封装:WebDriver单例模式实现、多浏览器支持与选项配置

好,咱们进入第三个实战环节。浏览器驱动封装,这名字听着挺唬人,说白了就是给 WebDriver 找个靠谱的“管家”。

我刚开始带团队的时候,发现一个特别头疼的问题:测试用例跑着跑着,浏览器窗口开了十几个,内存直接爆掉。后来一查,原来是每个用例都 new 了一个 WebDriver 实例,用完还不关。嗯,这就是典型的“驱动管理混乱症”。

今天咱们就彻底解决它。核心就三件事:单例模式保证唯一实例、多浏览器一键切换、浏览器选项灵活配置

3.1 为什么需要单例模式?

你想想看,一个自动化测试工程,可能有几百个测试用例。如果每个用例都自己创建一个浏览器驱动,那会怎样?

  • 资源浪费:每个实例都占用几百 MB 内存
  • 状态混乱:多个窗口互相干扰,定位元素经常失败
  • 维护成本高:改一个浏览器配置,要改几十个地方

单例模式,就是保证整个测试生命周期里,只有一个 WebDriver 实例。谁要用,都来找这个唯一的实例。用完也不销毁,等所有用例跑完,统一关闭。

核心原则:一个测试进程,一个浏览器实例。别搞“一人一窗口”那套。

3.2 单例模式实现:Java 版本

我个人习惯用 Java 写自动化框架,因为生态成熟。咱们直接看代码,我加了很多注释,方便你理解。

public class WebDriverSingleton {

    // 1. 私有静态变量,保存唯一实例
    private static WebDriver driver = null;

    // 2. 私有构造方法,防止外部 new
    private WebDriverSingleton() {
        // 啥也不干,就是不让 new
    }

    // 3. 公共静态方法,获取唯一实例
    public static WebDriver getDriver() {
        if (driver == null) {
            // 第一次调用时,才创建实例
            // 这里可以读取配置文件,决定用哪个浏览器
            String browser = ConfigReader.getBrowser(); // Chrome/Firefox/Edge
            driver = createDriver(browser);
        }
        return driver;
    }

    // 4. 创建具体浏览器驱动
    private static WebDriver createDriver(String browser) {
        WebDriver webDriver;
        switch (browser.toLowerCase()) {
            case "chrome":
                webDriver = createChromeDriver();
                break;
            case "firefox":
                webDriver = createFirefoxDriver();
                break;
            case "edge":
                webDriver = createEdgeDriver();
                break;
            default:
                throw new RuntimeException("不支持的浏览器类型: " + browser);
        }
        // 设置隐式等待,全局生效
        webDriver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        // 窗口最大化
        webDriver.manage().window().maximize();
        return webDriver;
    }

    // 5. 关闭驱动
    public static void quitDriver() {
        if (driver != null) {
            driver.quit();
            driver = null; // 置空,方便下次重新创建
        }
    }
}

避坑指南:我曾经犯过一个低级错误——在 quitDriver() 里忘了把 driver 置为 null。结果第二次调用 getDriver() 时,判断 driver != null,直接返回了一个已经关闭的实例,所有操作都报错。嗯,这个坑我替你们踩过了。

3.3 多浏览器支持:Chrome / Firefox / Edge

现在的项目,往往要兼容多个浏览器。你不能写死成 Chrome,万一客户用 Edge 呢?

我的做法是:通过配置文件控制浏览器类型。测试人员改一行配置,就能切换浏览器,不用改任何代码。

来看具体的创建方法:

3.3.1 Chrome 驱动创建

private static WebDriver createChromeDriver() {
    ChromeOptions options = new ChromeOptions();
    // 基础配置
    options.addArguments("--disable-notifications"); // 禁用通知弹窗
    options.addArguments("--disable-popup-blocking"); // 禁用弹窗拦截
    options.addArguments("--lang=zh-CN"); // 设置中文语言

    // 读取配置,决定是否启用无头模式
    if (ConfigReader.isHeadless()) {
        options.addArguments("--headless"); // 无头模式
        options.addArguments("--disable-gpu"); // 禁用 GPU 加速
        options.addArguments("--no-sandbox"); // 禁用沙盒(Linux 环境必需)
    }

    return new ChromeDriver(options);
}

3.3.2 Firefox 驱动创建

private static WebDriver createFirefoxDriver() {
    FirefoxOptions options = new FirefoxOptions();
    // 基础配置
    options.addPreference("dom.webnotifications.enabled", false); // 禁用通知
    options.addPreference("intl.accept_languages", "zh-CN"); // 中文语言

    if (ConfigReader.isHeadless()) {
        options.addArguments("--headless");
    }

    return new FirefoxDriver(options);
}

3.3.3 Edge 驱动创建

private static WebDriver createEdgeDriver() {
    EdgeOptions options = new EdgeOptions();
    // Edge 基于 Chromium,配置和 Chrome 很像
    options.addArguments("--disable-notifications");
    options.addArguments("--disable-popup-blocking");

    if (ConfigReader.isHeadless()) {
        options.addArguments("--headless");
        options.addArguments("--disable-gpu");
    }

    return new EdgeDriver(options);
}

关键点:每个浏览器的 Options 类不同,但配置思路完全一样。你只要封装好一个 createDriver 方法,根据参数分发即可。

3.4 浏览器选项配置:无头模式与禁用 GPU

为什么要把无头模式和禁用 GPU 放在一起讲?因为这两个配置经常成对出现。

无头模式:浏览器在后台运行,不显示界面。适合 CI/CD 环境,比如 Jenkins 上跑测试,没人盯着屏幕看。

禁用 GPU:无头模式下,GPU 加速反而会拖慢速度,甚至导致某些页面渲染异常。所以一般会一起禁用。

来看配置文件的实现:

// ConfigReader.java
public class ConfigReader {
    private static Properties props = new Properties();

    static {
        try {
            // 从 resources 目录加载 config.properties
            InputStream input = ConfigReader.class.getClassLoader()
                    .getResourceAsStream("config.properties");
            props.load(input);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 获取浏览器类型,默认 Chrome
    public static String getBrowser() {
        return props.getProperty("browser", "chrome");
    }

    // 是否启用无头模式,默认 false
    public static boolean isHeadless() {
        return Boolean.parseBoolean(props.getProperty("headless", "false"));
    }
}

对应的配置文件 config.properties

# 浏览器配置
browser=chrome
# 是否无头模式
headless=false

注意:无头模式不是万能的。有些前端动画、弹窗、文件下载等操作,在无头模式下可能表现异常。我建议:本地调试用有头模式,CI 环境用无头模式。别一上来就全无头,出了问题排查起来很痛苦。

3.5 完整使用示例

最后,咱们看看怎么用这个封装好的驱动管理器:

public class TestExample {
    public static void main(String[] args) {
        // 获取驱动实例
        WebDriver driver = WebDriverSingleton.getDriver();

        // 正常使用
        driver.get("https://www.baidu.com");
        System.out.println("页面标题: " + driver.getTitle());

        // 测试结束后,统一关闭
        WebDriverSingleton.quitDriver();
    }
}

看到了吗?测试代码里完全不用关心浏览器是什么、要不要无头模式。这些都在配置文件和驱动封装里处理好了。

我的经验:封装驱动管理器时,一定要考虑线程安全。如果你的测试用例是并行执行的(比如用 TestNG 的 parallel),那单例模式需要加锁。不过那是进阶话题了,咱们先把基础版本跑通。

3.6 本章小结

咱们今天干了三件事:

  • 单例模式:保证一个测试进程只有一个 WebDriver 实例,避免资源浪费
  • 多浏览器支持:通过配置文件一键切换 Chrome/Firefox/Edge
  • 选项配置:无头模式、禁用 GPU 等,灵活适应不同环境

下一章,咱们会深入 Page Object 模式,把页面元素和操作封装起来。到时候你会发现,有了今天这个驱动管理器,后面的工作会顺畅很多。

嗯,今天就到这儿。代码可以直接复制到你的项目里用,有问题随时翻翻这一章。