Skip to content

Commit b9d1956

Browse files
authoredMay 18, 2023
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]
1 parent 302c8a5 commit b9d1956

File tree

4 files changed

+1256
-160
lines changed

4 files changed

+1256
-160
lines changed
 

‎website_and_docs/content/documentation/test_practices/encouraged/page_object_models.en.md

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,152 @@ page itself. The same principles used for page objects can be used to
202202
create "Page _Component_ Objects" that represent discrete chunks of the
203203
page and can be included in page objects. These component objects can
204204
provide references to the elements inside those discrete chunks, and
205-
methods to leverage the functionality provided by them. You can even
205+
methods to leverage the functionality provided by them.
206+
207+
For example, a Product page has multiple products.
208+
209+
```html
210+
<!-- Products Page -->
211+
<div class="header_container">
212+
<span class="title">Products</span>
213+
</div>
214+
215+
<div class="inventory_list">
216+
<div class="inventory_item">
217+
</div>
218+
<div class="inventory_item">
219+
</div>
220+
<div class="inventory_item">
221+
</div>
222+
<div class="inventory_item">
223+
</div>
224+
<div class="inventory_item">
225+
</div>
226+
<div class="inventory_item">
227+
</div>
228+
</div>
229+
```
230+
231+
Each product is a component of the Products page.
232+
233+
234+
```html
235+
<!-- Inventory Item -->
236+
<div class="inventory_item">
237+
<div class="inventory_item_name">Backpack</div>
238+
<div class="pricebar">
239+
<div class="inventory_item_price">$29.99</div>
240+
<button id="add-to-cart-backpack">Add to cart</button>
241+
</div>
242+
</div>
243+
```
244+
245+
The Product page HAS-A list of products. This relationship is called Composition. In simpler terms, something is _composed of_ another thing.
246+
247+
```java
248+
public abstract class BasePage {
249+
protected WebDriver driver;
250+
251+
public BasePage(WebDriver driver) {
252+
this.driver = driver;
253+
}
254+
}
255+
256+
// Page Object
257+
public class ProductsPage extends BasePage {
258+
public ProductsPage(WebDriver driver) {
259+
super(driver);
260+
// No assertions, throws an exception if the element is not loaded
261+
new WebDriverWait(driver, Duration.ofSeconds(3))
262+
.until(d -> d.findElement(By.className​("header_container")));
263+
}
264+
265+
// Returning a list of products is a service of the page
266+
public List<Product> getProducts() {
267+
return driver.findElements(By.className​("inventory_item"))
268+
.stream()
269+
.map(e -> new Product(e)) // Map WebElement to a product component
270+
.toList();
271+
}
272+
273+
// Return a specific product using a boolean-valued function (predicate)
274+
// This is the behavioral Strategy Pattern from GoF
275+
public Product getProduct(Predicate<Product> condition) {
276+
return getProducts()
277+
.stream()
278+
.filter(condition) // Filter by product name or price
279+
.findFirst()
280+
.orElseThrow();
281+
}
282+
}
283+
```
284+
285+
The Product component object is used inside the Products page object.
286+
287+
```java
288+
public abstract class BaseComponent {
289+
protected WebElement root;
290+
291+
public BaseComponent(WebElement root) {
292+
this.root = root;
293+
}
294+
}
295+
296+
// Page Component Object
297+
public class Product extends BaseComponent {
298+
// The root element contains the entire component
299+
public Product(WebElement root) {
300+
super(root); // inventory_item
301+
}
302+
303+
public String getName() {
304+
// Locating an element begins at the root of the component
305+
return root.findElement(By.className("inventory_item_name")).getText();
306+
}
307+
308+
public BigDecimal getPrice() {
309+
return new BigDecimal(
310+
root.findElement(By.className("inventory_item_price"))
311+
.getText()
312+
.replace("$", "")
313+
).setScale(2, RoundingMode.UNNECESSARY); // Sanitation and formatting
314+
}
315+
316+
public void addToCart() {
317+
root.findElement(By.id("add-to-cart-backpack")).click();
318+
}
319+
}
320+
```
321+
322+
So now, the products test would use the page object and the page component object as follows.
323+
324+
```java
325+
public class ProductsTest {
326+
@Test
327+
public void testProductInventory() {
328+
var productsPage = new ProductsPage(driver);
329+
var products = productsPage.getProducts();
330+
assertEquals(6, products.size()); // expected, actual
331+
}
332+
333+
@Test
334+
public void testProductPrices() {
335+
var productsPage = new ProductsPage(driver);
336+
337+
// Pass a lambda expression (predicate) to filter the list of products
338+
// The predicate or "strategy" is the behavior passed as parameter
339+
var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack"));
340+
var bikeLight = productsPage.getProduct(p -> p.getName().equals("Bike Light"));
341+
342+
assertEquals(new BigDecimal("29.99"), backpack.getPrice());
343+
assertEquals(new BigDecimal("9.99"), bikeLight.getPrice());
344+
}
345+
}
346+
```
347+
348+
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
206351
nest component objects inside other component objects for more complex
207352
pages. If a page in the AUT has multiple components, or common
208353
components used throughout the site (e.g. a navigation bar), then it

‎website_and_docs/content/documentation/test_practices/encouraged/page_object_models.ja.md

Lines changed: 365 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,43 @@ aliases: [
99
]
1010
---
1111

