Day 4: Page Navigation and Forms
What You Will Learn Today
- page.goto() and navigation options (waitUntil)
- page.goBack(), page.goForward(), page.reload()
- Filling forms: fill(), clear(), pressSequentially()
- Checkboxes: check(), uncheck()
- Radio buttons
- Select dropdowns: selectOption()
- File upload: setInputFiles()
- File download handling
- Dialog handling (alert, confirm, prompt)
- Keyboard and mouse actions (press, click, dblclick, hover, dragTo)
Page Navigation
page.goto() Basics
page.goto() navigates to the specified URL. It returns a response object that you can inspect.
import { test, expect } from '@playwright/test';
test('basic navigation', async ({ page }) => {
// Basic navigation
await page.goto('https://example.com');
// Get the response object
const response = await page.goto('https://example.com/api');
console.log(response?.status()); // 200
});
The waitUntil Option
The waitUntil option controls when navigation is considered complete.
test('waitUntil options', async ({ page }) => {
// Default: 'load' - window.onload event fires
await page.goto('https://example.com');
// Wait until DOM content is loaded (faster)
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded'
});
// Wait until there are no network connections for 500ms
await page.goto('https://example.com', {
waitUntil: 'networkidle'
});
// Don't wait - just start navigation
await page.goto('https://example.com', {
waitUntil: 'commit'
});
});
| Option | Description | Use Case |
|---|---|---|
load |
Waits for the load event (default) |
General pages |
domcontentloaded |
Waits for DOMContentLoaded event | When you want to start interacting quickly |
networkidle |
Waits until no network connections for 500ms | SPAs or pages with many API calls |
commit |
Waits until response is received | Minimal waiting |
Setting Timeouts
test('navigation with timeout', async ({ page }) => {
await page.goto('https://slow-site.com', {
timeout: 60000 // 60 seconds
});
});
Browser Navigation
test('browser navigation', async ({ page }) => {
await page.goto('https://example.com/page1');
await page.goto('https://example.com/page2');
// Go back to page1
await page.goBack();
await expect(page).toHaveURL(/page1/);
// Go forward to page2
await page.goForward();
await expect(page).toHaveURL(/page2/);
// Reload the current page
await page.reload();
await expect(page).toHaveURL(/page2/);
});
Form Input
fill() - Basic Text Input
fill() clears the existing value and sets the new one in a single operation.
test('fill text inputs', async ({ page }) => {
await page.goto('https://example.com/form');
// Fill text input
await page.getByLabel('Username').fill('testuser');
// Fill email input
await page.getByLabel('Email').fill('test@example.com');
// Fill password input
await page.getByLabel('Password').fill('SecurePass123!');
// Fill textarea
await page.getByLabel('Bio').fill('Hello, I am a test user.\nNice to meet you.');
});
clear() - Clearing Input Values
test('clear input', async ({ page }) => {
await page.goto('https://example.com/form');
const input = page.getByLabel('Username');
await input.fill('testuser');
// Clear the input
await input.clear();
await expect(input).toHaveValue('');
});
pressSequentially() - Typing One Character at a Time
pressSequentially() simulates typing each character individually via the keyboard. This is useful for testing autocomplete or real-time validation.
test('press sequentially for autocomplete', async ({ page }) => {
await page.goto('https://example.com/search');
// Type one character at a time with delay
await page.getByLabel('Search').pressSequentially('playwright', {
delay: 100 // 100ms delay between each keystroke
});
// Wait for autocomplete suggestions
await expect(page.getByRole('listbox')).toBeVisible();
});
fill() vs pressSequentially():
fill()sets the value directly and is much faster.pressSequentially()fires individual key events, making it suitable for testing UIs that respond to input events in real time.
Checkboxes and Radio Buttons
check() and uncheck()
test('checkboxes', async ({ page }) => {
await page.goto('https://example.com/settings');
// Check a checkbox
await page.getByLabel('Enable notifications').check();
await expect(page.getByLabel('Enable notifications')).toBeChecked();
// Uncheck a checkbox
await page.getByLabel('Enable notifications').uncheck();
await expect(page.getByLabel('Enable notifications')).not.toBeChecked();
// check() is idempotent - does nothing if already checked
await page.getByLabel('Accept terms').check();
await page.getByLabel('Accept terms').check(); // No error
});
Radio Buttons
test('radio buttons', async ({ page }) => {
await page.goto('https://example.com/survey');
// Select a radio button
await page.getByLabel('Monthly plan').check();
await expect(page.getByLabel('Monthly plan')).toBeChecked();
// Select a different radio button in the same group
await page.getByLabel('Annual plan').check();
await expect(page.getByLabel('Annual plan')).toBeChecked();
await expect(page.getByLabel('Monthly plan')).not.toBeChecked();
});
Select Dropdowns
selectOption()
test('select dropdowns', async ({ page }) => {
await page.goto('https://example.com/form');
// Select by value
await page.getByLabel('Country').selectOption('jp');
// Select by label text
await page.getByLabel('Country').selectOption({ label: 'Japan' });
// Select by index
await page.getByLabel('Country').selectOption({ index: 2 });
// Multiple selection (for <select multiple>)
await page.getByLabel('Languages').selectOption(['en', 'ja', 'ko']);
});
Verifying Selected Values
test('verify selected option', async ({ page }) => {
await page.goto('https://example.com/form');
await page.getByLabel('Country').selectOption('jp');
await expect(page.getByLabel('Country')).toHaveValue('jp');
});
File Upload
setInputFiles()
test('file upload', async ({ page }) => {
await page.goto('https://example.com/upload');
// Upload a single file
await page.getByLabel('Profile picture').setInputFiles('tests/fixtures/avatar.png');
// Upload multiple files
await page.getByLabel('Documents').setInputFiles([
'tests/fixtures/doc1.pdf',
'tests/fixtures/doc2.pdf'
]);
// Clear file selection
await page.getByLabel('Profile picture').setInputFiles([]);
});
Uploading from a Buffer
When you don't want to prepare actual files, you can use buffers instead.
test('upload from buffer', async ({ page }) => {
await page.goto('https://example.com/upload');
await page.getByLabel('CSV file').setInputFiles({
name: 'data.csv',
mimeType: 'text/csv',
buffer: Buffer.from('name,age\nAlice,30\nBob,25')
});
});
File Download
Listening for the download Event
import { test, expect } from '@playwright/test';
import path from 'path';
test('file download', async ({ page }) => {
await page.goto('https://example.com/downloads');
// Start waiting for download before clicking
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'Download Report' }).click();
const download = await downloadPromise;
// Verify the file name
expect(download.suggestedFilename()).toBe('report.pdf');
// Save the file to a specific path
await download.saveAs(path.join('tests/downloads', download.suggestedFilename()));
});
Dialog Handling
In Playwright, you handle browser dialogs (alert, confirm, prompt) with page.on('dialog'). You must register the handler before the dialog appears.
alert
test('handle alert', async ({ page }) => {
await page.goto('https://example.com');
page.on('dialog', async dialog => {
expect(dialog.type()).toBe('alert');
expect(dialog.message()).toBe('Operation completed!');
await dialog.accept();
});
await page.getByRole('button', { name: 'Show Alert' }).click();
});
confirm
test('handle confirm - accept', async ({ page }) => {
await page.goto('https://example.com');
page.on('dialog', async dialog => {
expect(dialog.type()).toBe('confirm');
await dialog.accept(); // Click OK
});
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByText('Item deleted')).toBeVisible();
});
test('handle confirm - dismiss', async ({ page }) => {
await page.goto('https://example.com');
page.on('dialog', async dialog => {
await dialog.dismiss(); // Click Cancel
});
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByText('Item deleted')).not.toBeVisible();
});
prompt
test('handle prompt', async ({ page }) => {
await page.goto('https://example.com');
page.on('dialog', async dialog => {
expect(dialog.type()).toBe('prompt');
expect(dialog.defaultValue()).toBe('');
await dialog.accept('My Answer'); // Enter text and click OK
});
await page.getByRole('button', { name: 'Enter Name' }).click();
await expect(page.getByText('Hello, My Answer')).toBeVisible();
});
Keyboard Actions
press() - Special Keys
test('keyboard actions', async ({ page }) => {
await page.goto('https://example.com/editor');
const editor = page.getByRole('textbox');
await editor.fill('Hello World');
// Press Enter
await editor.press('Enter');
// Press keyboard shortcuts
await editor.press('Control+a'); // Select all
await editor.press('Control+c'); // Copy
await editor.press('End');
await editor.press('Control+v'); // Paste
// Press Escape
await page.press('body', 'Escape');
// Press Tab to move focus
await editor.press('Tab');
});
Common Key Names
| Key | Value |
|---|---|
| Enter | Enter |
| Tab | Tab |
| Escape | Escape |
| Backspace | Backspace |
| Delete | Delete |
| Arrow keys | ArrowUp, ArrowDown, ArrowLeft, ArrowRight |
| Modifier keys | Control, Shift, Alt, Meta |
Mouse Actions
Click Variations
test('mouse click variations', async ({ page }) => {
await page.goto('https://example.com');
// Standard click
await page.getByRole('button', { name: 'Submit' }).click();
// Double click
await page.getByText('Editable text').dblclick();
// Right click (context menu)
await page.getByText('Right click me').click({ button: 'right' });
// Click with modifier keys
await page.getByRole('link', { name: 'Open' }).click({ modifiers: ['Control'] });
// Click at specific position within the element
await page.getByTestId('canvas').click({ position: { x: 100, y: 200 } });
});
Hover
test('hover action', async ({ page }) => {
await page.goto('https://example.com');
await page.getByText('Hover me').hover();
await expect(page.getByText('Tooltip content')).toBeVisible();
});
Drag and Drop
test('drag and drop', async ({ page }) => {
await page.goto('https://example.com/kanban');
// Drag source to target
const source = page.getByText('Task 1');
const target = page.getByTestId('done-column');
await source.dragTo(target);
await expect(target).toContainText('Task 1');
});
Putting It All Together: Registration Form Test
Here is a practical test combining everything covered today.
import { test, expect } from '@playwright/test';
test.describe('User Registration Form', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com/register');
});
test('complete registration with all fields', async ({ page }) => {
// Text inputs
await page.getByLabel('Full Name').fill('Taro Yamada');
await page.getByLabel('Email').fill('taro@example.com');
await page.getByLabel('Password').fill('SecurePass123!');
await page.getByLabel('Confirm Password').fill('SecurePass123!');
// Select dropdown
await page.getByLabel('Country').selectOption({ label: 'Japan' });
// Radio button
await page.getByLabel('Monthly plan').check();
// Checkboxes
await page.getByLabel('Technology').check();
await page.getByLabel('Music').check();
// File upload
await page.getByLabel('Avatar').setInputFiles({
name: 'avatar.png',
mimeType: 'image/png',
buffer: Buffer.from('fake-image-data')
});
// Terms agreement
await page.getByLabel('I agree to the terms').check();
// Submit
await page.getByRole('button', { name: 'Register' }).click();
// Verify success
await expect(page).toHaveURL(/\/welcome/);
await expect(page.getByText('Registration complete')).toBeVisible();
});
test('shows validation errors for empty required fields', async ({ page }) => {
await page.getByRole('button', { name: 'Register' }).click();
await expect(page.getByText('Name is required')).toBeVisible();
await expect(page.getByText('Email is required')).toBeVisible();
});
});
Summary
| Category | Method | Purpose |
|---|---|---|
| Navigation | page.goto(url, options) |
Navigate to a page |
| Navigation | page.goBack() |
Go back to the previous page |
| Navigation | page.goForward() |
Go forward to the next page |
| Navigation | page.reload() |
Reload the current page |
| Text Input | locator.fill(value) |
Set value (clears existing) |
| Text Input | locator.clear() |
Clear the input value |
| Text Input | locator.pressSequentially(text) |
Type one character at a time |
| Checkbox | locator.check() |
Check a checkbox or radio |
| Checkbox | locator.uncheck() |
Uncheck a checkbox |
| Select | locator.selectOption(value) |
Select a dropdown option |
| File | locator.setInputFiles(files) |
Upload files |
| Download | page.waitForEvent('download') |
Wait for a download |
| Dialog | page.on('dialog', handler) |
Handle browser dialogs |
| Keyboard | locator.press(key) |
Press a key |
| Mouse | locator.click() |
Click |
| Mouse | locator.dblclick() |
Double-click |
| Mouse | locator.hover() |
Hover |
| Mouse | locator.dragTo(target) |
Drag and drop |
Key Takeaways
- Choose waitUntil wisely - Use
networkidlefor SPAs,domcontentloadedfor faster tests - Prefer fill() by default - Use
pressSequentially()only for specific cases like autocomplete - Register dialog handlers first - Set up
page.on('dialog')before the dialog appears - Use the Promise pattern for downloads - Call
waitForEventbefore clicking the download link - check() is idempotent - It won't throw an error if the checkbox is already checked
Practice Exercises
Basic
- Open a page with
page.goto()usingwaitUntil: 'domcontentloaded'and compare the speed withnetworkidle - Write a navigation test using
page.goBack()andpage.goForward() - Input the same text with both
fill()andpressSequentially()and observe the difference
Intermediate
- Create a test that interacts with select dropdowns, checkboxes, and radio buttons
- Write a file upload test using
setInputFiles()with a buffer - Handle all three dialog types (alert, confirm, prompt) in separate tests
Challenge
- Create a drag-and-drop UI test
- Write a test that downloads a file and verifies its contents
References
Next Preview
In Day 5, we will learn about Assertions and Snapshots. We will cover the rich set of expect() matchers, auto-retry mechanisms, and visual regression testing with screenshot comparison to make your tests more reliable and robust.