@zohodesk/testinglibrary 0.0.41-n20-experimental → 0.0.42-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.
- package/.github/agents/StepDefinitionValidator.agent.md +49 -0
- package/.github/agents/assertion-generator.agent.md +37 -0
- package/.github/agents/pom-generator.agent.md +50 -0
- package/.github/agents/selector-generator.agent.md +47 -0
- package/.github/agents/selector-recorder.agent.md +99 -0
- package/.github/agents/step-definition-generator.agent.md +55 -0
- package/.github/agents/steps-generation-orchestrator.agent.md +75 -0
- package/.github/copilot-instructions.md +95 -0
- package/.github/instructions/assertions.instructions.md +36 -0
- package/.github/instructions/dom-selectors.instructions.md +33 -0
- package/.github/instructions/pom-files.instructions.md +48 -0
- package/.github/instructions/step-definitions.instructions.md +44 -0
- package/.github/styleGuides/DomSelectors_README.md +72 -0
- package/.github/styleGuides/POM_README.md +602 -0
- package/.github/styleGuides/Steps_README.md +653 -0
- package/.vscode/mcp.json +1 -1
- package/build/core/playwright/constants/reporterConstants.js +0 -1
- package/build/core/playwright/report-generator.js +42 -0
- package/build/lib/cli.js +13 -29
- package/build/utils/commonUtils.js +3 -0
- package/npm-shrinkwrap.json +169 -106
- package/package.json +7 -8
- package/build/core/playwright/reporter/PlaywrightReporter.js +0 -44
- package/build/core/playwright/reporter/UnitReporter.js +0 -27
|
@@ -0,0 +1,602 @@
|
|
|
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.
|