or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

alerts.mdconfiguration.mddrivers.mdelements.mdindex.mdinteractions.mdjavascript.mdlocators.mdpage-objects.mdwaits.mdwebdriver.md

page-objects.mddocs/

0

# Page Object Model Support

1

2

Utilities for implementing the Page Object Model pattern including element initialization, locator annotations, and event handling for maintainable test automation.

3

4

## Capabilities

5

6

### PageFactory Class

7

8

Utility class for initializing Page Object Model classes with automatic element location and caching.

9

10

```java { .api }

11

/**

12

* PageFactory utility for initializing Page Object Model classes

13

* Automatically initializes WebElement fields marked with @FindBy annotations

14

*/

15

class PageFactory {

16

/**

17

* Initialize page object elements using default element locator factory

18

* @param driver - WebDriver instance for element location

19

* @param page - Page object instance to initialize

20

*/

21

static void initElements(WebDriver driver, Object page);

22

23

/**

24

* Initialize page object elements using custom element locator factory

25

* @param factory - Custom ElementLocatorFactory for element location strategy

26

* @param page - Page object instance to initialize

27

*/

28

static void initElements(ElementLocatorFactory factory, Object page);

29

30

/**

31

* Initialize page object elements with custom field decorator

32

* @param decorator - FieldDecorator for customizing element initialization

33

* @param page - Page object instance to initialize

34

*/

35

static void initElements(FieldDecorator decorator, Object page);

36

}

37

```

38

39

### FindBy Annotation

40

41

Annotation for marking WebElement fields with their location strategy.

42

43

```java { .api }

44

/**

45

* FindBy annotation for specifying element location strategy

46

* Used on WebElement fields in Page Object classes

47

*/

48

@interface FindBy {

49

/**

50

* Locate by ID attribute

51

* @return Element ID value

52

*/

53

String id() default "";

54

55

/**

56

* Locate by name attribute

57

* @return Element name value

58

*/

59

String name() default "";

60

61

/**

62

* Locate by CSS class name

63

* @return Element class name

64

*/

65

String className() default "";

66

67

/**

68

* Locate by HTML tag name

69

* @return Element tag name

70

*/

71

String tagName() default "";

72

73

/**

74

* Locate by XPath expression

75

* @return XPath expression

76

*/

77

String xpath() default "";

78

79

/**

80

* Locate by CSS selector

81

* @return CSS selector expression

82

*/

83

String css() default "";

84

85

/**

86

* Locate link by exact text

87

* @return Link text content

88

*/

89

String linkText() default "";

90

91

/**

92

* Locate link by partial text

93

* @return Partial link text content

94

*/

95

String partialLinkText() default "";

96

}

97

```

98

99

### FindBys Annotation

100

101

Annotation for combining multiple locators with AND logic.

102

103

```java { .api }

104

/**

105

* FindBys annotation for chaining multiple locators with AND logic

106

* Element must match ALL specified locator conditions

107

*/

108

@interface FindBys {

109

/**

110

* Array of FindBy annotations that must all match

111

* @return Array of FindBy conditions

112

*/

113

FindBy[] value();

114

}

115

```

116

117

### FindAll Annotation

118

119

Annotation for combining multiple locators with OR logic.

120

121

```java { .api }

122

/**

123

* FindAll annotation for combining multiple locators with OR logic

124

* Element matches if ANY of the specified locator conditions match

125

*/

126

@interface FindAll {

127

/**

128

* Array of FindBy annotations where any can match

129

* @return Array of FindBy conditions

130

*/

131

FindBy[] value();

132

}

133

```

134

135

### CacheLookup Annotation

136

137

Annotation for caching element lookups to improve performance.

138

139

```java { .api }

140

/**

141

* CacheLookup annotation for caching WebElement lookups

142

* Element is located once and cached for subsequent access

143

* Use with caution on dynamic content

144

*/

145

@interface CacheLookup {

146

}

147

```

148

149

## Usage Examples

150

151

### Basic Page Object Pattern

152

153

