 Welcome everyone to the Andrew's talk. So the topic he's going to cover is the screenplay pattern. It's better interaction for better automation. So I think I will leave it Andrew. And hello everyone. Thank you for joining my talk today. My name is Andy Knight. I'm the Automation Panda. Today I'm gonna be talking about screenplay pattern, better interactions for better automation. As I said, my name is Andy. I'm Automation Panda. I am also a developer advocate at Apple Tools. If you don't know what Apple Tools is, they provide visual testing tools to help your apps look visually perfect. It's actually really cool stuff. If you wanna give it a try, register for free app tools to come on their website. We have SDKs for all the major languages, tools, frameworks. You can do Selenium Java, you can do Cypress, Playwright, what have you. My job is to stand between company and community to make sure you get the best out of your testing and to listen to your needs to bring back to help us do better things. At Apple Tools, I'm also a director of Test Automation University, which I'm hoping most of you have heard of. It's one of the best online resources for learning. We have about 70 courses online completely for free about all topics, testing and automation. Be sure to check it out if you haven't already. Now, more pertinent to today's talk, I am the lead developer for BOA Constrictor, which is the .NET screenplay pattern. My hope today is I'm going to show you why screenplay pattern is awesome. Screenplay pattern is a new way to automate interactions. It's not exactly new, I shouldn't call it new. It's been around for quite a few years now, but it's still new to a lot of folks. You may have heard of it, you may not have heard of it, but most people that I've met in our industry doing testing and automation haven't actually used screenplay before. Personally, I believe screenplay is a much better way to model interactions than the traditional page object model. Wow, I know that might sound scandalous, that's SeleniumConf, but I believe this. And my goal today is to convince you of that as well. So I'm going to try to do that in three parts. First, I'll cover problems with traditional ways of automating interactions. Second, I'll explain why screenplay pattern is a better way. And third, I'll show how to use screenplay pattern with BOA constructor, the C-sharp library that my friends and I had built. So let's define that big I word I keep tossing around. Interactions. Simply put, interactions are how users operate with software. For this talk, I'll focus primarily on web UI interactions like clicking buttons and scraping text. Interactions are indispensable to testing. The simple switch to define testing is interaction plus verification. Think about it, that's it. You do something and you make sure it works. Think about any functional test case you've ever written or executed. The test case was a step-by-step procedure in which each step had interactions and verifications. Here's an example of a basic search engine test for let's say DuckDuckGo. It's like Google, Yahoo, Bing, they're all about the same. The steps here are fairly straightforward. Opening the search engine requires navigation. Searching for a phrase requires entering keystrokes and clicking the search button. Verifying results requires scraping the page title, resulting from the new page. As you can see, interactions are everywhere. Unfortunately, our industry struggles to handle automated web UI interactions well. Even though most teams use Selenium WebDriver and their test automation code, every team seems to use it differently. There's lots of duplicate code of flakiness, too. But let's look at the ways many teams evolve their web driver-based interactions. We're gonna use C-Sharp and I'll continue to use this DuckDuckGo example. When teams first start writing test automation code using Selenium WebDriver, they frequently write raw calls. Anyone familiar with the WebDriver API should recognize these calls, even if you're not a C-Sharp programmer. WebDriver object is initialized using, let's say Chrome driver. First step to open the search engine calls driver.navigate.go2url to the DuckDuckGo website address. Second step, perform search by fetching web elements using... Oh, pardon me, sorry, it's early morning here. Pardon me. Second step, perform search by fetching web elements using driver.findElement. It uses locators or selectors. And then it calls methods like senkeys and click. Third step, uses assertions to verify the contents of the page title and the existence of the result links. Finally, at the end of the test, WebDriver quit, browser clean up. Like I said, these are all common WebDriver calls. Unfortunately, there's a big problem in this code. Race conditions. There are no less than three race conditions in this code which the automation does not wait for the page to be ready before making interactions. WebDriver doesn't automatically wait for elements to load or for titles to appear. Oh, pardon me again. Sorry. Waiting is a huge challenge for WebUI automation. And it's one of the reasons for quote unquote flaky tests. Now you could set an implicit wait that will make the calls wait until target elements appear. But they don't work for all cases such as the title and race condition number two. Explicit waits provide much more control over waiting timeout and conditions. They use a WebDriver wait object with a preset timeout value and they must be placed explicitly throughout the code. Here, they are placed in the three spots where race conditions could happen. Oh, sorry, pardon me. Each wait.until call takes in a function that returns true when the condition is satisfied. These waits are necessary, but they cause new problems. First, they cause duplicate code because WebElement locators are used multiple times. Notice how search form input homepage is called twice. Second, raw calls with explicit waits may code less intuitive. If I remove the comments from each paragraph of code, what's left is a wall of text. It's difficult to understand what this code does at a glance. To remedy these problems, most teams use the page object pattern, page object model. In the page object model, each page is modeled as a class with locator variables and interaction methods. So, a search page class could look like this. At the top, there could be a constant for the page URL and variables, and variables for the search input and search button locators. Notice how each has an intuitive name. Next, there could be a variable to hold the WebDriver reference. This reference would come via dependency injection through the constructor. The first method would be a load method that navigates the browser to the page's URL. And the second method would be a search method that waits for the elements to appear, enters the phrase into the input field, and clicks the search button. This page object class has a decent structure and a mild separation of concerns. Locators and interactions have meaningful names. Page objects require a few more lines of code than raw calls at first, but their parts can easily be reused. The original test steps can be rewritten using this search page class. Notice how much cleaner this new code looks. We can also rewrite the other steps using page objects as well. Unfortunately though, this isn't quite perfect. Page objects themselves solve for problems of duplication in their interaction methods. Suppose a page object needs a method to click an element. We already know the logic. Wait for the element to exist and then click it. What about clicking another element? The method here is essentially hard coded for only one button. A second click method is needed to click another button. The code for both methods is essentially the same. And the code would be the same for any other click method as well. This is copy pasta, and it happens all the time in page objects. I've seen page objects grow to be thousands of lines long due to duplicative methods like these. This point, some teams will say, wait a minute, you got more duplicate code? We can solve that with more object-oriented programming. And they'll create the infamous base page, a parent class for all other page object classes. The base page will have variables for the web driver and the weight objects. It'll also provide common interaction methods, such as this click method that can click any element. Extraction wins, right? Child pages will inherit everything from the base page. Child page interaction methods frequently just call base page methods. You've seen many teams stop here and say, child with dog, this is good enough. Unfortunately, this really isn't very good at all. The base page helps mitigate code duplication, but it doesn't solve its root cause. Page objects inherently combine two separate concerns, page structure and interactions. Interactions are often generic enough to be used on any web element. Coupling interaction code with specific locators or pages forces testers to add new page object methods for every type of interaction needed for an element. Every element can potentially need to be clicked, have as text grades, check if it's displayed or anything else. It's a lot of extra code that shouldn't be necessary. The base page also becomes very top-heavy as testers add more and more code to ship. More frustratingly, the page object code I showed is merely one type of implementation. What do your page objects look like? I bet dollars to donuts, they look different than mine. Page objects are completely free form. Every team implements them differently. There's no official version of the page object model. There's no conformity in its design. Even worse, within its design, there's almost no way for the pattern to enforce good practices. That's why people argue whether page object locators should be public or private. Page objects would be better described as a convention rather than a true design pattern. Mm-hmm. There must be a better way to handle interactions. Thankfully, there is. Let's take a closer look at how interactions happen. First, there's someone who initiates interactions. Usually, this is a user. They're the ones making the clicks and taking the scrapes and navigating the pages. Let's call them the actor. Second, there's the product under test. For our examples in this talk, that's a web app. It has pages with elements. Web page structure is modeled using locators to access page elements from the DOM. Keep in mind, the thing under test could be anything else, like a mobile app, a microservice, even a command line. Third, there are the interactions themselves. For web apps, they could be simple clicks and keystrokes, or they could be more complex interactions, like looking into the app, searching for a phrase. Each interaction will do the same type of operation on whatever kind, target, page, or element it is given. Finally, we have abilities. They enable actors to perform certain types of interactions. For example, browser interactions need a tool like Selenium WebDriver to make clicks and scrapes. Actors, abilities, and interactions are each different types of concerns. We could summarize their relationship in one line. Actors use abilities to perform interactions. Actors use abilities to perform interactions. This is the heart of the screenplay pattern. In page object convention, page objects become messy because concerns are all combined. Screenplay pattern separates concerns for maximal reusability and scalability. So, let's learn how to screenplay using Boa Constrictor. Boa Constrictor is an open-source, C-sharp implementation of the screenplay pattern that my former team and I developed while I was at Precision Lenders, a Q2 company. It is the cornerstone of Precision Lenders end-to-end test automation solution to this day. It can be used with any .NET test framework, like spec flow or end-unit. The product has a rich doc site hosted using GitHub Pages, which you can check out. The repository name is Q2ebanking.boaconstrictor. And the NuGet package name is boa.constrictor. Let's rewrite that doc.go-search test from before using Boa Constrictor. All the code I show on this talk is available in the Boa Constrictor library. You can even take the full tutorial to get hands-on with the code. To use Boa Constrictor, you'll need to install the Boa Constrictor, breast-sharp, and Selending and WebDriver NuGet packages. My example code here will also use Fluent Assertions and ChromeDriver, which are extras. Let's begin. The Actor is the entity that initiates interactions. All screenplay calls start with an Actor. Most test cases need only one Actor. The Actor class optionally takes two arguments. First, arguments and name, which can help describe who the Actor is. This name will appear in logged messages. Second argument is a Logger, which will send logged messages from screenplay calls to a target destination. Loggers must implement Boa Constrictor's iLogger interface. ConsoleLogger is a class that will log messages to the system console. You can define your own custom Loggers by implementing that iLogger interface. Abilities enable Actors to initiate interactions. For example, an Actor needs a Selending and WebDriver instance to click Elements on a web page. Read this new line in plain English. The Actor can browse the web with a new ChromeDriver. Boa Constrictor's Fluent-like syntax makes its call chains very readable. Actor.can adds an ability to an Actor. Browse the web is the ability that enables the Actor to perform web UI interactions. Browse the web.width provides the WebDriver object that the Actor would use, which in this case is a new ChromeDriver object. Boa Constrictor supports all browser types. All abilities must implement the iAbility interface. Actors can be given any number of abilities. Browse the web simply holds a reference to the WebDriver object. Web UI interactions will retrieve this WebDriver object from the Actor. Before the Actor can call any WebDriver-based interactions, the WebDriver pages under test need models. These models should be static classes that include locators for elements on the page and possibly page URLs, if appropriate. Page classes should only model structure. They should not include any interaction logic. Screenplay patterns separates the concern of page structure from interaction. That way, interactions can target any element, maximizing code reusability. Interactions like clicks and scrapes work the same regardless of the target element. Search page class has two members. First is URL, second is the locator for the search input. Locators have two parts. First, a plain language description that will be used for logging. Second, has a query or selector that's used to find the element on the page. Boa Constrictor uses Selenium WebDrivers by queries. For convenience, locators can be constructed using the statically imported L method. Let's move on to interactions. Screenplay pattern has two types of interactions. First is called a task. A task performs actions without returning a value. Think of it as like a void method. Examples of tasks include clicking element, refreshing a browser and loading a page. These interactions all do something rather than get something. Here, Boa Constrictor provides a task named navigate for loading webpages using a target URL. Read this line in plain English. The actor attempts to navigate to the URL for the search page. Again, Boa Constrictor's fluent like syntax is very readable. Clearly this line will load the doc.go search page. Actor.attempts2 is what calls the task. All tasks must implement the itask interface. When the actor calls attempts2 on a task, it calls the tasks perform as method. Navigate is the name of the task and .tourl provides the target URL. Navigate tasks perform as method, fetches the WebDriver object from the actor's ability and uses it to load the given URL. Search page.url comes from the search page we previously wrote. Putting the URL in the page class just makes it universally available. In a more serious project, you might want to read this in as some sort of input value to target whatever environment you're covering. And that's a task. Second type of interaction is called a question. Question returns an answer after performing actions. Samples of questions include getting an element's text, location, or appearance. Each of these interactions returns some sort of value. Right there, like the number seven here. Hmm. Excuse me. Bo constrictor provides a question named value attribute that gets the value of the text currently inside an input field. Read this line in plain English. The actor asking for the value attribute of the search page and search input element should be empty. Actor.asking4 is what calls the question. All questions must implement the iQuestion interface. On the actor calls asking for or the equivalent asks for method, he calls the questions request as method. Value attribute is the name of the question. And .of provides the target web elements locator. Value attributes request as method fetches the web driver object, waits for the target element to exist on the page and scrapes and returns its value attribute. Search page.searchInput comes from that search page class. And finally, once that value attribute is obtained, the test must make an assertion on it. Should be empty is a fluent assertion that verifies that search input field is empty when the page is first loaded. Test case next step is to enter a search phrase. Doing this requires two interactions. Typing the phrase into the search input and clicking the search button. However, since searching is such a common operation, we can create a custom interaction for search by composing the lower level interactions together. The search.go test takes in a search phrase and in this perform as method, it calls two other interactions. Send keys and click. Using one task to combine these lower level interactions makes the test code more readable and understandable. It also improves automation and usability. Read this line in plain English now. The actor attempts to search.go for Panda. That's concise and intuitive. The last test case step should verify that the result links appear after entering a search phrase. Unfortunately, the step has a race condition. The result page takes a few seconds to display result links. Automation must wait for those links to appear. Checking too early will make the test case fail. Bo constrictor makes waiting easy. Read this line in plain English. The actor waits until the appearance of result page links is equal to true. In simpler terms, wait until result links appear. Waits until is a special method. It will repeatedly call a question until the answer meets the given condition. For this step, the question is the appearance of the result links to the result page. Before links are loaded, this question will return false. Once links appear, it will return true. The condition for waiting for the answer is for the value to become true. Bo constrictor provides several conditions out of the box, such as equality, math operations, and string matching. You can also implement custom conditions by implementing the I-condition interface. Waiting is smart. It will repeatedly ask the question until the answer is met, and then it will move on. This makes waiting much more efficient than hard sleeps. The answer does not meet the condition within the timeout. Then the wait will raise an exception. Default times out to 30 seconds, but it can be overwritten. Running a Bo constrictor's web driver-based interactions already have waiting. Anything that uses a target element, like click, send keys, or text, will wait for the element to exist before attempting any operation. We saw this in some of the previous example code. However, there are times where explicit waits are needed. Interactions that query appearance or existence do not automatically wait. Final test step is to quit the browser. Bo constrictor has a quit web driver task for that. Remember, if you don't quit the browser, it might remain open and turn into a zombie. Always quit your browser. Furthermore, in whatever test framework you use, put the step to quit the browser in a clean up or tear down routine. So it's called even when the test fails. And there we have our completed test using Bo constrictor's screenplay powder. All the separated concerns come together beautifully to handle interactions in a much better way. Screenplay can also be used for more than web UI interactions. Bo constrictor has interactions for REST APIs using RESTsharp as well. Let's quickly step through a REST API test. Actors the same as before to call REST APIs. Active needs an ability named call REST API. It uses a RESTsharp REST client with a base URL. For this test, we'll use a public API called dog API, which returns random pictures of dogs, though I'm sure none of them are gonna be accused, my puppy. For this test, we'll use, sorry, one actor can have multiple abilities as long as each ability has a different type. So I could have an actor with a browser web ability as well as a call REST API ability. Bo constrictor uses RESTsharp's request, REST request objects directly, which specify resource path and HTTP method. To call REST API, Bo constrictor uses this call. The actor calls the REST request using that request object. We can then check response object for response code and other data. This test is quite basic, but Bo constrictor can also do some advanced tricks like downloading files, dumping responses, and automatically deserializing response bodies. REST API interactions may also be composed with web UI interactions, pretty cool. For example, you could perform login with REST API, grab the authentication cookie, dunk it in the browser, refresh, and you optimize login. Cool stuff like that. As we said before, the screenplay pattern can be summed up in one line. Actors use abilities to perform interactions. It's that simple. Actors use abilities to perform interactions. Those who like object-oriented programming, screenplay pattern is in a sense, a solid refactoring of the page object model. Solid refers to five design principles for maintainability and extensibility. I won't go into detail about each principle here because the information is a bit dense. But if you're interested, then snap a quick snapshot and check out all of these principles later. Wikipedia is a great source. You'll find the screenplay fits each one of these nicely. So why should you use the screenplay pattern over page object model or raw web drive calls? Here's a few key reasons. First, screenplay pattern and specifically the Bo constrictor project provide rich, reusable, reliable interactions out of the box. Bo constrictor already has tasks and questions for every type of web driver-based interaction. And each one is battle-hardened and safe. Second, screenplay interactions are composable. Like we saw with search for a phrase, you can easily combine interactions. This makes coding easier to use and reuse and it avoids lots of duplication. Third, the screenplay pattern makes waiting easy using existing questions and conditions. Waiting is one of the toughest parts of black box automation. Fourth, screenplay calls are readable and understandable. They use a fluent-like syntax that reads more like prose than code. And finally, screenplay pattern at its core is a design pattern for any type of interaction. In this talk, I showed how to use it for web UI interactions. But the screenplay pattern could also be used for mobile, SAPI, and other platforms. You can make your own interactions too. Overall, screenplay pattern provides better interactions for better automation. That's the point. This isn't just another Selenium web driver wrapper. It's not just a new spin-on-page objects. Screenplay is a great way to exercise any feature behaviors under test. And as we saw before, it's not that complicated. Actors use abilities to perform interactions. That's it. The programming behind it just has some nifty dependency injection. If you'd like to start using the screenplay pattern for your test automation, there's a few ways to get started. If you're in C-sharp, use Boa Constrictor. The library I just showed you. If you're in Java or JavaScripts, you've got Serenity BDD, which is a mature, complete test automation framework that includes screenplay pattern. In fact, Serenity BDD greatly influenced Boa Constrictor. But Boa Constrictor is not Serenity BDD for .NET. They're two entirely separate projects. Instead, Boa Constrictor aims to be a simpler, standalone implementation of screenplay. If you're programming in Python, I got great news for you. I just released a package called Screenflow, all-overcase. Python's screenplay package is a bare-bones implementation of the core pattern. It's something I've been working on all the side. I've actually used it with PlayWrite, and I have an example out there. So if you're interested in that, let me know. And I'd love help making that project more mature. And if none of these options suit you, you could create your own. Screenplay pattern does require a bit of boilerplate code, but it's worthwhile in the end. You can always reference code from Boa Constrictor and Serenity BDD. If you want to learn more, visit our docs. It provides thorough information about projects and about screenplay in general. I recommend taking that hands-on tutorial I mentioned so you can develop a test automation project yourself with Boa Constrictor. That tutorial covers both WebUI and REST API interactions. And since Boa Constrictor is open source, we'd love for you to contribute. So thank you again so much for taking the time, this morning, evening, afternoon, wherever you are in the world to learn more about screenplay. Again, my name is Andy Knight. You can call me Pandy for short. I'm the Automation Panda. Hit me up on Twitter at AutomationPanda. Check out my blog, AutomationPanda.com. I'm developer, advocate at Apple Tools and director of Test Automation University. I would love for you to start doing screenplay and reach out if you'd love to join the Boa Constrictor project. Thank you so much and we'll now move on to Q&A. Hello, thank you very much, Andy. I think it was very, very detailed and insightful talk. So I hope everyone enjoys. So please give a big hand to Andy. He has done a great effort in doing the presentations and explanations. So now we have got like couple of minutes before we finish. So we have got Q&A session now. So if any questions, so please type it into Q&A chat box right now so we will deal on the stage right now. So Andy, there are three questions. So let me read the questions. So can we directly use the same actor to interact with both UI and API or we need two different actors? That is a fantastic question. You only need one actor. The whole purpose of screenplay is extensibility. It's the idea that you can add abilities and interactions as you need it without shoving everything into one class or one file. So you can say actor can browse the web and then the same actor can call REST API. You can even make other abilities. In my previous project, we had abilities, things like actor can read inputs, actor can read config. And so one actor can have multiple abilities. It's really nice that way. Okay, I hope it answers the questions. Another one is does it support Selenium 3 or we need to use Selenium 4? So Bow Constrictor is set to use Selenium 4. And the reason for that is a very pesky bug that we discovered in .NET framework, I think it was 4.7 under Selenium 3. So using Bow Constrictor, I should say it was not a Bow Constrictor thing, using Selenium 3 in .NET, if you were doing it at scale, it would eventually exhaust certain numbers of ports on your machine more rapidly than it should. But when we updated to Selenium 4, that problem went away. So hence, Bow Constrictor is set to use Selenium 4. Okay, I think I hope that answers the questions. So another one is how can we achieve this in Java? Great question. If you want to do screenplay in Java, right now, your primary option is Serenity BDD. That's the biggest implementation I know about in Java. I know a lot of people may not want to do that because Serenity BDD is a full framework and not just a standalone screenplay implementation. There's another guy out there. I forget his name. I wanna say it's Michael Kurtz. I'd have to look it up. There's some guy out there. I think he's from Europe. Who recently released his own tiny version of a screenplay pattern in Java. And I can't remember what it was called. Gosh, I'd have to look that up and share later. But he made like a little toy example. Maybe it's a little more than a toy. But he made like a small screenplay-ish implementation in Java. It's different from Serenity. It's different from Bow Constrictor. But you can look that one up too. I don't know how actively he's maintaining it. I got the, like, I shouldn't say anymore because I'm not very familiar with the project. I think he started it and tried it and kept going with it. And I think he's still working on it. I'm not exactly sure. I'm sorry. It's okay. I think that's the three questions were posted. So guys, if you, yeah, I think another one coming up. Does it support mobile app? So great question. Bow Constrictor currently does not have any out-of-the-box mobile interactions. That does not mean that screenplay or Bow Constrictor can't be used with mobile. You can make your own interactions for mobile. And so if that's something that you're interested in, I would love to chat with you because I would love to add, say, apiome interactions to Bow Constrictor. Okay. I hope that answers the questions, Charlie. Thank you very much, everyone. We are all signing off here and goodbye and good day.