Who Is This Course For?
Built for manual testers, career changers, and developers who want to master Playwright automation with JavaScript. You will learn to find any element on any page, perform every type of browser action, verify results professionally, and finish the course with a complete B149_AFW framework — POM, Allure reports, GitHub, and Jenkins — that you can show in an interview on day one.
01
Introduction & Setup
Days 1–2 | Node.js, VS Code,
npm init playwright@latest, your first testWhat You Will Learn
- Playwright — free, open-source, by Microsoft; vs Selenium comparison
- Browser support: Chromium (Chrome/Edge), Firefox, WebKit (Safari)
- Install Node.js and verify with
node --version - Set up a project with
npm init playwright@latestand select JavaScript - Set
"type":"module"inpackage.jsonfor modern ES imports - Playwright ships its own browsers — not the ones installed on your laptop
- Every Playwright method is
async— always useawait - Run:
npx playwright test demo.spec.js --headed test(title, async({page}) => {})— the{page}fixture is auto-injected- Install "Playwright Test for VS Code" extension — run tests with one click
- Configure headless/headed in
playwright.config.js - Available fixtures in tests:
{page},{context},{browser}
02
Web Elements & Page Navigation API
Day 2 | HTML basics, object hierarchy,
page.* methods, slowMoWhat You Will Learn
- Every button, input, and link is built from HTML — tag + attributes + text
- Open DevTools (F12) and inspect the code behind any element
- Object hierarchy:
playwright → browser → context → page page.goto(url)— open a web address;page.title()— read current titlepage.goBack()/goForward()/reload()/close()page.setViewportSize({width, height})— resize the browser windowpage.evaluate(fn)— run JavaScript directly inside the browserlaunchOptions:{slowMo:2000}— slow down each action by 2 seconds
03
Smart Locators — 7 Semantic APIs
Days 3–4 |
getByRole, getByLabel, getByTestId and 4 moreWhat You Will Learn
getByAltText()— matches thealtattribute on images; not case-sensitive, allows partial matchgetByTitle()— matches thetitletooltip attribute; not CS, allows PMgetByPlaceholder()— matches the greyed-out hint text inside inputsgetByLabel()— finds an input by the label that describes itgetByText()— finds any element by its visible text contentgetByTestId()— matchesdata-testid; case-sensitive, no partial matchgetByRole(role, {name})— finds by ARIA type: button, textbox, link, etc.{exact:true}— force case-sensitive exact match (works for locators 1–5, 7)- Timeout error after 30s when locator finds zero elements
- Strict mode violation when locator matches more than one element
- Debug live with Playwright Inspector:
npx playwright test --headed --debug - Open inspector mid-test:
await page.pause()
04
CSS Selectors
Day 4 |
tag[attr='val'], #id, .class, AND/OR, ^= *= $=, pseudo-classesWhat You Will Learn
- Standard CSS syntax:
tag[attributeName='value'] - ID shortcut:
input[id='u1']→input#u1→#u1 - Class shortcut:
button[class='btn']→button.btn - Multi-class element:
button.btn.btn-lg.btn-primary - AND condition:
[name='n1'][title='click me']— both must match - OR condition:
[name='n1'],[title='click me']— either matches - Partial match:
^=starts-with ·*=contains ·$=ends-with - Text support via Playwright pseudo-classes:
:has-text(),:text(),:text-is() - Verify selectors in DevTools → Elements → Ctrl+F before using in code
- CSS limitation: no native text support — use pseudo-classes or smart locators instead
05
XPath — Basic
Day 5 | Absolute vs relative,
@attr, text(), and/or/not, starts-with/containsWhat You Will Learn
- XPath describes the path to an element like an address in the HTML tree
- Absolute:
/html/body/div[2]/input— full path from root (fragile) - Relative:
//input[@name='username']— search anywhere in the tree (preferred) - Index (1-based):
//input[1],(//img)[last()],(//img)[3] - Wildcard:
//*[@id='a1']— any tag with that attribute - Logical AND:
//input[@name='x' and @type='text'] - Logical OR:
//input[@name='x' or @id='y'] - Logical NOT:
//input[not(@placeholder='p')] - Exact text:
//a[text()='Google']— or dot notation:.//a[.='Google'] - Partial text — starts-with:
//p[starts-with(text(),'User')] - Partial text — contains:
//button[contains(.,'Login')] - Browser supports XPath 1.0 only —
ends-with()is not available natively
06
XPath — Axes & Dependent Element Traversal
Days 6–7 |
parent, ancestor, following-sibling, IE & DE patternWhat You Will Learn
child /— navigate to a direct child elementparent /..— move up to the containing elementdescendant //— reach any element inside a container, any depthancestor— find the table that contains a specific cell://th[text()='Subject']/ancestor::tablefollowing-sibling— move sideways to the next element://th[text()='Subject']/following-sibling::thpreceding-sibling— move back to an earlier sibling with optional index- IE & DE pattern — start from a stable known element (IE) and navigate to a dynamic one (DE)
- Full traversal example:
//td[.='Pencil']/../td[8]— find price next to product name - Checkbox by row:
//td[.='soap']/..//input[@type='checkbox'] - Real-world practice on Flipkart, Amazon, and MakeMyTrip dynamic listings
07
Locator Filtering, Chaining & iFrames
Days 7–9 |
hasText, first(), and(), all(), frameLocator, Shadow DOMFiltering & Multi-Element Handling
.filter({hasText:'...'})— keep only elements containing this text.filter({hasNotText:'...'})— exclude elements containing this text.filter({has: page.locator('...')})— keep elements that contain a child element.filter({visible:true})— visible elements only.first()/.last()/.nth(n)— pick by position (0-indexed).count()— total number of matched elements.and(locator)/.or(locator)— combine two locator conditions.all()— get all matches as an array to loop through.allTextContents()/.allInnerTexts()— all visible text as string array
iFrames & Shadow DOM
- An
<iframe>is a page embedded inside another page — common in payment forms and ads page.frameLocator('#f1').locator('#t2')— locate the frame then the element insidepage.locator('#f1').contentFrame().locator('#t2')— via content frame methodpage.frame({name:'n1'})/page.frame({url:'...'})— access by name or URLpage.frames()[1]— access frame by index in the frames array- Shadow DOM — Playwright can reach elements inside shadow roots directly without special handling
08
Element Actions & Page API (33 Methods)
Days 10–13 |
click, fill, dragTo, state checks, screenshots, trace viewerInteraction Methods
click()— left-click;click({button:'right'})— right-click;dblclick()— double-clickfill(value)— clear and type into an input;clear()— clear onlypressSequentially(text)— type one character at a time like a humanpress('Enter')— press any keyboard keycheck()/uncheck()— tick or untick a checkboxhover()— mouse over to reveal tooltip or dropdowndragTo(targetLocator)— drag and drop one element onto anotherscrollIntoViewIfNeeded()— scroll until element is visibleboundingBox()— get position and size as{x, y, width, height}focus()/blur()/highlight()— focus management and debug aid
State Checks & Data Extraction
isVisible()/isHidden()/isEnabled()/isChecked()getAttribute(name)— read any HTML attribute valuetextContent()— all text including hidden;innerText()— visible text onlyinputValue()— current value of an input or textareascreenshot({path})— capture just this element;page.screenshot()— full pagepage.dragAndDrop(src, dst)— drag between two selectors on the pagepage.mouse.move(x,y)/page.keyboard.down('Control')— raw input APIs- Run with trace:
--trace=on; replay attrace.playwright.dev
09
Special Element Handling
Days 14–18 |
selectOption, JS popups (page.once), HTML modals, new tabsSelect / Listbox Handling
- Listbox built with
<select>and<option>tags selectOption({value:'b'})— select by the option's value attributeselectOption({label:'Chennai'})— select by visible label textselectOption({index:3})— select by zero-based position- Multi-select:
selectOption(['val1','val2'])— select multiple options at once - Read available options in DevTools console:
$("option[value='a']")
JavaScript Popups, HTML Modals & New Tabs
- JS popup types:
alert(message only),confirm(OK/Cancel),prompt(text input) page.once('dialog', d => d.accept())— handle the next popup oncedialog.dismiss()— click Cancel;dialog.fill(text)— type into a prompt- HTML modal popups — interact as normal elements using regular locators
- New tab from a click:
const [newPage] = await Promise.all([context.waitForEvent('page'), button.click()]) - Switch between tabs by using the
newPageobject for subsequent actions
10
API Testing & Browser Context
Day 19 |
request fixture, GET/POST, broken links, context isolationWhat You Will Learn
- Playwright can test backend APIs — not just browser pages
request.get(url)— send a GET request and capture the responserequest.post(url, {data:{...}})— send a POST with a JSON bodyresponse.status()— check the HTTP status code (200, 404, 500)response.json()— parse and read the response body- Verify broken links: collect all
<a href>elements, request each, check for non-200 status - Browser context — isolated session with its own cookies and storage
- Multiple contexts per browser = multiple isolated sessions running in parallel
- Combine API + browser testing in the same test — e.g. create a user via API, verify via browser
- Use API to set up login state without clicking through the login UI
11
Synchronisation & Timeouts
Days 20–22 |
waitFor, waitForURL, waitForLoadState, waitForFunctionTimeout Configuration
- Default test timeout: 30,000ms — override with
test.setTimeout(n) test.slow()— triple the current test timeout with one callpage.setDefaultTimeout(n)— applies to every action on this pagepage.setDefaultNavigationTimeout(n)— overrides only forgoto/reload/goBack- Config-level:
use:{actionTimeout:7000, navigationTimeout:5000} - Config-level test timeout:
timeout:40000indefineConfig()
Smart Wait Methods
page.waitForTimeout(ms)— blind wait (use only as last resort)locator.waitFor()— wait until element is visible (default state)- States:
attached·visible·hidden·detached page.waitForSelector('#id')— CSS/XPath only version ofwaitForpage.waitForURL('exact-url')— wait for full URL match after navigationpage.waitForURL(url => url.toString().endsWith('home'))— predicate matchpage.waitForLoadState('networkidle')— page fully settled, no pending requestspage.waitForFunction(fn)— wait until a JavaScript condition returns true
12
Assertions —
expect() APIDays 22–23 | Page / element / value level, soft assert,
.not, timeoutPage-Level Assertions
expect(page).toHaveTitle('...')— assert current page titleexpect(page).toHaveURL('...')— assert current URLexpect(page).toHaveScreenshot()— visual regression for the full page
Element-Level Assertions
toHaveAttribute(name, value)— verify any HTML attributetoBeVisible()/toBeHidden()/toBeEnabled()/toBeDisabled()toBeChecked()/toBeEditable()/toBeAttached()/toBeEmpty()toHaveValue('...')— current value of an inputtoHaveText('...')— visible text content of an elementtoHaveClass('...')/toHaveId('...')toHaveCSS('color', 'rgb(0,0,0)')— verify computed CSS styletoHaveScreenshot()— visual regression for a single element
Value-Level Assertions & Configuration
toBe()/toEqual()/toContain()— strict, deep, and substring checkstoBeLessThan(n)/toBeGreaterThan(n)/toBeLessThanOrEqual(n)- Default
expecttimeout: 5000ms — override per-assertion:{timeout:7000} - Global:
expect:{timeout:6000}in config applies to all assertions - Hard assert (default) — stops test immediately on failure
- Soft assert:
await expect.soft(el).toHaveValue('...')— continues, collects all failures - Negate any assertion:
await expect(page).not.toHaveTitle('Wrong Title')
13
Data-Driven Testing & Remote Execution
Days 24–25 | JSON,
xlsx, SauceLabs, chromium.connect(), fullyParallelWhat You Will Learn
- Import JSON test data:
import users from './users.json' assert{type:'json'} - Loop through JSON array with
for...of— run one test per data row - Read Excel data:
npm install xlsx→XLSX.readFile()→sheet_to_json() - Compatibility testing — same script runs on Chromium, Firefox, and WebKit
- Remote server:
npx playwright run-server --port 56789on the remote machine - Connect from local:
await chromium.connect('ws://localhost:56789/') - SauceLabs — cloud platform; configure via
saucectl+.sauce/config.yml - Parallel execution:
npx playwright test --headed— default workers = half your CPUs - Limit workers:
--workers=1orworkers:2in config - Run tests in same file in parallel:
fullyParallel:truein config
14
B149_AFW — Full Automation Framework
Days 26–33 | POM, hooks, custom fixtures, Allure, GitHub, Jenkins, tags
Page Object Model (POM)
- Encapsulation — declare page elements as
#privatefields, expose via getter/setter - JSDoc:
/** @param {import("@playwright/test").Page} page */— enables VS Code autocomplete exportclass from each page file;importin spec files- One class per page:
LoginPage,HomePage,ItemsPage,ItemKitsPage - Test file uses only human-readable method calls — no raw locators
- Update one POM class when the UI changes — all tests fixed instantly
Hooks, Custom Fixtures & Login-Once Strategy
test.beforeEach/test.afterEach— auto-run before/after each test in a filetest.beforeAll/test.afterAll— run once per file- Hooks accept fixtures:
({page, browser, context, browserName, baseURL}) test.describe('group', () => {})— group tests with shared setuptest.skip/test.only/test.describe.skip- Custom fixture in
base_test.js— reusablebeforeEach/afterEachacross all files global_setup.js— login once, save state tostorageState.jsonglobal_teardown.js— deletestorageState.jsonafter all tests complete- Config:
storageState:'storageState.json'— every test inherits the logged-in session
Allure Reports, Tags, GitHub & Jenkins
npm install --save-dev allure-playwright allure-commandline- Config:
[['allure-playwright',{outputFolder:'allure-results'}]] - Generate:
npx allure generate allure-results -o allure-report; Open:npx allure open allure-report - Tags:
test('login @smoke', async({page})=>{})— word starting with@ - Run by tag:
npx playwright test --grep '@smoke'/--grep-invert '@smoke' - OR tags:
--grep "@smoke|@sanity" - GitHub —
git config, VS Code Source Control, publish to private repo - Jenkins Free Style project — SCM: GitHub URL + credentials + branch
*/main - Build steps:
call npm install → call npx playwright install → call npx playwright test - Post-build: Allure plugin; Schedule cron:
00 20 * * *= 8 PM daily - Automatic trigger: "Build after other projects are built" — runs when dev deploys
Interview Prep
What is the Page Object Model and how does it improve a test suite?
POM gives each page its own class with private locators and public action methods. Tests call methods like
loginPage.login(user, pass) instead of repeating locators. One page change = one file update — all tests fixed without touching any test file.How do you handle a JavaScript alert in Playwright?
Register a listener with
page.once('dialog', d => d.accept()) before triggering the action that causes the alert. d.dismiss() clicks Cancel, and d.fill(text) types into a prompt dialog before accepting.What is the difference between hard and soft assertions?
Hard
expect stops the test immediately on failure. Soft expect.soft continues the test and collects all failures — the full report appears at the end. Use soft assertions when multiple independent checks should all be visible in one run.How does
storageState speed up a test suite?global_setup.js logs in once and serialises cookies to storageState.json. Every subsequent test loads that file and skips the login page entirely. global_teardown.js deletes it afterwards. A 100-test suite saves the login time for 99 tests.Why does Playwright have its own browsers and how does that differ from Selenium?
Playwright downloads specific browser versions that it has been tested against, ensuring consistent behaviour across every environment. Selenium uses whatever browser is already installed, which can cause version mismatch failures when Chrome auto-updates overnight.
What is the difference between action timeout and test timeout?
Test timeout is the budget for the entire test (default 30s). Action timeout is how long each individual method call waits for the element to be ready (default unlimited — best practice is to set it).
test.slow() triples the test timeout when a specific test legitimately needs more time.