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
(17 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]
 
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 44: Line 50:
== Section 3: Playwright Hands-On Overview ==
== Section 3: Playwright Hands-On Overview ==
* create new folder & <code>npm init playwright@latest</code>
* create new folder & <code>npm init playwright@latest</code>
* install browsers <code>npx playwright install</code>
=== Ways to Run & Debug ===
=== Ways to Run & Debug ===
* CLI Test Executions  
* CLI Test Executions  
Line 1,511: Line 1,518:


=== Parallel Execution ===
=== Parallel Execution ===
* worker per <code>*.spec.ts</code> file
* inside <code>*.spec.ts</code> files test cases are execute sequentially
In file <code>playwright.config.ts</code>
<nowiki>
fullyParallel: false,
workers: process.env.CI ? 1 : undefined,
</nowiki>
* <code>fullyParallel</code> is used to guide within a spec file
* undefined workers allows playwright to decide, usually one per spec file
Can override config values inside <code>*.spec.ts</code> files
<nowiki>
test.describe.configure({mode:'parallel'}) // spec file root level
...
test.describe.parallel("Forms Layouts page", () => { // in test suite
...
})</nowiki>
=== Screenshots and Videos ===
=== Screenshots and Videos ===
=== Environment Variables ===
 
=== Configuration File ===
In Code
=== Fixtures ===
<nowiki>
=== Project Setup and Teardown ===
await page.screenshot({ path: "screenshots/formsLayoutsPage.png" })
=== Test Tags ===
 
=== Mobile Device Emulator ===
const buffer = await page.screenshot()
=== Reporting ===
console.log(buffer.toString('base64'))
 
await page.locator("nb-card", {hasText: "Inline form" }).screenshot({ path: "screenshots/inlineForm.png" })</nowiki>
 
In file <code>playwright.config.ts</code>
<nowiki>
export default defineConfig({
  ...
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    ...
    video: 'on',
    .. or ..
    video: {
      mode: "on",
      size: { width: 1920, height: 1080 },
    },
  },
 
  /* Configure projects for major browsers */
  projects: [
    {
      name: "chromium",
      use: {
        ...devices["Desktop Chrome"],
        viewport: { width: 1920, height: 1080 },
      },
    },
  ...</nowiki>
 
=== Environment Variables ===
There are many ways to specify environment specific values
 
In file <code>playwright.config.ts</code>
<nowiki>
export default defineConfig<TestOptions>({
  ...
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    baseURL: 'http://localhost:4200',
    .. or ..
    baseURL: process.env.DEV ? 'http://the.dev.env.com'
    : process.env.STAGING ? 'http://the.staging.env.com'
    : 'http://localhost:4200',
  ...
  },
 
.. or ..
  /* Configure projects for major browsers */
  projects: [
    {
      name: "Dev",
      use: {
        baseURL: 'http://the.dev.env.com',       
      },
    },
    {
      name: "Staging",
      use: {
        baseURL: 'http://the.staging.env.com',       
      },
    },
...
</nowiki>
 
Or Extend
Create file <code>test-options.ts</code>
<nowiki>
import {test as base} from '@playwright/test'
 
export type TestOptions = {
    globalsQaURL: string
}
 
export const test = base.extend<TestOptions>({
    globalsQaURL: ['', {option:true}]
})</nowiki>
 
<nowiki>
import type { TestOptions } from "./test-options"
...
export default defineConfig<TestOptions>({
  ...
  use: {
    ...
    globalsQaURL: 'https://www.globalsqa.com/demo-site/draganddrop/',
    ...
  },
 
.. or ..
  /* Configure projects for major browsers */
  projects: [
    {
      name: "Dev",
      use: {
        ...
        globalsQaURL: 'https://dev.globalsqa.com/demo-site/draganddrop/',
        ...
      },
    },
...
</nowiki>
 
Then in <code>*.spec.ts</code>
<nowiki>
test("drag and drop with iframe", async ({ page, globalsQaURL }) => {
  await page.goto(globalsQaURL)
  ...</nowiki>
 
Using <code>process.env</code> OS environment variables (but there'll be shell and OS variations though)
Assuming bash shell.
Specify variable in file <code>package.json</code>
<nowiki>
  "scripts": {
    ...
    "pageObjects-chrome": "npx playwright test usePageObjects.spec.ts --project=chromium",
    "autoWait-dev": "URL=http://uitestingplayground.com/ajax npm run pageObjects-chrome"
  },</nowiki>
 
Then in <code>*.spec.ts</code>
<nowiki>
test.beforeEach(async ({ page }) => {
  await page.goto(process.env.URL)
  ...
})
...</nowiki>
 
Or use a <code>.env</code> file
In file <code>playwright.config.ts</code>
<nowiki>
...
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
require('dotenv').config(); //or ...config({ path: '/custom/path/to/.env' })
...</nowiki.
 
<nowiki>
URL=http://uitestingplayground.com/ajax
TESTUSERNAME=test@test.com
PASSWORD=Welcome1</nowiki>
 
Making sure to add <code>.env</code> to <code>.gitignore</code> to avoid leaking credentials to repo.
 
I like to create a <code>.env.template</code> file
<nowiki>
URL=http://uitestingplayground.com/ajax
TESTUSERNAME=test@test.com
PASSWORD=***REPLACE-ME***</nowiki>
 
=== 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 ===
* Power tool to setup the test environment
* custom fixtures extend the base test object
* you can auto load fixtures
* you can create dependencies between fixtures
* you can also specify tear down code
 
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 ===
=== 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>
=== Playwright with Docker Container ===
=== Playwright with Docker Container ===
=== Github Actions and Argos CI ===
=== Github Actions and Argos CI ===

Navigation menu