@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,653 @@
|
|
|
1
|
+
# Step Definitions Style Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Imports & BDD Setup](#1-imports--bdd-setup)
|
|
6
|
+
2. [Step Definition Syntax](#2-step-definition-syntax)
|
|
7
|
+
3. [Context Destructuring](#3-context-destructuring)
|
|
8
|
+
4. [Assertions](#4-assertions)
|
|
9
|
+
5. [Test Data Management](#5-test-data-management)
|
|
10
|
+
6. [CacheLayer & Data Sharing](#6-cachelayer--data-sharing)
|
|
11
|
+
7. [Internationalization (i18N)](#7-internationalization-i18n)
|
|
12
|
+
8. [Multi-Actor & Hooks](#8-multi-actor--hooks)
|
|
13
|
+
9. [Helper Functions & Utilities](#9-helper-functions--utilities)
|
|
14
|
+
10. [Barrel Exports (index.js)](#10-barrel-exports-indexjs)
|
|
15
|
+
11. [Anti-Patterns to Avoid](#11-anti-patterns-to-avoid)
|
|
16
|
+
12. [Checklist for New Step Definitions](#12-checklist-for-new-step-definitions)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### Naming Conventions
|
|
21
|
+
|
|
22
|
+
| Artifact | Convention | Examples |
|
|
23
|
+
|----------|-----------|----------|
|
|
24
|
+
| **Module folders** | PascalCase | `Ticket/`, `CustomModule/`, `UserManagement/` |
|
|
25
|
+
| **Spec files** | PascalCase | `SetupNavigation.spec.js`, `ContractCRUD.spec.js` |
|
|
26
|
+
| **Feature files** | PascalCase, matching spec | `SupervisorCRUD.feature` |
|
|
27
|
+
| **Assertion files** | PascalCase + `Assertions` suffix | `SetupAssertions.js`, `BusinessHoursAssertion.js` |
|
|
28
|
+
| **Selector files** | PascalCase + `Selectors` suffix | `LayoutruleSelectors.js`, `DashboardSelectors.js` |
|
|
29
|
+
| **Utility files** | PascalCase + `Utils` suffix | `TicketUtils.js`, `BusinessHourHelper.js` |
|
|
30
|
+
| **Index files** | Lowercase | `index.js` |
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Logic Placement Rules
|
|
34
|
+
|
|
35
|
+
When deciding where to place code, strictly follow these data flow rules:
|
|
36
|
+
- **If it is a locator string** → `dom-selectors/` (pure constants and builder helpers).
|
|
37
|
+
- **If it clicks, fills, or reads UI** → `page-object-model/` (factory functions using selectors).
|
|
38
|
+
- **If it wires a Gherkin step to a POM call** → `steps/` (thin Given/When/Then wiring).
|
|
39
|
+
- **If it validates an outcome** → `assertions/` (if reused across 2+ spec files) or inline in the step itself.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 1. Imports & BDD Setup
|
|
44
|
+
|
|
45
|
+
### Standard Import Block
|
|
46
|
+
|
|
47
|
+
Every spec.js file **must** begin with the BDD framework import and destructuring:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
import { createBdd } from '@zohodesk/testinglibrary';
|
|
51
|
+
|
|
52
|
+
const { Given, When, Then } = createBdd();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- Import `And` only when needed: `const { Given, When, Then, And } = createBdd();`
|
|
56
|
+
|
|
57
|
+
### Import Order (Recommended)
|
|
58
|
+
|
|
59
|
+
Follow this import grouping, separated by blank lines:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
// 1. Framework imports
|
|
63
|
+
import { createBdd, expect } from '@zohodesk/testinglibrary';
|
|
64
|
+
|
|
65
|
+
// 2. Cross-module POM / shared imports
|
|
66
|
+
import { FormActions } from '../../CommonActions';
|
|
67
|
+
import { getModuleObj } from '../../Ticket/utils/ticketUtils';
|
|
68
|
+
|
|
69
|
+
// 3. Module-local POM imports (via barrel export)
|
|
70
|
+
import { ContractsDetailView, ContractFormPOM, ContractListPOM } from '../index';
|
|
71
|
+
|
|
72
|
+
// 4. Assertions
|
|
73
|
+
import Assertions from '../../_assertions/Assertions';
|
|
74
|
+
import ContractAssertions from '../assertions/ContractAssertions';
|
|
75
|
+
|
|
76
|
+
// 5. Constants (i18N keys, test data) — NOT selectors (selectors belong in POM only)
|
|
77
|
+
import { I18N_KEYS } from '../constants/contractConstants';
|
|
78
|
+
|
|
79
|
+
// 6. BDD destructuring (always last before step definitions)
|
|
80
|
+
const { Given, When, Then } = createBdd();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 2. Step Definition Syntax
|
|
86
|
+
|
|
87
|
+
### Given / When / Then Separation
|
|
88
|
+
|
|
89
|
+
Maintain strict semantic separation:
|
|
90
|
+
|
|
91
|
+
| Keyword | Purpose | Example |
|
|
92
|
+
|---------|---------|---------|
|
|
93
|
+
| **Given** | Set up preconditions & navigation | `Given('user is in setup home page', ...)` |
|
|
94
|
+
| **When** | Perform user actions | `When('the user clicks {string} button', ...)` |
|
|
95
|
+
| **Then** | Assert expected outcomes | `Then('the page should display {string}', ...)` |
|
|
96
|
+
| **And** | Continue the preceding keyword | Chain additional Given or When actions or Then assertions |
|
|
97
|
+
|
|
98
|
+
### Step Name Guidelines
|
|
99
|
+
|
|
100
|
+
- Use `{string}` placeholders for dynamic values.
|
|
101
|
+
- Keep steps **reusable** — avoid module-specific hardcoding in the step name.
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
// ✅ Good — reusable and descriptive
|
|
105
|
+
When('the user update the {string} field value to {string}', async ({ page }, field, value) => { ... });
|
|
106
|
+
|
|
107
|
+
// ❌ Bad — too specific, not reusable
|
|
108
|
+
When('update ticket subject', async ({ page }) => { ... });
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Parameter Patterns
|
|
112
|
+
|
|
113
|
+
**Single string parameter:**
|
|
114
|
+
```javascript
|
|
115
|
+
When('the user access the {string} contact', async ({ page, i18N }, contact) => {
|
|
116
|
+
const ticketDv = await ticketDetailViewPOM({ page, i18N });
|
|
117
|
+
await ticketDv.goToContact(contact);
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Multiple string parameters:**
|
|
122
|
+
```javascript
|
|
123
|
+
Then('the {string} {string} should be updated as {string}',
|
|
124
|
+
async ({ page, i18N }, moduleName, recordName, statusName) => {
|
|
125
|
+
const listViewPom = ListView({ page, i18N });
|
|
126
|
+
const statusElem = await listViewPom.getStatusElem(recordName, statusName, moduleName);
|
|
127
|
+
await Assertions.isElementVisible(statusElem);
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**DataTable parameter:**
|
|
132
|
+
```javascript
|
|
133
|
+
When('the user create a contract with below details',
|
|
134
|
+
async ({ page, i18N, getMetaInfo, cacheLayer }, dataTable) => {
|
|
135
|
+
const data = dataTable.hashes(); // Array of row objects
|
|
136
|
+
const form = await FormActions({ page, i18N, getMetaInfo, cacheLayer });
|
|
137
|
+
await form.fillForm(data[0]);
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**String + DataTable combined:**
|
|
142
|
+
```javascript
|
|
143
|
+
When('the user create a contract for {string} department with below details',
|
|
144
|
+
async ({ page, i18N, getMetaInfo, cacheLayer }, departmentName, dataTable) => {
|
|
145
|
+
const data = dataTable.hashes();
|
|
146
|
+
// ...
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### No Branching Logic in Step Definitions
|
|
151
|
+
|
|
152
|
+
**A step definition must do exactly one thing — no `if/else`, `switch`, or ternary expressions** inside a step body. If conditional logic is needed, it belongs in the POM method; steps remain thin orchestrators.
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// ❌ Bad — branching inside a step
|
|
156
|
+
When('the user saves the {string} form', async ({ page }, formType) => {
|
|
157
|
+
if (formType === 'ticket') {
|
|
158
|
+
await page.getByTestId('ticketSaveBtn').click();
|
|
159
|
+
} else {
|
|
160
|
+
await page.getByTestId('contactSaveBtn').click();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ✅ Good — POM handles conditional logic internally
|
|
165
|
+
When('the user saves the {string} form', async ({ page, i18N }, formType) => {
|
|
166
|
+
const form = FormPOM({ page, i18N });
|
|
167
|
+
await form.save(formType);
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 3. Context Destructuring
|
|
174
|
+
|
|
175
|
+
### Available Context Objects
|
|
176
|
+
|
|
177
|
+
Every step receives a context object as its first argument. Destructure **only what you need**:
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
// Full set of available context properties:
|
|
181
|
+
async ({ page, i18N, getMetaInfo, cacheLayer, context, executionContext, actorContext }, ...params) => {}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Property | Type | Purpose |
|
|
185
|
+
|----------|------|---------|
|
|
186
|
+
| `page` | Playwright Page | Primary browser page interaction |
|
|
187
|
+
| `i18N` | Function (async) | Translate i18N keys to localized strings |
|
|
188
|
+
| `getMetaInfo` | Function (async) | Get current department, org info, actor info |
|
|
189
|
+
| `cacheLayer` | Object | Share data between steps within a scenario |
|
|
190
|
+
| `context` | Playwright BrowserContext | Multi-tab or multi-context operations |
|
|
191
|
+
| `executionContext` | Object | Test execution info (edition, profile, actorInfo) |
|
|
192
|
+
| `actorContext` | Object | Multi-actor support (switching user profiles) |
|
|
193
|
+
|
|
194
|
+
### Usage Examples for Less Common Context Properties
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// executionContext — check the current edition
|
|
198
|
+
When('the user checks edition-specific feature', async ({ page, executionContext }) => {
|
|
199
|
+
const { edition } = executionContext;
|
|
200
|
+
const featurePom = FeaturePOM({ page });
|
|
201
|
+
await featurePom.openEditionFeature(edition);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// actorContext — get the current actor's name
|
|
205
|
+
Then('the comment author should be the current user', async ({ page, actorContext }) => {
|
|
206
|
+
const { name } = actorContext;
|
|
207
|
+
const authorElem = page.getByText(name);
|
|
208
|
+
await Assertions.isElementVisible(authorElem);
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Destructure Only What You Need
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// ✅ Good — only destructure used properties
|
|
216
|
+
When('user clicks save', async ({ page }) => { ... });
|
|
217
|
+
|
|
218
|
+
When('user creates contract', async ({ page, i18N, getMetaInfo, cacheLayer }, dataTable) => { ... });
|
|
219
|
+
|
|
220
|
+
// ❌ Bad — destructuring unused properties
|
|
221
|
+
When('user clicks save', async ({ page, i18N, getMetaInfo, cacheLayer, context, executionContext }) => { ... });
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 4. Assertions
|
|
227
|
+
|
|
228
|
+
> Assertions must never be placed inside POM files. For the full decision guide on **when to use `assertions/` vs inline in the step file**, see [POM_README.md Rule 5](./POM_README.md#rule-5-keep-assertions-outside-pom).
|
|
229
|
+
|
|
230
|
+
Quick rule: keep assertions inline in the step unless the same locator-read + assertion combination appears in **2+ different spec files** — then extract to `assertions/`.
|
|
231
|
+
|
|
232
|
+
### Use the Centralized `Assertions` Library
|
|
233
|
+
|
|
234
|
+
Always import from `_assertions/Assertions.js`. **Do not use raw `expect()` directly** unless there is no matching assertion method.
|
|
235
|
+
|
|
236
|
+
### Then Steps Are Assertions Only
|
|
237
|
+
|
|
238
|
+
**`Then` steps must never perform actions.** No clicks, form fills, navigation, or any mutations. Only assertions belong in a `Then` block.
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// ❌ Bad — action inside Then
|
|
242
|
+
Then('the record is saved', async ({ page }) => {
|
|
243
|
+
await page.getByTestId('saveBtn').click(); // ← action in Then!
|
|
244
|
+
await Assertions.isElementVisible(page.getByText('Saved'));
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ✅ Good — Then only asserts
|
|
248
|
+
Then('the record should be saved', async ({ page, i18N }) => {
|
|
249
|
+
const form = FormPOM({ page, i18N });
|
|
250
|
+
const confirmation = await form.getConfirmationElement();
|
|
251
|
+
await Assertions.isElementVisible(confirmation);
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
import Assertions from '../../_assertions/Assertions';
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Available Assertion Methods
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
// Visibility
|
|
263
|
+
await Assertions.isElementVisible(element);
|
|
264
|
+
await Assertions.isElementNotVisible(element);
|
|
265
|
+
|
|
266
|
+
// Text / Value
|
|
267
|
+
await Assertions.isElementHaveText(element, 'Expected text');
|
|
268
|
+
await Assertions.isElementContainText(element, 'substring');
|
|
269
|
+
await Assertions.isElementHaveValue(element, 'value');
|
|
270
|
+
|
|
271
|
+
// Attributes
|
|
272
|
+
await Assertions.isElementHaveAttribute(element, { attribute: 'aria-checked', value: 'true' });
|
|
273
|
+
await Assertions.isElementHaveClass(element, 'active');
|
|
274
|
+
|
|
275
|
+
// State
|
|
276
|
+
await Assertions.isElementDisabled(element);
|
|
277
|
+
await Assertions.isElementEnabled(element);
|
|
278
|
+
await Assertions.isElementChecked(element);
|
|
279
|
+
|
|
280
|
+
// Equality
|
|
281
|
+
await Assertions.expectedToBe(actual, expected);
|
|
282
|
+
|
|
283
|
+
// URL
|
|
284
|
+
await Assertions.expectedURL(page, new RegExp(urlPattern));
|
|
285
|
+
|
|
286
|
+
// Array Comparisons
|
|
287
|
+
Assertions.verifySameElementsInOrder(actualArray, expectedArray);
|
|
288
|
+
Assertions.verifySameElementsIgnoringOrder(actualArray, expectedArray);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Playwright Auto-Waiting & Assertion Stability
|
|
292
|
+
|
|
293
|
+
Playwright's built-in `expect()` assertions have **built-in auto-waiting** — they retry until the condition is met or a timeout expires. This makes them inherently more stable than plain JavaScript comparison methods.
|
|
294
|
+
|
|
295
|
+
**Avoid plain JavaScript assertion methods for UI validations:**
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
// ❌ Bad — toEqual() is a plain JS comparison with no auto-wait or retry
|
|
299
|
+
const text = await element.textContent();
|
|
300
|
+
expect(text).toEqual('Open'); // Flaky if the element updates asynchronously
|
|
301
|
+
|
|
302
|
+
// ✅ Good — Playwright auto-waiting wrapped in our central library
|
|
303
|
+
await Assertions.isElementVisible(element);
|
|
304
|
+
await Assertions.isElementHaveText(element, 'Open');
|
|
305
|
+
// Use native expect ONLY if a wrapper like toHaveCount doesn't exist yet
|
|
306
|
+
await expect(element).toHaveCount(3);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### When to Use Direct `expect()`
|
|
310
|
+
|
|
311
|
+
Only when the centralized `Assertions` library doesn't cover your case:
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
// Acceptable — boolean comparison not covered by Assertions
|
|
315
|
+
const isListed = await initialPage.isViewNameListed(viewName);
|
|
316
|
+
expect(isListed).toBe(true);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## 5. Test Data Management
|
|
322
|
+
|
|
323
|
+
- Use **DataTable** parameters in feature files for structured test data.
|
|
324
|
+
- Use `data-generator/` files for complex or reusable test data sets.
|
|
325
|
+
- Never hardcode test values (record names, IDs, emails) directly in step definitions — pass them through feature file parameters or data generators.
|
|
326
|
+
- Keep test data portable — do not depend on data that exists only in a specific environment.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 6. CacheLayer & Data Sharing
|
|
331
|
+
|
|
332
|
+
Use `cacheLayer` to share data between steps **within the same scenario**:
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
// Step 1: Store data
|
|
336
|
+
When('the user creates a ticket', async ({ page, cacheLayer }) => {
|
|
337
|
+
const ticketId = await createTicket(page);
|
|
338
|
+
await cacheLayer.set('createdTicketId', ticketId);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Step 2: Retrieve data
|
|
342
|
+
Then('the ticket should appear in detail view', async ({ page, cacheLayer }) => {
|
|
343
|
+
const ticketId = await cacheLayer.get('createdTicketId');
|
|
344
|
+
await page.goto(`/tickets/${ticketId}`);
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Rules
|
|
349
|
+
|
|
350
|
+
- Use **descriptive keys** for cached values.
|
|
351
|
+
- Cache only **primitive values or simple objects**.
|
|
352
|
+
- Pass `cacheLayer` to POMs when they need access to shared data.
|
|
353
|
+
|
|
354
|
+
### No Global or Environment Variables for Test Data
|
|
355
|
+
|
|
356
|
+
**Never store test-specific runtime data in `process.env`, module-level variables, or any global scope.** Use `cacheLayer` for scenario-scoped sharing.
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
// ❌ Bad — module-level global (leaks between scenarios)
|
|
360
|
+
let createdTicketId;
|
|
361
|
+
When('the user creates a ticket', async ({ page }) => {
|
|
362
|
+
createdTicketId = await createTicket(page);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// ❌ Bad — environment variable mutation (persists globally)
|
|
366
|
+
When('the user creates a ticket', async ({ page }) => {
|
|
367
|
+
process.env.TICKET_ID = await createTicket(page);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// ✅ Good — cacheLayer is isolated to the current scenario
|
|
371
|
+
When('the user creates a ticket', async ({ page, cacheLayer }) => {
|
|
372
|
+
const ticketId = await createTicket(page);
|
|
373
|
+
await cacheLayer.set('ticketId', ticketId);
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## 7. Internationalization (i18N)
|
|
380
|
+
|
|
381
|
+
> Rule definition: all user-facing strings must use `i18N` — see [POM_README.md Rule 21](./POM_README.md#rule-21-use-i18n-for-all-user-facing-strings).
|
|
382
|
+
|
|
383
|
+
This section covers `i18N` usage patterns specific to step definitions.
|
|
384
|
+
|
|
385
|
+
### Usage in Steps
|
|
386
|
+
|
|
387
|
+
`i18N` is an **async function** — always `await` it:
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
When('user verifies the label', async ({ page, i18N }) => {
|
|
391
|
+
const labelText = await i18N('desk.tickets.status.open');
|
|
392
|
+
const element = page.getByText(labelText);
|
|
393
|
+
await Assertions.isElementVisible(element);
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Usage via Constants
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
// Define constants
|
|
401
|
+
const I18N_CONSTANTS = {
|
|
402
|
+
ACTIVE_FILTER: 'desk.filter.active',
|
|
403
|
+
SAVE_BUTTON: 'desk.common.save'
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// Use in steps
|
|
407
|
+
const saveText = await i18N(I18N_CONSTANTS.SAVE_BUTTON);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Rules
|
|
411
|
+
|
|
412
|
+
- **Always use `i18N` for user-facing strings** — never hardcode English text.
|
|
413
|
+
- Pass `i18N` to POMs that need to match labels or button text.
|
|
414
|
+
- Group `i18N` keys in **constants files** for maintainability.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## 8. Multi-Actor & Hooks
|
|
419
|
+
|
|
420
|
+
### Switching Between Actors
|
|
421
|
+
|
|
422
|
+
The following step is **already built into the framework** — do **not** create a new step definition for it. When this step appears in a feature file, it is automatically resolved from the framework side:
|
|
423
|
+
|
|
424
|
+
```gherkin
|
|
425
|
+
Given access the "admin" profile page
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
> **No spec.js implementation needed.** This step is pre-registered in the shared test framework (`@zohodesk/testinglibrary`) and handles actor switching internally.
|
|
429
|
+
|
|
430
|
+
### Rules
|
|
431
|
+
|
|
432
|
+
- **Do not re-implement framework-provided steps** — if a step like `Given('access the {string} profile page')` already exists in the framework, reuse it as-is.
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## 9. Helper Functions & Utilities
|
|
437
|
+
|
|
438
|
+
### When to Extract a Helper### Routing "Helper" Logic
|
|
439
|
+
|
|
440
|
+
Do not create arbitrary "helper" functions inside step definition files. When logic becomes repetitive or complex, extract it strictly according to the framework layers:
|
|
441
|
+
|
|
442
|
+
* **UI Interaction Helpers (Waits, Clicks, Forms):** If multi-step UI logic or wait logic is reused across steps, extract it as a new action method inside the target **Page Object Model (POM)**.
|
|
443
|
+
|
|
444
|
+
* **Validation Helpers:** If a complex, multi-line validation is reused across 2+ different `.spec.js` files, extract it to the module's `assertions/` folder.
|
|
445
|
+
|
|
446
|
+
* **Pure Data Utilities:** Only create a standalone utility file (e.g., `utils/dateFormatter.js`) if the logic is **pure data manipulation** (e.g., math, string formatting, regex, generating random emails) and contains absolutely zero Playwright imports or DOM interactions.
|
|
447
|
+
|
|
448
|
+
```javascript
|
|
449
|
+
// ✅ Good — extracted to utils
|
|
450
|
+
export async function switchView(page, getMetaInfo, moduleName, viewName) {
|
|
451
|
+
const { currentDepartment, actorInfo } = await getMetaInfo();
|
|
452
|
+
const { orgName } = actorInfo;
|
|
453
|
+
const sanitizedViewName = getSanitizedName(viewName);
|
|
454
|
+
const sanitizedModuleName = getSanitizedName(moduleName);
|
|
455
|
+
await page.goto(`${process.env.domain}/${orgName}/${sanitizedDeptName}/${sanitizedModuleName}/list/${sanitizedViewName}${process.env.devUrl}`);
|
|
456
|
+
const topbar = await page.getByTestId(COMMON_SELECTORS.TOP_BAND_HEADER);
|
|
457
|
+
await topbar.waitFor();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Helper for text sanitization
|
|
461
|
+
export const getSanitizedName = (name = '') => {
|
|
462
|
+
return name.replace(/\s/g, '-').replace(/_/g, '').toLowerCase();
|
|
463
|
+
};
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Waiting Patterns
|
|
467
|
+
|
|
468
|
+
```javascript
|
|
469
|
+
// ✅ Preferred — wait for page load
|
|
470
|
+
await page.waitForLoadState('load');
|
|
471
|
+
await page.waitForLoadState('domcontentloaded');
|
|
472
|
+
|
|
473
|
+
// ✅ Preferred — wait for a specific element (strongest validation)
|
|
474
|
+
await element.waitFor();
|
|
475
|
+
await element.waitFor({ state: 'hidden' });
|
|
476
|
+
|
|
477
|
+
// ✅ Wait for specific API response
|
|
478
|
+
await page.waitForResponse(
|
|
479
|
+
res => res.url().includes('tickets') && res.status() === 200
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
// ⚠️ Use with caution — networkidle (see warning below)
|
|
483
|
+
await page.waitForLoadState('networkidle');
|
|
484
|
+
|
|
485
|
+
// ❌ Never use fixed waits
|
|
486
|
+
await page.waitForTimeout(1000);
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### ⚠️ Avoid `networkidle` for Element Verification
|
|
490
|
+
|
|
491
|
+
**When to use `networkidle`:**
|
|
492
|
+
- It's **not** that `networkidle` should never be used — but it should **only** be applied where it is truly required (e.g., ensuring all resources are fully downloaded before taking a screenshot or exporting a PDF).
|
|
493
|
+
|
|
494
|
+
- **Never** use it as a substitute for element-based validation.
|
|
495
|
+
|
|
496
|
+
**What to use instead:**
|
|
497
|
+
```javascript
|
|
498
|
+
// ✅ Good — element-based validation (strongest and most reliable)
|
|
499
|
+
await element.waitFor();
|
|
500
|
+
await Assertions.isElementVisible(element);
|
|
501
|
+
|
|
502
|
+
// ✅ Good — wait for page load events
|
|
503
|
+
await page.waitForLoadState('load');
|
|
504
|
+
await page.waitForLoadState('domcontentloaded');
|
|
505
|
+
|
|
506
|
+
// ❌ Bad — using networkidle to verify element presence
|
|
507
|
+
await page.waitForLoadState('networkidle');
|
|
508
|
+
await Assertions.isElementVisible(element); // flaky!
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### DOM Readiness Validation Before Actions
|
|
512
|
+
|
|
513
|
+
**Always confirm an element is present and stable before performing an action on it.** Never interact with an element immediately after navigation without waiting.
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
// ❌ Bad — no DOM readiness check
|
|
517
|
+
async clickStatusDropdown() {
|
|
518
|
+
await this.page.getByTestId('statusDropdown').click(); // may not exist yet
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
// ✅ Good — wait for element before acting
|
|
522
|
+
async clickStatusDropdown() {
|
|
523
|
+
const dropdown = this.page.getByTestId('statusDropdown');
|
|
524
|
+
await dropdown.waitFor(); // confirms DOM is ready
|
|
525
|
+
await dropdown.click();
|
|
526
|
+
},
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Rules
|
|
530
|
+
|
|
531
|
+
- **Validate DOM readiness with `waitFor()`** before every action in POM methods — see [POM_README.md Rule 13](./POM_README.md#rule-13-validate-dom-readiness-before-any-action).
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## 10. Barrel Exports (index.js)
|
|
536
|
+
|
|
537
|
+
Each module must have a root `index.js` that exports all public POMs, assertions, and utilities:
|
|
538
|
+
|
|
539
|
+
> **Note:** This is the **module root** barrel (`modules/<ModuleName>/index.js`). There is also a **POM-internal** barrel (`page-object-model/index.js`) that re-exports only POM factory functions — see [POM_README.md Rule 20](./POM_README.md#rule-20-use-barrel-exports-for-local-module-poms). The module root barrel consumes the POM barrel and adds assertions and utilities.
|
|
540
|
+
|
|
541
|
+
```javascript
|
|
542
|
+
// modules/Contract/index.js
|
|
543
|
+
export { default as ContractsDetailView } from './page-object-model/ContractsDetailView';
|
|
544
|
+
export { default as ContractFormPOM } from './page-object-model/ContractFormPOM';
|
|
545
|
+
export { default as ContractListPOM } from './page-object-model/ContractListPOM';
|
|
546
|
+
|
|
547
|
+
export { default as ContractAssertions } from './assertions/ContractAssertions';
|
|
548
|
+
export { getContractData } from './utils/contractUtils';
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Rules
|
|
552
|
+
|
|
553
|
+
- **One `index.js` per module** — re-export everything needed externally.
|
|
554
|
+
- **Import from `../index`** in step definitions, not from deep paths.
|
|
555
|
+
- Keep internal files (selectors, helpers) out of barrel exports unless used cross-module.
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## 11. Anti-Patterns to Avoid
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
### ❌ Business Logic in Step Definitions
|
|
563
|
+
|
|
564
|
+
```javascript
|
|
565
|
+
// ❌ Bad — loop logic belongs in POM
|
|
566
|
+
Then('all rules should be listed', async ({ page }, dataTable) => {
|
|
567
|
+
const rules = dataTable.hashes();
|
|
568
|
+
for (const { ruleName } of rules) {
|
|
569
|
+
const elem = page.getByText(ruleName);
|
|
570
|
+
await Assertions.isElementVisible(elem);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// ✅ Good — delegate to POM
|
|
575
|
+
Then('all rules should be listed', async ({ page }, dataTable) => {
|
|
576
|
+
const rules = dataTable.hashes();
|
|
577
|
+
const ruleList = RuleListPOM({ page });
|
|
578
|
+
await ruleList.verifyRulesListed(rules);
|
|
579
|
+
});
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### ❌ Unused Context Destructuring
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
// ❌ Bad
|
|
586
|
+
When('step', async ({ page, i18N, getMetaInfo, cacheLayer, context, executionContext }) => {
|
|
587
|
+
await page.click('#btn');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// ✅ Good
|
|
591
|
+
When('step', async ({ page }) => {
|
|
592
|
+
const form = FormPOM({ page });
|
|
593
|
+
await form.clickBtn();
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### ❌ Missing Barrel Exports
|
|
598
|
+
|
|
599
|
+
```javascript
|
|
600
|
+
// ❌ Bad — deep path imports
|
|
601
|
+
import ContractFormPOM from '../page-object-model/ContractFormPOM';
|
|
602
|
+
import ContractListPOM from '../page-object-model/ContractListPOM';
|
|
603
|
+
|
|
604
|
+
// ✅ Good — via index.js
|
|
605
|
+
import { ContractFormPOM, ContractListPOM } from '../index';
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### ❌ Hardcoded IDs or Locators in Step Definitions
|
|
609
|
+
|
|
610
|
+
All locators must live in the selector file and be accessed through POM methods — never inline raw `data-testid` strings or CSS selectors in a step:
|
|
611
|
+
|
|
612
|
+
```javascript
|
|
613
|
+
// ❌ Bad — raw locator inline in step
|
|
614
|
+
When('the user opens the ticket', async ({ page }) => {
|
|
615
|
+
await page.locator('[data-testid="ticketDetail-12345"]').click();
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// ✅ Good — locators live in the selectors file, accessed via POM
|
|
619
|
+
When('the user opens the ticket', async ({ page, i18N }) => {
|
|
620
|
+
const list = TicketListPOM({ page, i18N });
|
|
621
|
+
await list.openFirstTicket();
|
|
622
|
+
});
|
|
623
|
+
```
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## 12. Checklist for New Step Definitions
|
|
627
|
+
|
|
628
|
+
Use this checklist when creating a new spec.js file:
|
|
629
|
+
|
|
630
|
+
- [ ] **File placed** in `modules/<ModuleName>/steps/` directory
|
|
631
|
+
- [ ] **File named** in PascalCase with `.spec.js` extension
|
|
632
|
+
- [ ] **Imports** follow the standard order (framework → cross-module → local → assertions → selectors → BDD destructuring)
|
|
633
|
+
- [ ] **`createBdd()`** called and destructured for only needed keywords
|
|
634
|
+
- [ ] If the step already exists → reuse it instead of creating a new one. If the step does not exist → create new steps.
|
|
635
|
+
- [ ] **Given/When/Then** semantically separated (setup → action → assertion)
|
|
636
|
+
- [ ] **Step names** are descriptive Gherkin English with `{string}` parameters
|
|
637
|
+
- [ ] **Context destructured** with only used properties
|
|
638
|
+
- [ ] **All DOM interactions** delegated to POMs — no raw selectors in steps
|
|
639
|
+
- [ ] **Assertions** use the centralized `Assertions` library
|
|
640
|
+
- [ ] **Test data** managed via utility functions or DataTable, not hardcoded
|
|
641
|
+
- [ ] **i18N used** for all user-facing strings
|
|
642
|
+
- [ ] **No hardcoded timeouts** — use explicit wait conditions
|
|
643
|
+
- [ ] **Corresponding feature file** exists with matching name and proper tags
|
|
644
|
+
- [ ] **Module `index.js`** updated if new POMs or exports are added
|
|
645
|
+
- [ ] **Helper functions** extracted for reused logic, placed in `utils/`
|
|
646
|
+
- [ ] **No branching logic** (`if/else`, `switch`, ternary) inside any step definition — move to POM
|
|
647
|
+
- [ ] **`Then` steps contain only assertions** — no clicks, fills, or navigation
|
|
648
|
+
- [ ] **Scenarios are independent** — can run in any order without relying on shared state
|
|
649
|
+
- [ ] **No global variables or `process.env` mutations** for test data — use `cacheLayer`
|
|
650
|
+
- [ ] **No hardcoded locators or IDs** in step definitions — all DOM access via POM methods
|
|
651
|
+
- [ ] **DOM readiness verified** with `waitFor()` before performing actions in POM methods
|
|
652
|
+
|
|
653
|
+
---
|
package/.vscode/mcp.json
CHANGED
|
@@ -11,6 +11,5 @@ const stage = (0, _ConfigurationHelper.getRunStage)();
|
|
|
11
11
|
class ReporterConstants {
|
|
12
12
|
static DEFAULT_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/${stage}/test-results/playwright-test-results.json`;
|
|
13
13
|
static LAST_RUN_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/${stage}/test-results/.last-run.json`;
|
|
14
|
-
static DEFAULT_UNIT_TEST_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/unit-test/unit_reports/report.html`;
|
|
15
14
|
}
|
|
16
15
|
exports.default = ReporterConstants;
|