Skip to content

Add tip "7: use a Page Object" in Selenide #710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions frontend/tips/7-use-a-page-object/_selenide.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import TipCode from "@site/src/components/tipCode";

## TL;DR - Show Me The Code

<TipCode
codePath={"7-use-a-page-object/code/selenide/src/test/java/com/elemental/selenium/PageObjectTest.java"}
language={"java"}/>

## Code Walkthrough

### Importing Libraries

First let's import our requisite classes:
* for annotations (e.g., `org.junit.jupiter.api.Test`),
* opening the browser with Selenide (e.g., `com.codeborne.selenide.Selenide.open`),

Next, we'll start our test.

### Example 1: pageObject

Our first example `pageObject` shows how we can open an url and create a page object instance for it.

Instead of a usual Selenide method `open(url)`, you can use method `open(url, Class)` that returns a newly created page object instance:
> var page = open("https://the-internet.herokuapp.com/dynamic_loading/2", DynamicLoadingPage.class);

Note that `page` is fully initialized object. You don't need to call any "page factories", initializers etc. - Selenide's already done it for you.

Now you can use this `page` as any other Java object - call its methods (or even pass it as parameter to other methods):
> page.start();

and
> page.waitForCompletion("Hello World!");

Take a look how this page object is implemented - it's pretty simple:

<details>
<summary>Toggle to see the <code>DynamicLoadingPage.java</code></summary>
<div>
<TipCode
codePath={"7-use-a-page-object/code/selenide/src/test/java/com/elemental/selenium/DynamicLoadingPage.java"}
language={"java"}/>
</div>
</details>


Web elements can be declared as fields in page object class.

NB! Please make the fields private. In OOP (Object-oriented programming),
* objects expose their behaviour via methods, but
* objects should NOT expose their state via public fields.

### Example 2: alternativePageObject

This example just shows a bit different page object that has fields of type `SelenideElement` instead of `By`.
It's a matter of taste which variant you like more.

<details>
<summary>Toggle to see the <code>AlternativeDynamicLoadingPage.java</code></summary>
<div>
<TipCode
codePath={"7-use-a-page-object/code/selenide/src/test/java/com/elemental/selenium/AlternativeDynamicLoadingPage.java"}
language={"java"}/>
</div>
</details>

### Example 3: anotherPageObject

This example just shows a bit different page object that has no fields at all. :)

Yes, it's also possible. And it's often even better because the code is shorter and simpler.

<details>
<summary>Toggle to see the <code>AnotherDynamicLoadingPage.java</code></summary>
<div>
<TipCode
codePath={"7-use-a-page-object/code/selenide/src/test/java/com/elemental/selenium/AnotherDynamicLoadingPage.java"}
language={"java"}/>
</div>
</details>

### Executing the Test

Before executing the test, we need to make sure the required dependencies are declared on the `pom.xml` file.

<details>
<summary>Toggle to see the <code>pom.xml</code> file.</summary>
<div>
<TipCode codePath={"7-use-a-page-object/code/selenide/pom.xml"} language={"xml"}/>
</div>
</details>

Finally, we can run the test by executing `mvn test` from the command-line.
39 changes: 39 additions & 0 deletions frontend/tips/7-use-a-page-object/code/selenide/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.elemental.selenium</groupId>
<artifactId>tips-selenide-page-object</artifactId>
<version>1.0.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>selenide</artifactId>
<version>7.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.elemental.selenium;

import com.codeborne.selenide.SelenideElement;

import java.time.Duration;

import static com.codeborne.selenide.Condition.text;
import static com.codeborne.selenide.Condition.visible;
import static com.codeborne.selenide.Selenide.$;

public class AlternativeDynamicLoadingPage {
private final SelenideElement startButton= $("#start button");
private final SelenideElement finishButton= $("#finish");

public void start() {
startButton.click();
}

public void waitForCompletion(String expectedText) {
finishButton.shouldBe(visible, Duration.ofSeconds(10));
finishButton.shouldHave(text(expectedText));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.elemental.selenium;

import java.time.Duration;

import static com.codeborne.selenide.Condition.text;
import static com.codeborne.selenide.Condition.visible;
import static com.codeborne.selenide.Selenide.$;

/**
* Page objects don't necessarily have to contain fields.
* It's totally ok to only have methods.
*
* Remember, object exposes its behavior via METHODS, not fields!
*/
public class AnotherDynamicLoadingPage {

public void start() {
$("#start button").click();
}

public void waitForCompletion(String expectedText) {
$("#finish")
.shouldBe(visible, Duration.ofSeconds(10))
.shouldHave(text(expectedText));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.elemental.selenium;

import org.openqa.selenium.By;

import java.time.Duration;

import static com.codeborne.selenide.Condition.text;
import static com.codeborne.selenide.Condition.visible;
import static com.codeborne.selenide.Selenide.$;

public class DynamicLoadingPage {
private final By startButton = By.cssSelector("#start button");
private final By finishButton = By.cssSelector("#finish");

public void start() {
$(startButton).click();
}

public void waitForCompletion(String expectedText) {
$(finishButton).shouldBe(visible, Duration.ofSeconds(10));
$(finishButton).shouldHave(text(expectedText));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.elemental.selenium;

import org.junit.jupiter.api.Test;

import static com.codeborne.selenide.Selenide.open;

public class PageObjectTest {
@Test
void pageObject() {
var page = open("https://the-internet.herokuapp.com/dynamic_loading/2", DynamicLoadingPage.class);
page.start();
page.waitForCompletion("Hello World!");
}

@Test
void alternativePageObject() {
var page = open("https://the-internet.herokuapp.com/dynamic_loading/2", AlternativeDynamicLoadingPage.class);
page.start();
page.waitForCompletion("Hello World!");
}

@Test
void anotherPageObject() {
var page = open("https://the-internet.herokuapp.com/dynamic_loading/2", AnotherDynamicLoadingPage.class);
page.start();
page.waitForCompletion("Hello World!");
}
}
23 changes: 15 additions & 8 deletions frontend/tips/7-use-a-page-object/main.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,24 @@ import DisplayTips from '@site/src/components/displayTips';

<DisplayTips languages={[
{label: 'Ruby', value: 'ruby'},
{label: 'Selenide', value: 'selenide'},
]}/>


## About The Author
## About The Authors

Dave Haeffner is the original writer of Elemental Selenium -- a free, once weekly Selenium tip newsletter that's read
by thousands of testing professionals. He also created and maintains the-internet (an open-source web app that's
perfect for writing automated tests against).
import DefaultAvatar from "@site/src/components/defaultAvatar";

Dave has helped numerous companies successfully implement automated acceptance testing; including The Motley Fool,
ManTech International, Sittercity, and Animoto. He is also an active member of the Selenium project and has spoken
at numerous conferences and meetups around the world about automated acceptance testing.
<DefaultAvatar
name="Dave Haeffner"
imgSrc={"/img/authors/dave-haeffner.jpeg#author-img"}
description={"the original writer of Elemental Selenium"}
url={"https://github.com/tourdedave"}
/>

![Dave Haeffner profile picture](/img/authors/dave-haeffner.jpeg#author-img 'a title')
<DefaultAvatar
name="Andrei Solntsev"
imgSrc={"https://github.com/asolntsev.png"}
description={"Contributed the Selenide code for this tip."}
url={"https://github.com/asolntsev"}
/>