```java

154

import org.openqa.selenium.WebDriver;

155

import org.openqa.selenium.WebElement;

156

import org.openqa.selenium.support.FindBy;

157

import org.openqa.selenium.support.PageFactory;

158

159

// Login page object class

160

public class LoginPage {

161

private WebDriver driver;

162

163

// Element declarations with @FindBy annotations

164

@FindBy(id = "username")

165

private WebElement usernameField;

166

167

@FindBy(id = "password")

168

private WebElement passwordField;

169

170

@FindBy(xpath = "//button[@type='submit']")

171

private WebElement loginButton;

172

173

@FindBy(className = "error-message")

174

private WebElement errorMessage;

175

176

@FindBy(linkText = "Forgot Password?")

177

private WebElement forgotPasswordLink;

178

179

// Constructor

180

public LoginPage(WebDriver driver) {

181

this.driver = driver;

182

PageFactory.initElements(driver, this);

183

}

184

185

// Page actions

186

public void enterUsername(String username) {

187

usernameField.clear();

188

usernameField.sendKeys(username);

189

}

190

191

public void enterPassword(String password) {

192

passwordField.clear();

193

passwordField.sendKeys(password);

194

}

195

196

public void clickLogin() {

197

loginButton.click();

198

}

199

200

public void login(String username, String password) {

201

enterUsername(username);

202

enterPassword(password);

203

clickLogin();

204

}

205

206

public String getErrorMessage() {

207

return errorMessage.getText();

208

}

209

210

public boolean isErrorDisplayed() {

211

return errorMessage.isDisplayed();

212

}

213

214

public void clickForgotPassword() {

215

forgotPasswordLink.click();

216

}

217

}

218

219

// Usage in test

220

WebDriver driver = new ChromeDriver();

221

LoginPage loginPage = new LoginPage(driver);

222

loginPage.login("testuser", "testpass");

223

```

224

225

### Advanced Locator Strategies

226

227

```java

228

public class AdvancedPage {

229

@FindBy(css = "input[data-testid='search-input']")

230

private WebElement searchBox;

231

232

@FindBy(xpath = "//div[@class='product-list']//div[contains(@class, 'product-item')]")

233

private List<WebElement> productItems;

234

235

// Using FindBys for AND logic (element must match ALL conditions)

236

@FindBys({

237

@FindBy(className = "btn"),

238

@FindBy(xpath = ".//span[text()='Submit']")

239

})

240

private WebElement submitButton;

241

242

// Using FindAll for OR logic (element matches ANY condition)

243

@FindAll({

244

@FindBy(id = "closeButton"),

245

@FindBy(xpath = "//button[text()='Close']"),

246

@FindBy(css = ".close-btn")

247

})

248

private WebElement closeButton;

249

250

// Cached lookup for static elements

251

@CacheLookup

252

@FindBy(id = "header-logo")

253

private WebElement headerLogo;

254

255

// Multiple elements with same locator

256

@FindBy(className = "nav-item")

257

private List<WebElement> navigationItems;

258

259

public AdvancedPage(WebDriver driver) {

260

PageFactory.initElements(driver, this);

261

}

262

263

public void searchFor(String query) {

264

searchBox.clear();

265

searchBox.sendKeys(query);

266

searchBox.submit();

267

}

268

269

public int getProductCount() {

270

return productItems.size();

271

}

272

273

public List<String> getNavigationItemTexts() {

274

return navigationItems.stream()

275

.map(WebElement::getText)

276

.collect(Collectors.toList());

277

}

278

}

279

```

280

281

### Page Object with Wait Strategies

282

283