12-
ページオブジェクトは、テストメンテナンスを強化し、コードの重複を減らすためのテスト自動化で一般的になったデザインパターンです。
13-
ページオブジェクトは、AUT(テスト対象アプリケーション)のページへのインターフェイスとして機能するオブジェクト指向クラスです。
14-
テストは、そのページのUIと対話する必要があるときは常に、このページオブジェクトクラスのメソッドを使用します。
15-
利点は、ページのUIが変更された場合、テスト自体を変更する必要はなく、ページオブジェクト内のコードのみを変更する必要があることです。
16-
その後、その新しいUIをサポートするためのすべての変更は1か所に配置されます。
17-
18-
ページオブジェクトデザインパターンには、次の利点があります。
19-
20-
* テストコードと、ロケーター(またはUIマップを使用している場合はロケーター)、レイアウトなどのページ固有のコードを明確に分離します。
21-
* これらのサービスをテスト全体に分散させるのではなく、ページによって提供されるサービスまたは操作用の単一のリポジトリがあります。
22-
23-
どちらの場合でも、これにより、UIの変更により必要な変更をすべて1か所で行うことができます。
24-
この'テストデザインパターン'が広く使用されるようになったため、この手法に関する有用な情報は多数のブログで見つけることができます。
25-
詳細を知りたい読者には、このテーマに関するブログをインターネットで検索することをお勧めします。
26-
多くの人がこの設計パターンについて書いており、このユーザーガイドの範囲を超えた有用なヒントを提供できます。
27-
ただし、簡単に始めるために、ページオブジェクトを簡単な例で説明します。
28-
29-
最初に、ページオブジェクトを使用しないテスト自動化の典型的な例を考えてみましょう。
12+
Note: this page has merged contents from multiple sources, including
13+
the [Selenium wiki](https://github.com/SeleniumHQ/selenium/wiki/PageObjects)
14+
15+
## Overview
16+
17+
Within your web app's UI, there are areas where your tests interact with.
18+
A Page Object only models these as objects within the test code.
19+
This reduces the amount of duplicated code and means that if the UI changes,
20+
the fix needs only to be applied in one place.
21+
22+
Page Object is a Design Pattern that has become popular in test automation for
23+
enhancing test maintenance and reducing code duplication. A page object is an
24+
object-oriented class that serves as an interface to a page of your AUT. The
25+
tests then use the methods of this page object class whenever they need to
26+
interact with the UI of that page. The benefit is that if the UI changes for
27+
the page, the tests themselves don’t need to change, only the code within the
28+
page object needs to change. Subsequently, all changes to support that new UI
29+
are located in one place.
30+
31+
### Advantages
32+
33+
* There is a clean separation between the test code and page-specific code, such as
34+
locators (or their use if you’re using a UI Map) and layout.
35+
* There is a single repository for the services or operations the page offers
36+
rather than having these services scattered throughout the tests.
37+
38+
In both cases, this allows any modifications required due to UI changes to all
39+
be made in one place. Helpful information on this technique can be found on
40+
numerous blogs as this ‘test design pattern’ is becoming widely used. We
41+
encourage readers who wish to know more to search the internet for blogs
42+
on this subject. Many have written on this design pattern and can provide
43+
helpful tips beyond the scope of this user guide. To get you started,
44+
we’ll illustrate page objects with a simple example.
45+
46+
### Examples
47+
First, consider an example, typical of test automation, that does not use a
48+
page object:
3049

3150
```java
3251
/***
@@ -38,7 +57,7 @@ public class Login {
3857
// fill login data on sign-in page
3958
driver.findElement(By.name("user_name")).sendKeys("userName");
4059
driver.findElement(By.name("password")).sendKeys("my supersecret password");
41-
driver.findElement(By.name("sign_in")).click();
60+
driver.findElement(By.name("sign-in")).click();
4261

4362
// verify h1 tag is "Hello userName" after login
4463
driver.findElement(By.tagName("h1")).isDisplayed();
@@ -47,14 +66,17 @@ public class Login {
4766
}
4867
```
4968

50-
このアプローチには2つの問題があります。
69+
There are two problems with this approach.
5170

52-
* テスト方法とAUTのロケーター(この例ではID)の間に区別はありません。
53-
どちらも単一のメソッドで絡み合っています。
54-
AUTのUIが識別子、レイアウト、またはログインの入力および処理方法を変更する場合、テスト自体を変更する必要があります。
55-
* IDロケーターは、このログインページを使用する必要があったすべてのテストで、複数のテストに分散されます。
71+
* There is no separation between the test method and the AUT’s locators (IDs in
72+
this example); both are intertwined in a single method. If the AUT’s UI changes
73+
its identifiers, layout, or how a login is input and processed, the test itself
74+
must change.
75+
* The ID-locators would be spread in multiple tests, in all tests that had to
76+
use this login page.
5677

57-
ページオブジェクトの手法を適用すると、この例は、サインインページのページオブジェクトの次の例のように書き換えることができます。
78+
Applying the page object techniques, this example could be rewritten like this
79+
in the following example of a page object for a Sign-in page.
5880

5981
```java
6082
import org.openqa.selenium.By;
@@ -75,7 +97,7 @@ public class SignInPage {
7597

7698
public SignInPage(WebDriver driver){
7799
this.driver = driver;
78-
if (!driver.getTitle().equals("Sign In Page")) {
100+
if (!driver.getTitle().equals("Sign In Page")) {
79101
throw new IllegalStateException("This is not Sign In Page," +
80102
" current page is: " + driver.getCurrentUrl());
81103
}
@@ -97,7 +119,7 @@ public class SignInPage {
97119
}
98120
```
99121

100-
そして、ホームページのページオブジェクトは次のようになります。
122+
and page object for a Home page could look like this.
101123

102124
```java
103125
import org.openqa.selenium.By;
@@ -139,7 +161,7 @@ public class HomePage {
139161
}
140162
```
141163

142-
したがって、ログインテストでは、これら2つのページオブジェクトを次のように使用します。
164+
So now, the login test would use these two page objects as follows.
143165

144166
```java
145167
/***
@@ -157,23 +179,322 @@ public class TestLogin {
157179
}
158180
```
159181

160-
ページオブジェクトの設計方法には多くの柔軟性がありますが、テストコードの望ましい保守性を得るための基本的なルールがいくつかあります。
182+
There is a lot of flexibility in how the page objects may be designed, but
183+
there are a few basic rules for getting the desired maintainability of your
184+
test code.
185+
186+
## Assertions in Page Objects
187+
Page objects themselves should never make verifications or assertions. This is
188+
part of your test and should always be within the test’s code, never in an page
189+
object. The page object will contain the representation of the page, and the
190+
services the page provides via methods but no code related to what is being
191+
tested should be within the page object.
192+
193+
There is one, single, verification which can, and should, be within the page
194+
object and that is to verify that the page, and possibly critical elements on
195+
the page, were loaded correctly. This verification should be done while
196+
instantiating the page object. In the examples above, both the SignInPage and
197+
HomePage constructors check that the expected page is available and ready for
198+
requests from the test.
199+
200+
## Page Component Objects
201+
A page object does not necessarily need to represent all the parts of a
202+
page itself. The same principles used for page objects can be used to
203+
create "Page _Component_ Objects" that represent discrete chunks of the
204+
page and can be included in page objects. These component objects can
205+
provide references to the elements inside those discrete chunks, and
206+
methods to leverage the functionality provided by them.
207+
208+
For example, a Product page has multiple products.
209+
210+
```html
211+
<!-- Products Page -->
212+
<div class="header_container">
213+
<span class="title">Products</span>
214+
</div>
215+
216+
<div class="inventory_list">
217+
<div class="inventory_item">
218+
</div>
219+
<div class="inventory_item">
220+
</div>
221+
<div class="inventory_item">
222+
</div>
223+
<div class="inventory_item">
224+
</div>
225+
<div class="inventory_item">
226+
</div>
227+
<div class="inventory_item">
228+
</div>
229+
</div>
230+
```
231+
232+
Each product is a component of the Products page.
233+
234+
235+
```html
236+
<!-- Inventory Item -->
237+
<div class="inventory_item">
238+
<div class="inventory_item_name">Backpack</div>
239+
<div class="pricebar">
240+
<div class="inventory_item_price">$29.99</div>
241+
<button id="add-to-cart-backpack">Add to cart</button>
242+
</div>
243+
</div>
244+
```
245+
246+
The Product page HAS-A list of products. This relationship is called Composition. In simpler terms, something is _composed of_ another thing.
247+
248+
```java
249+
public abstract class BasePage {
250+
protected WebDriver driver;
251+
252+
public BasePage(WebDriver driver) {
253+
this.driver = driver;
254+
}
255+
}
256+
257+
// Page Object
258+
public class ProductsPage extends BasePage {
259+
public ProductsPage(WebDriver driver) {
260+
super(driver);
261+
// No assertions, throws an exception if the element is not loaded
262+
new WebDriverWait(driver, Duration.ofSeconds(3))
263+
.until(d -> d.findElement(By.className​("header_container")));
264+
}
265+
266+
// Returning a list of products is a service of the page
267+
public List<Product> getProducts() {
268+
return driver.findElements(By.className​("inventory_item"))
269+
.stream()
270+
.map(e -> new Product(e)) // Map WebElement to a product component
271+
.toList();
272+
}
273+
274+
// Return a specific product using a boolean-valued function (predicate)
275+
// This is the behavioral Strategy Pattern from GoF
276+
public Product getProduct(Predicate<Product> condition) {
277+
return getProducts()
278+
.stream()
279+
.filter(condition) // Filter by product name or price
280+
.findFirst()
281+
.orElseThrow();
282+
}
283+
}
284+
```
285+
286+
The Product component object is used inside the Products page object.
287+
288+
```java
289+
public abstract class BaseComponent {
290+
protected WebElement root;
291+
292+
public BaseComponent(WebElement root) {
293+
this.root = root;
294+
}
295+
}
296+
297+
// Page Component Object
298+
public class Product extends BaseComponent {
299+
// The root element contains the entire component
300+
public Product(WebElement root) {
301+
super(root); // inventory_item
302+
}
303+
304+
public String getName() {
305+
// Locating an element begins at the root of the component
306+
return root.findElement(By.className("inventory_item_name")).getText();
307+
}
308+
309+
public BigDecimal getPrice() {
310+
return new BigDecimal(
311+
root.findElement(By.className("inventory_item_price"))
312+
.getText()
313+
.replace("$", "")
314+
).setScale(2, RoundingMode.UNNECESSARY); // Sanitation and formatting
315+
}
316+
317+
public void addToCart() {
318+
root.findElement(By.id("add-to-cart-backpack")).click();
319+
}
320+
}
321+
```
322+
323+
So now, the products test would use the page object and the page component object as follows.
324+
325+
```java
326+
public class ProductsTest {
327+
@Test
328+
public void testProductInventory() {
329+
var productsPage = new ProductsPage(driver);
330+
var products = productsPage.getProducts();
331+
assertEquals(6, products.size()); // expected, actual
332+
}
333+
334+
@Test
335+
public void testProductPrices() {
336+
var productsPage = new ProductsPage(driver);
337+
338+
// Pass a lambda expression (predicate) to filter the list of products
339+
// The predicate or "strategy" is the behavior passed as parameter
340+
var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack"));
341+
var bikeLight = productsPage.getProduct(p -> p.getName().equals("Bike Light"));
342+
343+
assertEquals(new BigDecimal("29.99"), backpack.getPrice());
344+
assertEquals(new BigDecimal("9.99"), bikeLight.getPrice());
345+
}
346+
}
347+
```
348+
349+
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
161415

162-
ページオブジェクト自体は、検証やアサーションを行うべきではありません。
163-
これはテストの一部であり、常にページオブジェクトではなく、テストのコード内にある必要があります。
164-
ページオブジェクトには、ページの表現と、ページがメソッドを介して提供するサービスが含まれますが、テスト対象に関連するコードはページオブジェクト内に存在しないようにします。
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
448+
driver.findElement(usernameLocator).sendKeys(username);
449+
450+
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
451+
return this;
452+
}
453+
454+
// The login page allows the user to type their password into the password field
455+
public LoginPage typePassword(String password) {
456+
// This is the only place that "knows" how to enter a password
457+
driver.findElement(passwordLocator).sendKeys(password);
458+
459+
// 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+
```
165496

166-
ページオブジェクト内に存在する可能性のある単一の検証があります。
167-
これは、ページおよびページ上の重要な要素が正しく読み込まれたことを検証するためのものです。
168-
この検証は、ページオブジェクトをインスタンス化する間に実行する必要があります。
169-
上記の例では、SignInPageコンストラクターとHomePageコンストラクターの両方が期待するページを取得し、テストからの要求に対応できることを確認します。
170497

171-
ページオブジェクトは、必ずしもページ全体を表す必要はありません。
172-
ページオブジェクトデザインパターンは、ページ上のコンポーネントを表すために使用できます。
173-
AUTのページに複数のコンポーネントがある場合、コンポーネントごとに個別のページオブジェクトがあると、保守性が向上する場合があります。
498+
## Support in WebDriver
174499

175-
また、テストで使用できる他のデザインパターンがあります。
176-
ページファクトリを使用してページオブジェクトをインスタンス化するものもあります。
177-
これらすべてについて議論することは、このユーザーガイドの範囲を超えています。
178-
ここでは、読者にできることのいくつかを認識させるための概念を紹介したいだけです。
179-
前述のように、多くの人がこのトピックについてブログを書いていますし、読者がこれらのトピックに関するブログを検索することをお勧めします。
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.

‎website_and_docs/content/documentation/test_practices/encouraged/page_object_models.pt-br.md

Lines changed: 377 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,43 @@ aliases: [
99
]
1010
---
1111

12-
Objeto de página é um padrão de design que se tornou popular na automação de teste para
13-
melhorar a manutenção de teste e reduzir a duplicação de código. Um objeto de página é uma
14-
classe orientada a objetos que serve como uma interface para uma página de seu AUT.
15-
Os testes então usam os métodos desta classe de objeto de página sempre que precisam
16-
interagir com a interface do usuário dessa página. O benefício é que, se a IU mudar para
17-
a página, os próprios testes não precisam ser alterados, apenas o código dentro do
18-
o objeto da página precisa ser alterado. Posteriormente, todas as alterações para oferecer suporte a essa nova IU
19-
estão localizados em um só lugar.
20-
21-
O padrão de design do objeto de página oferece as seguintes vantagens:
22-
23-
* Há uma separação clara entre o código de teste e o código específico da página, como
24-
localizadores (ou seu uso se você estiver usando um mapa de interface do usuário) e layout.
25-
* Existe um único repositório para os serviços ou operações oferecidos pela página
26-
em vez de ter esses serviços espalhados pelos testes.
27-
28-
Em ambos os casos, isso permite qualquer modificação necessária devido a mudanças na IU
29-
ser feito em um só lugar. Informações úteis sobre esta técnica podem ser encontradas em
30-
vários blogs, já que esse ‘padrão de design de teste’ está se tornando amplamente usado. Nós
31-
incentivamos o leitor que deseja saber mais a pesquisar blogs na internet
32-
nesse assunto. Muitos escreveram sobre este padrão de design e podem fornecer
33-
dicas úteis que vão além do escopo deste guia do usuário. Para começar, no entanto,
34-
vamos ilustrar objetos de página com um exemplo simples.
35-
36-
Primeiro, considere um exemplo, típico de automação de teste, que não usa um
37-
objeto de página:
12+
Note: this page has merged contents from multiple sources, including
13+
the [Selenium wiki](https://github.com/SeleniumHQ/selenium/wiki/PageObjects)
14+
15+
## Overview
16+
17+
Within your web app's UI, there are areas where your tests interact with.
18+
A Page Object only models these as objects within the test code.
19+
This reduces the amount of duplicated code and means that if the UI changes,
20+
the fix needs only to be applied in one place.
21+
22+
Page Object is a Design Pattern that has become popular in test automation for
23+
enhancing test maintenance and reducing code duplication. A page object is an
24+
object-oriented class that serves as an interface to a page of your AUT. The
25+
tests then use the methods of this page object class whenever they need to
26+
interact with the UI of that page. The benefit is that if the UI changes for
27+
the page, the tests themselves don’t need to change, only the code within the
28+
page object needs to change. Subsequently, all changes to support that new UI
29+
are located in one place.
30+
31+
### Advantages
32+
33+
* There is a clean separation between the test code and page-specific code, such as
34+
locators (or their use if you’re using a UI Map) and layout.
35+
* There is a single repository for the services or operations the page offers
36+
rather than having these services scattered throughout the tests.
37+
38+
In both cases, this allows any modifications required due to UI changes to all
39+
be made in one place. Helpful information on this technique can be found on
40+
numerous blogs as this ‘test design pattern’ is becoming widely used. We
41+
encourage readers who wish to know more to search the internet for blogs
42+
on this subject. Many have written on this design pattern and can provide
43+
helpful tips beyond the scope of this user guide. To get you started,
44+
we’ll illustrate page objects with a simple example.
45+
46+
### Examples
47+
First, consider an example, typical of test automation, that does not use a
48+
page object:
3849

3950
```java
4051
/***
@@ -43,36 +54,36 @@ objeto de página:
4354
public class Login {
4455

4556
public void testLogin() {
46-
// preenche dados de login na página de entrada
57+
// fill login data on sign-in page
4758
driver.findElement(By.name("user_name")).sendKeys("userName");
4859
driver.findElement(By.name("password")).sendKeys("my supersecret password");
4960
driver.findElement(By.name("sign-in")).click();
5061

51-
// verifica que a tag h1 é "Hello userName" após o login
62+
// verify h1 tag is "Hello userName" after login
5263
driver.findElement(By.tagName("h1")).isDisplayed();
5364
assertThat(driver.findElement(By.tagName("h1")).getText(), is("Hello userName"));
5465
}
5566
}
5667
```
5768

58-
Há dois problemas com esta abordagem.
69+
There are two problems with this approach.
5970

60-
* Não há separação entre o método de teste e os localizadores AUT (IDs neste exemplo);
61-
ambos estão interligados em um único método. Se a IU da aplicação muda
62-
seus identificadores, layout ou como um login é inserido e processado, o próprio teste
63-
deve mudar.
64-
* Os localizadores do ID estariam espalhados em vários testes, em todos os testes que precisassem
65-
usar esta página de login.
71+
* There is no separation between the test method and the AUT’s locators (IDs in
72+
this example); both are intertwined in a single method. If the AUT’s UI changes
73+
its identifiers, layout, or how a login is input and processed, the test itself
74+
must change.
75+
* The ID-locators would be spread in multiple tests, in all tests that had to
76+
use this login page.
6677

67-
Aplicando as técnicas de objeto de página, este exemplo poderia ser reescrito assim
68-
no exemplo a seguir de um objeto de página para uma página de Sign-in.
78+
Applying the page object techniques, this example could be rewritten like this
79+
in the following example of a page object for a Sign-in page.
6980

7081
```java
7182
import org.openqa.selenium.By;
7283
import org.openqa.selenium.WebDriver;
7384

7485
/**
75-
* Page Object encapsula a página de login.
86+
* Page Object encapsulates the Sign-in page.
7687
*/
7788
public class SignInPage {
7889
protected WebDriver driver;
@@ -86,14 +97,14 @@ public class SignInPage {
8697

8798
public SignInPage(WebDriver driver){
8899
this.driver = driver;
89-
if (!driver.getTitle().equals("Sign In Page")) {
100+
if (!driver.getTitle().equals("Sign In Page")) {
90101
throw new IllegalStateException("This is not Sign In Page," +
91102
" current page is: " + driver.getCurrentUrl());
92103
}
93104
}
94105

95106
/**
96-
* Login como um usuário válido
107+
* Login as valid user
97108
*
98109
* @param userName
99110
* @param password
@@ -108,14 +119,14 @@ public class SignInPage {
108119
}
109120
```
110121

111-
e o objeto de página de uma página inicial pode ter a seguinte aparência.
122+
and page object for a Home page could look like this.
112123

113124
```java
114125
import org.openqa.selenium.By;
115126
import org.openqa.selenium.WebDriver;
116127

117128
/**
118-
* Page Object encapsula a Home Page
129+
* Page Object encapsulates the Home Page
119130
*/
120131
public class HomePage {
121132
protected WebDriver driver;
@@ -141,16 +152,16 @@ public class HomePage {
141152
}
142153

143154
public HomePage manageProfile() {
144-
// Encapsulamento da página para gerenciar a funcionalidade do perfil
155+
// Page encapsulation to manage profile functionality
145156
return new HomePage(driver);
146157
}
147-
/* Mais métodos fornecendo o serviços representados pela Home Page
148-
do usuário logado. Esses métodos por sua vez podem retornar mais Page Objects
149-
por exemplo clicar no botão Compor Email poderia retornar um objeto ComposeMail */
158+
/* More methods offering the services represented by Home Page
159+
of Logged User. These methods in turn might return more Page Objects
160+
for example click on Compose mail button could return ComposeMail class object */
150161
}
151162
```
152163

153-
Portanto, agora, o teste de login usaria esses dois objetos de página da seguinte maneira.
164+
So now, the login test would use these two page objects as follows.
154165

155166
```java
156167
/***
@@ -168,36 +179,322 @@ public class TestLogin {
168179
}
169180
```
170181

171-
Há muita flexibilidade em como os objetos de página podem ser projetados, mas
172-
existem algumas regras básicas para obter a manutenção desejada de seu
173-
código de teste.
174-
175-
Os próprios objetos de página nunca devem fazer verificações ou afirmações. Isto é
176-
parte do seu teste e deve estar sempre dentro do código do teste, nunca em um objeto de página.
177-
O objeto da página conterá a representação da página, e o
178-
serviços que a página fornece por meio de métodos, mas nenhum código relacionado ao que está sendo
179-
testado deve estar dentro do objeto de página.
180-
181-
Há uma única verificação que pode e deve estar dentro do objeto de página e que é para verificar se a página
182-
e, possivelmente, elementos críticos em a página, foram carregados corretamente.
183-
Esta verificação deve ser feita enquanto instanciar o objeto de página.
184-
Nos exemplos acima, ambos SignInPage e os construtores da HomePage verificam se a página
185-
esperada está disponível e pronta para solicitações do teste.
186-
187-
Um objeto de página não precisa necessariamente representar todas as partes da página em si.
188-
Os mesmos princípios usados para objetos de página podem ser usados para
189-
criar "Objetos de _Componente_ de Página" que representam pedaços discretos da
190-
página e podem ser incluídos em objetos de página. Esses objetos de componentes podem
191-
fornecer referências aos elementos dentro desses blocos discretos, e
192-
métodos para utilizar a funcionalidade fornecida por eles. Você também pode
193-
aninhar objetos de componentes dentro de outros objetos de componentes para páginas mais complexas.
194-
Se uma página na aplicação tem vários componentes, ou
195-
componentes usados em todo o site (por exemplo, uma barra de navegação), então
196-
pode melhorar a manutenção e reduzir a duplicação de código.
197-
198-
Existem outros padrões de design que também podem ser usados em testes. Alguns usam um
199-
Page Factory para instanciar seus objetos de página. Discutir tudo isso é
200-
além do escopo deste guia do usuário. Aqui, queremos apenas apresentar o
201-
conceitos para tornar o leitor ciente de algumas coisas que podem ser feitas. Como
202-
foi mencionado anteriormente, muitos escreveram sobre este tópico e nós encorajamos o
203-
leitor para pesquisar blogs sobre esses tópicos.
182+
There is a lot of flexibility in how the page objects may be designed, but
183+
there are a few basic rules for getting the desired maintainability of your
184+
test code.
185+
186+
## Assertions in Page Objects
187+
Page objects themselves should never make verifications or assertions. This is
188+
part of your test and should always be within the test’s code, never in an page
189+
object. The page object will contain the representation of the page, and the
190+
services the page provides via methods but no code related to what is being
191+
tested should be within the page object.
192+
193+
There is one, single, verification which can, and should, be within the page
194+
object and that is to verify that the page, and possibly critical elements on
195+
the page, were loaded correctly. This verification should be done while
196+
instantiating the page object. In the examples above, both the SignInPage and
197+
HomePage constructors check that the expected page is available and ready for
198+
requests from the test.
199+
200+
## Page Component Objects
201+
A page object does not necessarily need to represent all the parts of a
202+
page itself. The same principles used for page objects can be used to
203+
create "Page _Component_ Objects" that represent discrete chunks of the
204+
page and can be included in page objects. These component objects can
205+
provide references to the elements inside those discrete chunks, and
206+
methods to leverage the functionality provided by them.
207+
208+
For example, a Product page has multiple products.
209+
210+
```html
211+
<!-- Products Page -->
212+
<div class="header_container">
213+
<span class="title">Products</span>
214+
</div>
215+
216+
<div class="inventory_list">
217+
<div class="inventory_item">
218+
</div>
219+
<div class="inventory_item">
220+
</div>
221+
<div class="inventory_item">
222+
</div>
223+
<div class="inventory_item">
224+
</div>
225+
<div class="inventory_item">
226+
</div>
227+
<div class="inventory_item">
228+
</div>
229+
</div>
230+
```
231+
232+
Each product is a component of the Products page.
233+
234+
235+
```html
236+
<!-- Inventory Item -->
237+
<div class="inventory_item">
238+
<div class="inventory_item_name">Backpack</div>
239+
<div class="pricebar">
240+
<div class="inventory_item_price">$29.99</div>
241+
<button id="add-to-cart-backpack">Add to cart</button>
242+
</div>
243+
</div>
244+
```
245+
246+
The Product page HAS-A list of products. This relationship is called Composition. In simpler terms, something is _composed of_ another thing.
247+
248+
```java
249+
public abstract class BasePage {
250+
protected WebDriver driver;
251+
252+
public BasePage(WebDriver driver) {
253+
this.driver = driver;
254+
}
255+
}
256+
257+
// Page Object
258+
public class ProductsPage extends BasePage {
259+
public ProductsPage(WebDriver driver) {
260+
super(driver);
261+
// No assertions, throws an exception if the element is not loaded
262+
new WebDriverWait(driver, Duration.ofSeconds(3))
263+
.until(d -> d.findElement(By.className​("header_container")));
264+
}
265+
266+
// Returning a list of products is a service of the page
267+
public List<Product> getProducts() {
268+
return driver.findElements(By.className​("inventory_item"))
269+
.stream()
270+
.map(e -> new Product(e)) // Map WebElement to a product component
271+
.toList();
272+
}
273+
274+
// Return a specific product using a boolean-valued function (predicate)
275+
// This is the behavioral Strategy Pattern from GoF
276+
public Product getProduct(Predicate<Product> condition) {
277+
return getProducts()
278+
.stream()
279+
.filter(condition) // Filter by product name or price
280+
.findFirst()
281+
.orElseThrow();
282+
}
283+
}
284+
```
285+
286+
The Product component object is used inside the Products page object.
287+
288+
```java
289+
public abstract class BaseComponent {
290+
protected WebElement root;
291+
292+
public BaseComponent(WebElement root) {
293+
this.root = root;
294+
}
295+
}
296+
297+
// Page Component Object
298+
public class Product extends BaseComponent {
299+
// The root element contains the entire component
300+
public Product(WebElement root) {
301+
super(root); // inventory_item
302+
}
303+
304+
public String getName() {
305+
// Locating an element begins at the root of the component
306+
return root.findElement(By.className("inventory_item_name")).getText();
307+
}
308+
309+
public BigDecimal getPrice() {
310+
return new BigDecimal(
311+
root.findElement(By.className("inventory_item_price"))
312+
.getText()
313+
.replace("$", "")
314+
).setScale(2, RoundingMode.UNNECESSARY); // Sanitation and formatting
315+
}
316+
317+
public void addToCart() {
318+
root.findElement(By.id("add-to-cart-backpack")).click();
319+
}
320+
}
321+
```
322+
323+
So now, the products test would use the page object and the page component object as follows.
324+
325+
```java
326+
public class ProductsTest {
327+
@Test
328+
public void testProductInventory() {
329+
var productsPage = new ProductsPage(driver);
330+
var products = productsPage.getProducts();
331+
assertEquals(6, products.size()); // expected, actual
332+
}
333+
334+
@Test
335+
public void testProductPrices() {
336+
var productsPage = new ProductsPage(driver);
337+
338+
// Pass a lambda expression (predicate) to filter the list of products
339+
// The predicate or "strategy" is the behavior passed as parameter
340+
var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack"));
341+
var bikeLight = productsPage.getProduct(p -> p.getName().equals("Bike Light"));
342+
343+
assertEquals(new BigDecimal("29.99"), backpack.getPrice());
344+
assertEquals(new BigDecimal("9.99"), bikeLight.getPrice());
345+
}
346+
}
347+
```
348+
349+
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
448+
driver.findElement(usernameLocator).sendKeys(username);
449+
450+
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
451+
return this;
452+
}
453+
454+
// The login page allows the user to type their password into the password field
455+
public LoginPage typePassword(String password) {
456+
// This is the only place that "knows" how to enter a password
457+
driver.findElement(passwordLocator).sendKeys(password);
458+
459+
// 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.

‎website_and_docs/content/documentation/test_practices/encouraged/page_object_models.zh-cn.md

Lines changed: 368 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,43 @@ aliases: [
99
]
1010
---
1111

12-
13-
PO(page object)设计模式是在自动化中已经流行起来的一种易于维护和减少代码的设计模式. 在自动化测试中, PO对象作为一个与页面交互的接口.
14-
测试中需要与页面的UI进行交互时, 便调用PO的方法. 这样做的好处是, 如果页面的UI发生了更改,那么测试用例本身不需要更改, 只需更改PO中的代码即可.
15-
16-
PO设计模式具有以下优点:
17-
18-
* 测试代码与页面的定位代码(如定位器或者其他的映射)相分离.
19-
* 该页面提供的方法或元素在一个独立的类中, 而不是将这些方法或元素分散在整个测试中.
20-
21-
这允许在一个地方修改由于UI变化所带来的所有修改. 随着这种"测试设计模式"的广泛使用, 可以在众多博客中找到有关此技术的有用信息.
22-
我们鼓励希望了解更多信息的读者在互联网上搜索有关此主题的博客. 许多人已经写过这种设计模式, 并且可以提供超出本用户指南范围的有用提示.
23-
不过, 为了让您入门, 我们将通过一个简单的示例来说明页面对象.
24-
25-
首先, 思考一个不使用PO模式的自动化测试的典型案例:
12+
Note: this page has merged contents from multiple sources, including
13+
the [Selenium wiki](https://github.com/SeleniumHQ/selenium/wiki/PageObjects)
14+
15+
## Overview
16+
17+
Within your web app's UI, there are areas where your tests interact with.
18+
A Page Object only models these as objects within the test code.
19+
This reduces the amount of duplicated code and means that if the UI changes,
20+
the fix needs only to be applied in one place.
21+
22+
Page Object is a Design Pattern that has become popular in test automation for
23+
enhancing test maintenance and reducing code duplication. A page object is an
24+
object-oriented class that serves as an interface to a page of your AUT. The
25+
tests then use the methods of this page object class whenever they need to
26+
interact with the UI of that page. The benefit is that if the UI changes for
27+
the page, the tests themselves don’t need to change, only the code within the
28+
page object needs to change. Subsequently, all changes to support that new UI
29+
are located in one place.
30+
31+
### Advantages
32+
33+
* There is a clean separation between the test code and page-specific code, such as
34+
locators (or their use if you’re using a UI Map) and layout.
35+
* There is a single repository for the services or operations the page offers
36+
rather than having these services scattered throughout the tests.
37+
38+
In both cases, this allows any modifications required due to UI changes to all
39+
be made in one place. Helpful information on this technique can be found on
40+
numerous blogs as this ‘test design pattern’ is becoming widely used. We
41+
encourage readers who wish to know more to search the internet for blogs
42+
on this subject. Many have written on this design pattern and can provide
43+
helpful tips beyond the scope of this user guide. To get you started,
44+
we’ll illustrate page objects with a simple example.
45+
46+
### Examples
47+
First, consider an example, typical of test automation, that does not use a
48+
page object:
2649

2750
```java
2851
/***
@@ -31,24 +54,29 @@ PO设计模式具有以下优点:
3154
public class Login {
3255

3356
public void testLogin() {
34-
// 在登录页面上填写登录数据
57+
// fill login data on sign-in page
3558
driver.findElement(By.name("user_name")).sendKeys("userName");
3659
driver.findElement(By.name("password")).sendKeys("my supersecret password");
3760
driver.findElement(By.name("sign-in")).click();
3861

39-
// 登录后验证h1标签是否为Hello userName
62+
// verify h1 tag is "Hello userName" after login
4063
driver.findElement(By.tagName("h1")).isDisplayed();
4164
assertThat(driver.findElement(By.tagName("h1")).getText(), is("Hello userName"));
4265
}
4366
}
4467
```
4568

46-
这种方法有两个问题.
69+
There are two problems with this approach.
4770

48-
* 测试方法与定位器 (在此实例中为By.name)耦合过于严重. 如果测试的用户界面更改了其定位器或登录名的输入和处理方式, 则测试本身必须进行更改.
49-
* 在对登录页面的所有测试中, 同一个定位器会散布在其中.
71+
* There is no separation between the test method and the AUT’s locators (IDs in
72+
this example); both are intertwined in a single method. If the AUT’s UI changes
73+
its identifiers, layout, or how a login is input and processed, the test itself
74+
must change.
75+
* The ID-locators would be spread in multiple tests, in all tests that had to
76+
use this login page.
5077

51-
可以在以下登录页面的示例中应用PO设计模式重写此示例.
78+
Applying the page object techniques, this example could be rewritten like this
79+
in the following example of a page object for a Sign-in page.
5280

5381
```java
5482
import org.openqa.selenium.By;
@@ -69,7 +97,7 @@ public class SignInPage {
6997

7098
public SignInPage(WebDriver driver){
7199
this.driver = driver;
72-
if (!driver.getTitle().equals("Sign In Page")) {
100+
if (!driver.getTitle().equals("Sign In Page")) {
73101
throw new IllegalStateException("This is not Sign In Page," +
74102
" current page is: " + driver.getCurrentUrl());
75103
}
@@ -91,7 +119,7 @@ public class SignInPage {
91119
}
92120
```
93121

94-
Home page的PO如下所示.
122+
and page object for a Home page could look like this.
95123

96124
```java
97125
import org.openqa.selenium.By;
@@ -127,12 +155,13 @@ public class HomePage {
127155
// Page encapsulation to manage profile functionality
128156
return new HomePage(driver);
129157
}
130-
/* 提供登录用户主页所代表的服务的更多方法. 这些方法可能会返回更多页面对象.
131-
例如, 单击"撰写邮件"按钮可以返回ComposeMail类对象 */
158+
/* More methods offering the services represented by Home Page
159+
of Logged User. These methods in turn might return more Page Objects
160+
for example click on Compose mail button could return ComposeMail class object */
132161
}
133162
```
134163

135-
那么, 接下来的登录测试用例将使用这两个页面对象.
164+
So now, the login test would use these two page objects as follows.
136165

137166
```java
138167
/***
@@ -150,18 +179,322 @@ public class TestLogin {
150179
}
151180
```
152181

153-
PO的设计方式具有很大的灵活性, 但是有一些基本规则可以使测试代码具有理想的可维护性.
182+
There is a lot of flexibility in how the page objects may be designed, but
183+
there are a few basic rules for getting the desired maintainability of your
184+
test code.
185+
186+
## Assertions in Page Objects
187+
Page objects themselves should never make verifications or assertions. This is
188+
part of your test and should always be within the test’s code, never in an page
189+
object. The page object will contain the representation of the page, and the
190+
services the page provides via methods but no code related to what is being
191+
tested should be within the page object.
192+
193+
There is one, single, verification which can, and should, be within the page
194+
object and that is to verify that the page, and possibly critical elements on
195+
the page, were loaded correctly. This verification should be done while
196+
instantiating the page object. In the examples above, both the SignInPage and
197+
HomePage constructors check that the expected page is available and ready for
198+
requests from the test.
199+
200+
## Page Component Objects
201+
A page object does not necessarily need to represent all the parts of a
202+
page itself. The same principles used for page objects can be used to
203+
create "Page _Component_ Objects" that represent discrete chunks of the
204+
page and can be included in page objects. These component objects can
205+
provide references to the elements inside those discrete chunks, and
206+
methods to leverage the functionality provided by them.
207+
208+
For example, a Product page has multiple products.
209+
210+
```html
211+
<!-- Products Page -->
212+
<div class="header_container">
213+
<span class="title">Products</span>
214+
</div>
215+
216+
<div class="inventory_list">
217+
<div class="inventory_item">
218+
</div>
219+
<div class="inventory_item">
220+
</div>
221+
<div class="inventory_item">
222+
</div>
223+
<div class="inventory_item">
224+
</div>
225+
<div class="inventory_item">
226+
</div>
227+
<div class="inventory_item">
228+
</div>
229+
</div>
230+
```
231+
232+
Each product is a component of the Products page.
233+
234+
235+
```html
236+
<!-- Inventory Item -->
237+
<div class="inventory_item">
238+
<div class="inventory_item_name">Backpack</div>
239+
<div class="pricebar">
240+
<div class="inventory_item_price">$29.99</div>
241+
<button id="add-to-cart-backpack">Add to cart</button>
242+
</div>
243+
</div>
244+
```
245+
246+
The Product page HAS-A list of products. This relationship is called Composition. In simpler terms, something is _composed of_ another thing.
247+
248+
```java
249+
public abstract class BasePage {
250+
protected WebDriver driver;
251+
252+
public BasePage(WebDriver driver) {
253+
this.driver = driver;
254+
}
255+
}
256+
257+
// Page Object
258+
public class ProductsPage extends BasePage {
259+
public ProductsPage(WebDriver driver) {
260+
super(driver);
261+
// No assertions, throws an exception if the element is not loaded
262+
new WebDriverWait(driver, Duration.ofSeconds(3))
263+
.until(d -> d.findElement(By.className​("header_container")));
264+
}
265+
266+
// Returning a list of products is a service of the page
267+
public List<Product> getProducts() {
268+
return driver.findElements(By.className​("inventory_item"))
269+
.stream()
270+
.map(e -> new Product(e)) // Map WebElement to a product component
271+
.toList();
272+
}
273+
274+
// Return a specific product using a boolean-valued function (predicate)
275+
// This is the behavioral Strategy Pattern from GoF
276+
public Product getProduct(Predicate<Product> condition) {
277+
return getProducts()
278+
.stream()
279+
.filter(condition) // Filter by product name or price
280+
.findFirst()
281+
.orElseThrow();
282+
}
283+
}
284+
```
285+
286+
The Product component object is used inside the Products page object.
287+
288+
```java
289+
public abstract class BaseComponent {
290+
protected WebElement root;
291+
292+
public BaseComponent(WebElement root) {
293+
this.root = root;
294+
}
295+
}
296+
297+
// Page Component Object
298+
public class Product extends BaseComponent {
299+
// The root element contains the entire component
300+
public Product(WebElement root) {
301+
super(root); // inventory_item
302+
}
303+
304+
public String getName() {
305+
// Locating an element begins at the root of the component
306+
return root.findElement(By.className("inventory_item_name")).getText();
307+
}
308+
309+
public BigDecimal getPrice() {
310+
return new BigDecimal(
311+
root.findElement(By.className("inventory_item_price"))
312+
.getText()
313+
.replace("$", "")
314+
).setScale(2, RoundingMode.UNNECESSARY); // Sanitation and formatting
315+
}
316+
317+
public void addToCart() {
318+
root.findElement(By.id("add-to-cart-backpack")).click();
319+
}
320+
}
321+
```
322+
323+
So now, the products test would use the page object and the page component object as follows.
324+
325+
```java
326+
public class ProductsTest {
327+
@Test
328+
public void testProductInventory() {
329+
var productsPage = new ProductsPage(driver);
330+
var products = productsPage.getProducts();
331+
assertEquals(6, products.size()); // expected, actual
332+
}
333+
334+
@Test
335+
public void testProductPrices() {
336+
var productsPage = new ProductsPage(driver);
337+
338+
// Pass a lambda expression (predicate) to filter the list of products
339+
// The predicate or "strategy" is the behavior passed as parameter
340+
var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack"));
341+
var bikeLight = productsPage.getProduct(p -> p.getName().equals("Bike Light"));
342+
343+
assertEquals(new BigDecimal("29.99"), backpack.getPrice());
344+
assertEquals(new BigDecimal("9.99"), bikeLight.getPrice());
345+
}
346+
}
347+
```
348+
349+
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.
154413

155-
PO本身绝不应进行判断或断言. 判断和断言是测试的一部分, 应始终在测试的代码内, 而不是在PO中.
156-
PO用来包含页面的表示形式, 以及页面通过方法提供的服务, 但是与PO无关的测试代码不应包含在其中.
414+
## Summary
157415

158-
实例化PO时, 应进行一次验证, 即验证页面以及页面上可能的关键元素是否已正确加载.
159-
在上面的示例中, SignInPage和HomePage的构造函数均检查预期的页面是否可用并准备接受测试请求.
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
448+
driver.findElement(usernameLocator).sendKeys(username);
449+
450+
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
451+
return this;
452+
}
453+
454+
// The login page allows the user to type their password into the password field
455+
public LoginPage typePassword(String password) {
456+
// This is the only place that "knows" how to enter a password
457+
driver.findElement(passwordLocator).sendKeys(password);
458+
459+
// 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+
```
160496

161-
PO不一定需要代表整个页面. PO设计模式可用于表示页面上的组件.
162-
如果自动化测试中的页面包含多个组件, 则每个组件都有单独的页面对象, 则可以提高可维护性.
163497

164-
还有其他设计模式也可以在测试中使用. 一些使用页面工厂实例化其页面对象. 讨论所有这些都不在本用户指南的范围之内.
165-
在这里, 我们只想介绍一些概念, 以使读者了解可以完成的一些事情.
166-
如前所述, 许多人都在此主题上写博客, 我们鼓励读者搜索有关这些主题的博客.
498+
## Support in WebDriver
167499

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.

0 commit comments

Comments
 (0)
Please sign in to comment.