Playwright: Interacting with Input
Input fields show up everywhere: search boxes, login forms, checkout steps, settings panels, and admin tools. If you are learning Playwright, input testing is one of the fastest ways to build useful automation habits because it combines locating elements, entering values, and asserting visible results.
This guide focuses on one Codemo playground feature: General Input. It covers common input behavior in one page, including typing, clearing, disabled and read-only states, validation, password masking, Enter key actions, and whitespace handling.
Why Test Input Interactions?
Input tests help confirm that a page behaves the way users expect. They are useful for checking that:
- users can enter and update values correctly
- empty states are handled safely
- disabled and read-only fields behave as intended
- validation messages appear at the right time
- keyboard interactions such as pressing Enter work correctly
- constrained inputs such as numeric-only or max-length fields enforce their rules
What The Codemo Input Playground Covers
The input playground is built around practical scenarios that map closely to real UI behavior. The page includes cases for:
- standard typing
- clearing a field
- disabled state
- read-only state
- character limits
- numeric-only input
- placeholder text
- Enter key submission
- required validation
- password masking
- leading and trailing whitespace
That makes it a good place to practice real interaction patterns without needing a larger application setup.
Finding Inputs With Playwright Locators
Playwright gives you several reliable ways to find inputs. For this playground, placeholder text is the most direct choice because each scenario uses a clear placeholder.
import { test, expect } from "@playwright/test"
test("types into an input field", async ({ page }) => {
await page.goto("https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Type something...")
await input.fill("Playwright")
await expect(input).toHaveValue("Playwright")
await expect(page.getByText("Current value: Playwright")).toBeVisible()
})
Useful locator strategies for inputs include:
getByPlaceholder()for fields with clear placeholder textgetByRole("textbox")for text inputs and textareasgetByLabel()when the form exposes accessible labels
getByPlaceholder() is the fastest route because each case is intentionally designed with distinct placeholder text.Testing Standard Typing
The most basic scenario is making sure a user can type into a field and see the value reflected correctly.
test("input should accept standard typing", async ({ page }) => {
await page.goto("https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Type something...")
await input.fill("Playwright")
await expect(input).toHaveValue("Playwright")
await expect(page.getByText("Current value: Playwright")).toBeVisible()
})
This scenario teaches the core loop you will reuse constantly in Playwright tests:
- locate the field
- enter a value
- assert the UI state that proves the interaction worked
Testing Clear Field Behavior
Clearing a field is as important as filling it. Empty values often affect validation, submission logic, and conditional UI.
test("input should clear the field", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Clear this field")
await input.clear()
await expect(input).toHaveValue("")
await expect(page.getByTestId("playground-case-footer-clear-field")).toHaveText("Current value: (empty)")
})
Testing Disabled And Read-Only Inputs
These two states look similar in a UI, but they behave differently.
- a disabled input cannot be edited or interacted with normally
- a read-only input can still be focused and selected, but its value should not change
Disabled State
test("input should stay disabled", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("This field is disabled")
await expect(input).toBeDisabled()
await expect(input).toHaveValue("Locked Value")
})
Read-Only State
test("input should keep its read-only value", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("This field is read-only")
await expect(input).toHaveAttribute("readonly", "")
await expect(input).toHaveValue("Read-only Value")
})
Testing Character Limits And Numeric-Only Inputs
Some fields enforce additional rules beyond plain typing. The playground includes both a max-length input and a numeric-only input.
Character Limit
test("input should respect the character limit", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Max 10 characters")
await input.fill("12345678901")
await expect(input).toHaveValue("1234567890")
await expect(page.getByTestId("playground-case-footer-character-limit")).toHaveText("10/10 characters")
})
Numeric-Only Input
test("input should accept numeric values", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Enter numbers only")
await input.fill("42")
await expect(input).toHaveValue("42")
await expect(page.getByTestId("playground-case-footer-numerical-only")).toContainText("Current value: 42")
})
These scenarios help you think beyond happy paths and verify the rules attached to a field.
Testing Placeholder Text
Placeholder text helps guide the user before typing starts. This case verifies both the empty state and the post-input state.
test("input should update placeholder visibility", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("I am the placeholder")
await expect(page.getByTestId("playground-case-footer-placeholder-text")).toHaveText("Placeholder visible: Yes")
await input.fill("Hello")
await expect(input).toHaveValue("Hello")
await expect(page.getByTestId("playground-case-footer-placeholder-text")).toHaveText("Placeholder visible: No")
})
Testing Enter Key Actions
Many forms trigger a primary action when the user presses Enter. This playground includes a dedicated case for that interaction.
test("input should submit on Enter", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Type and press Enter")
await input.fill("Playwright")
await input.press("Enter")
await expect(page.getByTestId("playground-case-footer-enter-key-action")).toHaveText("Submitted values: 1")
})
This is a good reminder that keyboard behavior matters just as much as clicks.
Testing Required Validation
Validation is one of the most common reasons to write input tests. In this playground, the required-field scenario uses a validate button to trigger the form rule.
test("input should show required validation", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Enter something...")
const validateButton = page.getByTestId("playground-case-button-required-validation-validate")
await validateButton.click()
await expect(page.getByTestId("playground-case-footer-required-validation")).toContainText("This field is required")
await input.fill("Valid input")
await validateButton.click()
await expect(page.getByTestId("playground-case-footer-required-validation")).toContainText("Status: Valid")
})
Testing Password Masking
Password fields should hide typed characters and expose the correct input type.
test("input should use password masking", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Enter password")
await expect(input).toHaveAttribute("type", "password")
await input.fill("secret")
await expect(input).toHaveValue("secret")
})
Testing Leading And Trailing Spaces
Whitespace bugs are easy to miss in manual testing. This scenario checks whether the UI correctly detects values with spaces at the start or end.
test("input should detect leading and trailing spaces", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Try spaces at start/end")
await input.fill(" Playwright ")
await expect(input).toHaveValue(" Playwright ")
await expect(page.getByText("Trimmed value: Playwright")).toBeVisible()
await expect(page.getByText("Has leading/trailing spaces")).toBeVisible()
await page.getByTestId("playground-case-button-leading/trailing-spaces-validate").click()
await expect(page.getByRole("button", { name: "1/11 Done" })).toBeVisible()
})
Practice With Codemo Playground
Codemo provides an interactive input testing playground where you can practice common input interactions one case at a time. Each scenario gives you a focused behavior to validate, which makes it easier to build confidence without jumping between unrelated UI patterns.
Try these challenges:
- Type into a standard field and verify the current value text updates.
- Clear a pre-filled input and confirm the field returns to an empty state.
- Check a disabled input and confirm its locked value stays unchanged.
- Trigger required validation with an empty field, then make it pass.
- Press Enter inside the submission case and confirm the counter increments.
- Fill the whitespace case and validate that the warning text appears.
Complete Test Example
import { test, expect } from "@playwright/test"
test.describe("Playground - General UI - Input", () => {
test("input should accept standard typing", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Type something...")
await input.fill("Playwright")
await expect(input).toHaveValue("Playwright")
await expect(page.getByText("Current value: Playwright")).toBeVisible()
})
test("input should clear the field", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Clear this field")
await input.clear()
await expect(input).toHaveValue("")
await expect(page.getByTestId("playground-case-footer-clear-field")).toHaveText("Current value: (empty)")
})
test("input should stay disabled", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("This field is disabled")
await expect(input).toBeDisabled()
await expect(input).toHaveValue("Locked Value")
})
test("input should keep its read-only value", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("This field is read-only")
await expect(input).toHaveAttribute("readonly", "")
await expect(input).toHaveValue("Read-only Value")
})
test("input should respect the character limit", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Max 10 characters")
await input.fill("12345678901")
await expect(input).toHaveValue("1234567890")
await expect(page.getByTestId("playground-case-footer-character-limit")).toHaveText("10/10 characters")
})
test("input should accept numeric values", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Enter numbers only")
await input.fill("42")
await expect(input).toHaveValue("42")
await expect(page.getByTestId("playground-case-footer-numerical-only")).toContainText("Current value: 42")
})
test("input should update placeholder visibility", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("I am the placeholder")
await expect(page.getByTestId("playground-case-footer-placeholder-text")).toHaveText("Placeholder visible: Yes")
await input.fill("Hello")
await expect(input).toHaveValue("Hello")
await expect(page.getByTestId("playground-case-footer-placeholder-text")).toHaveText("Placeholder visible: No")
})
test("input should submit on Enter", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Type and press Enter")
await input.fill("Playwright")
await input.press("Enter")
await expect(page.getByTestId("playground-case-footer-enter-key-action")).toHaveText("Submitted values: 1")
})
test("input should show required validation", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Enter something...")
const validateButton = page.getByTestId("playground-case-button-required-validation-validate")
await validateButton.click()
await expect(page.getByTestId("playground-case-footer-required-validation")).toContainText("This field is required")
await input.fill("Valid input")
await validateButton.click()
await expect(page.getByTestId("playground-case-footer-required-validation")).toContainText("Status: Valid")
})
test("input should use password masking", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Enter password")
await expect(input).toHaveAttribute("type", "password")
await input.fill("secret")
await expect(input).toHaveValue("secret")
})
test("input should detect leading and trailing spaces", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Try spaces at start/end")
await input.fill(" Playwright ")
await expect(input).toHaveValue(" Playwright ")
await expect(page.getByText("Trimmed value: Playwright")).toBeVisible()
await expect(page.getByText("Has leading/trailing spaces")).toBeVisible()
await page.getByTestId("playground-case-button-leading/trailing-spaces-validate").click()
await expect(page.getByRole("button", { name: "1/11 Done" })).toBeVisible()
})
test("input assertions", async ({ page }) => {
await page.goto("/https://codemo.dev/playground/general/input/")
const input = page.getByPlaceholder("Type something...")
await expect(input).toBeVisible()
await expect(input).toBeEnabled()
await expect(input).toHaveAttribute("placeholder", "Type something...")
await expect(page.getByPlaceholder("This field is disabled")).toBeDisabled()
await expect(page.getByPlaceholder("Enter password")).toHaveAttribute("type", "password")
})
})
Key Takeaways
- Prefer stable locators such as
getByPlaceholder(),getByRole("textbox"), andgetByLabel(). - Use
fill()for full value replacement andpress()for keyboard-driven actions. - Assert both the field value and the surrounding UI feedback.
- Test behavior differences such as disabled, read-only, password, and validation states.
- Include edge cases such as whitespace handling and length restrictions.