The Screenplay Pattern
Better Interactions for Better Automation
2
Andrew “Pandy” Knight
Lead Software Engineer in Test
PrecisionLender, a Q2 Company
AutomationPanda.com
@AutomationPanda
3
Andrew “Pandy” Knight
Lead Developer for Boa Constrictor
4
3-Part Talk
1. Problems with traditional interactions
2. Why Screenplay is better
3. Using Screenplay with Boa Constrictor
Interactions
Interactions
How users operate software: loads, clicks, scrapes, etc.
8
Testing =
Interaction +Verification
9
A simple DuckDuckGo search test
Open the
search engine
Search for a
phrase
Verify results
appear
10
A simple DuckDuckGo search test
Open the
search engine
Search for a
phrase
Verify results
appear
1. Navigate
11
A simple DuckDuckGo search test
Open the
search engine
Search for a
phrase
Verify results
appear
1. Navigate
1. Enter search phrase
2. Click search button
12
A simple DuckDuckGo search test
Open the
search engine
Search for a
phrase
Verify results
appear
1. Navigate
1. Enter search phrase
2. Click search button
1. Scrape page title
2. Scrape result links
13
14
Raw WebDriver calls
15
Raw WebDriver calls
IWebDriver driver = new ChromeDriver();
16
Raw WebDriver calls
IWebDriver driver = new ChromeDriver();
// Open the search engine
driver.Navigate().GoToUrl("https://duckduckgo.com/");
17
Raw WebDriver calls
IWebDriver driver = new ChromeDriver();
// Open the search engine
driver.Navigate().GoToUrl("https://duckduckgo.com/");
// Search for a phrase
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
driver.FindElement(By.Id("search_button_homepage")).Click();
18
Raw WebDriver calls
IWebDriver driver = new ChromeDriver();
// Open the search engine
driver.Navigate().GoToUrl("https://duckduckgo.com/");
// Search for a phrase
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
driver.FindElement(By.Id("search_button_homepage")).Click();
// Verify results appear
driver.Title.ToLower().Should().Contain("panda");
driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0);
19
Raw WebDriver calls
IWebDriver driver = new ChromeDriver();
// Open the search engine
driver.Navigate().GoToUrl("https://duckduckgo.com/");
// Search for a phrase
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
driver.FindElement(By.Id("search_button_homepage")).Click();
// Verify results appear
driver.Title.ToLower().Should().Contain("panda");
driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0);
driver.Quit();
20
Raw WebDriver calls
IWebDriver driver = new ChromeDriver();
// Open the search engine
driver.Navigate().GoToUrl("https://duckduckgo.com/");
// Search for a phrase
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
driver.FindElement(By.Id("search_button_homepage")).Click();
// Verify results appear
driver.Title.ToLower().Should().Contain("panda");
driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0);
driver.Quit();
Race
Conditions
!
1
2
3
21
Implicit waits
IWebDriver driver = new ChromeDriver();
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(30);
// Open the search engine
driver.Navigate().GoToUrl("https://duckduckgo.com/");
// Search for a phrase
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
driver.FindElement(By.Id("search_button_homepage")).Click();
// Verify results appear
driver.Title.ToLower().Should().Contain("panda");
driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0);
driver.Quit();
22
Explicit waits
IWebDriver driver = new ChromeDriver();
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
// Open the search engine
driver.Navigate().GoToUrl("https://duckduckgo.com/");
// Search for a phrase
wait.Until(d => d.FindElements(By.Id("search_form_input_homepage")).Count > 0);
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
driver.FindElement(By.Id("search_button_homepage")).Click();
// Verify results appear
wait.Until(d => d.Title.ToLower().Contains("panda"));
wait.Until(d => d.FindElements(By.CssSelector("a.result__a"))).Count > 0);
driver.Quit();
23
Duplicate code
IWebDriver driver = new ChromeDriver();
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
// Open the search engine
driver.Navigate().GoToUrl("https://duckduckgo.com/");
// Search for a phrase
wait.Until(d => d.FindElements(By.Id("search_form_input_homepage")).Count > 0);
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
driver.FindElement(By.Id("search_button_homepage")).Click();
// Verify results appear
wait.Until(d => d.Title.ToLower().Contains("panda"));
wait.Until(d => d.FindElements(By.CssSelector("a.result__a"))).Count > 0);
driver.Quit();
24
Unintuitive code
IWebDriver driver = new ChromeDriver();
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
driver.Navigate().GoToUrl("https://duckduckgo.com/");
wait.Until(d => d.FindElements(By.Id("search_form_input_homepage")).Count > 0);
driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda");
driver.FindElement(By.Id("search_button_homepage")).Click();
wait.Until(d => d.Title.ToLower().Contains("panda"));
wait.Until(d => d.FindElements(By.CssSelector("a.result__a"))).Count > 0);
driver.Quit();
25
Page Object Pattern
public class SearchPage
{
}
26
Page Object Pattern
public class SearchPage
{
public const string Url = "https://duckduckgo.com/";
public static By SearchInput => By.Id("search_form_input_homepage");
public static By SearchButton => By.Id("search_button_homepage");
}
27
Page Object Pattern
public class SearchPage
{
public const string Url = "https://duckduckgo.com/";
public static By SearchInput => By.Id("search_form_input_homepage");
public static By SearchButton => By.Id("search_button_homepage");
public IWebDriver Driver { get; private set; }
public SearchPage(IWebDriver driver) => Driver = driver;
}
28
Page Object Pattern
public class SearchPage
{
public const string Url = "https://duckduckgo.com/";
public static By SearchInput => By.Id("search_form_input_homepage");
public static By SearchButton => By.Id("search_button_homepage");
public IWebDriver Driver { get; private set; }
public SearchPage(IWebDriver driver) => Driver = driver;
public void Load() => Driver.Navigate().GoToUrl(Url);
}
29
Page Object Pattern
public class SearchPage
{
public const string Url = "https://duckduckgo.com/";
public static By SearchInput => By.Id("search_form_input_homepage");
public static By SearchButton => By.Id("search_button_homepage");
public IWebDriver Driver { get; private set; }
public SearchPage(IWebDriver driver) => Driver = driver;
public void Load() => Driver.Navigate().GoToUrl(Url);
public void Search(string phrase)
{
WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
wait.Until(d => d.FindElements(SearchInput).Count > 0);
Driver.FindElement(SearchInput).SendKeys(phrase);
Driver.FindElement(SearchButton).Click();
}
}
30
Page Object Pattern usage
IWebDriver driver = new ChromeDriver();
SearchPage searchPage = new SearchPage(driver);
searchPage.Load();
searchPage.Search(“panda”);
31
Page Object Pattern usage
IWebDriver driver = new ChromeDriver();
SearchPage searchPage = new SearchPage(driver);
searchPage.Load();
searchPage.Search(“panda”);
ResultPage resultPage = new ResultPage(driver);
resultPage.WaitForTitle(“panda”);
resultPage.WaitForResultLinks();
driver.Quit();
32
Page object duplication redux
public class AnyPage
{
// ...
}
33
Page object duplication redux
public class AnyPage
{
// ...
public void ClickButton()
{
Wait.Until(d => d.FindElements(Button).Count > 0);
driver.FindElement(Button).Click();
}
}
34
Page object duplication redux
public class AnyPage
{
// ...
public void ClickButton()
{
Wait.Until(d => d.FindElements(Button).Count > 0);
driver.FindElement(Button).Click();
}
public void ClickOtherButton()
{
Wait.Until(d => d.FindElements(OtherButton).Count > 0);
driver.FindElement(OtherButton).Click();
}
}
35
Page object duplication redux
public class AnyPage
{
// ...
public void ClickButton()
{
Wait.Until(d => d.FindElements(Button).Count > 0);
driver.FindElement(Button).Click();
}
public void ClickOtherButton()
{
Wait.Until(d => d.FindElements(OtherButton).Count > 0);
driver.FindElement(OtherButton).Click();
}
}
36
A base page
public class BasePage
{
}
37
A base page
public class BasePage
{
public IWebDriver Driver { get; private set; }
public WebDriverWait Wait { get; private set; }
public SearchPage(IWebDriver driver)
{
Driver = driver;
Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
}
}
38
A base page
public class BasePage
{
public IWebDriver Driver { get; private set; }
public WebDriverWait Wait { get; private set; }
public SearchPage(IWebDriver driver)
{
Driver = driver;
Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
}
protected void Click(By locator)
{
Wait.Until(d => d.FindElements(locator).Count > 0);
driver.FindElement(locator).Click();
}
}
39
Base page inheritance
public class AnyPage : BasePage
{
// ...
public AnyPage(IWebDriver driver) : base(driver) {}
public void ClickButton() => Click(Button);
public void ClickOtherButton() => Click(OtherButton);
}
40
Even more duplication?
public class AnyPage : BasePage
{
// ...
public void ClickButton() => Click(Button);
public void ClickOtherButton() => Click(OtherButton);
public void ButtonText() => Text(Button);
public void OtherButtonText() => Text(OtherButton);
public void IsButtonDisplayed() => IsDisplayed(Button);
public void IsOtherButtonDisplayed() => IsDisplayed(OtherButton);
}
Page objects
are free-form.
There’s no official version.There’s no conformity.There’s no enforcement.
Page objects are more of a “convention” than a true design pattern.
There must be
a better way.
43
Reconsidering interactions
44
Reconsidering interactions
Actor
45
Reconsidering interactions
Actor Product
46
Reconsidering interactions
Actor Interaction Product
47
Reconsidering interactions
Actor Ability Interaction Product
Actors
use Abilities
to perform Interactions.
Actors
use Abilities
to perform Interactions.
This is the heart of the Screenplay Pattern.
50
51
Boa Constrictor
• Open source C# implementation of the Screenplay Pattern
• Developed by PrecisionLender, a Q2 Company
• Can be used with any test framework (SpecFlow, NUnit, etc.)
• Doc site: q2ebanking.github.io/boa-constrictor
• GitHub repository: q2ebanking/boa-constrictor
• NuGet package: Boa.Constrictor
The .NET Screenplay Pattern
52
Let’s rewrite that
DuckDuckGo search test
using Boa Constrictor.
53
Required packages
// NuGet Packages:
// Boa.Constrictor
// FluentAssertions
// RestSharp
// Selenium.Support
// Selenium.WebDriver
using Boa.Constrictor.Logging;
using Boa.Constrictor.RestSharp;
using Boa.Constrictor.Screenplay;
using Boa.Constrictor.WebDriver;
using FluentAssertions;
using OpenQA.Selenium.Chrome;
using static Boa.Constrictor.WebDriver.WebLocator;
54
Creating the Actor
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
55
Adding Abilities
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
56
Adding Abilities
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
public class BrowseTheWeb : IAbility
{
public IWebDriver WebDriver { get; }
private BrowseTheWeb(IWebDriver driver) =>
WebDriver = driver;
public static BrowseTheWeb With(IWebDriver driver) =>
new BrowseTheWeb(driver);
}
57
Modeling web pages
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
public static class SearchPage
{
public const string Url =
"https://www.duckduckgo.com/";
public static IWebLocator SearchInput => L(
"DuckDuckGo Search Input",
By.Id("search_form_input_homepage"));
}
58
Attempting a Task
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
59
Attempting a Task
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
public void AttemptsTo(ITask task)
{
task.PerformAs(this);
}
60
Attempting a Task
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
public class Navigate : ITask
{
private string Url { get; set; }
private Navigate(string url) => Url = url;
public static Navigate ToUrl(string url) => new Navigate(url);
public void PerformAs(IActor actor)
{
var driver = actor.Using<BrowseTheWeb>().WebDriver;
driver.Navigate().GoToUrl(Url);
}
}
61
Attempting a Task
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
public class Navigate : ITask
{
private string Url { get; set; }
private Navigate(string url) => Url = url;
public static Navigate ToUrl(string url) => new Navigate(url);
public void PerformAs(IActor actor)
{
var driver = actor.Using<BrowseTheWeb>().WebDriver;
driver.Navigate().GoToUrl(Url);
}
}
62
Attempting a Task
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
public static class SearchPage
{
public const string Url =
"https://www.duckduckgo.com/";
public static IWebLocator SearchInput => L(
"DuckDuckGo Search Input",
By.Id("search_form_input_homepage"));
}
63
Asking a Question
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
64
Asking a Question
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
public TAnswer
AskingFor<TAnswer>(IQuestion<TAnswer> question)
{
return question.RequestAs(this);
}
65
Asking a Question
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
public class ValueAttribute : IQuestion<string>
{
public IWebLocator Locator { get; }
private ValueAttribute(IWebLocator locator) => Locator = locator;
public static ValueAttribute Of(IWebLocator locator) => new ValueAttribute(locator);
public string RequestAs(IActor actor)
{
var driver = actor.Using<BrowseTheWeb>().WebDriver;
actor.AttemptsTo(Wait.Until(Existence.Of(Locator), IsEqualTo.True()));
return driver.FindElement(Locator.Query).GetAttribute("value");
}
}
66
Asking a Question
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
public class ValueAttribute : IQuestion<string>
{
public IWebLocator Locator { get; }
private ValueAttribute(IWebLocator locator) => Locator = locator;
public static ValueAttribute Of(IWebLocator locator) => new ValueAttribute(locator);
public string RequestAs(IActor actor)
{
var driver = actor.Using<BrowseTheWeb>().WebDriver;
actor.AttemptsTo(Wait.Until(Existence.Of(Locator), IsEqualTo.True()));
return driver.FindElement(Locator.Query).GetAttribute("value");
}
}
67
Asking a Question
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
public static class SearchPage
{
public const string Url =
"https://www.duckduckgo.com/";
public static IWebLocator SearchInput => L(
"DuckDuckGo Search Input",
By.Id("search_form_input_homepage"));
}
68
Asking a Question
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
69
Composing a custom Interaction
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
70
Composing a custom Interaction
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
public class SearchDuckDuckGo : ITask
{
public string Phrase { get; }
private SearchDuckDuckGo(string phrase) =>
Phrase = phrase;
public static SearchDuckDuckGo For(string phrase) =>
new SearchDuckDuckGo(phrase);
public void PerformAs(IActor actor)
{
actor.AttemptsTo(SendKeys.To(SearchPage.SearchInput, Phrase));
actor.AttemptsTo(Click.On(SearchPage.SearchButton));
}
}
71
Composing a custom Interaction
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
public class SearchDuckDuckGo : ITask
{
public string Phrase { get; }
private SearchDuckDuckGo(string phrase) =>
Phrase = phrase;
public static SearchDuckDuckGo For(string phrase) =>
new SearchDuckDuckGo(phrase);
public void PerformAs(IActor actor)
{
actor.AttemptsTo(SendKeys.To(SearchPage.SearchInput, Phrase));
actor.AttemptsTo(Click.On(SearchPage.SearchButton));
}
}
72
Composing a custom Interaction
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
73
Waiting for Questions to yield answers
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
74
Waiting for Questions to yield answers
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
75
Waiting for Questions to yield answers
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
public static IWebLocator ResultLinks => L(
"DuckDuckGo Result Page Links",
By.ClassName("result__a"));
76
Waiting for Questions to yield answers
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
77
Waiting for Questions to yield answers
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
78
Quitting the web browser
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
actor.AttemptsTo(QuitWebDriver.ForBrowser());
79
A completed test
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
actor.Can(BrowseTheWeb.With(new ChromeDriver()));
actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
actor.AttemptsTo(QuitWebDriver.ForBrowser());
80
A REST API test
81
A REST API test
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
82
A REST API test
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo")));
83
A REST API test
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo")));
var request = new RestRequest("api/breeds/image/random", Method.GET);
84
A REST API test
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo")));
var request = new RestRequest("api/breeds/image/random", Method.GET);
var response = Actor.Calls(Rest.Request(request));
85
A REST API test
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo")));
var request = new RestRequest("api/breeds/image/random", Method.GET);
var response = Actor.Calls(Rest.Request(request));
response.StatusCode.Should().Be(HttpStatusCode.OK);
86
A REST API test
IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo")));
var request = new RestRequest("api/breeds/image/random", Method.GET);
var response = Actor.Calls(Rest.Request(request));
response.StatusCode.Should().Be(HttpStatusCode.OK);
Actors
Actors
use Abilities
Actors
use Abilities
to perform Interactions.
91
SOLID principles
Single-Responsibility Principle Actors,Abilities, and Interactions are treated as separate
concerns.
Open-Closed Principle Each new Interaction must be a new class, rather than a
modification of an existing class.
Liskov Substitution Principle Actors can call all Abilities and Interactions the same way.
Interface Segregation Principle Actors,Abilities, and Interactions each have distinct,
separate interfaces.
Dependency Inversion Principle Abilities and Interactions are handled as interfaces.
Interactions use Abilities via dependency injection from
the Actor.
92
Why use Screenplay?
93
Why use Screenplay?
1. Rich, reusable, reliable interactions
94
Why use Screenplay?
1. Rich, reusable, reliable interactions
2. Interactions are composable
95
Why use Screenplay?
1. Rich, reusable, reliable interactions
2. Interactions are composable
3. Easy waiting
96
Why use Screenplay?
1. Rich, reusable, reliable interactions
2. Interactions are composable
3. Easy waiting
4. Readable, understandable calls
97
Why use Screenplay?
1. Rich, reusable, reliable interactions
2. Interactions are composable
3. Easy waiting
4. Readable, understandable calls
5. Covers all interactions – not justWeb UI
98
The Screenplay Pattern
99
The Screenplay Pattern
provides better interactions
100
The Screenplay Pattern
provides better interactions
for better automation.
101
Actors use Abilities to perform Interactions
Actor Ability Interaction
102
Getting started
103
Getting started
104
Getting started
105
Getting started
106
Getting started
107
https://q2ebanking.github.io/boa-constrictor/
108
109
I’ll mail Boa Constrictor stickers to 10 people who
tweet what they learned from this webinar!
Include @AutomationPanda & #BoaConstrictor!

