Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
To wait until a button is enabled in Playwright, locate it and use the retrying assertion await expect(button).toBeEnabled(). If your test’s goal is simply to click it, await button.click() already waits for the button to become enabled and meet the other click actionability conditions.
Choose the wait that matches what your test needs to prove
There are two useful patterns. Use an assertion when “the button becomes enabled” is itself an expected state. Use a normal click when the only outcome that matters is clicking the button as soon as Playwright can do so.
| Pattern | Use it when | What Playwright does |
|---|---|---|
await expect(button).toBeEnabled() |
Enabled state is a checkpoint or behavior the test must verify. | Retries the assertion until it passes or its timeout is reached. |
await button.click() |
The test’s intended outcome is the click. | Waits for the click target to be unique, visible, stable, able to receive events, and enabled before clicking. |
Both patterns use a locator, which Playwright resolves against the current DOM when the operation runs. This is useful when a page re-renders while a form is being completed. The locator must still identify the intended button unambiguously.
Free tools Windows power users keep installed
One-click scans. No signup required.
Assert that the button becomes enabled
In a Playwright Test file, import test and expect from @playwright/test, then write the assertion like this:
#1 Best Overall
import { test, expect } from '@playwright/test';
test('enables Submit after required fields are filled', async ({ page }) => {
await page.goto('https://example.com/signup');
const email = page.getByLabel('Email');
const submit = page.getByRole('button', { name: 'Submit' });
await email.fill('[email protected]');
await expect(submit).toBeEnabled();
});
toBeEnabled() is an auto-retrying assertion: Playwright rechecks the condition rather than taking one immediate snapshot. Keep the assertion awaited. If the button never becomes enabled, the assertion fails when its configured timeout is reached; it does not wait forever.
This is the right choice when the test is checking a state transition—for example, that completing a required field makes submission available. It also makes the test’s intent explicit: a regression that leaves the button disabled is reported at the state check, before a later action obscures the cause.
The Locator API documents toBeEnabled() as available since Playwright v1.20. The optional enabled setting documented for that assertion was added in v1.26. These are feature-version notes, not a claim that every installed Playwright version is current. See the LocatorAssertions API and Locator API.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Rank #2
Click as soon as the button is actionable
If you do not need to make enabled state a separate assertion, call click() directly:
const submit = page.getByRole('button', { name: 'Submit' });
await submit.click();
A normal Playwright click waits for its target to resolve to exactly one element and pass the actionability checks needed to click it: it must be visible, stable, able to receive pointer events, and enabled. Therefore, adding await expect(submit).toBeEnabled() immediately before the click is usually redundant if the assertion is not independently verifying behavior.
Keep the assertion when it communicates a meaningful intermediate expectation; otherwise prefer the click and let action auto-waiting handle readiness. Playwright’s documentation describes these checks in Auto-waiting and its recommended test-writing patterns in Writing tests.
Locate the intended button reliably
Use a locator that reflects how a user or assistive technology identifies the control. For a button with the accessible name “Submit,” a typical choice is:
Recommended Free Tools
const submit = page.getByRole('button', { name: 'Submit' });
Role-and-name locators are user-facing and usually clearer than selectors tied to layout or styling. Playwright recommends built-in locators such as getByRole(); see Locators.
If the page has two buttons named “Submit”—perhaps one in a dialog and one in the page beneath it—scope the locator to the relevant container rather than accepting ambiguity:
Rank #4
const dialog = page.getByRole('dialog', { name: 'Confirm order' });
const submit = dialog.getByRole('button', { name: 'Submit' });
await expect(submit).toBeEnabled();
A click requires a unique target. If the locator matches multiple elements, refine it by scoping to a dialog, form, or other meaningful region, or adjust the accessible names in the application so the controls are distinguishable. Avoid using a broad selector and then selecting an arbitrary match just to suppress a strictness error: that can make the test interact with the wrong control.
Understand what Playwright considers enabled
For native form controls such as <button>, enabled state is governed by browser semantics. A native button with a disabled attribute is disabled; native controls can also be disabled through a disabled <fieldset>. Playwright’s documented actionability behavior also treats descendants of an element with aria-disabled="true" as disabled.
Do not assume that adding disabled to any arbitrary element gives it native button behavior. Browsers ignore that attribute on elements that do not support it as a disabling mechanism. A custom clickable element should use appropriate semantics and accessibility state—often a button element where possible—so browser behavior, assistive technology, and the test agree about whether it is disabled. The details are in Playwright’s actionability guide and assertion API.
Common mistakes and their fixes
- Using
isEnabled()as if it waits.await locator.isEnabled()returns a boolean for the state at that moment; it does not retry until the control changes. Useawait expect(locator).toBeEnabled()when you need a wait-and-assert pattern. See the Locator API. - Checking only visibility. A visible button can still be disabled.
toBeVisible()does not prove it is enabled; assert enabled state separately if that is what the test needs to establish. - Adding a fixed sleep. A delay such as
await page.waitForTimeout(1000)does not establish that the button became enabled. It may waste time when the page is fast and still fail when the page is slower. Use the retrying assertion or letclick()auto-wait. - Forcing the click. A forced click bypasses non-essential actionability checks. If the scenario is meant to behave like a user waiting for an enabled button, use a normal click; forcing it avoids the very condition the test is supposed to exercise.
- Waiting on the wrong match. A vague locator can match multiple buttons or a hidden copy. Use a specific accessible name and scope it to the appropriate form, dialog, or region.
- Expecting an arbitrary custom element to obey native disabled rules. Confirm the application exposes a meaningful disabled state, preferably by using a native button or implementing appropriate ARIA semantics and behavior.
Troubleshoot a button that never becomes enabled
- Check the locator first. Confirm its role and accessible name identify the intended control, and scope it if the page has duplicates. A locator that does not resolve to the expected button cannot verify the intended state.
- Check the application’s actual state transition. Confirm the test has completed the inputs or other prerequisites that should enable the control. If the product enables the button only after validation or a server response, verify that condition rather than inserting an arbitrary delay.
- Inspect disabled semantics. Look for a native
disabledattribute, a disabled fieldset, or an applicablearia-disabled="true"ancestor. For a custom control, verify that its semantics and implementation communicate the same state that the user sees. - Decide whether the assertion is necessary. If the actual purpose is to submit the form, try a normal
click()and let actionability waiting handle readiness. KeeptoBeEnabled()when enabled state is a requirement the test should report explicitly. - Read the failure as a state or timeout problem. A retrying assertion that reaches its timeout means the expected state was not observed within the configured window. Fix the missing application prerequisite, wrong locator, or disabled-state implementation; increasing a timeout alone does not make the condition true.
Or skip the browser setup
When you need a screenshot to inspect a page or share visual context, ScreenshotNeo offers a one-request alternative to setting up a browser capture flow. It is a screenshot API and MCP server for developers; its output can be PNG, JPEG, WebP, or PDF. This does not replace Playwright’s enabled-state assertion or test interaction.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Before capture, it can accept cookie or consent banners and remove supported consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Further reading
For assertion retry behavior, see Playwright’s Assertions documentation. For the conditions a click waits for, use Auto-waiting. Locator API options evolve across versions, so consult the documentation matching the Playwright version installed in your project.
Frequently Asked Questions
Does Playwright’s toBeEnabled() assertion check custom CSS that makes a button look disabled?
The documented enabled state is based on disabled semantics, not appearance alone. A visual style such as reduced opacity does not by itself disable a control; implement and expose the control’s actual disabled state.
Can I use toBeEnabled() with a locator returned by getByRole()?
Yes. getByRole() returns a locator, so it can be passed directly to expect(...).toBeEnabled().
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.

