pomwright 1.1.1 → 1.2.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 +583 -0
- package/README.md +228 -43
- package/dist/index.d.mts +524 -128
- package/dist/index.d.ts +524 -128
- package/dist/index.js +320 -117
- package/dist/index.mjs +320 -117
- package/index.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
"pomwright": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# POMWright
|
|
2
6
|
|
|
3
7
|
 [](https://www.npmjs.com/package/pomwright)   [](https://www.npmjs.com/package/playwright) [](https://www.ice.no/)
|
|
4
8
|
|
|
5
9
|
POMWright is a TypeScript-based framework that implements the Page Object Model Design Pattern, designed specifically to augment Playwright's testing capabilities.
|
|
6
10
|
|
|
7
|
-
POMWright provides a way of abstracting the implementation details of a web page and encapsulating them into a reusable page object. This approach makes the tests easier to read, write and maintain, and helps reduce duplicated code by breaking down the code into smaller, reusable components, making the code more maintainable and organized.
|
|
11
|
+
POMWright provides a way of abstracting the implementation details of a web page and encapsulating them into a reusable page object. This approach makes the tests easier to read, write, and maintain, and helps reduce duplicated code by breaking down the code into smaller, reusable components, making the code more maintainable and organized.
|
|
8
12
|
|
|
9
13
|
## Features
|
|
10
14
|
|
|
11
15
|
### Easy Creation of Page Object Classes
|
|
12
16
|
|
|
13
|
-
Simply extend a class with BasePage to create a Page Object Class (POC).
|
|
17
|
+
Simply extend a class with `BasePage` to create a Page Object Class (POC).
|
|
14
18
|
|
|
15
19
|
### Support for Multiple Domains/BaseURLs
|
|
16
20
|
|
|
17
|
-
Define different base URLs by extending an abstract class with BasePage per domain and have your POCs for each domain extend the abstract classes.
|
|
21
|
+
Define different base URLs by extending an abstract class with `BasePage` per domain and have your POCs for each domain extend the abstract classes.
|
|
18
22
|
|
|
19
23
|
### Custom Playwright Fixture Integration
|
|
20
24
|
|
|
@@ -26,15 +30,15 @@ Define comprehensive locators for each POC and share common locators between the
|
|
|
26
30
|
|
|
27
31
|
### Advanced Locator Management
|
|
28
32
|
|
|
29
|
-
Efficiently manage and chain locators through
|
|
33
|
+
Efficiently manage and chain locators through `LocatorSchemaPath`s.
|
|
30
34
|
|
|
31
35
|
### Dynamic Locator Schema Updates
|
|
32
36
|
|
|
33
|
-
Modify single or multiple locators within a chained locator dynamically during tests.
|
|
37
|
+
Modify single or multiple locators within a chained locator dynamically during tests using the new `.update()` and `.addFilter()` methods.
|
|
34
38
|
|
|
35
39
|
### Deep Copy of LocatorSchemas
|
|
36
40
|
|
|
37
|
-
Ensure that original LocatorSchemas remain immutable and reusable across tests.
|
|
41
|
+
Ensure that original `LocatorSchemas` remain immutable and reusable across tests.
|
|
38
42
|
|
|
39
43
|
### Custom HTML Logger
|
|
40
44
|
|
|
@@ -42,7 +46,12 @@ Gain insights with detailed logs for nested locators, integrated with Playwright
|
|
|
42
46
|
|
|
43
47
|
### SessionStorage Handling
|
|
44
48
|
|
|
45
|
-
Enhance your tests with advanced sessionStorage handling capabilities.
|
|
49
|
+
Enhance your tests with advanced `sessionStorage` handling capabilities.
|
|
50
|
+
|
|
51
|
+
### Enhanced Locator Filtering
|
|
52
|
+
|
|
53
|
+
- **New `filter` Property**: Apply filters across various locator types beyond just the `locator` method.
|
|
54
|
+
- **New `.addFilter()` Method**: Dynamically add filters to any part of the locator chain.
|
|
46
55
|
|
|
47
56
|
## Installation
|
|
48
57
|
|
|
@@ -70,7 +79,7 @@ Navigate to the "example" folder and install the necessary dependencies:
|
|
|
70
79
|
pnpm install
|
|
71
80
|
```
|
|
72
81
|
|
|
73
|
-
### Playwright
|
|
82
|
+
### Playwright Browsers
|
|
74
83
|
|
|
75
84
|
Install or update Playwright browsers:
|
|
76
85
|
|
|
@@ -78,9 +87,9 @@ Install or update Playwright browsers:
|
|
|
78
87
|
pnpm playwright install --with-deps
|
|
79
88
|
```
|
|
80
89
|
|
|
81
|
-
### Run
|
|
90
|
+
### Run Tests
|
|
82
91
|
|
|
83
|
-
Execute tests across
|
|
92
|
+
Execute tests across Chromium, Firefox, and WebKit:
|
|
84
93
|
|
|
85
94
|
```bash
|
|
86
95
|
pnpm playwright test
|
|
@@ -96,7 +105,7 @@ pnpm playwright test --workers 2 # Set the number of parallel workers
|
|
|
96
105
|
|
|
97
106
|
### Reports
|
|
98
107
|
|
|
99
|
-
After the tests complete, a Playwright HTML report is available in
|
|
108
|
+
After the tests complete, a Playwright HTML report is available in `./example/playwright-report`. Open the `index.html` file in your browser to view the results.
|
|
100
109
|
|
|
101
110
|
## Usage
|
|
102
111
|
|
|
@@ -104,7 +113,7 @@ Dive into using POMWright with these examples:
|
|
|
104
113
|
|
|
105
114
|
### Create a Page Object Class (POC)
|
|
106
115
|
|
|
107
|
-
```
|
|
116
|
+
```typescript
|
|
108
117
|
import { Page, TestInfo } from "@playwright/test";
|
|
109
118
|
import { BasePage, PlaywrightReportLogger, GetByMethod } from "pomwright";
|
|
110
119
|
|
|
@@ -156,7 +165,7 @@ export default class Profile extends BasePage<LocatorSchemaPath> {
|
|
|
156
165
|
|
|
157
166
|
### Creating a Custom Playwright Fixture
|
|
158
167
|
|
|
159
|
-
```
|
|
168
|
+
```typescript
|
|
160
169
|
import { test as base } from "pomwright";
|
|
161
170
|
import Profile from "...";
|
|
162
171
|
|
|
@@ -172,11 +181,11 @@ export const test = base.extend<fixtures>({
|
|
|
172
181
|
});
|
|
173
182
|
```
|
|
174
183
|
|
|
175
|
-
### Using the
|
|
184
|
+
### Using the Fixture in Playwright Tests
|
|
176
185
|
|
|
177
|
-
#### Click
|
|
186
|
+
#### Click Edit Button with a Single Locator
|
|
178
187
|
|
|
179
|
-
```
|
|
188
|
+
```typescript
|
|
180
189
|
import { test } from ".../fixtures";
|
|
181
190
|
|
|
182
191
|
test("click edit button with a single locator", async ({ profile }) => {
|
|
@@ -190,13 +199,12 @@ test("click edit button with a single locator", async ({ profile }) => {
|
|
|
190
199
|
const editBtn = await profile.getLocator("content.region.details.button.edit");
|
|
191
200
|
|
|
192
201
|
await editBtn.click();
|
|
193
|
-
|
|
194
202
|
});
|
|
195
203
|
```
|
|
196
204
|
|
|
197
|
-
#### Click
|
|
205
|
+
#### Click Edit Button with a Nested Locator
|
|
198
206
|
|
|
199
|
-
```
|
|
207
|
+
```typescript
|
|
200
208
|
import { test } from ".../fixtures";
|
|
201
209
|
|
|
202
210
|
test("click edit button with a nested locator", async ({ profile }) => {
|
|
@@ -213,13 +221,12 @@ test("click edit button with a nested locator", async ({ profile }) => {
|
|
|
213
221
|
const editBtn = await profile.getNestedLocator("content.region.details.button.edit");
|
|
214
222
|
|
|
215
223
|
await editBtn.click();
|
|
216
|
-
|
|
217
224
|
});
|
|
218
225
|
```
|
|
219
226
|
|
|
220
|
-
#### Specify
|
|
227
|
+
#### Specify Index for Nested Locators
|
|
221
228
|
|
|
222
|
-
```
|
|
229
|
+
```typescript
|
|
223
230
|
import { test } from ".../fixtures";
|
|
224
231
|
|
|
225
232
|
test("specify index for nested locator(s)", async ({ profile }) => {
|
|
@@ -234,17 +241,17 @@ test("specify index for nested locator(s)", async ({ profile }) => {
|
|
|
234
241
|
* content.region.details.button.edit with .nth(1)
|
|
235
242
|
*/
|
|
236
243
|
const editBtn = await profile.getNestedLocator("content.region.details.button.edit", {
|
|
237
|
-
|
|
244
|
+
"content.region.details": 0,
|
|
245
|
+
"content.region.details.button.edit": 1
|
|
238
246
|
});
|
|
239
247
|
|
|
240
248
|
await editBtn.click();
|
|
241
|
-
|
|
242
249
|
});
|
|
243
250
|
```
|
|
244
251
|
|
|
245
|
-
#### Update a
|
|
252
|
+
#### Update a Locator Before Use
|
|
246
253
|
|
|
247
|
-
```
|
|
254
|
+
```typescript
|
|
248
255
|
import { test } from ".../fixtures";
|
|
249
256
|
|
|
250
257
|
test("update a locator before use", async ({ profile }) => {
|
|
@@ -259,17 +266,18 @@ test("update a locator before use", async ({ profile }) => {
|
|
|
259
266
|
* content.region.details.button.edit (updated)
|
|
260
267
|
*/
|
|
261
268
|
const editBtn = await profile.getLocatorSchema("content.region.details.button.edit")
|
|
262
|
-
.update(
|
|
269
|
+
.update("content.region.details.button.edit", {
|
|
270
|
+
roleOptions: { name: "Edit details" }
|
|
271
|
+
})
|
|
263
272
|
.getNestedLocator();
|
|
264
273
|
|
|
265
274
|
await editBtn.click();
|
|
266
|
-
|
|
267
275
|
});
|
|
268
276
|
```
|
|
269
277
|
|
|
270
|
-
#### Update a
|
|
278
|
+
#### Update a Nested Locator Before Use
|
|
271
279
|
|
|
272
|
-
```
|
|
280
|
+
```typescript
|
|
273
281
|
import { test } from ".../fixtures";
|
|
274
282
|
|
|
275
283
|
test("update a nested locator before use", async ({ profile }) => {
|
|
@@ -284,22 +292,19 @@ test("update a nested locator before use", async ({ profile }) => {
|
|
|
284
292
|
* content.region.details.button.edit
|
|
285
293
|
*/
|
|
286
294
|
const editBtn = await profile.getLocatorSchema("content.region.details.button.edit")
|
|
287
|
-
.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
})
|
|
295
|
+
.update("content.region.details", {
|
|
296
|
+
locator: ".profile-details",
|
|
297
|
+
locatorMethod: GetByMethod.locator
|
|
298
|
+
})
|
|
293
299
|
.getNestedLocator();
|
|
294
300
|
|
|
295
301
|
await editBtn.click();
|
|
296
|
-
|
|
297
302
|
});
|
|
298
303
|
```
|
|
299
304
|
|
|
300
|
-
#### Make
|
|
305
|
+
#### Make Multiple Versions of a Locator
|
|
301
306
|
|
|
302
|
-
```
|
|
307
|
+
```typescript
|
|
303
308
|
import { test } from ".../fixtures";
|
|
304
309
|
|
|
305
310
|
test("make multiple versions of a locator", async ({ profile }) => {
|
|
@@ -312,7 +317,7 @@ test("make multiple versions of a locator", async ({ profile }) => {
|
|
|
312
317
|
const editBtn = await editBtnSchema.getLocator();
|
|
313
318
|
await editBtn.click();
|
|
314
319
|
|
|
315
|
-
editBtnSchema.update({ roleOptions: { name: "Edit details" }});
|
|
320
|
+
editBtnSchema.update("content.region.details.button.edit", { roleOptions: { name: "Edit details" } });
|
|
316
321
|
|
|
317
322
|
const editBtnUpdated = await editBtnSchema.getNestedLocator();
|
|
318
323
|
await editBtnUpdated.click();
|
|
@@ -321,18 +326,198 @@ test("make multiple versions of a locator", async ({ profile }) => {
|
|
|
321
326
|
* Calling profile.getLocatorSchema("content.region.details.button.edit") again
|
|
322
327
|
* will return a new deepCopy of the original LocatorSchema
|
|
323
328
|
*/
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Deprecations
|
|
333
|
+
|
|
334
|
+
### 1. Deprecated `.update` and `.updates` Methods
|
|
335
|
+
|
|
336
|
+
- **Old `.update(updates: Partial<LocatorSchemaWithoutPath>): LocatorSchemaWithMethods`**
|
|
337
|
+
- **Old `.updates(indexedUpdates: { [index: number]: Partial<LocatorSchemaWithoutPath> | null }): LocatorSchemaWithMethods`**
|
|
338
|
+
|
|
339
|
+
**Reason:**
|
|
340
|
+
The `.updates` method relied on index-based updates, which are prone to errors and require manual maintenance, especially when `LocatorSchemaPath` strings are renamed or restructured. Additionally, the old `.update` method could only update the last `LocatorSchema` in the chain, making it less flexible.
|
|
341
|
+
|
|
342
|
+
**Replacement:**
|
|
343
|
+
Use the new `.update(subPath, modifiedSchema)` method, which leverages valid `subPath`s of `LocatorSchemaPath` strings for more intuitive and maintainable updates.
|
|
344
|
+
|
|
345
|
+
**Removal Schedule:**
|
|
346
|
+
These methods are deprecated and will be removed in version 2.0.0.
|
|
324
347
|
|
|
348
|
+
### 2. Deprecated Old `getNestedLocator` Method
|
|
349
|
+
|
|
350
|
+
- **Old `getNestedLocator(indices?: { [key: number]: number | null }): Promise<Locator>`**
|
|
351
|
+
|
|
352
|
+
**Reason:**
|
|
353
|
+
Index-based indexing is less readable and requires manual updates when `LocatorSchemaPath` strings change.
|
|
354
|
+
|
|
355
|
+
**Replacement:**
|
|
356
|
+
Use the updated `getNestedLocator(subPathIndices?: { [K in SubPaths<LocatorSchemaPathType, LocatorSubstring>]: number | null }): Promise<Locator>` method, which utilizes `LocatorSchemaPath` strings for indexing.
|
|
357
|
+
|
|
358
|
+
**Removal Schedule:**
|
|
359
|
+
This method is deprecated and will be removed in version 2.0.0.
|
|
360
|
+
|
|
361
|
+
## Migration Guide
|
|
362
|
+
|
|
363
|
+
### Updating `.update` and `.updates` Methods
|
|
364
|
+
|
|
365
|
+
**Old Usage:**
|
|
366
|
+
```typescript
|
|
367
|
+
const allCheckboxes = await poc
|
|
368
|
+
.getLocatorSchema("main.products.searchControls.filterType.label.checkbox")
|
|
369
|
+
.updates({ 3: { locatorOptions: { hasText: /Producer/i } } })
|
|
370
|
+
.getNestedLocator();
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**New Usage:**
|
|
374
|
+
```typescript
|
|
375
|
+
const allCheckboxes = await poc
|
|
376
|
+
.getLocatorSchema("main.products.searchControls.filterType.label.checkbox")
|
|
377
|
+
.update("main.products.searchControls.filterType", { locatorOptions: { hasText: /Producer/i } })
|
|
378
|
+
.getNestedLocator();
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Updating `getNestedLocator` Method
|
|
382
|
+
|
|
383
|
+
**Old Usage (Deprecated):**
|
|
384
|
+
```typescript
|
|
385
|
+
const saveBtn = await profile.getNestedLocator("content.region.details.button.save", { 4: 2 });
|
|
386
|
+
|
|
387
|
+
const editBtn = await profile.getLocatorSchema("content.region.details.button.edit")
|
|
388
|
+
.getNestedLocator({ 2: index });
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**New Usage:**
|
|
392
|
+
```typescript
|
|
393
|
+
const saveBtn = await profile.getNestedLocator("content.region.details.button.save", {
|
|
394
|
+
"content.region.details.button.save": 2
|
|
325
395
|
});
|
|
396
|
+
|
|
397
|
+
const editBtn = await profile.getLocatorSchema("content.region.details.button.edit")
|
|
398
|
+
.getNestedLocator({ "content.region.details": index });
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Utilizing `LocatorSchemaPath` Instead of Indices
|
|
402
|
+
|
|
403
|
+
Transition from index-based to `LocatorSchemaPath`-based indexing to improve code readability and maintainability.
|
|
404
|
+
|
|
405
|
+
**Old Example:**
|
|
406
|
+
```typescript
|
|
407
|
+
const allCheckboxes = await poc
|
|
408
|
+
.getLocatorSchema("main.form.item.checkbox")
|
|
409
|
+
.updates({ 3: { locatorOptions: { hasText: /Producer/i } } })
|
|
410
|
+
.getNestedLocator();
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**New Example:**
|
|
414
|
+
```typescript
|
|
415
|
+
const allCheckboxes = await poc
|
|
416
|
+
.getLocatorSchema("main.form.item.checkbox")
|
|
417
|
+
.update("main.form.item", { locatorOptions: { hasText: /Producer/i } })
|
|
418
|
+
.getNestedLocator();
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Example in Context
|
|
422
|
+
|
|
423
|
+
### Defining a LocatorSchema with `filter` and Using `.addFilter()`
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
// Defining LocatorSchemas
|
|
427
|
+
this.locators.addSchema("body.main.section@userInfo", {
|
|
428
|
+
role: "region",
|
|
429
|
+
roleOptions: { name: "Contact Info" },
|
|
430
|
+
filter: { hasText: /e-mail/i },
|
|
431
|
+
locatorMethod: GetByMethod.role
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Dynamically adding additional filters using `.addFilter()`
|
|
435
|
+
const specificSection = await poc
|
|
436
|
+
.getLocatorSchema("body.main.section@userInfo")
|
|
437
|
+
.addFilter("body.main.section@userInfo", { hasText: "Additional Services" })
|
|
438
|
+
.getNestedLocator();
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Updating LocatorSchemas with the New `.update()` Method
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
const editBtn = await profile
|
|
445
|
+
.getLocatorSchema("content.region.details.button.edit")
|
|
446
|
+
.update("content.region.details.button.edit", {
|
|
447
|
+
roleOptions: { name: "new accessibility name" }
|
|
448
|
+
})
|
|
449
|
+
.getNestedLocator();
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Reusing LocatorSchemas with `LocatorSchemaWithoutPath`
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { GetByMethod, LocatorSchemaWithoutPath } from "pomwright";
|
|
456
|
+
import { missingInputError } from "@common/page-components/errors.locatorSchema.ts";
|
|
457
|
+
|
|
458
|
+
export type LocatorSchemaPath =
|
|
459
|
+
| "body"
|
|
460
|
+
| "body.main"
|
|
461
|
+
| "body.main.section"
|
|
462
|
+
| "body.main.section@products"
|
|
463
|
+
| "body.main.section@userInfo"
|
|
464
|
+
| "body.main.section@userInfo.input@email"
|
|
465
|
+
| "body.main.section@userInfo.inputError"
|
|
466
|
+
| "body.main.section@deliveryInfo";
|
|
467
|
+
|
|
468
|
+
export function initLocatorSchemas(locators: GetLocatorBase<LocatorSchemaPath>) {
|
|
469
|
+
locators.addSchema("body", {
|
|
470
|
+
locator: "body",
|
|
471
|
+
locatorMethod: GetByMethod.locator,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
locators.addSchema("body.main", {
|
|
475
|
+
locator: "main",
|
|
476
|
+
locatorMethod: GetByMethod.locator,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const region: LocatorSchemaWithoutPath = { role: "region", locatorMethod: GetByMethod.role };
|
|
480
|
+
|
|
481
|
+
locators.addSchema("body.main.section", {
|
|
482
|
+
...region,
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
locators.addSchema("body.main.section@products", {
|
|
486
|
+
...region,
|
|
487
|
+
roleOptions: { name: "Products" },
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
locators.addSchema("body.main.section@userInfo", {
|
|
491
|
+
...region,
|
|
492
|
+
roleOptions: { name: "Contact Info" },
|
|
493
|
+
filter: { hasText: /e-mail/i },
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
locators.addSchema("body.main.section@userInfo.input@email", {
|
|
497
|
+
role: "textbox",
|
|
498
|
+
roleOptions: { name: "Input your e-mail" },
|
|
499
|
+
locatorMethod: GetByMethod.role,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
locators.addSchema("body.main.section@userInfo.inputError", {
|
|
503
|
+
...missingInputError,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
locators.addSchema("body.main.section@deliveryInfo", {
|
|
507
|
+
...region,
|
|
508
|
+
roleOptions: { name: "Delivery Info" },
|
|
509
|
+
});
|
|
510
|
+
}
|
|
326
511
|
```
|
|
327
512
|
|
|
328
513
|
## Troubleshooting and Support
|
|
329
514
|
|
|
330
|
-
If you encounter any issues or have questions, please check our issues page or reach out to us directly.
|
|
515
|
+
If you encounter any issues or have questions, please check our [issues page](https://github.com/DyHex/POMWright/issues) or reach out to us directly.
|
|
331
516
|
|
|
332
517
|
## Contributing
|
|
333
518
|
|
|
334
|
-
Pull Requests are welcome!
|
|
519
|
+
Pull Requests are welcome! Please open an issue or submit a pull request for any enhancements or bug fixes.
|
|
335
520
|
|
|
336
521
|
## License
|
|
337
522
|
|
|
338
|
-
POMWright is open-source software licensed under the Apache-2.0 license
|
|
523
|
+
POMWright is open-source software licensed under the [Apache-2.0 license](https://github.com/DyHex/POMWright/blob/main/LICENSE).
|