1. 项目概述:为什么我们需要这份全流程指南?

如果你是一名测试工程师、开发人员,或者正在学习自动化测试,那么“Python + Selenium”这个组合对你来说一定不陌生。它几乎是UI自动化测试的代名词,以其跨浏览器、开源免费和强大的社区支持而广受欢迎。然而,从零开始搭建一个能稳定运行的Selenium自动化测试环境,尤其是与特定版本的Chrome浏览器协同工作,这个过程远没有想象中那么一帆风顺。你可能遇到过驱动版本不匹配导致浏览器闪退,或者Chrome自动更新后脚本突然失效的窘境。这份指南,正是为了解决这些“最后一公里”的问题而生。

我们不仅仅要“跑起来”,更要“稳定地跑起来”。本指南将聚焦于从检查当前Chrome版本开始,到最终成功运行第一个自动化脚本的全流程。这包括了驱动程序的精准匹配、环境变量的巧妙配置、常见启动问题的排查,以及如何构建一个健壮的、可复用的测试基础框架。无论你是刚接触自动化测试的新手,还是希望优化现有流程的老手,这份结合了实战经验和避坑技巧的指南,都将为你提供一个清晰、可靠的行动路线图。

2. 环境准备与核心组件解析

2.1 Python环境:版本选择与包管理策略

Python是这一切的基础。对于Selenium自动化测试,我强烈推荐使用Python 3.7及以上版本,因为它们拥有更好的异步支持和更稳定的包管理。不要使用Python 2.x,其官方支持早已终止,很多新库已不再兼容。

安装Python时,务必勾选“Add Python to PATH”选项,这是避免后续在命令行中频繁输入完整路径的关键一步。安装完成后,打开终端(Windows上是CMD或PowerShell,macOS/Linux上是Terminal),输入 python --version python3 --version 来验证安装是否成功。

接下来是包管理。 pip 是Python的包安装工具。为了获得更快的下载速度和稳定性,建议配置国内镜像源。你可以创建一个配置文件,或者直接在安装命令中指定镜像。例如,使用阿里云镜像安装Selenium:

pip install selenium -i https://mirrors.aliyun.com/pypi/simple/

注意:在公司内网环境或对依赖有严格版本控制的项目中,建议使用 virtualenv conda 创建独立的虚拟环境。这能有效隔离项目依赖,避免不同项目间的包版本冲突。例如, python -m venv my_selenium_env 创建一个虚拟环境,然后激活它。

2.2 Chrome浏览器:版本锁定与自动更新的应对

Chrome浏览器的版本是Selenium自动化中最不稳定的因素之一,因为它会频繁自动更新。而Selenium WebDriver需要与Chrome浏览器版本严格匹配的 chromedriver 才能正常工作。

首先,检查你当前的Chrome版本。打开Chrome,点击右上角三个点 -> 帮助 -> 关于Google Chrome。你会看到类似“版本 128.0.6613.138(正式版本) (64 位)”的信息。记下主版本号,这里是“128”。

对于自动化测试环境,我个人的经验是: 尽量禁用或控制Chrome的自动更新 。在测试环境中,版本的稳定性比新特性更重要。在Windows上,可以通过组策略或服务管理来禁用Google更新服务;在macOS上,可能需要使用一些第三方工具或脚本。如果无法完全禁用,那么你的自动化框架必须具备“驱动版本自动检测与匹配”的能力,这我们会在后续章节详细讨论。

2.3 ChromeDriver:驱动管理的艺术

chromedriver 是一个独立的可执行文件,它是Selenium与Chrome浏览器通信的桥梁。它的版本必须与Chrome浏览器的主版本号匹配。例如,Chrome 128.x 需要 chromedriver 128.x.x.x。

获取驱动: 官方下载地址是 Chrome for Testing availability dashboard 。这个新站点比旧的下载页更清晰,直接提供了各个平台(Win32, Win64, Mac-arm64, Mac-x64, Linux64)的稳定版驱动。找到对应你Chrome主版本号的链接进行下载。

