or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdassertions-expectations.mdbrowser-control.mdelement-interaction.mdindex.mdmodern-element-api.mdpage-object-model.mdprogrammatic-api.mdprotocol-commands.md

page-object-model.mddocs/

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

```