pomwright 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +5 -5
- package/dist/index.d.mts +91 -989
- package/dist/index.d.ts +91 -989
- package/dist/index.js +627 -1887
- package/dist/index.mjs +633 -1888
- package/package.json +9 -11
- package/AGENTS.md +0 -37
- package/docs/v1/BaseApi-explanation.md +0 -63
- package/docs/v1/BasePage-explanation.md +0 -96
- package/docs/v1/LocatorSchema-explanation.md +0 -271
- package/docs/v1/LocatorSchemaPath-explanation.md +0 -165
- package/docs/v1/PlaywrightReportLogger-explanation.md +0 -56
- package/docs/v1/get-locator-methods-explanation.md +0 -250
- package/docs/v1/intro-to-using-pomwright.md +0 -899
- package/docs/v1/sessionStorage-methods-explanation.md +0 -38
- package/docs/v1/tips-folder-structure.md +0 -38
- package/docs/v1-to-v2-migration/bridge-migration-guide.md +0 -159
- package/docs/v1-to-v2-migration/direct-migration-guide.md +0 -238
- package/docs/v1-to-v2-migration/v1-to-v2-comparison.md +0 -547
- package/docs/v2/PageObject.md +0 -293
- package/docs/v2/composing-locator-modules.md +0 -93
- package/docs/v2/locator-registry.md +0 -693
- package/docs/v2/logging.md +0 -168
- package/docs/v2/overview.md +0 -515
- package/docs/v2/session-storage.md +0 -160
- package/index.ts +0 -75
- package/intTestV2/.env +0 -0
- package/intTestV2/fixtures/testApp.fixtures.ts +0 -43
- package/intTestV2/package.json +0 -22
- package/intTestV2/page-object-models/testApp/pages/iframe/iframe.locatorSchema.ts +0 -24
- package/intTestV2/page-object-models/testApp/pages/iframe/iframe.page.ts +0 -17
- package/intTestV2/page-object-models/testApp/pages/testPage.locatorSchema.ts +0 -32
- package/intTestV2/page-object-models/testApp/pages/testPage.page.ts +0 -119
- package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.locatorSchema.ts +0 -29
- package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.page.ts +0 -48
- package/intTestV2/page-object-models/testApp/pages/testPath/testPath.locatorSchema.ts +0 -9
- package/intTestV2/page-object-models/testApp/pages/testPath/testPath.page.ts +0 -23
- package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.locatorSchema.ts +0 -114
- package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.page.ts +0 -23
- package/intTestV2/page-object-models/testApp/testApp.base.ts +0 -20
- package/intTestV2/playwright.config.ts +0 -54
- package/intTestV2/server.js +0 -216
- package/intTestV2/test-data/staticPage/index.html +0 -280
- package/intTestV2/test-data/staticPage/w3images/avatar2.png +0 -0
- package/intTestV2/test-data/staticPage/w3images/avatar3.png +0 -0
- package/intTestV2/test-data/staticPage/w3images/avatar5.png +0 -0
- package/intTestV2/test-data/staticPage/w3images/avatar6.png +0 -0
- package/intTestV2/test-data/staticPage/w3images/forest.jpg +0 -0
- package/intTestV2/test-data/staticPage/w3images/lights.jpg +0 -0
- package/intTestV2/test-data/staticPage/w3images/mountains.jpg +0 -0
- package/intTestV2/test-data/staticPage/w3images/nature.jpg +0 -0
- package/intTestV2/test-data/staticPage/w3images/snow.jpg +0 -0
- package/intTestV2/tests/locatorRegistry/add/add.describe.spec.ts +0 -54
- package/intTestV2/tests/locatorRegistry/add/add.filter.spec.ts +0 -143
- package/intTestV2/tests/locatorRegistry/add/add.frameLocator.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.getByAltText.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.getById.spec.ts +0 -45
- package/intTestV2/tests/locatorRegistry/add/add.getByLabel.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.getByPlaceholder.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.getByRole.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.getByTestId.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.getByText.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.getByTitle.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.locator.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/add/add.reuseExisting.spec.ts +0 -66
- package/intTestV2/tests/locatorRegistry/add/add.reuseReusable.spec.ts +0 -311
- package/intTestV2/tests/locatorRegistry/add/add.spec.ts +0 -159
- package/intTestV2/tests/locatorRegistry/filter.cycle.spec.ts +0 -39
- package/intTestV2/tests/locatorRegistry/getLocator/getLocator.spec.ts +0 -253
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.clearSteps.spec.ts +0 -105
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.describe.spec.ts +0 -23
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.filter.spec.ts +0 -368
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getLocator.spec.ts +0 -56
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getNestedLocator.spec.ts +0 -175
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.nth.spec.ts +0 -60
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.remove.spec.ts +0 -32
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.replace.spec.ts +0 -24
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.spec.ts +0 -110
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.update.spec.ts +0 -322
- package/intTestV2/tests/locatorRegistry/getNestedLocator/getNestedLocator.spec.ts +0 -412
- package/intTestV2/tests/locatorRegistry/registry/registry.binding.spec.ts +0 -50
- package/intTestV2/tests/locatorRegistry/validation/validation.locatorSchemaPath.spec.ts +0 -115
- package/intTestV2/tests/locatorRegistry/validation/validation.sub-path.spec.ts +0 -45
- package/intTestV2/tests/step/step.spec.ts +0 -49
- package/intTestV2/tests/testApp/color.spec.ts +0 -15
- package/intTestV2/tests/testApp/iframe.spec.ts +0 -57
- package/intTestV2/tests/testApp/testFilters.spec.ts +0 -24
- package/intTestV2/tests/testApp/testPage.spec.ts +0 -161
- package/intTestV2/tests/testApp/testPath.spec.ts +0 -18
- package/pack-build.sh +0 -11
- package/pack-test-v2.sh +0 -36
- package/playwright.base.ts +0 -42
- package/skills/README.md +0 -56
- package/skills/pomwright-v1-5-bridge-migration/SKILL.md +0 -40
- package/skills/pomwright-v1-5-bridge-migration/references/call-site-migration.md +0 -178
- package/skills/pomwright-v1-5-bridge-migration/references/schema-translation.md +0 -183
- package/skills/pomwright-v2-migration/SKILL.md +0 -63
- package/skills/pomwright-v2-migration/references/call-site-migration.md +0 -265
- package/skills/pomwright-v2-migration/references/class-migration.md +0 -266
- package/skills/pomwright-v2-migration/references/fixture-and-helpers.md +0 -423
- package/skills/pomwright-v2-migration/references/locator-registration.md +0 -344
- package/srcV2/fixture/base.fixtures.ts +0 -23
- package/srcV2/helpers/navigation.ts +0 -153
- package/srcV2/helpers/playwrightReportLogger.ts +0 -196
- package/srcV2/helpers/sessionStorage.ts +0 -251
- package/srcV2/helpers/stepDecorator.ts +0 -106
- package/srcV2/locators/index.ts +0 -15
- package/srcV2/locators/locatorQueryBuilder.ts +0 -427
- package/srcV2/locators/locatorRegistrationBuilder.ts +0 -558
- package/srcV2/locators/locatorRegistry.ts +0 -541
- package/srcV2/locators/locatorUpdateBuilder.ts +0 -602
- package/srcV2/locators/reusableLocatorBuilder.ts +0 -200
- package/srcV2/locators/types.ts +0 -256
- package/srcV2/locators/utils.ts +0 -309
- package/srcV2/locators/v1SchemaTranslator.ts +0 -178
- package/srcV2/pageObject.ts +0 -105
package/docs/v2/logging.md
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# Logging (v2)
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
POMWright v2 provides a `PlaywrightReportLogger` class and a `test` fixture that attaches log entries to the Playwright HTML report. The logger:
|
|
6
|
-
|
|
7
|
-
- Supports `debug`, `info`, `warn`, and `error` levels.
|
|
8
|
-
- Shares log entries and log level across child loggers created from the same root.
|
|
9
|
-
- Attaches logs to the Playwright report when a test finishes.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## The `test` fixture
|
|
14
|
-
|
|
15
|
-
POMWright exports a `test` fixture that injects a `log` instance.
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
import { test, expect } from "pomwright";
|
|
19
|
-
|
|
20
|
-
test("logs are attached to the HTML report", async ({ page, log }) => {
|
|
21
|
-
log.info("navigating to login");
|
|
22
|
-
await page.goto("https://example.com/login");
|
|
23
|
-
expect(page.url()).toContain("/login");
|
|
24
|
-
});
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Fixture behavior
|
|
28
|
-
|
|
29
|
-
The fixture uses a shared log level and shared log entry list within a single test:
|
|
30
|
-
|
|
31
|
-
- On the first run, the default level is `warn`.
|
|
32
|
-
- On retries, the default level is `debug`.
|
|
33
|
-
|
|
34
|
-
At the end of each test, it attaches log entries to the HTML report in timestamp order.
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## `PlaywrightReportLogger` class
|
|
39
|
-
|
|
40
|
-
### Constructor
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
const log = new PlaywrightReportLogger(sharedLogLevel, sharedLogEntry, contextName);
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
In normal usage you do **not** construct this directly; use the `test` fixture and derive child loggers as needed.
|
|
47
|
-
|
|
48
|
-
### Log methods
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
log.debug("debug message");
|
|
52
|
-
log.info("info message");
|
|
53
|
-
log.warn("warning message");
|
|
54
|
-
log.error("error message");
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Log entries include:
|
|
58
|
-
|
|
59
|
-
- Timestamp
|
|
60
|
-
- Log level
|
|
61
|
-
- Prefix (context name)
|
|
62
|
-
- Message
|
|
63
|
-
|
|
64
|
-
### Child loggers
|
|
65
|
-
|
|
66
|
-
Create a child logger for additional context.
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
const pageLog = log.getNewChildLogger("LoginPage");
|
|
70
|
-
pageLog.info("starting login flow");
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Child loggers share:
|
|
74
|
-
|
|
75
|
-
- The same log level
|
|
76
|
-
- The same log entry list
|
|
77
|
-
|
|
78
|
-
Changing the log level on any child affects all loggers in the tree.
|
|
79
|
-
|
|
80
|
-
### Log level control
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
log.setLogLevel("debug");
|
|
84
|
-
log.getCurrentLogLevel();
|
|
85
|
-
log.resetLogLevel();
|
|
86
|
-
log.isCurrentLogLevel("warn");
|
|
87
|
-
log.isLogLevelEnabled("info");
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Attaching logs to the report
|
|
93
|
-
|
|
94
|
-
When used with the fixture, logs are attached automatically:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
export const test = base.extend({
|
|
98
|
-
log: async ({}, use, testInfo) => {
|
|
99
|
-
const log = new PlaywrightReportLogger(sharedLogLevel, sharedLogEntry, "TestCase");
|
|
100
|
-
await use(log);
|
|
101
|
-
log.attachLogsToTest(testInfo);
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
`attachLogsToTest`:
|
|
107
|
-
|
|
108
|
-
- Sorts entries by timestamp.
|
|
109
|
-
- Tries to parse JSON messages for structured display.
|
|
110
|
-
- Attaches each log entry as a separate report attachment.
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Using logging inside page objects
|
|
115
|
-
|
|
116
|
-
`PageObject` does **not** embed a logger in v2. If you want log support, you can pass a child logger into your POM or create a small wrapper.
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
class LoginPageWithLogging extends LoginPage {
|
|
120
|
-
constructor(page: Page, private log: PlaywrightReportLogger) {
|
|
121
|
-
super(page);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async loginAsUser(user: User) {
|
|
125
|
-
this.log.info(`logging in as ${user.username}`);
|
|
126
|
-
await super.loginAsUser(user);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Custom log fixture with Playwright test
|
|
134
|
-
|
|
135
|
-
If you want to use `@playwright/test` directly (instead of `pomwright`'s exported `test`), you can still create the log
|
|
136
|
-
fixture yourself:
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
import { test as base } from "@playwright/test";
|
|
140
|
-
import { PlaywrightReportLogger, type LogEntry, type LogLevel } from "pomwright";
|
|
141
|
-
|
|
142
|
-
type Fixtures = { log: PlaywrightReportLogger };
|
|
143
|
-
|
|
144
|
-
export const test = base.extend<Fixtures>({
|
|
145
|
-
log: async ({}, use, testInfo) => {
|
|
146
|
-
const sharedLogEntry: LogEntry[] = [];
|
|
147
|
-
const sharedLogLevel: { current: LogLevel; initial: LogLevel } =
|
|
148
|
-
testInfo.retry === 0
|
|
149
|
-
? { current: "warn", initial: "warn" }
|
|
150
|
-
: { current: "debug", initial: "debug" };
|
|
151
|
-
|
|
152
|
-
const log = new PlaywrightReportLogger(sharedLogLevel, sharedLogEntry, "TestCase");
|
|
153
|
-
await use(log);
|
|
154
|
-
log.attachLogsToTest(testInfo);
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## v1 to v2 differences
|
|
162
|
-
|
|
163
|
-
- v1 BasePage embedded a logger automatically; v2 does not.
|
|
164
|
-
- v2 provides logging as a Playwright fixture and a standalone logger class.
|
|
165
|
-
|
|
166
|
-
For migration guidance, see `docs/v1-to-v2-migration`.
|
|
167
|
-
|
|
168
|
-
---
|
package/docs/v2/overview.md
DELETED
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
# POMWright v2.0.0 Overview
|
|
2
|
-
|
|
3
|
-
## 1) What POMWright v2 is (conceptual overview)
|
|
4
|
-
|
|
5
|
-
POMWright v2 is a TypeScript-first, Playwright companion framework that formalizes the Page Object Model (POM) with a fluent, typed locator registry. It focuses on composability, explicit locator definitions, and ergonomic, strongly typed accessors to create reliable UI automation in large test suites. v2 unifies locator registration and retrieval into a registry API, promotes functional composition over inheritance-heavy patterns, and treats navigation, logging, and session storage as modular helpers that can be adopted independently. The result is a POM framework that is explicit about page structure, predictable in how it chains locators, and easy to scale across domains.
|
|
6
|
-
|
|
7
|
-
Key ideas:
|
|
8
|
-
|
|
9
|
-
- **Typed locator paths**: Locator schema paths are a literal union that is validated both at compile time and runtime.
|
|
10
|
-
- **Fluent registry DSL**: Locators are registered with a fluent builder (`add(...).getByRole(...).filter(...).nth(...)`).
|
|
11
|
-
- **Composable POMs**: `PageObject` is a minimal base class that provides navigation, session storage, and typed locator accessors, but leaves everything else to your own composition.
|
|
12
|
-
- **Explicit navigation flows**: URL typing (string vs RegExp) determines available navigation methods and enforces correct usage.
|
|
13
|
-
- **Opt-in logging**: Logging is provided as a fixture and can be threaded into POMs only where needed.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 2) Public API feature set (mid-to-high level with complete examples)
|
|
18
|
-
|
|
19
|
-
This section focuses strictly on the public-facing surface of POMWright v2. Each feature includes a complete code example.
|
|
20
|
-
|
|
21
|
-
### 2.1 `PageObject` (v2 base class)
|
|
22
|
-
|
|
23
|
-
`PageObject` wires a Playwright `Page` to the v2 locator registry, navigation helper, and session storage helper. You implement `defineLocators()` and `pageActionsToPerformAfterNavigation()`.
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
import { expect, type Page } from "@playwright/test";
|
|
27
|
-
import { PageObject, step } from "pomwright";
|
|
28
|
-
import type { User } from "testData";
|
|
29
|
-
|
|
30
|
-
type Paths =
|
|
31
|
-
| "common.spinner"
|
|
32
|
-
| "main"
|
|
33
|
-
| "main.button@login"
|
|
34
|
-
| "main.form@login"
|
|
35
|
-
| "main.form@login.input@username"
|
|
36
|
-
| "main.form@login.input@password";
|
|
37
|
-
|
|
38
|
-
export class LoginPage extends PageObject<Paths> {
|
|
39
|
-
constructor(page: Page) {
|
|
40
|
-
super(page, "https://example.com", "/login", { label: "LoginPage" });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
protected defineLocators(): void {
|
|
44
|
-
this.add("common.spinner").getByTestId("loading-spinner");
|
|
45
|
-
this.add("main").locator("main");
|
|
46
|
-
this.add("main.form@login").getByRole("form", { name: "Login" });
|
|
47
|
-
this.add("main.form@login.input@username").getByLabel("Username");
|
|
48
|
-
this.add("main.form@login.input@password").getByLabel("Password");
|
|
49
|
-
this.add("main.button@login").getByRole("button", { name: "Login" });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
protected pageActionsToPerformAfterNavigation() {
|
|
53
|
-
return [
|
|
54
|
-
async () => {
|
|
55
|
-
await expect(this.getLocator("common.spinner")).toHaveCount(0);
|
|
56
|
-
await this.getNestedLocator("main.form@login").waitFor({ state: "visible" });
|
|
57
|
-
},
|
|
58
|
-
];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@step()
|
|
62
|
-
async loginAsUser(user: User) {
|
|
63
|
-
await this.getNestedLocator("main.form@login.input@username").fill(user.username);
|
|
64
|
-
await this.getNestedLocator("main.form@login.input@password").fill(user.password);
|
|
65
|
-
await this.getNestedLocator("main.button@login").click();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Custom Playwright fixture using the `LoginPage` POC:
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
import { type Page, test as base } from "@playwright/test";
|
|
74
|
-
import { LoginPage } from "./login.page";
|
|
75
|
-
|
|
76
|
-
type Fixtures = { loginPage: LoginPage };
|
|
77
|
-
|
|
78
|
-
export const test = base.extend<Fixtures>({
|
|
79
|
-
loginPage: async ({ page }, use) => {
|
|
80
|
-
await use(new LoginPage(page as Page));
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Test example using the fixture:
|
|
86
|
-
|
|
87
|
-
```ts
|
|
88
|
-
import { test } from "./fixtures";
|
|
89
|
-
|
|
90
|
-
test("login flow", async ({ loginPage, testData }) => {
|
|
91
|
-
await loginPage.navigation.gotoThisPage();
|
|
92
|
-
await loginPage.loginAsUser(testData.user.alice);
|
|
93
|
-
await loginPage.navigation.expectAnotherPage();
|
|
94
|
-
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### 2.2 Typed locator registry: `createRegistryWithAccessors`, `LocatorRegistry` and accessor types
|
|
98
|
-
|
|
99
|
-
Use this when you want the registry without `PageObject` (e.g., for custom POCs or functional helpers).
|
|
100
|
-
|
|
101
|
-
**Example — custom POC with `createRegistryWithAccessors` (similar wiring to `PageObject`):**
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
import { expect, type Page } from "@playwright/test";
|
|
105
|
-
import {
|
|
106
|
-
type AddAccessor,
|
|
107
|
-
createRegistryWithAccessors,
|
|
108
|
-
type GetLocatorAccessor,
|
|
109
|
-
type GetLocatorSchemaAccessor,
|
|
110
|
-
type GetNestedLocatorAccessor,
|
|
111
|
-
type LocatorRegistry
|
|
112
|
-
step
|
|
113
|
-
} from "pomwright";
|
|
114
|
-
import type { User } from "testData";
|
|
115
|
-
|
|
116
|
-
type Paths =
|
|
117
|
-
| "common.spinner"
|
|
118
|
-
| "main"
|
|
119
|
-
| "main.button@login"
|
|
120
|
-
| "main.form@login"
|
|
121
|
-
| "main.form@login.input@username"
|
|
122
|
-
| "main.form@login.input@password";
|
|
123
|
-
|
|
124
|
-
export class LoginPage<Paths> {
|
|
125
|
-
public readonly urlPath: string = "/login"
|
|
126
|
-
public readonly page: Page;
|
|
127
|
-
protected readonly locatorRegistry: LocatorRegistry<Paths>;
|
|
128
|
-
public readonly add: AddAccessor<Paths>;
|
|
129
|
-
public readonly getLocator: GetLocatorAccessor<Paths>;
|
|
130
|
-
public readonly getLocatorSchema: GetLocatorSchemaAccessor<Paths>;
|
|
131
|
-
public readonly getNestedLocator: GetNestedLocatorAccessor<Paths>;
|
|
132
|
-
|
|
133
|
-
constructor(page: Page) {
|
|
134
|
-
this.page = page;
|
|
135
|
-
const { registry, add, getLocator, getNestedLocator, getLocatorSchema } =
|
|
136
|
-
createRegistryWithAccessors<Paths>(page);
|
|
137
|
-
this.locatorRegistry = registry;
|
|
138
|
-
this.add = add;
|
|
139
|
-
this.getLocator = getLocator;
|
|
140
|
-
this.getNestedLocator = getNestedLocator;
|
|
141
|
-
this.getLocatorSchema = getLocatorSchema;
|
|
142
|
-
this.defineLocators();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private defineLocators() {
|
|
146
|
-
this.add("common.spinner").getByTestId("loading-spinner");
|
|
147
|
-
this.add("main").locator("main");
|
|
148
|
-
this.add("main.form@login").getByRole("form", { name: "Login" });
|
|
149
|
-
this.add("main.form@login.input@username").getByLabel("Username");
|
|
150
|
-
this.add("main.form@login.input@password").getByLabel("Password");
|
|
151
|
-
this.add("main.button@login").getByRole("button", { name: "Login" });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
@step()
|
|
155
|
-
async loginAsUser(user: User) {
|
|
156
|
-
await this.getNestedLocator("main.form@login.input@username").fill(user.username);
|
|
157
|
-
await this.getNestedLocator("main.form@login.input@password").fill(user.password);
|
|
158
|
-
await this.getNestedLocator("main.button@login").click();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
@step()
|
|
162
|
-
async gotoThisPage() {
|
|
163
|
-
await this.page.goto(this.urlPath);
|
|
164
|
-
await expect(this.getLocator("common.spinner")).toHaveCount(0);
|
|
165
|
-
await this.getNestedLocator("main.form@login").waitFor({ state: "visible" });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### 2.3 Locator registration: `add(path)` with fluent steps
|
|
171
|
-
|
|
172
|
-
Each `add` call registers one locator strategy and any number of ordered steps (`filter` and `nth`).
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
add("main").locator("main");
|
|
176
|
-
add("main.button@login").getByRole("button", { name: "Login" });
|
|
177
|
-
add("main.form@login").getByRole("form", { name: "Login" });
|
|
178
|
-
add("main.form@login.input@username").getByLabel("Username");
|
|
179
|
-
add("main.form@login.input@password").getByLabel("Password");
|
|
180
|
-
add("main.list").locator("ul.list").filter({ hasText: "List" }).nth(0);
|
|
181
|
-
add("main.list.item").getByRole("listitem", { name: /Row/ }).filter({ hasText: "Row" }).nth("last");
|
|
182
|
-
add("main.frame@login").frameLocator("iframe#login");
|
|
183
|
-
add("main.link@forgot").getByText("Forgot password?");
|
|
184
|
-
add("main.banner@error").getById("error-banner");
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### 2.4 Locator retrieval: `getLocator`, `getNestedLocator`
|
|
188
|
-
|
|
189
|
-
- `getLocator(path)` resolves only the terminal locator (ignores ancestor steps).
|
|
190
|
-
- `getNestedLocator(path)` chains each segment along the dot-delimited path.
|
|
191
|
-
|
|
192
|
-
```ts
|
|
193
|
-
import { test } from "./fixtures";
|
|
194
|
-
|
|
195
|
-
test("locator helpers", async ({ loginPage }) => {
|
|
196
|
-
const terminal = loginPage.getLocator("main.form@login.input@username");
|
|
197
|
-
// terminal => getByLabel('Username')
|
|
198
|
-
|
|
199
|
-
const chained = loginPage.getNestedLocator("main.form@login.input@username");
|
|
200
|
-
// chained => locator('main').getByRole('form', { name: 'Login' }).getByLabel('Username')
|
|
201
|
-
});
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### 2.5 Fluent schema mutation: `getLocatorSchema` builder
|
|
205
|
-
|
|
206
|
-
Use `getLocatorSchema(path)` when you want to patch definitions, append filters, or change indices in a test. `getLocatorSchema(path)` retrieves a clone which all mutations are applied to, thus you can always fetch a new clone of the original from the registry when needed.
|
|
207
|
-
|
|
208
|
-
```ts
|
|
209
|
-
import { test } from "./fixtures";
|
|
210
|
-
|
|
211
|
-
test("schema builder", async ({ loginPage }) => {
|
|
212
|
-
const updated = loginPage
|
|
213
|
-
.getLocatorSchema("main.form@login.input@username")
|
|
214
|
-
.update("main.form@login.input@username")
|
|
215
|
-
.getByLabel("Username", { exact: true })
|
|
216
|
-
.filter("main.form@login.input@username", { hasText: /User/i })
|
|
217
|
-
.nth("main.form@login.input@username", 0)
|
|
218
|
-
.getNestedLocator();
|
|
219
|
-
// updated =>
|
|
220
|
-
// locator('main').getByRole('form', { name: 'Login' }).getByLabel('Username', { exact: true }).filter({ hasText: /User/i }).first()
|
|
221
|
-
});
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
> **Tip:** `filter` accepts Playwright `Locator` instances. That means you can pass locators returned from
|
|
225
|
-
> `getLocator(path)`, `getNestedLocator(path)`, or `getLocatorSchema(path)...getLocator/getNestedLocator`, in addition to
|
|
226
|
-
> `page.locator(...)` and `page.getBy...(...)`.
|
|
227
|
-
|
|
228
|
-
### 2.6 `update` vs `replace` vs `remove` on the schema builder
|
|
229
|
-
|
|
230
|
-
- `update(subPath?)` patches an existing definition (PATCH semantics).
|
|
231
|
-
- `replace(subPath?)` overwrites a definition (POST semantics).
|
|
232
|
-
- `remove(subPath?)` soft-deletes a segment for the builder resolution only.
|
|
233
|
-
All of the above mutate the retrieved clone only, leaving the registry original as defined by its add call.
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
import { test } from "./fixtures";
|
|
237
|
-
|
|
238
|
-
test("update/replace/remove", async ({ loginPage }) => {
|
|
239
|
-
const patched = loginPage
|
|
240
|
-
.getLocatorSchema("main.button@login")
|
|
241
|
-
.update()
|
|
242
|
-
.getByRole({ name: "Sign in" })
|
|
243
|
-
.getNestedLocator();
|
|
244
|
-
// patched => locator('main').getByRole('button', { name: 'Sign in' })
|
|
245
|
-
|
|
246
|
-
const replaced = loginPage
|
|
247
|
-
.getLocatorSchema("main.button@login")
|
|
248
|
-
.replace()
|
|
249
|
-
.locator("button.primary", { hasText: "Sign in" })
|
|
250
|
-
.getNestedLocator();
|
|
251
|
-
// replaced => locator('main').locator('button.primary').filter({ hasText: 'Sign in' })
|
|
252
|
-
|
|
253
|
-
const removedAncestor = loginPage
|
|
254
|
-
.getLocatorSchema("main.form@login.input@username")
|
|
255
|
-
.remove("main")
|
|
256
|
-
.getNestedLocator();
|
|
257
|
-
// removedAncestor => getByRole('form', { name: 'Login' }).getByLabel('Username')
|
|
258
|
-
});
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### 2.7 Reusable locators: `registry.createReusable`
|
|
262
|
-
|
|
263
|
-
Reusable locators let you seed a locator definition and steps without immediately registering it, then reuse it across paths with optional PATCH-style overrides. You can also reuse an existing path directly by referencing that path in `{ reuse }`.
|
|
264
|
-
|
|
265
|
-
```ts
|
|
266
|
-
const h1 = registry.createReusable.getByRole("heading", { level: 1 });
|
|
267
|
-
|
|
268
|
-
registry.add("heading", { reuse: h1 });
|
|
269
|
-
|
|
270
|
-
registry.add("heading@summary", { reuse: h1 }).getByRole({ name: "Summary" });
|
|
271
|
-
|
|
272
|
-
// Alternative reuse: clone from an existing registered path, as is.
|
|
273
|
-
registry.add("main.form@login.error@invalidPassword", { reuse: "errors.invalidPassword" });
|
|
274
|
-
|
|
275
|
-
registry.add("main.region@profile.form@changePassword.error@invalidPassword",
|
|
276
|
-
{ reuse: "errors.invalidPassword" });
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
Thus use createReusable if you need a reusable definition as base for creating multiple variations, and reuse by path to reuse an exact locator definition across different DOM-scopes/nesting levels.
|
|
280
|
-
|
|
281
|
-
### 2.8 Navigation helper: `navigation` (on `PageObject`)
|
|
282
|
-
|
|
283
|
-
The navigation helper is **only available through `PageObject`**. Its methods depend on the type of `fullUrl` (string vs RegExp). You can return `[]` or `null` from `pageActionsToPerformAfterNavigation()` to skip actions, or provide any number of actions (including Playwright calls or `this.getNestedLocator(...).waitFor(...)` calls).
|
|
284
|
-
|
|
285
|
-
Below is the same `LoginPage` example from 2.1:
|
|
286
|
-
|
|
287
|
-
```ts
|
|
288
|
-
protected pageActionsToPerformAfterNavigation() {
|
|
289
|
-
return [
|
|
290
|
-
async () => {
|
|
291
|
-
await expect(this.getLocator("common.spinner")).toHaveCount(0);
|
|
292
|
-
await this.getNestedLocator("main.form@login").waitFor({ state: "visible" });
|
|
293
|
-
},
|
|
294
|
-
];
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Usage:
|
|
298
|
-
// await loginPage.navigation.gotoThisPage();
|
|
299
|
-
// await loginPage.navigation.goto("/profil"); prefixes with this.baseUrl
|
|
300
|
-
// await loginPage.navigation.goto("https://anotherDomain.com/");
|
|
301
|
-
// await loginPage.navigation.expectThisPage();
|
|
302
|
-
// await loginPage.navigation.expectAnotherPage();
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
Only `.gotoThisPage()` and `.expectThisPage()` calls `pageActionsToPerformAfterNavigation`.
|
|
306
|
-
|
|
307
|
-
### 2.9 `SessionStorage` helper
|
|
308
|
-
|
|
309
|
-
`SessionStorage` is available via `PageObject.sessionStorage` or direct instantiation.
|
|
310
|
-
|
|
311
|
-
**Example A — using `LoginPage.sessionStorage` in a test:**
|
|
312
|
-
|
|
313
|
-
```ts
|
|
314
|
-
import { test, expect } from "./fixtures";
|
|
315
|
-
|
|
316
|
-
test("session storage via PageObject", async ({ loginPage }) => {
|
|
317
|
-
await loginPage.navigation.gotoThisPage();
|
|
318
|
-
await loginPage.sessionStorage.set({ token: "abc" }, { reload: true });
|
|
319
|
-
await loginPage.sessionStorage.setOnNextNavigation({ theme: "dark" });
|
|
320
|
-
|
|
321
|
-
const data = await loginPage.sessionStorage.get(["token", "theme"], { waitForContext: true });
|
|
322
|
-
await loginPage.sessionStorage.clear(["token"], { waitForContext: true });
|
|
323
|
-
|
|
324
|
-
expect(data.token).toBe("abc");
|
|
325
|
-
expect(data.theme).not.toBe("dark")
|
|
326
|
-
await loginPage.page.reload();
|
|
327
|
-
expect(data.theme).toBe("dark")
|
|
328
|
-
});
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
**Example B — custom SessionStorage fixture:**
|
|
332
|
-
|
|
333
|
-
```ts
|
|
334
|
-
import { test as base } from "pomwright";
|
|
335
|
-
import type { SessionStorage } from "pomwright";
|
|
336
|
-
import { SessionStorage as SessionStorageHelper } from "pomwright";
|
|
337
|
-
|
|
338
|
-
type Fixtures = { sessionStorage: SessionStorage };
|
|
339
|
-
|
|
340
|
-
export const test = base.extend<Fixtures>({
|
|
341
|
-
sessionStorage: async ({ page }, use) => {
|
|
342
|
-
await use(new SessionStorageHelper(page, { label: "SessionStorage" }));
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
```ts
|
|
348
|
-
import { test } from "./fixtures";
|
|
349
|
-
import { expect } from "@playwright/test";
|
|
350
|
-
|
|
351
|
-
test("session storage via custom fixture", async ({ page, sessionStorage }) => {
|
|
352
|
-
await page.goto("https://example.com/login");
|
|
353
|
-
await sessionStorage.set({ token: "abc" });
|
|
354
|
-
|
|
355
|
-
const data = await sessionStorage.get(["token"]);
|
|
356
|
-
expect(data.token).toBe("abc");
|
|
357
|
-
});
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### 2.10 `step` decorator
|
|
361
|
-
|
|
362
|
-
The `step` decorator wraps a method in `test.step` (only works within a Class), defaulting the title to `ClassName.methodName`. Here it is applied to the `LoginPage` helper method from 2.1.
|
|
363
|
-
|
|
364
|
-
```ts
|
|
365
|
-
import { step } from "pomwright";
|
|
366
|
-
import type { User } from "somewhere";
|
|
367
|
-
|
|
368
|
-
class LoginPage {
|
|
369
|
-
@step()
|
|
370
|
-
async loginAsUser(user: User) {
|
|
371
|
-
// ...
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
@step supports all the same arguments as playwright test.step
|
|
377
|
-
|
|
378
|
-
### 2.11 Logging: `PlaywrightReportLogger` and `test` fixture
|
|
379
|
-
|
|
380
|
-
POMWright v2 exports a `test` fixture that injects a `log` instance. You can also create loggers manually or add a child of the log fixture to each of your POCs if you want.
|
|
381
|
-
|
|
382
|
-
```ts
|
|
383
|
-
import { test, expect } from "pomwright";
|
|
384
|
-
|
|
385
|
-
test("logs are attached to the HTML report", async ({ page, log }) => {
|
|
386
|
-
log.info("navigating to login");
|
|
387
|
-
await page.goto("https://example.com/login");
|
|
388
|
-
expect(page.url()).toContain("/login");
|
|
389
|
-
});
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
|
-
## 3) Public vs internal API (technical breakdown)
|
|
395
|
-
|
|
396
|
-
This section separates public API from internal implementation details and explains how internal pieces support the public surface.
|
|
397
|
-
|
|
398
|
-
### 3.1 Public API (what users import and rely on)
|
|
399
|
-
|
|
400
|
-
#### `PageObject`
|
|
401
|
-
|
|
402
|
-
- **Role**: Base class that binds locators, navigation, and session storage to a Playwright `Page`.
|
|
403
|
-
- **Key members**: `add`, `getLocator`, `getNestedLocator`, `getLocatorSchema`, `navigation`, `sessionStorage`.
|
|
404
|
-
- **Contracts**: `defineLocators()` and `pageActionsToPerformAfterNavigation()` are required abstract hooks.
|
|
405
|
-
|
|
406
|
-
#### `createRegistryWithAccessors` + `LocatorRegistry`
|
|
407
|
-
|
|
408
|
-
- **Role**: Standalone registry factory returning the registry plus bound helpers.
|
|
409
|
-
- **Key outputs**: `registry`, `add`, `getLocator`, `getNestedLocator`, `getLocatorSchema`.
|
|
410
|
-
- **Contract**: Typed path unions are validated both at compile time and runtime.
|
|
411
|
-
|
|
412
|
-
#### Locator builder methods (public usage)
|
|
413
|
-
|
|
414
|
-
- **Registration**: `add(path).getByRole(...)`, `getByText`, `locator`, `frameLocator`, `getByTestId`, `getById`, etc.
|
|
415
|
-
- **Steps**: `filter(...)`, `nth(...)`, `describe(...)`.
|
|
416
|
-
- **Retrieval**: `getLocator(path)`, `getNestedLocator(path)`, `getLocatorSchema(path)`.
|
|
417
|
-
- **Schema builder**: `update`, `replace`, `remove`, `clearSteps`.
|
|
418
|
-
|
|
419
|
-
#### Helpers
|
|
420
|
-
|
|
421
|
-
- **Navigation**: `navigation.goto`, `gotoThisPage`, `expectThisPage`, `expectAnotherPage` (availability depends on string vs RegExp URL typing).
|
|
422
|
-
- **SessionStorage**: `set`, `setOnNextNavigation`, `get`, `clear`.
|
|
423
|
-
- **Step decorator**: `step`.
|
|
424
|
-
- **Logging**: `PlaywrightReportLogger` and `test` fixture.
|
|
425
|
-
|
|
426
|
-
#### Type exports
|
|
427
|
-
|
|
428
|
-
- `UrlTypeOptions`, `BaseUrlTypeFromOptions`, `UrlPathTypeFromOptions`, `FullUrlTypeFromOptions`.
|
|
429
|
-
- `NavigationOptions`.
|
|
430
|
-
- `LocatorRegistry` type.
|
|
431
|
-
|
|
432
|
-
### 3.2 Internal API (implementation details and dependencies)
|
|
433
|
-
|
|
434
|
-
This is not intended for direct usage but explains the building blocks used by the public API.
|
|
435
|
-
|
|
436
|
-
#### `LocatorRegistryInternal`
|
|
437
|
-
|
|
438
|
-
- **Purpose**: Stores locator schemas and implements resolution logic.
|
|
439
|
-
- **Used by**: `createRegistryWithAccessors`, `PageObject` (via factory), and all public locator accessors.
|
|
440
|
-
- **Key responsibilities**:
|
|
441
|
-
- Registration and replacement of schema records.
|
|
442
|
-
- Path validation and error handling.
|
|
443
|
-
- Locator chain resolution (`buildLocatorChain`).
|
|
444
|
-
|
|
445
|
-
#### `LocatorRegistrationBuilder`
|
|
446
|
-
|
|
447
|
-
- **Purpose**: Implements `add(path)` fluent chaining and enforces the “one locator strategy per registration” rule.
|
|
448
|
-
- **Used by**: `LocatorRegistryInternal.add` (public `add` method returns its fluent surface).
|
|
449
|
-
|
|
450
|
-
#### `LocatorQueryBuilder`
|
|
451
|
-
|
|
452
|
-
- **Purpose**: Implements `getLocatorSchema(path)` mutable clones for filters, indices, update/replace/remove, and resolution.
|
|
453
|
-
- **Used by**: `LocatorRegistryInternal.getLocatorSchema` (public `getLocatorSchema`).
|
|
454
|
-
|
|
455
|
-
#### `LocatorUpdateBuilder`
|
|
456
|
-
|
|
457
|
-
- **Purpose**: Patch/replace logic for locator strategies, with correct overload parsing.
|
|
458
|
-
- **Used by**: `LocatorQueryBuilder.update` and `LocatorQueryBuilder.replace` (public schema builder API).
|
|
459
|
-
|
|
460
|
-
#### `ReusableLocatorFactory` and `ReusableLocatorBuilder`
|
|
461
|
-
|
|
462
|
-
- **Purpose**: Build reusable locator seeds (definition + steps) without registry mutation.
|
|
463
|
-
- **Used by**: `LocatorRegistryInternal.createReusable` and public `{ reuse }` registration flows.
|
|
464
|
-
|
|
465
|
-
#### Locator types and validation helpers (`types.ts`, `utils.ts`)
|
|
466
|
-
|
|
467
|
-
- **Purpose**: Encode type-level path validation, locator strategy shapes, steps, and filters.
|
|
468
|
-
- **Used by**: All locator registry builders and accessors.
|
|
469
|
-
- **Dependencies**:
|
|
470
|
-
- `LocatorSchemaPathFormat` -> compile-time path validation.
|
|
471
|
-
- `validateLocatorSchemaPath` -> runtime path validation.
|
|
472
|
-
- `createLocator` / `applyIndexSelector` -> core chaining mechanics.
|
|
473
|
-
|
|
474
|
-
#### `createNavigation` + `Navigation` class
|
|
475
|
-
|
|
476
|
-
- **Purpose**: Provide navigation API and correct type narrowing based on URL type.
|
|
477
|
-
- **Used by**: `PageObject` constructor to build `navigation`.
|
|
478
|
-
|
|
479
|
-
#### `SessionStorage`
|
|
480
|
-
|
|
481
|
-
- **Purpose**: Manage session storage with robust context handling and Playwright `test.step` reporting.
|
|
482
|
-
- **Used by**: `PageObject` (as `sessionStorage`) and can be used directly.
|
|
483
|
-
|
|
484
|
-
#### `PlaywrightReportLogger` and `test` fixture
|
|
485
|
-
|
|
486
|
-
- **Purpose**: Create and attach log entries to Playwright HTML reports.
|
|
487
|
-
- **Used by**: Public `test` export and optional injection into custom POMs.
|
|
488
|
-
|
|
489
|
-
#### `addV1SchemaToV2Registry`
|
|
490
|
-
|
|
491
|
-
- **Purpose**: Migration helper translating v1 `LocatorSchema` objects into v2 registry definitions.
|
|
492
|
-
- **Used by**: v1-to-v2 bridge workflows and migration paths.
|
|
493
|
-
|
|
494
|
-
---
|
|
495
|
-
|
|
496
|
-
## 4) Glossary (quick reference)
|
|
497
|
-
|
|
498
|
-
- **Locator schema path**: Or just "Paths", is a Dot-delimited string union defining a page structure, e.g. `"main.form.submit"` and is the key to a unique Locator definition.
|
|
499
|
-
- **Terminal locator**: The locator produced by resolving only the last path segment of the full path.
|
|
500
|
-
- **Nested locator**: The locator produced by chaining all segments in a path.
|
|
501
|
-
- **Filter step**: A recorded `locator.filter(...)` operation applied in sequence.
|
|
502
|
-
- **Index step**: A recorded `first`, `last`, or `nth(n)` applied in sequence.
|
|
503
|
-
- **Reusable locator**: A seed definition (strategy + steps) that can be reused across paths via `{ reuse }`.
|
|
504
|
-
|
|
505
|
-
---
|
|
506
|
-
|
|
507
|
-
## 5) Suggested next documents
|
|
508
|
-
|
|
509
|
-
This overview pairs with more focused documents:
|
|
510
|
-
|
|
511
|
-
- `docs/v1-to-v2-migration` (migration guide, API differances and similarities)
|
|
512
|
-
- `docs/v2/PageObject.md` (Indepth documentation of the PageObject Class)
|
|
513
|
-
- `docs/v2/locator-registry.md` (Indepth documentation of locator registry and all its methods)
|
|
514
|
-
- `docs/v2/session-storage.md` (Indepth documentation of SessiosStorage and its methods)
|
|
515
|
-
- `docs/v2/logging.md` (Indepth documentation of PlaywrightReportLogger/log fixture)
|