The Screenplay Pattern: Better Interactions for Better Automation

  • 1.
    The Screenplay Pattern BetterInteractions for Better Automation
  • 2.
    2 Andrew “Pandy” Knight LeadSoftware Engineer in Test PrecisionLender, a Q2 Company AutomationPanda.com @AutomationPanda
  • 3.
    3 Andrew “Pandy” Knight LeadDeveloper for Boa Constrictor
  • 4.
    4 3-Part Talk 1. Problemswith traditional interactions 2. Why Screenplay is better 3. Using Screenplay with Boa Constrictor
  • 6.
  • 7.
    Interactions How users operatesoftware: loads, clicks, scrapes, etc.
  • 8.
  • 9.
    9 A simple DuckDuckGosearch test Open the search engine Search for a phrase Verify results appear
  • 10.
    10 A simple DuckDuckGosearch test Open the search engine Search for a phrase Verify results appear 1. Navigate
  • 11.
    11 A simple DuckDuckGosearch test Open the search engine Search for a phrase Verify results appear 1. Navigate 1. Enter search phrase 2. Click search button
  • 12.
    12 A simple DuckDuckGosearch test Open the search engine Search for a phrase Verify results appear 1. Navigate 1. Enter search phrase 2. Click search button 1. Scrape page title 2. Scrape result links
  • 13.
  • 14.
  • 15.
    15 Raw WebDriver calls IWebDriverdriver = new ChromeDriver();
  • 16.
    16 Raw WebDriver calls IWebDriverdriver = new ChromeDriver(); // Open the search engine driver.Navigate().GoToUrl("https://duckduckgo.com/");
  • 17.
    17 Raw WebDriver calls IWebDriverdriver = new ChromeDriver(); // Open the search engine driver.Navigate().GoToUrl("https://duckduckgo.com/"); // Search for a phrase driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda"); driver.FindElement(By.Id("search_button_homepage")).Click();
  • 18.
    18 Raw WebDriver calls IWebDriverdriver = new ChromeDriver(); // Open the search engine driver.Navigate().GoToUrl("https://duckduckgo.com/"); // Search for a phrase driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda"); driver.FindElement(By.Id("search_button_homepage")).Click(); // Verify results appear driver.Title.ToLower().Should().Contain("panda"); driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0);
  • 19.
    19 Raw WebDriver calls IWebDriverdriver = new ChromeDriver(); // Open the search engine driver.Navigate().GoToUrl("https://duckduckgo.com/"); // Search for a phrase driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda"); driver.FindElement(By.Id("search_button_homepage")).Click(); // Verify results appear driver.Title.ToLower().Should().Contain("panda"); driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0); driver.Quit();
  • 20.
    20 Raw WebDriver calls IWebDriverdriver = new ChromeDriver(); // Open the search engine driver.Navigate().GoToUrl("https://duckduckgo.com/"); // Search for a phrase driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda"); driver.FindElement(By.Id("search_button_homepage")).Click(); // Verify results appear driver.Title.ToLower().Should().Contain("panda"); driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0); driver.Quit(); Race Conditions ! 1 2 3
  • 21.
    21 Implicit waits IWebDriver driver= new ChromeDriver(); driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(30); // Open the search engine driver.Navigate().GoToUrl("https://duckduckgo.com/"); // Search for a phrase driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda"); driver.FindElement(By.Id("search_button_homepage")).Click(); // Verify results appear driver.Title.ToLower().Should().Contain("panda"); driver.FindElements(By.CssSelector("a.result__a")).Should().BeGreaterThan(0); driver.Quit();
  • 22.
    22 Explicit waits IWebDriver driver= new ChromeDriver(); WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30)); // Open the search engine driver.Navigate().GoToUrl("https://duckduckgo.com/"); // Search for a phrase wait.Until(d => d.FindElements(By.Id("search_form_input_homepage")).Count > 0); driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda"); driver.FindElement(By.Id("search_button_homepage")).Click(); // Verify results appear wait.Until(d => d.Title.ToLower().Contains("panda")); wait.Until(d => d.FindElements(By.CssSelector("a.result__a"))).Count > 0); driver.Quit();
  • 23.
    23 Duplicate code IWebDriver driver= new ChromeDriver(); WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30)); // Open the search engine driver.Navigate().GoToUrl("https://duckduckgo.com/"); // Search for a phrase wait.Until(d => d.FindElements(By.Id("search_form_input_homepage")).Count > 0); driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda"); driver.FindElement(By.Id("search_button_homepage")).Click(); // Verify results appear wait.Until(d => d.Title.ToLower().Contains("panda")); wait.Until(d => d.FindElements(By.CssSelector("a.result__a"))).Count > 0); driver.Quit();
  • 24.
    24 Unintuitive code IWebDriver driver= new ChromeDriver(); WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30)); driver.Navigate().GoToUrl("https://duckduckgo.com/"); wait.Until(d => d.FindElements(By.Id("search_form_input_homepage")).Count > 0); driver.FindElement(By.Id("search_form_input_homepage")).SendKeys("panda"); driver.FindElement(By.Id("search_button_homepage")).Click(); wait.Until(d => d.Title.ToLower().Contains("panda")); wait.Until(d => d.FindElements(By.CssSelector("a.result__a"))).Count > 0); driver.Quit();
  • 25.
    25 Page Object Pattern publicclass SearchPage { }
  • 26.
    26 Page Object Pattern publicclass SearchPage { public const string Url = "https://duckduckgo.com/"; public static By SearchInput => By.Id("search_form_input_homepage"); public static By SearchButton => By.Id("search_button_homepage"); }
  • 27.
    27 Page Object Pattern publicclass SearchPage { public const string Url = "https://duckduckgo.com/"; public static By SearchInput => By.Id("search_form_input_homepage"); public static By SearchButton => By.Id("search_button_homepage"); public IWebDriver Driver { get; private set; } public SearchPage(IWebDriver driver) => Driver = driver; }
  • 28.
    28 Page Object Pattern publicclass SearchPage { public const string Url = "https://duckduckgo.com/"; public static By SearchInput => By.Id("search_form_input_homepage"); public static By SearchButton => By.Id("search_button_homepage"); public IWebDriver Driver { get; private set; } public SearchPage(IWebDriver driver) => Driver = driver; public void Load() => Driver.Navigate().GoToUrl(Url); }
  • 29.
    29 Page Object Pattern publicclass SearchPage { public const string Url = "https://duckduckgo.com/"; public static By SearchInput => By.Id("search_form_input_homepage"); public static By SearchButton => By.Id("search_button_homepage"); public IWebDriver Driver { get; private set; } public SearchPage(IWebDriver driver) => Driver = driver; public void Load() => Driver.Navigate().GoToUrl(Url); public void Search(string phrase) { WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30)); wait.Until(d => d.FindElements(SearchInput).Count > 0); Driver.FindElement(SearchInput).SendKeys(phrase); Driver.FindElement(SearchButton).Click(); } }
  • 30.
    30 Page Object Patternusage IWebDriver driver = new ChromeDriver(); SearchPage searchPage = new SearchPage(driver); searchPage.Load(); searchPage.Search(“panda”);
  • 31.
    31 Page Object Patternusage IWebDriver driver = new ChromeDriver(); SearchPage searchPage = new SearchPage(driver); searchPage.Load(); searchPage.Search(“panda”); ResultPage resultPage = new ResultPage(driver); resultPage.WaitForTitle(“panda”); resultPage.WaitForResultLinks(); driver.Quit();
  • 32.
    32 Page object duplicationredux public class AnyPage { // ... }
  • 33.
    33 Page object duplicationredux public class AnyPage { // ... public void ClickButton() { Wait.Until(d => d.FindElements(Button).Count > 0); driver.FindElement(Button).Click(); } }
  • 34.
    34 Page object duplicationredux public class AnyPage { // ... public void ClickButton() { Wait.Until(d => d.FindElements(Button).Count > 0); driver.FindElement(Button).Click(); } public void ClickOtherButton() { Wait.Until(d => d.FindElements(OtherButton).Count > 0); driver.FindElement(OtherButton).Click(); } }
  • 35.
    35 Page object duplicationredux public class AnyPage { // ... public void ClickButton() { Wait.Until(d => d.FindElements(Button).Count > 0); driver.FindElement(Button).Click(); } public void ClickOtherButton() { Wait.Until(d => d.FindElements(OtherButton).Count > 0); driver.FindElement(OtherButton).Click(); } }
  • 36.
    36 A base page publicclass BasePage { }
  • 37.
    37 A base page publicclass BasePage { public IWebDriver Driver { get; private set; } public WebDriverWait Wait { get; private set; } public SearchPage(IWebDriver driver) { Driver = driver; Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30)); } }
  • 38.
    38 A base page publicclass BasePage { public IWebDriver Driver { get; private set; } public WebDriverWait Wait { get; private set; } public SearchPage(IWebDriver driver) { Driver = driver; Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30)); } protected void Click(By locator) { Wait.Until(d => d.FindElements(locator).Count > 0); driver.FindElement(locator).Click(); } }
  • 39.
    39 Base page inheritance publicclass AnyPage : BasePage { // ... public AnyPage(IWebDriver driver) : base(driver) {} public void ClickButton() => Click(Button); public void ClickOtherButton() => Click(OtherButton); }
  • 40.
    40 Even more duplication? publicclass AnyPage : BasePage { // ... public void ClickButton() => Click(Button); public void ClickOtherButton() => Click(OtherButton); public void ButtonText() => Text(Button); public void OtherButtonText() => Text(OtherButton); public void IsButtonDisplayed() => IsDisplayed(Button); public void IsOtherButtonDisplayed() => IsDisplayed(OtherButton); }
  • 41.
    Page objects are free-form. There’sno official version.There’s no conformity.There’s no enforcement. Page objects are more of a “convention” than a true design pattern.
  • 42.
    There must be abetter way.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
    Actors use Abilities to performInteractions. This is the heart of the Screenplay Pattern.
  • 50.
  • 51.
    51 Boa Constrictor • Opensource C# implementation of the Screenplay Pattern • Developed by PrecisionLender, a Q2 Company • Can be used with any test framework (SpecFlow, NUnit, etc.) • Doc site: q2ebanking.github.io/boa-constrictor • GitHub repository: q2ebanking/boa-constrictor • NuGet package: Boa.Constrictor The .NET Screenplay Pattern
  • 52.
    52 Let’s rewrite that DuckDuckGosearch test using Boa Constrictor.
  • 53.
    53 Required packages // NuGetPackages: // Boa.Constrictor // FluentAssertions // RestSharp // Selenium.Support // Selenium.WebDriver using Boa.Constrictor.Logging; using Boa.Constrictor.RestSharp; using Boa.Constrictor.Screenplay; using Boa.Constrictor.WebDriver; using FluentAssertions; using OpenQA.Selenium.Chrome; using static Boa.Constrictor.WebDriver.WebLocator;
  • 54.
    54 Creating the Actor IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger());
  • 55.
    55 Adding Abilities IActor actor= new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver()));
  • 56.
    56 Adding Abilities IActor actor= new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); public class BrowseTheWeb : IAbility { public IWebDriver WebDriver { get; } private BrowseTheWeb(IWebDriver driver) => WebDriver = driver; public static BrowseTheWeb With(IWebDriver driver) => new BrowseTheWeb(driver); }
  • 57.
    57 Modeling web pages IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); public static class SearchPage { public const string Url = "https://www.duckduckgo.com/"; public static IWebLocator SearchInput => L( "DuckDuckGo Search Input", By.Id("search_form_input_homepage")); }
  • 58.
    58 Attempting a Task IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url));
  • 59.
    59 Attempting a Task IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); public void AttemptsTo(ITask task) { task.PerformAs(this); }
  • 60.
    60 Attempting a Task IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); public class Navigate : ITask { private string Url { get; set; } private Navigate(string url) => Url = url; public static Navigate ToUrl(string url) => new Navigate(url); public void PerformAs(IActor actor) { var driver = actor.Using<BrowseTheWeb>().WebDriver; driver.Navigate().GoToUrl(Url); } }
  • 61.
    61 Attempting a Task IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); public class Navigate : ITask { private string Url { get; set; } private Navigate(string url) => Url = url; public static Navigate ToUrl(string url) => new Navigate(url); public void PerformAs(IActor actor) { var driver = actor.Using<BrowseTheWeb>().WebDriver; driver.Navigate().GoToUrl(Url); } }
  • 62.
    62 Attempting a Task IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); public static class SearchPage { public const string Url = "https://www.duckduckgo.com/"; public static IWebLocator SearchInput => L( "DuckDuckGo Search Input", By.Id("search_form_input_homepage")); }
  • 63.
    63 Asking a Question IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
  • 64.
    64 Asking a Question IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); public TAnswer AskingFor<TAnswer>(IQuestion<TAnswer> question) { return question.RequestAs(this); }
  • 65.
    65 Asking a Question IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); public class ValueAttribute : IQuestion<string> { public IWebLocator Locator { get; } private ValueAttribute(IWebLocator locator) => Locator = locator; public static ValueAttribute Of(IWebLocator locator) => new ValueAttribute(locator); public string RequestAs(IActor actor) { var driver = actor.Using<BrowseTheWeb>().WebDriver; actor.AttemptsTo(Wait.Until(Existence.Of(Locator), IsEqualTo.True())); return driver.FindElement(Locator.Query).GetAttribute("value"); } }
  • 66.
    66 Asking a Question IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); public class ValueAttribute : IQuestion<string> { public IWebLocator Locator { get; } private ValueAttribute(IWebLocator locator) => Locator = locator; public static ValueAttribute Of(IWebLocator locator) => new ValueAttribute(locator); public string RequestAs(IActor actor) { var driver = actor.Using<BrowseTheWeb>().WebDriver; actor.AttemptsTo(Wait.Until(Existence.Of(Locator), IsEqualTo.True())); return driver.FindElement(Locator.Query).GetAttribute("value"); } }
  • 67.
    67 Asking a Question IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); public static class SearchPage { public const string Url = "https://www.duckduckgo.com/"; public static IWebLocator SearchInput => L( "DuckDuckGo Search Input", By.Id("search_form_input_homepage")); }
  • 68.
    68 Asking a Question IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty();
  • 69.
    69 Composing a customInteraction IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
  • 70.
    70 Composing a customInteraction IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); public class SearchDuckDuckGo : ITask { public string Phrase { get; } private SearchDuckDuckGo(string phrase) => Phrase = phrase; public static SearchDuckDuckGo For(string phrase) => new SearchDuckDuckGo(phrase); public void PerformAs(IActor actor) { actor.AttemptsTo(SendKeys.To(SearchPage.SearchInput, Phrase)); actor.AttemptsTo(Click.On(SearchPage.SearchButton)); } }
  • 71.
    71 Composing a customInteraction IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); public class SearchDuckDuckGo : ITask { public string Phrase { get; } private SearchDuckDuckGo(string phrase) => Phrase = phrase; public static SearchDuckDuckGo For(string phrase) => new SearchDuckDuckGo(phrase); public void PerformAs(IActor actor) { actor.AttemptsTo(SendKeys.To(SearchPage.SearchInput, Phrase)); actor.AttemptsTo(Click.On(SearchPage.SearchButton)); } }
  • 72.
    72 Composing a customInteraction IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda"));
  • 73.
    73 Waiting for Questionsto yield answers IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
  • 74.
    74 Waiting for Questionsto yield answers IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
  • 75.
    75 Waiting for Questionsto yield answers IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True()); public static IWebLocator ResultLinks => L( "DuckDuckGo Result Page Links", By.ClassName("result__a"));
  • 76.
    76 Waiting for Questionsto yield answers IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
  • 77.
    77 Waiting for Questionsto yield answers IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True());
  • 78.
    78 Quitting the webbrowser IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True()); actor.AttemptsTo(QuitWebDriver.ForBrowser());
  • 79.
    79 A completed test IActoractor = new Actor(name: "Andy", logger: new ConsoleLogger()); actor.Can(BrowseTheWeb.With(new ChromeDriver())); actor.AttemptsTo(Navigate.ToUrl(SearchPage.Url)); actor.AskingFor(ValueAttribute.Of(SearchPage.SearchInput)).Should().BeEmpty(); actor.AttemptsTo(SearchDuckDuckGo.For("panda")); actor.WaitsUntil(Appearance.Of(ResultPage.ResultLinks), IsEqualTo.True()); actor.AttemptsTo(QuitWebDriver.ForBrowser());
  • 80.
  • 81.
    81 A REST APItest IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger());
  • 82.
    82 A REST APItest IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo")));
  • 83.
    83 A REST APItest IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo"))); var request = new RestRequest("api/breeds/image/random", Method.GET);
  • 84.
    84 A REST APItest IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo"))); var request = new RestRequest("api/breeds/image/random", Method.GET); var response = Actor.Calls(Rest.Request(request));
  • 85.
    85 A REST APItest IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo"))); var request = new RestRequest("api/breeds/image/random", Method.GET); var response = Actor.Calls(Rest.Request(request)); response.StatusCode.Should().Be(HttpStatusCode.OK);
  • 86.
    86 A REST APItest IActor actor = new Actor(name: "Andy", logger: new ConsoleLogger()); Actor.Can(CallRestApi.Using(new RestClient("https://dog.ceo"))); var request = new RestRequest("api/breeds/image/random", Method.GET); var response = Actor.Calls(Rest.Request(request)); response.StatusCode.Should().Be(HttpStatusCode.OK);
  • 88.
  • 89.
  • 90.
  • 91.
    91 SOLID principles Single-Responsibility PrincipleActors,Abilities, and Interactions are treated as separate concerns. Open-Closed Principle Each new Interaction must be a new class, rather than a modification of an existing class. Liskov Substitution Principle Actors can call all Abilities and Interactions the same way. Interface Segregation Principle Actors,Abilities, and Interactions each have distinct, separate interfaces. Dependency Inversion Principle Abilities and Interactions are handled as interfaces. Interactions use Abilities via dependency injection from the Actor.
  • 92.
  • 93.
    93 Why use Screenplay? 1.Rich, reusable, reliable interactions
  • 94.
    94 Why use Screenplay? 1.Rich, reusable, reliable interactions 2. Interactions are composable
  • 95.
    95 Why use Screenplay? 1.Rich, reusable, reliable interactions 2. Interactions are composable 3. Easy waiting
  • 96.
    96 Why use Screenplay? 1.Rich, reusable, reliable interactions 2. Interactions are composable 3. Easy waiting 4. Readable, understandable calls
  • 97.
    97 Why use Screenplay? 1.Rich, reusable, reliable interactions 2. Interactions are composable 3. Easy waiting 4. Readable, understandable calls 5. Covers all interactions – not justWeb UI
  • 98.
  • 99.
  • 100.
    100 The Screenplay Pattern providesbetter interactions for better automation.
  • 101.
    101 Actors use Abilitiesto perform Interactions Actor Ability Interaction
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
    109 I’ll mail BoaConstrictor stickers to 10 people who tweet what they learned from this webinar! Include @AutomationPanda & #BoaConstrictor!