├── .gitignore ├── README.md ├── assets ├── test-report-expanded-view.png └── test-report.png ├── e2e-tests ├── cart.spec.js ├── home.spec.js ├── page-objects │ ├── CartPage.js │ ├── HomePage.js │ └── SearchPage.js └── search.spec.js ├── e2e.config.js ├── my-awesome-reporter.js ├── package-lock.json ├── package.json ├── playwright.config.js ├── renovate.json └── saucelabs.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | allure-results/ 2 | allure-report/ 3 | node_modules/ 4 | test-results/ 5 | playwright-report/ 6 | playwright/.cache/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Playwright and JavaScript Setup Guide 3 | --- 4 | 5 | ## Features of this framework 6 | * [Design Pattern: Page Object Model](https://playwright.dev/docs/test-pom) 7 | * [Reporting: Allure](https://www.npmjs.com/package/allure-playwright) 8 | * [Cloud Integration: SauceLab](https://saucelabs.com/) 9 | * [Deep Deletion](https://www.npmjs.com/package/rimraf) 10 | 11 | ## Getting started 12 | 13 | ### Pre-requisites 14 | * Download and install Node.js 15 | * Download and install any Text Editor like Visual Code/Sublime/Brackets 16 | 17 | ### Setup Visual Code 18 | * Install GitLens Extension from the Marketplace: `GitLens — Git supercharged by GitKraken https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens` 19 | * Go to Visual Code Preference > Setting and search `formatOnSave` and enable/ON it. 20 | 21 | ### Setup Scripts 22 | * Clone the repository into a folder 23 | * Go to Project root directory and install Dependency: `npm install` 24 | * All the dependencies from package.json would be installed in node_modules folder. 25 | 26 | ### How to write Test 27 | * Add new spec under `e2e-tests` folder 28 | * Name the file as .spec.js (e.g. home.spec.js) 29 | * Create folder under page-objects/pages as (e.g. homePage) 30 | * Under page folder create constant, helper and page object file. 31 | * .constants.js (e.g. home.constants.js) 32 | * .helper.js (e.g. home.helper.js) 33 | * .po.js (e.g. home.po.js) 34 | 35 | ### How to Run Test 36 | * Go to Project root directory and run command: `npm test` 37 | * If you want to run e2e tests then run command: `npm run e2e` 38 | 39 | ### How to Update local npm packages 40 | * Go to Project root directory and run command: `npm update` 41 | 42 | ### How to view HTML report 43 | * Go to Project root directory: `./playwright-report/index.html` 44 | 45 | ### How to view failed test screenshot 46 | * Go to Project root directory: `./test-results/` 47 | 48 | ### Sample Allure Test Report 49 | ![Playwright and JavaScript Test Report](./assets/test-report.png?raw=true "Playwright and JavaScript Test Report") 50 | 51 | ![Playwright and JavaScript Test Report Expanded View](./assets/test-report-expanded-view.png?raw=true "Playwright and JavaScript Test Report Expanded View") 52 | 53 | 54 | ### How to run Test on SauceLabs 55 | * [SauceLabs Quickstart](https://docs.saucelabs.com/web-apps/automated-testing/playwright/quickstart/) 56 | * Set Environment Variables: 57 | * Open Terminal 58 | * Run `touch ~/.bash_profile; open ~/.bash_profile` 59 | * In TextEdit, add 60 | * `export SAUCE_USERNAME=“YOUR USERNAME”` 61 | * `export SAUCE_ACCESS_KEY="YOUR ACCESS KEY"` 62 | * Save the .bash_profile file and Quit (Command + Q) Text Edit. 63 | * In Terminal echo $SAUCE_USERNAME 64 | * In Terminal echo $SAUCE_ACCESS_KEY 65 | * Configure: 66 | `saucectl config` 67 | * Run tests: `npm saucectl run` 68 | -------------------------------------------------------------------------------- /assets/test-report-expanded-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewithmmak/playwright-javascript/57478468812b0104962136441ea55366ed406e4b/assets/test-report-expanded-view.png -------------------------------------------------------------------------------- /assets/test-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewithmmak/playwright-javascript/57478468812b0104962136441ea55366ed406e4b/assets/test-report.png -------------------------------------------------------------------------------- /e2e-tests/cart.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | const { SearchPage } = require("./page-objects/SearchPage"); 3 | const { HomePage } = require("./page-objects/HomePage"); 4 | const { CartPage } = require("./page-objects/CartPage"); 5 | 6 | test.describe("Cart Tests", () => { 7 | test.beforeEach(async ({ page }) => { 8 | const homePage = new HomePage(page); 9 | await homePage.navigate(); 10 | await homePage.pageTitle(); 11 | }); 12 | 13 | test("Verify user is able to add product to Cart", async ({ page }) => { 14 | const searchPage = new SearchPage(page); 15 | await searchPage.navigatetoProductDetailPage(); 16 | await searchPage.pageHeader(); 17 | 18 | const cartPage = new CartPage(page); 19 | // await cartPage.selectColour(); 20 | await cartPage.selectSize(); 21 | await cartPage.addToCart(); 22 | // await page.waitForLoadState(); 23 | await page.waitForTimeout(2000); 24 | await cartPage.productInCart(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /e2e-tests/home.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | const { HomePage } = require("./page-objects/HomePage"); 3 | 4 | test.describe("Home Tests", () => { 5 | test.beforeEach(async ({ page }) => { 6 | const homePage = new HomePage(page); 7 | await homePage.navigate(); 8 | }); 9 | 10 | test("Verify Home page title", async ({ page }) => { 11 | const homePage = new HomePage(page); 12 | await homePage.pageTitle(); 13 | }); 14 | 15 | test("Verify Logo on Home page", async ({ page }) => { 16 | const homePage = new HomePage(page); 17 | await homePage.logo(); 18 | }); 19 | 20 | test("Verify top navigation on Home page", async ({ page }) => { 21 | const homePage = new HomePage(page); 22 | await expect(homePage.topNavLinksLoc).toHaveText([ 23 | "All", 24 | "New Arrivals", 25 | "Featured", 26 | ]); 27 | }); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /e2e-tests/page-objects/CartPage.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("@playwright/test"); 2 | 3 | exports.CartPage = class CartPage { 4 | constructor(page) { 5 | this.page = page; 6 | this.colorBtnLoc = page.locator("button[title='white']"); 7 | this.sizeBtnLoc = page.locator("button[aria-label='size l']"); 8 | this.addToCartBtnLoc = page.locator("//button[text()='Add To Cart']"); 9 | this.productInCartLoc = page.locator( 10 | "(//span[text()='Special Edition T-Shirt'])[2]" 11 | ); 12 | } 13 | 14 | async pageTitle() { 15 | await expect(this.page).toHaveText( 16 | "Special Edition T-Shirt - ACME Storefront" 17 | ); 18 | } 19 | 20 | async selectColour() { 21 | await this.colorBtnLoc.click(); 22 | } 23 | 24 | async selectSize() { 25 | await this.sizeBtnLoc.click(); 26 | } 27 | 28 | async selectSize() { 29 | await this.sizeBtnLoc.click(); 30 | } 31 | 32 | async addToCart() { 33 | await this.addToCartBtnLoc.click(); 34 | } 35 | 36 | async productInCart() { 37 | expect(this.productInCartLoc).toContainText("Special Edition T-Shirt"); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /e2e-tests/page-objects/HomePage.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("@playwright/test"); 2 | 3 | exports.HomePage = class HomePage { 4 | constructor(page) { 5 | this.page = page; 6 | this.acceptCookies = page.locator(`//button[text()='Accept cookies']`); 7 | this.logoLoc = page.locator('(//*[name()="rect"])[1]'); 8 | this.topNavLinksLoc = page.locator( 9 | "//nav[@class='Navbar_navMenu__lJ9fT']/a" 10 | ); 11 | } 12 | 13 | async navigate() { 14 | await this.page.goto("https://demo.vercel.store"); 15 | // Accept cookies 16 | await this.acceptCookies.click(); 17 | } 18 | 19 | async pageTitle() { 20 | await expect(this.page).toHaveTitle("ACME Storefront | Powered by Next.js Commerce"); 21 | } 22 | 23 | async logo() { 24 | await expect(this.logoLoc).toBeVisible(); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /e2e-tests/page-objects/SearchPage.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("@playwright/test"); 2 | 3 | exports.SearchPage = class SearchPage { 4 | constructor(page) { 5 | this.page = page; 6 | this.allLinkLoc = page.locator("//a[text()='All']"); 7 | this.acmeLinkLoc = page.locator("a[href='/search/designers/acme']"); 8 | this.productLinkLoc = page.locator("//span[text()='Special Edition T-Shirt']"); 9 | this.pageHeaderLoc = page.locator("h3[class='ProductTag_name__C_niq'] span"); 10 | } 11 | 12 | async navigatetoProductDetailPage() { 13 | await this.allLinkLoc.click(); 14 | await this.acmeLinkLoc.click(); 15 | await this.productLinkLoc.click(); 16 | } 17 | 18 | async pageHeader() { 19 | await expect(this.pageHeaderLoc).toHaveText("Special Edition T-Shirt"); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /e2e-tests/search.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | const { SearchPage } = require("./page-objects/SearchPage"); 3 | const { HomePage } = require("./page-objects/HomePage"); 4 | 5 | test.describe("Search Tests", () => { 6 | test.beforeEach(async ({ page }) => { 7 | const homePage = new HomePage(page); 8 | await homePage.navigate(); 9 | await homePage.pageTitle(); 10 | }); 11 | 12 | test("Verify user is able to select product from navigation list", async ({ 13 | page, 14 | }) => { 15 | const searchPage = new SearchPage(page); 16 | await searchPage.navigatetoProductDetailPage(); 17 | await searchPage.pageHeader(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /e2e.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { devices } = require('@playwright/test'); 3 | 4 | /** 5 | * Read environment variables from file. 6 | * https://github.com/motdotla/dotenv 7 | */ 8 | // require('dotenv').config(); 9 | 10 | /** 11 | * @see https://playwright.dev/docs/test-configuration 12 | * @type {import('@playwright/test').PlaywrightTestConfig} 13 | */ 14 | const config = { 15 | testDir: './e2e-tests', 16 | /* Maximum time one test can run for. */ 17 | timeout: 30 * 1000, 18 | expect: { 19 | /** 20 | * Maximum time expect() should wait for the condition to be met. 21 | * For example in `await expect(locator).toHaveText();` 22 | */ 23 | timeout: 5000, 24 | }, 25 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 26 | forbidOnly: !!process.env.CI, 27 | /* Retry on CI only */ 28 | retries: process.env.CI ? 2 : 0, 29 | /* Opt out of parallel tests on CI. */ 30 | workers: process.env.CI ? 1 : undefined, 31 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 32 | // reporter: 'html', 33 | reporter: [ 34 | ['list'], 35 | ['html'], 36 | ['./my-awesome-reporter.js'], 37 | ['allure-playwright', { outputFolder: 'allure-results' }] 38 | ], 39 | 40 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 41 | use: { 42 | headless: false, 43 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ 44 | actionTimeout: 0, 45 | /* Base URL to use in actions like `await page.goto('/')`. */ 46 | baseURL: 'https://demo.vercel.store', 47 | 48 | // screenshot: 'only-on-failure', 49 | screenshot: 'on', 50 | 51 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 52 | trace: 'on-first-retry', 53 | }, 54 | 55 | 56 | /* Configure projects for major browsers */ 57 | projects: [ 58 | // { 59 | // name: 'chromium', 60 | // use: { 61 | // ...devices['Desktop Chrome'], 62 | // }, 63 | // }, 64 | 65 | // { 66 | // name: 'firefox', 67 | // use: { 68 | // ...devices['Desktop Firefox'], 69 | // }, 70 | // }, 71 | 72 | // { 73 | // name: 'webkit', 74 | // use: { 75 | // ...devices['Desktop Safari'], 76 | // }, 77 | // }, 78 | 79 | /* Test against mobile viewports. */ 80 | // { 81 | // name: 'Mobile Chrome', 82 | // use: { 83 | // ...devices['Galaxy S9+'], 84 | // }, 85 | // }, 86 | // { 87 | // name: 'Mobile Safari', 88 | // use: { 89 | // ...devices['iPhone X'], 90 | // }, 91 | // }, 92 | 93 | /* Test against branded browsers. */ 94 | // { 95 | // name: 'Microsoft Edge', 96 | // use: { 97 | // channel: 'msedge', 98 | // }, 99 | // }, 100 | { 101 | name: 'Google Chrome', 102 | use: { 103 | channel: 'chrome', 104 | }, 105 | } 106 | 107 | ], 108 | 109 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */ 110 | outputDir: 'test-results/', 111 | 112 | /* Run your local dev server before starting the tests */ 113 | // webServer: { 114 | // command: 'npm run start', 115 | // port: 3000, 116 | // }, 117 | }; 118 | 119 | module.exports = config; 120 | -------------------------------------------------------------------------------- /my-awesome-reporter.js: -------------------------------------------------------------------------------- 1 | // my-awesome-reporter.js 2 | // // @ts-check 3 | 4 | /** @implements {import('@playwright/test/reporter').Reporter} */ 5 | class MyReporter { 6 | onBegin(config, suite) { 7 | console.log(`Starting the run with ${suite.allTests().length} tests`); 8 | } 9 | 10 | onTestBegin(test) { 11 | console.log(`Starting test ${test.title}`); 12 | } 13 | 14 | onTestEnd(test, result) { 15 | console.log(`Finished test ${test.title}: ${result.status}`); 16 | } 17 | 18 | onEnd(result) { 19 | console.log(`Finished the run: ${result.status}`); 20 | } 21 | } 22 | 23 | module.exports = MyReporter; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playwright-javascript", 3 | "version": "1.0.0", 4 | "description": "This is Test Automation framework designed using Playwright, and JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rimraf allure-report/ && rimraf playwright-report/", 8 | "test": "playwright test", 9 | "headedTest": "playwright test --headed", 10 | "single-test-file": "npm run clean && playwright test cart.spec.js --reporter=line", 11 | "test-list-reporter": "npm run clean && playwright test cart.spec.js --reporter=list", 12 | "test-line-reporter": "npm run clean && playwright test cart.spec.js --reporter=line", 13 | "test-dot-reporter": "npm run clean && playwright test cart.spec.js --reporter=dot", 14 | "test-html-reporter": "npm run clean && playwright test cart.spec.js --reporter=html", 15 | "test-json-reporter": "npm run clean && playwright test cart.spec.js --reporter=json", 16 | "test-junit-reporter": "npm run clean && playwright test cart.spec.js --reporter=junit", 17 | "e2e": "npm run clean && playwright test -c e2e.config.js", 18 | "e2e-commandline-reporter": "npm run clean && playwright test -c e2e.config.js --reporter=line,./my-awesome-reporter.js,allure-playwright", 19 | "allure-report": "npx allure generate ./allure-results && allure open" 20 | }, 21 | "keywords": [], 22 | "author": "Code with MMAK", 23 | "devDependencies": { 24 | "@playwright/test": "^1.32.1", 25 | "allure-commandline": "^2.21.0", 26 | "allure-playwright": "^2.1.0", 27 | "copyfiles": "^2.4.1", 28 | "npm-check-updates": "^16.9.0", 29 | "rimraf": "^5.0.0", 30 | "saucectl": "^0.147.0" 31 | } 32 | } -------------------------------------------------------------------------------- /playwright.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { devices } = require('@playwright/test'); 3 | 4 | /** 5 | * Read environment variables from file. 6 | * https://github.com/motdotla/dotenv 7 | */ 8 | // require('dotenv').config(); 9 | 10 | /** 11 | * @see https://playwright.dev/docs/test-configuration 12 | * @type {import('@playwright/test').PlaywrightTestConfig} 13 | */ 14 | const config = { 15 | testDir: './e2e-tests', 16 | /* Maximum time one test can run for. */ 17 | timeout: 30 * 1000, 18 | expect: { 19 | /** 20 | * Maximum time expect() should wait for the condition to be met. 21 | * For example in `await expect(locator).toHaveText();` 22 | */ 23 | timeout: 5000, 24 | }, 25 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 26 | forbidOnly: !!process.env.CI, 27 | /* Retry on CI only */ 28 | retries: process.env.CI ? 2 : 0, 29 | /* Opt out of parallel tests on CI. */ 30 | workers: process.env.CI ? 1 : undefined, 31 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 32 | // reporter: 'html', 33 | // reporter: [ 34 | // ['html'], 35 | // ['list'], 36 | // ['./my-awesome-reporter.js'], 37 | // ['allure-playwright', { outputFolder: 'allure-results' }] 38 | // ], 39 | 40 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 41 | use: { 42 | headless: false, 43 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ 44 | actionTimeout: 0, 45 | /* Base URL to use in actions like `await page.goto('/')`. */ 46 | baseURL: 'https://demo.vercel.store', 47 | 48 | // screenshot: 'only-on-failure', 49 | screenshot: 'on', 50 | 51 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 52 | trace: 'on-first-retry', 53 | }, 54 | 55 | 56 | /* Configure projects for major browsers */ 57 | projects: [ 58 | // { 59 | // name: 'chromium', 60 | // use: { 61 | // ...devices['Desktop Chrome'], 62 | // }, 63 | // }, 64 | 65 | // { 66 | // name: 'firefox', 67 | // use: { 68 | // ...devices['Desktop Firefox'], 69 | // }, 70 | // }, 71 | 72 | // { 73 | // name: 'webkit', 74 | // use: { 75 | // ...devices['Desktop Safari'], 76 | // }, 77 | // }, 78 | 79 | /* Test against mobile viewports. */ 80 | // { 81 | // name: 'Mobile Chrome', 82 | // use: { 83 | // ...devices['Galaxy S9+'], 84 | // }, 85 | // }, 86 | // { 87 | // name: 'Mobile Safari', 88 | // use: { 89 | // ...devices['iPhone 12'], 90 | // }, 91 | // }, 92 | 93 | /* Test against branded browsers. */ 94 | // { 95 | // name: 'Microsoft Edge', 96 | // use: { 97 | // channel: 'msedge', 98 | // }, 99 | // }, 100 | { 101 | name: 'Google Chrome', 102 | use: { 103 | channel: 'chrome', 104 | }, 105 | } 106 | 107 | ], 108 | 109 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */ 110 | outputDir: 'test-results/', 111 | 112 | /* Run your local dev server before starting the tests */ 113 | // webServer: { 114 | // command: 'npm run start', 115 | // port: 3000, 116 | // }, 117 | }; 118 | 119 | module.exports = config; 120 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /saucelabs.config.js: -------------------------------------------------------------------------------- 1 | const { config } = require('./e2e.config'); 2 | 3 | // ===================== 4 | // Sauce specific config 5 | // ===================== 6 | // See https://webdriver.io/docs/sauce-service.html for more information 7 | config.user = process.env.SAUCE_USERNAME; 8 | config.key = process.env.SAUCE_ACCESS_KEY; 9 | config.region = process.env.REGION || 'us'; 10 | 11 | // =================================================================================== 12 | // Capabilities 13 | // You can find more about constructing the capabilities for real device testing here 14 | // https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/ 15 | // 16 | // All test configuration options and W3C compliant options can be found here 17 | // https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options 18 | // 19 | // To read more about W3C and Sauce Labs please check 20 | // https://wiki.saucelabs.com/display/DOCS/W3C+Capabilities+Support 21 | // =================================================================================== 22 | config.capabilities = [ 23 | { 24 | // For the W3C capabilities, please check 25 | // https://www.w3.org/TR/webdriver1/#capabilities 26 | browserName: 'chrome', 27 | platformName: 'Windows 10', 28 | browserVersion: 'latest', 29 | // All vendor specific, in this case Sauce specific capabilities, should be 30 | // put in vendor prefixed options, see 31 | // https://www.w3.org/TR/webdriver1/#dfn-extension-capability 32 | 'sauce:options': { 33 | build: `Sauce Labs build-${new Date().getTime()}`, 34 | screenResolution: '1440x900' 35 | }, 36 | }, 37 | ]; 38 | 39 | config.services = config.services.concat('sauce'); 40 | 41 | exports.config = config; --------------------------------------------------------------------------------