Automation Training by Bipin Thakare  ·  Ex-Nvidia Mock tests Live batches Job postings

Locator practice, in order.

Twenty one numbered elements, top to bottom. Start where getBy works, watch it break, then learn XPath from a single attribute up to sibling navigation.

01–07  getBy works
08–10  getBy fails
11–21  XPath
Zone 4  practice
01
Zone 1 · getBy works · 01 to 07

Create your account

Seven elements, one per row. Each describes itself in a different way — use Playwright's built-in locator methods.

By alt text

TaskLocate using getByAltText()
01 Profile photo

By placeholder

TaskLocate using getByPlaceholder()
02

By label

TaskLocate using getByLabel()
03

By title attribute

TaskLocate using getByTitle()

By test id

TaskLocate using getByTestId()
05 Password strength: Weak

By visible text

TaskLocate using getByText()
06

Your details are never shared with sellers

By role

TaskLocate using getByRole()
07
02
Zone 2 · where getBy fails · 08 to 10

Trending this week

Three rows, three different reasons getBy cannot reach the element — switch to XPath.

Dell Inspiron 15

₹55,000

3 matches
TaskLocate using getByRole()
08

Samsung Galaxy M14

₹25,000

padded text
TaskLocate the Free Delivery text using getByText()
09
Free Delivery

Noise ColorFit Pro

₹3,200

no handles
TaskLocate the wishlist icon using getByRole()
10
03
Zone 3 · XPath · simple to complex · 11 to 21

Delivery details

Start with a single attribute. Finish with sibling navigation and indexing — XPath only.

By id

TaskLocate using the id attribute
11

By another attribute

TaskLocate using the name attribute
12

Two attributes combined

TaskLocate using two attributes
13

By exact text

TaskLocate using exact text match
14 Home Office

Text with spaces — normalize-space

TaskLocate using normalize-space()
15
Same Day

Partial match — contains

TaskLocate using contains()
16

Partial match — starts-with

TaskLocate using starts-with()
17

Parent to child

TaskLocate via the parent element
18

Order summary

Elements 19 to 21 — relative XPath.

Tasks 19 — Locate the price beside Laptop using following-sibling 20 — Locate the product whose price is 25000 21 — Locate the second Remove button using positional index
ProductPriceQuantityAction
Laptop 1955000 1
20Mobile 25000 1 21
04
Zone 4 · practice · no numbers, no hints

Payment

No numbers, no hints — identify the right strategy yourself.

Payment method

TaskLocate the UPI option

Card details

TaskLocate both fields independently

Card expiry

TaskLocate each field independently

Gift message

TaskLocate without using the id

Saved cards

TaskLocate using sibling navigation
Saved cardExpiryAction
HDFC Visa 441108 / 2029
ICICI Amex 773203 / 2028

Finish

TaskLocate the button, the icon and the image
Secure payment badge
01
Checkbox · 01 to 05

Passenger preferences

check(), uncheck(), isChecked() and isDisabled(). One row per situation.

Single checkbox

TaskCheck it and assert it is checked. Uncheck it and assert it is unchecked
01
checkbox.spec.ts
1test("Checkbox: check and uncheck", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 let terms = page.locator('#terms'); 6 7 await terms.check(); 8 expect(await terms.isChecked()).toBe(true); 9 10 await terms.uncheck(); 11 expect(await terms.isChecked()).toBe(false); 12});

Pre-checked

TaskAssert it is checked without clicking
02
checkbox.spec.ts
1test("Checkbox: pre-checked without clicking", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // no click here — just read the initial state 6 let isChecked = await page.locator('#newsletter').isChecked(); 7 expect(isChecked).toBe(true); 8});

Disabled checkbox

TaskAssert it is disabled
03
checkbox.spec.ts
1test("Checkbox: disabled state", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 let isDisabled = await page.locator('#insurance').isDisabled(); 6 expect(isDisabled).toBe(true); 7});

Group of checkboxes

