@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,653 +0,0 @@
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 DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "servers": {
3
- "playwright-test": {
4
- "type": "stdio",
5
- "command": "npx",
6
- "args": ["playwright", "run-test-mcp-server"]
7
- }
8
- }
9
- }