Package: testla-screenplay
Testla ScreenplayIntroductionThe testla project is a collection of tools of different tools to help in the QA automation process.
Testla screenplay core defines the frame for an implementation of the Screenplay Pattern.What is Screenplay Pattern and how does it work?The Screenplay Pattern is a user-centred approach to writing high-quality automated tests. It steers you towards an effective use of layers of abstraction, helps your tests capture the business vernacular, and encourages good testing and software engineering habits.Instead of focusing on low-level, interface-centric interactions, you describe your test scenarios in a similar way you'd describe them to a human being - an actor in Screenplay-speak. You write simple, readable and highly-reusable code that instructs the actors what activities to perform and what things to check. The domain-specific test language you create is used to express screenplays - the activities for the actors to perform in a given test scenario.The Screenplay Pattern is beautiful in its simplicity. It's made up of five elements, five types of building blocks that Testla gives you to design any functional acceptance test you need, no matter how sophisticated or how simple.The key elements of the pattern are: actors, abilities, tasks, actions and questions.How to use this package?Define an abilityAbilities are essential since they definewhatan actorcan do. So the first thing we need to do is to define an ability by extending the testla ability.from@testla/screenplayimportAbility,ActorclassMyBrowseAbility(Ability):def__init__(self,page:Page):super.__init__()self.page=page# passing in whatever is required for this ability# in our example a page object from playwright@staticmethoddefusing(page:Page)->"MyBrowseAbility":returnMyBrowseAbility(page)# this function is essential so that the actor can execute a task/action with this ability@staticmethoddefAs(actor:Actor)->"MyBrowseAbility":returnactor.with_ability_to(MyBrowseAbility)# navigate functionality by using playwright spicific code for our exampledefnavigate(self,url:str)->None:returnself.page.goto(url)# fill functionality by using playwright spicific code for our exampledeffill(self,locator:str,value:str)->None:returnself.page.fill(locator,value)# click functionality by using playwright spicific code for our exampledefclick(self,locator:str)->None:returnself.page.click(locator)# find functionality by using playwright spicific code for our exampledeffind(locator:string)->object:returnself.page.wait_for_selector(locator)# further implementations# ...Define actionsThe next step is to define actions and which can be grouped into tasks later. Actions use abilities to perform actual activities.from@testla/screenplayimportActor,ActionclassNavigate(Action):def__init__(self,url:str):super.__init__()self.url=url# the actual implementation of the actiondefperform_as(self,actor:Actor)->object:returnMyBrowseAbility.As(actor).navigate(self.url)# static member method to invoke the action@staticmethoddefto(url:string)->"Navigate":returnNavigate(url)classFill(Action):def__init__(self,locator:str,value:str){super.__init__()self.locator=locatorself.value=value}# the actual implementation of the actiondefperform_as(self,actor:Actor)->object:returnMyBrowseAbility.As(actor).fill(self.locator,self.value)# static member method to invoke the action@staticmethoddefWith(locator:str,value:str)->"Fill":returnFill(locator,value)classClick(Action):def__init__(self,locator:str):super.__init__()self.locator=locator# the actual implementation of the actiondefperform_as(self,actor:Actor)->object:returnMyBrowseAbility.As(actor).click(self.locator)# static member method to invoke the action@staticmethoddefon(locator:str)->"Click":returnClick(locator)Define a taskTasks group actions into logical entities.from@testla/screenplayimportActor,TaskclassLogin(Task):# the actual implementation of the taskdefperform_as(actor:Actor)->object:returnactor.attempts_to(Navigate.to("https://www.my-fancy-url.com"),Fill.With("#username",actor.username),Fill.With("#password",actor.password),Click.on("#login-button"),)# static member method to invoke the task@staticmethoddefto_app()->"Login":returnLogin()Define a questionQuestions are used to check the status of the application under test.from@testla/screenplayimportActor,QuestionclassLoginStatus(Question):# the actual implementation of the taskdefanswered_by(actor:Actor)->object:returnBrowseTheWeb.as(actor).find("#logged-in-indicator")# static member method to invoke the question@staticmethoddefof():returnLoginStatus()Define a test caseThe final step is to define a test case using the Actions and Abilities defined above.from@testla/screenplayimportActor# Example test case with Playwrightdefmy_first_test(page):actor=Actor.named("James").With("username","John Doe").With("password","MySecretPassword").can(MyBrowseAbility.using(page))actor.attempts_to(Login.to_app())expect(actor.asks(LoginStatus.of())).not.to_be_null()What about the 'Screen' in 'Screenplay'?With screen is meant that all locators for page elements are held in specific files/collections. In our example from above we put the locators inline. A sample screen file for the Login task could look like this:# screen.pyLOGIN_URL="https://www.my-fancy-url.com"USERNAME_INPUT="#username"PASSWORD_INPUT="#password"LOGIN_BUTTON="#login-button"Within the task the screen elements are then used as:from@testla/screenplayimportActorfromscreenimportLOGIN_URL,USERNAME_INPUT,PASSWORD_INPUT,LOGIN_BUTTONdefperform_as(actor:Actor)->object:returnactor.attempts_to(Navigate.to(LOGIN_URL),Fill.With(USERNAME_INPUT,actor.states('username')),Fill.With(PASSWORD_INPUT,actor.states('password')),Click.on(LOGIN_BUTTON),)