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
```