Current behavior
cy.contains('User') finds <button>Username</button> and <a>Username</a> (substring
match) but does not find <input type="submit" value="Username"> with the identical
visible label. If the submit input is the only candidate, the command fails with
"Expected to find content: 'User' but never did."
Cause: getContainsSelector (packages/driver/src/dom/elements/find.ts:325) matches
every element type through the :cy-contains / :cy-contains-insensitive pseudo, a
substring test (normalizeWhitespaces(elem).includes(text)), except submit inputs, which
get a separate comma-appended branch:
return `${filter}:${expr}(${textToFind}), ${filter}[type='submit'][value~=${textToFind}]`
CSS ~= is the whitespace-token (whole-word) operator, not substring. Because
normalizeWhitespaces returns '' for an <input> (no text content), the [value~=]
branch is a submit input's only path, so any partial-word query skips it without error:
| query |
element |
visible text |
cy.contains |
expected (substring) |
'User' |
<input type=submit value="Username"> |
Username |
miss |
match |
'name' |
<input type=submit value="Username"> |
Username |
miss |
match |
've cha' |
<input type=submit value="Save changes"> |
Save changes |
miss |
match |
'User' |
<button>Username</button> |
Username |
match |
match |
Reproduced end-to-end in Cypress 15.18.0 (cypress run, Electron): cy.contains('User')
times out with "Expected to find content: 'User' but never did" against
<input type="submit" value="Username">, while the two controls pass in the same run
(cy.contains('Username') finds the submit input; cy.contains('User') finds
<button>Username</button>). Host-independent: pure native querySelectorAll
(no jQuery/Sizzle) confirms [type=submit][value~='User'] matches nothing for
value="Username" in any engine.
Desired behavior
Submit inputs should get the same substring semantics as every other element; the same
label text should be findable regardless of whether it renders as a <button> or an
<input type=submit>. Cypress already decided this for the RegExp path: #21285 (fixing
#21166) added if (isSubmit(elem)) return regex.test(elem.value) to the
cy-contains-regex pseudo, a proper pattern test on value. The plain-string path
stayed on whole-word [value~=] and now disagrees with that fix, with the .includes()
semantics used everywhere else, and with the docs (which advertise "Find the
input[type='submit'] by value" with no whole-word caveat).
Suggested fix, mirroring #21285: make the :cy-contains* pseudos read value for submit
inputs and drop the [value~=] branch, e.g. in the pseudo matcher:
const testText = isSubmit(elem) ? elem.value : normalizeWhitespaces(elem)
Test code to reproduce
Minimal page (index.html):
<input type="submit" value="Username">
Spec:
it('cy.contains partial text does NOT find a submit input by value — FAILS', () => {
cy.visit('index.html')
// "Username".includes("User") is true, but [value~='User'] is whole-word -> 0 matches
cy.contains('User') // fails: Expected to find content: 'User' but never did.
})
it('control: identical label as a <button> IS found', () => {
// page: <button>Username</button>
cy.visit('button.html')
cy.contains('User') // passes
})
it('control: exact single-word value IS found', () => {
cy.visit('index.html')
cy.contains('Username') // passes ([value~=] exact word)
})
Cypress Version
Reproduced end-to-end on the released 15.18.0 binary (cypress run, Electron,
2026-07-20). Affected code unchanged on develop @ 37a899cab.
Debug Logs
n/a: deterministic selector-level behavior.
Other
- Root cause:
packages/driver/src/dom/elements/find.ts:325 (getContainsSelector
submit branch, [value~=]); find.ts:255-278 (:cy-contains* pseudos, substring);
packages/driver/src/dom/elements/utils.ts (normalizeWhitespaces returns '' for
inputs, so [value~=] is the only submit path). End-to-end check confirmed
getContainsSelector is the sole matcher; querying.ts only picks among
already-matched elements and cannot re-introduce a skipped submit.
- Note: an exact multi-word query (
cy.contains('Save changes')) happens to match, but
only via a Sizzle tokenizer quirk that degrades [value~='Save changes'] to
[value~='Save']. Masked, not correct; the clean divergences are single-word partials
('User' for Username) and cross-boundary partials ('ve cha' for Save changes).
- Honest scope: severity low. Exact single-word and regex lookups work, and a
cy.get('input[type=submit]') workaround exists; the defect is the button-vs-submit
inconsistency for identical labels, which fails with a "content not found" error that
points the user at the wrong cause.
Found via property-based & differential bug-hunting, part of an effort to scale PBT (DepTyCheck-based) testing across the OSS ecosystem.
If this is intended / by-design: I'm really sorry, please just close it — no need to flag or ban me. I'm trying to scale property-based testing across the whole ecosystem and my publishing agents may have gotten this one wrong. I read every issue and follow up on each.
Current behavior
cy.contains('User')finds<button>Username</button>and<a>Username</a>(substringmatch) but does not find
<input type="submit" value="Username">with the identicalvisible label. If the submit input is the only candidate, the command fails with
"Expected to find content: 'User' but never did."
Cause:
getContainsSelector(packages/driver/src/dom/elements/find.ts:325) matchesevery element type through the
:cy-contains/:cy-contains-insensitivepseudo, asubstring test (
normalizeWhitespaces(elem).includes(text)), except submit inputs, whichget a separate comma-appended branch:
CSS
~=is the whitespace-token (whole-word) operator, not substring. BecausenormalizeWhitespacesreturns''for an<input>(no text content), the[value~=]branch is a submit input's only path, so any partial-word query skips it without error:
'User'<input type=submit value="Username">'name'<input type=submit value="Username">'ve cha'<input type=submit value="Save changes">'User'<button>Username</button>Reproduced end-to-end in Cypress 15.18.0 (
cypress run, Electron):cy.contains('User')times out with "Expected to find content: 'User' but never did" against
<input type="submit" value="Username">, while the two controls pass in the same run(
cy.contains('Username')finds the submit input;cy.contains('User')finds<button>Username</button>). Host-independent: pure nativequerySelectorAll(no jQuery/Sizzle) confirms
[type=submit][value~='User']matches nothing forvalue="Username"in any engine.Desired behavior
Submit inputs should get the same substring semantics as every other element; the same
label text should be findable regardless of whether it renders as a
<button>or an<input type=submit>. Cypress already decided this for the RegExp path: #21285 (fixing#21166) added
if (isSubmit(elem)) return regex.test(elem.value)to thecy-contains-regexpseudo, a proper pattern test onvalue. The plain-string pathstayed on whole-word
[value~=]and now disagrees with that fix, with the.includes()semantics used everywhere else, and with the docs (which advertise "Find the
input[type='submit']by value" with no whole-word caveat).Suggested fix, mirroring #21285: make the
:cy-contains*pseudos readvaluefor submitinputs and drop the
[value~=]branch, e.g. in the pseudo matcher:Test code to reproduce
Minimal page (
index.html):Spec:
Cypress Version
Reproduced end-to-end on the released 15.18.0 binary (
cypress run, Electron,2026-07-20). Affected code unchanged on
develop@37a899cab.Debug Logs
n/a: deterministic selector-level behavior.
Other
packages/driver/src/dom/elements/find.ts:325(getContainsSelectorsubmit branch,
[value~=]);find.ts:255-278(:cy-contains*pseudos, substring);packages/driver/src/dom/elements/utils.ts(normalizeWhitespacesreturns''forinputs, so
[value~=]is the only submit path). End-to-end check confirmedgetContainsSelectoris the sole matcher;querying.tsonly picks amongalready-matched elements and cannot re-introduce a skipped submit.
cy.contains('Save changes')) happens to match, butonly via a Sizzle tokenizer quirk that degrades
[value~='Save changes']to[value~='Save']. Masked, not correct; the clean divergences are single-word partials(
'User'forUsername) and cross-boundary partials ('ve cha'forSave changes).cy.get('input[type=submit]')workaround exists; the defect is the button-vs-submitinconsistency for identical labels, which fails with a "content not found" error that
points the user at the wrong cause.
Found via property-based & differential bug-hunting, part of an effort to scale PBT (DepTyCheck-based) testing across the OSS ecosystem.
If this is intended / by-design: I'm really sorry, please just close it — no need to flag or ban me. I'm trying to scale property-based testing across the whole ecosystem and my publishing agents may have gotten this one wrong. I read every issue and follow up on each.