mcpField
M
migrate-protractor-to-codeceptjs

migrate-protractor-to-codeceptjs

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

关于 migrate-protractor-to-codeceptjs

这项迁移 Skill 指导团队将 Protractor 端到端测试套件转到 CodeceptJS 4。内容涵盖项目盘点、配置转换、共享 Helper 和页面对象迁移,并指出需要以不同方式处理的旧有行为。

主要功能

  1. 01盘点 Protractor 项目
  2. 02映射到 CodeceptJS 的配置
  3. 03设置 WebDriver 或 Playwright
  4. 04转换自定义 Helper
  5. 05页面对象迁移指导
  6. 06替换旧有行为的指导

适用场景

  1. 01规划渐进式 Protractor 迁移
  2. 02转换 Protractor 配置
  3. 03迁移共享 Helper 和页面对象
  4. 04替换 Angular 等待和 Promise 链

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/codeceptjs/skills --skill migrate-protractor-to-codeceptjs

安装后在 Claude Code 中输入/migrate-protractor-to-codeceptjs确认已加载。

详细介绍

迁移 Protractor → CodeceptJS 4

Protractor 已于 2023 年 4 月终止生命周期。CodeceptJS 4 是一个理想的迁移目标:它原生支持 WebDriver(Protractor 所基于的运行时),也支持 Playwright;Playwright 速度更快、更稳定,是新项目的推荐选择。此次迁移还将移除 Protractor 本身已弃用的三项遗留机制:Selenium ControlFlow、Angular 同步钩子(waitForAngular)以及用于排入浏览器操作队列的 .then() Promise 链。

需要理解的三个根本差异:

  1. 不再使用 Promise 链。 Protractor 的 .then() 链会在 Selenium ControlFlow 上排入操作;CodeceptJS 通过内部 recorder 自动将 I.* 调用排入队列,因此测试代码看起来是同步的。只有获取类操作需要 await(await I.grabTextFrom(...))。
  2. 使用 Helpers,而不是 browser / driver。 I.* 会将操作分发给已配置的 helper。推荐使用 Playwright;如果测试套件必须继续通过 Selenium Grid 运行,也可以使用 WebDriver——无论使用哪一种,测试代码都相同。
  3. 自动等待,而不是等待 Angular。 移除 browser.waitForAngular() 和 browser.ignoreSynchronization。Playwright 和 WebDriver helpers 会等待 DOM 和元素稳定,无需框架专用钩子即可覆盖 Angular 的渲染周期。

权威参考资料:node_modules/codeceptjs/docs/(basics、locators、playwright、webdriver、custom-helpers、pageobjects)。

何时触发迁移

符合以下任一条件:

  • 仓库根目录下存在 protractor.conf.{js,ts}。
  • devDependencies 中列出了 protractor(通常还包括 @types/jasmine、jasmine、jasmine-spec-reporter)。
  • 存在带有 spec 文件的 e2e/ 或 protractor/ 目录(常见文件名为 *.e2e-spec.{js,ts} 或 *.spec.{js,ts})。
  • 从 protractor 导入了 browser、element、by、ExpectedConditions、ElementFinder 或 ElementArrayFinder。
  • 代码调用了 element(by.X(...))、element.all(...)、by.addLocator(...)、browser.get(...)、browser.executeScript(...)、browser.wait(EC.*)、browser.waitForAngular()、browser.ignoreSynchronization、browser.params.* 或 browser.driver.*。
  • 用户提出“从 Protractor 迁移/移植/转换”。

无法迁移的内容

请一开始就坦诚说明:

  • Selenium ControlFlow——已移除。迁移过程中需要将剩余的 ControlFlow 式操作顺序转换为普通的 async/await。如果项目已禁用 SELENIUM_PROMISE_MANAGER,这基本上只是机械转换;如果仍启用,则需审查每个 spec,检查隐式执行顺序。
  • browser.waitForAngular() / browser.ignoreSynchronization / browser.waitForAngularEnabled(false)——移除。CodeceptJS 会自动等待 DOM 稳定,这正是 Angular 所需的行为。如果某个步骤依赖 Angular 同步来掩盖实际的竞态条件,迁移后就会明确失败——请用具体的 I.waitFor* 修复。
  • Angular 专用定位策略——by.binding(...)、by.repeater(...)、by.model(...)、by.options(...) 没有内置的 CodeceptJS 等效项。请改用 CSS 属性选择器({ css: '[ng-model="user.email"]' });如果使用范围很广,则在 WebExtra / customLocator 中注册自定义定位策略。
  • Jasmine 测试基础设施——jasmineNodeOpts、自定义匹配器(jasmine.addMatchers)、jasmine-spec-reporter。CodeceptJS 使用 Mocha;匹配器可转换为 ExpectHelper / codeceptjs/assertions,报告器可转换为 CodeceptJS 插件(@testomatio/reporter、mochawesome 等)。
  • browser.params——替换为普通的 process.env.*。没有与 Protractor 类型化参数对象等效的功能。