```java

284

import org.openqa.selenium.support.ui.WebDriverWait;

285

import org.openqa.selenium.support.ui.ExpectedConditions;

286

import java.time.Duration;

287

288

public class WaitingPage {

289

private WebDriver driver;

290

private WebDriverWait wait;

291

292

@FindBy(id = "dynamic-content")

293

private WebElement dynamicContent;

294

295

@FindBy(className = "loading-spinner")

296

private WebElement loadingSpinner;

297

298

@FindBy(id = "ajax-button")

299

private WebElement ajaxButton;

300

301

@FindBy(css = ".result-item")

302

private List<WebElement> results;

303

304

public WaitingPage(WebDriver driver) {

305

this.driver = driver;

306

this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));

307

PageFactory.initElements(driver, this);

308

}

309

310

public void waitForPageLoad() {

311

wait.until(ExpectedConditions.invisibilityOf(loadingSpinner));

312

wait.until(ExpectedConditions.visibilityOf(dynamicContent));

313

}

314

315

public void clickAjaxButtonAndWait() {

316

ajaxButton.click();

317

wait.until(ExpectedConditions.invisibilityOf(loadingSpinner));

318

wait.until(driver -> results.size() > 0);

319

}

320

321

public String getDynamicContentText() {

322

wait.until(ExpectedConditions.visibilityOf(dynamicContent));

323

return dynamicContent.getText();

324

}

325

}

326

```

327

328

### Hierarchical Page Objects

329

330

```java

331

// Base page class with common elements

332

public abstract class BasePage {

333

protected WebDriver driver;

334

protected WebDriverWait wait;

335

336

@FindBy(id = "header")

337

protected WebElement header;

338

339

@FindBy(id = "footer")

340

protected WebElement footer;

341

342

@FindBy(className = "user-menu")

343

protected WebElement userMenu;

344

345

@FindBy(linkText = "Logout")

346

protected WebElement logoutLink;

347

348

public BasePage(WebDriver driver) {

349

this.driver = driver;

350

this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));

351

PageFactory.initElements(driver, this);

352

}

353

354

public void logout() {

355

userMenu.click();

356

logoutLink.click();

357

}

358

359

public String getPageTitle() {

360

return driver.getTitle();

361

}

362

363

public abstract boolean isPageLoaded();

364

}

365

366

// Specific page extending base

367

public class DashboardPage extends BasePage {

368

@FindBy(className = "dashboard-widget")

369

private List<WebElement> widgets;

370

371

@FindBy(id = "welcome-message")

372

private WebElement welcomeMessage;

373

374

@FindBy(css = ".quick-action-btn")

375

private List<WebElement> quickActionButtons;

376

377

public DashboardPage(WebDriver driver) {

378

super(driver);

379

}

380

381

@Override

382

public boolean isPageLoaded() {

383

return welcomeMessage.isDisplayed() && widgets.size() > 0;

384

}

385

386

public int getWidgetCount() {

387

return widgets.size();

388

}

389

390

public void clickQuickAction(String actionText) {

391

quickActionButtons.stream()

392

.filter(btn -> btn.getText().equals(actionText))

393

.findFirst()

394

.ifPresent(WebElement::click);

395

}

396

}

397

```

398

399

### Custom Element Locator Factory

400

401

