关于 migrate-selenium-java-to-codeceptjs
一项将 Selenium 或 Selenide 的 Java 测试套件迁移到 CodeceptJS 4 的技能,涵盖项目盘点、并行环境搭建和测试模式转换,可在迁移期间保留现有套件运行。
主要功能
- 01盘点 Java 测试套件及共享抽象
- 02在 Java 项目旁搭建 CodeceptJS
- 03将测试迁移到 WebDriver helper
- 04转换页面对象、辅助类和测试数据
- 05识别需要重新设计的 Java 专属功能
适用场景
- 01规划 Selenium 或 Selenide 套件迁移
- 02迁移到 CodeceptJS 期间继续运行 Java 测试
- 03将现有测试抽象映射到 CodeceptJS 对应方案
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ codeceptjs/ skills --skill migrate-selenium-java-to-codeceptjs安装后在 Claude Code 中输入/migrate-selenium-java-to-codeceptjs确认已加载。
详细介绍
将 Selenium / Selenide(Java)迁移到 CodeceptJS 4
这次迁移不只是语法转换——每个文件都要更换语言。Java 变为 JavaScript(或 TypeScript),Maven/Gradle 变为 npm,JUnit/TestNG 则变为 CodeceptJS 运行器。机械式转换完全可行;迁移带来的好处也很实在(语义化定位器、自动等待、一流抽象、AI 辅助编写),而且这些模式可以迁移使用。
需要牢记三个根本区别:
- 测试按顺序执行,无需显式等待。 CodeceptJS 会自动将每个
I.*调用排入内部记录器,并自动等待 DOM/元素稳定。Selenium 中的WebDriverWait+ExpectedConditions.*样板代码可以全部移除;Selenide 的.shouldBe(visible)/.shouldHave(text(...))链通常会合并到操作本身(I.click('Save')会等待、点击并断言)。在 CodeceptJS 中,await仅用于抓取操作(await I.grabTextFrom(...))——用于普通操作也能运行,但不建议这样做。这是规范文件中最明显的变化。 - 使用 Helpers,而不是
WebDriver/driver。I.*会将操作分派给已配置的 helper。默认使用 WebDriver helper——它使用与你现有 Java 测试套件相同的 W3C WebDriver 协议,因此迁移方式最原生:相同的 Selenium 服务、相同的浏览器驱动、相同的能力配置,如果你有 Grid,也可以使用相同的 Grid。测试套件在 WebDriver 上通过后,你可以通过修改一个 helper 区块,切换为 Playwright helper(现代、速度更快、更稳定,并且通过一份配置即可原生支持多浏览器)——无论使用哪种 helper,测试代码都完全相同。 - 一流抽象。 页面对象、
auth插件、多用户session(...)、自定义 helpers 和customLocator插件均为内置功能。Java 测试套件通常已将这些内容集中管理(PageFactory、LoginHelper、DriverManager);迁移会将它们整合到框架惯用的模式中,而不是继续沿用 Java 特有的胶水代码。
权威参考资料:node_modules/codeceptjs/docs/(basics、locators、playwright、webdriver、custom-helpers、pageobjects)。
适用时机
符合以下任一条件:
pom.xml将selenium-java、selenide、webdrivermanager、junit-jupiter、junit或testng声明为依赖项。build.gradle/build.gradle.kts中包含org.seleniumhq.selenium:selenium-java、com.codeborne:selenide、org.junit.jupiter:junit-jupiter或org.testng:testng。- 存在包含
*Test.java/*IT.java/*Tests.java/*Steps.java的src/test/java/目录树。 - 导入了
org.openqa.selenium.*(WebDriver、WebElement、By、WebDriverWait、ExpectedConditions、Actions、JavascriptExecutor、Select、Keys)。 - 导入了
com.codeborne.selenide.*(Selenide、SelenideElement、Configuration、Condition、ElementsCollection)。 - 使用了 JUnit / TestNG 注解:
@Test、@BeforeEach/@BeforeMethod/@BeforeClass/@BeforeAll/@BeforeSuite、@After*、@DataProvider、@ParameterizedTest、@FindBy/@FindAll。 - 代码调用了
PageFactory.initElements(...)、WebDriverManager.*.setup()、new ChromeDriver(...)、new RemoteWebDriver(...)、Selenide.open(...)、$(...)/$$(...)链。 src/test/java/下有 Cucumber-JVM 步骤定义(io.cucumber.java.en.*)。- 用户提出“从 Selenium / Selenide / Java 迁移 / 移植 / 转换”。
无法迁移的内容
请一开始就坦诚说明:
- 测试代码中的 Java 语言特性——泛型、流、Lambda 表达式、自定义异常层次结构、AspectJ 编织、JUnit
@ExtendWith扩展、TestNGIInvokedMethodListener监听器。这些都可以用普通 JS 重新实现,通常代码更短,但应选择最简单的移植方式,而不是忠实翻译。 - 与测试绑定的 Maven / Gradle 构建阶段——集成测试前后阶段、由配置文件驱动的测试排除、surefire-failsafe 拆分。请用 npm 脚本和 CodeceptJS
--grep/tag过滤器重新实现。 - 默认 WebDriver 辅助工具下的客户端网络拦截。WebDriver 没有与 Playwright 的
I.mockRoute(...)等效的功能。使用 WebDriver 时,请将 WireMock(或任何代理 / 服务虚拟化工具)作为旁路组件保留。可选地切换到 Playwright 后,如果需要,可将存根移植到I.mockRoute(...)。 - 自定义 Selenium
EventFiringWebDriver/ Selenide 事件监听器——CodeceptJS 使用事件钩子(event.test.before、event.step.failed)和aiTrace插件;如果某个监听器至关重要,请基于这些机制重新实现。 - Java 专属报告功能(测试方法上的 Allure 注解、ExtentReports、ReportPortal Java 代理)——请改用 CodeceptJS 报告器。对于仪表板,
@testomatio/reporter是最直接的替代方案;必须保留 Allure 格式的团队可以使用 Allure 的 CodeceptJS 适配器。 - Cucumber-JVM 步骤定义语言和钩子——特性文件可原样移植,步骤定义则通过 CodeceptJS BDD 用 JS 重写。Java 专属的 glue(
@ScenarioScope、PicoContainer DI)无法迁移。
工作流
按顺序执行各阶段。在每个阶段边界处提交代码,这样任何回归都可以通过二分查找定位。
1. 清点 Java 项目
动手前先了解项目全貌。分两轮进行。
项目结构 — 使用 grep / find 估算迁移工作量:
pom.xml/build.gradle{,.kts}— Selenium/Selenide/JUnit/TestNG 的版本,以及额外依赖(RestAssured、WireMock、Allure、Cucumber、AssertJ、Hamcrest、Awaitility)。src/test/java/**/*.java— 文件数量、包结构。- WebDriver 初始化 —
WebDriverManager.*.setup()、new ChromeDriver(...)、RemoteWebDriver、Configuration.*(Selenide)的使用位置;通常集中在DriverManager/BrowserFactory/BaseTest类中。 - 统计
@FindBy(、@FindAll(、PageFactory.initElements(、WebDriverWait(、ExpectedConditions.、.shouldBe(、.shouldHave(、.shouldNot(、JavascriptExecutor、new Actions(、new Select(、@DataProvider、@ParameterizedTest、Cucumber.feature的出现次数——每种都对应一种已知的替换模式。
共享逻辑和页面对象 — Java 测试套件大量使用 POM 和辅助类。修改测试前先找出它们:
- 页面对象 — 通常位于
src/test/java/**/pages/、**/pageobjects/、**/po/下。包含@FindBy字段和用于驱动交互的方法的类。几乎总是存在。 将其逐一迁移为 CodeceptJS 页面对象。 - 基础测试类 —
BaseTest、BaseUITest、AbstractTest。通常包含@BeforeEachWebDriver 初始化、@AfterEachWebDriver 清理、失败时截图逻辑和共享实用工具。WebDriver 初始化不再需要(由 CodeceptJS 管理生命周期);失败时截图改由screenshot插件实现;共享实用工具拆分到辅助模块中(见下文)。 - 辅助类 / 管理器类 —
LoginHelper、ApiHelper、DbHelper、BrowserHelper、DataFactory、WaitUtils。UI 辅助功能(DOM 技巧、JS 执行、通过 Robot 上传文件)迁移到WebExtra;HTTP 辅助功能(RestAssured 封装、编程式登录、通过 API 准备/清理数据)迁移到ApiExtras;DB 和纯实用类改为普通 JS 模块或ApiExtras方法。 - 监听器 / 扩展 — TestNG
ITestListener、JUnit 5@ExtendWith(...)。改为 CodeceptJS 插件、事件钩子或Before/After钩子。 - 数据提供器 / 固件 —
@DataProvider方法、@ParameterizedTest数据源,以及通过 Jackson / OpenCSV / Apache POI 加载的 JSON/CSV/Excel 文件。改为 CodeceptJS 数据驱动场景使用的Data(...),以及 ES 导入 /fs/promises加载。 - Cucumber 步骤定义 — 如果存在,列出所有步骤正则表达式及其当前 Java 函数体。每个步骤都将改为使用 CodeceptJS BDD 的 JS 步骤定义。
输出一份简短清单:列出每个共享抽象、其当前 Java 位置,以及计划迁移到的 CodeceptJS 目标位置。编写代码前先由用户审阅。
2. 在并行目录中安装 CodeceptJS
跨语言迁移需要并行共存——你无法直接在原位置将 Java 文件改成 JS。创建一个同级目录(例如 e2e/、codeceptjs/;如果团队希望清晰拆分,也可以使用单独的仓库):
mkdir e2e && cd e2e
npx codeceptjs init
选择 WebDriver helper。 这是 Selenium-Java 移植的原生目标——使用相同的 W3C 协议、相同的 capabilities,以及相同的 Grid(如果你有的话)。Java 测试套件会继续通过 CI 并行运行,因此在移植完成前,覆盖率仍能保持正常。
默认无需设置驱动程序。 WebDriver helper 基于 WebdriverIO v9 运行,它会自动下载并启动匹配的浏览器驱动程序。你无需安装 Chromedriver / Geckodriver、运行 Selenium server,也无需设置 host / port。只需配置 helper 并运行即可——无需进行其他设置:
// codecept.conf.js
helpers: {
WebDriver: {
url: 'https://your-app.example.com',
browser: 'chrome',
windowSize: '1280x720',
smartWait: 5000,
desiredCapabilities: {
'goog:chromeOptions': { args: ['--disable-gpu', '--no-sandbox'] },
},
},
},
只有在未设置连接信息(host / port)时,helper 才会启动本地驱动程序;因此,不填写这些信息即可启用自动管理。
smartWait 会在每次查找定位器时提供与 Selenide 等效的隐式等待;再加上自动重试,Java 测试套件中的几乎所有 WebDriverWait / ExpectedConditions.* 行都可以删除。
备选方案:在 Docker 中运行 Selenium。 仅当你需要并行/隔离运行、固定的浏览器版本、在没有本地浏览器的 CI 中运行,或使用现有 Grid 时才需要——codeceptjs-fundamentals 中的惯例以及 node_modules/codeceptjs/docs/webdriver.md § "Selenium in Docker (Selenoid)" 有相关说明。启动一个容器,并在 helper 配置块中添加 host / port(这样会禁用自动管理,改为指向容器):
# Selenoid (parallel-friendly) — see https://aerokube.com/selenoid for full setup
docker run -d --name selenoid -p 4444:4444 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $PWD/selenoid:/etc/selenoid aerokube/selenoid:latest-release
# or the simplest single-browser option
docker run -d --name selenium -p 4444:4444 --shm-size=2g \
selenium/standalone-chrome:latest
// add to the WebDriver block only when using the Docker/Grid fallback
host: '127.0.0.1',
port: 4444,
在开始移植前先对安装进行冒烟测试。 使用一个存根 Scenario(Scenario('boot', ({ I }) => I.amOnPage('/')))运行 npx codeceptjs run --steps,必须能打开浏览器并通过测试。在这里解决驱动程序/capability/容器问题,不要等移植完抽象后再处理。
测试套件通过后:考虑使用 Playwright。 迁移完成且 WebDriver 上的测试稳定后,可以切换 helper,以便通过一个配置实现跨浏览器覆盖(Chromium / Firefox / WebKit)、使用 I.mockRoute(...) 进行网络模拟,并将运行速度提升约 2–3 倍。迁移过程只需机械地替换——将 WebDriver helper 配置块替换为 Playwright 配置块(参见 playwright.md),如果之前使用了 Docker 备选方案,则停止它,然后重新运行。测试代码保持不变。在 WebDriver 测试通过之前不要这样做——同时调试两个新变量(helper 和已移植的测试)只会白费工夫。
3. 移植配置
将 Java 配置来源映射到 codecept.conf.{js,ts}。首选目标:WebDriver helper(稍后切换时会展示 Playwright 替代方案):
| Java 来源 | CodeceptJS 4(WebDriver helper — 默认) | Playwright(稍后切换) |
|---|---|---|
Configuration.baseUrl(Selenide) | helpers.WebDriver.url | helpers.Playwright.url |
Configuration.browser = "chrome" | helpers.WebDriver.browser: 'chrome' | helpers.Playwright.browser: 'chromium' |
Configuration.headless = true | 将 --headless 注入 desiredCapabilities['goog:chromeOptions'].args(或使用 setHeadlessWhen(CI)) | helpers.Playwright.show: false |
Configuration.timeout / Selenium 隐式等待 | helpers.WebDriver.smartWait(每次定位器查找时自动重试) | helpers.Playwright.waitForTimeout |
pageLoadTimeout | helpers.WebDriver.timeouts['page load'] | helpers.Playwright.timeout |
Configuration.browserSize = "1280x720" | helpers.WebDriver.windowSize: '1280x720' | helpers.Playwright.windowSize: '1280x720' |
ChromeOptions().addArguments(...) | desiredCapabilities['goog:chromeOptions'].args | chromium.args / launchOptions.args |
WebDriverManager.*.setup() | 删除 — WebdriverIO v9 会自动管理驱动程序(无需 setup);仅在使用备用方案时需要 Docker | 删除 — Playwright 会管理浏览器 |
RemoteWebDriver URL → Selenium Grid | 添加 helpers.WebDriver.host / port,指向 Grid / Selenoid(备用方案) | 删除 Grid — Playwright 在本地运行 |
Maven surefire / failsafe <systemPropertyVariables> | process.env.* | process.env.* |
pom.xml / build.gradle 测试依赖 | package.json devDependencies(codeceptjs、webdriverio、可选插件) | package.json(codeceptjs、playwright) |
@Test(retryAnalyzer = ...) / Selenide 重试 | 顶层 retry: N | 顶层 retry: N |
@Test(timeOut = 30000)(TestNG) | 每个 Scenario 设置 Scenario(...).timeout(30) | 每个 Scenario 设置 Scenario(...).timeout(30) |
| 测试方法上的 Allure | @testomatio/reporter 或 Allure CodeceptJS 适配器 | 相同 |
4. 移植共享抽象
这是基础工作。应在重写任何 spec 前完成 — 每个 spec 重写的工作量都会随之减少,因为它所需的动词(I.doSmth(...)、loginPage.fill(...))已经存在。
共享 helper 类的硬性规则。 Java *Helper / *Util / *Manager 类中的每个方法,都要成为自定义 CodeceptJS helper 上的方法。按操作类型拆分到两个 helper 中 — 它们的访问模式不同,适用的 API 也不同:
WebExtra(lib/helpers/WebExtra.js)用于由浏览器驱动的操作 — 任何需要打开的页面、DOM、JS 执行、存储或等待网络响应的操作。所有JavascriptExecutor.executeScript(...)方法体都放在这里,实现为browser.execute(...)(WebDriver)或page.evaluate(...)(稍后切换到 Playwright 后)。通过this.helpers['WebDriver'].browser访问 — 这是 WebdriverIO browser 实例。请参阅node_modules/codeceptjs/docs/custom-helpers.md § WebDriver Example。ApiExtras(lib/helpers/ApiExtras.js)用于纯 HTTP 操作 — 通过程序化方式登录、填充 / 清理数据,以及对 API 执行 CRUD 操作。所有 RestAssured 封装 / Apache HttpClient 调用都放在这里,通过 REST helper 路由。通过this.helpers['REST'](或GraphQL)访问。请参阅node_modules/codeceptjs/docs/api.md。
在 codecept.conf.{js,ts} 的 helpers 下注册这两个 helper。
API 操作绝不能绕过 REST helper,直接使用浏览器 helper 底层的 HTTP 接口。这样会跳过 REST + JSONResponse 堆栈 — 不会记录步骤、没有 I.seeResponseCodeIsSuccessful 断言,也没有共享请求头。如果 API 需要使用与浏览器相同的身份验证,请在配置顶部共享一次 Cookie:
import { setSharedCookies } from '@codeceptjs/configure'
setSharedCookies()
……或者在 REST helper 上为基于 token 的身份验证设置 defaultHeaders,也可以在每个测试中使用 I.amBearerAuthenticated(secret(token))。
WebExtra 示例——通过 WebDriver 辅助工具驱动浏览器(来自 BrowserHelper.java 的 JavascriptExecutor 调用会在此处变为 browser.execute(...)):
import Helper from '@codeceptjs/helper'
import fs from 'node:fs/promises'
export default class WebExtra extends Helper {
async scrollIntoView(selector) {
const { browser } = this.helpers['WebDriver']
const el = await browser.$(selector)
await el.scrollIntoView()
}
async setLocalStorage(key, value) {
const { browser } = this.helpers['WebDriver']
await browser.execute((k, v) => localStorage.setItem(k, v), key, value)
}
async writeJsonFile(filePath, data) {
await fs.writeFile(filePath, JSON.stringify(data, null, 2))
}
}
之后切换到 Playwright 辅助工具时,只需更改每个方法的主体——
WebExtra提供的I.*接口保持不变。将this.helpers['WebDriver'].browser替换为this.helpers['Playwright'].page,并将browser.execute(...)转换为page.evaluate(...)。测试无需更改。
ApiExtras 示例——纯 HTTP(来自 LoginHelper.java 的 RestAssured 包装器会在此处呈现):
import Helper from '@codeceptjs/helper'
export default class ApiExtras extends Helper {
async loginViaApi(email, password) {
const REST = this.helpers['REST']
await REST.sendPostRequest('/api/auth/login', { email, password })
}
async seedUser(payload) {
const REST = this.helpers['REST']
const { data } = await REST.sendPostRequest('/api/users', payload)
return data
}
}
辅助代码风格——适用于两者:
- 所有
import语句都放在文件顶部。不要在方法内部使用const fs = await import('node:fs/promises')。 - 使用内置断言(API 使用
I.seeResponseCodeIsSuccessful,浏览器使用I.seeElement)、ExpectHelper或codeceptjs/assertions中的工厂方法——绝不要使用if (cond) throw new Error('...')。失败必须呈现为规范的断言错误。 - 如果你的
WebExtra开始按用户建立会话缓存映射,那你就是在重新实现auth插件——请停止,并让auth插件(阶段 8)处理会话复用。
页面对象。 Java POM 可以非常顺畅地移植,因为 CodeceptJS 将页面对象视为一等 DI 对象。谨慎移植——只保留原 Java 类中已有的方法。完全移除 PageFactory;@FindBy 提供的延迟查找,正是 I.* 调用中的普通定位器字符串已经实现的功能。
// Java
public class LoginPage {
@FindBy(id = "email") WebElement emailField;
@FindBy(css = "[type=submit]") WebElement submitBtn;
public LoginPage(WebDriver driver) { PageFactory.initElements(driver, this); }
public void login(String email, String password) {
emailField.sendKeys(email);
$(By.id("password")).setValue(password);
submitBtn.click();
}
}
// CodeceptJS
import { inject } from 'codeceptjs'
const { I } = inject()
export default {
fields: {
email: '#email',
password: '#password',
submit: { css: '[type=submit]' },
},
login(email, password) {
I.fillField(this.fields.email, email)
I.fillField(this.fields.password, secret(password))
I.click(this.fields.submit)
},
}
在 codecept.conf.{js,ts} 的 include 下注册,这样页面对象就会自动注入 Scenarios。
应避免的页面对象反模式(除非原 Java 代码中已有):
- 断言方法(
checkTitleVisible() { I.seeElement(...) })——页面对象应提供动作方法(fillForm、submitOrder);断言应放在测试中。 - 围绕单个
I.click/I.see*/I.grabTextFrom的单行包装方法——与直接在测试中调用I.*相比,这类包装没有任何益处。 - 仅被一个测试使用的方法——将步骤保留在测试中。
- 任何方法中的
if (cond) throw new Error(...)——应改用I.see*、I.seeNumberOfElements、ExpectHelper或codeceptjs/assertions工厂方法。
钩子。 Java @BeforeEach / @BeforeMethod 中的驱动程序设置可以移除(CodeceptJS 负责生命周期管理)。其他钩子主体移植为 CodeceptJS 钩子:@BeforeEach / @BeforeMethod → Before(({ I }) => ...),@BeforeAll / @BeforeClass → BeforeSuite(...),@After* → 对应的 After*。项目级设置(数据库初始化、生成 API 令牌)放在配置中的 bootstrap() / teardown() 里。
继续之前先做健全性检查:npx codeceptjs check -c <config> 必须通过,并且 npx codeceptjs list -c <config> 必须显示每个 Java 辅助方法都作为由 WebExtra 或 ApiExtras(具体取决于归属)提供的 I.* 操作。
5. 转换规范文件
一次处理一个文件,借助第 4 阶段的抽象。将每个规范文件的工作交给 writing-codeceptjs-tests 技能处理——它会通过 MCP 驱动实时浏览器,并在提交前验证每个步骤。
| Java(JUnit 5 / TestNG / Selenide) | CodeceptJS 4 |
|---|---|
class LoginTest { @Test void X() { ... } } | Feature('Login') + Scenario('X', ({ I }) => { ... }) |
@Test,每个类中有多个 | 每个对应一个 Scenario(...) |
@DisplayName("X")(JUnit 5)/ @Test(description = "X")(TestNG) | Scenario(...) 标题 |
@BeforeEach / @BeforeMethod | Before(({ I }) => { ... }) |
@AfterEach / @AfterMethod | After(({ I }) => { ... }) |
@BeforeAll / @BeforeClass | BeforeSuite(({ I }) => { ... }) |
@AfterAll / @AfterClass | AfterSuite(...) |
@Disabled / @Ignore / enabled = false | xScenario(...) 或 Scenario.skip(...) |
@Tag("smoke")(JUnit 5)/ @Test(groups = "smoke")(TestNG) | Scenario(...).tag('@smoke') |
@ParameterizedTest + @CsvSource(...) | Data([{ ... }, { ... }]).Scenario(...) |
@Test(dataProvider = "rows") + @DataProvider | 同样使用:Data(rows).Scenario(...) |
driver.get('/x') / Selenide.open('/x') | I.amOnPage('/x') |
loginPage.login(...)(POM 调用) | LoginPage.login(...)(自动注入页面对象) |
删除显式等待代码。 这是改动最大的一项机械性工作:
// Selenium — gone
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.elementToBeClickable(By.id("save")))
.click();
// Selenide — collapses
$("#save").shouldBe(visible).click();
// CodeceptJS — auto-waits
I.click('#save')
如果某个特定等待不可或缺(例如点击前等待加载旋转图标消失),请使用正确的 I.waitFor* 保留它:
I.waitForInvisible('.spinner', 10)
I.click('#save')
迭代。 在测试、页面对象和辅助代码中,任何包含 I.* 调用的循环都使用 for...of。绝不要使用 Array.prototype.forEach。.forEach 会吞掉迭代回调的返回值——其中的 await 不会阻塞外层函数,CodeceptJS 记录器可能会以错误顺序排队步骤,或在循环完成前结束 Scenario。
for (const sort of testSort) {
I.click(locate(this.filterFormLabel).withText(sort))
}
每批:运行 npx codeceptjs dry-run --steps -c <config>——加载每个 Scenario,解析每个 I.* 调用,无需启动浏览器。几秒内即可发现拼写错误、缺少导入、未放在 include 下的页面对象,以及不存在的动词。先修复这些问题,再进行任何实际运行。
然后运行此批次:npx codeceptjs run --steps -c <config>。
- 首次实际运行几乎总会失败——定位器偏移、显式
WebDriverWait代码掩盖的时序问题、身份验证/会话差异、数据假设。这是预期情况;修复问题本就是迁移的一部分。 - 每次失败 → 调用
debugging-codeceptjs-tests并立即修复(断点、实时页面检查、验证修复)。不要盲目重写,也不要用retry掩盖问题。 - 批次运行通过才算完成,而不是 dry-run 无报错就算完成。
6. 定位器
每个定位器都要限定上下文。 每个操作的最后一个参数都会将查找范围缩小到某个区域——I.click('Save', '.toolbar')、I.fillField('Email', '[email protected]', '#login-form')、I.click({ role: 'button', name: 'Delete' }, '.modal')。简短的语义或 ARIA 定位器加上上下文,胜过冗长且未限定范围的定位器:它读起来就像页面本身,不用不断增加长度就能区分重复标签,也能经受标记改动。请将这一做法应用于下方表格的每一行——源框架中的链通常都能清晰拆分为区域 + 用户看到的内容。
CodeceptJS 优先级——选择最适合的最高优先级方式,然后添加上下文:
- 语义字符串——按钮文本、标签、占位符、链接文本:
I.click('Save', '.toolbar')、I.fillField('Email', '[email protected]', '#login-form')。 普通字符串本身就能匹配aria-label,因此对于aria-label="Save"的纯图标控件,应使用I.click('Save', <context>)——绝不要使用'aria-label=Save'或{ css: '[aria-label="Save"]' }。 - ARIA 角色——
I.click({ role: 'button', name: 'Sign In' }, '#login-form')。 - 通过
customLocator插件使用$name——适用于大量使用data-test/data-qa属性的测试套件。 locate()构造器——I.click(locate('button').withText('Edit').inside('tr').withText('Acme'));通常拆分后更好:I.click('Edit', locate('tr').withText('Acme'))。- CSS / XPath——仅作为后备选项。
Java 测试套件大量使用 By.id、By.cssSelector、By.xpath、By.linkText 和 @FindBy(...)。对应写法如下:
| Java | CodeceptJS |
|---|---|
By.id("save") / @FindBy(id = "save") | '#save' |
By.cssSelector(...) / @FindBy(css = ...) | { css: '...' } |
By.xpath(...) / @FindBy(xpath = ...) | { xpath: '...' } |
By.linkText("Edit") / @FindBy(linkText = ...) | 'Edit'(语义定位) |
By.partialLinkText("Edit") | locate('a').withText('Edit') |
By.tagName("button") / By.className(...) | 'button' / '.cls' |
By.name("email") | { name: 'email' }(与 Selenide 等效的 [name=email] 查找) |
@FindBy(how = How.NAME, using = "email") | { name: 'email' } |
Selenide $(...).find(...) / .parent() | locate(...).inside(...) / .parent() |
$$(...).filter(text("Active")).first() | locate(...).withText('Active').first() |
如果测试套件大量使用 [data-test=...] / [data-qa=...] 属性,请启用 customLocator 插件,这样就可以写成 $submit,而不是 { css: '[data-test=submit]' }。完整指南见 writing-codeceptjs-tests § 定位器。
7. 操作、断言、获取值
| Java (Selenium / Selenide) | CodeceptJS 4 |
|---|---|
el.click() / $(...).click() | I.click(sel) |
el.sendKeys("x") / $(...).setValue("x") | I.fillField(sel, 'x') |
el.clear() / $(...).clear() | I.clearField(sel) |
new Select(el).selectByVisibleText("A") / $(...).selectOption("A") | I.selectOption(sel, 'A') |
复选框 el.click() / $(...).setSelected(true) | I.checkOption(sel) / I.uncheckOption(sel) |
new Actions(driver).moveToElement(el).perform() | I.moveCursorTo(sel) |
Actions...dragAndDrop(a, b).perform() | I.dragAndDrop(srcSel, dstSel) |
Actions...keyDown(Keys.CONTROL).sendKeys("a")... | I.pressKey(['Control', 'a']) |
el.isDisplayed() / $(...).shouldBe(visible) | I.seeElement(sel) |
el.getText() / $(...).getText() | await I.grabTextFrom(sel) |
$(...).shouldHave(text("X")) / assertEquals("X", el.getText()) | I.see('X', sel) |
$(...).shouldHave(value("X")) / assertEquals("X", el.getAttribute("value")) | I.seeInField(sel, 'X') |
$$(...).shouldHave(size(5)) / assertEquals(5, els.size()) | I.seeNumberOfElements(sel, 5) |
driver.getCurrentUrl().contains("/x") / webdriver().shouldHave(url(...)) | I.seeInCurrentUrl('/x') |
((JavascriptExecutor) driver).executeScript(js, args) | 在 WebExtra 中调用 page.evaluate 的方法 |
driver.manage().getCookies() | await I.grabCookie() |
driver.manage().addCookie(...) | I.setCookie({...}) |
((TakesScreenshot) driver).getScreenshotAs(...) | 删除——失败时由 screenshot 插件处理;手动截图使用 I.saveScreenshot('name.png') |
仅获取值时使用 await。普通操作会自动排队。
8. 会话与身份验证
Java 测试套件几乎总会使用 LoginHelper / AuthBase 执行 UI 登录,或通过编程方式登录并注入 Cookie。两种方式都迁移到 auth 插件。请交由 codeceptjs-auth 技能处理设置流程。
- 编程方式登录 → 在
ApiExtras中定义方法(loginViaApi);auth插件的角色定义会调用该方法一次,并缓存生成的 Cookie / 存储状态。 - UI 登录 → 在
WebExtra中定义方法(login);auth插件的角色会以相同方式调用该方法。 - 在测试间复用 Cookie → 删除手动逻辑;
auth会缓存辅助方法生成的 Cookie 和存储状态(无论使用哪种插件配置,PlaywrightstorageState和 WebDriver Cookie jar 都适用)。 - 多用户场景 → 使用
codeceptjs/effects中的session(...)。
9. 测试数据与夹具
| Java | CodeceptJS 4 |
|---|---|
返回 Object[][] 的 @DataProvider | Data([{ ... }, { ... }]).Scenario(...) |
@ParameterizedTest + @CsvSource / @ValueSource | Data([...]).Scenario(...) |
Jackson ObjectMapper.readValue(jsonFile, ...) | import users from './fixtures/users.json' with { type: 'json' } |
OpenCSV CSVReader | @codeceptjs/data 或直接导入 csv-parse |
| Apache POI 读取 Excel | 较少使用;提前将工作表转换为 JSON/CSV,或在确有需要时使用 xlsx |
Faker (Java) | @faker-js/faker(可直接替换) |
10. API 测试与模拟
| Java | CodeceptJS 4 |
|---|---|
RestAssured given().when().then() | REST helper:await I.sendPostRequest(...) + I.seeResponseCodeIsSuccessful / I.seeResponseContainsKeys / Zod schema |
| Apache HttpClient wrappers | 通过 REST helper 调用 ApiExtras 上的方法 |
| WireMock client-side stubs | 在 WebDriver 上:保留 WireMock(或任何 sidecar proxy)——WebDriver 不支持客户端网络拦截。之后切换到 Playwright 时,迁移为 I.mockRoute(url, route => route.fulfill({...})) |
| WireMock backend sidecar | 保留 WireMock 作为 sidecar process 运行;无论如何,CodeceptJS 都不会取代后端服务模拟 |
客户端网络模拟是 WebDriver helper 无法匹配的一项能力。如果 Java 测试套件大量依赖 WireMock 客户端 stub,且你希望将它们以内联方式写成
I.mockRoute(...),那么在 WebDriver 测试通过后立即安排切换到 Playwright,是一个充分的理由。
11. Cucumber-JVM(如果存在)
如果 Java 测试套件由 Cucumber 驱动:
.feature文件保留原位(或移至 CodeceptJS 项目下的features/)。- 配置 CodeceptJS BDD(
node_modules/codeceptjs/docs/bdd.md)以加载这些文件。 - 将每个 Java step def 改写为 JS step def,并调用
I.*和页面对象。step 文本和正则表达式保持不变,因此 feature 无需修改。 - Java 专用的 Cucumber glue(
@ScenarioScope、PicoContainer、cucumber.options)将移除——CodeceptJS BDD 有自己的 hook 接口。
12. 退役 Java 测试套件
只有在所有 spec 都已迁移且 CI 中的 CodeceptJS 测试通过后,才能执行以下操作:
- 删除 Java 测试源文件(
src/test/java/,以及仅用于测试的src/test/resources/)。 - 从
pom.xml/build.gradle中移除 Selenium / Selenide / JUnit / TestNG / RestAssured / WireMock-Java / Allure /webdrivermanager。 - 从 CI 中移除 Maven / Gradle 测试调用,并替换为
npx codeceptjs run-workers <N>(或run)任务。
验证
- 默认路径:无需检查——WebdriverIO v9 会启动驱动程序。只有使用 Docker fallback 时才需检查:容器可访问,且
curl -fsS http://127.0.0.1:4444/status返回ready: true。 npx codeceptjs check -c <config>— 检查 config、helper 和 plugin 是否正常。npx codeceptjs list -c <config>— 每个已迁移的 Java helper 方法都作为来自WebExtra或ApiExtras的I.*操作出现;每个页面对象的方法也都出现。npx codeceptjs dry-run --steps -c <config>— 每个 Scenario 都能加载。- 在 WebDriver 上完整运行:
npx codeceptjs run --steps -c <config>。首次运行时出现失败是预期情况——通过debugging-codeceptjs-testsskill 逐一排查并修复(不要使用retry,也不要盲目重写)。只有整个转换后的测试套件全部通过,迁移才算完成。 - 交由
codeceptjs-run-analysis检查output/trace_*/产物(需要启用aiTraceplugin)。 find src/test/java -name "*.java"— 删除 Java 目录前,结果应为空。
可选后续步骤:切换到 Playwright
迁移完成且 WebDriver 测试在 CI 中稳定运行后,可考虑为后续工作切换 helper:
npm i -D playwright。- 将
helpers下的WebDriver块替换为Playwright块(参见node_modules/codeceptjs/docs/playwright.md);移除host/port/desiredCapabilities。 - 将每个
WebExtra方法体中的this.helpers['WebDriver'].browser.execute(...)转换为this.helpers['Playwright'].page.evaluate(...)。helper 提供的I.*接口保持不变。 - 如果使用了 Docker fallback,请停止容器——Playwright 会自行管理浏览器。
- 重新运行
dry-run和 smoke suite。页面对象、spec、fixture 和authplugin 配置均无需修改。
你将获得:通过一份 config 覆盖多个浏览器(Chromium / Firefox / WebKit)、更快的运行速度、原生的 I.mockRoute(...) 网络模拟,以及通过 MCP loop 获取更丰富的 ARIA 快照。
相关技能
writing-codeceptjs-tests— 针对每个 spec 的重写指南(由 MCP 驱动,步骤经过验证)debugging-codeceptjs-tests— 首次完整运行后,对每个失败的测试都使用此技能codeceptjs-auth— 替代LoginHelper/ 基于 cookie 的会话复用codeceptjs-fundamentals— Docker 回退 Selenium 约定;迁移后运行以确认接线配置- 参考文档:
node_modules/codeceptjs/docs/(基础知识、webdriver、playwright、定位器、自定义助手、API、身份验证、BDD、效果)