驱动管理策略: 直接将 chromedriver.exe 扔到项目根目录是一种简单方式,但不利于团队协作和持续集成。更佳实践是:

  1. 系统PATH路径 :将下载的 chromedriver 所在目录添加到系统的环境变量 PATH 中。这样,你可以在任何位置启动它。
  2. 使用 webdriver-manager 工具 :这是一个Python库,可以自动下载、缓存和管理正确版本的浏览器驱动。这是目前最推荐的方式,它能极大简化版本匹配问题。
    pip install webdriver-manager
    
    在代码中,你可以这样使用:
    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    from webdriver_manager.chrome import ChromeDriverManager
    
    service = Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service)
    
    webdriver-manager 会自动检查本地缓存和你的Chrome版本,下载匹配的驱动并返回其路径,几乎一劳永逸。

3. 核心脚本编写与浏览器启动配置

3.1 第一个脚本:从“Hello World”到可靠启动

让我们从一个最基础的脚本开始,但我们会为它注入“工业级”的稳定性。创建一个名为 first_test.py 的文件。

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import time

# 1. 使用 webdriver-manager 管理驱动(推荐)
from webdriver_manager.chrome import ChromeDriverManager

def main():
    try:
        # 设置 ChromeService,并自动管理驱动
        service = Service(ChromeDriverManager().install())

        # 2. 配置浏览器选项,增加稳定性
        options = webdriver.ChromeOptions()
        # 避免一些奇怪的权限弹窗和自动化提示
        options.add_argument('--disable-blink-features=AutomationControlled')
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        options.add_experimental_option('useAutomationExtension', False)
        # 以无头模式运行(不显示GUI),适用于服务器/CI环境
        # options.add_argument('--headless')
        # 禁用GPU加速,在某些虚拟环境中可避免崩溃
        options.add_argument('--disable-gpu')
        # 设置中文语言
        options.add_argument('lang=zh-CN.UTF-8')
        # 忽略证书错误(用于测试环境)
        # options.add_argument('--ignore-certificate-errors')

        # 3. 初始化驱动对象
        driver = webdriver.Chrome(service=service, options=options)
        # 最大化窗口
        driver.maximize_window()

        # 4. 执行你的测试操作
        driver.get("https://www.baidu.com")
        print(f"页面标题是:{driver.title}")

        # 简单的元素查找示例
        search_box = driver.find_element(By.ID, 'kw')
        search_box.send_keys('Selenium自动化测试')
        search_box.submit()

        # 等待一下,观察结果
        time.sleep(3)

    except Exception as e:
        print(f"程序运行出错:{e}")
    finally:
        # 5. 无论如何,最后都要关闭浏览器
        input("按回车键关闭浏览器...") # 用于演示,实际测试中可去掉
        driver.quit()

if __name__ == "__main__":
    main()

这个脚本包含了几个关键点:

  • 异常处理 :用 try...except...finally 包裹核心逻辑,确保即使出错,浏览器也能在 finally 块中被关闭 ( driver.quit() ),避免残留进程。
  • 浏览器选项 :通过 ChromeOptions 进行配置,这是优化和稳定化测试执行的核心。上述参数能帮助脚本更像真人操作,并适应更多环境。
  • 显式等待 :示例中用了 time.sleep ,这只是为了演示。在实际项目中, 绝对不要 大量使用固定休眠,而应使用Selenium提供的 WebDriverWait expected_conditions 进行显式等待,这是编写稳定自动化脚本的第一要义。

3.2 ChromeOptions 深度配置:应对复杂场景

ChromeOptions 是控制浏览器行为的强大工具。以下是一些实战中高频使用的配置:

用户数据目录 :复用已有浏览器会话(如已登录状态)。

options.add_argument(r'--user-data-dir=C:\Users\YourName\AppData\Local\Google\Chrome\User Data')
options.add_argument('--profile-directory=Default')

这非常有用,可以避免每次测试都重新登录。但要注意并发问题,多个脚本不能同时使用同一个用户目录。

下载路径设置

prefs = {
    "download.default_directory": r"D:\auto_downloads", # 设置默认下载路径
    "download.prompt_for_download": False, # 下载时不弹出提示框
    "download.directory_upgrade": True,
    "safebrowsing.enabled": True # 安全浏览,可选
}
options.add_experimental_option("prefs", prefs)