TaskSelect Vegetarian and Jain. Count how many are checked
04
checkbox.spec.ts
1test("Checkbox: select two from a group and count checked", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('input[name="meal"][value="veg"]').check(); 6 await page.locator('input[name="meal"][value="jain"]').check(); 7 8 // count only the ones that are actually checked in this group 9 let checkedCount = await page.locator( 10 'input[name="meal"]:checked' 11 ).count(); 12 expect(checkedCount).toBe(2); 13});

Select all

TaskClick the master checkbox. Assert all add-ons are checked
05
checkbox.spec.ts
1test("Checkbox: select all ticks every add-on", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('#selectAllAddons').check(); 6 7 // every .addon checkbox should now be checked 8 let addons = page.locator('.addon'); 9 let total = await addons.count(); 10 11 for (let i = 0; i < total; i++) { 12 expect(await addons.nth(i).isChecked()).toBe(true); 13 } 14});
02
Radio button · 01 to 03

Journey details

Only one option in a group can be selected. That is the whole difference from a checkbox.

Radio group

TaskSelect Economy and assert it is checked. Select Business and assert Economy is now unchecked
01
radio.spec.ts
1test("Radio: selecting one clears the other", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 let economy = page.locator('input[name="seatclass"][value="economy"]'); 6 let business = page.locator('input[name="seatclass"][value="business"]'); 7 8 await economy.check(); 9 expect(await economy.isChecked()).toBe(true); 10 11 // selecting a second option in the SAME group auto-clears the first 12 await business.check(); 13 expect(await economy.isChecked()).toBe(false); 14});

Pre-selected radio

TaskAssert the pre-selected option is checked without clicking
02
radio.spec.ts
1test("Radio: pre-selected without clicking", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 let isChecked = await page.locator( 6 'input[name="triptype"][value="round"]' 7 ).isChecked(); 8 expect(isChecked).toBe(true); 9});

Disabled radio

TaskAssert the disabled option is disabled
03
radio.spec.ts
1test("Radio: disabled state", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 let isDisabled = await page.locator( 6 'input[name="fare"][value="premium"]' 7 ).isDisabled(); 8 expect(isDisabled).toBe(true); 9});
04
Auto suggest · 01 to 02

Search suggestions

The list does not exist until you start typing. fill() pastes everything at once and never triggers it, so use pressSequentially().

Auto suggest with delay

TaskType “Del” and wait for suggestions to appear. Print all suggestions.
01
  • Delhi Airport Hotel
  • Delhi Marriott
  • Delhi Oberoi
  • Mumbai Taj
  • Mumbai Leela
  • Mumbai ITC Grand
  • Hyderabad Novotel
  • Hyderabad Trident
  • Bengaluru Hilton
  • Bengaluru Sheraton
  • Chennai Park Hyatt
  • Pune Marriott
  • Pune Sheraton
  • Kolkata Oberoi Grand
  • Goa Taj Holiday Village
autosuggest.spec.ts
1test("Auto suggest: type Del, print suggestions", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // fill() will NOT trigger the suggestion list — it pastes the 6 // text in one shot with no real keystrokes. pressSequentially() types 7 // one character at a time, exactly like a real user 8 await page.locator('#hotelSearch').pressSequentially("Del", { delay: 100 }); 9 10 // this list has a 400ms delay before it appears, so wait for it 11 await page.waitForTimeout(600); 12 13 let suggestions = await page.locator( 14 '#hotelSuggest li:visible' 15 ).allTextContents(); 16 console.log(suggestions); 17});

Auto suggest without delay

TaskType “Mum” and print all suggestions.
02
  • Mumbai
  • Mumbai Central
  • Pune
  • Pune Airport
  • Delhi
  • Delhi Cantt
  • New Delhi
  • Hyderabad
  • Bengaluru
  • Bengaluru City
  • Chennai
  • Kolkata
  • Goa
  • Ahmedabad
  • Surat
autosuggest.spec.ts
1test("Auto suggest: type Mum, print suggestions", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('#citySearch').pressSequentially("Mum", { delay: 80 }); 6 7 // this list has no delay, so the suggestions appear immediately — 8 // no waitForTimeout needed here, unlike the hotel search above 9 let suggestions = await page.locator( 10 '#citySuggest li:visible' 11 ).allTextContents(); 12 console.log(suggestions); 13});
05
Web table · 01 to 04

Booking history

Reading cells, counting rows, moving between siblings, and handling a table that changes after you sort or filter it.

Read a cell

Tasks Find the fare for the route "Mumbai to Goa" and print it. Find all rows where Status is "Confirmed" and print the Booking ID for each. Count the total data rows (exclude the header) and assert the count is 4.
01
Booking IDRouteFareStatus
BK1001Pune to Delhi5400Confirmed
BK1002Mumbai to Goa3200Pending
BK1003Delhi to Dubai18700Confirmed
BK1004Chennai to Pune4100Cancelled
webtable.spec.ts
1import { test } from "playwright/test"; 2 3test("Web Table: find fare for Mumbai to Goa", async ({ page }) => { 4 5 await page.goto("<your-practicekart-url>"); 6 7 // locate the row where Route is "Mumbai to Goa" 8 // then move to the Fare cell using following-sibling 9 let fare = await page.locator( 10 '//table[@id="bookingTable"]' 11 + '//td[text()="Mumbai to Goa"]/following-sibling::td[1]' 12 ).textContent(); 13 14 console.log(fare); // 3200 15});

Count rows and columns

Tasks Count the data rows Count the columns
02
FlightFromToSeatsPrice
AI202MumbaiDelhi1804500
6E401PuneBengaluru1503200
SG301ChennaiHyderabad1202800
webtable.spec.ts
1test("Web Table: count rows and columns", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // count the data rows — skip the header row 6 let rowCount = await page.locator( 7 '//table[@id="countTable"]/tr[position()>1]' 8 ).count(); 9 console.log(rowCount); // 3 10 11 // count the columns — count the header cells 12 let colCount = await page.locator( 13 '//table[@id="countTable"]//th' 14 ).count(); 15 console.log(colCount); // 5 16});

Sortable column

Tasks Click the "Flights" header to sort ascending. Assert the first row value is 18. Click again to sort descending. Assert the first row value is 42.
03
CityFlights
Pune18
Delhi42
Chennai25
Bengaluru31
webtable.spec.ts
1test("Web Table: sortable column", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // click Flights header once — sorts ascending 6 await page.locator('//table[@id="sortTable"]//th[text()="Flights"]').click(); 7 8 // read the first data row's Flights cell — must be re-read live, 9 // the DOM order has actually changed after the sort 10 let firstAsc = await page.locator( 11 '//table[@id="sortTable"]/tr[2]/td[2]' 12 ).textContent(); 13 expect(firstAsc).toBe("18"); 14 15 // click again — sorts descending 16 await page.locator('//table[@id="sortTable"]//th[text()="Flights"]').click(); 17 18 let firstDesc = await page.locator( 19 '//table[@id="sortTable"]/tr[2]/td[2]' 20 ).textContent(); 21 expect(firstDesc).toBe("42"); 22});

Filtered table

Tasks Type "Pune" in the filter. Assert only 1 row is visible using isVisible(). Clear the filter. Assert all 4 rows are visible again.
04
CityAirport
PunePNQ
MumbaiBOM
DelhiDEL
KolkataCCU
webtable.spec.ts
1test("Web Table: filtered table", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // type Pune in the filter box 6 await page.locator('#tableFilter').fill("Pune"); 7 8 // Pune row stays visible, Mumbai row does not 9 let puneVisible = await page.locator( 10 '//table[@id="filterTable"]//td[text()="Pune"]' 11 ).isVisible(); 12 expect(puneVisible).toBe(true); 13 14 let mumbaiVisible = await page.locator( 15 '//table[@id="filterTable"]//td[text()="Mumbai"]' 16 ).isVisible(); 17 expect(mumbaiVisible).toBe(false); 18 19 // clear the filter — all rows return 20 await page.locator('#tableFilter').fill(""); 21 22 let allRows = await page.locator( 23 '//table[@id="filterTable"]/tr[position()>1]' 24 ).count(); 25 expect(allRows).toBe(4); 26});
06
iFrame · 01 to 03

Frames

An iframe is a page inside a page. Playwright cannot reach inside it with an ordinary locator. You must switch into the frame first using frameLocator().

Simple iframe

TaskClick the button inside the frame
01
iframe id = simpleFrame
iframe.spec.ts
1test("iFrame: click button inside frame", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // step into the frame first, then locate the button inside it 6 await page.frameLocator('#simpleFrame') 7 .locator('#frameButton') 8 .click(); 9});

Form inside a frame

TaskFill the field inside the frame. Read the value back
02
iframe id = formFrame
iframe.spec.ts
1test("iFrame: fill field and read value back", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 let field = page.frameLocator('#formFrame').locator('#frameName'); 6 7 await field.fill("Milan"); 8 9 // read the value back to confirm the fill landed inside the frame 10 let value = await field.inputValue(); 11 expect(value).toBe("Milan"); 12});

Two frames on one page

TaskClick the action button in frameA, then click it in frameB
03
frameA
frameB
iframe.spec.ts
1test("iFrame: click action button in frameA and frameB", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // both frames have an element with the SAME id — only the frame 6 // you switch into decides which one you actually reach 7 await page.frameLocator('#frameA') 8 .locator('#frameAction') 9 .click(); 10 11 await page.frameLocator('#frameB') 12 .locator('#frameAction') 13 .click(); 14});
07
Nested iFrame · 01

Frame inside a frame

A frame can contain another frame. Each level needs its own frameLocator, chained one after the other.

Element inside nested frames

TaskFill both inputs
01
outerFrame
nested-iframe.spec.ts
1test("Nested iFrame: fill outer and inner inputs", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // outerInput is one level deep — one frameLocator reaches it 6 await page.frameLocator('#outerFrame') 7 .locator('#outerInput') 8 .fill("Outer value"); 9 10 // innerInput is two levels deep — chain a second frameLocator 11 // before the final locator, one frameLocator per boundary crossed 12 await page.frameLocator('#outerFrame') 13 .frameLocator('#innerFrame') 14 .locator('#innerInput') 15 .fill("Inner value"); 16});
08
Alert and dialogue box · 01 to 04

Browser dialogues

A dialogue blocks the page until it is answered. Playwright auto dismisses them by default, so you must register a handler with page.on("dialog") BEFORE the click that triggers it.

Simple alert

TaskTrigger the alert, print the message and accept it
01
alerts.spec.ts
1test("Alert: accept and read message", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // register the listener BEFORE the click that triggers the alert 6 page.on("dialog", async dialog => { 7 console.log(dialog.message()); // Your booking has been saved. 8 await dialog.accept(); 9 }); 10 11 await page.locator('#alertBtn').click(); 12});

Confirm box

TaskAccept it once and dismiss it once. Assert the result changes
02
alerts.spec.ts
1test("Confirm: accept once, dismiss once", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // first run — accept 6 page.once("dialog", dialog => dialog.accept()); 7 await page.locator('#confirmBtn').click(); 8 9 let accepted = await page.locator('#dialogOut').textContent(); 10 expect(accepted).toBe("Confirm accepted"); 11 12 // second run — dismiss, using once() again since the listener 13 // only fires for a single dialog 14 page.once("dialog", dialog => dialog.dismiss()); 15 await page.locator('#confirmBtn').click(); 16 17 let dismissed = await page.locator('#dialogOut').textContent(); 18 expect(dismissed).toBe("Confirm dismissed"); 19});

Prompt box

TaskPass your name into the prompt. Assert it appears in the result
03
alerts.spec.ts
1test("Prompt: pass name and assert result", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // accept() on a prompt can take the text to type, then confirm 6 page.once("dialog", dialog => dialog.accept("Bipin")); 7 await page.locator('#promptBtn').click(); 8 9 let result = await page.locator('#dialogOut').textContent(); 10 expect(result).toBe("Prompt accepted with: Bipin"); 11});

Result

TaskAssert the result text matches the last action performed
04
No dialogue answered yet
alerts.spec.ts
1test("Result box reflects the last dialogue action", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // before any dialogue is answered, the box shows a placeholder 6 let before = await page.locator('#dialogOut').textContent(); 7 expect(before).toBe("No dialogue answered yet"); 8 9 // trigger the alert — the result box should update to match 10 page.once("dialog", dialog => dialog.accept()); 11 await page.locator('#alertBtn').click(); 12 13 let after = await page.locator('#dialogOut').textContent(); 14 expect(after).toBe("Alert was accepted"); 15});
09
Mouse action · 01 to 04

Simple mouse actions

click(), dblclick(), click({ button: "right" }) and hover(). Each one produces a different result below.

Single click

TaskClick the button. Assert the result updates
01
Click me
mouse.spec.ts
1test("Mouse: single click", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('#singleClickZone').click(); 6 7 let result = await page.locator('#mouseOut').textContent(); 8 expect(result).toBe("Single click detected"); 9});

Double click

TaskDouble-click the button using dblclick(). Assert the result updates
02
Double click me
mouse.spec.ts
1test("Mouse: double click", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // a single click() here does nothing — the element only 6 // listens for the dblclick event 7 await page.locator('#doubleClickZone').dblclick(); 8 9 let result = await page.locator('#mouseOut').textContent(); 10 expect(result).toBe("Double click detected"); 11});

Right click

TaskRight-click to open the menu. Click "Copy booking ID". Assert the result updates
03
Right click me
  • Copy booking ID
  • Download ticket
  • Cancel booking
mouse.spec.ts
1test("Mouse: right click opens context menu", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('#rightClickZone').click({ button: "right" }); 6 7 await page.locator('#ctxMenu li:has-text("Copy booking ID")').click(); 8 9 let result = await page.locator('#mouseOut').textContent(); 10 expect(result).toBe("Context menu item chosen: Copy booking ID"); 11});

Hover

TaskHover the button. Assert the menu appears
04
  • Change date
  • Add baggage
  • Request refund
mouse.spec.ts
1test("Mouse: hover reveals menu", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // the menu does not exist visibly until the button is hovered 6 await page.locator('#hoverBtn').hover(); 7 8 let result = await page.locator('#mouseOut').textContent(); 9 expect(result).toBe("Hover detected, menu is now visible"); 10});

Result

TaskAssert the result text matches the last action performed
05
No action yet
mouse.spec.ts
1test("Mouse: result box reflects the last action", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // before any action, the box shows a placeholder 6 let before = await page.locator('#mouseOut').textContent(); 7 expect(before).toBe("No action yet"); 8 9 // perform any action — the box should update to match it 10 await page.locator('#singleClickZone').click(); 11 12 let after = await page.locator('#mouseOut').textContent(); 13 expect(after).toBe("Single click detected"); 14});
10
Mouse action advanced · 01 to 03

Drag, slide and resize

These need real mouse movement rather than a simple click. Use dragTo(), or mouse.down(), mouse.move() and mouse.up() for full control.

Drag and drop

TaskDrag the card into the drop zone
01
Boarding pass
Drop it here
mouse-advanced.spec.ts
1test("Advanced mouse: drag and drop", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // dragTo() performs the full sequence — hover, mouse down, 6 // move to the target, and mouse up — in a single call 7 await page.locator('#dragMe').dragTo( 8 page.locator('#dropHere') 9 ); 10 11 let result = await page.locator('#dropHere').textContent(); 12 expect(result).toBe("Dropped: Boarding pass"); 13});

Slider

TaskMove the handle to the midpoint
02

Value: 20

mouse-advanced.spec.ts
1test("Advanced mouse: move slider to midpoint", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // range inputs support fill() directly — Playwright sets the 6 // value and fires the input event, no manual mouse math needed 7 await page.locator('#budgetSlider').fill("50"); 8 9 let value = await page.locator('#sliderValue').textContent(); 10 expect(value).toBe("50"); 11});

Resizable box

TaskMake the box larger
03
Drag my bottom right corner
mouse-advanced.spec.ts
1test("Advanced mouse: resize the box", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // this is a native CSS resize handle — there is no built-in 6 // Playwright shortcut, so drive the mouse manually 7 let box = await page.locator('#resizeBox').boundingBox(); 8 if (!box) { 9 throw new Error("resize box not found"); 10 } 11 12 // the resize handle sits right at the bottom right corner 13 let startX = box.x + box.width - 4; 14 let startY = box.y + box.height - 4; 15 16 await page.mouse.move(startX, startY); 17 await page.mouse.down(); 18 await page.mouse.move(startX + 120, startY + 80); 19 await page.mouse.up(); 20});
11
Shadow DOM · 01 to 03

Encapsulated components

A shadow root hides its markup from the main document. An ordinary locator cannot see inside it, so Playwright has to pierce the boundary. Open DevTools and look for #shadow-root.

Single shadow root

TaskLocate the button inside the shadow root and click it
01

host id = shadowHost

shadow-dom.spec.ts
1test("Shadow DOM: click button inside shadow root", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // a normal page.locator() cannot see past the shadow boundary. 6 // Playwright pierces open shadow roots automatically when the 7 // locator is scoped starting from the host element 8 await page.locator('#shadowHost') 9 .locator('#shadowButton') 10 .click(); 11});

Input inside shadow root

TaskFill the input and read its value back
02

host id = shadowForm

shadow-dom.spec.ts
1test("Shadow DOM: fill input and read value back", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 let field = page.locator('#shadowForm').locator('#shadowInput'); 6 7 await field.fill("Testing shadow input"); 8 9 let value = await field.inputValue(); 10 expect(value).toBe("Testing shadow input"); 11});

Nested shadow roots

TaskFill the input two shadow boundaries deep
03

host id = shadowOuter

shadow-dom.spec.ts
1test("Shadow DOM: fill input two boundaries deep", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // #shadowOuter contains a nested #innerHost, which has its own 6 // shadow root. Chain .locator() once per boundary crossed — 7 // exactly the same principle as nested iframes 8 await page.locator('#shadowOuter') 9 .locator('#innerHost') 10 .locator('#nestedShadowInput') 11 .fill("Two levels deep"); 12});
13
New Tab · 01 to 03

Tabs and popups

Practice handling new tabs, reading their titles, and interacting with elements inside them.

01 — Open a new tab

TaskClick the button and confirm the new tab opened
newtab.spec.ts
1test("New tab: open and confirm", async ({ page, context, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // register the listener BEFORE the click that opens the tab 6 await page.locator('a[href="https://playwright.dev/"]').click(); 7 await page.waitForEvent('popup'); 8 9 // context.pages() lists every open tab — index 0 is the 10 // original, index 1 is the new one 11 let allTabs = context.pages(); 12 expect(allTabs.length).toBe(2); 13});

02 — Read the new tab title

TaskPrint the title of the new tab
newtab.spec.ts
1test("New tab: print the title", async ({ page, context }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('a[href="https://the-internet.herokuapp.com/"]').click(); 6 await page.waitForEvent('popup'); 7 8 let allTabs = context.pages(); 9 let newTab = allTabs[1]; 10 11 // the URL resolves quickly, but the title needs the page to 12 // finish loading first 13 await newTab.waitForLoadState(); 14 15 console.log(await newTab.title()); 16});

03 — Click an element in the new tab

TaskLocate a link in the new tab and click it
newtab.spec.ts
1test("New tab: click a link inside it", async ({ page, context }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('a[href="https://github.com/microsoft/playwright"]').click(); 6 await page.waitForEvent('popup'); 7 8 let newTab = context.pages()[1]; 9 await newTab.waitForLoadState(); 10 11 // page still refers to the ORIGINAL tab — every locator on 12 // the new tab must go through newTab, never through page 13 await newTab.locator('a[href*="issues"]').first().click(); 14});
14
Screenshots · 01 to 03

Capture the page

Practice visible area, full page and element-level screenshots using Playwright's screenshot() method.

01 — Visible area screenshot

TaskCapture the visible area
screenshot.spec.ts
1test("Screenshot: visible area only", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.screenshot({ 6 path: "Screenshot/visible.png", 7 fullPage: false 8 }); 9});

02 — Full page screenshot

TaskCapture the full page
Scroll end ↓
screenshot.spec.ts
1test("Screenshot: full scrollable page", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // fullPage: true scrolls through the entire page and stitches 6 // it into one image, including content below the fold 7 await page.screenshot({ 8 path: "Screenshot/complete.png", 9 fullPage: true 10 }); 11});

03 — Element screenshot

TaskCapture only the button
screenshot.spec.ts
1test("Screenshot: single element only", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // calling screenshot() on a LOCATOR, not on page, crops the 6 // image tightly to just that element 7 await page.locator('#screenshotTarget').screenshot({ 8 path: "Screenshot/element.png" 9 }); 10});
15
File Upload · 01 to 02

Upload your files

Practice single and multiple file upload using setInputFiles(). Remember: never use fill() or click() on a file input.

01 — Single file upload

TaskUpload a file
fileupload.spec.ts
1test("File upload: single file", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // never use fill() or click() on a file input — 6 // setInputFiles() is the only method that works 7 await page.locator('#singleFile').setInputFiles( 8 "/path/to/your/file.png" 9 ); 10});

02 — Multiple file upload

TaskUpload two files at once
fileupload.spec.ts
1test("File upload: multiple files", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // pass an array of paths instead of a single string — 6 // each path in the array is attached as a separate file 7 await page.locator('#multiFile').setInputFiles([ 8 "/path/to/file-one.png", 9 "/path/to/file-two.jpeg" 10 ]); 11});
16
File Download · 01 to 02

Download and validate

Practice registering the download event listener before the click, then validating the download using download.failure().

01 — Download a file

TaskDownload the file
download.spec.ts
1test("File download: trigger and capture", async ({ page, context }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // this link opens in a new tab — wait for that tab first 6 await page.locator('a[href="https://the-internet.herokuapp.com/download"]').click(); 7 await page.waitForEvent('popup'); 8 let downloadTab = context.pages()[1]; 9 await downloadTab.waitForLoadState(); 10 11 // register the download listener WITHOUT await, then click the 12 // file link, then await the promise 13 let downloadPromise = downloadTab.waitForEvent('download'); 14 await downloadTab.locator('a').first().click(); 15 16 let download = await downloadPromise; 17 console.log(download.suggestedFilename()); 18});

02 — Validate the download

TaskAssert the download succeeded
download.spec.ts
1test("File download: validate with download.failure()", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // no await here — only registering the listener 6 let downloadPromise = page.waitForEvent('download'); 7 8 await page.locator('a[download="practice-download.txt"]').click(); 9 10 let download = await downloadPromise; 11 12 // failure() returns null on success, an error string on failure 13 expect(await download.failure()).toBeNull(); 14});
12
Tooltip · 01 to 03

Hover and title tooltips

A tooltip only exists while the pointer is over the element, so you must hover first and then read it. Native title tooltips are rendered by the browser and never appear in the DOM at all.

CSS tooltip

TaskHover the button and read the tooltip text
01 Your booking is refundable until 24 hours before departure
tooltip.spec.ts
1test("Tooltip: hover and read CSS tooltip", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // the tip element already exists in the DOM, it is just 6 // hidden by CSS until the wrapper is hovered 7 await page.locator('#tipBtn').hover(); 8 9 let tipText = await page.locator('#tipText').textContent(); 10 console.log(tipText); 11});

Native title tooltip

TaskRead the title attribute value — do not try to locate the tooltip
02
tooltip.spec.ts
1test("Tooltip: read native title attribute", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // a native title tooltip is drawn by the OS/browser itself. 6 // it never becomes a locatable DOM element, so read the 7 // title attribute directly instead of hovering 8 let tip = await page.locator('#titleTipBtn').getAttribute("title"); 9 10 expect(tip).toBe("Fares are locked for 20 minutes once you begin checkout"); 11});

Tooltip on a disabled field

TaskHover and read the tooltip text
03 This fare cannot be edited after confirmation
tooltip.spec.ts
1test("Tooltip: hover on wrapper around disabled field", async ({ page }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 // disabled elements can behave inconsistently with hover across 6 // browsers, so hover the WRAPPER span instead of the input itself 7 // — the CSS rule that reveals the tip is attached to the wrapper 8 await page.locator('#tipWrap3').hover(); 9 10 let tipText = await page.locator('#tipLocked').textContent(); 11 console.log(tipText); 12});
13
Toast / Snackbar · 01 to 04

Messages that disappear

A toast appears for a moment and then removes itself. If your assertion runs too late the element is already gone, which is why these are a classic source of flaky tests.

Success toast, 3 seconds

TaskClick the button and assert the toast text before it disappears
01
toast.spec.ts
1test("Toast: success message", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('#toastOkBtn').click(); 6 7 // toBeVisible / toHaveText auto-retry, which matters here — 8 // the toast text is set a tick after the click, not instantly 9 await expect(page.locator('#toastBar')).toHaveText( 10 "Booking saved successfully" 11 ); 12});

Error toast, 3 seconds

TaskClick and assert the error message text
02
toast.spec.ts
1test("Toast: error message", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('#toastErrBtn').click(); 6 7 // same toast bar, different message and colour class — 8 // assert on the text, the colour is just presentation 9 await expect(page.locator('#toastBar')).toHaveText( 10 "Booking could not be cancelled" 11 ); 12});

Fast toast, 1 second

TaskCatch the toast within 1 second and assert the text
03
toast.spec.ts
1test("Toast: fast toast, disappears in 1 second", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('#toastFastBtn').click(); 6 7 // this toast is gone after 1 second — do NOT add any 8 // waitForTimeout before the assertion or you will miss it entirely 9 await expect(page.locator('#toastBar')).toHaveText( 10 "Saved", { timeout: 900 } 11 ); 12});

Delayed toast, appears after 2 seconds

TaskWait for the toast to appear, then assert the text
04
toast.spec.ts
1test("Toast: delayed toast, appears after 2s", async ({ page, expect }) => { 2 3 await page.goto("<your-practicekart-url>"); 4 5 await page.locator('#toastSlowBtn').click(); 6 7 // nothing appears immediately here — give the assertion a 8 // long enough timeout to cover the 2 second delay, then it 9 // still has 3 seconds of visible life to be caught within 10 await expect(page.locator('#toastBar')).toHaveText( 11 "Sent for approval", { timeout: 3000 } 12 ); 13});
← Back to practice
Now live Join for exam tips & behind the scenes

GitHub Copilot Certification — Mock Tests

Full-length, scored practice exams for the GH-300 certification, built the same way as everything else here — from real teaching experience, not scraped question dumps.

Format

6 full-length tests

Questions per test

60, timed (100 min)

Explanations

Every question

Price

Free

What makes this different

  • Written and reviewed by an active trainer, not generated and dumped
  • Explanations for every answer, including why the wrong options are tempting
  • Domain-wise breakdown after every attempt, so you know exactly what to study next
  • Backed by live batches — questions reflect what students actually get wrong

Six papers, two per tier (Simple, Medium, Complex). No negative marking. Pass mark 700/1000.

← Back to practice
Live batch · Starts 7th September

AI-Powered QA Expert Program

A 3-week live, online program on AI-driven test automation — taught by Bipin Thakare (Ex-Nvidia). Small batches, hands-on, built around real production workflows.

Starts

7th September

Duration

3 weeks

Schedule

Mon–Thu, 9:00–10:30 PM

Fee

₹7,000

What you'll master

  • AI test generation from Figma, PRDs and live applications
  • GitHub Copilot mastery — certification-level depth
  • AI-driven frameworks with Playwright and Selenium POM
  • MCP servers (Playwright, Figma, Atlassian) for website auto-exploration
  • Autonomous MCP agents that plan, write, fix and self-heal scripts
  • Claude Code CLI setup, rules (CLAUDE.md) and production workflows
  • Build 2 custom AI agents from scratch and 2 full web applications
  • Token optimization by hosting LLMs locally with Ollama

Seats fill up fast — batches are kept small on purpose. Message on WhatsApp for enquiries.


Playwright + TypeScript · Dates announced soon

Playwright + TypeScript Automation Batch

From zero to job-ready automation engineer. TypeScript, Playwright, BDD with Cucumber, and AI tooling — taught by Bipin Thakare (Ex-Nvidia).

Starts

To be announced

Duration

1.5 months

Schedule

Mon–Thu, 9:00–10:30 PM

Fee

₹9,000

What you'll master

  • TypeScript fundamentals — variables, functions, OOP and exception handling
  • Playwright locators, iframes, shadow DOM and complex mouse gestures
  • API testing and Page Object Model framework design from scratch
  • BDD automation with Cucumber — Gherkin, scenario outlines and hooks
  • Debugging with Trace Viewer, Allure reporting and video recordings
  • GitHub Copilot and the Playwright Agent — Planner, Generator, Healer
  • Mock interviews and resume reviews to get you job-ready

Reach Bipin directly on WhatsApp: +91 93255 97453 / 7798452716