Almost every business application contains forms. Examples include login forms, registration forms, customer creation, checkout, employee onboarding and search forms.
Text Input
Enter names, email addresses, usernames, addresses and other text values.
Password
Enter passwords securely using accessible field locators.
Dropdown
Select country, state, department, category or status.
Checkbox
Select options such as terms and conditions or permissions.
Radio Button
Select one option from a group of choices.
File Upload
Upload documents, profile images, invoices and resumes.
A professional Playwright test should model the real user workflow rather than interacting with fields using fragile CSS or XPath selectors whenever better locators are available.
The most common form operation is entering text.
Playwright provides fill() for this purpose.
page.get_by_label("First Name").fill("John")
page.get_by_label("Last Name").fill("Smith")
page.get_by_label("Email").fill("john@example.com")
fill() clears the existing value and enters the
requested text. This makes test setup straightforward and
deterministic.
Imagine a CRM application where a new customer must be registered.
from playwright.sync_api import expect
page.goto("https://crm.example.test/customers/new")
page.get_by_label("First Name").fill("John")
page.get_by_label("Last Name").fill("Smith")
page.get_by_label("Email").fill("john.smith@example.com")
page.get_by_label("Phone").fill("9876543210")
page.get_by_role(
"button",
name="Create Customer"
).click()
expect(
page.get_by_text("Customer created successfully")
).to_be_visible()
Password fields can be handled using the same accessible locator strategy.
page.get_by_label("Password").fill("MySecret123!")
page.get_by_label(
"Confirm Password"
).fill("MySecret123!")
Use check() and uncheck() for checkbox
controls.
page.get_by_label(
"I agree to the Terms and Conditions"
).check()
page.get_by_label(
"Subscribe to newsletter"
).check()
Validate Checkbox State
from playwright.sync_api import expect
terms = page.get_by_label(
"I agree to the Terms and Conditions"
)
terms.check()
expect(terms).to_be_checked()
Radio buttons are useful when the user must select exactly one option.
page.get_by_label("Male").check()
page.get_by_label("Female").check()
For a native HTML <select> element,
Playwright provides select_option().
page.get_by_label("Country").select_option("IN")
page.get_by_label("Department").select_option(
label="Engineering"
)
select_option() is for native HTML
<select> elements. A custom JavaScript dropdown
may behave like a button/listbox instead, in which case you should
interact with its actual accessible UI.
Modern applications frequently use custom dropdown components. These are not always native HTML select elements.
page.get_by_role(
"combobox",
name="Country"
).click()
page.get_by_role(
"option",
name="India"
).click()
The exact locator should match the application's accessibility structure.
Date fields can be handled according to the type of control used by the application.
page.get_by_label("Date of Birth").fill(
"1990-05-15"
)
Playwright provides a direct API for file uploads.
page.get_by_label("Resume").set_input_files(
"test-data/resume.pdf"
)
Real-Time Example — Employee Onboarding
page.goto(
"https://hr.example.test/employees/new"
)
page.get_by_label("First Name").fill("John")
page.get_by_label("Last Name").fill("Smith")
page.get_by_label("Resume").set_input_files(
"test-data/john-smith-resume.pdf"
)
page.get_by_role(
"button",
name="Create Employee"
).click()
Multi-line text fields can also use fill().
page.get_by_label("Description").fill(
"Customer requested a priority callback."
)
Playwright's fill() replaces the existing value.
This often removes the need for a separate clear operation.
email = page.get_by_label("Email")
email.fill("old@example.com")
email.fill("new@example.com")
The second fill() replaces the existing value with
the new value.
❌ Avoid When Better Locators Exist
page.locator(
"#root > div:nth-child(2) > input"
).fill("John")This selector depends heavily on the page's DOM structure. A harmless UI change can break the test.
✅ Preferred
page.get_by_label(
"First Name"
).fill("John")This communicates what field the test is using and is generally easier to maintain.
A good locator strategy is one of the biggest factors in creating maintainable Playwright form tests.
| Locator | Typical Use | Example |
|---|---|---|
get_by_label() |
Form fields | get_by_label("Email") |
get_by_role() |
Buttons, checkboxes, radios, comboboxes | get_by_role("button", name="Save") |
get_by_placeholder() |
Inputs where placeholder is meaningful | get_by_placeholder("Enter email") |
get_by_test_id() |
Stable application test identifiers | get_by_test_id("email-input") |
locator() |
More specific CSS/XPath or complex locator needs | locator("input[name='email']") |
A professional test should validate not only that the form was filled, but also that the application responds correctly.
❌ Weak Validation
page.get_by_label(
"Email"
).fill("invalid-email")
page.get_by_role(
"button",
name="Submit"
).click()
# No validation✅ Strong Validation
page.get_by_label(
"Email"
).fill("invalid-email")
page.get_by_role(
"button",
name="Submit"
).click()
expect(
page.get_by_text(
"Enter a valid email address"
)
).to_be_visible()Consider an e-commerce checkout form containing customer details, address, country, payment information and terms.
from playwright.sync_api import expect
page.goto(
"https://shop.example.test/checkout"
)
# Customer details
page.get_by_label("First Name").fill("John")
page.get_by_label("Last Name").fill("Smith")
page.get_by_label("Email").fill(
"john@example.com"
)
# Address
page.get_by_label("Address").fill(
"123 Main Street"
)
page.get_by_label("City").fill(
"Pune"
)
page.get_by_label("Postal Code").fill(
"411001"
)
# Country
page.get_by_label("Country").select_option("IN")
# Terms
terms = page.get_by_label(
"I agree to the Terms and Conditions"
)
terms.check()
expect(terms).to_be_checked()
# Submit
page.get_by_role(
"button",
name="Place Order"
).click()
expect(
page.get_by_text("Order placed successfully")
).to_be_visible()
Form automation becomes more robust when the test validates the result using web-first assertions.
page.get_by_label("Email").fill(
"customer@example.com"
)
page.get_by_label("Password").fill(
"secret"
)
page.get_by_role(
"button",
name="Sign In"
).click()
expect(page).to_have_url(
r".*/dashboard"
)
expect(
page.get_by_role(
"heading",
name="Dashboard"
)
).to_be_visible()
Both Playwright and Selenium can automate forms. The practical differences are mainly around API design, locator capabilities, synchronization and the surrounding test ecosystem.
| Form Operation | Playwright | Selenium |
|---|---|---|
| Text input | locator.fill() |
send_keys() |
| Checkbox | check() |
click() / element interaction |
| Radio button | check() |
click() |
| Native select | select_option() |
Select helper |
| File upload | set_input_files() |
send_keys() is commonly used with file inputs |
| Form assertions | Web-first expect() assertions |
Assertions depend on the test framework and synchronization approach |
| Locator model | Role, label, text, test id and locator APIs | CSS, XPath and WebDriver locator strategies |
| Synchronization | Strong automatic waiting around locator actions and web-first assertions | Explicit waits are commonly used when application state requires synchronization |
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get(
"https://example.test/register"
)
driver.find_element(
By.ID,
"firstName"
).send_keys("John")
driver.find_element(
By.ID,
"lastName"
).send_keys("Smith")
driver.find_element(
By.ID,
"email"
).send_keys("john@example.com")
driver.find_element(
By.ID,
"password"
).send_keys("secret")
driver.find_element(
By.ID,
"register"
).click()
driver.quit()
from playwright.sync_api import expect
page.goto(
"https://example.test/register"
)
page.get_by_label(
"First Name"
).fill("John")
page.get_by_label(
"Last Name"
).fill("Smith")
page.get_by_label(
"Email"
).fill("john@example.com")
page.get_by_label(
"Password"
).fill("secret")
page.get_by_role(
"button",
name="Register"
).click()
expect(
page.get_by_text(
"Registration successful"
)
).to_be_visible()
1. Accessible Locators
Locators such as get_by_label() and
get_by_role() make form automation easier
to understand.
2. Automatic Waiting
Playwright waits for relevant actionability conditions before many locator interactions.
3. Web-First Assertions
Form results can be validated using retrying assertions.
4. File Upload API
File input handling can be performed directly with
set_input_files().
5. Modern Web Support
Playwright is designed around modern browser automation scenarios including dynamic applications.
6. Less Timing Code
A well-designed Playwright test can avoid many manual timing workarounds.
❌ Avoid
page.get_by_label(
"Email"
).fill("john@example.com")
page.get_by_role(
"button",
name="Save"
).click()
page.wait_for_timeout(3000)
assert "success" in page.content()✅ Preferred
page.get_by_label(
"Email"
).fill("john@example.com")
page.get_by_role(
"button",
name="Save"
).click()
expect(
page.get_by_text(
"Customer saved successfully"
)
).to_be_visible()from playwright.sync_api import Page, expect
def test_create_customer(page: Page):
# Arrange
page.goto(
"https://crm.example.test/customers/new"
)
# Fill customer details
page.get_by_label(
"First Name"
).fill("John")
page.get_by_label(
"Last Name"
).fill("Smith")
page.get_by_label(
"Email"
).fill("john.smith@example.com")
page.get_by_label(
"Phone"
).fill("9876543210")
# Address
page.get_by_label(
"Address"
).fill("123 Main Street")
page.get_by_label(
"City"
).fill("Pune")
page.get_by_label(
"Country"
).select_option("IN")
# Terms
terms = page.get_by_label(
"I agree to the Terms and Conditions"
)
terms.check()
expect(terms).to_be_checked()
# Submit
page.get_by_role(
"button",
name="Create Customer"
).click()
# Validate business result
expect(
page.get_by_text(
"Customer created successfully"
)
).to_be_visible()
expect(page).to_have_url(
r".*/customers/.*"
)
-
Prefer
get_by_label()for labeled form fields. -
Prefer
get_by_role()for buttons, checkboxes, radios and other accessible controls. -
Use
fill()for normal text input. -
Use
check()anduncheck()for checkboxes. -
Use
select_option()for native select elements. -
Use
set_input_files()for file input controls. - Validate the submitted result, not only the interaction.
- Avoid brittle CSS and XPath when a better semantic locator is available.
- Avoid hard waits for normal form synchronization.
- Keep real credentials and secrets outside the test source.
- Use deterministic test data.
- Keep each test focused on a meaningful business scenario.
Find → Type → Click
It is:
Locate with a stable locator → Enter realistic data → Submit → Validate the actual application result
This is where Playwright can provide a practical advantage for modern form automation: its locator model, actionability checks, and web-first assertions allow the test to focus more on the business workflow and less on manually managing browser timing.