docs/migrate-from-protractor.md
Protractor was end-of-lifed in April 2023 and has shipped nothing since. Its design wrapped Selenium with two things: waitForAngular, which blocked each action until Angular's digest cycle settled, and the ControlFlow promise manager, which ordered commands so test code could be written as if it were synchronous. That is why a Protractor suite pins an old Selenium version and threads results through .then(). The dependency only ages from here.
CodeceptJS drops the Angular coupling. The helper waits on the element you are acting on, not on Angular's digest, so the same test works on an Angular app, a React app, or a static page. It still speaks the WebDriver protocol, so an existing Selenium Grid keeps serving the new suite, or you switch the config to Playwright and run the same test code faster.
Migrating a Protractor suite looks like a lot of work. It is not. We prepared a set of skills, so you can relax and let an agent do the migration.
The original test in Protractor:
// Protractor + Jasmine
describe('login', function () {
it('user can log in', function () {
browser.get('https://example.com/login');
element(by.model('user.email')).sendKeys('[email protected]');
element(by.model('user.password')).sendKeys('secret');
element(by.css('button[type=submit]')).click();
expect(browser.getCurrentUrl()).toContain('/dashboard');
expect(element(by.css('.welcome')).getText()).toContain('Welcome, Alice');
});
});
Will look in CodeceptJS:
// CodeceptJS
Scenario('user can log in', ({ I }) => {
I.amOnPage('/login');
I.fillField('Email', '[email protected]');
I.fillField('Password', 'secret');
I.click('Sign in');
I.seeInCurrentUrl('/dashboard');
I.see('Welcome, Alice', '.welcome');
});
The describe/it nesting, the by.model strategy, and the Jasmine assertions are gone. The steps read as a sequence instead of a chain of .then() calls.
And here is how the test looks while it runs. Every step is printed live, in the same order it was written:
user can log in
I am on page "/login"
I fill field "Email", "[email protected]"
I fill field "Password", "secret"
I click "Sign in"
I see in current url "/dashboard"
I see "Welcome, Alice", ".welcome"
When a step fails, the output stays on that line, with the locator that missed and a screenshot attached. There is no separate report step before you know what happened.
The conversions are mechanical, so you do not have to do them by hand, and the work does not cost you working time. Install the skills bundle, point an agent at the repo, and check back when it reports.
The migrate-protractor-to-codeceptjs skill in the CodeceptJS skills bundle does the whole port:
First runs fail, because locators drift and timing changes. The agent then uses the debugging-codeceptjs-tests skill to fix each failure against the live browser before moving on. Your Protractor suite keeps running in CI until the port is green, so nothing is at risk while the agent works.
Install the bundle in Claude Code:
/plugin marketplace add codeceptjs/skills
/plugin install codeceptjs@codeceptjs-skills
Or any other agent:
npx skills add codeceptjs/skills
Then ask: "Migrate this Protractor suite to CodeceptJS." The skill triggers on the Protractor signatures in your repo. Start it, do other work, and read the step output when it reports back.
locate(), and the customLocator plugin