关于 migrate-protractor-to-codeceptjs
这项迁移 Skill 指导团队将 Protractor 端到端测试套件转到 CodeceptJS 4。内容涵盖项目盘点、配置转换、共享 Helper 和页面对象迁移,并指出需要以不同方式处理的旧有行为。
主要功能
- 01盘点 Protractor 项目
- 02映射到 CodeceptJS 的配置
- 03设置 WebDriver 或 Playwright
- 04转换自定义 Helper
- 05页面对象迁移指导
- 06替换旧有行为的指导
适用场景
- 01规划渐进式 Protractor 迁移
- 02转换 Protractor 配置
- 03迁移共享 Helper 和页面对象
- 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 链。
需要理解的三个根本差异:
- 不再使用 Promise 链。 Protractor 的
.then()链会在 Selenium ControlFlow 上排入操作;CodeceptJS 通过内部 recorder 自动将I.*调用排入队列,因此测试代码看起来是同步的。只有获取类操作需要await(await I.grabTextFrom(...))。 - 使用 Helpers,而不是
browser/driver。I.*会将操作分发给已配置的 helper。推荐使用 Playwright;如果测试套件必须继续通过 Selenium Grid 运行,也可以使用 WebDriver——无论使用哪一种,测试代码都相同。 - 自动等待,而不是等待 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}:
| Protractor | CodeceptJS 4(Playwright 辅助工具) |
|---|---|
baseUrl | helpers.Playwright.url |
capabilities.browserName(chrome / firefox) | helpers.Playwright.browser(chromium / firefox / webkit) |
capabilities.chromeOptions.args | helpers.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 / defaultTimeoutInterval | helpers.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 驱动实时浏览器,并在提交前验证每一步。
| Protractor | CodeceptJS 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 优先级——选择最符合需求的最高优先级定位器,然后添加上下文:
- 语义字符串 — 按钮文本、标签、占位符、链接文本:
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"]' }。 - ARIA 角色 —
I.click({ role: 'button', name: 'Sign In' }, '#login-form')。适用于采用 Material / 符合 ARIA 规范组件的 Angular 应用,是稳妥的默认选择。 - 通过
customLocator插件使用$name— 当应用使用data-qa/data-test标记元素时;这比在每个调用处重复属性选择器更好。 locate()构建器 —I.click(locate('.row').withText('Acme').inside('table'))。它直接对应by.cssContainingText+ 元素遍历链——不过,I.click('Edit', locate('tr').withText('Acme'))通常是更好的拆分方式。- 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:
| Protractor | CodeceptJS 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. 操作、断言与抓取
| Protractor | CodeceptJS 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.user | process.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、请求与任务
| Protractor | CodeceptJS 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 hooks | CodeceptJS 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 二进制文件。
验证
npx codeceptjs check -c <config>— 检查配置、辅助工具和插件是否正常。npx codeceptjs list -c <config>— 每个移植的辅助工具方法都应作为WebExtra或ApiExtras中的I.*操作出现;每个页面对象的方法也都应出现。npx codeceptjs dry-run --steps -c <config>— 每个 Scenario 都应能加载。- 完整运行:
npx codeceptjs run --steps -c <config>。首次运行时出现失败是预期的——通过debugging-codeceptjs-tests技能逐一修复(不要重试,也不要盲目重写)。只有整个转换后的测试套件全部通过,迁移才算完成。 - 交由
codeceptjs-run-analysis检查output/trace_*/产物(需启用aiTrace插件)。 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、断言、页面对象、会话、效果)