0
# Page Object Model
1
2
Structured page object pattern for organizing elements, sections, and custom commands into reusable, maintainable test components.
3
4
## Capabilities
5
6
### Page Object Definition
7
8
Define page objects with elements, sections, and custom commands for organized test structure.
9
10
```javascript { .api }
11
/**
12
* Page object definition interface
13
*/
14
interface PageObjectModel {
15
// Page URL (string or function)
16
url: string | (() => string);
17
18
// Element definitions
19
elements: Record<string, ElementProperties>;
20
21
// Section definitions for nested components
22
sections: Record<string, SectionProperties>;
23
24
// Custom command definitions
25
commands: Record<string, Function>;
26
27
// Custom properties
28
props?: Record<string, any> | (() => Record<string, any>);
29
}
30
31
/**
32
* Element properties definition
33
*/
34
interface ElementProperties {
35
selector: string;
36
locateStrategy?: LocateStrategy;
37
index?: number;
38
abortOnFailure?: boolean;
39
timeout?: number;
40
retryInterval?: number;
41
suppressNotFoundErrors?: boolean;
42
}
43
44
/**
45
* Section properties definition
46
*/
47
interface SectionProperties {
48
selector: string;
49
locateStrategy?: LocateStrategy;
50
elements?: Record<string, ElementProperties>;
51
sections?: Record<string, SectionProperties>;
52
commands?: Record<string, Function>;
53
props?: Record<string, any> | (() => Record<string, any>);
54
}
55
56
/**
57
* Locate strategy options
58
*/
59
type LocateStrategy = 'css selector' | 'xpath' | 'tag name' | 'class name' | 'id' | 'name' | 'link text' | 'partial link text';
60
```
61
62
**Usage Examples:**
63
64
```javascript
65
// Define a login page object
66
const loginPage = {
67
url: 'https://example.com/login',
68
69
elements: {
70
usernameInput: '#username',
71
passwordInput: '#password',
72
submitButton: 'button[type="submit"]',
73
errorMessage: {
74
selector: '.error-message',
75
suppressNotFoundErrors: true
76
}
77
},
78
79
sections: {
80
header: {
81
selector: '#header',
82
elements: {
83
logo: '.logo',
84
userMenu: '.user-menu'
85
}
86
}
87
},
88
89
commands: {
90
login: function(username, password) {
91
return this
92
.setValue('@usernameInput', username)
93
.setValue('@passwordInput', password)
94
.click('@submitButton');
95
}
96
}
97
};
98
99
module.exports = loginPage;
100
```
101
102
### Enhanced Page Object Instance
103
104
Runtime page object instances with navigation and element access capabilities.
105
106
```javascript { .api }
107
/**
108
* Enhanced page object instance interface
109
*/
110
interface PageObjectInstance {
111
// Page object metadata
112
name: string;
113
114
// Nightwatch client and API access
115
client: NightwatchClient;
116
api: NightwatchAPI;
117
118
// Element access function
119
element: (selector: string) => Element;
120
121
// Element instances map (using @ syntax)
122
elements: Record<string, ElementInstance>;
123
124
// Section instances map
125
section: Record<string, SectionInstance>;
126
127
// Custom properties
128
props: Record<string, any>;
129
130
// Navigation method
131
navigate(url?: string): Promise<PageObjectInstance>;
132
}
133
134
/**
135
* Element instance with page object context
136
*/
137
interface ElementInstance {
138
selector: string;
139
locateStrategy: LocateStrategy;
140
141
// Standard element methods
142
click(): Promise<ElementInstance>;
143
setValue(value: string): Promise<ElementInstance>;
144
getText(): Promise<string>;
145
isVisible(): Promise<boolean>;
146
147
// Page object specific methods
148
waitForElementVisible(timeout?: number): Promise<ElementInstance>;
149
assert: {
150
visible(): Promise<ElementInstance>;
151
present(): Promise<ElementInstance>;
152
textContains(text: string): Promise<ElementInstance>;
153
};
154
}
155
156
/**
157
* Section instance with nested elements and sections
158
*/
159
interface SectionInstance {
160
selector: string;
161
elements: Record<string, ElementInstance>;
162
sections: Record<string, SectionInstance>;
163
164
// Section-specific commands
165
[commandName: string]: Function;
166
}
167
```
168
169
**Usage Examples:**
170
171
```javascript
172
// Use page object in test
173
module.exports = {
174
'Login Test': function(browser) {
175
const loginPage = browser.page.loginPage();
176
177
loginPage
178
.navigate()
179
.login('testuser', 'password123')
180
.assert.visible('@errorMessage'); // Using @ syntax for elements
181
}
182
};
183
```
184
185
### Page Object Navigation
186
187
Navigate to page URLs with support for dynamic URL generation.
188
189
```javascript { .api }
190
/**
191
* Navigate to page URL
192
* @param url - Optional URL override
193
* @returns Promise resolving with page object instance
194
*/
195
pageObject.navigate(url?: string): Promise<PageObjectInstance>;
196
197
/**
198
* Dynamic URL generation function
199
* @param params - URL parameters
200
* @returns Generated URL string
201
*/
202
type UrlFunction = (params?: Record<string, any>) => string;
203
```
204
205
**Usage Examples:**
206
207
```javascript
208
// Static URL page object
209
const homePage = {
210
url: 'https://example.com',
211
elements: {
212
welcomeMessage: '#welcome'
213
}
214
};
215
216
// Dynamic URL page object
217
const userProfilePage = {
218
url: function(userId) {
219
return `https://example.com/users/${userId}`;
220
},
221
elements: {
222
profileName: '#profile-name',
223
profileEmail: '#profile-email'
224
}
225
};
226
227
// Usage in tests
228
module.exports = {
229
'Navigate to pages': function(browser) {
230
// Navigate to static URL
231
const home = browser.page.homePage();
232
home.navigate();
233
234
// Navigate to dynamic URL
235
const profile = browser.page.userProfilePage();
236
profile.navigate('123'); // Navigates to /users/123
237
}
238
};
239
```
240
241
### Element Access Patterns
242
243
Access page object elements using various syntax patterns.
244
245
```javascript { .api }
246
/**
247
* Element access using @ syntax (recommended)
248
*/
249
pageObject.click('@elementName');
250
pageObject.setValue('@inputElement', 'value');
251
252
/**
253
* Element access using elements map
254
*/
255
pageObject.elements.elementName.click();
256
pageObject.elements.inputElement.setValue('value');
257
258
/**
259
* Direct selector access
260
*/
261
pageObject.click('css selector', '#direct-selector');
262
```
263
264
**Usage Examples:**
265
266
```javascript
267
// Page object definition
268
const formPage = {
269
elements: {
270
firstName: '#first-name',
271
lastName: '#last-name',
272
submitBtn: 'button[type="submit"]'
273
},
274
275
commands: {
276
fillForm: function(first, last) {
277
return this
278
.setValue('@firstName', first) // @ syntax
279
.setValue('@lastName', last)
280
.click('@submitBtn');
281
}
282
}
283
};
284
285
// Test usage
286
module.exports = {
287
'Form Test': function(browser) {
288
const form = browser.page.formPage();
289
290
// Using @ syntax
291
form.setValue('@firstName', 'John');
292
form.setValue('@lastName', 'Doe');
293
form.click('@submitBtn');
294
295
// Using elements map
296
form.elements.firstName.setValue('Jane');
297
form.elements.lastName.setValue('Smith');
298
299
// Using custom command
300
form.fillForm('Bob', 'Johnson');
301
}
302
};
303
```
304
305
### Section Definitions
306
307
Organize related elements into reusable sections within page objects.
308
309
```javascript { .api }
310
/**
311
* Section definition with nested elements
312
*/
313
interface SectionDefinition {
314
selector: string;
315
elements: Record<string, ElementProperties>;
316
sections?: Record<string, SectionDefinition>;
317
commands?: Record<string, Function>;
318
}
319
```
320
321
**Usage Examples:**
322
323
```javascript
324
// Page object with sections
325
const dashboardPage = {
326
url: '/dashboard',
327
328
sections: {
329
navigation: {
330
selector: '#nav-bar',
331
elements: {
332
homeLink: 'a[href="/"]',
333
profileLink: 'a[href="/profile"]',
334
settingsLink: 'a[href="/settings"]'
335
},
336
commands: {
337
navigateToProfile: function() {
338
return this.click('@profileLink');
339
}
340
}
341
},
342
343
sidebar: {
344
selector: '.sidebar',
345
elements: {
346
searchBox: 'input[type="search"]',
347
filterButton: '.filter-btn'
348
},
349
350
sections: {
351
filters: {
352
selector: '.filters-panel',
353
elements: {
354
categoryFilter: '#category',
355
dateFilter: '#date-range'
356
}
357
}
358
}
359
}
360
}
361
};
362
363
// Test usage
364
module.exports = {
365
'Dashboard Test': function(browser) {
366
const dashboard = browser.page.dashboardPage();
367
368
dashboard.navigate();
369
370
// Access section elements
371
dashboard.section.navigation.click('@profileLink');
372
dashboard.section.sidebar.setValue('@searchBox', 'test query');
373
374
// Access nested section elements
375
dashboard.section.sidebar.section.filters.click('@categoryFilter');
376
377
// Use section commands
378
dashboard.section.navigation.navigateToProfile();
379
}
380
};
381
```
382
383
### Custom Commands
384
385
Define reusable command functions for common page interactions.
386
387
```javascript { .api }
388
/**
389
* Custom command function signature
390
* @param this - Page object or section instance
391
* @param args - Command arguments
392
* @returns Promise or page object instance for chaining
393
*/
394
type CustomCommand = (this: PageObjectInstance | SectionInstance, ...args: any[]) => any;
395
```
396
397
**Usage Examples:**
398
399
```javascript
400
// Page object with custom commands
401
const loginPage = {
402
url: '/login',
403
404
elements: {
405
username: '#username',
406
password: '#password',
407
submitButton: '#submit',
408
errorMessage: '.error'
409
},
410
411
commands: {
412
// Simple login command
413
login: function(user, pass) {
414
return this
415
.setValue('@username', user)
416
.setValue('@password', pass)
417
.click('@submitButton');
418
},
419
420
// Login with validation
421
loginAndExpectSuccess: function(user, pass) {
422
return this
423
.login(user, pass)
424
.waitForElementNotVisible('@errorMessage', 2000)
425
.assert.urlContains('/dashboard');
426
},
427
428
// Async custom command
429
loginWithDelay: async function(user, pass, delay = 1000) {
430
await this.setValue('@username', user);
431
await this.pause(delay);
432
await this.setValue('@password', pass);
433
await this.click('@submitButton');
434
return this;
435
}
436
}
437
};
438
439
// Test usage
440
module.exports = {
441
'Login Commands Test': function(browser) {
442
const login = browser.page.loginPage();
443
444
login.navigate();
445
446
// Use custom commands
447
login.login('testuser', 'password123');
448
login.loginAndExpectSuccess('validuser', 'validpass');
449
}
450
};
451
```
452
453
### Properties and Dynamic Content
454
455
Define dynamic properties and computed values for page objects.
456
457
```javascript { .api }
458
/**
459
* Properties definition (object or function)
460
*/
461
interface PropertiesDefinition {
462
[key: string]: any;
463
}
464
465
type PropertiesFunction = (this: PageObjectInstance) => Record<string, any>;
466
```
467
468
**Usage Examples:**
469
470
```javascript
471
// Page object with static properties
472
const apiPage = {
473
url: '/api/docs',
474
475
props: {
476
apiBaseUrl: 'https://api.example.com/v1',
477
defaultHeaders: {
478
'Content-Type': 'application/json',
479
'Accept': 'application/json'
480
}
481
},
482
483
elements: {
484
endpointList: '.endpoint-list'
485
}
486
};
487
488
// Page object with dynamic properties
489
const userPage = {
490
url: function(userId) {
491
return `/users/${userId}`;
492
},
493
494
props: function() {
495
return {
496
currentUser: this.api.globals.currentUser,
497
baseUrl: this.api.launchUrl,
498
timestamp: new Date().toISOString()
499
};
500
},
501
502
elements: {
503
userProfile: '#user-profile'
504
},
505
506
commands: {
507
verifyUserData: function() {
508
const { currentUser } = this.props;
509
return this.assert.textContains('#user-name', currentUser.name);
510
}
511
}
512
};
513
514
// Test usage
515
module.exports = {
516
'Properties Test': function(browser) {
517
const userPage = browser.page.userPage();
518
519
// Access static properties
520
console.log('API Base URL:', userPage.props.apiBaseUrl);
521
522
// Access dynamic properties
523
const timestamp = userPage.props.timestamp;
524
console.log('Page loaded at:', timestamp);
525
526
// Use properties in commands
527
userPage.verifyUserData();
528
}
529
};
530
```
531
532
### Page Object Organization
533
534
Best practices for organizing page objects in test projects.
535
536
**File Structure:**
537
538
```
539
pages/
540
├── index.js // Export all page objects
541
├── loginPage.js // Login page object
542
├── dashboardPage.js // Dashboard page object
543
└── components/
544
├── header.js // Reusable header section
545
└── navigation.js // Reusable navigation section
546
```
547
548
**Usage Examples:**
549
550
```javascript
551
// pages/loginPage.js
552
module.exports = {
553
url: '/login',
554
elements: {
555
usernameInput: '#username',
556
passwordInput: '#password',
557
submitButton: '#submit'
558
},
559
commands: {
560
login: function(username, password) {
561
return this
562
.setValue('@usernameInput', username)
563
.setValue('@passwordInput', password)
564
.click('@submitButton');
565
}
566
}
567
};
568
569
// pages/index.js - Export all page objects
570
module.exports = {
571
loginPage: require('./loginPage'),
572
dashboardPage: require('./dashboardPage')
573
};
574
575
// nightwatch.conf.js - Configure page objects path
576
module.exports = {
577
page_objects_path: './pages',
578
// ... other config
579
};
580
581
// Test file usage
582
module.exports = {
583
'E2E Test': function(browser) {
584
const login = browser.page.loginPage();
585
const dashboard = browser.page.dashboardPage();
586
587
login
588
.navigate()
589
.login('testuser', 'password');
590
591
dashboard
592
.navigate()
593
.assert.visible('@welcomeMessage');
594
}
595
};
596
```