Playwright: Interacting with Input

Learn how to test input interactions in Playwright, including typing, validation, disabled states, and Enter key actions.
Ryan | Aug 30, 2026

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.

Playground - General UI - Input

Click here to practice input interactions in the Codemo Playground.

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 text
  • getByRole("textbox") for text inputs and textareas
  • getByLabel() when the form exposes accessible labels
On this playground, 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.

tests/playground/general-ui/input.spec.ts
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:

  1. locate the field
  2. enter a value
  3. 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.

tests/playground/general-ui/input.spec.ts
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

tests/playground/general-ui/input.spec.ts
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

tests/playground/general-ui/input.spec.ts
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")
})
These cases are useful because they test actual browser behavior, not just visual styling.

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

tests/playground/general-ui/input.spec.ts
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

tests/playground/general-ui/input.spec.ts
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.

tests/playground/general-ui/input.spec.ts
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.

tests/playground/general-ui/input.spec.ts
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.

tests/playground/general-ui/input.spec.ts
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.

tests/playground/general-ui/input.spec.ts
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.

tests/playground/general-ui/input.spec.ts
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()
})
Whitespace checks are easy to skip, but they often expose real issues in validation, duplicate detection, and backend data handling.

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:

  1. Type into a standard field and verify the current value text updates.
  2. Clear a pre-filled input and confirm the field returns to an empty state.
  3. Check a disabled input and confirm its locked value stays unchanged.
  4. Trigger required validation with an empty field, then make it pass.
  5. Press Enter inside the submission case and confirm the counter increments.
  6. Fill the whitespace case and validate that the warning text appears.

Complete Test Example

tests/playground/general-ui/input.spec.ts
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"), and getByLabel().
  • Use fill() for full value replacement and press() 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.

References

Practice real input scenarios on the Codemo Playground and build more reliable Playwright tests for typing, validation, and keyboard interactions.