Migrate from CodeceptJS to Playwright.
Apply with the Grit CLI
grit apply codecept_to_playwright
Converts Codecept property
BEFORE
// @filename: test.js const { I } = inject(); export default { url: 'https://grit.io', selector: locate('#migration-selector').as('Selector'), openai: locate('text=custodian-sample-org/openai-quickstart-python').as('Openai'), };
AFTER
// @filename: test.js import { expect } from '@playwright/test'; export default class Test extends BasePage { get url() { return 'https://grit.io'; } get selector() { return this.page.locator('#migration-selector'); } get openai() { return this.page.locator('text=custodian-sample-org/openai-quickstart-python'); } }
Converts waiters
BEFORE
// @filename: someFolder/test.js const { I } = inject(); export default { url: 'https://grit.io', waitForGrit() { I.waitInUrl(this.url); I.waitForText('Studio', 10, this.heading); I.see('Function expressions to arrow functions', this.rewrite); I.click(this.button); I.waitForVisible(this.rewritten); }, };
AFTER
// @filename: someFolder/test.js import { expect } from '@playwright/test'; export default class Test extends BasePage { get url() { return 'https://grit.io'; } async waitForGrit() { await this.page.waitForURL(new RegExp(this.url)); await expect(this.heading).toHaveText('Studio', { timeout: 10 * 1000, ignoreCase: true, }); await expect(this.rewrite).toContainText('Function expressions to arrow functions'); await this.button.click(); await this.rewritten.waitFor({ state: 'visible' }); } }
Converts complex locators
BEFORE
// @filename: someFolder/test.js const { I } = inject(); export default { studio: locate('.studio'), message: 'Hello world', button: (name) => locate(`//button[contains(text(), "${name}")]`).as(name), waitForGrit() { I.waitForVisible(this.studio.withText(this.message), 5); I.click(this.button('grit').at(2), this.studio); I.seeCssPropertiesOnElements(this.studio, { 'background-color': '#3570b6', display: 'flex', }); I.seeAttributesOnElements(this.studio, { open: true, 'grit-label': 'nice', }); let lines = I.grabNumberOfVisibleElements(locate('div').withDescendant('p')); I.seeNumberOfVisibleElements(locate('div').withDescendant('p'), lines); }, };
AFTER
// @filename: someFolder/test.js import { expect } from '@playwright/test'; export default class Test extends BasePage { get studio() { return this.page.locator('.studio'); } get message() { return 'Hello world'; } button(name) { return this.page.locator(`//button[contains(text(), "${name}")]`); } async waitForGrit() { await this.studio .and(this.page.locator(`:has-text("${this.message}")`)) .waitFor({ state: 'visible', timeout: 5 * 1000 }); await this.studio.locator(this.button('grit').nth(1)).click(); await expect(this.studio).toHaveCSS('background-color', '#3570b6'); await expect(this.studio).toHaveCSS('display', 'flex'); await expect(this.studio).toHaveAttribute('open', 'true'); await expect(this.studio).toHaveAttribute('grit-label', 'nice'); let lines = await this.page .locator('div') .filter({ has: this.page.locator('p') }) .count(); expect( await this.page .locator('div') .filter({ has: this.page.locator('p') }) .count(), ).toEqual(lines); } }
Converts Codecept scenario
BEFORE
Scenario('Trivial test', async ({ I }) => { projectPage.open(); I.waitForVisible(projectPage.list); I.refreshPage(); I.see(projectPage.demo, projectPage.list); expect(true).toBe(true); projectPage.close(); }) .tag('Email') .tag('Studio') .tag('Projects');
AFTER
import { expect } from '@playwright/test'; test('Trivial test @Email @Studio @Projects', async ({ page, factory, context }) => { var projectPage = new ProjectPage(page, context); await projectPage.open(); await projectPage.list.waitFor({ state: 'visible' }); await page.reload(); await expect(projectPage.list).toContainText(projectPage.demo); await expect(true).toBe(true); await projectPage.close(); });
Does not convert inner object properties to getters
BEFORE
// @filename: someFolder/test.js const { I } = inject(); export default { studio: locate('.studio'), message: 'Hello world', section: { editor: locate('#editor'), title: 'Apply a GritQL pattern', }, someMethod() { return { foo: bar, }; }, };
AFTER
// @filename: someFolder/test.js import { expect } from '@playwright/test'; export default class Test extends BasePage { get studio() { return this.page.locator('.studio'); } get message() { return 'Hello world'; } get section() { return { editor: this.page.locator('#editor'), title: 'Apply a GritQL pattern', }; } async someMethod() { return { foo: bar, }; } }
Converts Codecept scenario with multiple args and parentheses in description
BEFORE
Scenario('Trivial test (good)', async ({ I, loginAs }) => { projectPage.open(); listModal.open(); patternsList.open(); I.waitForVisible(projectPage.list); }) .tag('Email') .tag('Studio') .tag('Projects') .tag('Multiword tag');
AFTER
import { expect } from '@playwright/test'; test('Trivial test (good) @Email @Studio @Projects @Multiword tag', async ({ page, factory, context, }) => { var projectPage = new ProjectPage(page, context); var listModal = new ListModal(page, context); var patternsList = new PatternsList(page, context); await projectPage.open(); await listModal.open(); await patternsList.open(); await projectPage.list.waitFor({ state: 'visible' }); });
Converts parameterized tests
BEFORE
let myData = new DataTable(['id', 'name', 'capital']); myData.add([1, 'England', 'London']); myData.add([2, 'France', 'Paris']); myData.add([3, 'Germany', 'Berlin']); myData.add([4, 'Italy', 'Rome']); Data(myData) .Scenario('Trivial test', { myData }, async ({ I, current }) => { I.say(current.capital); I.dragAndDrop(data.label, data.map); I.dragToPoint(data.label, 400, 0); }) .tag('Email') .tag('Studio') .tag('Projects');
AFTER
import { expect } from '@playwright/test'; let myData = [ { id: 1, name: 'England', capital: 'London' }, { id: 2, name: 'France', capital: 'Paris' }, { id: 3, name: 'Germany', capital: 'Berlin' }, { id: 4, name: 'Italy', capital: 'Rome' }, ]; for (const current of myData) { test('Trivial test @Email @Studio @Projects', async ({ page, factory, context }) => { console.log(current.capital); await data.label.dragTo(data.map); await data.label.dragTo(data.label, { targetPosition: { x: 400, y: 0 } }); }); }
Wraps tests in describe block
BEFORE
Feature('Test capitals'); import { Capitals } from '../data/capitals'; let myData = new DataTable(['id', 'name', 'capital']); myData.add([1, 'England', Capitals.London]); myData.add([2, 'France', Capitals.Paris]); myData.add([3, 'Germany', Capitals.Berlin]); myData.add([4, 'Italy', Capitals.Rome]); Data(myData) .Scenario('Trivial test', { myData }, async ({ I, current }) => { I.say(current.capital); }) .tag('Email') .tag('Studio') .tag('Projects');
AFTER
import { Capitals } from '../data/capitals'; import { expect } from '@playwright/test'; test.describe('Test capitals', () => { let myData = [ { id: 1, name: 'England', capital: Capitals.London }, { id: 2, name: 'France', capital: Capitals.Paris }, { id: 3, name: 'Germany', capital: Capitals.Berlin }, { id: 4, name: 'Italy', capital: Capitals.Rome }, ]; for (const current of myData) { test('Trivial test @Email @Studio @Projects', async ({ page, factory, context }) => { console.log(current.capital); }); } });
Converts tests with backtick descriptions
BEFORE
let myData = new DataTable(['id', 'name', 'capital']); myData.add([1, 'England', 'London']); myData.add([2, 'France', 'Paris']); myData.add([3, 'Germany', 'Berlin']); myData.add([4, 'Italy', 'Rome']); Data(myData) .Scenario(`Trivial test`, { myData }, async ({ I, current }) => { I.say(current.capital); }) .tag('Email') .tag('Studio') .tag('Projects');
AFTER
import { expect } from '@playwright/test'; let myData = [ { id: 1, name: 'England', capital: 'London' }, { id: 2, name: 'France', capital: 'Paris' }, { id: 3, name: 'Germany', capital: 'Berlin' }, { id: 4, name: 'Italy', capital: 'Rome' }, ]; for (const current of myData) { test(`Trivial test @Email @Studio @Projects`, async ({ page, factory, context }) => { console.log(current.capital); }); }
Intelligently converts stringlike locators
BEFORE
Scenario('Trivial test', async ({ I }) => { project_page.open(); I.waitForVisible('.list' + ' ' + className); I.waitNumberOfVisibleElements('.grit-sample', 3); I.seeInField(`input[name="${username}"]`, 'admin'); }) .tag('Email') .tag('Studio') .tag('Projects');
AFTER
import { expect } from '@playwright/test'; test('Trivial test @Email @Studio @Projects', async ({ page, factory, context }) => { var project_page = new ProjectPage(page, context); await project_page.open(); await page.locator('.list' + ' ' + className).waitFor({ state: 'visible' }); await expect(page.locator('.grit-sample')).toHaveCount(3); await expect(page.locator(`input[name="${username}"]`)).toHaveValue('admin'); });
Converts Before and After hooks
BEFORE
Feature('Project page'); BeforeSuite(({ I }) => { I.say('Ensure that you have access to the project'); }); Scenario('Trivial test', async ({ I }) => { projectPage.open(); }) .tag('Email') .tag('Studio') .tag('Projects'); After(async ({ I }) => { await resetProjectSettings(); });
AFTER
import { expect } from '@playwright/test'; test.describe('Project page', () => { test.beforeAll(async ({ page, request }) => { console.log('Ensure that you have access to the project'); }); test('Trivial test @Email @Studio @Projects', async ({ page, factory, context }) => { var projectPage = new ProjectPage(page, context); await projectPage.open(); }); test.afterEach(async ({ page, request }) => { await resetProjectSettings(); }); });