移动端模拟

from selenium.webdriver.common.device_metrics import DeviceMetrics

mobile_emulation = {
    "deviceMetrics": {"width": 375, "height": 667, "pixelRatio": 3.0},
    "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) ..."
}
options.add_experimental_option("mobileEmulation", mobile_emulation)

禁用沙盒和DevShm :在Docker容器或无头Linux服务器中运行时,这两个参数常能解决启动崩溃问题。

options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')

4. 自动化测试框架雏形搭建

一个脚本不足以支撑项目。我们需要一个简单的框架雏形来组织代码,使其更清晰、更易维护。

4.1 项目结构设计

一个典型的、简单清晰的项目目录可以这样组织:

your_project/
├── config/
│   └── settings.py      # 存放配置常量,如URL、超时时间、浏览器路径
├── drivers/             # 存放浏览器驱动(如果不用webdriver-manager)
├── pages/               # 页面对象模型(Page Object Model, POM)
│   ├── __init__.py
│   ├── base_page.py     # 所有页面对象的基类
│   └── baidu_page.py    # 例如,百度首页的页面对象
├── tests/               # 测试用例
│   ├── __init__.py
│   └── test_baidu_search.py
├── utils/               # 工具函数
│   ├── __init__.py
│   └── driver_manager.py # 封装的驱动初始化模块
├── logs/                # 日志目录(运行时生成)
├── reports/             # 测试报告目录(运行时生成)
└── requirements.txt     # 项目依赖列表

4.2 封装驱动管理模块

utils/driver_manager.py 中,我们将浏览器的初始化逻辑封装起来,实现单例模式,确保整个测试过程中只有一个驱动实例。

# utils/driver_manager.py
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

class DriverManager:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(DriverManager, cls).__new__(cls)
            cls._instance._init_driver()
        return cls._instance

    def _init_driver(self):
        """初始化WebDriver,包含所有通用配置"""
        service = Service(ChromeDriverManager().install())

        options = webdriver.ChromeOptions()
        # 你的通用配置选项
        options.add_argument('--disable-blink-features=AutomationControlled')
        options.add_argument('--disable-gpu')
        options.add_argument('--start-maximized')
        # 无头模式配置示例,可通过环境变量控制
        import os
        if os.getenv('RUN_HEADLESS', 'false').lower() == 'true':
            options.add_argument('--headless')

        self.driver = webdriver.Chrome(service=service, options=options)
        # 设置全局隐式等待(备用,显式等待为主)
        self.driver.implicitly_wait(10)

    def get_driver(self):
        return self.driver

    def quit_driver(self):
        if self.driver:
            self.driver.quit()
            self._instance = None
            self.driver = None

# 全局访问点
def get_driver():
    manager = DriverManager()
    return manager.get_driver()

def quit_driver():
    manager = DriverManager()
    manager.quit_driver()

4.3 实现页面对象模型

POM是UI自动化的最佳设计模式之一,它将页面元素定位和操作封装成类,使测试用例更易读、更易维护。

首先,创建基类 pages/base_page.py

# pages/base_page.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from utils.driver_manager import get_driver

class BasePage:
    def __init__(self):
        self.driver = get_driver()

    def find_element(self, locator, timeout=10):
        """查找单个元素,使用显式等待"""
        wait = WebDriverWait(self.driver, timeout)
        return wait.until(EC.presence_of_element_located(locator))

    def find_elements(self, locator, timeout=10):
        """查找多个元素"""
        wait = WebDriverWait(self.driver, timeout)
        return wait.until(EC.presence_of_all_elements_located(locator))

    def click(self, locator, timeout=10):
        """点击元素"""
        element = self.find_element(locator, timeout)
        element.click()

    def input_text(self, locator, text, timeout=10):
        """向输入框输入文本"""
        element = self.find_element(locator, timeout)
        element.clear()
        element.send_keys(text)

然后,创建具体的页面类,例如 pages/baidu_page.py

# pages/baidu_page.py
from selenium.webdriver.common.by import By
from .base_page import BasePage

