Read element attributes in Playwright
Read current HTML attributes with getAttribute(), assert them with toHaveAttribute(), and distinguish missing attributes from empty values.
Use await locator.getAttribute('data-action') to read an attribute as a string or null. To verify it in a test, prefer await expect(locator).toHaveAttribute('data-action', 'cancel'), which retries. A missing attribute returns null; an existing attribute with an empty value returns ''.
The snippets assume Playwright Test's expect is imported and the page is loaded. Each locator used for a single-element operation must identify one element; add a name or another constraint if your page has several links or buttons.
Read and assert attributes
const href = await page.getByRole('link', { name: 'Docs' }).getAttribute('href');
const action = await page.getByRole('button', { name: 'Cancel' }).getAttribute('data-action');
await expect(page.getByRole('button', { name: 'Cancel' }))
.toHaveAttribute('data-action', 'cancel');
await expect(page.getByRole('link', { name: 'Docs' }))
.toHaveAttribute('href', /\/docs/);
Check classes and boolean attributes
// Match a complete class token without comparing the entire class string.
await expect(locator).toHaveClass(/(^|\s)active(\s|$)/);
// Effective disabled state versus literal attribute presence.
const isDisabled = await locator.isDisabled();
const hasAttr = (await locator.getAttribute('disabled')) !== null;
The semantic method is normally better when you care whether a control is disabled. Attribute presence and effective state can differ, for example when a disabled ancestor fieldset affects a control.
Distinguish null from an empty string
Given:
<div data-empty="" data-none></div>
getAttribute('data-empty')returns''.getAttribute('data-none')also returns''.getAttribute('data-missing')returnsnull.
Use !== null to test presence. A truthiness check incorrectly treats an existing empty attribute as absent.
Attributes and DOM properties
getAttribute() reads the current HTML attribute. Scripts can update attributes, so they are not permanently frozen at their initial markup values. A form control's live state, such as value or checked, is represented by DOM properties that can diverge from the attributes:
await locator.inputValue(); // Current form-control value.
await locator.getAttribute('value'); // Current value attribute, not necessarily the live input.
Frequently asked questions
Why does getAttribute() return an old value or null?
You may need a DOM property or semantic state instead. Use inputValue() for a control's current value, isChecked() for its checked state, or isVisible() for Playwright's visibility query.
Can I read data attributes?
Yes: getAttribute('data-user-id') reads that attribute directly. For a test locator, getByTestId('submit-btn') uses data-testid by default; the attribute name is configurable.
How do I assert that an attribute is absent?
Use await expect(locator).not.toHaveAttribute('disabled', /.*/) for a retrying check. Reading the attribute and then applying toBeNull() only checks one snapshot. If the requirement is that the control is enabled, use toBeEnabled() instead; attribute absence is not a general guarantee of actionability.
References
Related reading
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan