├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── data └── login.cred.json ├── jest-playwright.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── page ├── Header.page.ts ├── Login.page.ts └── common.page.ts ├── playwright-ws.code-workspace ├── report.bat ├── test ├── alerts │ └── dialogs.test.ts ├── dragdrop │ └── drag.test.ts ├── elements │ └── search.test.ts ├── eval │ └── changeIMage.test.ts ├── frames │ └── frame.test.ts ├── inputs │ └── inputs.test.ts ├── launchBrowser.test.ts ├── login.test.ts ├── mutilple-browser │ └── browser.test.ts ├── pageObjectsTest │ ├── TC001_Login.test.ts │ └── TC002.test.ts ├── select │ └── dropdown.test.ts ├── uploadFile.test.ts └── windowHandling │ └── windows.test.ts ├── tsconfig.json └── utils ├── environment.ts └── reportUtils.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | videos 9 | allure-results 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Koushik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Playwright-TypeScript-Jest 2 | ## What is Playwright? 3 | The playwright is a Node.js library to automate Chromium, Firefox, and WebKit with a single API. Playwright is built to enable cross-browser web testing. 4 | 5 | Playwright by Microsoft did start as a fork of Puppeteer 6 | Puppeteer is a node library to automate the chromium browsers with the JavaScript API 7 | ### Capabilities: 8 | * It spans multiple pages, domains, and iframes 9 | * Intercept network activity for stubbing and mocking network requests 10 | * Emulate mobile devices, geolocation, permissions 11 | * Native input events for mouse and keyboard 12 | * Upload & download support 13 | 14 | Playwright enables fast, reliable, and capable automation across all modern browsers 15 | 16 | ### Support for all browsers 17 | * Test on Chromium, Firefox, and WebKit 18 | * Test for mobile (device emulation) 19 | * Headless and headful 20 | 21 | ### Fast and reliable execution 22 | * Auto-wait APIs (clicks, types, etc) 23 | * Timeout-free automation 24 | * Lean parallelization with browser contexts 25 | * Wide variety of selectors (locators) & shadow-dom support 26 | * Can handle single page application 27 | 28 | ## Youtube Tutorial 29 | * Please follow the sequence to enhance your learning! 30 | 31 | [1. What is Playwright? | Playwright with Typescript & Jest - Part 1](https://youtu.be/zY-IoTYcbWs) 32 | 33 | [2. Playwright Jest Config & Launch Browser | Playwright - Part 2](https://youtu.be/DbdqflN3dJ4) 34 | 35 | [3. First Script - Auto Waits | Playwright - Part 3](https://youtu.be/9xEzNdG4XaQ) 36 | 37 | [4. Codeless Automation With PlayWright | Playwright - Part 4](https://youtu.be/gb43GiWwQKg) 38 | 39 | [5. Save Test Execution In Video | Playwright - Part 5](https://youtu.be/0125rwgsBP8) 40 | 41 | [6. How to upload files | Playwright - Part 6](https://youtu.be/e8jfjV71E6Q) 42 | 43 | [7. Handling different types of inputs | Playwright - Part 7](https://youtu.be/Slv5fuTrIZg) 44 | 45 | [8. Handling different types of alerts | Playwright - Part 8](https://youtu.be/RzBlwacFIl0) 46 | 47 | [9. Handling Select/DropDown | Playwright - Part 9](https://youtu.be/IubdSQFOdiU) 48 | 49 | [10. Window Handling | Playwright - Part 10](https://youtu.be/DyHQ3G442jY) 50 | 51 | [11. Frames | Playwright - Part 11](https://youtu.be/Vqm-8G81W8w) 52 | 53 | [12. Find Multiple Elements - part 12](https://youtu.be/54OwsiRa_eE) 54 | 55 | [13. How To Take Screenshot - part 13](https://youtu.be/G650JxukN1A) 56 | 57 | [14. How To Run In Local Browser | Playwright - Part 14](https://youtu.be/5LrRFHI81o4) 58 | 59 | [15. Drag and Drop | Playwright - Part 15](https://youtu.be/0wFkhkdcT8A) 60 | 61 | [16. Page Object Model | Playwright - Part 16](https://youtu.be/WSd6-X-n6P8) 62 | 63 | [17. POM Enhancement & JSON | Playwright - Part 17](https://youtu.be/00xGOpnOzds) 64 | 65 | [18. POM Enhancement & JSON | Playwright - Part 18](https://youtu.be/w05KGL8G0f4) 66 | 67 | [19. Jest Allure Report | Playwright - Part 19](https://youtu.be/tjpSkaBq9c0) 68 | 69 | [20. Jest Allure Report | Playwright - Part 20](https://youtu.be/xffrNccLIso) 70 | 71 | [21. Skip on failure | Playwright - Part 21](https://youtu.be/4-IBKtbAxlg) 72 | -------------------------------------------------------------------------------- /data/login.cred.json: -------------------------------------------------------------------------------- 1 | { 2 | "email": "koushik1@letcode.in", 3 | "pass": "Pass123$" 4 | } -------------------------------------------------------------------------------- /jest-playwright.config.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | module.exports = { 4 | browsers: ["chromium"], 5 | exitOnPageError: false, 6 | launchOptions: { 7 | headless: true 8 | }, 9 | contextOptions: { 10 | recordVideo: { 11 | dir: "/videos/" 12 | } 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@jest/types').Config.InitialOptions} */ 2 | module.exports = { 3 | verbose: true, 4 | preset: 'jest-playwright-preset', 5 | transform: { 6 | '^.+\\.ts$': 'ts-jest', 7 | }, 8 | testRunner: 'jasmine2', 9 | setupFilesAfterEnv: ["jest-allure/dist/setup"], 10 | testMatch: [ 11 | "/test/pageObjectsTest/TC002.test.ts" 12 | ], 13 | bail: true, 14 | bail: 1, 15 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "let-playright", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest -i" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "jasmine": "^3.7.0", 14 | "@types/jest": "^26.0.20", 15 | "jasmine-allure-reporter": "^1.0.2", 16 | "jest": "^26.6.3", 17 | "jest-allure": "^0.1.3", 18 | "jest-playwright-preset": "1.7.0", 19 | "playwright": "1.13.0", 20 | "ts-jest": "^26.5.0", 21 | "typescript": "^4.1.3" 22 | }, 23 | "dependencies": { 24 | "ts-node": "^10.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /page/Header.page.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "playwright"; 2 | 3 | export default class HeaderPage { 4 | 5 | private page: Page; 6 | 7 | constructor(page: Page) { 8 | this.page = page; 9 | } 10 | 11 | 12 | // locators 13 | 14 | public get eleLoginBtn() { 15 | const loginBtn = this.page.$("text=Log in"); 16 | if (loginBtn != null) { 17 | return loginBtn; 18 | } else throw new Error("No Element") 19 | } 20 | 21 | public get eleSignOutBtn() { 22 | const signoutEle = this.page.$("text=Sign out"); 23 | if (signoutEle != null) { 24 | return signoutEle; 25 | } else throw new Error("No Element") 26 | } 27 | 28 | public async clickLoginLink() { 29 | await Promise.all([ 30 | this.page.waitForNavigation({ 31 | waitUntil: "domcontentloaded" 32 | }), 33 | this.page.click("text=Log in") 34 | ]) 35 | // const ele = await this.eleLoginBtn; 36 | // await ele?.click(); 37 | } 38 | public async clickSignOutLink() { 39 | const ele = await this.eleSignOutBtn; 40 | await ele?.click(); 41 | } 42 | } -------------------------------------------------------------------------------- /page/Login.page.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "playwright"; 2 | 3 | export default class LoginPage { 4 | 5 | private page: Page; 6 | constructor(page: Page) { 7 | this.page = page; 8 | } 9 | 10 | eleEmailTextField = async () => await this.page.$("input[name='email']"); 11 | 12 | // public get eleEmailTextField() { 13 | // return this.page.$("input[name='email']") 14 | // // return elename; 15 | // } 16 | 17 | elePassTextField = async () => await this.page.$("input[name='password']"); 18 | 19 | public get eleLoginBtn() { 20 | return this.page.$("//button[text()='LOGIN']") 21 | // return elename; 22 | } 23 | 24 | public async enterUserName(name: string) { 25 | const ele = await this.eleEmailTextField(); 26 | if (ele != null) 27 | await ele.fill(name); 28 | else throw new Error("No element, hence failed") 29 | } 30 | public async enterUserPassword(pass: string) { 31 | const ele = await this.elePassTextField(); 32 | await ele?.fill(pass); 33 | } 34 | public async clickLoginBtn() { 35 | const ele = await this.eleLoginBtn; 36 | await ele?.click(); 37 | } 38 | 39 | public async login(username: string, pass: string) { 40 | await this.enterUserName(username); 41 | await this.enterUserPassword(pass); 42 | await this.clickLoginBtn(); 43 | } 44 | } -------------------------------------------------------------------------------- /page/common.page.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "playwright"; 2 | 3 | export default class CommonFunctions { 4 | 5 | private page: Page; 6 | constructor(page: Page) { 7 | this.page = page; 8 | } 9 | 10 | toaster = async () => await this.page.waitForSelector("div[role='alertdialog']"); 11 | 12 | // public async verifToastMessage() { 13 | 14 | // } 15 | } -------------------------------------------------------------------------------- /playwright-ws.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /report.bat: -------------------------------------------------------------------------------- 1 | allure serve -------------------------------------------------------------------------------- /test/alerts/dialogs.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, chromium, ElementHandle, Page } from "playwright" 2 | 3 | describe("Learn how to handle alert", () => { 4 | 5 | let browser: Browser; 6 | let context: BrowserContext; 7 | let page: Page; 8 | beforeAll(async () => { 9 | browser = await chromium.launch({ 10 | headless: false 11 | }); 12 | context = await browser.newContext() 13 | page = await context.newPage(); 14 | await page.goto("https://letcode.in/alert") 15 | }) 16 | test("Handle dialogs", async () => { 17 | const ele = await page.$("#prompt"); 18 | page.on("dialog", (dialog) => { 19 | console.log('Message: ' + dialog.message()); 20 | console.log('Default Value: ' + dialog.defaultValue()); 21 | console.log('Type: ' + dialog.type()); 22 | dialog.accept("hello koushik"); 23 | // dialog.dismiss() 24 | }) 25 | await ele?.click(); 26 | }) 27 | 28 | afterAll(async () => { 29 | await page.close() 30 | await context.close() 31 | await browser.close() 32 | }) 33 | }) -------------------------------------------------------------------------------- /test/dragdrop/drag.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, Page, chromium, Frame } from "playwright"; 2 | 3 | describe("Drag and Drop", () => { 4 | let browser: Browser; 5 | let context: BrowserContext; 6 | let page: Page; 7 | beforeAll(async () => { 8 | browser = await chromium.launch({ 9 | headless: false 10 | }); 11 | context = await browser.newContext() 12 | page = await context.newPage(); 13 | }) 14 | xtest("my test", async () => { 15 | await page.goto("https://letcode.in/dropable") 16 | const src = await page.$("#draggable") 17 | const dst = await page.$("#droppable"); 18 | if (src && dst) { 19 | const srcBound = await src.boundingBox() 20 | const dstBound = await dst.boundingBox() 21 | if (srcBound && dstBound) { 22 | await page.mouse.move(srcBound.x + srcBound.width / 2, srcBound.y + srcBound.height / 2) 23 | await page.mouse.down(); 24 | await page.mouse.move(dstBound.x + dstBound.width / 2, dstBound.y + dstBound.height / 2) 25 | await page.mouse.down(); 26 | } else { 27 | throw new Error("No Element") 28 | } 29 | } 30 | 31 | }) 32 | 33 | test("my test", async () => { 34 | await page.goto("https://jqueryui.com/droppable/") 35 | // switch to frame 36 | const frame = page.frame({ url: "https://jqueryui.com/resources/demos/droppable/default.html" }) 37 | if (frame) { 38 | const src = await frame.$("#draggable") 39 | const dst = await frame.$("#droppable"); 40 | if (src && dst) { 41 | const srcBound = await src.boundingBox() 42 | const dstBound = await dst.boundingBox() 43 | if (srcBound && dstBound) { 44 | await page.mouse.move(srcBound.x + srcBound.width / 2, srcBound.y + srcBound.height / 2) 45 | await page.mouse.down(); 46 | await page.mouse.move(dstBound.x + dstBound.width / 2, dstBound.y + dstBound.height / 2) 47 | await page.mouse.down(); 48 | } else { 49 | throw new Error("No Element") 50 | } 51 | } 52 | } 53 | }) 54 | 55 | }) -------------------------------------------------------------------------------- /test/elements/search.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, Page, chromium } from "playwright"; 2 | 3 | describe("Search Git Repo", () => { 4 | let browser: Browser; 5 | let context: BrowserContext; 6 | let page: Page; 7 | beforeAll(async () => { 8 | browser = await chromium.launch({ 9 | headless: false 10 | }); 11 | context = await browser.newContext() 12 | page = await context.newPage(); 13 | await page.goto("https://letcode.in/elements") 14 | }) 15 | 16 | test("Enter Git username", async () => { 17 | 18 | const header = await page.$("nav[role='navigation']") 19 | header?.screenshot({ path: "header.png" }) 20 | const ele = await page.$("input[name='username']") 21 | await ele?.fill("ortonikc"); 22 | await ele?.press("Enter"); 23 | }) 24 | test("Print all the repos", async () => { 25 | await page.waitForSelector("app-gitrepos ol li", { timeout: 5000 }) 26 | const repos = await page.$$("app-gitrepos ol li"); 27 | console.log(repos.length); 28 | // for await 29 | // for await (const repo of repos) { 30 | // console.log(await repo.innerText()); 31 | // } 32 | // map 33 | const allUrls = await Promise.all(repos.map(async (repo, i) => { 34 | return await repo.innerText() 35 | })) 36 | console.log(allUrls); 37 | await page.screenshot({ path: "fs.png", fullPage: true }) 38 | }) 39 | afterEach(async () => { 40 | await page.screenshot({ path: Date.now() + 'screenshot1.png' }) 41 | }) 42 | afterAll(async () => { 43 | await page.close(); 44 | await context.close(); 45 | await browser.close() 46 | }) 47 | }) -------------------------------------------------------------------------------- /test/eval/changeIMage.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, chromium, ElementHandle, Page } from "playwright" 2 | 3 | describe("Learn how to handle input fields", () => { 4 | 5 | let browser: Browser; 6 | let context: BrowserContext; 7 | let page: Page; 8 | beforeAll(async () => { 9 | browser = await chromium.launch({ 10 | headless: false 11 | }); 12 | context = await browser.newContext() 13 | page = await context.newPage(); 14 | await page.goto("https://letcode.in/") 15 | }) 16 | test("change image", async () => { 17 | const ele = await page.$("img") 18 | ele?.evaluate(ele => ele.src = "https://avatars.githubusercontent.com/u/58769833?v=4") 19 | // console.log(text); 20 | }) 21 | }) -------------------------------------------------------------------------------- /test/frames/frame.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, Page, chromium } from "playwright"; 2 | 3 | describe("Frames handling concept", () => { 4 | 5 | let browser: Browser; 6 | let context: BrowserContext; 7 | let page: Page; 8 | beforeAll(async () => { 9 | browser = await chromium.launch({ 10 | headless: false 11 | }); 12 | context = await browser.newContext() 13 | page = await context.newPage(); 14 | await page.goto("https://letcode.in/frame") 15 | }) 16 | test("Interact with frames", async () => { 17 | const frame = page.frame({ name: "firstFr" }); 18 | // frame?.fill("") 19 | if (frame != null) { 20 | await frame.fill("input[name='fname']", "Koushik"); 21 | await frame.fill("input[name='lname']", "Chatterjee"); 22 | 23 | // inner frame 24 | const frames = frame.childFrames(); 25 | console.log('No. of inner frames: ' + frames.length); 26 | if (frames != null) 27 | await frames[0].fill("input[name='email']", "koushik@mail.com") 28 | else { 29 | console.log("Wrong frame"); 30 | } 31 | const parent = frames[0].parentFrame() 32 | // await frame.fill("input[name='lname']", "Letcode"); 33 | await parent?.fill("input[name='lname']", "Youtube"); 34 | } else throw new Error("No such frame") 35 | }) 36 | }) -------------------------------------------------------------------------------- /test/inputs/inputs.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, chromium, ElementHandle, Page } from "playwright" 2 | 3 | describe("Learn how to handle input fields", () => { 4 | 5 | let browser: Browser; 6 | let context: BrowserContext; 7 | let page: Page; 8 | beforeAll(async () => { 9 | browser = await chromium.launch({ 10 | headless: false 11 | }); 12 | context = await browser.newContext() 13 | page = await context.newPage(); 14 | await page.goto("https://letcode.in/edit") 15 | }) 16 | 17 | test("Enter your full Name", async () => { 18 | // await page.type("id=fullName", "Koushik Chatterjee") 19 | const name = await page.$("#fullName") 20 | // if (name != null) { 21 | // name.type("") 22 | // } 23 | await name?.type("koushik chatterjee") 24 | }) 25 | test("Append a text and press keyboard tab", async () => { 26 | const join = await page.$("#join") 27 | await join?.focus(); 28 | await page.keyboard.press("End") 29 | await join?.type(" Human") 30 | // await join?.fill(" Human") 31 | }) 32 | test("What is inside the text box", async () => { 33 | const text = await page.getAttribute("id=getMe", "value"); 34 | console.log(text); 35 | }) 36 | test("Clear the text", async () => { 37 | await page.fill("//input[@value='Koushik Chatterjee']", "") 38 | }) 39 | afterAll(async () => { 40 | await page.close() 41 | await context.close() 42 | await browser.close() 43 | }) 44 | }) -------------------------------------------------------------------------------- /test/launchBrowser.test.ts: -------------------------------------------------------------------------------- 1 | import { chromium } from "playwright"; 2 | describe('Launch Browser', () => { 3 | 4 | test('Open letcode', async () => { 5 | const browser = await chromium.launch({ 6 | headless: false 7 | }) 8 | const context = await browser.newContext(); 9 | const page = await context.newPage(); 10 | await page.goto('https://letcode.in/') 11 | await browser.close() 12 | }) 13 | 14 | }) -------------------------------------------------------------------------------- /test/login.test.ts: -------------------------------------------------------------------------------- 1 | import { chromium } from "playwright"; 2 | describe('Launch Browser', () => { 3 | 4 | test('Recorded script', async () => { 5 | const browser = await chromium.launch({ 6 | headless: false 7 | }); 8 | const context = await browser.newContext({ 9 | recordVideo: { 10 | dir: "./videos/", 11 | size: { 12 | width: 800, 13 | height: 600 14 | } 15 | } 16 | }); 17 | 18 | // Open new page 19 | const page = await context.newPage(); 20 | 21 | // Go to https://letcode.in/ 22 | await page.goto('https://letcode.in/'); 23 | 24 | // Click text=/.*Log in.*/ 25 | await Promise.all([ 26 | page.waitForNavigation(/*{ url: 'https://letcode.in/signin' }*/), 27 | page.click('text=/.*Log in.*/') 28 | ]); 29 | 30 | // Click input[name="email"] 31 | await page.click('input[name="email"]'); 32 | 33 | // Fill input[name="email"] 34 | await page.fill('input[name="email"]', 'koushik350@gmail.com'); 35 | 36 | // Press Tab 37 | await page.press('input[name="email"]', 'Tab'); 38 | 39 | // Fill input[name="password"] 40 | await page.fill('input[name="password"]', 'Pass123$'); 41 | 42 | // Click //button[normalize-space(.)='LOGIN'] 43 | await Promise.all([ 44 | page.waitForNavigation(/*{ url: 'https://letcode.in/' }*/), 45 | page.click('//button[normalize-space(.)=\'LOGIN\']') 46 | ]); 47 | 48 | // Click text="Let's Go" 49 | await page.click('text="Let\'s Go"'); 50 | // assert.equal(page.url(), 'https://letcode.in/test'); 51 | 52 | // Click //div[5]/div/div[3]/div/div[3]/button[normalize-space(.)='Get me in!'] 53 | await page.click('//div[5]/div/div[3]/div/div[3]/button[normalize-space(.)=\'Get me in!\']'); 54 | // assert.equal(page.url(), 'https://letcode.in/forms'); 55 | 56 | // Click input[placeholder="Text input"] 57 | await page.click('input[placeholder="Text input"]'); 58 | 59 | // Fill input[placeholder="Text input"] 60 | await page.fill('input[placeholder="Text input"]', 'koushik'); 61 | 62 | // Press Tab 63 | await page.press('input[placeholder="Text input"]', 'Tab'); 64 | 65 | // Fill //div[normalize-space(.)='Last Name']/div/input[normalize-space(@placeholder)='Text input' and normalize-space(@type)='text'] 66 | await page.fill('//div[normalize-space(.)=\'Last Name\']/div/input[normalize-space(@placeholder)=\'Text input\' and normalize-space(@type)=\'text\']', 'chatterjee'); 67 | 68 | // Click //div[normalize-space(.)='Email'] 69 | await page.click('//div[normalize-space(.)=\'Email\']'); 70 | 71 | // Fill input[placeholder="Email input"] 72 | await page.fill('input[placeholder="Email input"]', 'koushik350@gmail.com'); 73 | 74 | // Click input[placeholder="Phone Number"] 75 | await page.click('input[placeholder="Phone Number"]'); 76 | 77 | // Fill input[placeholder="Phone Number"] 78 | await page.fill('input[placeholder="Phone Number"]', '9999999999'); 79 | 80 | // Click input[placeholder="Address Line-1"] 81 | await page.click('input[placeholder="Address Line-1"]'); 82 | 83 | // Fill input[placeholder="Address Line-1"] 84 | await page.fill('input[placeholder="Address Line-1"]', 'adyar'); 85 | 86 | // Click input[placeholder="Address Line-2"] 87 | await page.click('input[placeholder="Address Line-2"]'); 88 | 89 | // Fill input[placeholder="Address Line-2"] 90 | await page.fill('input[placeholder="Address Line-2"]', 'chennai'); 91 | 92 | // Click input[placeholder="State"] 93 | await page.click('input[placeholder="State"]'); 94 | 95 | // Fill input[placeholder="State"] 96 | await page.fill('input[placeholder="State"]', 'TN'); 97 | 98 | // Click input[placeholder="Postal-Code"] 99 | await page.click('input[placeholder="Postal-Code"]'); 100 | 101 | // Fill input[placeholder="Postal-Code"] 102 | await page.fill('input[placeholder="Postal-Code"]', '600113'); 103 | 104 | // Fill input[name="dob"] 105 | await page.fill('input[name="dob"]', '1994-12-04'); 106 | 107 | // Check input[type="checkbox"] 108 | await page.check('input[type="checkbox"]'); 109 | 110 | // Fill input[name="gender"] 111 | // await page.fill('input[name="gender"]', 'on'); 112 | 113 | // Click text=/.*Male.*/ 114 | await page.click('text=/.*Male.*/'); 115 | 116 | // Click input[type="submit"] 117 | await Promise.all([ 118 | page.waitForNavigation(/*{ url: 'https://letcode.in/forms' }*/), 119 | page.click('input[type="submit"]') 120 | ]); 121 | 122 | // Click text=/.*Sign out.*/ 123 | await page.click('text=/.*Sign out.*/'); 124 | // assert.equal(page.url(), 'https://letcode.in/'); 125 | 126 | // Close page 127 | await page.close(); 128 | 129 | // --------------------- 130 | await context.close(); 131 | await browser.close(); 132 | }) 133 | 134 | 135 | xtest('Open letcode', async () => { 136 | const browser = await chromium.launch({ 137 | headless: false 138 | }) 139 | const context = await browser.newContext(); 140 | const page = await context.newPage(); 141 | await page.goto('https://letcode.in/'); 142 | await page.click("text=Log in"); 143 | await page.fill("input[name='email']", 'koushik350@gmail.com'); 144 | await page.fill("input[name='password']", 'Pass123$'); 145 | await page.click('button:text("LOGIN")') 146 | await page.click('"Sign out"'); 147 | await browser.close() 148 | }) 149 | 150 | }) -------------------------------------------------------------------------------- /test/mutilple-browser/browser.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, Page, chromium, firefox } from "playwright"; 2 | 3 | describe("Launch local browser", () => { 4 | 5 | let browser: Browser; 6 | let context: BrowserContext; 7 | let page: Page; 8 | beforeAll(async () => { 9 | browser = await firefox.launch({ 10 | headless: false, 11 | // channel: "msedge" 12 | // executablePath: "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe" 13 | }); 14 | context = await browser.newContext() 15 | page = await context.newPage(); 16 | await page.goto("https://letcode.in/") 17 | }) 18 | test("Goto letcode and verify title as LetCode with Koushik", async () => { 19 | const title = await page.title(); 20 | console.log(title); 21 | expect(title).toBe("LetCode with Koushik") 22 | }) 23 | }) -------------------------------------------------------------------------------- /test/pageObjectsTest/TC001_Login.test.ts: -------------------------------------------------------------------------------- 1 | import CommonFunctions from "../../page/common.page"; 2 | import HeaderPage from "../../page/Header.page"; 3 | import LoginPage from "../../page/Login.page"; 4 | import Env from "../../utils/environment"; 5 | import * as data from "../../data/login.cred.json"; 6 | import { Page } from "playwright"; 7 | import ReportUtils from "../../utils/reportUtils"; 8 | 9 | declare const page: Page; 10 | declare const reporter: any; 11 | 12 | describe("TC001", () => { 13 | // my pages 14 | let header: HeaderPage; 15 | let login: LoginPage; 16 | let common: CommonFunctions; 17 | 18 | beforeAll(async () => { 19 | await page.goto(Env.test); 20 | header = new HeaderPage(page); 21 | login = new LoginPage(page); 22 | common = new CommonFunctions(page); 23 | }) 24 | 25 | test("Login positive _ JIRA101", async () => { 26 | await reporter 27 | .description("Login into LetCode") 28 | .story("JIRA101"); 29 | await reporter.startStep("Navigate to letcode"); 30 | expect(page.url()).toBe("https://letcode.com/") 31 | await ReportUtils.screenshot("naviagation") 32 | await reporter.endStep(); 33 | await reporter.startStep("Click login link"); 34 | await header.clickLoginLink(); 35 | await reporter.endStep(); 36 | expect(page.url()).toBe("https://letcode.in/signin") 37 | await reporter.startStep("enter username"); 38 | await login.enterUserName(data.email); 39 | await ReportUtils.screenshot("username") 40 | // const screenshotBuffer = await page.screenshot(); 41 | // await reporter.addAttachment("username", screenshotBuffer, "image/png"); 42 | await reporter.endStep(); 43 | await reporter.startStep("enter password"); 44 | await login.enterUserPassword(data.pass); 45 | await ReportUtils.screenshot(); 46 | await reporter.endStep(); 47 | await login.clickLoginBtn(); 48 | const toaster = await common.toaster(); 49 | expect(await toaster?.textContent()).toContain("Welcome"); 50 | await reporter.startStep("Log out"); 51 | await header.clickSignOutLink(); 52 | await ReportUtils.screenshot("done") 53 | await reporter.endStep(); 54 | 55 | }); 56 | test("Login again", async () => { 57 | await page.goto(Env.test, { 58 | waitUntil: "domcontentloaded" 59 | }); 60 | await header.clickLoginLink(); 61 | await login.login("koushik350@gmail.com", "Pass123$"); 62 | await page.waitForNavigation(); 63 | expect(page.url()).toBe("https://letcode.in/") 64 | await ReportUtils.screenshot("done") 65 | }) 66 | }) -------------------------------------------------------------------------------- /test/pageObjectsTest/TC002.test.ts: -------------------------------------------------------------------------------- 1 | import CommonFunctions from "../../page/common.page"; 2 | import HeaderPage from "../../page/Header.page"; 3 | import LoginPage from "../../page/Login.page"; 4 | import Env from "../../utils/environment"; 5 | import * as data from "../../data/login.cred.json"; 6 | import { Page } from "playwright"; 7 | import ReportUtils from "../../utils/reportUtils"; 8 | 9 | declare const page: Page; 10 | declare const reporter: any; 11 | 12 | describe("TC002", () => { 13 | // my pages 14 | let header: HeaderPage; 15 | let login: LoginPage; 16 | let common: CommonFunctions; 17 | 18 | beforeAll(async () => { 19 | await page.goto(Env.test); 20 | header = new HeaderPage(page); 21 | login = new LoginPage(page); 22 | common = new CommonFunctions(page); 23 | }) 24 | 25 | afterAll(async () => { 26 | await page.close(); 27 | await context.close(); 28 | await browser.close(); 29 | }) 30 | 31 | test("TC002 Login 1", async () => { 32 | await reporter 33 | .description("Login into LetCode") 34 | .story("JIRA101"); 35 | await reporter.startStep("Navigate to letcode"); 36 | expect(page.url()).toBe("https://letcode.in/") 37 | await ReportUtils.screenshot("naviagation") 38 | await reporter.endStep(); 39 | await reporter.startStep("Click login link"); 40 | await header.clickLoginLink(); 41 | await reporter.endStep(); 42 | expect(page.url()).toBe("https://letcode.in/signin") 43 | await reporter.startStep("enter username"); 44 | await login.enterUserName(data.email); 45 | await ReportUtils.screenshot("username") 46 | // const screenshotBuffer = await page.screenshot(); 47 | // await reporter.addAttachment("username", screenshotBuffer, "image/png"); 48 | await reporter.endStep(); 49 | await reporter.startStep("enter password"); 50 | await login.enterUserPassword(data.pass); 51 | await ReportUtils.screenshot(); 52 | await reporter.endStep(); 53 | await login.clickLoginBtn(); 54 | const toaster = await common.toaster(); 55 | expect(await toaster?.textContent()).toContain("Welcome"); 56 | await reporter.startStep("Log out"); 57 | await header.clickSignOutLink(); 58 | await ReportUtils.screenshot("done") 59 | await reporter.endStep(); 60 | 61 | }); 62 | // test("TC002 Login 2", async () => { 63 | // await page.goto(Env.test, { 64 | // waitUntil: "domcontentloaded" 65 | // }); 66 | // await header.clickLoginLink(); 67 | // await login.login("koushik350@gmail.com", "Pass123$"); 68 | // await page.waitForNavigation(); 69 | // expect(page.url()).toBe("https://letcode.in/") 70 | // await ReportUtils.screenshot("done") 71 | // }) 72 | }) -------------------------------------------------------------------------------- /test/select/dropdown.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, Page, chromium } from "playwright"; 2 | 3 | describe("How to handle Select", () => { 4 | 5 | let browser: Browser; 6 | let context: BrowserContext; 7 | let page: Page; 8 | beforeAll(async () => { 9 | browser = await chromium.launch({ 10 | headless: false 11 | }); 12 | context = await browser.newContext() 13 | page = await context.newPage(); 14 | await page.goto("https://letcode.in/dropdowns") 15 | }) 16 | test("Select a dropdown based on value", async () => { 17 | const fruits = await page.$("#fruits"); 18 | // await fruits?.selectOption({ label: "" }); 19 | await fruits?.selectOption("2"); 20 | const msg = await page.$("div.notification.is-success"); 21 | if (msg) { 22 | // expect(await msg.textContent()).toContain("Apple"); 23 | } 24 | }) 25 | test("Select multiple", async () => { 26 | const heros = await page.$("#superheros"); 27 | heros?.selectOption([ 28 | { label: "Aquaman" }, { value: "bt" }, { index: 8 } 29 | ]) 30 | }) 31 | test("Count of the select", async () => { 32 | const lang = await page.$$("#lang option") 33 | console.log(lang.length); 34 | }) 35 | test("get selected text", async () => { 36 | await page.selectOption("#country", "India"); 37 | const text = await page.$eval("#country", ele => ele.value) 38 | console.log(text); 39 | expect(text).toBe("India"); 40 | }) 41 | 42 | afterAll(async () => { 43 | await page.close() 44 | await context.close() 45 | await browser.close() 46 | }) 47 | }) -------------------------------------------------------------------------------- /test/uploadFile.test.ts: -------------------------------------------------------------------------------- 1 | import { chromium } from 'playwright'; 2 | 3 | 4 | describe('Upload file', () => { 5 | 6 | const filePath0 = '../videos/a.webm'; 7 | const filePath1 = '../videos/b.webm'; 8 | xtest("upload file using set input files", async () => { 9 | 10 | const browser = await chromium.launch({ 11 | headless: false 12 | }) 13 | const context = await browser.newContext(); 14 | const page = await context.newPage(); 15 | await page.goto('https://www.sendgb.com/'); 16 | await page.setInputFiles("input[name='qqfile']", [filePath0, filePath1]); 17 | }) 18 | 19 | test("Upload using on function", async () => { 20 | const browser = await chromium.launch({ 21 | headless: false 22 | }) 23 | const context = await browser.newContext(); 24 | const page = await context.newPage(); 25 | await page.goto('https://the-internet.herokuapp.com/upload'); 26 | page.on("filechooser", async (filechooser) => { 27 | // filechooser.isMultiple(); 28 | await filechooser.setFiles([filePath0, filePath1]) 29 | }) 30 | await page.click(".example + div#drag-drop-upload", { force: true }) 31 | }) 32 | 33 | }) -------------------------------------------------------------------------------- /test/windowHandling/windows.test.ts: -------------------------------------------------------------------------------- 1 | import { Browser, BrowserContext, Page, chromium } from "playwright"; 2 | 3 | describe("Window handling", () => { 4 | 5 | let browser: Browser; 6 | let context: BrowserContext; 7 | let page: Page; 8 | beforeAll(async () => { 9 | browser = await chromium.launch({ 10 | headless: false 11 | }); 12 | context = await browser.newContext() 13 | page = await context.newPage(); 14 | await page.goto("https://letcode.in/windows") 15 | }) 16 | 17 | test("Home Page", async () => { 18 | console.log(await page.title()); 19 | expect(await page.title()).toBe("Window handling - LetCode"); 20 | }) 21 | 22 | xtest("Single page handling", async () => { 23 | const [newWindow] = await Promise.all([ 24 | context.waitForEvent("page"), 25 | await page.click("#home") 26 | ]) 27 | await newWindow.waitForLoadState(); 28 | expect(newWindow.url()).toContain("test"); 29 | await newWindow.click('"Log in"'); 30 | await newWindow.waitForNavigation(); 31 | expect(newWindow.url()).toContain("signin"); 32 | // await newWindow.close(); 33 | await page.bringToFront(); 34 | await page.click('"LetXPath"'); 35 | }) 36 | test("Multipage handling", async () => { 37 | const [multipage] = await Promise.all([ 38 | context.waitForEvent("page"), 39 | await page.click("#multi") 40 | ]) 41 | await multipage.waitForLoadState(); 42 | const allwindows = page.context().pages(); 43 | console.log("no.of windows: " + allwindows.length); 44 | allwindows.forEach(page => { 45 | console.log(page.url()); 46 | }); 47 | await allwindows[1].bringToFront(); 48 | allwindows[1].on("dialog", (dialog) => { 49 | console.log('Message: ' + dialog.message()); 50 | dialog.accept(); 51 | }) 52 | await allwindows[1].click("id=accept") 53 | 54 | }) 55 | afterAll(async () => { 56 | await page.close() 57 | await context.close() 58 | await browser.close() 59 | }) 60 | }) -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "strict": true, 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "resolveJsonModule": true, 8 | "types": [ 9 | "@types/jest", 10 | "jest-playwright-preset", 11 | "expect-playwright", 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /utils/environment.ts: -------------------------------------------------------------------------------- 1 | export default class Env { 2 | public static test: string = "https://letcode.in/"; 3 | public static integration: string = "https://int.letcode.in/"; 4 | public static production: string = "https://prod.letcode.in/"; 5 | public static staging: string = "https://stg.letcode.in/"; 6 | } -------------------------------------------------------------------------------- /utils/reportUtils.ts: -------------------------------------------------------------------------------- 1 | declare const reporter: any; 2 | export default class ReportUtils { 3 | 4 | 5 | public static async screenshot(description?: string) { 6 | const screenshotBuffer = await page.screenshot(); 7 | description = description != undefined ? description : "screenshot"; 8 | await reporter.addAttachment(description, screenshotBuffer, "image/png"); 9 | } 10 | 11 | } --------------------------------------------------------------------------------