ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Codex 100个真实案例 - 用AI自动生成Selenium测试脚本(解放测试双手)

Codex 100个真实案例 - 用AI自动生成Selenium测试脚本(解放测试双手) 1. 为什么我决定让 Codex 接管 Selenium 脚本如果你做过 Web 自动化测试大概率经历过这样的循环打开浏览器 F12 翻元素、复制 XPath、粘贴到脚本里、跑一遍报错、改定位、再跑、再报错。一个登录用例写下来半小时没了而且下次页面改版全部重来。Codex 这类 AI 编程助手出现之后我把这套流程整个换掉了——现在我用自然语言描述测试意图让它生成完整的 Selenium 测试脚本包括 Page Object、断言、截图、报告甚至 CI 配置。这篇内容聚焦一个具体场景用 Codex 从零生成可运行的 Selenium 测试脚本覆盖登录、表单填写、元素断言这些最常见的测试动作。我会给出可复制的config.toml骨架、TaoToken 统一 Key/API 通道的配置方式以及脚本生成后如何在本地跑通验证。适合谁看测试工程师、QA、想入门自动化测试的后端/前端同学以及已经在用 Selenium 但被重复代码折磨的人。核心检索词先摆出来Codex 生成 Selenium 测试脚本、AI 自动化测试、Page Object 模式、pytest 断言、TaoToken API 通道。下面所有步骤都可以跟着做代码直接复制就能跑。2. TaoToken 前置统一 Key 与 API 通道配置在让 Codex 生成脚本之前先把模型调用通道配好。我用 TaoToken 做统一入口好处是一个 Key 走通对话、编码、Agent 多种场景不用在多个平台之间切换。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 地址是 https://taotoken.net/api这个不加 UTM。配置分两步拿 Key写 config.toml。2.1 获取 API Key登录后进入控制台在 API Keys 页面创建一个新 Key。建议按项目命名比如selenium-codex方便后续排查是哪个项目在用。创建后立刻复制保存页面刷新后就看不到完整 Key 了。2.2 config.toml 骨架Codex CLI 和很多 AI 编码工具都支持config.toml作为配置文件。下面是我实测可用的骨架放在项目根目录或用户配置目录下都行# config.toml - Codex TaoToken 统一通道配置 [model] provider taotoken model_name claude-sonnet-4-20250514 api_base https://taotoken.net/api api_key sk-你的TaoToken密钥 [model.params] temperature 0.2 max_tokens 8192 top_p 0.95 [project] language python framework selenium test_runner pytest page_object true [generation] auto_save true screenshot_on_failure true report_format html几个参数说明temperature设 0.2 是因为测试代码需要稳定不要太多随机发挥max_tokens给足因为一个完整的 Page Object 类可能上千行page_object true是告诉模型按页面对象模式组织代码这个很关键后面会展开。注意api_key不要提交到 Git。建议用环境变量覆盖或者在.gitignore里排除config.toml。2.3 环境变量覆盖方式如果你不想把 Key 写死在文件里可以用环境变量export TAOTOKEN_API_KEYsk-你的密钥 export TAOTOKEN_API_BASEhttps://taotoken.net/api然后在config.toml里引用[model] api_key ${TAOTOKEN_API_KEY} api_base ${TAOTOKEN_API_BASE}这样本地开发和 CI 环境可以用不同的 Key互不干扰。3. 可复制配置从零生成 Selenium 测试脚本配置好通道后开始让 Codex 生成脚本。我按实际项目结构拆成几个可复制的步骤每一步都给出提示词和生成结果。3.1 项目初始化与依赖先建目录结构。我习惯这样组织mkdir -p selenium-codex/{pages,tests,utils,config,reports,screenshots} cd selenium-codex touch pages/__init__.py tests/__init__.py utils/__init__.py config/__init__.py依赖文件requirements.txtselenium4.21.0 pytest8.2.2 pytest-html4.1.1 pytest-xdist3.5.0 webdriver-manager4.0.1 Pillow10.3.0安装pip install -r requirements.txt这里用webdriver-manager自动管理浏览器驱动省去手动下载 ChromeDriver 的麻烦。Selenium 4.x 自带 Selenium Manager但webdriver-manager在多浏览器切换时更稳。3.2 全局配置类让 Codex 生成config/settings.py用 dataclass 管理配置# config/settings.py import os from dataclasses import dataclass dataclass class TestConfig: base_url: str os.getenv(TEST_BASE_URL, https://the-internet.herokuapp.com) browser: str os.getenv(TEST_BROWSER, chrome) headless: bool os.getenv(TEST_HEADLESS, false).lower() true implicit_wait: int int(os.getenv(TEST_IMPLICIT_WAIT, 10)) explicit_wait: int int(os.getenv(TEST_EXPLICIT_WAIT, 15)) page_load_timeout: int int(os.getenv(TEST_PAGE_LOAD_TIMEOUT, 30)) screenshot_dir: str os.path.join(os.path.dirname(os.path.dirname(__file__)), screenshots) report_dir: str os.path.join(os.path.dirname(os.path.dirname(__file__)), reports) window_width: int 1920 window_height: int 1080 retry_count: int int(os.getenv(TEST_RETRY_COUNT, 2)) def __post_init__(self): os.makedirs(self.screenshot_dir, exist_okTrue) os.makedirs(self.report_dir, exist_okTrue) config TestConfig()这个类的价值在于所有环境相关的参数都集中在一处CI 里改环境变量就能切换浏览器、无头模式、基础 URL测试代码一行不用动。3.3 浏览器驱动工厂utils/driver_factory.py负责创建不同浏览器的 WebDriver# utils/driver_factory.py from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.firefox.service import Service as FirefoxService from selenium.webdriver.edge.service import Service as EdgeService from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.firefox import GeckoDriverManager from webdriver_manager.microsoft import EdgeChromiumDriverManager from config.settings import config class DriverFactory: staticmethod def create_driver(browser: str None): browser (browser or config.browser).lower() if browser chrome: return DriverFactory._create_chrome() elif browser firefox: return DriverFactory._create_firefox() elif browser edge: return DriverFactory._create_edge() raise ValueError(f不支持的浏览器: {browser}) staticmethod def _create_chrome(): options webdriver.ChromeOptions() if config.headless: options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) options.add_argument(--disable-gpu) options.add_argument(f--window-size{config.window_width},{config.window_height}) service ChromeService(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionsoptions) driver.implicitly_wait(config.implicit_wait) driver.set_page_load_timeout(config.page_load_timeout) return driver staticmethod def _create_firefox(): options webdriver.FirefoxOptions() if config.headless: options.add_argument(--headless) service FirefoxService(GeckoDriverManager().install()) driver webdriver.Firefox(serviceservice, optionsoptions) driver.implicitly_wait(config.implicit_wait) driver.set_page_load_timeout(config.page_load_timeout) return driver staticmethod def _create_edge(): options webdriver.EdgeOptions() if config.headless: options.add_argument(--headlessnew) options.add_argument(--no-sandbox) service EdgeService(EdgeChromiumDriverManager().install()) driver webdriver.Edge(serviceservice, optionsoptions) driver.implicitly_wait(config.implicit_wait) driver.set_page_load_timeout(config.page_load_timeout) return driver切换浏览器只需要改TEST_BROWSER环境变量测试代码完全不用动。3.4 Page Object 基类pages/base_page.py封装通用操作这是减少重复代码的核心# pages/base_page.py import os import logging from datetime import datetime from typing import Tuple, List from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, ElementClickInterceptedException from config.settings import config logger logging.getLogger(__name__) class BasePage: def __init__(self, driver: WebDriver): self.driver driver self.wait WebDriverWait(driver, config.explicit_wait) def find_element(self, locator: Tuple[str, str]) - WebElement: try: element self.wait.until(EC.presence_of_element_located(locator)) return element except TimeoutException: logger.error(f超时未找到元素: {locator}) self.take_screenshot(fnot_found_{locator[1]}) raise def find_clickable(self, locator: Tuple[str, str]) - WebElement: return self.wait.until(EC.element_to_be_clickable(locator)) def click(self, locator: Tuple[str, str]): for attempt in range(config.retry_count 1): try: element self.find_clickable(locator) element.click() return except ElementClickInterceptedException: element self.find_element(locator) self.driver.execute_script(arguments[0].click();, element) return def input_text(self, locator: Tuple[str, str], text: str, clear_first: bool True): element self.find_element(locator) if clear_first: element.clear() element.send_keys(Keys.CONTROL a) element.send_keys(Keys.DELETE) element.send_keys(text) def get_text(self, locator: Tuple[str, str]) - str: return self.find_element(locator).text def is_visible(self, locator: Tuple[str, str], timeout: int 5) - bool: try: WebDriverWait(self.driver, timeout).until(EC.visibility_of_element_located(locator)) return True except TimeoutException: return False def take_screenshot(self, name: str None) - str: timestamp datetime.now().strftime(%Y%m%d_%H%M%S) filename f{name}_{timestamp}.png if name else fscreenshot_{timestamp}.png filepath os.path.join(config.screenshot_dir, filename) self.driver.save_screenshot(filepath) return filepath def navigate_to(self, url: str): self.driver.get(url) def get_current_url(self) - str: return self.driver.current_url这个基类封装了查找、点击、输入、断言、截图这些高频操作子类页面对象只需要定义元素定位器和业务方法。3.5 登录页面对象pages/login_page.py# pages/login_page.py from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver from pages.base_page import BasePage from config.settings import config class LoginPage(BasePage): URL f{config.base_url}/login USERNAME_INPUT (By.ID, username) PASSWORD_INPUT (By.ID, password) LOGIN_BUTTON (By.CSS_SELECTOR, button[typesubmit]) ERROR_MESSAGE (By.CSS_SELECTOR, #flash.error) SUCCESS_MESSAGE (By.CSS_SELECTOR, #flash.success) PAGE_HEADER (By.TAG_NAME, h2) def __init__(self, driver: WebDriver): super().__init__(driver) def open(self): self.navigate_to(self.URL) return self def login(self, username: str, password: str): self.input_text(self.USERNAME_INPUT, username) self.input_text(self.PASSWORD_INPUT, password) self.click(self.LOGIN_BUTTON) return self def get_error_message(self) - str: return self.get_text(self.ERROR_MESSAGE).strip() def get_success_message(self) - str: return self.get_text(self.SUCCESS_MESSAGE).strip() def is_login_successful(self) - bool: return self.is_visible(self.SUCCESS_MESSAGE) def is_error_displayed(self) - bool: return self.is_visible(self.ERROR_MESSAGE)3.6 测试用例与断言tests/test_login.py用 pytest 组织用例# tests/test_login.py import pytest from pages.login_page import LoginPage from pages.dashboard_page import DashboardPage class TestLogin: VALID_USERNAME tomsmith VALID_PASSWORD SuperSecretPassword! pytest.mark.smoke def test_login_success(self, driver): login_page LoginPage(driver) login_page.open() login_page.login(self.VALID_USERNAME, self.VALID_PASSWORD) dashboard DashboardPage(driver) assert dashboard.is_on_dashboard(), 登录后应跳转到仪表盘 pytest.mark.parametrize( username, password, expected_error, [ (invalid_user, SuperSecretPassword!, Your username is invalid), (tomsmith, wrong_password, Your password is invalid), (, , Your username is invalid), ], ids[错误用户名, 错误密码, 全空], ) def test_login_failure(self, driver, username, password, expected_error): login_page LoginPage(driver) login_page.open() login_page.login(username, password) assert login_page.is_error_displayed(), 应显示错误提示 assert expected_error in login_page.get_error_message()tests/conftest.py提供 driver fixture 和失败自动截图# tests/conftest.py import pytest from utils.driver_factory import DriverFactory from utils.screenshot import ScreenshotManager pytest.fixture(scopefunction) def driver(): _driver DriverFactory.create_driver() _driver.maximize_window() yield _driver _driver.quit() pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: driver item.funcargs.get(driver) if driver: ScreenshotManager(driver).capture_on_failure(item.name)4. 验证请求本地跑通与成功结果脚本生成后必须本地跑一遍确认能通。这一步很多人跳过结果 CI 里才发现问题。4.1 运行测试python -m pytest tests/ -v --tbshort预期输出类似tests/test_login.py::TestLogin::test_login_success PASSED tests/test_login.py::TestLogin::test_login_failure[错误用户名] PASSED tests/test_login.py::TestLogin::test_login_failure[错误密码] PASSED tests/test_login.py::TestLogin::test_login_failure[全空] PASSED 4 passed in 12.34s 4.2 生成 HTML 报告python -m pytest tests/ -v --htmlreports/report.html --self-contained-html打开reports/report.html能看到每个用例的执行状态、耗时、失败截图。--self-contained-html把 CSS 和图片内联方便直接发给同事。4.3 无头模式验证CI 环境通常没有显示器用无头模式跑TEST_HEADLESStrue python -m pytest tests/ -v如果无头模式通过、有头模式也通过说明脚本稳定。如果只有无头失败通常是窗口大小或元素可见性问题检查--window-size参数。4.4 多浏览器验证TEST_BROWSERfirefox python -m pytest tests/ -v TEST_BROWSERedge python -m pytest tests/ -v三个浏览器都通过基本可以放心提交。5. 本篇常见错排查生成脚本后跑不通大概率是下面几个问题。我按出现频率排序。5.1 元素定位超时报错TimeoutException: Message: element not found。原因通常是定位器写错或者页面还没加载完。排查方法先用driver.page_source打印页面源码确认元素是否存在再用浏览器 F12 验证 XPath/CSS 选择器。如果元素在 iframe 里需要先switch_to.frame()。5.2 点击被拦截报错ElementClickInterceptedException。常见于弹窗遮挡、动画未结束、元素被其他元素覆盖。基类里已经用 JS 点击兜底但如果还失败检查是否有display: none的遮罩层。5.3 驱动版本不匹配报错SessionNotCreatedException: This version of ChromeDriver only supports Chrome version XX。用webdriver-manager一般不会出现如果出现删掉缓存目录重装rm -rf ~/.wdm pip install --force-reinstall webdriver-manager5.4 无头模式截图空白无头模式下截图全白通常是窗口尺寸没设。在 options 里加--window-size1920,1080或者用driver.set_window_size(1920, 1080)。5.5 pytest 找不到测试报错no tests ran。检查文件名是否以test_开头类名是否以Test开头方法名是否以test_开头。pytest 的发现规则很严格命名不对直接跳过。5.6 中文乱码HTML 报告里中文显示乱码在pytest.ini里加[pytest] log_cli true log_cli_level INFO并在生成报告时确保文件编码为 UTF-8。6. 语义一致 CTA把通道和工具用起来脚本能跑通之后下一步是把它接入日常开发流。如果你还在手动切换多个模型平台建议用 TaoToken 统一 Key 管理一个通道走通对话、编码、Agent 场景。具体入口需要排查接入问题、管理密钥API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite想先验证模型输出质量模型对话 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite长期做编码和 Agent 任务Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite查接入文档和参数说明文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite我自己的做法是本地开发用模型对话快速验证提示词确认生成质量后再写进 Codex 配置批量生成脚本最后用 Coding Plan 跑长期任务。这样分工明确不会在一个入口上堆所有事情。最后留一个实用技巧把config.toml里的temperature设低一点0.1–0.3生成的测试代码更稳定如果发现模型总爱加多余的time.sleep()在提示词里明确写“使用显式等待不要用 sleep”效果立竿见影。
返回列表