pomwright 1.2.0 → 1.3.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 +29 -0
- package/README.md +38 -494
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +13 -8
- package/dist/index.mjs +10 -5
- package/docs/BaseApi-explanation.md +63 -0
- package/docs/BasePage-explanation.md +96 -0
- package/docs/LocatorSchema-explanation.md +271 -0
- package/docs/LocatorSchemaPath-explanation.md +165 -0
- package/docs/PlaywrightReportLogger-explanation.md +56 -0
- package/docs/get-locator-methods-explanation.md +266 -0
- package/docs/intro-to-using-pomwright.md +899 -0
- package/docs/sessionStorage-methods-explanation.md +38 -0
- package/docs/tips-folder-structure.md +38 -0
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -18,8 +18,8 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
23
|
BaseApi: () => BaseApi,
|
|
24
24
|
BasePage: () => BasePage,
|
|
25
25
|
GetByMethod: () => GetByMethod,
|
|
@@ -27,7 +27,7 @@ __export(POMWright_exports, {
|
|
|
27
27
|
PlaywrightReportLogger: () => PlaywrightReportLogger,
|
|
28
28
|
test: () => test3
|
|
29
29
|
});
|
|
30
|
-
module.exports = __toCommonJS(
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
31
|
|
|
32
32
|
// src/basePage.ts
|
|
33
33
|
var import_test3 = require("@playwright/test");
|
|
@@ -427,6 +427,11 @@ var GetLocatorBase = class {
|
|
|
427
427
|
* Throws an error if a schema already exists at that path.
|
|
428
428
|
*/
|
|
429
429
|
addSchema(locatorSchemaPath, schemaDetails) {
|
|
430
|
+
if (locatorSchemaPath.length === 0 || locatorSchemaPath.startsWith(".") || locatorSchemaPath.endsWith(".") || locatorSchemaPath.includes("..")) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`[${this.pageObjectClass.pocName}] Invalid LocatorSchemaPath '${locatorSchemaPath}'. LocatorSchemaPath must not be empty, start or end with a '.', or contain consecutive '.'.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
430
435
|
const newLocatorSchema = this.createLocatorSchema(schemaDetails, locatorSchemaPath);
|
|
431
436
|
const existingSchemaFunc = this.safeGetLocatorSchema(locatorSchemaPath);
|
|
432
437
|
if (existingSchemaFunc) {
|
|
@@ -807,7 +812,7 @@ var SessionStorage = class {
|
|
|
807
812
|
const item = sessionStorage.getItem(key);
|
|
808
813
|
try {
|
|
809
814
|
storage[key] = item ? JSON.parse(item) : null;
|
|
810
|
-
} catch (
|
|
815
|
+
} catch (_e) {
|
|
811
816
|
storage[key] = item;
|
|
812
817
|
}
|
|
813
818
|
}
|
|
@@ -857,7 +862,7 @@ var SessionStorage = class {
|
|
|
857
862
|
contextExists = await this.page.evaluate(() => {
|
|
858
863
|
return typeof window !== "undefined" && window.sessionStorage !== void 0;
|
|
859
864
|
});
|
|
860
|
-
} catch (
|
|
865
|
+
} catch (_e) {
|
|
861
866
|
contextExists = false;
|
|
862
867
|
}
|
|
863
868
|
if (contextExists) {
|
|
@@ -888,7 +893,7 @@ var SessionStorage = class {
|
|
|
888
893
|
const allData = await this.readFromSessionStorage();
|
|
889
894
|
if (keys && keys.length > 0) {
|
|
890
895
|
for (const key of keys) {
|
|
891
|
-
if (Object.
|
|
896
|
+
if (Object.hasOwn(allData, key)) {
|
|
892
897
|
result[key] = allData[key];
|
|
893
898
|
}
|
|
894
899
|
}
|
|
@@ -1000,7 +1005,7 @@ var BasePage = class {
|
|
|
1000
1005
|
* Ensures a flexible approach to URL matching (string or regex-based).
|
|
1001
1006
|
*/
|
|
1002
1007
|
constructFullUrl(baseUrl, urlPath) {
|
|
1003
|
-
const escapeStringForRegExp = (str) => str.replace(/[
|
|
1008
|
+
const escapeStringForRegExp = (str) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
1004
1009
|
if (typeof baseUrl === "string" && typeof urlPath === "string") {
|
|
1005
1010
|
return `${baseUrl}${urlPath}`;
|
|
1006
1011
|
}
|
|
@@ -1234,7 +1239,7 @@ ${args.join("\n\n")}`
|
|
|
1234
1239
|
const parsedMessage = JSON.parse(log.message);
|
|
1235
1240
|
messageContentType = "application/json";
|
|
1236
1241
|
messageBody = JSON.stringify(parsedMessage, null, 2);
|
|
1237
|
-
} catch (
|
|
1242
|
+
} catch (_error) {
|
|
1238
1243
|
messageContentType = "text/plain";
|
|
1239
1244
|
messageBody = log.message;
|
|
1240
1245
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -396,6 +396,11 @@ var GetLocatorBase = class {
|
|
|
396
396
|
* Throws an error if a schema already exists at that path.
|
|
397
397
|
*/
|
|
398
398
|
addSchema(locatorSchemaPath, schemaDetails) {
|
|
399
|
+
if (locatorSchemaPath.length === 0 || locatorSchemaPath.startsWith(".") || locatorSchemaPath.endsWith(".") || locatorSchemaPath.includes("..")) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`[${this.pageObjectClass.pocName}] Invalid LocatorSchemaPath '${locatorSchemaPath}'. LocatorSchemaPath must not be empty, start or end with a '.', or contain consecutive '.'.`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
399
404
|
const newLocatorSchema = this.createLocatorSchema(schemaDetails, locatorSchemaPath);
|
|
400
405
|
const existingSchemaFunc = this.safeGetLocatorSchema(locatorSchemaPath);
|
|
401
406
|
if (existingSchemaFunc) {
|
|
@@ -776,7 +781,7 @@ var SessionStorage = class {
|
|
|
776
781
|
const item = sessionStorage.getItem(key);
|
|
777
782
|
try {
|
|
778
783
|
storage[key] = item ? JSON.parse(item) : null;
|
|
779
|
-
} catch (
|
|
784
|
+
} catch (_e) {
|
|
780
785
|
storage[key] = item;
|
|
781
786
|
}
|
|
782
787
|
}
|
|
@@ -826,7 +831,7 @@ var SessionStorage = class {
|
|
|
826
831
|
contextExists = await this.page.evaluate(() => {
|
|
827
832
|
return typeof window !== "undefined" && window.sessionStorage !== void 0;
|
|
828
833
|
});
|
|
829
|
-
} catch (
|
|
834
|
+
} catch (_e) {
|
|
830
835
|
contextExists = false;
|
|
831
836
|
}
|
|
832
837
|
if (contextExists) {
|
|
@@ -857,7 +862,7 @@ var SessionStorage = class {
|
|
|
857
862
|
const allData = await this.readFromSessionStorage();
|
|
858
863
|
if (keys && keys.length > 0) {
|
|
859
864
|
for (const key of keys) {
|
|
860
|
-
if (Object.
|
|
865
|
+
if (Object.hasOwn(allData, key)) {
|
|
861
866
|
result[key] = allData[key];
|
|
862
867
|
}
|
|
863
868
|
}
|
|
@@ -969,7 +974,7 @@ var BasePage = class {
|
|
|
969
974
|
* Ensures a flexible approach to URL matching (string or regex-based).
|
|
970
975
|
*/
|
|
971
976
|
constructFullUrl(baseUrl, urlPath) {
|
|
972
|
-
const escapeStringForRegExp = (str) => str.replace(/[
|
|
977
|
+
const escapeStringForRegExp = (str) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
973
978
|
if (typeof baseUrl === "string" && typeof urlPath === "string") {
|
|
974
979
|
return `${baseUrl}${urlPath}`;
|
|
975
980
|
}
|
|
@@ -1203,7 +1208,7 @@ ${args.join("\n\n")}`
|
|
|
1203
1208
|
const parsedMessage = JSON.parse(log.message);
|
|
1204
1209
|
messageContentType = "application/json";
|
|
1205
1210
|
messageBody = JSON.stringify(parsedMessage, null, 2);
|
|
1206
|
-
} catch (
|
|
1211
|
+
} catch (_error) {
|
|
1207
1212
|
messageContentType = "text/plain";
|
|
1208
1213
|
messageBody = log.message;
|
|
1209
1214
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# BaseApi
|
|
2
|
+
|
|
3
|
+
The `BaseApi` class offers a minimal foundation for writing API helpers in POMWright. It wraps Playwright's `APIRequestContext` and integrates with the shared [`PlaywrightReportLogger`](./PlaywrightReportLogger-explanation.md).
|
|
4
|
+
|
|
5
|
+
## Constructor
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
constructor(baseUrl: string, apiName: string, context: APIRequestContext, pwrl: PlaywrightReportLogger)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
* `baseUrl` – root address used to build request URLs.
|
|
12
|
+
* `apiName` – human‑readable identifier; also used as a logging prefix.
|
|
13
|
+
* `context` – Playwright [`APIRequestContext`](https://playwright.dev/docs/api/class-apirequestcontext).
|
|
14
|
+
* `pwrl` – logger instance supplied by the test fixtures. A child logger is created for the API class.
|
|
15
|
+
|
|
16
|
+
The class stores these values on the instance and exposes the `request` and `log` properties for use in subclasses.
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import type { APIRequestContext } from "@playwright/test";
|
|
22
|
+
import { BaseApi, type PlaywrightReportLogger } from "pomwright";
|
|
23
|
+
|
|
24
|
+
class MyApi extends BaseApi {
|
|
25
|
+
constructor(baseUrl: string, context: APIRequestContext, pwrl: PlaywrightReportLogger) {
|
|
26
|
+
super(baseUrl, MyApi.name, context, pwrl);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
v1 = {
|
|
30
|
+
users: {
|
|
31
|
+
id: {
|
|
32
|
+
get: async (id: string, status: number = 200) => {
|
|
33
|
+
const response = await this.request.get(`/api/users/${id}`);
|
|
34
|
+
expect(response.status(), "should have status code: 200").toBe(status);
|
|
35
|
+
const body = await response.json();
|
|
36
|
+
return body;
|
|
37
|
+
},
|
|
38
|
+
//...etc.
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
products: {
|
|
42
|
+
//...etc.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then in a test you'd do:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
test("some test", { tag: ["@api", "@user"] }, async ({ myApi }) => {
|
|
52
|
+
// Create user
|
|
53
|
+
|
|
54
|
+
const existingUser = await myApi.v1.users.id.get(userId); // 200 (default) user found
|
|
55
|
+
|
|
56
|
+
// Delete user
|
|
57
|
+
|
|
58
|
+
const deletedUser = await myApi.v1.users.id.get(userId, 204); // 204 user not found successfully
|
|
59
|
+
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`BaseApi` does not dictate how requests are made; you are free to add domain‑specific methods using the provided `request` context and logger.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# BasePage
|
|
2
|
+
|
|
3
|
+
`BasePage` is the foundation for all Page Object Classes (POCs) in POMWright. It provides common plumbing for Playwright pages, logging, locator management and session storage.
|
|
4
|
+
|
|
5
|
+
## Generics
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
BasePage<LocatorSchemaPathType, Options = { urlOptions: { baseUrlType: string; urlPathType: string } }, LocatorSubstring>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
* **LocatorSchemaPathType** – union of valid locator paths for the POC.
|
|
12
|
+
* **Options** – optional configuration that allows the `baseUrl` or `urlPath` to be typed as a `RegExp` when dynamic values are required for mapping certain resource paths. See [intTest/page-object-models/testApp/with-options/base/baseWithOptions.page.ts](../intTest/page-object-models/testApp/with-options/base/baseWithOptions.page.ts)
|
|
13
|
+
* **LocatorSubstring** – internal type used when working with sub paths; normally left undefined.
|
|
14
|
+
|
|
15
|
+
## Constructor
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
constructor(
|
|
19
|
+
page: Page,
|
|
20
|
+
testInfo: TestInfo,
|
|
21
|
+
baseUrl: ExtractBaseUrlType<Options>,
|
|
22
|
+
urlPath: ExtractUrlPathType<Options>,
|
|
23
|
+
pocName: string,
|
|
24
|
+
pwrl: PlaywrightReportLogger
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The base class stores these values and derives `fullUrl`. A child [`PlaywrightReportLogger`](./PlaywrightReportLogger-explanation.md) is created for the POC and a [`SessionStorage`](./sessionStorage-methods-explanation.md) helper is exposed as `sessionStorage`.
|
|
29
|
+
|
|
30
|
+
## Required implementation
|
|
31
|
+
|
|
32
|
+
Every POC extending `BasePage` must:
|
|
33
|
+
|
|
34
|
+
1. Define a `LocatorSchemaPath` union describing available locators.
|
|
35
|
+
2. Implement the `initLocatorSchemas()` method which adds schemas to the internal `GetLocatorBase` instance.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// login.page.ts
|
|
39
|
+
import type { Page, TestInfo } from "@playwright/test";
|
|
40
|
+
import { BasePage, type PlaywrightReportLogger } from "pomwright";
|
|
41
|
+
import { test } from "@fixtures/all.fixtures";
|
|
42
|
+
import { type LocatorSchemaPath, initLocatorSchemas } from "./login.locatorSchema";
|
|
43
|
+
|
|
44
|
+
export default class Login extends BasePage<LocatorSchemaPath> {
|
|
45
|
+
constructor(page: Page, testInfo: TestInfo, pwrl: PlaywrightReportLogger) {
|
|
46
|
+
super(page, testInfo, "https://example.com", "/login", Login.name, pwrl);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected initLocatorSchemas() {
|
|
50
|
+
initLocatorSchemas(this.locators);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public async login(user: string, pass: string) {
|
|
54
|
+
await test.step(`${this.pocName}: Fill login form and login`, async () => {
|
|
55
|
+
|
|
56
|
+
await test.step(`${this.pocName}: Fill username field`, async () => {
|
|
57
|
+
const locator = await this.getNestedLocator("main.section@login.form.textbox@username");
|
|
58
|
+
await locator.fill(user);
|
|
59
|
+
await locator.blur();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await test.step(`${this.pocName}: Fill password field`, async () => {
|
|
63
|
+
const locator = await this.getNestedLocator("main.section@login.form.textbox@password");
|
|
64
|
+
await locator.fill(pass);
|
|
65
|
+
await locator.blur();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await test.step(`${this.pocName}: Click login button`, async () => {
|
|
69
|
+
const locator = await this.getNestedLocator("main.section@login.button@login");
|
|
70
|
+
await locator.click();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* We probably don't want to await navigation here, instead we do it in the test,
|
|
75
|
+
* using the POC representing the page we are navigated to (e.g. /profile)
|
|
76
|
+
*/
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Helper methods
|
|
83
|
+
|
|
84
|
+
`BasePage` exposes a small API built around `LocatorSchema` definitions:
|
|
85
|
+
|
|
86
|
+
* `getLocatorSchema(path)` – returns a chainable object; see [locator methods](./get-locator-methods-explanation.md).
|
|
87
|
+
* `getLocator(path)` – wrapper method for `getLocatorSchema(path).getLocator();` - resolves the locator for the final segment of a path, e.g. the Locator created from the LocatorSchema the full LocatorSchemaPath references.
|
|
88
|
+
* `getNestedLocator(path, indices?)` – wrapper method for `getLocatorSchema(path).getNestedLocator();` - automatically chains locators along the path and optionally selects nth occurrences.
|
|
89
|
+
|
|
90
|
+
All locators are strongly typed, giving compile‑time suggestions and preventing typos in `LocatorSchemaPath` strings.
|
|
91
|
+
|
|
92
|
+
## Recommendation
|
|
93
|
+
|
|
94
|
+
Instead of extending each of your POC's with BasePage from POMWright, opt instead to create an abstract BasePage class for your domain and have it extend BasePage from POMWright. Then have each of your POC's extend your abstract POC instead. This allows us to easily sentralize common helper methods, LocatorSchema, etc. and reuse it across all our POC's for the given domain.
|
|
95
|
+
|
|
96
|
+
For examples see [intTest/page-object-models/testApp](../intTest/page-object-models/testApp)
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# LocatorSchema
|
|
2
|
+
|
|
3
|
+
A `LocatorSchema` describes how to find an element in the DOM. Instead of storing Playwright `Locator` objects directly, POMWright keeps these schema definitions and resolves them only when a test requests the locator. The same schema can be reused across multiple page objects and test files without risking stale references.
|
|
4
|
+
|
|
5
|
+
Every schema must set `locatorMethod` to one of the [`GetByMethod`](../src/helpers/locatorSchema.interface.ts) enum values. The matching selector fields listed below determine which Playwright API call is executed under the hood. You can provide more than one selector field, but the method chosen by `locatorMethod` is the one that will be used when the locator is created.
|
|
6
|
+
|
|
7
|
+
When you register a schema with `GetLocatorBase.addSchema(path, schemaDetails)` the `locatorSchemaPath` is stored automatically and becomes part of the structured logs produced by POMWright.
|
|
8
|
+
|
|
9
|
+
## Selector strategies
|
|
10
|
+
|
|
11
|
+
> Note: You can define both "role" and "locator" on the same LocatorSchema, but which one is used is dictated by "locatorMethod". Too keep things clean I recommend only defining the actual type of Playwright Locator you'll be using.
|
|
12
|
+
|
|
13
|
+
Each selector strategy maps directly to a Playwright locator helper. The snippets below show the POMWright configuration alongside the equivalent raw Playwright call.
|
|
14
|
+
|
|
15
|
+
### `role` and `roleOptions`
|
|
16
|
+
|
|
17
|
+
Use [ARIA roles](https://playwright.dev/docs/api/class-page#page-get-by-role) as your primary selector. The optional `roleOptions` map to the options object accepted by `page.getByRole()`.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
locators.addSchema("content.button@edit", {
|
|
21
|
+
role: "button",
|
|
22
|
+
roleOptions: { name: "Edit" },
|
|
23
|
+
locatorMethod: GetByMethod.role
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Playwright equivalent:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
page.getByRole("button", { name: "Edit" });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `text` and `textOptions`
|
|
34
|
+
|
|
35
|
+
Match visible text using the same semantics as [`page.getByText()`](https://playwright.dev/docs/api/class-page#page-get-by-text).
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
locators.addSchema("content.link.help", {
|
|
39
|
+
text: "Need help?",
|
|
40
|
+
textOptions: { exact: true },
|
|
41
|
+
locatorMethod: GetByMethod.text
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Playwright equivalent:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
page.getByText("Need help?", { exact: true });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `label` and `labelOptions`
|
|
52
|
+
|
|
53
|
+
Reference form controls by their accessible label. `labelOptions` accepts the same arguments as [`page.getByLabel()`](https://playwright.dev/docs/api/class-page#page-get-by-label).
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
locators.addSchema("content.form.password", {
|
|
57
|
+
label: "Password",
|
|
58
|
+
locatorMethod: GetByMethod.label
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Playwright equivalent:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
page.getByLabel("Password");
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `placeholder` and `placeholderOptions`
|
|
69
|
+
|
|
70
|
+
Target elements by placeholder text via [`page.getByPlaceholder()`](https://playwright.dev/docs/api/class-page#page-get-by-placeholder).
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
locators.addSchema("content.form.search", {
|
|
74
|
+
placeholder: "Search",
|
|
75
|
+
placeholderOptions: { exact: true },
|
|
76
|
+
locatorMethod: GetByMethod.placeholder
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Playwright equivalent:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
page.getByPlaceholder("Search", { exact: true });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `altText` and `altTextOptions`
|
|
87
|
+
|
|
88
|
+
Resolve images and other elements with alternative text via [`page.getByAltText()`](https://playwright.dev/docs/api/class-page#page-get-by-alt-text).
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
locators.addSchema("content.hero.image", {
|
|
92
|
+
altText: "Company logo",
|
|
93
|
+
locatorMethod: GetByMethod.altText
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Playwright equivalent:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
page.getByAltText("Company logo");
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `title` and `titleOptions`
|
|
104
|
+
|
|
105
|
+
Tap into the [`title` attribute](https://playwright.dev/docs/api/class-page#page-get-by-title) of an element.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
locators.addSchema("content.tooltip.info", {
|
|
109
|
+
title: "More information",
|
|
110
|
+
locatorMethod: GetByMethod.title
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Playwright equivalent:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
page.getByTitle("More information");
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `locator` and `locatorOptions`
|
|
121
|
+
|
|
122
|
+
Fallback to raw selectors or locator chaining with [`page.locator()`](https://playwright.dev/docs/api/class-page#page-locator). `locatorOptions` is passed straight through to the Playwright call.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
locators.addSchema("content.main", {
|
|
126
|
+
locator: "main",
|
|
127
|
+
locatorOptions: { hasText: "Profile" },
|
|
128
|
+
locatorMethod: GetByMethod.locator
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Playwright equivalent:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
page.locator("main", { hasText: "Profile" });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `frameLocator`
|
|
139
|
+
|
|
140
|
+
Create [`FrameLocator`](https://playwright.dev/docs/api/class-page#page-frame-locator) instances when your DOM interaction starts inside an iframe.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
locators.addSchema("iframe.login", {
|
|
144
|
+
frameLocator: "#auth-frame",
|
|
145
|
+
locatorMethod: GetByMethod.frameLocator
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Playwright equivalent:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
page.frameLocator("#auth-frame");
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `testId`
|
|
156
|
+
|
|
157
|
+
Call [`page.getByTestId()`](https://playwright.dev/docs/api/class-page#page-get-by-test-id) by setting the `testId` field.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
locators.addSchema("content.button.reset", {
|
|
161
|
+
testId: "reset-btn",
|
|
162
|
+
locatorMethod: GetByMethod.testId
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Playwright equivalent:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
page.getByTestId("reset-btn");
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `dataCy`
|
|
173
|
+
|
|
174
|
+
`dataCy` is a POMWright convenience for the legacy `data-cy` attribute used in Cypress-based projects. Under the hood it still calls `page.locator()` with the [`data-cy=` selector engine](https://playwright.dev/docs/extensibility#custom-selector-engines) registered by `BasePage`.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
locators.addSchema("content.card.actions", {
|
|
178
|
+
dataCy: "card-actions",
|
|
179
|
+
locatorMethod: GetByMethod.dataCy
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Playwright equivalent:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
page.locator("data-cy=card-actions");
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
If you prefer to control the selector string yourself you can pass the full expression (`"data-cy=card-actions"`) and it will be used verbatim.
|
|
190
|
+
|
|
191
|
+
### `id`
|
|
192
|
+
|
|
193
|
+
`id` is the second POMWright-specific helper. Provide either the raw id string or a `RegExp`. Strings are normalised to CSS id selectors, so `"details"`, `"#details"`, and `"id=details"` all resolve to the same element. A regular expression matches the start of the `id` attribute, allowing you to capture generated identifiers.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
locators.addSchema("content.region.details", {
|
|
197
|
+
id: "profile-details",
|
|
198
|
+
locatorMethod: GetByMethod.id
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
locators.addSchema("content.region.dynamic", {
|
|
202
|
+
id: /^region-/,
|
|
203
|
+
locatorMethod: GetByMethod.id
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Playwright equivalent:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
page.locator("#profile-details");
|
|
211
|
+
page.locator('*[id^="region-"]');
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `filter`
|
|
215
|
+
|
|
216
|
+
Any schema can define a `filter` object with the same shape as [`locator.filter()`](https://playwright.dev/docs/api/class-locator#locator-filter). Filters are applied immediately after the initial Playwright locator is created.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
locators.addSchema("content.list.item", {
|
|
220
|
+
role: "listitem",
|
|
221
|
+
locatorMethod: GetByMethod.role,
|
|
222
|
+
filter: { hasText: /Primary Colors/i }
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Playwright equivalent:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
page.getByRole("listitem").filter({ hasText: /Primary Colors/i });
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Putting it together
|
|
233
|
+
|
|
234
|
+
Schemas are typically registered inside `initLocatorSchemas()` of a `BasePage` subclass. The example below demonstrates reusable fragments and multiple selector strategies working together.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { GetByMethod, type GetLocatorBase, type LocatorSchemaWithoutPath } from "pomwright";
|
|
238
|
+
type LocatorSchemaPath =
|
|
239
|
+
| "main"
|
|
240
|
+
| "main.button"
|
|
241
|
+
| "main.button@continue"
|
|
242
|
+
| "main.button@back";
|
|
243
|
+
|
|
244
|
+
export function initLocatorSchemas(locators: GetLocatorBase<LocatorSchemaPath>) {
|
|
245
|
+
locators.addSchema("main", {
|
|
246
|
+
locator: "main",
|
|
247
|
+
locatorMethod: GetByMethod.locator
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const button: LocatorSchemaWithoutPath = {
|
|
251
|
+
role: "button",
|
|
252
|
+
locatorMethod: GetByMethod.role
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
locators.addSchema("main.button", {
|
|
256
|
+
...button
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
locators.addSchema("main.button@continue", {
|
|
260
|
+
...button,
|
|
261
|
+
roleOptions: { name: "Continue" }
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
locators.addSchema("main.button@back", {
|
|
265
|
+
...button,
|
|
266
|
+
roleOptions: { name: "Back" }
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Once registered, the schemas can be consumed through the BasePage helpers documented in [`docs/get-locator-methods-explanation.md`](./get-locator-methods-explanation.md).
|