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/README.md CHANGED
@@ -1,20 +1,24 @@
1
+ ---
2
+ "pomwright": minor
3
+ ---
4
+
1
5
  # POMWright
2
6
 
3
7
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/DyHex/POMWright/main.yaml?label=CI%20on%20main) [![NPM Version](https://img.shields.io/npm/v/pomwright)](https://www.npmjs.com/package/pomwright) ![NPM Downloads](https://img.shields.io/npm/dt/pomwright) ![GitHub License](https://img.shields.io/github/license/DyHex/POMWright) [![NPM dev or peer Dependency Version](https://img.shields.io/npm/dependency-version/pomwright/peer/%40playwright%2Ftest)](https://www.npmjs.com/package/playwright) [![Static Badge](https://img.shields.io/badge/created%40-ICE-ffcd00)](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 LocatorSchemaPaths.
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 browsers
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 tests
90
+ ### Run Tests
82
91
 
83
- Execute tests across chromium, firefox, and webkit:
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 ./example/playwright-report. Open the index.html file in your browser to view the results.
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
- ```TS
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
- ```TS
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 fixture in a Playwright tests
184
+ ### Using the Fixture in Playwright Tests
176
185
 
177
- #### Click edit button with a single locator
186
+ #### Click Edit Button with a Single Locator
178
187
 
179
- ```TS
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 edit button with a nested locator
205
+ #### Click Edit Button with a Nested Locator
198
206
 
199
- ```TS
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 index for nested locator(s)
227
+ #### Specify Index for Nested Locators
221
228
 
222
- ```TS
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
- 3: 0, 4: 1
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 locator before use
252
+ #### Update a Locator Before Use
246
253
 
247
- ```TS
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({ roleOptions: { name: "Edit details" }})
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 nested locator before use
278
+ #### Update a Nested Locator Before Use
271
279
 
272
- ```TS
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
- .updates({
288
- 2: { // content.region.details
289
- locator: ".profile-details",
290
- locatorMethod: GetByMethod.locator
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 multiple versions of a locator
305
+ #### Make Multiple Versions of a Locator
301
306
 
302
- ```TS
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).