Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsSome links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use a locator’s inputValue() method to read the live value of an <input>, <textarea>, or <select>:
const email = page.getByLabel('Email');
const value = await email.inputValue();
Use textContent() for text held by a DOM node, and expect(locator).toHaveValue() when the purpose is to verify a value in a test. These APIs answer different questions and should not be substituted for one another.
Read a form control’s current value
Playwright’s locator API documents inputValue() for matching <input>, <textarea>, and <select> elements. It returns the control’s current, live value, including changes made by typing or JavaScript after the page loaded.
import { test } from '@playwright/test';
test('reads the email field', async ({ page }) => {
await page.goto('https://example.com/signup');
const email = page.getByLabel('Email');
await email.fill('[email protected]');
const value = await email.inputValue();
console.log(value); // [email protected]
});
The method is asynchronous because Playwright resolves the locator and waits for the element according to its normal actionability and auto-waiting behavior. The official Locator API describes it as returning the value for the matching form control: Locator API.
#1 Best Overall
Use a label locator first
A label locator expresses the same relationship a user sees. getByLabel() can use visible label text, aria-labelledby, or aria-label.
const username = page.getByLabel('Username');
const usernameValue = await username.inputValue();
If the field has an accessible role and name but no usable label, use a role locator:
const search = page.getByRole('textbox', { name: 'Search' });
const query = await search.inputValue();
Playwright’s locator guide recommends user-facing locators and explicit contracts because locators are the central mechanism for auto-waiting and retryability: Locators guide.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
When a field has no accessible name
Fix the markup if possible by associating a real <label> or adding an accessible name. As a fallback, use a stable test contract such as getByTestId():
const coupon = page.getByTestId('coupon-code');
const code = await coupon.inputValue();
CSS and XPath can work, but selectors coupled to DOM structure are more likely to break when the implementation changes. If you must use one, keep it narrow and stable:
const phone = page.locator('input[name="phone"]');
const phoneValue = await phone.inputValue();
Choose the API that matches your data
| What you need | Use | What it returns or does |
|---|---|---|
| Live value of an input, textarea, or select | await locator.inputValue() |
Current form-control value; throws when the target is not a supported control. |
| Text contained by a DOM node | await locator.textContent() |
The node’s DOM textContent, not a control’s live value. |
| Verify a value in a test | await expect(locator).toHaveValue('expected') |
A retrying assertion that waits for the expected value. |
| Read an HTML attribute | await locator.getAttribute('name') |
The serialized attribute value, which may differ from live state. |
inputValue() versus textContent()
Form controls do not normally expose what a user typed as child text. The current value is a property maintained by the control, so use inputValue().
const field = page.getByLabel('Message');
await field.fill('A message typed by the test');
const liveValue = await field.inputValue();
console.log(liveValue); // A message typed by the test
Use textContent() when the information is text inside an element such as a heading, status message, or paragraph:
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Rank #2
const status = page.getByText('Welcome');
const text = await status.textContent();
console.log(text);
Calling textContent() on an input generally does not give you its current typed value. Conversely, calling inputValue() on a paragraph, heading, or arbitrary container fails because those elements are not supported form controls.
Attribute value is not live value
The HTML value attribute represents markup or a default value; it is not a reliable read of the control’s current property after interaction. If you specifically need an attribute, request it explicitly:
const markupValue = await page.getByLabel('Email').getAttribute('value');
For what the user currently sees in the control, use inputValue().
Assert a value instead of retrieving it
When the goal is a test assertion, use Playwright Test’s toHaveValue(). It retries while the page updates, reducing races in reactive interfaces.
import { test, expect } from '@playwright/test';
test('the email is accepted', async ({ page }) => {
await page.goto('https://example.com/signup');
const email = page.getByLabel('Email');
await email.fill('[email protected]');
await expect(email).toHaveValue('[email protected]');
});
A one-time read followed by a manual comparison can observe the field too early:
const actual = await email.inputValue();
expect(actual).toBe('[email protected]');
That pattern is appropriate when you need the value for another operation. For verification alone, the retrying assertion is the clearer and more resilient choice. See the Locator API.
Complete examples for common controls
Text and password inputs
const firstName = page.getByLabel('First name');
const password = page.getByLabel('Password');
await firstName.fill('Avery');
await password.fill('correct-horse-battery-staple');
const nameValue = await firstName.inputValue();
const passwordValue = await password.inputValue();
Avoid logging secrets in real test output. Read a password only when the test genuinely needs to inspect it.
Textarea
const notes = page.getByLabel('Notes');
await notes.fill('Follow up next week.');
const notesValue = await notes.inputValue();
Select
const country = page.getByLabel('Country');
await country.selectOption('ca');
const selectedValue = await country.inputValue();
console.log(selectedValue); // ca
For a custom listbox that is not a native <select>, inputValue() may not apply. Locate the selected option or other exposed text according to that widget’s accessibility contract.
Several matching controls
A locator can match multiple elements, but inputValue() expects one resolved control. Narrow the locator with a label, role name, index, or a containing region:
const billingEmail = page.getByRole('region', { name: 'Billing' }).getByLabel('Email');
const value = await billingEmail.inputValue();
If you intentionally need every matching value, iterate over the locator:
const fields = page.locator('input[data-field="tag"]');
const values = await fields.evaluateAll(elements =>
elements.map(element => (element instanceof HTMLInputElement ? element.value : ''))
);
Prefer a user-facing locator for the individual control whenever one exists.
Timing, frames, and dynamic pages
Wait for the control’s state
Locators automatically wait for the element to be found, but application code may populate a value later. Use an assertion when you need to wait for that state:
const accountId = page.getByLabel('Account ID');
await expect(accountId).toHaveValue(/^[A-Z0-9-]+$/);
const id = await accountId.inputValue();
A targeted wait for a loading indicator or a relevant response can also make the application transition explicit, but avoid arbitrary sleeps unless the page truly has no observable signal.
Controls inside an iframe
const paymentFrame = page.frameLocator('iframe[title="Payment"]');
const cardNumber = paymentFrame.getByLabel('Card number');
const value = await cardNumber.inputValue();
The locator must be created through the frame locator; a page-level locator cannot cross a frame boundary.
Rank #4
Shadow DOM
Playwright locators generally pierce open shadow DOM. Continue to prefer a label, role, or test id exposed by the component. Closed shadow roots require an application-supported access path because the internal control is not available to ordinary page locators.
Troubleshooting failures
“Element is not an input, textarea, or select”
The locator resolved to a button, paragraph, custom widget, or container. Use textContent() for text, or locate the widget’s actual input. For a button displaying a value, inspect its accessible name or text instead of calling inputValue().
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →The value is empty or stale
Check that you are reading the same control the user edited, then wait for the application state with toHaveValue(). A duplicated label, hidden template, or re-rendered field can make a broad selector target the wrong element.
Strict-mode or multiple-match error
Make the locator unique by using an accessible name, a containing dialog or region, or a stable test id. Do not silence the problem with an arbitrary index unless the order is part of the UI contract.
Label lookup does not find the field
Ensure the label is associated with the control using a matching for/id pair, nesting, aria-label, or aria-labelledby. If you cannot change the page, use a role locator with its accessible name or a stable attribute as a fallback.
The page API example is outdated
Older examples may show page.inputValue() or page.textContent(). Current Playwright guidance favors locator methods, such as page.getByLabel('Email').inputValue(), so that the target and its waiting behavior remain explicit. The Page API marks those page-level methods as discouraged: Page API.
Recommended Free Tools
Performance and test design
- Keep locators specific so Playwright does not search unnecessary portions of the DOM.
- Read a value once when passing it to application code; use an assertion when checking it.
- Do not use fixed delays to compensate for a race that can be expressed as a value assertion.
- Use accessible labels and roles to make tests survive harmless DOM refactors.
- Keep sensitive values out of traces, screenshots, console output, and failure messages where possible.
The API call itself is small; most delay comes from locating the element, waiting for it to appear, or waiting for the application to populate it. Reliable locator contracts matter more than micro-optimizing the property read.
Or skip the browser setup
If your real goal is to capture a page rather than interact with a control, ScreenshotNeo makes a screenshot with one request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the complete option list and authentication details in the ScreenshotNeo documentation.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));
Every feature is available on every plan. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for the free plan at ScreenshotNeo.
Free tools Windows power users keep installed
One-click scans. No signup required.
FAQ
Does inputValue() return a string?
Yes. It returns the control’s value as a string. Convert it yourself when the application expects a number or another type.
Can I use inputValue() on a checkbox?
Use isChecked() to inspect a checkbox’s checked state. The checkbox’s value attribute is a different concept from whether it is selected.
What should I use for a selected option’s label?
inputValue() returns the selected option value. If you need the human-readable label, locate the selected option or inspect the select’s rendered text according to the page’s markup.
How do I inspect a value without exposing it in logs?
Assert the value directly with toHaveValue(), or compare a masked or transformed value instead of printing the raw secret.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

