Udemy Playwright: Web Automation Testing From Zero to Hero: Difference between revisions

Jump to navigation Jump to search
m
no edit summary
mNo edit summary
 
(21 intermediate revisions by the same user not shown)
Line 1: Line 1:
* Link to Udemy: [https://www.udemy.com/course/playwright-from-zero-to-hero Udemy Playwright: Web Automation Testing From Zero to Hero]
<span id="BackToTop"></span>
<div class="noprint" style="background-color:#FAFAFA; position:fixed; bottom:2%; left:0.25%; padding:0; margin:0;">
[[#BackToTop|Back to the Top]]
</div>
* Udemy: [https://www.udemy.com/course/playwright-from-zero-to-hero Udemy Playwright: Web Automation Testing From Zero to Hero]


* Link to GitHub: [https://github.com/VincentDirks/Playwright-Udemy-Course my Playwright Udemy Course repo]
* My GitHub Playwright Udemy Course [https://github.com/VincentDirks/Playwright-Udemy-Course my repo #1] & [https://github.com/VincentDirks/Playwright-Udemy-Course-2 my repo #2]
 
* Started implementing a [https://github.com/VincentDirks/Playwright-my-cvwiki suite for this CV Wiki]
 
Note: This page is now massive, but it's handy to have it all in one place so that I can use ctrl-f to find notes... I'll be using this page as my cheatsheet for Playwright


== Section 1: Preparation ==
== Section 1: Preparation ==
Line 1,691: Line 1,699:


=== Configuration File ===
=== Configuration File ===
Cleaning up <code>playwright.config.ts</code>
* removing default settings
* removing comments
There are
* global settings, and
* global runtime settings in the <code>use:{...}</code> block.
Then inside the <code>projects[{...},{...} ...]</code> array each node has a name, and can then override any of the global settings, and runtime settings in a <code>use:{...}</code> subblock.
You can also create and use entirely separate <code>*.config.ts</code> files and run them with <code>npx playwright test --config=playwright-prod.config.ts</code>
Also see [https://playwright.dev/docs/test-configuration test configuration] and [https://playwright.dev/docs/test-use-options test use options] in the Playwright documentation.
=== Fixtures ===
=== Fixtures ===
=== Project Setup and Teardown ===
* Power tool to setup the test environment
=== Test Tags ===
* custom fixtures extend the base test object
=== Mobile Device Emulator ===
* you can auto load fixtures
=== Reporting ===
* you can create dependencies between fixtures
=== Visual Testing ===
* you can also specify tear down code
=== Playwright with Docker Container ===
 
Update file <code>test-options.ts</code>
<nowiki>
import { test as base } from "@playwright/test"
import { PageManager } from "./page-objects/pageManager"
 
export type TestOptions = {
  globalsQaURL: string
  formLayoutsPage: string
  pageManager: PageManager
}
 
export const test = base.extend<TestOptions>({
  globalsQaURL: ["", { option: true }],
 
  formLayoutsPage: async ({ page }, use) => {
    await page.goto("/")
    await page.getByText("Forms").click()
    await page.getByText("Form Layouts").click()
    await use("")
    console.log('Tear Down formLayoutsPage')
  },
  // { auto: true }],
 
  pageManager: async ({ page, formLayoutsPage }, use) => {  // pageManager depends on formLayoutsPage
    const pm = new PageManager(page)
    await use(pm)
    console.log('Tear Down pageManager')
  },
})</nowiki>
 
Create new file <code>tests\testWithFictures.spec.ts</code>
<nowiki>
import { test } from "../test-options"
import { faker } from "@faker-js/faker"
 
//test("parameterised methods", async ({ page, formLayoutsPage }) => { // using formLayoutsPage fixture
//test("parameterised methods", async ({ page }) => {      // using {auto:true} in formLayoutsPage fixture
test("parameterised methods", async ({ pageManager }) => {  // using new pageManager fixture
  const randomFullName = faker.person.fullName()
  const randomEmail = `${randomFullName.replace(/ /g, "")}${faker.number.int(
    1000
  )}@test.com`
 
  await pageManager
    .onFormLayoutsPage()
    .submitUsingTheGridFormWithCredentialsAndSelectOption(
      process.env.TESTUSERNAME,
      process.env.PASSWORD,
      "Option 2"
    )
 
  await pageManager
    .onFormLayoutsPage()
    .submitInLineFormWithNameEmailAndCheckbox(randomFullName, randomEmail, true)
})</nowiki>
 
=== Project Setup and Teardown ===
 
1. Create <code>tests\newArticle.setup.ts</code> to create a new article via API
<nowiki>
import { expect, test as setup } from "@playwright/test"
 
setup("create new article", async ({ request }) => {
  const articleResponse = await request.post(
    "https://conduit-api.bondaracademy.com/api/articles/",
    {
      data: {
        article: {
          title: "Likes Test Article",
          description: "Test description",
          body: "Test body",
          tagList: [],
        },
      },
    }
  )
  expect(articleResponse.status()).toEqual(201)
 
  const response = await articleResponse.json()
  const slugId = response.article.slug
  process.env["SLUGID"] = slugId
})</nowiki>
2. Create <code>tests\likesCounter.spec.ts</code> to test the like counter
<nowiki>
...
test("Like counter increase", async ({ page }) => {
  await page.goto("https://conduit.bondaracademy.com/")
  await page.getByText("Global Feed").click()
  const firstLikeButton = page
    .locator("app-article-preview")
    .first()
    .locator("button")
  await expect(firstLikeButton).toContainText("0")
  await firstLikeButton.click()
  await expect(firstLikeButton).toContainText("1")
})</nowiki>
3. Create <code>tests\articleCleanUp.setup.ts</code> to clean up (ie. delete) the article created via API
<nowiki>
...
setup("delete article", async ({ request }) => {
  // Clean up
  // delete the article using the slug extracted earlier
  const articleDeleteResponse = await request.delete(
    `https://conduit-api.bondaracademy.com/api/articles/${process.env.SLUGID}`
  )
  expect(articleDeleteResponse.status()).toEqual(204)
})</nowiki>
4. Update <code>playwright.config.ts</code> to create new projects
:* articleSetup (with dependency on setup to fetch auth token, and using teardown)
:* likeCounter (with dependency on articleSetup)
:* articleCleanUp
<nowiki>
...
export default defineConfig({
  ...
  projects: [
    { name: "setup", testMatch: "auth.setup.ts" },
    {
      name: "articleSetup",
      testMatch: "newArticle.setup.ts",
      dependencies: ["setup"],
      teardown: "articleCleanUp",
    },
    {
      name: "articleCleanUp",
      testMatch: "articleCleanUp.setup.ts",
    },
    {
      name: "likeCounter",
      testMatch: "likesCounter.spec.ts",
      use: { ...devices["Desktop Chrome"], storageState: ".auth/user.json" },
      dependencies: ["articleSetup"],
    },
  ],
})</nowiki>
 
=== Global Setup and Teardown ===
1. Create file <code>global-setup.ts</code> with more or less same code as file <code>tests\newArticle.setup.ts</code>
 
<nowiki>
import { expect, request } from "@playwright/test"
import user from "./.auth/user.json"
import fs from "fs"
 
async function globalSetup() {
  const authfile = ".auth/user.json"
  const context = await request.newContext()
 
  const responseToken = await context.post(...  )
  const responseBody = await responseToken.json()
  const accessToken = responseBody.user.token
 
  user.origins[0].localStorage[0].value = accessToken
  fs.writeFileSync(authfile, JSON.stringify(user))
  process.env["ACCESS_TOKEN"] = accessToken
 
  const articleResponse = await context.post(...)
  expect(articleResponse.status()).toEqual(201)
 
  const articleResponseBody = await articleResponse.json()
  const slugId = articleResponseBody.article.slug
  process.env["SLUGID"] = slugId
}
 
export default globalSetup</nowiki>
 
2. Create file <code>global-teardown.ts</code>
<nowiki>
import { request, expect } from "@playwright/test"
 
async function globalTeardown() {
  const context = await request.newContext()
 
  // Clean up
  // delete the article using the slug extracted earlier
  const articleDeleteResponse = await context.delete(
    `https://conduit-api.bondaracademy.com/api/articles/${process.env.SLUGID}`,
    {
      headers: { Authorization: `Token ${process.env.ACCESS_TOKEN}` },
    }
  )
  expect(articleDeleteResponse.status()).toEqual(204)
}
 
export default globalTeardown</nowiki>
 
3. Create file <code>tests\likesCounterGlobal.spec.ts</code>
<nowiki>
import { test, expect, request } from "@playwright/test"
 
test("Like counter increase", async ({ page }) => {
  await page.goto("https://conduit.bondaracademy.com/")
  await page.getByText("Global Feed").click()
  const firstLikeButton = page
    .locator("app-article-preview")
    .first()
    .locator("button")
  await expect(firstLikeButton).toContainText("0")
  await firstLikeButton.click()
  await expect(firstLikeButton).toContainText("1")
})</nowiki>
 
4. Update file <code>playwright.config.ts</code>
<nowiki>
export default defineConfig({
  ...
  globalSetup: require.resolve("./global-setup.ts"),
  globalTeardown: require.resolve("./global-teardown.ts"),
 
  projects: [
    ...
    {
      name: "likeCounterGlobal",
      testMatch: "likesCounterGlobal.spec.ts",
      use: { ...devices["Desktop Chrome"], storageState: ".auth/user.json" },
    },
  ],
})</nowiki>
 
=== Test Tags ===
<nowiki>
test("navigate to all pages @smoke @regression", async ({ page }) => {
...
test("parameterised methods @smoke", async ({ page }) => {
...
test.describe("Forms Layouts page @block", () => {
  test.describe.configure({ retries: 2 })
  test("input fields", async ({ page }) => {
  ...</nowiki>
 
<nowiki>
npx playwright test --project=chromium --grep @smoke
npx playwright test --project=chromium --grep @regression
npx playwright test --project=chromium --grep @block
npx playwright test --project=chromium --grep "@block|@smoke"</nowiki>
 
=== Mobile Device Emulator ===
1. Create new file <code>tests/testMobile.spec.ts</code>
<nowiki>
import { test } from "@playwright/test"
 
test("input fields", async ({ page }, testInfo) => {
  await page.goto("/")
 
  const isMobile = testInfo.project.name === 'mobile'
 
  isMobile && await page.locator('.sidebar-toggle').click() // to show the sidebar which is hidden for mobile devices
  await page.getByText("Forms").click()
  await page.getByText("Form Layouts").click()
  isMobile && await page.locator('.sidebar-toggle').click()
 
  const usingTheGridEmailInput = page
    .locator("nb-card", { hasText: "Using the Grid" })
    .getByRole("textbox", { name: "Email" })
 
  await usingTheGridEmailInput.fill("test@test.com")
  await usingTheGridEmailInput.clear()
  await usingTheGridEmailInput.fill("test2@test.com")
})</nowiki>
 
2. Update <code>playwright.config.ts</code>
<nowiki>
export default defineConfig<TestOptions>({
  ...
  projects: [
    ...
    {
      name: "mobile",
      testMatch: "testMobile.spec.ts",
      use: {
      ...devices["iPhone 13 Pro"],
        // ..OR..
        // viewport: { width: 414, height: 800 },
      },
    },
  ],
})</nowiki>
 
=== Reporters ===
Configure reporters in <code>playwright.config.ts</code> as per
<nowiki>
...
export default defineConfig<TestOptions>({
  ...
  reporter: [
    ["json", { outputFile: "test-results/jsonReport.json" }],
    ["junit", { outputFile: "test-results/junitReport.xml" }],
    ['allure-playwright']
  ],
...</nowiki>
 
==== Allure ====
Setup for windows
* [https://www.java.com/en/download/ java download] (FYI: I did not need to set JAVA_HOME)
* [https://scoop.sh/ Scoop]
<nowiki>
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression</nowiki>
 
* [https://allurereport.org/docs/install-for-windows/ Allure report install for windows]
<nowiki>scoop install allure</nowiki>
 
* [https://www.npmjs.com/package/allure-playwright/v/2.15.1 npm:allure-playwright]
<nowiki>
npm i -D @playwright/test allure-playwright --force</nowiki>
 
Now you can run some tests followed by
<nowiki>
allure generate allure-results -o allure-report --clean
allure open allure-report</nowiki>
 
Don't forget to add <code>allure-results</code> and <code>allure-report</code> to <code>.gitignore</code>
 
Allure looks cool but also looks like a bit of a learning curve ...
 
PS. it seems pretty straight forward to create your own custom reporter as per these instructions [https://playwright.dev/docs/api/class-reporter Playwright class reporter]
 
=== Visual Testing ===
This is so cool! Simply call <code>await expect(locator).toHaveScreenshot(...)</code> on an element. The first run through creates screenshot(s) inside a subfolder from <code>tests</code> folder. Second run compares current actual with previously expected. If there's any significant differences they are saved to subfolders in the <code>test-results</code> folder, and the html reporter has a very nice way to see and compare snapshots.
 
You can control settings in the code
<nowiki>
    await expect(locator).toHaveScreenshot({maxDiffPixels:150, maxDiffPixelRatio: 0.01})</nowiki>
 
as well as in file <code>playwright.config.ts</code>
<nowiki>
export default defineConfig<TestOptions>({
  expect: {
    ...
    toMatchSnapshot: { maxDiffPixels: 50 },
  },
  ...</nowiki>
 
If you need to update a lot of snapshots use
<nowiki>
npx playwright test --update-snapshots</nowiki>
 
I would like to try doing visual testing of small parts on the screen, and then test the integration of those parts but masking the smaller parts so that the larger integration test only checks that the sub parts are there, but not test the sub part internals. The mask option of <code>.toHaveScreenshot({mask: [maskedElement1,maskedElement2]})</code> can take an array of locators. However, this would apply the same colour to all of them, might try using CSS through <code>.toHaveScreenshot({ stylePath: path.join(__dirname, 'screenshot.css') })</code> to render the elements with a solid block with different colours.
 
To do this I need to identify the child elements and set their <code>visibility: hidden;</code> and then the element to have <code>background-color: #909090;</code>
<nowiki>
ngx-form-layouts > div.row:nth-child(2) > div.col-md-6:nth-child(2) > nb-card:nth-child(1) > nb-card-body {
  visibility:hidden;
}
ngx-form-layouts > div.row:nth-child(2) > div.col-md-6:nth-child(2) > nb-card:nth-child(1) {
  background-color: #909090;
}</nowiki>
for the course's test web app.
 
Could consider assigning colours programmatically at run time...
 
=== Playwright with Docker Container ===
* Make sure [https://www.docker.com/products/docker-desktop/ Docker Desktop] is installed
 
 
<code>docker</code> file allows you to build a docker image
<nowiki>
FROM mcr.microsoft.com/playwright:v1.44.1-jammy
 
RUN mkdir /app
WORKDIR /app
COPY . /app/
 
RUN npm install --force
RUN npx playwright install</nowiki>
Commands
<nowiki>
docker build -t pw-pageobject-test .
docker images
docker run -it pw-pageobject-test</nowiki>
The last command starts the container and you can execute commands such as
<nowiki>
npm run pageObjects-chrome</nowiki>
 
 
<code>compose.yaml</code> file allows you to build and execute an image as well as obtain files/folder from a running instance
<nowiki>
services:
  playwright-test:
    image: playwright-test
    build:
      context: .
      dockerfile: ./dockerfile
    command: npm run pageObjects-chrome
    volumes:
      - ./playwright-report/:/app/playwright-report
      - ./test-results/:/app/test-results</nowiki>
 
Commands
<nowiki>
docker-compose up --build
docker-compose up</nowiki>
 
Will run (& build) the image in the container, execute the command, and at the end copy the generated files back to the host computer.
 
=== Github Actions and Argos CI ===
=== Github Actions and Argos CI ===
'''GitHub Actions'''
* Link to instructions [https://playwright.dev/docs/ci-intro on Playwright]
* Create file <code>.github\workflows\playwright.yml</code> copy from instructions above, then some small edits
<nowiki>
name: Playwright Tests
on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
    - name: Install dependencies
      run: npm ci --force
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps --force
    - name: Run Playwright tests
      run: npm run pageObjects-chrome
    - uses: actions/upload-artifact@v4
      if: ${{ !cancelled() }}
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30</nowiki>
* update node version, add --force x2, change final run command <code>run: npm run pageObjects-chrome</code>
* commit & push to [https://github.com/VincentDirks/Playwright-Udemy-Course GitHub]
* see [https://github.com/VincentDirks/Playwright-Udemy-Course/actions actions tab] for progress and to see test artifacts saved
'''argos CI'''
* Go to [https://argos-ci.com argos-CI] to create account, best to select to continue with GitHub. (selecting Hobby - I'm working on personal projects)
* Create a new project, and select the repo to integrate
* Go to [https://argos-ci.com/docs/quickstart/playwright Playwright Quickstart] for instructions
** install <code>npm i --save-dev @argos-ci/playwright --force</code>
** Setup Argos in your Playwright config (no need to provide token value when integrating through GitHub)
** Take screenshots
eg. In file <code>tests\usePageObjects.spec.ts</code>
<nowiki>
...
import { argosScreenshot } from "@argos-ci/playwright"
...
test.only("Testing with argos CI", async ({ page }) => {
  const pm = new PageManager(page)
  await pm.navigateTo().formLayoutsPage()
  await argosScreenshot(page,"form layouts page")
  await pm.navigateTo().datePickerPage()
  await argosScreenshot(page,"date picker page")
})</nowiki>
** commit & push to GitHub
** Verify the GitHub action has been processed
** Verify the reference build in argo-ci
* Implement some "design changes" to the web app being tested
* Create new branch, make (visual) changes, commit, push to GitHub, create PR to master
* This will trigger GitHub action and argos-CI will detect the visual changes
* verify the argos-CI verification step has failed on GitHub
* review the visual changes in argos-CI, approve them
* pull request can now be merged
Note: checked the documentation for argos-CI and you can also mask and apply CSS prior to taking the screenshot.

Navigation menu