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.
Files changed (117) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +5 -5
  3. package/dist/index.d.mts +91 -989
  4. package/dist/index.d.ts +91 -989
  5. package/dist/index.js +627 -1887
  6. package/dist/index.mjs +633 -1888
  7. package/package.json +9 -11
  8. package/AGENTS.md +0 -37
  9. package/docs/v1/BaseApi-explanation.md +0 -63
  10. package/docs/v1/BasePage-explanation.md +0 -96
  11. package/docs/v1/LocatorSchema-explanation.md +0 -271
  12. package/docs/v1/LocatorSchemaPath-explanation.md +0 -165
  13. package/docs/v1/PlaywrightReportLogger-explanation.md +0 -56
  14. package/docs/v1/get-locator-methods-explanation.md +0 -250
  15. package/docs/v1/intro-to-using-pomwright.md +0 -899
  16. package/docs/v1/sessionStorage-methods-explanation.md +0 -38
  17. package/docs/v1/tips-folder-structure.md +0 -38
  18. package/docs/v1-to-v2-migration/bridge-migration-guide.md +0 -159
  19. package/docs/v1-to-v2-migration/direct-migration-guide.md +0 -238
  20. package/docs/v1-to-v2-migration/v1-to-v2-comparison.md +0 -547
  21. package/docs/v2/PageObject.md +0 -293
  22. package/docs/v2/composing-locator-modules.md +0 -93
  23. package/docs/v2/locator-registry.md +0 -693
  24. package/docs/v2/logging.md +0 -168
  25. package/docs/v2/overview.md +0 -515
  26. package/docs/v2/session-storage.md +0 -160
  27. package/index.ts +0 -75
  28. package/intTestV2/.env +0 -0
  29. package/intTestV2/fixtures/testApp.fixtures.ts +0 -43
  30. package/intTestV2/package.json +0 -22
  31. package/intTestV2/page-object-models/testApp/pages/iframe/iframe.locatorSchema.ts +0 -24
  32. package/intTestV2/page-object-models/testApp/pages/iframe/iframe.page.ts +0 -17
  33. package/intTestV2/page-object-models/testApp/pages/testPage.locatorSchema.ts +0 -32
  34. package/intTestV2/page-object-models/testApp/pages/testPage.page.ts +0 -119
  35. package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.locatorSchema.ts +0 -29
  36. package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.page.ts +0 -48
  37. package/intTestV2/page-object-models/testApp/pages/testPath/testPath.locatorSchema.ts +0 -9
  38. package/intTestV2/page-object-models/testApp/pages/testPath/testPath.page.ts +0 -23
  39. package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.locatorSchema.ts +0 -114
  40. package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.page.ts +0 -23
  41. package/intTestV2/page-object-models/testApp/testApp.base.ts +0 -20
  42. package/intTestV2/playwright.config.ts +0 -54
  43. package/intTestV2/server.js +0 -216
  44. package/intTestV2/test-data/staticPage/index.html +0 -280
  45. package/intTestV2/test-data/staticPage/w3images/avatar2.png +0 -0
  46. package/intTestV2/test-data/staticPage/w3images/avatar3.png +0 -0
  47. package/intTestV2/test-data/staticPage/w3images/avatar5.png +0 -0
  48. package/intTestV2/test-data/staticPage/w3images/avatar6.png +0 -0
  49. package/intTestV2/test-data/staticPage/w3images/forest.jpg +0 -0
  50. package/intTestV2/test-data/staticPage/w3images/lights.jpg +0 -0
  51. package/intTestV2/test-data/staticPage/w3images/mountains.jpg +0 -0
  52. package/intTestV2/test-data/staticPage/w3images/nature.jpg +0 -0
  53. package/intTestV2/test-data/staticPage/w3images/snow.jpg +0 -0
  54. package/intTestV2/tests/locatorRegistry/add/add.describe.spec.ts +0 -54
  55. package/intTestV2/tests/locatorRegistry/add/add.filter.spec.ts +0 -143
  56. package/intTestV2/tests/locatorRegistry/add/add.frameLocator.spec.ts +0 -23
  57. package/intTestV2/tests/locatorRegistry/add/add.getByAltText.spec.ts +0 -23
  58. package/intTestV2/tests/locatorRegistry/add/add.getById.spec.ts +0 -45
  59. package/intTestV2/tests/locatorRegistry/add/add.getByLabel.spec.ts +0 -23
  60. package/intTestV2/tests/locatorRegistry/add/add.getByPlaceholder.spec.ts +0 -23
  61. package/intTestV2/tests/locatorRegistry/add/add.getByRole.spec.ts +0 -23
  62. package/intTestV2/tests/locatorRegistry/add/add.getByTestId.spec.ts +0 -23
  63. package/intTestV2/tests/locatorRegistry/add/add.getByText.spec.ts +0 -23
  64. package/intTestV2/tests/locatorRegistry/add/add.getByTitle.spec.ts +0 -23
  65. package/intTestV2/tests/locatorRegistry/add/add.locator.spec.ts +0 -23
  66. package/intTestV2/tests/locatorRegistry/add/add.reuseExisting.spec.ts +0 -66
  67. package/intTestV2/tests/locatorRegistry/add/add.reuseReusable.spec.ts +0 -311
  68. package/intTestV2/tests/locatorRegistry/add/add.spec.ts +0 -159
  69. package/intTestV2/tests/locatorRegistry/filter.cycle.spec.ts +0 -39
  70. package/intTestV2/tests/locatorRegistry/getLocator/getLocator.spec.ts +0 -253
  71. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.clearSteps.spec.ts +0 -105
  72. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.describe.spec.ts +0 -23
  73. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.filter.spec.ts +0 -368
  74. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getLocator.spec.ts +0 -56
  75. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getNestedLocator.spec.ts +0 -175
  76. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.nth.spec.ts +0 -60
  77. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.remove.spec.ts +0 -32
  78. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.replace.spec.ts +0 -24
  79. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.spec.ts +0 -110
  80. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.update.spec.ts +0 -322
  81. package/intTestV2/tests/locatorRegistry/getNestedLocator/getNestedLocator.spec.ts +0 -412
  82. package/intTestV2/tests/locatorRegistry/registry/registry.binding.spec.ts +0 -50
  83. package/intTestV2/tests/locatorRegistry/validation/validation.locatorSchemaPath.spec.ts +0 -115
  84. package/intTestV2/tests/locatorRegistry/validation/validation.sub-path.spec.ts +0 -45
  85. package/intTestV2/tests/step/step.spec.ts +0 -49
  86. package/intTestV2/tests/testApp/color.spec.ts +0 -15
  87. package/intTestV2/tests/testApp/iframe.spec.ts +0 -57
  88. package/intTestV2/tests/testApp/testFilters.spec.ts +0 -24
  89. package/intTestV2/tests/testApp/testPage.spec.ts +0 -161
  90. package/intTestV2/tests/testApp/testPath.spec.ts +0 -18
  91. package/pack-build.sh +0 -11
  92. package/pack-test-v2.sh +0 -36
  93. package/playwright.base.ts +0 -42
  94. package/skills/README.md +0 -56
  95. package/skills/pomwright-v1-5-bridge-migration/SKILL.md +0 -40
  96. package/skills/pomwright-v1-5-bridge-migration/references/call-site-migration.md +0 -178
  97. package/skills/pomwright-v1-5-bridge-migration/references/schema-translation.md +0 -183
  98. package/skills/pomwright-v2-migration/SKILL.md +0 -63
  99. package/skills/pomwright-v2-migration/references/call-site-migration.md +0 -265
  100. package/skills/pomwright-v2-migration/references/class-migration.md +0 -266
  101. package/skills/pomwright-v2-migration/references/fixture-and-helpers.md +0 -423
  102. package/skills/pomwright-v2-migration/references/locator-registration.md +0 -344
  103. package/srcV2/fixture/base.fixtures.ts +0 -23
  104. package/srcV2/helpers/navigation.ts +0 -153
  105. package/srcV2/helpers/playwrightReportLogger.ts +0 -196
  106. package/srcV2/helpers/sessionStorage.ts +0 -251
  107. package/srcV2/helpers/stepDecorator.ts +0 -106
  108. package/srcV2/locators/index.ts +0 -15
  109. package/srcV2/locators/locatorQueryBuilder.ts +0 -427
  110. package/srcV2/locators/locatorRegistrationBuilder.ts +0 -558
  111. package/srcV2/locators/locatorRegistry.ts +0 -541
  112. package/srcV2/locators/locatorUpdateBuilder.ts +0 -602
  113. package/srcV2/locators/reusableLocatorBuilder.ts +0 -200
  114. package/srcV2/locators/types.ts +0 -256
  115. package/srcV2/locators/utils.ts +0 -309
  116. package/srcV2/locators/v1SchemaTranslator.ts +0 -178
  117. package/srcV2/pageObject.ts +0 -105
@@ -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
- ---
@@ -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)