From “I know commands” to “I know how to test.”
This page consolidates the Playwright material you uploaded and the practical patterns you worked through: locators, assertions, controls, dynamic elements, frames, tabs, files, hooks, groups, execution, reports, flaky tests, retries, and your OrangeHRM project.
01. Core mental model
LOCATE → ACTION → VERIFY
Locator finds the element. Action tells the browser what to do. Assertion proves the expected result.
What does await mean?
Browser operations take time. await tells JavaScript to wait for the operation before continuing.
Action vs retrieve vs assert
| Category | Examples | Meaning |
|---|---|---|
| ACTION | fill(), click(), check(), uncheck(), selectOption() | DO IT |
| RETRIEVE / ASK | inputValue(), isChecked(), count(), textContent(), getAttribute() | TELL ME WHAT HAPPENED |
| ASSERT | toHaveValue(), toBeChecked(), toHaveCount(), toBeVisible(), toHaveAttribute() | PROVE IT IS CORRECT |
02. Locators
User-facing locators
- getByRole() — by role/name
- getByLabel() — form control by label
- getByPlaceholder() — placeholder
- getByAltText() — image alt text
- getByTitle() — title
- getByTestId() — test-specific attribute
- getByText() — visible text
CSS / XPath / locator()
Use locator() when a stable semantic locator is unavailable or when CSS/XPath is appropriate for the actual DOM.
page.locator('#username')
page.locator('.product-image-wrapper')
page.locator('input[type="checkbox"]')Position / filtering
locator.first()
locator.last()
locator.nth(2)
locator.filter({ hasText: 'Blue Top' })
locator.filter({ visible: true })
await locator.all()nth(2) means the third matching element because indexing starts at 0.
Locator discipline
- Prefer meaningful, stable locators.
- Do not use nth() blindly.
- Inspect actual HTML before copying a locator.
- A locator identifies an element; it does not perform the action.
03. Assertions
| Assertion | Use |
|---|---|
toBeVisible() | Element is visible |
toBeEnabled() | Element is enabled |
toBeEditable() | Textbox can be edited |
toHaveValue() | Input / single-select contains expected value |
toBeChecked() | Checkbox/radio is selected |
toHaveCount(n) | Exactly n DOM elements match |
toHaveLength(n) | JavaScript array has n items |
toHaveAttribute() | Attribute has expected value |
toHaveText() | Element contains expected text |
toHaveURL() | Page is at expected URL |
toHaveTitle() | Page has expected title |
Classic confusion
// Retrieve
const value = await email.inputValue();
// Assert
await expect(email).toHaveValue('test@gmail.com');Count confusion
// Retrieve a number const count = await items.count(); // Assert exact DOM count await expect(items).toHaveCount(5); // Array length const texts = await items.allTextContents(); await expect(texts).toHaveLength(5);
04. UI controls
| Control | User action | Main Playwright action | Verification |
|---|---|---|---|
| Textbox | Type / edit | fill() / type() | toHaveValue() |
| Checkbox | Choose zero / one / many | check() / uncheck() | toBeChecked() |
| Radio | Choose one | check() | toBeChecked() |
| Native dropdown | Select option | selectOption() | toHaveValue() |
| Auto-suggestion | Type → find → click | fill() + click matching suggestion | Result / state / text |
Textbox
const email = page.getByRole('textbox', { name: 'Email Address' });
await expect(email).toBeVisible();
await expect(email).toBeEnabled();
await expect(email).toBeEditable();
await email.fill('test@gmail.com');
await expect(email).toHaveValue('test@gmail.com');
const value = await email.inputValue();Checkbox
const monday = page.getByLabel('Monday');
await monday.check();
await expect(monday).toBeChecked();
const selected = await monday.isChecked();
await monday.uncheck();Radio
const male = page.getByLabel('Male');
await male.check();
await expect(male).toBeChecked();Memory trick: Radio = ONE.
Native dropdown
const country = page.locator('#country');
await country.selectOption('india');
await expect(country).toHaveValue('india');
await country.selectOption({ label: 'Germany' });
await country.selectOption({ index: 2 });Multi-select dropdown
const colors = page.locator('#colors');
await colors.selectOption(['yellow', 'white']);
const selectedOptions = page.locator('#colors option:checked');
const selectedTexts = await selectedOptions.allTextContents();
const selectedValues = await selectedOptions.evaluateAll(
options => options.map(option => option.value)
);
await expect(selectedOptions).toHaveCount(2);
await expect(selectedValues).toEqual(['yellow', 'white']);05. Dynamic UI
Auto-suggestion dropdown
await page.locator('#search').fill('java');
const suggestions = page.locator('#suggestions li');
const items = await suggestions.all();
for (let item of items) {
const text = await item.textContent();
if (text && text.trim() === 'Java Selenium') {
await item.click();
break;
}
}Dynamic button / timer
Your practice page included a dynamic button/timer area. The key lesson is to interact with the state the application actually presents and verify the resulting state rather than relying on arbitrary delays.
Alerts & popups
The practice page included simple, confirmation, and prompt-style modal alerts, plus popup-related exercises. The core idea is to identify whether you are dealing with a page element/modal or a separate browser/page event and automate the correct object.
Tabs inside a page
For UI tabs, locate the tab buttons and verify the associated content/state. This is different from browser tabs/pages, which use the Page objects discussed later.
06. Mouse & keyboard
Mouse actions
await locator.hover();
await locator.dblclick();
await locator.click({ button: 'right' });
await locator.scrollIntoViewIfNeeded();Keyboard actions
await locator.click();
await page.keyboard.type('Bruce Wayne');
await page.keyboard.press('Tab');
await page.keyboard.press('Escape');Example workflow: click a field → type → Tab → type next field → Tab/Escape → assert values.
Scrolling
const heading = page.getByText('Range Slider').first();
await heading.scrollIntoViewIfNeeded();
await expect(heading).toBeVisible();
await page.locator('body').press('Home');07. Date pickers
Calendar navigation
Open the date picker, inspect the displayed month/year, navigate with the previous/next controls, then select the target date.
const datePicker = page.locator('#datepicker');
await datePicker.click();
console.log(await page.locator('.ui-datepicker-year').innerText());
console.log(await page.locator('.ui-datepicker-month').innerText());
// navigate until the required month/year
// then click the required dateNative select inside a date picker
const yearDropdown = page.locator('.ui-datepicker-year');
await yearDropdown.selectOption({ label: '2025' });
const monthDropdown = page.locator('.ui-datepicker-month');
// The actual option label on your practice page was 'Nov'
await monthDropdown.selectOption({ label: 'Nov' });Important debugging lesson
A selector can be correct while the requested option is wrong. Always inspect the actual DOM/options. Also be careful with loop conditions when waiting for a combined year + month state.
08. Drag, drop & range slider
Drag and drop
const source = page.locator('#drag-src');
const target = page.locator('#drag-tgt');
await source.dragTo(target);
const afterDropText = await target.innerText();
console.log(afterDropText);Range slider
const volumeSlider = page.locator('#volumeSlider');
await volumeSlider.evaluate((slider) => {
slider.value = 80;
});This is the simple DOM-property approach you practiced. Your class material did not add event dispatching to this exercise.
Reset-state verification
For drag/drop, capture the actual text value if you need to compare later:
const initialText = await source.innerText();
// after reset:
await expect(target).toHaveText('Drop Here\nRelease to drop');Conceptually: retrieve the text first; then assert using the retrieved string.
09. Frames / iframes
Why normal locators fail
page.locator() searches the main page DOM. An element inside an iframe belongs to that frame's DOM.
Frame locator
const frame = page.frameLocator('#loginFrame');
await frame
.locator('#username')
.fill('admin');Access a frame by URL
const frame1 = await page.frame({
url: 'https://ui.vision/demo/webtest/frames/frame_1'
});
await frame1
.getByRole('textbox')
.fill('STAD Solution');Nested frames
const frame3 = page.frame({ url: /frame_3/ });
await frame3
.locator('input[name="mytext3"]')
.fill('welcome');
const childFrames = frame3.childFrames();
const radioButton = childFrames[0]
.locator('//[@id="i5"]/div[3]/div');
await radioButton.check();
await expect(radioButton).toBeChecked();Memory model: main page → frame → child frame → element.
10. Multiple tabs / browser pages
Manual page creation
const browser = await chromium.launch(); const context = await browser.newContext(); const page1 = await context.newPage(); const page2 = await context.newPage(); const allPages = context.pages(); console.log(allPages.length);
Wait for a newly opened page
const newPagePromise = context.waitForEvent('page');
await page.getByText('New Tab').click();
const newPage = await newPagePromise;
await newPage.waitForLoadState();Multiple pages at the same time
const [page1, page2, page3] = await Promise.all([
context.waitForEvent('page'),
page.getByText('OrangeHRM, Inc').click(),
page.getByText('forget password').click()
]);Key idea: set up the event listener before the action that opens the page.
11. File upload & download
Single file upload
await page
.locator('#file-upload')
.setInputFiles('testdata/sample.pdf');The file path is relative to the project root in the exercise.
Multiple file upload
await page
.locator('#file-upload')
.setInputFiles([
'testdata/file1.pdf',
'testdata/file2.pdf'
]);The same setInputFiles() concept handles multiple files when the input supports it.
Download — the order matters
const downloadPromise = page.waitForEvent('download');
await page.getByText('Download').click();
const download = await downloadPromise;
console.log(download.suggestedFilename());
await download.saveAs('downloads/sample.pdf');12. Hooks
| Hook | When it runs | Typical use |
|---|---|---|
beforeAll | Once before tests in its scope | Suite-level setup |
beforeEach | Before every test | Navigate / reset test state |
afterEach | After every test | Cleanup / logging |
afterAll | Once after tests in its scope | Suite-level cleanup |
Best practical pattern
test.describe('Login Tests', () => {
test.beforeEach(async ({ page }) => {
await page.goto(LOGIN_URL);
});
test('valid login', async ({ page }) => {
// test
});
});Important distinction
Use beforeEach when each test needs a fresh page/setup. Do not try to use the test-scoped page fixture as though it were a suite-wide object in beforeAll.
13. Groups, describe & tags
test.describe()
test.describe('Login Module', () => {
test('valid login', async ({ page }) => {
// ...
});
test('invalid login', async ({ page }) => {
// ...
});
});Why groups matter
- Cleaner structure
- Selective execution
- Shared hooks
- Better debugging
- Scalability
- Team ownership
- Clearer reports
Tags and grep
test('Valid login @smoke', async ({ page }) => {
// ...
});
test('Full login validation @regression', async ({ page }) => {
// ...
});npx playwright test --grep "@smoke" npx playwright test -g "@smoke" npx playwright test --grep "@smoke|@regression" npx playwright test --grep-invert "@slow" npx playwright test --grep "@regression" --grep-invert "@slow"
AND logic for tags
npx playwright test --grep "(?=.*@smoke)(?=.*@regression)"
The class example used a regex lookahead to select tests carrying both tags.
14. Execution model
Sequential
Tests run one after another in the same worker flow. Useful when you deliberately want controlled ordering, but independent tests are generally easier to maintain.
Parallel
Independent tests can execute concurrently across workers. This can reduce total execution time, especially in CI.
Test isolation
Each test should be able to run independently. Avoid making one test depend on another test's state. This is also one of the major defenses against flaky tests.
15. Playwright reports
Why reports matter
- Quickly identify failing tests
- Share results with non-technical stakeholders
- Track regressions and trends
- Useful in CI/CD
HTML reporter
export default defineConfig({
reporter: 'html'
});Multiple reporters
export default defineConfig({
reporter: [
['html'],
['line'],
['json', { outputFile: 'results.json' }]
]
});CI-aware example from class
export default defineConfig({
reporter: process.env.CI
? [['github'], ['html', { open: 'never' }]]
: [['html'], ['line']]
});open: 'never' prevents the HTML report from automatically opening a browser in CI.
16. Flaky tests, retries & repeatEach
What is flaky?
Common causes
- Timing / synchronization
- Unstable or incorrect locators
- Slow API / network
- Dynamic test data
- Race conditions
- Tests depending on other tests
Retries
export default defineConfig({
retries: 2
});Retry runs a test again when it fails. If it passes on retry, the result can be reported as flaky.
repeatEach
Repeats every test, including tests that pass, mainly to investigate instability.
Retries vs repeatEach
| Feature | retries | repeatEach |
|---|---|---|
| Purpose | Retry failed tests | Repeat every test |
| Passing test rerun? | No | Yes |
| Failed test rerun? | Yes | Yes |
| Flaky investigation | Useful | Very useful |
| Typical use | CI/CD | Investigation |
17. OrangeHRM project
Basic login coverage you executed
- Login page
- Login UI elements
- Valid credentials
- Invalid username
- Invalid password
- Invalid username + password
- Empty username
- Empty password
- Empty username + password
- Forgot password
Professional grouping direction
- Smoke: availability, essential UI, valid login, forgot-password navigation
- Regression: validations, whitespace, masking, refresh/state, URL leakage, boundary behavior
- Security / robustness: harmless injection/XSS checks and resilience observations
What you learned from your actual failures
- getByText('Dashboard').nth(2) was wrong because there were not three matching elements. A heading role was more precise.
- getByText('Reset Password') matched both heading and button. Use a more specific role/name when strict mode finds multiple matches.
- URL assertions can pass even when a later UI assertion fails.
18. Your actual debugging lessons
1. Locator ≠ element state
Creating a locator does not mean the element is visible, selected, or ready. Use the appropriate assertion/state query.
2. Retrieve vs assert
inputValue() gets a value; toHaveValue() checks it. Same idea for count() vs toHaveCount().
3. Missing await
If a method returns a Promise and you forget await, you may compare a Promise instead of the actual result.
4. Wrong expected type
toHaveText() expects a string/regex, not a Locator object.
5. Strict mode
Multiple matches can make Playwright reject an ambiguous locator. Make the locator more specific instead of blindly adding positions.
6. CSS selector syntax
Classes require dots; attribute selectors need correct quoting/brackets. A malformed selector can fail before the browser even searches for the element.
7. Actual DOM beats assumptions
Your date-picker failure showed that the actual option label was Nov, not November. Inspect the page instead of guessing.
8. Event listener order
For downloads and new pages, register the event listener/promise before triggering the action.
19. Interview quick-fire
Core
How does it differ from an action?
What happens if you omit it?
Why is an action alone not enough?
Controls
Framework
Advanced practical
⚡ 60-second final memory sheet
| Topic | Remember this |
|---|---|
| Locator | FIND the element |
| Action | DO what the user does |
| Assertion | PROVE the expected result |
| Textbox | VALUE |
| Radio | ONE choice |
| Checkbox | MANY choices |
| Native dropdown | selectOption() |
| Auto-suggestion | TYPE → FIND → CLICK |
| Frame | Separate DOM boundary |
| New tab/page | Listen for page event before action |
| Download | Listen → click → await → save/inspect |
| Hooks | beforeAll / beforeEach / afterEach / afterAll |
| Groups | test.describe() |
| Tags | --grep / --grep-invert |
| Flaky | Same test, different result without relevant code change |
| retries | Retry failed tests |
| repeatEach | Repeat every test to investigate instability |
| Reports | Turn execution results into readable evidence |