Udemy Playwright: Web Automation Testing From Zero to Hero
Link to Udemy Udemy Playwright: Web Automation Testing From Zero to Hero
Section 1: Preparation
Playwright vs Cypress
Playwright Pros. | Cypress Pros. |
---|---|
|
|
Development Environment Configuration
- node.js => updated => done
- Git => updated => done
- VS Code => updated => done
- Playwright extn for VS Code => installed
Clone Test App
- From https://github.com/bondar-artem/pw-practice-app
- Cloned it in VS Code
npm install --force
- --force needed to accept various warnings
npm start
http://localhost:4200/
Section 2: JavaScript Fundamentals
I'm familiar with JavaScript - I'm fast forwarding through this without keeping notes
Section 3: Playwright Hands-On Overview
- create new folder &
npm init playwright@latest
Ways to Run & Debug
- CLI Test Executions
npx playwright test npx playwright test example.spec.ts --project=chromium --headed npx playwright test -g "has title" npx playwright show-report
- Test Execution with UI - OMG this debug UI is cool!
npx playwright test --ui
- Test Execution with trace on
npx playwright test --project=chromium --trace on npx playwright show-report
- => you can now open the trace from the report (which looks similar to the ui tool above)
- trace can be generated from CI/CD pipeline too, and then you can view the results saved in a zip file with a trace viewer
- Test Execution with debug npx playwright test --project=chromium --debug
- this opens the Playwright inspector showing the code, debugging controls, and console information
- and the browser window
- Test execution with VS Code Extension => Test Explorer
- Navigate to the test you want to debug
- set any breakpoint(s)
- VS Code shows the code, debugging controls, and console information
Tests Structure
First Test
- In VS Code
- Open PW-PRACTICE-APP
- run
npm init playwright@latest --force
- force is needed to avoid errors
- package.json updated with new dev dependencies
- playwright.config.ts is created as well as other files
- delete test-examples folder - it's not needed
- delete test/example.spec.ts file - it's not needed
- create file firsTest.spec.ts
import {test} from '@playwright/test' test('the first test', async ({page}) => { await page.goto('http://localhost:4200/') await page.getByText('Forms').click() await page.getByText('Form Layouts').click() })
- Notice the
page
fixture, it has a lot of useful methods, eg.page.goto('url')
andpage.getByText('label')
Hooks & Control Flow
tes.describe(' a test suite'...)
test.beforeEach()
andtest.beforeAll()
- can be used outside as well as inside a suite
.only(..)
can be used on tests as well as suitestest.afterEach()
andtest.afterAll()
- try to avoid using the after... hooks, better to do it in the before... hooks
Section 4: Interaction with Web Elements
Understanding DOM and Terminology
Review HTML terms
<parent> <html_tag_name html-attribute="a value" class="class1 class2" id="unique"> <child> ... html text value </child> </html_tag_name> <sibling></sibling> </parent>
Locator Syntax Rules
page.locator('input') //finds all of them page.locator('#inputEmail1') // by id page.locator('.shape-rectangle') //by class value page.locator('[placeholder="Email"]') // by attribute page.locator('[class="input-full-width size-medium status-basic shape-rectangle nb-transition cdk-focused cdk-mouse-focused"]') // by class value (full) page.locator('input[placeholder="Email"][nbinput].shape-rectangle') // combine selectors page.locator('//*[@id="inputEmail1"]') // XPath (NOT Recommended because it's testing implementation rather than user visible aspects) page.locator(':text("Using")') // by partial text match page.locator(':text-is("Using the Grid")') // by exact text match
Note:
If you had previously run the test, and the associated browser window is still open, then when the cursor is on a code line with page.locator(...)
it highlights the elements selected by the locator (very cool!)
It's blue when a single element is selected, and orange when multiple elements match the locator.
page.locator(...)
will always return all matching elements, can use .first()
to refine to first element to perform an action.
use npx playwright test --ui
and click the watch icon so that test auto-re-runs when you edit the code (I love this)
User Facing Locators
- Test user-visible behaviour
- tests should typically only see/interact with rendered output
- mimic user behaviour
- page.getByRole(...)
- See ARIA roles and attributes (google it?)
await page.getByRole("textbox", { name: "Email" }).first().click() await page.getByRole("button", { name: "Sign in" }).first().click() await page.getByLabel("Email").first().click() await page.getByPlaceholder('Jane Doe').click() await page.getByText('Using the Grid').click() await page.getByTestId('SignIn').click() // this expects html attribute=> data-testid="SignIn" await page.getByTitle('IoT Dashboard').click()
Child Elements
test("Locating child elements", async ({ page }) => { await page.locator('nb-card nb-radio :text-is("Option 1")').click() await page.locator('nb-card').locator('nb-radio').locator(':text-is("Option 2")').click() // this is nicer than line above await page.locator('nb-card').getByRole('button', {name: "Sign In"}).first().click() // avoid first last, and nth because the lists enveriably change await page.locator('nb-card').nth(3).getByRole('button').click() })
Parent Elements
test("Locating parent elements", async ({ page }) => { await page .locator("nb-card", { hasText: "Using the Grid" }) .getByRole("textbox", { name: "Email" }) .click() await page .locator("nb-card", { has: page.locator("#inputEmail1") }) .getByRole("textbox", { name: "Email" }) .click() await page .locator("nb-card") .filter({ hasText: "Basic Form" }) .getByRole("textbox", { name: "Email" }) .click() await page .locator("nb-card") .filter({ has: page.locator("nb-checkbox") }) .filter({ hasText: "Sign in" }) .getByRole("textbox", { name: "Email" }) .click() await page .locator(':text-is("Using the Grid")') .locator("..") .getByRole("textbox", { name: "Email" }) .click() await page .getByText("Using the Grid") .locator("..") .getByRole("textbox", { name: "Email" }) .click() })
I think I like best the last one, where you select something that looks like a heading to the user, and then go to the parent that contains the selected element, and the element you want to locate.