class BaiduPage(BasePage):
    # 元素定位器(Locators)
    SEARCH_INPUT = (By.ID, 'kw')
    SEARCH_BUTTON = (By.ID, 'su')
    FIRST_RESULT = (By.XPATH, '//div[@id="content_left"]//h3/a[1]')

    def open(self):
        self.driver.get("https://www.baidu.com")
        return self

    def search(self, keyword):
        self.input_text(self.SEARCH_INPUT, keyword)
        self.click(self.SEARCH_BUTTON)
        return self

    def get_first_result_title(self):
        first_link = self.find_element(self.FIRST_RESULT)
        return first_link.text

4.4 编写测试用例

现在,测试用例变得非常简洁和易读,在 tests/test_baidu_search.py 中:

# tests/test_baidu_search.py
import pytest
from pages.baidu_page import BaiduPage
from utils.driver_manager import quit_driver

class TestBaiduSearch:
    def test_search_functionality(self):
        page = BaiduPage().open()
        page.search("Selenium自动化测试")
        title = page.get_first_result_title()
        print(f"第一个结果的标题是:{title}")
        # 这里可以添加断言,例如:
        # assert "Selenium" in title
        # 注意:实际断言需要根据具体返回结果调整

    def teardown_method(self):
        """每个测试方法结束后执行,关闭浏览器"""
        quit_driver()

# 如果直接运行此文件
if __name__ == "__main__":
    test = TestBaiduSearch()
    test.test_search_functionality()
    test.teardown_method()

通过这样的结构,我们将驱动管理、页面逻辑和测试用例分离,符合单一职责原则。当页面元素发生变化时,你只需要修改对应的 Page 类中的定位器,而不需要到处修改测试脚本。

5. 高级技巧与稳定性优化

5.1 等待策略:告别 time.sleep

固定等待 ( time.sleep ) 是自动化脚本不稳定的罪魁祸首。我们必须掌握显式等待。

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException

def smart_wait_example(driver):
    # 等待元素出现并可点击
    try:
        element = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.ID, "submit-button"))
        )
        element.click()
    except TimeoutException:
        print("提交按钮在10秒内未变为可点击状态")
        # 可以在这里进行截图、日志记录等失败处理

    # 等待元素消失(例如加载动画)
    WebDriverWait(driver, 15).until(
        EC.invisibility_of_element_located((By.CLASS_NAME, "loading-spinner"))
    )

    # 等待页面标题包含特定文字
    WebDriverWait(driver, 10).until(
        EC.title_contains("订单成功")
    )

你可以封装自己的等待工具函数,比如等待并重试某个操作。

5.2 处理弹窗、新窗口和iframe

JavaScript弹窗

from selenium.webdriver.common.alert import Alert

# 切换到alert并接受(确定)
alert = driver.switch_to.alert
print(alert.text) # 获取弹窗文本
alert.accept() # 点击确定
# alert.dismiss() # 点击取消

新窗口/标签页

# 获取当前所有窗口句柄
main_window = driver.current_window_handle
all_windows = driver.window_handles

# 点击一个会打开新窗口的链接
driver.find_element(By.LINK_TEXT, "新窗口打开").click()

# 等待新窗口出现
WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) > 1)

# 切换到新窗口
new_window = [w for w in driver.window_handles if w != main_window][0]
driver.switch_to.window(new_window)

# 在新窗口操作...
# 操作完毕后切回主窗口
driver.switch_to.window(main_window)

iframe

# 通过ID、Name或索引切换到iframe内部
iframe = driver.find_element(By.TAG_NAME, "iframe")
driver.switch_to.frame(iframe)
# 或者 driver.switch_to.frame("iframe_id")

# 在iframe内操作元素...

# 操作完成后切回主文档
driver.switch_to.default_content()

5.3 执行JavaScript与截图

有时需要通过JS直接操作DOM或获取浏览器信息。

# 执行JS脚本
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 滚动到底部
title_via_js = driver.execute_script("return document.title;") # 获取标题

# 修改元素属性(例如,让一个隐藏的元素可见)
driver.execute_script("arguments[0].style.display = 'block';", element)

