You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add code examples to Page Component Objects (EN) (#1387)
* Add code examples to Page Component Objects (EN)
* Update closing text
* All relevant pages for page_object_models.*.md to English
---------
Co-authored-by: Diego Molina <[email protected]>
[deploy site]
The page and component are represented by their own objects. Both objects only have methods for the **services** they offer, which matches the real-world application in object-oriented programming.
349
+
350
+
You can even
206
351
nest component objects inside other component objects for more complex
207
352
pages. If a page in the AUT has multiple components, or common
208
353
components used throughout the site (e.g. a navigation bar), then it
The page and component are represented by their own objects. Both objects only have methods for the **services** they offer, which matches the real-world application in object-oriented programming.
350
+
351
+
You can even
352
+
nest component objects inside other component objects for more complex
353
+
pages. If a page in the AUT has multiple components, or common
354
+
components used throughout the site (e.g. a navigation bar), then it
355
+
may improve maintainability and reduce code duplication.
356
+
357
+
## Other Design Patterns Used in Testing
358
+
There are other design patterns that also may be used in testing. Some use a
359
+
Page Factory for instantiating their page objects. Discussing all of these is
360
+
beyond the scope of this user guide. Here, we merely want to introduce the
361
+
concepts to make the reader aware of some of the things that can be done. As
362
+
was mentioned earlier, many have blogged on this topic and we encourage the
363
+
reader to search for blogs on these topics.
364
+
365
+
## Implementation Notes
366
+
367
+
368
+
PageObjects can be thought of as facing in two directions simultaneously. Facing toward the developer of a test, they represent the **services** offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page) It's simplest to think of the methods on a Page Object as offering the "services" that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system. Amongst the services it offers are the ability to compose a new email, choose to read a single email, and list the subject lines of the emails in the inbox. How these are implemented shouldn't matter to the test.
369
+
370
+
Because we're encouraging the developer of a test to try and think about the services they're interacting with rather than the implementation, PageObjects should seldom expose the underlying WebDriver instance. To facilitate this, methods on the PageObject should return other PageObjects. This means we can effectively model the user's journey through our application. It also means that should the way that pages relate to one another change (like when the login page asks the user to change their password the first time they log into a service when it previously didn't do that), simply changing the appropriate method's signature will cause the tests to fail to compile. Put another way; we can tell which tests would fail without needing to run them when we change the relationship between pages and reflect this in the PageObjects.
371
+
372
+
One consequence of this approach is that it may be necessary to model (for example) both a successful and unsuccessful login; or a click could have a different result depending on the app's state. When this happens, it is common to have multiple methods on the PageObject:
373
+
374
+
```
375
+
public class LoginPage {
376
+
public HomePage loginAs(String username, String password) {
377
+
// ... clever magic happens here
378
+
}
379
+
380
+
public LoginPage loginAsExpectingError(String username, String password) {
381
+
// ... failed login here, maybe because one or both of the username and password are wrong
382
+
}
383
+
384
+
public String getErrorMessage() {
385
+
// So we can verify that the correct error is shown
386
+
}
387
+
}
388
+
```
389
+
390
+
The code presented above shows an important point: the tests, not the PageObjects, should be responsible for making assertions about the state of a page. For example:
391
+
392
+
```
393
+
public void testMessagesAreReadOrUnread() {
394
+
Inbox inbox = new Inbox(driver);
395
+
inbox.assertMessageWithSubjectIsUnread("I like cheese");
396
+
inbox.assertMessageWithSubjectIsNotUnread("I'm not fond of tofu");
397
+
}
398
+
```
399
+
400
+
could be re-written as:
401
+
402
+
```
403
+
public void testMessagesAreReadOrUnread() {
404
+
Inbox inbox = new Inbox(driver);
405
+
assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
406
+
assertFalse(inbox.isMessageWithSubjectIsUnread("I'm not fond of tofu"));
407
+
}
408
+
```
409
+
410
+
Of course, as with every guideline, there are exceptions, and one that is commonly seen with PageObjects is to check that the WebDriver is on the correct page when we instantiate the PageObject. This is done in the example below.
411
+
412
+
Finally, a PageObject need not represent an entire page. It may represent a section that appears frequently within a site or page, such as site navigation. The essential principle is that there is only one place in your test suite with knowledge of the structure of the HTML of a particular (part of a) page.
There is a PageFactory in the support package that provides support for this pattern and helps to remove some boiler-plate code from your Page Objects at the same time.
The page and component are represented by their own objects. Both objects only have methods for the **services** they offer, which matches the real-world application in object-oriented programming.
350
+
351
+
You can even
352
+
nest component objects inside other component objects for more complex
353
+
pages. If a page in the AUT has multiple components, or common
354
+
components used throughout the site (e.g. a navigation bar), then it
355
+
may improve maintainability and reduce code duplication.
356
+
357
+
## Other Design Patterns Used in Testing
358
+
There are other design patterns that also may be used in testing. Some use a
359
+
Page Factory for instantiating their page objects. Discussing all of these is
360
+
beyond the scope of this user guide. Here, we merely want to introduce the
361
+
concepts to make the reader aware of some of the things that can be done. As
362
+
was mentioned earlier, many have blogged on this topic and we encourage the
363
+
reader to search for blogs on these topics.
364
+
365
+
## Implementation Notes
366
+
367
+
368
+
PageObjects can be thought of as facing in two directions simultaneously. Facing toward the developer of a test, they represent the **services** offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page) It's simplest to think of the methods on a Page Object as offering the "services" that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system. Amongst the services it offers are the ability to compose a new email, choose to read a single email, and list the subject lines of the emails in the inbox. How these are implemented shouldn't matter to the test.
369
+
370
+
Because we're encouraging the developer of a test to try and think about the services they're interacting with rather than the implementation, PageObjects should seldom expose the underlying WebDriver instance. To facilitate this, methods on the PageObject should return other PageObjects. This means we can effectively model the user's journey through our application. It also means that should the way that pages relate to one another change (like when the login page asks the user to change their password the first time they log into a service when it previously didn't do that), simply changing the appropriate method's signature will cause the tests to fail to compile. Put another way; we can tell which tests would fail without needing to run them when we change the relationship between pages and reflect this in the PageObjects.
371
+
372
+
One consequence of this approach is that it may be necessary to model (for example) both a successful and unsuccessful login; or a click could have a different result depending on the app's state. When this happens, it is common to have multiple methods on the PageObject:
373
+
374
+
```
375
+
public class LoginPage {
376
+
public HomePage loginAs(String username, String password) {
377
+
// ... clever magic happens here
378
+
}
379
+
380
+
public LoginPage loginAsExpectingError(String username, String password) {
381
+
// ... failed login here, maybe because one or both of the username and password are wrong
382
+
}
383
+
384
+
public String getErrorMessage() {
385
+
// So we can verify that the correct error is shown
386
+
}
387
+
}
388
+
```
389
+
390
+
The code presented above shows an important point: the tests, not the PageObjects, should be responsible for making assertions about the state of a page. For example:
391
+
392
+
```
393
+
public void testMessagesAreReadOrUnread() {
394
+
Inbox inbox = new Inbox(driver);
395
+
inbox.assertMessageWithSubjectIsUnread("I like cheese");
396
+
inbox.assertMessageWithSubjectIsNotUnread("I'm not fond of tofu");
397
+
}
398
+
```
399
+
400
+
could be re-written as:
401
+
402
+
```
403
+
public void testMessagesAreReadOrUnread() {
404
+
Inbox inbox = new Inbox(driver);
405
+
assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
406
+
assertFalse(inbox.isMessageWithSubjectIsUnread("I'm not fond of tofu"));
407
+
}
408
+
```
409
+
410
+
Of course, as with every guideline, there are exceptions, and one that is commonly seen with PageObjects is to check that the WebDriver is on the correct page when we instantiate the PageObject. This is done in the example below.
411
+
412
+
Finally, a PageObject need not represent an entire page. It may represent a section that appears frequently within a site or page, such as site navigation. The essential principle is that there is only one place in your test suite with knowledge of the structure of the HTML of a particular (part of a) page.
413
+
414
+
## Summary
415
+
416
+
* The public methods represent the services that the page offers
417
+
* Try not to expose the internals of the page
418
+
* Generally don't make assertions
419
+
* Methods return other PageObjects
420
+
* Need not represent an entire page
421
+
* Different results for the same action are modelled as different methods
422
+
423
+
## Example
424
+
425
+
```
426
+
public class LoginPage {
427
+
private final WebDriver driver;
428
+
429
+
public LoginPage(WebDriver driver) {
430
+
this.driver = driver;
431
+
432
+
// Check that we're on the right page.
433
+
if (!"Login".equals(driver.getTitle())) {
434
+
// Alternatively, we could navigate to the login page, perhaps logging out first
435
+
throw new IllegalStateException("This is not the login page");
436
+
}
437
+
}
438
+
439
+
// The login page contains several HTML elements that will be represented as WebElements.
440
+
// The locators for these elements should only be defined once.
441
+
By usernameLocator = By.id("username");
442
+
By passwordLocator = By.id("passwd");
443
+
By loginButtonLocator = By.id("login");
444
+
445
+
// The login page allows the user to type their username into the username field
446
+
public LoginPage typeUsername(String username) {
447
+
// This is the only place that "knows" how to enter a username
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
460
+
return this;
461
+
}
462
+
463
+
// The login page allows the user to submit the login form
464
+
public HomePage submitLogin() {
465
+
// This is the only place that submits the login form and expects the destination to be the home page.
466
+
// A seperate method should be created for the instance of clicking login whilst expecting a login failure.
467
+
driver.findElement(loginButtonLocator).submit();
468
+
469
+
// Return a new page object representing the destination. Should the login page ever
470
+
// go somewhere else (for example, a legal disclaimer) then changing the method signature
471
+
// for this method will mean that all tests that rely on this behaviour won't compile.
472
+
return new HomePage(driver);
473
+
}
474
+
475
+
// The login page allows the user to submit the login form knowing that an invalid username and / or password were entered
476
+
public LoginPage submitLoginExpectingFailure() {
477
+
// This is the only place that submits the login form and expects the destination to be the login page due to login failure.
478
+
driver.findElement(loginButtonLocator).submit();
479
+
480
+
// Return a new page object representing the destination. Should the user ever be navigated to the home page after submiting a login with credentials
481
+
// expected to fail login, the script will fail when it attempts to instantiate the LoginPage PageObject.
482
+
return new LoginPage(driver);
483
+
}
484
+
485
+
// Conceptually, the login page offers the user the service of being able to "log into"
486
+
// the application using a user name and password.
487
+
public HomePage loginAs(String username, String password) {
488
+
// The PageObject methods that enter username, password & submit login have already defined and should not be repeated here.
489
+
typeUsername(username);
490
+
typePassword(password);
491
+
return submitLogin();
492
+
}
493
+
}
494
+
495
+
```
496
+
497
+
498
+
## Support in WebDriver
499
+
500
+
There is a PageFactory in the support package that provides support for this pattern and helps to remove some boiler-plate code from your Page Objects at the same time.
The page and component are represented by their own objects. Both objects only have methods for the **services** they offer, which matches the real-world application in object-oriented programming.
350
+
351
+
You can even
352
+
nest component objects inside other component objects for more complex
353
+
pages. If a page in the AUT has multiple components, or common
354
+
components used throughout the site (e.g. a navigation bar), then it
355
+
may improve maintainability and reduce code duplication.
356
+
357
+
## Other Design Patterns Used in Testing
358
+
There are other design patterns that also may be used in testing. Some use a
359
+
Page Factory for instantiating their page objects. Discussing all of these is
360
+
beyond the scope of this user guide. Here, we merely want to introduce the
361
+
concepts to make the reader aware of some of the things that can be done. As
362
+
was mentioned earlier, many have blogged on this topic and we encourage the
363
+
reader to search for blogs on these topics.
364
+
365
+
## Implementation Notes
366
+
367
+
368
+
PageObjects can be thought of as facing in two directions simultaneously. Facing toward the developer of a test, they represent the **services** offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page) It's simplest to think of the methods on a Page Object as offering the "services" that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system. Amongst the services it offers are the ability to compose a new email, choose to read a single email, and list the subject lines of the emails in the inbox. How these are implemented shouldn't matter to the test.
369
+
370
+
Because we're encouraging the developer of a test to try and think about the services they're interacting with rather than the implementation, PageObjects should seldom expose the underlying WebDriver instance. To facilitate this, methods on the PageObject should return other PageObjects. This means we can effectively model the user's journey through our application. It also means that should the way that pages relate to one another change (like when the login page asks the user to change their password the first time they log into a service when it previously didn't do that), simply changing the appropriate method's signature will cause the tests to fail to compile. Put another way; we can tell which tests would fail without needing to run them when we change the relationship between pages and reflect this in the PageObjects.
371
+
372
+
One consequence of this approach is that it may be necessary to model (for example) both a successful and unsuccessful login; or a click could have a different result depending on the app's state. When this happens, it is common to have multiple methods on the PageObject:
373
+
374
+
```
375
+
public class LoginPage {
376
+
public HomePage loginAs(String username, String password) {
377
+
// ... clever magic happens here
378
+
}
379
+
380
+
public LoginPage loginAsExpectingError(String username, String password) {
381
+
// ... failed login here, maybe because one or both of the username and password are wrong
382
+
}
383
+
384
+
public String getErrorMessage() {
385
+
// So we can verify that the correct error is shown
386
+
}
387
+
}
388
+
```
389
+
390
+
The code presented above shows an important point: the tests, not the PageObjects, should be responsible for making assertions about the state of a page. For example:
391
+
392
+
```
393
+
public void testMessagesAreReadOrUnread() {
394
+
Inbox inbox = new Inbox(driver);
395
+
inbox.assertMessageWithSubjectIsUnread("I like cheese");
396
+
inbox.assertMessageWithSubjectIsNotUnread("I'm not fond of tofu");
397
+
}
398
+
```
399
+
400
+
could be re-written as:
401
+
402
+
```
403
+
public void testMessagesAreReadOrUnread() {
404
+
Inbox inbox = new Inbox(driver);
405
+
assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
406
+
assertFalse(inbox.isMessageWithSubjectIsUnread("I'm not fond of tofu"));
407
+
}
408
+
```
409
+
410
+
Of course, as with every guideline, there are exceptions, and one that is commonly seen with PageObjects is to check that the WebDriver is on the correct page when we instantiate the PageObject. This is done in the example below.
411
+
412
+
Finally, a PageObject need not represent an entire page. It may represent a section that appears frequently within a site or page, such as site navigation. The essential principle is that there is only one place in your test suite with knowledge of the structure of the HTML of a particular (part of a) page.
There is a PageFactory in the support package that provides support for this pattern and helps to remove some boiler-plate code from your Page Objects at the same time.
0 commit comments