Package: testla-screenplay-playwright
Testla Screenplay-PlaywrightIntroductionThe testla project is a collection of tools of different tools to help in the QA automation process.
This package uses theTestla screenplay core packageto implement the screenplay pattern for Playwright.How to use this package?Testla Screenplay-Playwright comes with abilities, question and actions to browse user interfaces and querying APIs.BrowseTheWeb AbilityThis ability enables the actor to interact with the browser and browse web user interfaces.using(page: Page)Initializes the Ability by passing a Playwright Page object.as(actor: Actor)Use the Ability as an Actor. Required by Actions to get access to the ability functions. Examples can be found below.addCookies(cookies: Cookie[])Add cookies into this browser context. All pages within this context will have these cookies installed. Cookies can be obtained via BrowseTheWeb.getCookies([urls]).BrowseTheWeb.as(actor).addCookies([{name:'my cookie',value:'my value',url:'http://www.myapp.com',}]);checkBox(selector: string, options?: SelectorOptions)Check the specified checkbox.// simple call with just selectorBrowseTheWeb.as(actor).checkBox('mySelector');// or with optionsBrowseTheWeb.as(actor).checkBox('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});clearCookies()Clear the browser context cookies.BrowseTheWeb.as(actor).clearCookies();click(selector: string, options?: SelectorOptions)Click the element specified by the selector.// simple call with just selectorBrowseTheWeb.as(actor).click('mySelector');// or with optionsBrowseTheWeb.as(actor).click('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});dblclick(selector: string, options?: SelectorOptions)Double Click the element specified by the selector.// simple call with just selectorBrowseTheWeb.as(actor).dblclick('mySelector');// or with optionsBrowseTheWeb.as(actor).dblclick('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});dragAndDrop(sourceSelector: string, targetSelector: string, options?: { source?: SelectorOptions, target?: SelectorOptions })Drag the specified source element to the specified target element and drop it.// simple call with just source and target selectorBrowseTheWeb.as(actor).dragAndDrop('sourceSelector','targetSelector');// or with optionsBrowseTheWeb.as(actor).dragAndDrop('sourceSelector','targetSelector',{source:{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]},target:{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]}});fill(selector: string, input: string, options?: SelectorOptions)Fill the element specified by the selector with the given input.// simple call with just selector and input valueBrowseTheWeb.as(actor).fill('mySelector','myInput');// or with optionsBrowseTheWeb.as(actor).fill('mySelector','myInput',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});getCookies(urls?: string | string[])Get the cookies of the current browser context. If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those URLs are returned.// get all cookiesBrowseTheWeb.as(actor).getCookies();// get cookies for one single domainBrowseTheWeb.as(actor).getCookies('https:www.myapp.com');// get cookies for two domainsBrowseTheWeb.as(actor).getCookies(['https:www.myapp.com','https:www.another-app.com']);getLocalStorageItem(key: string)Get a local storage item specified by the given key.BrowseTheWeb.as(actor).getLocalStorageItem('some key');getSessionStorageItem(key: string)Get a session storage item specified by given key.BrowseTheWeb.as(actor).getSessionStorageItem('some key');goto(url: string)Use the page to navigate to the specified url.BrowseTheWeb.as(actor).goto('myURL');hover(selector: string, options?: SelectorOptions & { modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[] })Use the page mouse to hover over the specified element.// simple call with just selectorBrowseTheWeb.as(actor).hover('mySelector');// or with optionsBrowseTheWeb.as(actor).hover('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]modifiers:['Alt','Shift']});checkEnabledState(selector: string, mode: 'enabled' | 'disabled', options?: SelectorOptions, timeout?: number)Verify if a locator on the page is enabled or disabled.// simple call with just selectorBrowseTheWeb.as(actor).checkEnabledState('mySelector','enabled');// or with optionsBrowseTheWeb.as(actor).checkEnabledState('mySelector','disabled',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});checkVisibilityState(selector: string, mode: 'visible' | 'hidden', options?: SelectorOptions, timeout?: number)Verify if a locator on the page is visible.// simple call with just selectorBrowseTheWeb.as(actor).checkVisibilityState('mySelector','visible');// or with optionsBrowseTheWeb.as(actor).checkVisibilityState('mySelector','hidden',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});press(keys: string)Press the specified key(s) on the keyboard.// Press a single buttonBrowseTheWeb.as(actor).press('A');// or multiple buttonsBrowseTheWeb.as(actor).press('Control+A');removeLocalStorageItem(key: string)Delete a local storage item, if a key/value pair with the given key exists.BrowseTheWeb.as(actor).removeLocalStorageItem('some key');removeSessionStorageItem(key: string)Delete a session storage item, if a key/value pair with the given key exists.BrowseTheWeb.as(actor).removeSessionStorageItem('some key');setLocalStorageItem(key: string, value: any)Set a local storage item identified by the given key + value, creating a new key/value pair if none existed for key previously.BrowseTheWeb.as(actor).setLocalStorageItem('some key','some value');setSessionStorageItem(key: string, value: any)Set a session storage item identified by the given key + value, creating a new key/value pair if none existed for key previously.BrowseTheWeb.as(actor).setSessionStorageItem('some key','some value');type(selector: string, input: string, options?: SelectorOptions)Type the given input into the element specified by the selector.// simple call with just selector and input valueBrowseTheWeb.as(actor).type('mySelector','myInput');// or with optionsBrowseTheWeb.as(actor).type('mySelector','myInput',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});waitForLoadState(status: 'load' | 'domcontentloaded' | 'networkidle')Wait for the specified loading state.BrowseTheWeb.as(actor).waitForLoadState('networkidle');waitForSelector(selector: string, options?: SelectorOptions)Wait until the element of the specified selector exists.// simple call with just selectorBrowseTheWeb.as(actor).waitForSelector('mySelector');// or with optionsBrowseTheWeb.as(actor).waitForSelector('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});Web ActionsAdd.cookies(cookies: Cookie[])Add the specified cookies.Add.cookies([{name:'my cookie',value:'my value',url:'http://www.myapp.com',}]);Check.element(selector: string, options?: SelectorOptions)Checks a checkbox// simple call with just selectorCheck.element('mySelector');// or with optionsCheck.element('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});Clear.cookies()Clear all browser cookies.Clear.cookies();Click.on(selector: string, options?: SelectorOptions)Click an element// simple call with just selectorClick.on('mySelector');// or with optionsClick.on('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});DoubleClick.on(selector: string, options?: SelectorOptions)Doubleclick an element// simple call with just selectorDoubleClick.on('mySelector');// or with optionsDoubleClick.on('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});DragAndDrop.execute(sourceSelector: string, targetSelector: string, options?: { source?: SelectorOptions, target?: SelectorOptions })Drag an element specified by the source selector and drop it at the target selector// simple call with just selectorDragAndDrop.execute('sourceSelector','targetSelector');// or with optionsDragAndDrop.execute('sourceSelector','targetSelector',{source:{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]},target:{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]}});Fill.in(selector: string, value: string, options?: SelectorOptions)Fill a given string into an input element// simple call with just selectorFill.in('mySelector','myInput');// or with optionsFill.in('mySelector','myInput',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});Get.cookies(urls?: string | string[])Get the specified cookies. If no urls are speciefied, get all cookies.// get all cookiesGet.cookies();// get cookies for a single domainGet.cookies('https://www.myapp.com');// get cookies for two domainsGet.cookies(['https://www.myapp.com','https://www.another-app.com']);Get.localStorageItem(key: string)Get a local storage item.Get.localStorageItem('some key');Get.sessionStorageItem(key: string)Get a session storage item.Get.sessionStorageItem('some key');Hover.over(selector: string, options?: SelectorOptions & { modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[] })Hover an element// simple call with just selectorHover.over('mySelector');// or with optionsHover.over('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]modifiers:['Alt','Shift']});Press.key(keys: string)Press key(s) on the keyboard// single keyPress.key('A');// multiple keysPress.key('Control+A')Navigate.to(url: string)Use the browser page to navigate to a specified urlNavigate.to('myUrl');Remove.localStorageItem(key: string)Remove a local storage item, if a key/value pair with the given key exists.Remove.localStorageItem('some key');Remove.sessionStorageItem(key: string)Remove a session storage item, if a key/value pair with the given key exists.Remove.sessionStorageItem('some key');Set.localStorageItem(key: string, value: any)Set a local storage item identified by the given key + value, creating a new key/value pair if none existed for key previously.Set.localStorageItem('some key','some value');Set.sessionStorageItem(key: string, value: any)Set a session storage item identified by the given key + value, creating a new key/value pair if none existed for key previously.Set.sessionStorageItem('some key','some value');Type.in(selector: string, value: string, options?: SelectorOptions)Type a given string into an input element// simple call with just selectorType.in('mySelector','myInput');// or with optionsType.in('mySelector','myInput',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});Wait.forLoadState(state: 'load' | 'domcontentloaded' | 'networkidle')Wait for a load state to be presentWait.forLoadState(state:'load'|'domcontentloaded'|'networkidle');Wait.forSelector(selector: string, value: string, options?: SelectorOptions)Wait for an element to be present// simple call with just selectorWait.forSelector('mySelector');// or with optionsWait.forSelector('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});UseAPI AbilityThis ability enables the actor to interact with APIs by sending http requests.as(actor: Actor)Use the Ability as an Actor. Used by Actions to get access to the ability functions. Examples can be found below.using(requestContext: APIRequestContext)Initialize this Ability by passing an already existing Playwright APIRequestContext object.sendRequest(method: RequestMethod, url: string, headers?: any, responseFormat?: ResponseBodyFormat, data?: any)Send a request (GET, POST, PATCH, PUT, HEAD or DELETE) to the specified url. Headers, a desired format for the response's body and data can also be sent.UseApi.as(actor).sendRequest(REQUEST_METHOD.POST,'/items',{authorization:'Bearer dfh.dasgeq65qg.eyjkhf'},'json',{title:'new item'});checkStatus(response: Response, status: number, mode: 'equal' | 'unequal')Verify if the given response's status is equal to the expected status.UseApi.as(actor).checkStatus(response,200,'equal');checkBody(response: Response, body: ResponseBodyType, mode: 'equal' | 'unequal')Verify if the given response's body is equal to the expected body. The check includes type safety.// json responseUseApi.as(actor).checkBody(response,{text:'test'},'equal');// text responseUseApi.as(actor).checkBody(response,'test','unequal');// buffer responseUseApi.as(actor).checkBody(response,Buffer.from('abc'),'equal');checkHeaders(response: Response, headers: {[key: string]: string | undefined }, mode: 'included' | 'excluded')Verify if the given headers are included in the given response's headers.
If the header has a value !== undefined, both key and value will be checked. If a header has a value === undefined, only the key will be checked.// check only keysUseApi.as(actor).checkHeaders(response,{contentType:undefined},'included');// check key and valueUseApi.as(actor).checkHeaders(response,{contentType:'application/json'},'excluded');checkDuration(response: Response, duration: number, mode: 'lessOrEqual' | 'greater')Verify if the reponse (including receiving body) was received within a given duration.// check if response was received within 2sUseApi.as(actor).checkDuration(response,2000,'lessOrEqual');API ActionsDelete.from(url: string)Sends a DELETE request to the api. Optionally it is possible to chain definitions for headers and data as well as the expected response type.// simple requestDelete.from('https://my-fancy-url.com');// with chained definitionsDelete.from('https://my-fancy-url.com')// add headers.withHeaders({key:value,})// add data.withData({key:value,})// define expected response format.withResponseFormat('text');Get.from(url: string)Sends a GET request to the api. Optionally it is possible to chain definitions for headers as well as the expected response type.// simple requestGet.from('https://my-fancy-url.com');// with chained definitionsGet.from('https://my-fancy-url.com')// add headers.withHeaders({key:value,})// define expected response format.withResponseFormat('text');Head.from(url: string)Sends a HEAD request to the api. Optionally it is possible to chain definitions for headers as well as the expected response type.// simple requestHead.from('https://my-fancy-url.com');// with chained definitionsHead.from('https://my-fancy-url.com')// add headers.withHeaders({key:value,})// define expected response format.withResponseFormat('text');Patch.to(url: string)Sends a PATCH request to the api. Optionally it is possible to chain definitions for header sand data as well as the expected response type.// simple requestPatch.to('https://my-fancy-url.com');// with chained definitionsPatch.to('https://my-fancy-url.com')// add headers.withHeaders({key:value,})// add data.withData({key:value,})// define expected response format.withResponseFormat('text');Post.to(url: string)Sends a POST request to the api. Optionally it is possible to chain definitions for header sand data as well as the expected response type.// simple requestPost.to('https://my-fancy-url.com');// with chained definitionsPost.to('https://my-fancy-url.com')// add headers.withHeaders({key:value,})// add data.withData({key:value,})// define expected response format.withResponseFormat('text');Put.to(url: string)Sends a Put request to the api. Optionally it is possible to chain definitions for header sand data as well as the expected response type.// simple requestPut.to('https://my-fancy-url.com');// with chained definitionsPut.to('https://my-fancy-url.com')// add headers.withHeaders({key:value,})// add data.withData({key:value,})// define expected response format.withResponseFormat('text');Generic Actions which do not require any particular AbilitySleep.for(ms: number)Pause the execution of further test steps for a given interval in millisecondsSleep.for(5000);Available Web QuestionsElement.toBeChecks if a condition is true.Element.notToBeChecks if a condition is false.Element.*.visible(selector: string, options?: SelectorOptions)Validates wether an element is visible. A mode operator must be prepended.// simple call with just selectorElement.toBe.visible('mySelector');// or with optionsElement.notToBe.visible('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});Element.*.enabled(selector: string, options?: SelectorOptions)Validates wether an element is enabled. A mode operator must be prepended.// simple call with just selectorElement.toBe.enabled('mySelector');// or with optionsElement.notToBe.enabled('mySelector',{hasText:'myText',subSelector:['mySubSelector',{hasText:'anotherText'}]});Available Api QuestionsResponse.hasChecks if a condition is true.Response.hasNotChecks if a condition is false.Response.*.statusCode(response: Response, code: number)Checks if the response has a given status code. A mode operator must be prepended.Response.has.statusCode(response,200);Response.hasNot.statusCode(response,200);Response.*.body(response: Response, body: ResponseBodyType)Checks if the response body equals a given body. A mode operator must be prepended.// json formatResponse.has.body(response,{key:value});// text formatResponse.hasNot.body(response,'text');// buffer formatResponse.has.body(response,Buffer.from('abc'));Response.*.headers(response: Response, headers: Headers)Checks if the response has the given headers either by key (value to be set to undefined) or key/value lookup. A mode operator must be prepended.// only check for header presence by passing undefined as the valueResponse.has.headers(response,{'content-type':undefined});// lookup for key/value combination to be presentResponse.hasNot.headers(response,{'content-type':'application/json'});Response.*.beenReceivedWithin(response: Response, duration: number)Checks if the reponse (including receiving body) was received within a given duration. A mode operator must be prepended.// check if response was received within 2sResponse.has.beenReceivedWithin(response,2000);// check if response was not received within 2sResponse.hasNot.beenReceivedWithin(response,2000);Group Actions into a TaskTasks group actions into logical entities. Here is a task that uses the actions Navigate, Fill and Click from the web capabilities and Get from api capabilities.// file: ./task/Login.tsimport{Actor,Task}from'@testla/screenplay-playwright';import{Click,Fill,Navigate}from'@testla/screenplay-playwright/web';import{Get}from'@testla/screenplay-playwright/api';exportclassLoginextendsTask{publicasyncperformAs(actor:Actor):Promise<void>{returnactor.attemptsTo(Navigate.to('https://www.my-fancy-url.com'),Fill.with('#username',actor.states('username')||''),Fill.with('#password',actor.states('password')||''),Click.on('#login-button'),Get.from('https://www.my-fancy-url.com'));}publicstatictoApp():Login{returnnewLogin();}}Initialize Actor with AbilitiesInitialize an actor with abilities for later use in a test case.constactor=Actor.named('James').with('username','John Doe').with('password','MySecretPassword');.can(BrowseTheWeb.using(page)).can(UseAPI.using(request));Test case exampleThe final step is to define a test case using the Task defined above.import{Actor}from'@testla/screenplay-playwright';import{BrowseTheWeb,Element}from'@testla/screenplay-playwright/web';import{Login}from'./task/Login';// Example test case with Playwrighttest.describe('My Test',()=>{test('My first test',async({page,request})=>{constactor=Actor.named('James').with('username','John Doe').with('password','MySecretPassword');.can(BrowseTheWeb.using(page)).can(UseAPI.using(request));// Execute the task Login - as defined further aboveawaitactor.attemptsTo(Login.toApp());// Check if the login was successful - use the status question from the web packageawaitactor.asks(Element.toBe.visible('#logged-in-indicator'));});});Besides the existing actions, abilities and questions it is of course possible to define your own ones. How this is done, please refer to ourcore package.Since tasks, actions and questions return promises, we advise to make use of therequire-awaitrule in case of using eslint. This will help to prevent unexpected behavior when forgetting to await tasks/actions or questions.