This guide shows how to migrate directly from BasePage (v1) to PageObject (v2). It focuses on the structural changes, locator conversion, and API differences you must address.
v1 is deprecated and will be removed in 2.0.0. v1.5.0 is the final v1 release.
Before changing code, list where the following appear:
BasePagesubclasses.initLocatorSchemasdefinitions.LocatorSchemaobjects andGetByMethodusage.getLocatorSchema(...).update(...)andaddFilter(...)chains.getLocator/getNestedLocatorwrappers.SessionStorageusage (old signatures).PlaywrightReportLoggerusage in constructors.- Any
data-cyselectors or custom selector engines.
class LoginPage extends BasePage<Paths> {
constructor(page: Page, testInfo: TestInfo, log: PlaywrightReportLogger) {
super(page, testInfo, "https://example.com", "/login", "LoginPage", log);
}
protected initLocatorSchemas() {
this.locators.addSchema({
locatorSchemaPath: "main.button@login",
locatorMethod: GetByMethod.role,
role: "button",
roleOptions: { name: "Login" },
});
}
}class LoginPage extends PageObject<Paths> {
constructor(page: Page) {
super(page, "https://example.com", "/login", { label: "LoginPage" });
}
protected defineLocators(): void {
this.add("main.button@login").getByRole("button", { name: "Login" });
}
protected pageActionsToPerformAfterNavigation() {
return [];
}
}Key changes:
- Remove
testInfo,PlaywrightReportLogger, andpocNameconstructor args. - Use
defineLocators()instead ofinitLocatorSchemas(). - Provide
pageActionsToPerformAfterNavigation().
this.locators.addSchema({
locatorSchemaPath: "main.form@login.input@username",
locatorMethod: GetByMethod.label,
label: "Username",
});this.add("main.form@login.input@username").getByLabel("Username");Mapping guide:
v1 locatorMethod |
v2 method |
|---|---|
role |
getByRole(...) |
text |
getByText(...) |
label |
getByLabel(...) |
placeholder |
getByPlaceholder(...) |
altText |
getByAltText(...) |
title |
getByTitle(...) |
locator |
locator(...) |
frameLocator |
frameLocator(...) |
testId |
getByTestId(...) |
id (custom) |
getById(...) (custom) |
dataCy (custom) |
locator('[data-cy="..."]') |
In v1 these were async wrappers and accepted index maps. In v2 they are synchronous and do not accept index maps.
const submit = await poc.getNestedLocator("main.form.button@submit", {
"main.form": 0,
});const submit = poc
.getLocatorSchema("main.form.button@submit")
.nth("main.form", 0)
.getNestedLocator();const submit = await poc
.getLocatorSchema("main.form.button@submit")
.update("main.form.button@submit", { roleOptions: { name: "Sign in" } })
.addFilter("main.form.button@submit", { hasText: /Sign in/i })
.getNestedLocator();const submit = poc
.getLocatorSchema("main.form.button@submit")
.update("main.form.button@submit")
.getByRole({ name: "Sign in" })
.filter("main.form.button@submit", { hasText: /Sign in/i })
.getNestedLocator();await poc.sessionStorage.set({ token: "abc" }, true);
const data = await poc.sessionStorage.get(["token"]);
await poc.sessionStorage.clear();await poc.sessionStorage.set({ token: "abc" }, { reload: true });
const data = await poc.sessionStorage.get(["token"], { waitForContext: true });
await poc.sessionStorage.clear({ waitForContext: true });In v1, BasePage receives PlaywrightReportLogger and keeps it internally. In v2, logging is provided by the test fixture.
import { test } from "pomwright";
test("login flow", async ({ page, log }) => {
log.info("starting login");
});If you want logging inside POMs, pass a child logger explicitly.
v2 adds a navigation helper to PageObject and expects a pageActionsToPerformAfterNavigation() method.
protected pageActionsToPerformAfterNavigation() {
return [
async () => {
await this.getNestedLocator("main.form@login").waitFor({ state: "visible" });
},
];
}
await loginPage.navigation.gotoThisPage();
await loginPage.navigation.expectAnotherPage();BaseApiis deprecated and not part of v2. Use your own API base class.GetByandLocatorSchemaobjects are replaced by the registry DSL.data-cyselector engine is removed; uselocator('[data-cy="..."]')or register your own selector engine in Playwright.
- Run tests.
- Check all POMs compile with strict types.
- Confirm registry paths match the new dot-delimited rules (no whitespace).
- Replace
BasePagewithPageObject. - Move
initLocatorSchemas→defineLocators. - Convert
LocatorSchemaobjects toadd(...).getBy...calls. - Replace
addFilterand index maps withfilter+nth. - Update
SessionStoragesignatures. - Remove
data-cyand custom selector engine assumptions. - Adopt the v2
testfixture for logging or implement it directly, see docs/v2/logging.md.
For the bridge option, see bridge-migration-guide.md.