@@ -15,23 +15,23 @@ It reveals pain points at each step to show why the Screenplay Pattern is ultima
15
15
16
16
## Phase 1: Raw WebDriver Calls
17
17
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 ) .
19
19
You could simply write raw [ Selenium WebDriver] ( https://www.selenium.dev/documentation/en/webdriver/ ) code like this:
20
20
21
21
``` csharp
22
22
// Initialize the WebDriver
23
23
IWebDriver driver = new ChromeDriver ();
24
24
25
25
// Open the search engine
26
- driver .Navigate ().GoToUrl (" https://duckduckgo.com/ " );
26
+ driver .Navigate ().GoToUrl (" https://en.wikipedia.org/wiki/Main_Page " );
27
27
28
28
// 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 ();
31
31
32
32
// Verify results appear
33
33
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 " );
35
35
36
36
// Quit the WebDriver
37
37
driver .Quit ();
@@ -65,24 +65,24 @@ IWebDriver driver = new ChromeDriver();
65
65
WebDriverWait wait = new WebDriverWait (driver , TimeSpan .FromSeconds (30 ));
66
66
67
67
// Open the search engine
68
- driver .Navigate ().GoToUrl (" https://duckduckgo.com/ " );
68
+ driver .Navigate ().GoToUrl (" https://en.wikipedia.org/wiki/Main_Page " );
69
69
70
70
// 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 ();
74
74
75
75
// Verify results appear
76
76
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 " );
78
78
79
79
// Quit the WebDriver
80
80
driver .Quit ();
81
81
```
82
82
83
83
These waits are necessary to make the code correct, but they cause new problems.
84
84
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.
86
86
Second, raw calls with explicit waits make code more cryptic and less intuitive.
87
87
It is difficult to understand what this code does at a glance.
88
88
@@ -94,15 +94,15 @@ In the Page Object Model (or "Page Object Pattern"), each page is modeled as a c
94
94
So, a page object for the search page could look like this:
95
95
96
96
``` csharp
97
- public class SearchPage
97
+ public class MainPage
98
98
{
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'] " );
102
102
103
103
public IWebDriver Driver { get ; private set ; }
104
104
105
- public SearchPage (IWebDriver driver ) => Driver = driver ;
105
+ public MainPage (IWebDriver driver ) => Driver = driver ;
106
106
107
107
public void Load () => driver .Navigate ().GoToUrl (Url );
108
108
@@ -117,25 +117,25 @@ public class SearchPage
117
117
```
118
118
119
119
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 ` ).
121
121
The ` Search ` method uses an explicit wait before attempting to interact with elements.
122
122
It also uses the locator properties so that locator queries are not duplicated.
123
123
Locators and interaction methods have meaningful names.
124
124
Page objects require a few more lines of code that raw calls at first, but their parts can be called easily.
125
125
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.
127
127
This new code looks much cleaner:
128
128
129
129
``` csharp
130
130
IWebDriver driver = new ChromeDriver ();
131
131
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" );
135
135
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 ();
139
139
140
140
driver .Quit ();
141
141
```
@@ -184,7 +184,7 @@ public abstract class BasePage
184
184
public IWebDriver Driver { get ; private set ; }
185
185
public WebDriverWait Wait { get ; private set ; }
186
186
187
- public SearchPage (IWebDriver driver )
187
+ public BasePage (IWebDriver driver )
188
188
{
189
189
Driver = driver ;
190
190
Wait = new WebDriverWait (Driver , TimeSpan .FromSeconds (30 ));
@@ -272,53 +272,53 @@ The test could be rewritten using Boa Constrictor's Screenplay calls like this:
272
272
``` csharp
273
273
IActor actor = new Actor (logger : new ConsoleLogger ());
274
274
actor .Can (BrowseTheWeb .With (new ChromeDriver ()));
275
- actor .AttemptsTo (Navigate .ToUrl (SearchPage .Url ));
275
+ actor .AttemptsTo (Navigate .ToUrl (MainPage .Url ));
276
276
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 " ));
279
279
```
280
280
281
281
The page classes would provide locators:
282
282
283
283
``` csharp
284
- public static class SearchPage
284
+ public static class MainPage
285
285
{
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" ;
291
287
292
288
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" ));
295
295
}
296
296
297
- public static class ResultPage
297
+ public static class ArticlePage
298
298
{
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 " ));
302
302
}
303
303
```
304
304
305
- Performing the DuckDuckGo search could use a custom interaction like this:
305
+ Performing the Wikipedia search could use a custom interaction like this:
306
306
307
307
``` csharp
308
- public class SearchDuckDuckGo : ITask
308
+ public class SearchWikipedia : ITask
309
309
{
310
310
public string Phrase { get ; }
311
311
312
- private SearchDuckDuckGo (string phrase ) =>
312
+ private SearchWikipedia (string phrase ) =>
313
313
Phrase = phrase ;
314
314
315
- public static SearchDuckDuckGo For (string phrase ) =>
316
- new SearchDuckDuckGo (phrase );
315
+ public static SearchWikipedia For (string phrase ) =>
316
+ new SearchWikipedia (phrase );
317
317
318
318
public void PerformAs (IActor actor )
319
319
{
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 ));
322
322
}
323
323
}
324
324
```
0 commit comments