Skip to content

Commit b9d1956

Browse files
authored
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

+146-1
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

0 commit comments

Comments
 (0)