mcpField
M
migrate-selenium-java-to-codeceptjs

migrate-selenium-java-to-codeceptjs

github.com/codeceptjs测试与质量252 次安装1 周前更新
代码仓库

关于 migrate-selenium-java-to-codeceptjs

一项将 Selenium 或 Selenide 的 Java 测试套件迁移到 CodeceptJS 4 的技能,涵盖项目盘点、并行环境搭建和测试模式转换,可在迁移期间保留现有套件运行。

主要功能

  1. 01盘点 Java 测试套件及共享抽象
  2. 02在 Java 项目旁搭建 CodeceptJS
  3. 03将测试迁移到 WebDriver helper
  4. 04转换页面对象、辅助类和测试数据
  5. 05识别需要重新设计的 Java 专属功能

适用场景

  1. 01规划 Selenium 或 Selenide 套件迁移
  2. 02迁移到 CodeceptJS 期间继续运行 Java 测试
  3. 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 辅助编写),而且这些模式可以迁移使用。

需要牢记三个根本区别:

  1. 测试按顺序执行,无需显式等待。 CodeceptJS 会自动将每个 I.* 调用排入内部记录器,并自动等待 DOM/元素稳定。Selenium 中的 WebDriverWait + ExpectedConditions.* 样板代码可以全部移除;Selenide 的 .shouldBe(visible) / .shouldHave(text(...)) 链通常会合并到操作本身(I.click('Save') 会等待、点击并断言)。在 CodeceptJS 中,await 仅用于抓取操作(await I.grabTextFrom(...))——用于普通操作也能运行,但不建议这样做。这是规范文件中最明显的变化。
  2. 使用 Helpers,而不是 WebDriver / driver。 I.* 会将操作分派给已配置的 helper。默认使用 WebDriver helper——它使用与你现有 Java 测试套件相同的 W3C WebDriver 协议,因此迁移方式最原生:相同的 Selenium 服务、相同的浏览器驱动、相同的能力配置,如果你有 Grid,也可以使用相同的 Grid。测试套件在 WebDriver 上通过后,你可以通过修改一个 helper 区块,切换为 Playwright helper(现代、速度更快、更稳定,并且通过一份配置即可原生支持多浏览器)——无论使用哪种 helper,测试代码都完全相同。
  3. 一流抽象。 页面对象、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 扩展、TestNG IInvokedMethodListener 监听器。这些都可以用普通 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。通常包含 @BeforeEach WebDriver 初始化、@AfterEach WebDriver 清理、失败时截图逻辑和共享实用工具。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.urlhelpers.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
pageLoadTimeouthelpers.WebDriver.timeouts['page load']helpers.Playwright.timeout
Configuration.browserSize = "1280x720"helpers.WebDriver.windowSize: '1280x720'helpers.Playwright.windowSize: '1280x720'
ChromeOptions().addArguments(...)desiredCapabilities['goog:chromeOptions'].argschromium.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 / @BeforeMethodBefore(({ I }) => { ... })
@AfterEach / @AfterMethodAfter(({ I }) => { ... })
@BeforeAll / @BeforeClassBeforeSuite(({ I }) => { ... })
@AfterAll / @AfterClassAfterSuite(...)
@Disabled / @Ignore / enabled = falsexScenario(...) 或 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 优先级——选择最适合的最高优先级方式,然后添加上下文:

  1. 语义字符串——按钮文本、标签、占位符、链接文本: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"]' }。
  2. ARIA 角色——I.click({ role: 'button', name: 'Sign In' }, '#login-form')。
  3. 通过 customLocator 插件使用 $name——适用于大量使用 data-test / data-qa 属性的测试套件。
  4. locate() 构造器——I.click(locate('button').withText('Edit').inside('tr').withText('Acme'));通常拆分后更好:I.click('Edit', locate('tr').withText('Acme'))。
  5. CSS / XPath——仅作为后备选项。

Java 测试套件大量使用 By.id、By.cssSelector、By.xpath、By.linkText 和 @FindBy(...)。对应写法如下:

JavaCodeceptJS
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 和存储状态(无论使用哪种插件配置,Playwright storageState 和 WebDriver Cookie jar 都适用)。
  • 多用户场景 → 使用 codeceptjs/effects 中的 session(...)。
9. 测试数据与夹具
JavaCodeceptJS 4
返回 Object[][] 的 @DataProviderData([{ ... }, { ... }]).Scenario(...)
@ParameterizedTest + @CsvSource / @ValueSourceData([...]).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 测试与模拟
JavaCodeceptJS 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)任务。

验证

  1. 默认路径:无需检查——WebdriverIO v9 会启动驱动程序。只有使用 Docker fallback 时才需检查:容器可访问,且 curl -fsS http://127.0.0.1:4444/status 返回 ready: true。
  2. npx codeceptjs check -c <config> — 检查 config、helper 和 plugin 是否正常。
  3. npx codeceptjs list -c <config> — 每个已迁移的 Java helper 方法都作为来自 WebExtra 或 ApiExtras 的 I.* 操作出现;每个页面对象的方法也都出现。
  4. npx codeceptjs dry-run --steps -c <config> — 每个 Scenario 都能加载。
  5. 在 WebDriver 上完整运行:npx codeceptjs run --steps -c <config>。首次运行时出现失败是预期情况——通过 debugging-codeceptjs-tests skill 逐一排查并修复(不要使用 retry,也不要盲目重写)。只有整个转换后的测试套件全部通过,迁移才算完成。
  6. 交由 codeceptjs-run-analysis 检查 output/trace_*/ 产物(需要启用 aiTrace plugin)。
  7. find src/test/java -name "*.java" — 删除 Java 目录前,结果应为空。

可选后续步骤:切换到 Playwright

迁移完成且 WebDriver 测试在 CI 中稳定运行后,可考虑为后续工作切换 helper:

  1. npm i -D playwright。
  2. 将 helpers 下的 WebDriver 块替换为 Playwright 块(参见 node_modules/codeceptjs/docs/playwright.md);移除 host / port / desiredCapabilities。
  3. 将每个 WebExtra 方法体中的 this.helpers['WebDriver'].browser.execute(...) 转换为 this.helpers['Playwright'].page.evaluate(...)。helper 提供的 I.* 接口保持不变。
  4. 如果使用了 Docker fallback,请停止容器——Playwright 会自行管理浏览器。
  5. 重新运行 dry-run 和 smoke suite。页面对象、spec、fixture 和 auth plugin 配置均无需修改。

你将获得:通过一份 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、效果)

评论

支持 Markdown