Skip to content

Commit f1b5d18

Browse files
authored
Update README.md
1 parent 645e5ae commit f1b5d18

File tree

1 file changed

+138
-142
lines changed

1 file changed

+138
-142
lines changed

README.md

Lines changed: 138 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -100,36 +100,52 @@ In the rest of this article, we will walk through some of the highlights of both
100100
Both variations of the sample project uses the sample Cucumber scenario. In this scenario, Sergey (who likes to search for stuff) is performing a search on the DuckDuckGo search engine:
101101

102102
```Gherkin
103-
Feature: Search by keyword
103+
Feature: Verify Jungle socks home page
104104
105-
Scenario: Searching for a term
106-
Given Sergey is on the DuckDuckGo home page
107-
When he searches for "cucumber"
108-
Then all the result titles should contain the word "cucumber"
105+
Scenario: Verify jungle socks home page navigation
106+
Given I open the jungle socks home page url
107+
Then I should see jungle socks home page title as "JungleSocks"
108+
Then I should see jungle socks home page header as "Welcome To Jungle Socks!"
109109
```
110110

111111
This scenario lets us explore a few of the new Cucumber 4 expressions. Cucumber 4 supports both the classic regular expressions, and the new _Cucumber Expressions_, which are more readable albeit not as powerful in some cases.
112112

113113
The glue code for this scenario uses both regular expressions and cucumber expressions. The glue code looks this this:
114114

115115
```java
116-
@Given("^(?:.*) is on the DuckDuckGo home page")
117-
public void i_am_on_the_DuckDuckGo_home_page() {
118-
navigateTo.theDuckDuckGoHomePage();
119-
}
120-
121-
@When("(s)he searches for {string}")
122-
public void i_search_for(String term) {
123-
searchFor.term(term);
124-
}
125-
126-
@Then("all the result titles should contain the word {string}")
127-
public void all_the_result_titles_should_contain_the_word(String term) {
128-
assertThat(searchResult.titles())
129-
.matches(results -> results.size() > 0)
130-
.allMatch(title ->
131-
textOf(title).containsIgnoringCase(term));
132-
}
116+
@Steps
117+
HomePageActions homePageActions;
118+
119+
120+
@Given("I open the jungle socks home page url")
121+
public void i_open_the_jungle_socks_home_page_url()
122+
{
123+
homePageActions.openJungleSocksHomePageurl();
124+
}
125+
126+
@Then("I should see jungle socks home page title as {string}")
127+
public void i_should_see_jungle_socks_home_page(String title)
128+
{
129+
homePageActions.verifyHomePageJungleSocksTitle(title);
130+
}
131+
132+
@Then("I should see jungle socks home page header as {string}")
133+
public void i_should_see_jungle_socks_home_page_header_as(String header)
134+
{
135+
homePageActions.verifyJungleSocksHomePageHeader(header);
136+
}
137+
138+
@Then("I should see different products on jungle socks home page")
139+
public void i_should_see_different_products_on_jungle_socks_home_page(List productList)
140+
{
141+
homePageActions.verifyJungleSocksHomeProductDetails(productList);
142+
}
143+
144+
@Then("I should see product as {string} with price as {string} and instock quantity as {string}")
145+
public void i_should_see_product_as_with_price_as_and_instock_quantity_as(String productName, String price, String quantity)
146+
{
147+
homePageActions.verifyProductPriceAndQuantityByName(productName, price, quantity);
148+
}
133149
```
134150

135151
The `@Given` step uses a regular expression; the action class approach we use here is action-centric, not actor-centric, so we ignore the name of the actor.
@@ -144,152 +160,132 @@ The glue code shown above uses Serenity step libraries as _action classes_ to ma
144160
These classes are declared using the Serenity `@Steps` annotation, shown below:
145161
```java
146162
@Steps
147-
NavigateTo navigateTo;
148-
149-
@Steps
150-
SearchFor searchFor;
163+
HomePageActions homePageActions;
151164

152165
@Steps
153-
SearchResult searchResult;
166+
HomePageActions homePageActions;
167+
154168
```
155169

156170
The `@Steps`annotation tells Serenity to create a new instance of the class, and inject any other steps or page objects that this instance might need.
157171

158172
Each action class models a particular facet of user behaviour: navigating to a particular page, performing a search, or retrieving the results of a search. These classes are designed to be small and self-contained, which makes them more stable and easier to maintain.
159173

