@zohodesk/testinglibrary 0.0.42-n20-experimental → 0.0.44-n20-experimental

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.
@@ -1,602 +0,0 @@
1
- # Page Object Model (POM) Structure Guide
2
-
3
- ## Purpose
4
-
5
- This document describes the recommended structure, format, and creation rules for Page Object Model files used in this testing framework.
6
-
7
- > **Related guide:** Selector file structure, naming rules, and `dom-selectors/` conventions are documented separately in [DomSelectors_README.md](./DomSelectors_README.md).
8
-
9
-
10
- ## What Each Folder Should Contain
11
-
12
- ### `page-object-model/`
13
-
14
- Store reusable page factory functions and lightweight page access helpers here.
15
-
16
- Examples:
17
-
18
- - one file per major page or panel
19
- - one `index.js` barrel export file re-exporting all POMs in the module
20
- - methods for navigation, element access, and user actions
21
-
22
- > Full module folder hierarchy — see [Steps_README.md §1](./Steps_README.md#1-file--folder-structure).
23
-
24
-
25
- ## Recommended POM Design
26
-
27
- This section describes what a well-structured POM should look like.
28
-
29
- Use it for design guidance such as class structure, method style, and separation of responsibilities.
30
-
31
- ### 1. One factory function per page or panel
32
-
33
- Create one factory function for each meaningful UI surface. Factory functions are initialized with a shared context object.
34
-
35
- > Full context object shape with types — see [Steps_README.md §4](./Steps_README.md#4-context-destructuring).
36
-
37
- Each factory destructures only the properties it needs:
38
-
39
- ```javascript
40
- TicketCommentsPanel({ page, i18N, actorContext })
41
- ```
42
-
43
- Avoid passing the whole context object through to individual methods — destructure at the factory level.
44
-
45
- Examples:
46
-
47
- - `SetupHomePage`
48
- - `SetupMenuPanel`
49
- - `TicketDetailsPage`
50
- - `TicketCommentsPanel`
51
-
52
- Avoid putting unrelated flows in the same factory function.
53
-
54
- ### 2. Expose meaningful methods
55
-
56
- Methods should represent business or UI actions.
57
-
58
- Good examples:
59
-
60
- - `openTicket(subject)`
61
- - `addComment(text)`
62
- - `deleteComment(text)`
63
- - `searchMenuPanel(keyword)`
64
-
65
- Avoid low-value names like:
66
-
67
- - `clickButton1()`
68
- - `fillField2()`
69
-
70
- ### 3. Separate selectors from actions when needed
71
-
72
- Keep raw locator definitions in `dom-selectors/` and keep reusable interaction logic in the page object.
73
-
74
- Use this split:
75
-
76
- - `dom-selectors/` stores selector constants or selector helper functions — see [DomSelectors_README.md](./DomSelectors_README.md)
77
- - `page-object-model/` exposes business-friendly methods such as `openTicket()` and `addComment()`
78
- - step files should call page object methods instead of repeating selector details
79
-
80
-
81
- ## Suggested POM File Format
82
-
83
- A page object file should usually contain:
84
-
85
- 1. imports
86
- 2. selector imports if needed
87
- 3. factory function definition destructuring the required properties from the shared context object
88
- 4. locator getter methods using `page` from the closure
89
- 5. action methods using `i18N`, `getMetaInfo`, `cacheLayer`, `executionContext`, or `actorContext` as needed from the closure
90
- 6. optional state access helpers
91
- 7. named export
92
-
93
- Example:
94
-
95
- ```javascript
96
- import { TicketSelectors } from '../dom-selectors/TicketSelectors';
97
-
98
- // Destructure only the context properties this POM needs
99
- const TicketCommentsPanel = ({ page, i18N, actorContext }) => {
100
- const getCommentInput = () => page.locator(TicketSelectors.COMMENT_INPUT);
101
-
102
- const getSaveCommentButton = () => page.locator(TicketSelectors.SAVE_COMMENT);
103
-
104
- const addComment = async (commentText) => {
105
- await getCommentInput().fill(commentText);
106
- await getSaveCommentButton().click();
107
- };
108
-
109
- const getCommentLocator = async (commentKey) => page.getByText(await i18N(commentKey));
110
-
111
- const getActorName = () => actorContext.name;
112
-
113
- return { getCommentInput, getSaveCommentButton, addComment, getCommentLocator, getActorName };
114
- };
115
-
116
- export { TicketCommentsPanel };
117
- ```
118
-
119
- ## Rules for POM Creation
120
-
121
- Before creating a new POM file, check whether an existing POM in the same module can be reused or extended.
122
-
123
-
124
- ### Rule 1: Place page objects inside the relevant module
125
-
126
- Keep module-specific page objects inside that module's `page-object-model/` folder.
127
-
128
- Example:
129
-
130
- ```text
131
- test-slices/uat/modules/Ticket/page-object-model/TicketDetailsPage.js
132
- ```
133
-
134
- ### Rule 2: Match file names to responsibility
135
-
136
- Use file names that reflect the page or component responsibility.
137
-
138
- POM files must use PascalCase:
139
-
140
- - `TicketDetailsPage.js`
141
- - `TicketCommentsPanel.js`
142
- - `SetupHomePage.js`
143
- - `SetupPages.js`
144
-
145
- For selector file naming conventions, see [DomSelectors_README.md](./DomSelectors_README.md).
146
-
147
- ### Rule 3: Use page objects for reusable UI logic only
148
-
149
- Move these into page objects:
150
-
151
- - navigation helpers
152
- - locator access
153
- - repeated button clicks
154
- - text input flows
155
- - reusable UI workflows
156
- - state access helpers
157
-
158
- Keep these out of page objects when possible:
159
-
160
- - feature wording
161
- - business rules
162
- - scenario decision-making
163
- - workflow orchestration across unrelated modules
164
- - test-specific branching based on expected business outcomes
165
-
166
- ### Rule 4: Prefer stable selectors
167
-
168
- Prefer robust selectors — see [DomSelectors_README.md](./DomSelectors_README.md) for the full list of preferred selector types and rules on stable selector usage.
169
-
170
- When a page has multiple similar elements, scope the locator to the nearest stable parent container before locating the child element.
171
-
172
- Example:
173
-
174
- - good: find the comment composer first, then locate the save button inside it
175
- - weak: call `page.getByRole(...).first()` on the full page when multiple matching buttons may exist
176
-
177
-
178
- ### Rule 5: Keep assertions outside POM
179
-
180
- Page object files must contain only UI interaction logic and state access helpers.
181
-
182
- POM files must not contain:
183
-
184
- - assertion helper imports
185
- - reusable validation logic
186
- - scenario outcome checks
187
- - methods that both read UI state and validate the result in the same place
188
-
189
- Preferred pattern:
190
-
191
- - good: the page object returns a locator, text, value, count, or state, and validation is performed in the step file or `assertions/`
192
- - weak: the page object clicks, reads UI state, and decides whether the scenario passed
193
-
194
- ### Where to place assertions: `assertions/` vs step file vs POM
195
-
196
- Assertions can live in exactly two places — never in a POM:
197
-
198
- | Place it here | When |
199
- |---|---|
200
- | **Inline in the step definition** | The assertion is used in only **one** spec file, or is a simple one-liner (`Assertions.isElementVisible(elem)`) |
201
- | **`assertions/` module file** | The **same locator-read + assertion combination** appears in **2 or more different spec files** |
202
- | **POM file** | **Never** — POMs return locators/values; they do not validate |
203
-
204
- **Decision flow:**
205
-
206
- 1. Write the assertion inline in the `Then` step.
207
- 2. If you later find the same assertion needed in a second spec file, extract it into `assertions/<ModuleName>Assertions.js`.
208
- 3. The assertion file receives a **locator or value** from a POM method — it never calls POM methods itself.
209
- 4. If the assertion is a single Playwright `expect()` call with no POM interaction, keep it inline — extraction adds no value.
210
-
211
- **What belongs in an assertion file:**
212
-
213
- - A function that accepts a locator, value, or element and runs one or more `Assertions.*` / `expect()` calls.
214
- - Assertion files must not import POMs, must not perform clicks or navigation, and must not contain `Given`/`When`/`Then` steps.
215
-
216
- ```javascript
217
- // assertions/ContractAssertions.js
218
- import Assertions from '../../_assertions/Assertions';
219
-
220
- const ContractAssertions = {
221
- async verifyContractStatus(statusElement, expectedStatus) {
222
- await Assertions.isElementVisible(statusElement);
223
- await Assertions.isElementHaveText(statusElement, expectedStatus);
224
- },
225
- };
226
-
227
- export default ContractAssertions;
228
- ```
229
-
230
- ```javascript
231
- // In a step file — uses POM to get the element, then passes to assertion
232
- Then('the contract status should be {string}', async ({ page, i18N }, status) => {
233
- const detailView = ContractsDetailView({ page, i18N });
234
- const statusElem = detailView.getStatusElement();
235
- await ContractAssertions.verifyContractStatus(statusElem, status);
236
- });
237
- ```
238
-
239
-
240
- ### Rule 6: Return locators from getter methods
241
-
242
- When an element is reused multiple times, create getter methods.
243
-
244
- Getter methods (e.g., `getSearchInput()`) must return a **Playwright Locator** — not an awaited value. Do not `await` inside a getter. Action methods (e.g., `clickSave()`) are async and `await` Playwright actions internally.
245
-
246
- Example:
247
-
248
- - `getSearchInput()` — returns `page.locator(...)` (synchronous, returns Locator)
249
- - `getSetupMenu(menuName)` — returns `page.locator(...)` (synchronous, returns Locator)
250
- - `clickSave()` — async, awaits `click()` internally
251
-
252
- ### Rule 7: Use async methods for UI actions
253
-
254
- Any method that interacts with the browser should be `async` and should `await` Playwright actions.
255
-
256
- ### Rule 8: Keep methods focused
257
-
258
- A method should do one clear thing.
259
-
260
- Good:
261
-
262
- - `openSetupPage(moduleName)`
263
- - `clearSearchInput()`
264
- - `openTicketDetail(subject)`
265
-
266
- Avoid very large methods that perform unrelated actions.
267
-
268
- ### Rule 9: Reuse shared helper files when useful
269
-
270
- A helper file can be used to create and return page object instances.
271
-
272
- Example pattern:
273
-
274
- - `SetupPages.js` returns `getHomePage(context)` and `getSetupMenuPanel(context)`, where `context` is the shared context object passed from the step file
275
-
276
- This is useful when multiple step files use the same objects.
277
-
278
-
279
- ### Rule 10: No arbitrary waits
280
-
281
- Do not use hard waits or time-based delays in POM files.
282
-
283
- > Full waiting patterns, `networkidle` warnings, and DOM readiness guidance — see [Steps_README.md §10](./Steps_README.md#10-helper-functions--utilities).
284
-
285
- Key rule: use only explicit readiness checks (`element.waitFor()`, stable container visibility, or required network completion when it is part of the UI flow). Never use `page.waitForTimeout()`.
286
-
287
- ### Rule 11: No static or hardcoded locators in POM files
288
-
289
- Do not define raw locator strings directly inside POM action methods.
290
-
291
- Required rule:
292
-
293
- - reusable selectors must live in `dom-selectors/` — see [DomSelectors_README.md](./DomSelectors_README.md)
294
- - dynamic selector patterns must be implemented as selector helper functions in `dom-selectors/`
295
- - POM files must import selectors instead of embedding raw locator strings
296
-
297
- Use text-based location only when there is no stable selector and the text is part of the intentional UI contract.
298
-
299
- ### Rule 12: Design POMs for order-independent execution
300
-
301
- POM design must support order-independent execution.
302
-
303
- Do not rely on:
304
-
305
- - previously executed scenarios
306
- - cached mutable state shared across tests
307
- - data created implicitly by earlier scenarios
308
-
309
- Every scenario must prepare, create, or receive the state it needs explicitly.
310
-
311
- For random-order-safe automation:
312
-
313
- - never use `.first()` or `.last()` to resolve ambiguity
314
- - never rely on visual order
315
- - prefer unique ids, record ids, or returned API/UI identifiers
316
-
317
- If multiple records may share similar visible text, resolve the exact record through a stable identifier instead of choosing the first visible match.
318
-
319
- Better pattern example:
320
-
321
- ```javascript
322
- import { TicketSelectors } from '../dom-selectors/TicketSelectors';
323
-
324
- const TicketListPage = ({ page }) => {
325
- const getSubjectCell = (subject) =>
326
- page.locator(ticketSelectors.SUBJECT_CELL).getByText(subject, { exact: true });
327
-
328
- const getTicketIdBySubject = async (subject) => {
329
- const subjectCell = getSubjectCell(subject);
330
- await subjectCell.waitFor();
331
- const dataId = await subjectCell.getAttribute('data-id');
332
- return dataId?.replace('subject_', '');
333
- };
334
-
335
- const getTicketRowById = (ticketId) =>
336
- page.locator(ticketSelectors.ticketRowById(ticketId));
337
-
338
- const getTicketRow = async (subject) => {
339
- const ticketId = await getTicketIdBySubject(subject);
340
- return getTicketRowById(ticketId);
341
- };
342
-
343
- return { getSubjectCell, getTicketIdBySubject, getTicketRowById, getTicketRow };
344
- };
345
- ```
346
-
347
- ### Rule 13: Validate DOM readiness before any action
348
-
349
- Before clicking, filling, hovering, selecting, or submitting, validate that the UI is ready.
350
-
351
- `await element.waitFor()` defaults to `{ state: 'visible' }`. Always call it before `click()`, `fill()`, `hover()`, `selectOption()`, or `check()`. Use `{ state: 'hidden' }` only when waiting for an element to disappear. Use `{ state: 'attached' }` only when the element is expected to exist but may not yet be visible.
352
-
353
- Preferred sequence:
354
-
355
- 1. locate a stable parent container or target element
356
- 2. call `await element.waitFor()` to confirm readiness
357
- 3. perform the action
358
-
359
- Do not perform actions against elements that may still be loading, hidden, detached, or stale.
360
-
361
-
362
- ### Rule 14: File names must follow naming conventions
363
-
364
- File names must match the UI responsibility and use consistent naming.
365
-
366
- Required rules for POM files:
367
-
368
- - use descriptive PascalCase
369
- - file name should match the exported factory function name
370
- - prefer responsibility suffixes when applicable:
371
- - `Page`
372
- - `Panel`
373
- - `Dialog`
374
- - `Drawer`
375
- - `Section`
376
-
377
- Recommended POM examples:
378
-
379
- - `ActivityListPage.js`
380
- - `ActivityDetailViewPage.js`
381
- - `AccountContractsPanel.js`
382
-
383
- Avoid names like:
384
-
385
- - `test.js`
386
- - `activityPOM.js`
387
- - `leftPanel.js`
388
-
389
- For selector file naming conventions, see [DomSelectors_README.md](./DomSelectors_README.md).
390
-
391
- > Rules 17 and 18 cover `dom-selectors/` file purity and selector duplication. See [DomSelectors_README.md](./DomSelectors_README.md).
392
-
393
- ### Rule 15: Use factory functions initialized with a context object
394
-
395
- POMs must be defined as factory functions, not classes.
396
-
397
- Do not use `class`, `this`, `new`, `constructor`, or `prototype` in POM files. POMs must be plain functions that return plain objects.
398
-
399
- Factory functions accept a shared context object and destructure only the properties they need:
400
-
401
- ```javascript
402
- // Destructure only what this POM uses
403
- const TicketDetailsPage = ({ page, i18N, getMetaInfo, actorContext }) => {
404
- // All destructured properties are accessed via closure
405
- return { ... };
406
- };
407
- ```
408
-
409
- > Full context object shape with types — see [Steps_README.md §4](./Steps_README.md#4-context-destructuring).
410
-
411
- Required rules:
412
-
413
- - destructure only the properties the POM actually uses — do not accept and forward the full object
414
- - all destructured properties are accessed from the closure and never passed to individual methods
415
- - methods are returned as a plain object instead of relying on class instances
416
- - the factory is exported as a named export
417
-
418
- ### Rule 16: Use barrel exports for local module POMs
419
-
420
- Each module must have an `index.js` file inside `page-object-model/` that re-exports all POMs in that module.
421
-
422
- > **Two barrels exist:** This barrel (`page-object-model/index.js`) re-exports only POM factory functions. The module root `index.js` (see [Steps_README.md §11](./Steps_README.md#11-barrel-exports-indexjs)) re-exports POMs, assertions, and utilities for cross-module use. Step files should import from the **module root barrel** (`'../index'`), not from `'../page-object-model/index'` directly.
423
-
424
- Required rules:
425
-
426
- - the POM barrel index must contain only named re-exports of POM factory functions
427
- - the module root `index.js` consumes this barrel and adds assertions and utilities
428
-
429
- Example barrel file:
430
-
431
- ```javascript
432
- // page-object-model/index.js
433
- export { TicketDetailsPage } from './TicketDetailsPage';
434
- export { TicketCommentsPanel } from './TicketCommentsPanel';
435
- ```
436
-
437
- Step files import from the module root barrel:
438
-
439
- ```javascript
440
- import { TicketDetailsPage } from '../index';
441
- ```
442
-
443
- ### Rule 17: Use i18N for all user-facing strings
444
-
445
- All user-facing text matched in the UI must be resolved through `i18N`.
446
-
447
- **`i18N` is an async function** — always `await i18N('key')` before passing the result to a locator. Never write `i18N('key')` inline inside a Playwright locator call without awaiting first.
448
-
449
- **What counts as user-facing:** button labels, column headers, status values, error messages, placeholder text, tooltips, and menu items — any string a user can read in the UI.
450
-
451
- **What does NOT need i18N:** `data-test-id` values, `data-id` values, `id` attributes, `name` attributes, ARIA role names, CSS class names, and URL paths.
452
-
453
- Required rules:
454
-
455
- - destructure `i18N` from the shared context object in every POM factory that matches user-facing text
456
- - never hardcode translated strings directly in locator calls
457
- - use the `i18N` function as the single source for any text used in `getByText`, `getByLabel`, `getByPlaceholder`, or similar matchers
458
-
459
- Examples:
460
-
461
- - good: `page.getByText(await i18N('ticket.comments.save'))`
462
- - weak: `page.getByText('Save')`
463
-
464
- This ensures that tests remain valid across all supported locales.
465
-
466
-
467
- ## POM Creation Workflow
468
-
469
- This section describes the order to follow when creating or updating a POM.
470
-
471
- It does not redefine the design rules. It shows how to apply them step by step.
472
-
473
- ### Step 1: Identify the UI surface
474
-
475
- First, check whether a suitable POM already exists. Reuse or extend it if possible.
476
-
477
- Then decide whether the logic belongs to:
478
-
479
- - a full page
480
- - a side panel
481
- - a detail view
482
- - a reusable component
483
-
484
- ### Step 2: Create the page object file
485
-
486
- Create or update the file under the module `page-object-model/` folder.
487
-
488
- ### Step 3: Move selectors
489
-
490
- Create or update a `dom-selectors/` file and export selectors only when needed. Follow the conventions in [DomSelectors_README.md](./DomSelectors_README.md).
491
-
492
- ### Step 4: Add clear action methods
493
-
494
- Implement or extend focused, business-friendly methods like:
495
-
496
- - `createTicket()`
497
- - `updateTicket()`
498
- - `addComment()`
499
- - `deleteComment()`
500
-
501
- ### Step 5: Export from the barrel index
502
-
503
- Add the new POM as a named export in `page-object-model/index.js`.
504
-
505
- ### Step 6: Use the page object from step files
506
-
507
- Import the page object from the module root barrel (`'../index'`) and call its methods.
508
-
509
- ### Step 7: Add assertions outside the POM
510
-
511
- Write assertions in `assertions/` or in the step definition file, not in the page object.
512
-
513
- ## Naming Recommendations
514
-
515
- ### File names
516
-
517
- - use descriptive PascalCase names for POM files
518
- - use helper wrapper files only when they simplify usage
519
-
520
- ### Factory function names
521
-
522
- - match the file name
523
- - keep names domain-specific
524
-
525
- Examples:
526
-
527
- - `TicketListPage`
528
- - `TicketDetailsPage`
529
- - `TicketCommentsPanel`
530
-
531
- ### Method names
532
-
533
- Use verbs that describe intent:
534
-
535
- - `visitSetupHomePage()`
536
- - `selectModule(moduleName)`
537
- - `openTicket(subject)`
538
- - `updateComment(oldText, newText)`
539
-
540
- ## Best Practices
541
-
542
- Use this section for quality guidance that improves readability and maintainability.
543
-
544
- - Keep page objects readable and small.
545
- - Prefer business-level actions over raw clicks in step files.
546
- - Let POM methods return locators, values, or state to callers instead of combining too many responsibilities.
547
- - Keep selectors centralized when they are shared.
548
- - Prefer unique ids, record ids, or returned UI identifiers over visual-order-based selection.
549
- - Use one module-local POM structure instead of a large global one.
550
- - Reuse existing page objects before creating new ones.
551
- - Add comments only when logic is not obvious.
552
-
553
- ## Validation Checklist
554
-
555
- Before considering a POM ready, verify that:
556
-
557
- 1. The file is placed under the correct module.
558
- 2. The factory function name matches the page responsibility.
559
- 3. Selectors are stable and readable — verified against [DomSelectors_README.md](./DomSelectors_README.md).
560
- 4. Repeated locator logic is extracted into getters.
561
- 5. Child elements are scoped under a stable parent container when needed.
562
- 6. Reusable workflows are inside page methods.
563
- 7. Step files use the page object instead of raw locator-heavy logic.
564
- 8. Assertions are not placed in the page object by default.
565
- 9. Shared assertions are extracted into `assertions/` when needed.
566
- 10. No business logic is placed in the page object.
567
- 11. No arbitrary waits are used.
568
- 12. Raw hardcoded locators are not embedded repeatedly in action methods.
569
- 13. DOM readiness is checked before each significant interaction.
570
- 14. The implementation is safe for random scenario execution order.
571
- 15. Scenario data is not stored in mutable global or environment state.
572
- 16. Selectors are not duplicated — see [DomSelectors_README.md](./DomSelectors_README.md).
573
- 17. Unused variables, imports, methods, and selectors are removed.
574
- 18. POM file names follow PascalCase naming convention.
575
- 19. Selector files remain pure — see [DomSelectors_README.md](./DomSelectors_README.md).
576
- 20. Assertion files remain validation-only.
577
- 21. Record selection does not depend on `.first()`, `.last()`, or visual order when a stable identifier is available.
578
- 22. POMs are defined as factory functions that destructure only the context properties they need.
579
- 23. All used context properties (`page`, `i18N`, `getMetaInfo`, `cacheLayer`, `context`, `executionContext`, `actorContext`) are accessed from the factory closure and not passed to individual methods.
580
- 24. All user-facing strings are resolved through `i18N` and not hardcoded.
581
- 25. Each module has an `index.js` barrel export in `page-object-model/`.
582
- 26. Step files import POMs from the barrel index, not from deep file paths.
583
-
584
-
585
- For selector file references, see [DomSelectors_README.md](./DomSelectors_README.md).
586
-
587
- ## Quick Checklist
588
-
589
- Use this section as a short working summary while creating or updating a POM.
590
-
591
- - Reuse an existing module POM when possible.
592
- - Keep the file in the correct `page-object-model/` folder.
593
- - Use one focused factory function per page or component.
594
- - Add reusable getters and clear action methods.
595
- - Keep validations outside the POM and step files thin.
596
- - Avoid hard waits and confirm DOM readiness before actions.
597
- - Remove unused or duplicated code before finishing.
598
- - Follow consistent PascalCase file naming for POMs.
599
- - Define POMs as factory functions that destructure only the context properties they need (`page`, `i18N`, `getMetaInfo`, `cacheLayer`, `context`, `executionContext`, `actorContext`).
600
- - Access all context properties from the factory closure, not as method-level parameters.
601
- - Use `i18N` for all user-facing strings in locators; never hardcode translated text.
602
- - Export all module POMs via `page-object-model/index.js` and import from the module root barrel (`'../index'`) in step files.