工作流程

按顺序执行各阶段。每个阶段完成后都提交代码,以便通过二分查找定位任何回归问题。

1. 清点 Protractor 项目

动手之前,先全面了解项目。分两轮进行。

项目概况 — 使用 grep / wc -l 查找影响工作量的因素:

  • protractor.conf.{js,ts} — 正在使用哪些键(seleniumAddress、directConnect、capabilities、baseUrl、specs、params、onPrepare、onComplete、framework、jasmineNodeOpts、allScriptsTimeout)
  • **/*.e2e-spec.{js,ts}(或 e2e/ 目录下的 **/*.spec.{js,ts})— 测试规范数量
  • 导入 protractor 的文件 — 所有使用 browser、element、by、ExpectedConditions 的文件
  • 统计 .then(、by.addLocator(、browser.waitForAngular(、browser.executeScript(、browser.ignoreSynchronization、browser.params.、EC. 的出现次数 — 每种情况都对应一种已知的替换模式

共享逻辑与页面对象 — Protractor 项目几乎总是采用显式的页面对象模式(这是 Protractor 官方文档推荐的惯用做法)。在处理测试规范之前先找出这些内容:

  • 页面对象 — 通常位于 e2e/page-objects/、e2e/pages/、e2e/po/ 或 e2e/<feature>/<feature>.po.ts。导出类(或普通对象)的模块,其中包含使用 element(by.X(...)) 的属性(惰性的 ElementFinder 引用)以及用于驱动交互的方法。这些是一等抽象 — 直接移植为 CodeceptJS 页面对象。
  • 自定义定位器 — 每个 by.addLocator('<name>', fn),通常位于 onPrepare 或辅助文件中。每个都应转换为 WebExtra 中的自定义策略,或 customLocator 插件配置中的一项。
  • 辅助模块 — 位于 e2e/helpers/ 或 e2e/utils/。UI 辅助逻辑(DOM 操作、executeScript)移至 WebExtra;HTTP 辅助逻辑(编程式登录、数据预置/清理)移至 ApiExtras。
  • onPrepare / onComplete 钩子 — Protractor 的全局设置/清理逻辑。一次性设置改为 codecept.conf.{js,ts} 中的 bootstrap() / teardown(),或改为基础测试规范中的 Before / BeforeSuite,用于每个测试套件的设置。
  • Jasmine 自定义匹配器 — jasmine.addMatchers({ ... }) 定义改为自定义辅助工具中的可复用断言,并使用 codeceptjs/assertions 工厂函数。

生成一份简短清单,列出每个共享抽象的当前位置和计划迁移到的 CodeceptJS 位置。在编写任何代码之前,先由用户审核。

2. 在 Protractor 旁安装 CodeceptJS

运行 npx codeceptjs init 并选择 Playwright 辅助工具(更现代、更快,且比 Selenium 更稳定)。只有在团队必须继续使用 Selenium Grid 时才选择 WebDriver — 无论选择哪种方式,测试代码都相同。暂时不要移除 Protractor — 迁移期间两者并行运行,因此即使测试套件只完成部分迁移,也能保持覆盖率通过。

3. 移植配置

将 protractor.conf.{js,ts} 的键映射到 codecept.conf.{js,ts}:

ProtractorCodeceptJS 4(Playwright 辅助工具)
baseUrlhelpers.Playwright.url
capabilities.browserName(chrome / firefox)helpers.Playwright.browser(chromium / firefox / webkit)
capabilities.chromeOptions.argshelpers.Playwright.chromium.args / launchOptions.args
directConnect: true删除 — Playwright 会管理浏览器
seleniumAddress删除(Playwright),或设为 helpers.WebDriver.host / port(WebDriver 辅助工具)
specs: ['./e2e/**/*.e2e-spec.ts']tests: './tests/**/*_test.{js,ts}'
allScriptsTimeout / defaultTimeoutIntervalhelpers.Playwright.waitForTimeout、顶层的 timeout
params: { ... }process.env.*
framework: 'jasmine' / jasmineNodeOpts删除 — CodeceptJS 使用 Mocha
onPrepare(...)bootstrap()
onComplete(...)teardown()
SELENIUM_PROMISE_MANAGER: false删除 — CodeceptJS 必须使用 async/await
useAllAngular2AppRoots / rootElement删除 — 不需要
4. 移植共享抽象

这是迁移的基石。在重写任何测试规范之前先完成这一步 — 每个测试规范都会因此变得更精简,因为它所需的操作(I.doSmth(...))已经存在。 共享辅助代码的硬性规则。 Protractor 辅助模块上的每个方法都会成为自定义 CodeceptJS 辅助模块上的一个方法。按操作类型拆分到两个辅助模块中——它们的访问模式和正确 API 各不相同:

  • WebExtra(lib/helpers/WebExtra.js)用于由浏览器驱动的操作——任何需要打开的页面、DOM、evaluate、初始化脚本、存储、等待网络响应的操作。所有 browser.executeScript(...) 调用都应放在这里,并转换为 page.evaluate(...)。通过 this.helpers['Playwright'].page / .browserContext 访问。
  • ApiExtras(lib/helpers/ApiExtras.js)用于纯 HTTP 操作——以编程方式登录、初始化 / 清理数据、通过 API 执行 CRUD。通过 this.helpers['REST'](或 GraphQL)访问。REST 辅助模块配置请参阅 node_modules/codeceptjs/docs/api.md。

在 codecept.conf.{js,ts} 的 helpers 下注册这两个辅助模块。

绝不要使用 this.helpers['Playwright'].browserContext.request.* 执行 API 操作。 这会绕过 REST + JSONResponse 调用栈——没有步骤日志记录、没有 I.seeResponseCodeIsSuccessful 断言,也没有共享标头。如果 API 需要使用与浏览器相同的身份验证,请在配置顶部共享一次 Cookie:

import { setSharedCookies } from '@codeceptjs/configure'
setSharedCookies()

……或者在 REST 辅助模块上设置 defaultHeaders,用于基于令牌的身份验证;也可以在每个测试中使用 I.amBearerAuthenticated(secret(token))。api.md 涵盖了这三种模式。

WebExtra 示例——由浏览器驱动的操作,包括将 browser.executeScript 转换为 page.evaluate 的规范做法:

import Helper from '@codeceptjs/helper'

export default class WebExtra extends Helper {
  async setLocalStorage(key, value) {
    const { page } = this.helpers['Playwright']
    await page.evaluate(([k, v]) => localStorage.setItem(k, v), [key, value])
  }

  async scrollIntoView(selector) {
    const { page } = this.helpers['Playwright']
    await page.locator(selector).scrollIntoViewIfNeeded()
  }

  async waitForAngularRouterEvents(timeout = 10000) {
    const { page } = this.helpers['Playwright']
    await page.waitForFunction(
      () => !document.querySelector('.cdk-overlay-backdrop, .mat-progress-bar'),
      { timeout },
    )
  }
}

ApiExtras 示例——通过 REST 辅助模块执行的纯 HTTP 操作:

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(user) {
    const REST = this.helpers['REST']
    const { data } = await REST.sendPostRequest('/api/users', user)
    return data
  }
}

辅助模块代码风格——适用于这两个辅助模块:

  • 所有 import 语句都放在文件顶部。绝不要在方法内部写 const fs = await import('node:fs/promises')。
  • 使用内置断言(API 使用 I.seeResponseCodeIsSuccessful,浏览器使用 I.seeElement)、ExpectHelper,或来自 codeceptjs/assertions 的工厂函数——绝不要写 if (cond) throw new Error('...')。失败必须呈现为规范的断言错误。请参阅 node_modules/codeceptjs/docs/assertions.md。

自定义定位器策略(by.addLocator)——有两个去向:

  • 简单属性策略(在底层转换为 CSS / XPath)→ 启用 customLocator 插件并配置每个策略。启用后,I.click('$submit') 会解析 submit 策略。
  • 复杂策略(针对 DOM 执行 JavaScript)→ 在 WebExtra 中编写一个返回匹配元素的方法;如有需要,再添加一个精简的 I.click* / I.see* 包装方法。

阶段 1 清单中的其他去向:

  • Protractor 页面对象 → 放在 pages/ 下的 CodeceptJS 页面对象类。谨慎移植——只保留原页面对象已有的方法;迁移期间不要臆造新的包装方法。ElementFinder 属性(get usernameField() { return element(by.id('user')) })转换为定位器字符串字段(fields = { usernameField: '#user' });方法则在顶部使用 const { I } = inject(),并调用 I.fillField、I.click 以及 WebExtra / ApiExtras 辅助模块现在提供的任何 I.* 动词。在 codecept.conf.{js,ts} 的 include 下注册,以便页面对象自动注入到场景中。

    应避免的页面对象反模式(除非原 Protractor 页面对象中已有这些做法):

    • 断言方法(checkTitle() { I.seeElement(...) })——页面对象应包含操作动词(fillForm、submitOrder);断言应放在测试中。
    • 对单个 I.click / I.see* / I.grabTextFrom 调用进行的单行包装——与其使用包装方法,不如直接在测试中调用 I.*。
    • 只被一个测试使用的方法——将步骤留在测试中。
    • 任何方法中的 if (cond) throw new Error(...)——改用 I.see*、I.seeNumberOfElements、ExpectHelper 或 codeceptjs/assertions 工厂函数。
  • 共享选择器常量 → 放在相关页面对象的字段中。不要使用游离的 selectors.{js,ts}。

  • 不操作浏览器的纯工具模块 → 使用纯 ES 模块,并在需要时导入。

  • onPrepare / onComplete → 在 codecept.conf.{js,ts} 中使用 bootstrap / teardown,或者在工作针对每个 spec 时使用 BeforeSuite / AfterSuite。

继续之前先做健全性检查:npx codeceptjs check -c <config> 必须通过,且 npx codeceptjs list -c <config> 必须将每个已移植的辅助方法列为由 WebExtra 或 ApiExtras 提供的 I.* 操作。

5. 转换 spec 文件

每次处理一个文件,充分利用第 4 阶段的抽象。将每个 spec 的工作交给 writing-codeceptjs-tests 技能——它通过 MCP 驱动实时浏览器,并在提交前验证每一步。

ProtractorCodeceptJS 4
文件 *.e2e-spec.{js,ts}*_test.{js,ts}
describe('X', () => { ... })顶层的 Feature('X'),每个文件一个 Feature
it('Y', () => { ... })Scenario('Y', ({ I }) => { ... })
beforeEach(() => { ... })Before(({ I }) => { ... })
afterEach(() => { ... })After(({ I }) => { ... })
beforeAll(...) / afterAll(...)BeforeSuite(...) / AfterSuite(...)
browser.get('/x')I.amOnPage('/x')
await loginPage.login(u, p)loginPage.login(u, p)(封装操作的无返回值页面对象方法无需使用 await)

彻底删除 browser.waitForAngular() / browser.ignoreSynchronization——包括每个调用点。CodeceptJS 会自动等待。

Promise 链——大多数 .then(...) 链都可以简化为普通语句,因为 recorder 会将操作加入队列。仅在获取值时使用 await:

const text = await I.grabTextFrom('.foo')
I.expectEqual(text, 'Hello')

迭代——在测试、页面对象和辅助模块中,凡是包含 I.* 调用的循环,都使用 for...of。绝不要使用 Array.prototype.forEach。.forEach 会忽略迭代回调的返回值——其中的 await 不会阻塞外层函数,recorder 可能会将步骤按错误顺序加入队列,或者在循环完成前就结束 Scenario。for...of 可确保循环按顺序执行,也便于日后添加 await,无需重写代码:

for (const sort of testSort) {
  I.click(locate(this.filterFormLabel).withText(sort))
}
for (const row of await I.grabWebElements('.row')) {
  const text = await row.getText()
  I.expectNotEmpty(text)
}

每批次:运行 npx codeceptjs dry-run --steps -c <config>——加载每个 Scenario,解析每个 I.* 调用,但不启动浏览器。几秒内即可发现拼写错误、缺少的导入、未包含在 include 中的页面对象,以及不存在的动词。开始实际运行前先修复。

然后运行该批次:npx codeceptjs run --steps -c <config>。

  • 首次实际运行几乎总会失败——定位器偏移、ControlFlow 隐藏在自身排队机制后的时序问题、身份验证/会话差异、数据假设。这很正常;修复问题是迁移工作的一部分。
  • 每次失败时,都调用 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')。可直接覆盖 Protractor 的 by.linkText、by.buttonText、by.partialButtonText、by.partialLinkText。 普通字符串本身就会匹配 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')。适用于采用 Material / 符合 ARIA 规范组件的 Angular 应用,是稳妥的默认选择。
  3. 通过 customLocator 插件使用 $name — 当应用使用 data-qa / data-test 标记元素时;这比在每个调用处重复属性选择器更好。
  4. locate() 构建器 — I.click(locate('.row').withText('Acme').inside('table'))。它直接对应 by.cssContainingText + 元素遍历链——不过,I.click('Edit', locate('tr').withText('Acme')) 通常是更好的拆分方式。
  5. CSS / XPath / 属性对象 — { id: 'foo' }、{ name: 'email' }、{ css: '[ng-model="user.email"]' }、{ xpath: '//div[@id="x"]' }。用于 Angular 指令属性的后备方案。
Protractor 定位器CodeceptJS 4
by.css('.btn')'.btn'
by.id('foo'){ id: 'foo' }
by.name('email'){ name: 'email' }
by.tagName('input'){ css: 'input' }
by.linkText('Sign In')'Sign In'(语义定位)
by.partialLinkText('Sig')'Sig'(语义定位,默认部分匹配)
by.buttonText('Submit')'Submit'(语义定位)
by.partialButtonText('Sub')'Sub'(语义定位)
by.xpath('//div[@id="x"]'){ xpath: '//div[@id="x"]' }
by.cssContainingText('.row', 'Acme')locate('.row').withText('Acme')
by.binding('user.name'){ css: '[ng-bind="user.name"]' } 或自定义定位器
by.model('user.email'){ css: '[ng-model="user.email"]' }
by.repeater('item in items'){ css: '[ng-repeat="item in items"]' }
by.options('opt for opt in options'){ css: '[ng-options="opt for opt in options"]' }
by.addLocator('myLocator', fn)customLocator 插件,或 WebExtra 上的方法

元素遍历 — Protractor 的链(element(...).element(...)、.all(...)、.first()、.get(N))可以转换为 CodeceptJS 的上下文参数、locate() 链和 step.opts:

ProtractorCodeceptJS 4
element(by.X)仅定位器('.foo'、{ role: ... } 等)
element.all(by.X)await I.grabWebElements(sel)
element.all(by.X).count()(断言)I.seeNumberOfElements(sel, N)
element.all(by.X).first()step.opts({ elementIndex: 'first' })(或 1)
element.all(by.X).last()step.opts({ elementIndex: 'last' })
element.all(by.X).get(N)step.opts({ elementIndex: N + 1 })
element(by.X).element(by.Y)上下文参数:I.click(Y, X)——或 locate(Y).inside(X)
element(by.X).$('.foo')同上

step.opts(...) 来自 import step from 'codeceptjs/steps'。

7. 操作、断言与抓取
ProtractorCodeceptJS 4
element(sel).click()I.click(sel)
element(sel).sendKeys('x')I.fillField(sel, 'x')
element(sel).clear()I.clearField(sel)
element(sel).submit()I.click('Submit', form) 或 I.pressKey('Enter')
element(sel).getText().then(...)const t = await I.grabTextFrom(sel)
element(sel).getAttribute('data-id')await I.grabAttributeFrom(sel, 'data-id')
element(sel).isDisplayed() (asserted)I.seeElement(sel)
element(sel).isPresent() (asserted)I.seeElementInDOM(sel)
expect(element(sel).getText()).toEqual('X')I.see('X', sel)
expect(element.all(sel).count()).toBe(N)I.seeNumberOfElements(sel, N)
expect(browser.getCurrentUrl()).toContain('/x')I.seeInCurrentUrl('/x')
browser.executeScript(fn, args)在 helper 中:await page.evaluate(fn, args)
browser.refresh()I.refreshPage()
browser.getCurrentUrl()await I.grabCurrentUrl()
browser.getTitle()await I.grabTitle()
browser.sleep(N)I.wait(N / 1000) — CodeceptJS 使用秒;避免在已提交的测试中直接等待
browser.wait(EC.visibilityOf(el), 5000)I.waitForVisible(sel, 5)
browser.wait(EC.invisibilityOf(el), 5000)I.waitForInvisible(sel, 5)
browser.wait(EC.presenceOf(el), 5000)I.waitForElement(sel, 5)
browser.wait(EC.textToBePresentInElement(el, 'X'), 5000)I.waitForText('X', 5, sel)
browser.wait(EC.urlContains('/x'), 5000)I.waitInUrl('/x', 5)
browser.waitForAngular()删除
browser.params.userprocess.env.USER

仅在抓取操作前使用 await。普通操作会自动排队。

8. 会话与身份验证

Protractor 没有原生的会话复用功能——大多数项目要么在每个 beforeEach 中通过 UI 登录,要么通过 browser.driver.manage().addCookie(...) 来实现快捷操作。请用 auth 插件替换这两种做法。有关设置步骤,请参阅 codeceptjs-auth。如果你已在第 4 阶段将登录迁移到 ApiExtras 中的 I.loginViaApi(...),或迁移到 WebExtra 中的 I.login(...),那么 auth 插件的角色定义只需调用它。对于多用户场景(Protractor 没有原生的对应功能),请使用 codeceptjs/effects 中的 session(...)。

9. Fixtures、请求与任务
ProtractorCodeceptJS 4
require('./fixtures/users.json')import users from './fixtures/users.json' with { type: 'json' }
onPrepare 中的 http.request(...)通过 REST helper 调用 await I.sendPostRequest(...);在第 4 阶段的 ApiExtras helper 中封装可复用的流程
protractor-cucumber-framework / Cucumber hooksCodeceptJS BDD(gherkin:steps)或普通的 bootstrap / teardown

REST helper 身份验证:@codeceptjs/configure 中的 setSharedCookies() 会将浏览器会话与 REST 共享,使同一用户在两端都处于登录状态;也可以为静态令牌设置 defaultHeaders,或在每项测试中使用 I.amBearerAuthenticated(secret(token))。请参阅 node_modules/codeceptjs/docs/api.md。

10. 网络模拟

Protractor 没有内置的网络拦截功能——项目通常通过 browser.executeScript 修补 XMLHttpRequest,或使用后端 stub 自行实现。对于基于 Playwright 的 CodeceptJS 迁移,请改用 I.mockRoute(url, route => route.fulfill({ ... })),或使用 I.stopMockingRoute(url) 禁用模拟。请参阅 node_modules/codeceptjs/docs/playwright.md § Mocking Network Requests。

11. 移除 Protractor

仅在所有 spec 都已迁移且 CI 全部通过后:删除 e2e/(或存放 Protractor spec 的目录)、protractor.conf.*,从 devDependencies 中移除 protractor、@types/jasmine、jasmine、jasmine-spec-reporter,删除 Protractor CI 任务,并卸载对应的 Chrome / Selenium webdriver-manager 二进制文件。

验证

  1. npx codeceptjs check -c <config> — 检查配置、辅助工具和插件是否正常。
  2. npx codeceptjs list -c <config> — 每个移植的辅助工具方法都应作为 WebExtra 或 ApiExtras 中的 I.* 操作出现;每个页面对象的方法也都应出现。
  3. npx codeceptjs dry-run --steps -c <config> — 每个 Scenario 都应能加载。
  4. 完整运行:npx codeceptjs run --steps -c <config>。首次运行时出现失败是预期的——通过 debugging-codeceptjs-tests 技能逐一修复(不要重试,也不要盲目重写)。只有整个转换后的测试套件全部通过,迁移才算完成。
  5. 交由 codeceptjs-run-analysis 检查 output/trace_*/ 产物(需启用 aiTrace 插件)。
  6. grep -rE "\\bbrowser\\.|\\bby\\.|\\.then\\(|waitForAngular" e2e/ — 删除 e2e/ 前,搜索结果应为空。

相关技能

  • writing-codeceptjs-tests — 各测试规范的重写指南(由 MCP 驱动,步骤经过验证)
  • debugging-codeceptjs-tests — 首次完整运行后,用它处理每个失败的测试
  • codeceptjs-auth — 替代每个 beforeEach 中通过 UI 重新登录或手动注入 Cookie 的做法
  • codeceptjs-fundamentals — 迁移后运行,以确认接线正确
  • 参考文档:node_modules/codeceptjs/docs/(基础、playwright、webdriver、定位器、自定义辅助工具、API、断言、页面对象、会话、效果)

评论

支持 Markdown