160-
The `NavigateTo` class is an example of a very simple action class. In a larger application, it might have some other methods related to high level navigation, but in our sample project, it just needs to open the DuckDuckGo home page:
161-
```java
162-
public class NavigateTo {
163-
164-
DuckDuckGoHomePage duckDuckGoHomePage;
165-
166-
@Step("Open the DuckDuckGo home page")
167-
public void theDuckDuckGoHomePage() {
168-
duckDuckGoHomePage.open();
169-
}
170-
}
171-
```
172-
173-
It does this using a standard Serenity Page Object. Page Objects are often very minimal, storing just the URL of the page itself:
174-
```java
175-
@DefaultUrl("https://duckduckgo.com")
176-
class DuckDuckGoHomePage extends PageObject {}
177-
```
178-
179-
The second class, `SearchFor`, is an interaction class. It needs to interact with the web page, and to enable this, we make the class extend the Serenity `UIInteractionSteps`. This gives the class full access to the powerful Serenity WebDriver API, including the `$()` method used below, which locates a web element using a `By` locator or an XPath or CSS expression:
180-
```java
181-
public class SearchFor extends UIInteractionSteps {
182-
183-
@Step("Search for term {0}")
184-
public void term(String term) {
185-
$(SearchForm.SEARCH_FIELD).clear();
186-
$(SearchForm.SEARCH_FIELD).type(term);
187-
$(SearchForm.SEARCH_BUTTON).click();
188-
}
189-
}
190-
```
191-
192-
The `SearchForm` class is typical of a light-weight Page Object: it is responsible uniquely for locating elements on the page, and it does this by defining locators or occasionally by resolving web elements dynamically.
193174
```java
194-
class SearchForm {
195-
static By SEARCH_FIELD = By.cssSelector(".js-search-input");
196-
static By SEARCH_BUTTON = By.cssSelector(".js-search-button");
197-
}
198-
```
175+
public class HomePageActions
176+
{
199177

200-
The last step library class used in the step definition code is the `SearchResult` class. The job of this class is to query the web page, and retrieve a list of search results that we can use in the AssertJ assertion at the end of the test. This class also extends `UIInteractionSteps` and
201-
```java
202-
public class SearchResult extends UIInteractionSteps {
203-
public List<String> titles() {
204-
return findAll(SearchResultList.RESULT_TITLES)
205-
.stream()
206-
.map(WebElementFacade::getTextContent)
207-
.collect(Collectors.toList());
208-
}
209-
}
210-
```
178+
HomePage homePage;
211179

212-
The `SearchResultList` class is a lean Page Object that locates the search result titles on the results page:
213-
```java
214-
class SearchResultList {
215-
static By RESULT_TITLES = By.cssSelector(".result__title");
216-
}
217-
```
180+
/**
181+
*
182+
*/
183+
@Step
184+
public void openJungleSocksHomePageurl()
185+
{
186+
homePage.open();
218187

219-
The main advantage of the approach used in this example is not in the lines of code written, although Serenity does reduce a lot of the boilerplate code that you would normally need to write in a web test. The real advantage is in the use of many small, stable classes, each of which focuses on a single job. This application of the _Single Responsibility Principle_ goes a long way to making the test code more stable, easier to understand, and easier to maintain.
188+
}
220189

221-
## The Screenplay starter project
222-
If you prefer to use the Screenplay pattern, or want to try it out, check out the _screenplay_ branch instead of the _master_ branch. In this version of the starter project, the same scenario is implemented using the Screenplay pattern.
190+
/**
191+
*
192+
*/
193+
public void verifyHomePageJungleSocksTitle(String title)
194+
{
195+
assertThat("Verify create asset page:", homePage.getHomePageTitle(), equalTo(title));
223196

224-
The Screenplay pattern describes tests in terms of actors and the tasks they perform. Tasks are represented as objects performed by an actor, rather than methods. This makes them more flexible and composable, at the cost of being a bit more wordy. Here is an example:
225-
```java
226-
@Before
227-
public void setTheStage() {
228-
OnStage.setTheStage(new OnlineCast());
229-
}
197+
}
230198

231-
@Given("^(.*) is on the DuckDuckGo home page")
232-
public void on_the_DuckDuckGo_home_page(String actor) {
233-
theActorCalled(actor).attemptsTo(
 NavigateTo.theDuckDuckGoHomePage()
 );
234-
}
199+
/**
200+
*
201+
*/
202+
public void verifyJungleSocksHomePageHeader(String header)
203+
{
204+
assertThat("Verify create asset page:", homePage.getHomePageHeader(), equalTo(header));
235205

236-
@When("she/he searches for {string}")
237-
public void search_for(String term) {
238-
theActorInTheSpotlight().attemptsTo(
239-
SearchFor.term(term) 
 );
240-
}
206+
}
241207

242-
@Then("all the result titles should contain the word {string}")
243-
public void all_the_result_titles_should_contain_the_word(String term) {
244-
theActorInTheSpotlight().should(
245-
seeThat("search result titles",
246-
SearchResult.titles(),
 hasSize(greaterThan(0))),
247-
seeThat("search result titles",
248-
SearchResult.titles(),
 everyItem(containsIgnoringCase(term)))
249-
);
250-
}
251-
```
208+
/**
209+
*
210+
*/
211+
public void verifyJungleSocksHomeProductDetails(List productList)
212+
{
213+
assertThat("Verify create asset page:", homePage.getPoductList(), equalTo(productList));
252214

253-
In both approaches, the Page Objects very close or identical. The differences are mainly in the action classes. Screenplay classes emphasise reusable components and a very readable declarative style, whereas Lean Page Objects and Action Classes opt for a more imperative style.
215+
}
254216

255-
The `NavigateTo` class performs the same role as it’s equivalent in the Lean Page Object/Action Class version, and looks quite similar:
256-
```java
257-
public class NavigateTo {
217+
/**
218+
*
219+
*/
220+
public void verifyProductPriceAndQuantityByName(String productName, String price, String quantity)
221+
{
222+
int index = homePage.getProductRowIndexByName(productName);
223+
System.out.println("ProductName:" + productName + ", " + index);
258224

259-
public static Performable theDuckDuckGoHomePage() {
260-
return Task.where("{0} opens the DuckDuckGo home page",
261-
Open.browserOn().the(DuckDuckGoHomePage.class)
262-
);
263-
}
264-
}
265-
```
225+
assertThat("Verify product price:", homePage.getProductPriceByIndex(index), equalTo(price));
226+
assertThat("Verify product quantity:", homePage.getProductInStockQuantityByIndex(index), equalTo(quantity));
266227

267-
The `SearchFor` class is also similar: it is shown below:
268-
```java
269-
public class SearchFor {
228+
}
270229

271-
public static Performable term(String term) {
272-
return Task.where("{0} attempts to search for #term",
273-
Clear.field(SearchForm.SEARCH_FIELD),
 Enter.theValue(term).into(SearchForm.SEARCH_FIELD),
274-
Click.on(SearchForm.SEARCH_BUTTON)
275-
).with("term").of(term);
276-
}
277-
}
278230
```
279231

280-
In Screenplay, there is a clear distinction between actions (which change the system state) and questions (which read the system state). In Screenplay, we fetch the search results using a Question class, like this:
281232
```java
282-
public class SearchResult {
283-
public static Question<List<String>> titles() {
284-
return actor ->
285-
TextContent.of(SearchResultList.RESULT_TITLES)
286-
.viewedBy(actor)
287-
.asList();
288-
}
289-
}
290-
```
233+
public class HomePage extends PageObject
234+
{
235+
236+
@FindBy(css = "body > h1")
237+
private WebElement homePageHeader;
238+
239+
@FindBy(xpath = "//*[contains(@class,'line_item')]/td[1]")
240+
private List<WebElement> productListWebElement;
241+
242+
@FindBy(css = "[name=\"state\"]")
243+
private WebElement stateDropDown;
244+
245+
@FindBy(css = "[name=\"state\"] option")
246+
private List<WebElement> stateList;
247+
248+
@FindBy(name = "commit")
249+
private WebElement checkoutButton;
250+
251+
@FindBy(css ="#subtotal")
252+
private WebElement productPriceSubTotal;
253+
254+
@FindBy(css ="#taxes")
255+
private WebElement productTaxesTotal;
256+
257+
@FindBy(css ="#total")
258+
private WebElement productTotalPrice;
259+
260+
261+
public String getHomePageTitle()
262+
{
263+
return getDriver().getTitle();
264+
}
265+
266+
public String getHomePageHeader()
267+
{
268+
CommonUtils.waitForElementToBeVisible(getDriver(), homePageHeader);
269+
return homePageHeader.getText();
270+
}
271+
272+
public List getPoductList()
273+
{
274+
275+
List<String> productList = new ArrayList<>();
276+
277+
int productSize = productListWebElement.size();
278+
for (int i = 0; i < productSize ; i++)
279+
{
280+
productList.add(productListWebElement.get(i).getText());
281+
}
282+
return productList;
283+
284+
}
285+
286+
```
287+
The main advantage of the approach used in this example is not in the lines of code written, although Serenity does reduce a lot of the boilerplate code that you would normally need to write in a web test. The real advantage is in the use of many small, stable classes, each of which focuses on a single job. This application of the _Single Responsibility Principle_ goes a long way to making the test code more stable, easier to understand, and easier to maintain.
291288

292-
The Screenplay DSL is rich and flexible, and well suited to teams working on large test automation projects with many team members, and who are reasonably comfortable with Java and design patterns. The Lean Page Objects/Action Classes approach proposes a gentler learning curve, but still provides significant advantages in terms of maintainability and reusability.
293289

294290
## Executing the tests
295291
To run the sample project, you can either just run the `CucumberTestSuite` test runner class, or run either `mvn verify` or `gradle test` from the command line.

0 commit comments

Comments
 (0)