Skip to content

Commit 00d4256

Browse files
Rewrote Web UI example test for Wikipedia
1 parent fdad706 commit 00d4256

20 files changed

+306
-300
lines changed

Boa.Constrictor.Example/Boa.Constrictor.Example.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFramework>net7.0</TargetFramework>
5-
<Version>3.0.4</Version>
5+
<Version>3.1.0</Version>
66
<Authors>Pandy Knight and the PrecisionLender SETs</Authors>
77
<Company>Q2</Company>
88
<Title>Boa.Constrictor.Example</Title>

Boa.Constrictor.Example/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ Versioning is performed purely for tracking changes.
2121
(none)
2222

2323

24+
## [3.1.0] - 2023-05-28
25+
26+
### Changed
27+
28+
- Replaced DuckDuckGo search test with Wikipedia search test since DuckDuckGo changes their home page
29+
30+
2431
## [3.0.4] - 2022-12-13
2532

2633
### Changed

Boa.Constrictor.Example/Interactions/SearchDuckDuckGo.cs

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Boa.Constrictor.Screenplay;
2+
using Boa.Constrictor.Selenium;
3+
4+
namespace Boa.Constrictor.Example
5+
{
6+
public class SearchWikipedia : ITask
7+
{
8+
public string Phrase { get; }
9+
10+
private SearchWikipedia(string phrase) =>
11+
Phrase = phrase;
12+
13+
public static SearchWikipedia For(string phrase) =>
14+
new SearchWikipedia(phrase);
15+
16+
public void PerformAs(IActor actor)
17+
{
18+
actor.AttemptsTo(SendKeys.To(MainPage.SearchInput, Phrase));
19+
actor.AttemptsTo(Click.On(MainPage.SearchButton));
20+
}
21+
}
22+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Boa.Constrictor.Selenium;
2+
using OpenQA.Selenium;
3+
using static Boa.Constrictor.Selenium.WebLocator;
4+
5+
namespace Boa.Constrictor.Example
6+
{
7+
public static class ArticlePage
8+
{
9+
public static IWebLocator Title => L(
10+
"Title Span",
11+
By.CssSelector("[id='firstHeading'] span"));
12+
}
13+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Boa.Constrictor.Selenium;
2+
using OpenQA.Selenium;
3+
using static Boa.Constrictor.Selenium.WebLocator;
4+
5+
namespace Boa.Constrictor.Example
6+
{
7+
public static class MainPage
8+
{
9+
public const string Url = "https://en.wikipedia.org/wiki/Main_Page";
10+
11+
public static IWebLocator SearchButton => L(
12+
"Wikipedia Search Button",
13+
By.XPath("//button[text()='Search']"));
14+
15+
public static IWebLocator SearchInput => L(
16+
"Wikipedia Search Input",
17+
By.Name("search"));
18+
}
19+
}

Boa.Constrictor.Example/Pages/ResultPage.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.

Boa.Constrictor.Example/Pages/SearchPage.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

Boa.Constrictor.Example/Tests/ScreenplayWebUiTest.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ public class ScreenplayWebUiTest
1414
public void InitializeScreenplay()
1515
{
1616
ChromeOptions options = new ChromeOptions();
17-
options.AddArgument("headless"); // Remove this line to "see" the browser run
17+
options.AddArgument("headless"); // Remove this line to "see" the browser run
18+
options.AddArgument("window-size=1920,1080"); // Use this option with headless mode
1819
ChromeDriver driver = new ChromeDriver(options);
1920

2021
Actor = new Actor(name: "Andy", logger: new ConsoleLogger());
@@ -28,12 +29,12 @@ public void QuitBrowser()
2829
}
2930

3031
[Test]
31-
public void TestDuckDuckGoWebSearch()
32+
public void TestWikipediaSearch()
3233
{
33-
Actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
34-
Actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
35-
Actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
36-
Actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
34+
Actor.AttemptsTo(Navigate.ToUrl(MainPage.Url));
35+
Actor.AskingFor(ValueAttribute.Of(MainPage.SearchInput)).Should().BeEmpty();
36+
Actor.AttemptsTo(SearchWikipedia.For("Giant panda"));
37+
Actor.WaitsUntil(Text.Of(ArticlePage.Title), IsEqualTo.Value("Giant panda"));
3738
}
3839
}
3940
}

docs/pages/main-docs/getting-started/page-objects.md

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@ It reveals pain points at each step to show why the Screenplay Pattern is ultima
1515