# 截图
driver.save_screenshot("./screenshots/error_page.png")
# 或者只截取某个元素
element.screenshot("./screenshots/button_element.png")

6. 常见问题排查与实战心得

6.1 驱动版本不匹配问题

这是最常见的问题。症状通常是: SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version XX

解决方案

  1. 首选方案 :使用 webdriver-manager ,如前所述,它能自动处理。
  2. 手动检查 :如果不用管理器,需手动核对。Chrome主版本号(如128)必须与ChromeDriver的主版本号完全一致。小版本号(如128.0.6613)可以不一致,但最好也尽量接近。
  3. 清理缓存 :有时旧驱动缓存会导致问题。手动删除 ~/.wdm %USERPROFILE%\.wdm 目录( webdriver-manager 的缓存),然后重试。

6.2 Chrome无法启动或秒退

可能原因及解决

  • 端口占用 :默认端口9515被占用。可以通过 options.add_experimental_option('debuggerAddress', '127.0.0.1:9222') 指定另一个调试端口,并确保该端口未被占用。
  • 用户数据目录冲突 :多个实例尝试使用同一个用户数据目录。确保每个测试实例使用独立目录,或使用无痕模式 options.add_argument('--incognito')
  • 权限问题 :在Linux/macOS上,确保 chromedriver 有可执行权限 ( chmod +x chromedriver )。
  • 浏览器路径问题 :如果Chrome未安装在默认位置,需指定路径: options.binary_location = r"C:\Custom\Path\chrome.exe"

6.3 元素找不到或交互失败

排查步骤

  1. 等待不够 :这是首要原因。确保使用了合适的显式等待,而不是硬等待。
  2. 定位器失效 :页面结构可能已更改。使用浏览器开发者工具(F12)的检查器重新确认元素定位器(ID、XPath、CSS Selector等)。优先使用稳定的ID,其次是CSS Selector,尽量避免使用绝对XPath。
  3. 元素在iframe或shadow DOM内 :确认是否需要先切换上下文。
  4. 页面未完全加载 :等待整个文档就绪 WebDriverWait(driver, 10).until(lambda d: d.execute_script('return document.readyState') == 'complete')
  5. 元素被遮挡 :其他元素(如弹窗、固定导航栏)可能覆盖了目标元素。尝试滚动到元素位置 driver.execute_script("arguments[0].scrollIntoView(true);", element) ,然后再操作。

6.4 提升脚本稳定性的个人心得

  1. 隔离与清理 :每个测试用例(或测试类)都应以一个干净的浏览器会话开始和结束。在 setup 中初始化,在 teardown 中调用 driver.quit() 。避免测试间的状态污染。
  2. 善用日志和截图 :在关键步骤(如点击、输入、断言前)和捕获异常时,记录日志并截图。这能极大提升调试效率。可以集成Python的 logging 模块。
  3. 引入重试机制 :对于网络不稳定或偶尔出现的元素加载慢,可以对某些操作(如查找元素)进行有限次数的重试。但需谨慎使用,避免掩盖真正的bug。
  4. 数据驱动 :将测试数据(如用户名、密码、搜索关键词)与测试逻辑分离,存放在外部文件(如JSON、YAML、Excel)中。这使测试更易维护和扩展。
  5. 并行测试考虑 :如果计划并行运行测试,必须确保资源(如驱动、端口、用户数据目录)是隔离的。 webdriver-manager 能很好地处理驱动的并行下载,但你需要管理好浏览器实例的端口和用户目录。

从检查Chrome版本到编写出稳定、可维护的自动化测试脚本,这个过程充满了细节。最关键的其实不是记住所有API,而是建立起一套稳健的工程实践: 使用工具管理驱动、用POM组织代码、用显式等待代替休眠、用日志和截图辅助调试 。把这些基础打牢,再复杂的自动化场景也能从容应对。在实际项目中,你可能会进一步集成 pytest 来管理用例、使用 Allure 生成漂亮报告、结合 Jenkins GitHub Actions 做持续集成,但那些都是建立在这个坚实起点之上的扩展。先从让第一个脚本在本地稳定运行开始吧。

更多推荐