```java

402

import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;

403

import org.openqa.selenium.support.pagefactory.ElementLocator;

404

import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;

405

406

// Custom locator factory with retry logic

407

public class RetryingElementLocatorFactory implements ElementLocatorFactory {

408

private final WebDriver driver;

409

private final int timeOutInSeconds;

410

411

public RetryingElementLocatorFactory(WebDriver driver, int timeOutInSeconds) {

412

this.driver = driver;

413

this.timeOutInSeconds = timeOutInSeconds;

414

}

415

416

@Override

417

public ElementLocator createLocator(Field field) {

418

return new RetryingElementLocator(driver, field, timeOutInSeconds);

419

}

420

}

421

422

// Custom element locator with retry mechanism

423

public class RetryingElementLocator implements ElementLocator {

424

private final WebDriver driver;

425

private final By by;

426

private final boolean shouldCache;

427

private final int timeOutInSeconds;

428

private WebElement cachedElement;

429

private List<WebElement> cachedElementList;

430

431

public RetryingElementLocator(WebDriver driver, Field field, int timeOutInSeconds) {

432

this.driver = driver;

433

this.timeOutInSeconds = timeOutInSeconds;

434

this.shouldCache = field.getAnnotation(CacheLookup.class) != null;

435

this.by = buildByFromField(field);

436

}

437

438

@Override

439

public WebElement findElement() {

440

if (shouldCache && cachedElement != null) {

441

return cachedElement;

442

}

443

444

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(timeOutInSeconds));

445

WebElement element = wait.until(ExpectedConditions.presenceOfElementLocated(by));

446

447

if (shouldCache) {

448

cachedElement = element;

449

}

450

451

return element;

452

}

453

454

@Override

455

public List<WebElement> findElements() {

456

if (shouldCache && cachedElementList != null) {

457

return cachedElementList;

458

}

459

460

List<WebElement> elements = driver.findElements(by);

461

462

if (shouldCache) {

463

cachedElementList = elements;

464

}

465

466

return elements;

467

}

468

469

private By buildByFromField(Field field) {

470

FindBy findBy = field.getAnnotation(FindBy.class);

471

if (findBy != null) {

472

return buildByFromFindBy(findBy);

473

}

474

throw new IllegalArgumentException("Field must be annotated with @FindBy");

475

}

476

477

private By buildByFromFindBy(FindBy findBy) {

478

if (!findBy.id().isEmpty()) return By.id(findBy.id());

479

if (!findBy.name().isEmpty()) return By.name(findBy.name());

480

if (!findBy.className().isEmpty()) return By.className(findBy.className());

481

if (!findBy.tagName().isEmpty()) return By.tagName(findBy.tagName());

482

if (!findBy.xpath().isEmpty()) return By.xpath(findBy.xpath());

483

if (!findBy.css().isEmpty()) return By.cssSelector(findBy.css());

484

if (!findBy.linkText().isEmpty()) return By.linkText(findBy.linkText());

485

if (!findBy.partialLinkText().isEmpty()) return By.partialLinkText(findBy.partialLinkText());

486

487

throw new IllegalArgumentException("No locator strategy specified in @FindBy");

488

}

489

}

490

491

// Usage with custom factory

492

public class CustomPage {

493

@FindBy(id = "slow-loading-element")

494

private WebElement slowElement;

495

496

public CustomPage(WebDriver driver) {

497

RetryingElementLocatorFactory factory = new RetryingElementLocatorFactory(driver, 15);

498

PageFactory.initElements(factory, this);

499

}

500

}

501

```

502

503

### Page Object Best Practices

504

505

```java

506

// Complete page object example with best practices

507

public class ProductPage {

508

private final WebDriver driver;

509

private final WebDriverWait wait;

510

511

// Page elements

512

@FindBy(id = "product-title")

513

private WebElement productTitle;

514

515

@FindBy(className = "price")

516

private WebElement price;

517

518

@FindBy(id = "add-to-cart")

519

private WebElement addToCartButton;

520

521

@FindBy(id = "quantity")

522

private WebElement quantityInput;

523

524

@FindBy(css = ".product-image img")

525

private WebElement productImage;

526

527

@FindBy(className = "reviews")

528

private List<WebElement> reviews;

529

530

public ProductPage(WebDriver driver) {

531

this.driver = driver;

532

this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));

533

PageFactory.initElements(driver, this);

534

}

535

536

// Verification methods

537

public boolean isProductPageLoaded() {

538

return productTitle.isDisplayed() && price.isDisplayed();

539

}

540

541

// Information retrieval methods

542

public String getProductTitle() {

543

return productTitle.getText();

544

}

545

546

public String getPrice() {

547

return price.getText();

548

}

549

550

public int getReviewCount() {

551

return reviews.size();

552

}

553

554

// Action methods

555

public void setQuantity(int quantity) {

556

quantityInput.clear();

557

quantityInput.sendKeys(String.valueOf(quantity));

558

}

559

560

public void addToCart() {

561

wait.until(ExpectedConditions.elementToBeClickable(addToCartButton));

562

addToCartButton.click();

563

}

564

565

public void addToCartWithQuantity(int quantity) {

566

setQuantity(quantity);

567

addToCart();

568

}

569

570

// Navigation methods

571

public CartPage addToCartAndGoToCart(int quantity) {

572

addToCartWithQuantity(quantity);

573

// Wait for success indication or navigate to cart

574

return new CartPage(driver);

575

}

576

577

// Validation methods

578

public boolean isAddToCartButtonEnabled() {

579

return addToCartButton.isEnabled();

580

}

581

582

public boolean isProductImageDisplayed() {

583

return productImage.isDisplayed();

584

}

585

}

586

```