1616
## Phase 1: Raw WebDriver Calls
1717

18-
Let's say you want to automate a test that performs a [DuckDuckGo](https://duckduckgo.com/) web search.
18+
Let's say you want to automate a test that searches for an article on [Wikipedia](https://en.wikipedia.org/wiki/Main_Page).
1919
You could simply write raw [Selenium WebDriver](https://www.selenium.dev/documentation/en/webdriver/) code like this:
2020

2121
```csharp
2222
// Initialize the WebDriver
2323
IWebDriver driver = new ChromeDriver();
2424

2525
// Open the search engine
26-
driver.Navigate().GoToUrl("https://duckduckgo.com/");
26+
driver.Navigate().GoToUrl("https://en.wikipedia.org/wiki/Main_Page");
2727

2828
// Search for a phrase
29-
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
30-
driver.FindElement(By.Id("search_button_homepage")).Click();
29+
driver.FindElement(By.Name("search")).SendKeys("Giant panda");
30+
driver.FindElement(By.XPath("//button[text()='Search']")).Click();
3131

3232
// Verify results appear
3333
driver.Title.ToLower().Should().Contain("panda");
34-
driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0);
34+
driver.FindElement(By.CssSelector("[id='firstHeading'] span")).Text.Should().Be("Giant panda");
3535

3636
// Quit the WebDriver
3737
driver.Quit();
@@ -65,24 +65,24 @@ IWebDriver driver = new ChromeDriver();
6565
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
6666

6767
// Open the search engine
68-
driver.Navigate().GoToUrl("https://duckduckgo.com/");
68+
driver.Navigate().GoToUrl("https://en.wikipedia.org/wiki/Main_Page");
6969

7070
// Search for a phrase
71-
wait.Until(d => d.FindElements(By.Id("search_form_input_homepage")).Count > 0);
72-
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
73-
driver.FindElement(By.Id("search_button_homepage")).Click();
71+
wait.Until(d => d.FindElements(By.Name("search")).Count > 0);
72+
driver.FindElement(By.Name("search")).SendKeys("Giant panda");
73+
driver.FindElement(By.XPath("//button[text()='Search']")).Click();
7474

7575
// Verify results appear
7676
wait.Until(d => d.Title.ToLower().Contains("panda"));
77-
wait.Until(d => d.FindElements(By.CssSelector("a.result__a"))).Count > 0);
77+
wait.Until(d => d.FindElements(By.CssSelector("[id='firstHeading'] span")).Text == "Giant Panda");
7878

7979
// Quit the WebDriver
8080
driver.Quit();
8181
```
8282

8383
These waits are necessary to make the code correct, but they cause new problems.
8484
First, they cause duplicate code because Web element locators are used multiple times.
85-
Notice how the locator `By.Id("search_form_input_homepage")` is written twice.
85+
Notice how the locator `By.Name("search")` is written twice.
8686
Second, raw calls with explicit waits make code more cryptic and less intuitive.
8787
It is difficult to understand what this code does at a glance.
8888

@@ -94,15 +94,15 @@ In the Page Object Model (or "Page Object Pattern"), each page is modeled as a c
9494
So, a page object for the search page could look like this:
9595

9696
```csharp
97-
public class SearchPage
97+
public class MainPage
9898
{
99-
public const string Url = "https://duckduckgo.com/";
100-
public static By SearchInput => By.Id("search_form_input_homepage");
101-
public static By SearchButton => By.Id("search_button_homepage");
99+
public const string Url = "https://en.wikipedia.org/wiki/Main_Page";
100+
public static By SearchInput => By.Name("search");
101+
public static By SearchButton => By.XPath("//button[text()='Search']");
102102

103103
public IWebDriver Driver { get; private set; }
104104

105-
public SearchPage(IWebDriver driver) => Driver = driver;
105+
public MainPage(IWebDriver driver) => Driver = driver;
106106

107107
public void Load() => driver.Navigate().GoToUrl(Url);
108108

@@ -117,25 +117,25 @@ public class SearchPage
117117
```
118118

119119
This page object class has a decent structure and a mild separation of concerns.
120-
The `SearchPage` class has locators (`SearchInput` and `SearchButton`) and interaction methods (`Load` and `Search`).
120+
The `MainPage` class has locators (`SearchInput` and `SearchButton`) and interaction methods (`Load` and `Search`).
121121
The `Search` method uses an explicit wait before attempting to interact with elements.
122122
It also uses the locator properties so that locator queries are not duplicated.
123123
Locators and interaction methods have meaningful names.
124124
Page objects require a few more lines of code that raw calls at first, but their parts can be called easily.
125125

126-
The original test steps can be rewritten using this new `SearchPage` class, as well as a hypothetical `ResultPage` class.
126+
The original test steps can be rewritten using this new `MainPage` class, as well as a hypothetical `ArticlePage` class.
127127
This new code looks much cleaner:
128128

129129
```csharp
130130
IWebDriver driver = new ChromeDriver();
131131

132-
SearchPage searchPage = new SearchPage(driver);
133-
searchPage.Load();
134-
searchPage.Search("panda");
132+
MainPage mainPage = new MainPage(driver);
133+
mainPage.Load();
134+
mainPage.Search("Giant panda");
135135

136-
ResultPage resultPage = new ResultPage(driver);
137-
resultPage.WaitForTitle("panda");
138-
resultPage.WaitForResultLinks();
136+
ArticlePage articlePage = new ArticlePage(driver);
137+
articlePage.WaitForTitle("panda");
138+
articlePage.WaitForArticleTitle();
139139

140140
driver.Quit();
141141
```
@@ -184,7 +184,7 @@ public abstract class BasePage
184184
public IWebDriver Driver { get; private set; }
185185
public WebDriverWait Wait { get; private set; }
186186

187-
public SearchPage(IWebDriver driver)
187+
public BasePage(IWebDriver driver)
188188
{
189189
Driver = driver;
190190
Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
@@ -272,53 +272,53 @@ The test could be rewritten using Boa Constrictor's Screenplay calls like this:
272272
```csharp
273273
IActor actor = new Actor(logger: new ConsoleLogger());
274274
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
275-
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
275+
actor.AttemptsTo(Navigate.ToUrl(MainPage.Url));
276276
string title = actor.AsksFor(Title.OfPage());
277-
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
278-
actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
277+
actor.AttemptsTo(SearchWikipedia.For("Giant panda"));
278+
actor.WaitsUntil(Text.Of(ArticlePage.Title), IsEqualTo.Value("Giant panda"));
279279
```
280280

281281
The page classes would provide locators:
282282

283283
```csharp
284-
public static class SearchPage
284+
public static class MainPage
285285
{
286-
public const string Url = "https://www.duckduckgo.com/";
287-
288-
public static IWebLocator SearchInput => L(
289-
"DuckDuckGo Search Input",
290-
By.Id("search_form_input_homepage"));
286+
public const string Url = "https://en.wikipedia.org/wiki/Main_Page";
291287

292288
public static IWebLocator SearchButton => L(
293-
"DuckDuckGo Search Button",
294-
By.Id("search_button_homepage"));
289+
"Wikipedia Search Button",
290+
By.XPath("//button[text()='Search']"));
291+
292+
public static IWebLocator SearchInput => L(
293+
"Wikipedia Search Input",
294+
By.Name("search"));
295295
}
296296

297-
public static class ResultPage
297+
public static class ArticlePage
298298
{
299-
public static IWebLocator ResultLinks => L(
300-
"DuckDuckGo Result Page Links",
301-
By.ClassName("result__a"));
299+
public static IWebLocator Title => L(
300+
"Title Span",
301+
By.CssSelector("[id='firstHeading'] span"));
302302
}
303303
```
304304

305-
Performing the DuckDuckGo search could use a custom interaction like this:
305+
Performing the Wikipedia search could use a custom interaction like this:
306306

307307
```csharp
308-
public class SearchDuckDuckGo : ITask
308+
public class SearchWikipedia : ITask
309309
{
310310
public string Phrase { get; }
311311

312-
private SearchDuckDuckGo(string phrase) =>
312+
private SearchWikipedia(string phrase) =>
313313
Phrase = phrase;
314314

315-
public static SearchDuckDuckGo For(string phrase) =>
316-
new SearchDuckDuckGo(phrase);
315+
public static SearchWikipedia For(string phrase) =>
316+
new SearchWikipedia(phrase);
317317

318318
public void PerformAs(IActor actor)
319319
{
320-
actor.AttemptsTo(SendKeys.To(SearchPage.SearchInput, Phrase));
321-
actor.AttemptsTo(Click.On(SearchPage.SearchButton));
320+
actor.AttemptsTo(SendKeys.To(MainPage.SearchInput, Phrase));
321+
actor.AttemptsTo(Click.On(MainPage.SearchButton));
322322
}
323323
}
324324
```

0 commit comments

Comments
 (0)