The link steps assert the anchors on a page: that a link with a given visible text points at the right href, that it lives inside the right region of the page, that a link carries a specific title attribute, and whether an href is absolute or relative. They are quick wins for content QA and SEO reviews - a navigation menu that silently lost its Contact link, a footer Privacy link pointing at a stale path, or an internal link accidentally hardcoded to an absolute production URL all become one-line assertions.
Under the hood each step builds a Playwright locator from the visible text (a:has-text("..."), a substring match on the link's text) combined with an attribute selector. The href check is a contains match ([href*="..."]), so "/about" matches /about, /about-us, and https://example.com/about - quote as much of the path as you need to be unambiguous. The title checks, by contrast, require an exact attribute value. The "within the element" variants scope the search to a parent CSS selector, which is the right tool when the same link text appears in both the header and the footer.
These are count-based existence assertions: they check the DOM immediately rather than retrying, so run them after the page (or the AJAX update) has finished loading - pair with a wait step when asserting links injected by JavaScript. The one action step in the group, clicking a link by its title, is handy for icon-only links that have no readable text.
Quick reference
| Step | What it does |
|---|---|
Then the link "About" with the href "/about" should exist | Assert a link with that visible text and an href containing the fragment exists. |
Then the link "Home" with the href "/" within the element "#main-nav" should exist | Same assertion, scoped to a parent element. |
Then the link "Sign in" with the href "/login" should not exist | Assert no link with that text + href fragment exists. |
Then the link "Logout" with the href "/logout" within the element ".guest-nav" should not exist | Negative assertion scoped to a parent element. |
Then the link with the title "Open menu" should exist | Assert at least one link has that exact title attribute. |
Then the link with the title "Delete user" should not exist | Assert no link has that exact title attribute. |
Then the link "Twitter" should be an absolute link | Assert the link's href starts with http:// or https://. |
Then the link "Home" should not be an absolute link | Assert the link's href is relative (no scheme). |
When I click on the link with the title "Open menu" | Click the first link with that exact title attribute. |
Steps in detail
Then the link "About" with the href "/about" should exist / should not exist
Asserts that at least one (or, for the negative form, zero) anchor whose visible text contains the first argument has an href containing the second argument. Both matches are substring matches, which keeps scenarios stable across query strings and domain changes - but be specific enough: asserting "/" matches almost every link, and the negative form of a too-loose fragment can never pass.
Given I am on "/links.html"
Then the link "About" with the href "/about" should exist
And the link "About" with the href "/missing" should not exist
Then the link "Home" with the href "/" within the element "#main-nav" should exist / should not exist
Scoped variants that only search inside the parent matched by the third argument (any CSS selector - #main-nav, footer, .breadcrumb). Use these to disambiguate repeated link text and to assert region-specific rules, such as no Logout link inside the guest navigation.
Then the link "Contact" with the href "/contact" within the element "#nav" should exist
And the link "Logout" with the href "/logout" within the element ".guest-nav" should not exist
Then the link with the title "Open menu" should exist / should not exist
Asserts on the title attribute instead of visible text - the natural addressing scheme for icon links (hamburger menus, download buttons, print links). Unlike the href steps, the title must match exactly, including case.
Then the link with the title "About us" should exist
And the link with the title "Nonexistent" should not exist
Then the link "Twitter" should be an absolute link / should not be an absolute link
Reads the href of the first link whose text matches and tests it against ^https?://. The positive form catches external links written as relative paths; the negative form catches internal links accidentally hardcoded with a full domain - a classic source of broken links after a domain or environment change. Both forms fail if the link has no href at all, and the failure message includes the actual href found.
Then the link "Example" should be an absolute link
And the link "About" should not be an absolute link
When I click on the link with the title "Open menu"
Clicks the first anchor with the exact title attribute. Because it is a Playwright click, it auto-waits for the link to be visible and stable before clicking. Reach for it when the link has no usable visible text - otherwise the plain When I follow "..." navigation step reads better.
When I click on the link with the title "Open menu"
Then I should see "Navigation"
Complete example
Adapted from tests/features/link.feature, running against the examples/links.html fixture:
Feature: Link step definitions
Scenario: Link href + title assertions
Given I am on "/links.html"
Then the link "About" with the href "/about" should exist
And the link "Contact" with the href "/contact" within the element "#nav" should exist
And the link "About" with the href "/missing" should not exist
And the link with the title "About us" should exist
And the link with the title "Nonexistent" should not exist
And the link "Example" should be an absolute link
And the link "About" should not be an absolute link
Tips
- Link text matching uses
:has-text(), which matches substrings -"About"also matches "About us". When several links share a prefix, add the href fragment or a parent scope to pin down the one you mean. - For asserting a link's other attributes (
target,rel,class), use the generic element-attribute steps in the element steps group. - Links rendered by JavaScript after load need a wait first - see wait steps.