Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Playwright’s toBeDisabled() assertion to verify a button’s disabled state. Locate the intended button by its role and accessible name, then assert the state directly:

import { test, expect } from '@playwright/test';

test('submit button is disabled', async ({ page }) => {
  await expect(
    page.getByRole('button', { name: 'Submit' })
  ).toBeDisabled();
});

For application logic that needs a boolean instead of a test assertion, call isDisabled() on the locator. Playwright recognizes native disabled attributes and aria-disabled states.

Choose a locator that identifies the right button

A disabled-state assertion is only useful if the locator points to the button you mean to test. For a button with a clear accessible name, the usual starting point is page.getByRole('button', { name: 'Submit' }). A role locator corresponds to how users and assistive technology perceive the page, and the name narrows the match to the intended control.

Use the button role and accessible name

const submitButton = page.getByRole('button', { name: 'Submit' });
await expect(submitButton).toBeDisabled();

The accessible name may come from visible button text or other accessible naming markup. Match the name a user would perceive rather than relying first on a styling class or a brittle position in the page. For example, if the page has both a form submit button and a dialog submit button, use names that distinguish them in the UI, or scope the locator to the relevant area before asserting.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make ambiguous matches specific

If several buttons share the same name, narrow the locator to the relevant form, dialog, or other containing region, then find the named button within that region. Avoid choosing the first matching button merely because it makes the assertion pass: that can test a different control than the one the user is concerned about.

CSS or XPath selectors are available when a semantic locator cannot identify the element reliably, but role-and-name locators are the clearer default for a user-facing button. If you do use a selector, keep it tied to a meaningful, stable hook where possible and check that it identifies the intended control.

Assert disabled state with toBeDisabled()

In a test, prefer the Playwright assertion:

await expect(page.getByRole('button', { name: 'Submit' })).toBeDisabled();

toBeDisabled() is the purpose-built assertion for this check: it ensures the locator points to a disabled element. Use it when the test’s expected outcome is that the button is disabled. If the expected outcome is the opposite, use the corresponding enabled-state assertion rather than negating a boolean read; this keeps the test expressed as an expectation about the page.

Check the state after the action that should disable the button

Place the assertion at the point in the scenario where the application is expected to disable the control. For example, if a form begins with an unavailable submit button, assert its initial state. If a user action should disable it, perform that action first and then assert the resulting state. The assertion should describe the expected UI state, not merely inspect the element at an arbitrary point in the test.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
test('submit is disabled before the form is ready', async ({ page }) => {
  await page.goto('/signup');

  const submitButton = page.getByRole('button', { name: 'Create account' });
  await expect(submitButton).toBeDisabled();
});

Replace /signup and the accessible name with values from your application. The example checks the button as rendered on the page; it does not prescribe when your product should enable it.

Read the state as a boolean with isDisabled()

Use isDisabled() when application code needs a boolean value for conditional logic rather than a test assertion:

const submitButton = page.getByRole('button', { name: 'Submit' });
const disabled = await submitButton.isDisabled();

if (disabled) {
  // Handle the disabled state in application or test logic.
}

The locator API defines isDisabled() as returning whether the element is disabled. For a test expectation, Playwright recommends toBeDisabled(). In short, use the assertion for “this test expects disabled” and the boolean read when subsequent logic genuinely needs a value.

Do not turn a state assertion into an unnecessary branch

If the only purpose of a test is to verify that the button is disabled, a boolean plus an if statement adds indirection without making the expected outcome clearer. Use toBeDisabled() directly. Reserve isDisabled() for a case where code must make a decision based on the current state.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What Playwright considers disabled

Playwright recognizes an element as disabled when it has a native disabled attribute or is disabled through aria-disabled. The native attribute applies to native controls such as button, input, select, textarea, option, and optgroup.

Native disabled controls

A native button can be disabled with the HTML attribute:

<button disabled>Submit</button>

For a native control, toBeDisabled() checks the disabled state rather than the visual appearance. This makes it a better test of the control’s state than checking whether a particular CSS class is present.

ARIA-disabled controls

A UI can communicate a disabled state using aria-disabled. Playwright’s disabled-state assertion recognizes that state too. This matters for controls whose state is represented with ARIA rather than the native HTML attribute. If your application uses a custom control, use a locator that reflects its role and accessible name, then verify the disabled state with the same assertion.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Do not confuse an element that merely looks muted with one that has a recognized disabled state. A gray color, reduced opacity, or a class named disabled is a visual or implementation detail; it is not itself the native disabled attribute or aria-disabled state that this assertion checks. If your design intends a control to be disabled, ensure the markup exposes the state and test that state.

Common mistakes and how to fix them

  • Using a locator that matches the wrong button: Find the control by role and accessible name, and narrow it to the right section if multiple controls have the same name.
  • Checking styling instead of state: A CSS class or muted color does not establish the disabled state. Assert the actual native disabled or ARIA state.
  • Using a boolean read for a simple expectation: Replace a manual isDisabled() check with await expect(locator).toBeDisabled() when the test’s sole purpose is to assert that state.
  • Expecting native behavior from a custom control without an exposed state: If a custom control is meant to be disabled, make sure its markup exposes a disabled state, such as aria-disabled, and then assert it.
  • Asserting too early in the scenario: Put the assertion after the page is in the state whose button behavior you are testing; perform the triggering action first when the expected disabled state follows an action.

Playwright version note

The LocatorAssertions documentation identifies toBeDisabled() as added in Playwright v1.20. If your project uses an older version and the assertion is unavailable, check which Playwright version the project installs and update it if your project permits. The version note is about availability of this assertion, not a claim about the age or support status of your entire Playwright installation.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

A screenshot can help document what a page looks like, but it cannot replace a Playwright assertion when you need to know whether a button is actually disabled. For screenshot-based visual inspection, ScreenshotNeo offers a one-request website screenshot API. Its request accepts a URL and can return an image or PDF; use the Playwright check above for the button’s DOM state.

For example, this cURL request captures a page as an image. See the ScreenshotNeo documentation for request options and setup:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
  • Cookie and consent banners are accepted or removed before capture, along with supported newsletter popups and chat widgets; each cleanup step can be turned off.
  • Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. The response includes X-Page-Verdict and X-Billed headers.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and 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 to get 1,000 free screenshots a month with no card.

Frequently Asked Questions

Which Playwright version added toBeDisabled()?

The LocatorAssertions API documents it as added in Playwright v1.20.

Can a screenshot tell me whether a button is disabled?

No. A screenshot records the page’s appearance; use toBeDisabled() to assert the state Playwright recognizes.

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.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.