Given this HTML
:
<p>Target String</p>
<ul>
<li>Target String but not really</li>
<li>Target Strings?</li>
<li>Not Target String</li>
<li>Target String(nah)</li>
<li>Target String</li> <!-- Select ONLY this -->
<li>So not Target String</li>
<li>No Target String here</li>
</ul>
- I want to use Cypress to select one element from the HTML below. Specifically, the
<li>Target String</li>
element. Note the constraints:- There may be other elements of different types with the same
innerText
. - There may be elements on the same type that contain the same
innerText
plus more text either before, after theTarget String
, or both. - It's not possible to assign the
innerText
as an attribute for any of the<li>
elements because they were generated by a library. - It's not possible to manually add a unique
id
or other attribute to the desired element, since it was generated by a library.
- There may be other elements of different types with the same
We need to select the <li>
element with an exact match for our Target String
. This is the most elegant way I can think of. We need a little help from our unsightly friend, regExp
:
const regExp = /(?<!.)(Target String)(?!.)/;
cy.contains("li", regExp); // 🎉SUCCESS!
Initially, I didn't think the .contains()
method was appropriate because the Cypress docs say it matches any string that includes
the target string.
DOM elements can contain more than the desired text and still match.
Since they expressed this so explicitly even though they indicated that selectors could be regExp
, I thought it meant that even a regExp
selector would still return elements that included the target string.
👉 Click to read why I didn't choose other options
I tried using other strategies, but they were not successful.
.select
would have been great, but it only works on<option>
elements nested in<select>
..get('li[innerText="Target String"]')
- though you can use HTMLattributes
to build a more complex selector, you're limited by what is visible in the DOM. Every element has aninnerText
attribute under the hood, but it's not visible in the DOM so it can't be used..should
did not seem to have a useful assertion or chainer.- I also thought of getting all
<li>
elements that containTarget String
, but then I'd have to iterate through them, which does not seem elegant. I thought about using the.filter
and.children
in this case. .find
wouldn't work because it only acceptsstring
selectors, notregExp
..within
,.spread
, and.siblings
seemed inelegant.- I probably considered other options but they were not quite suitable or elegant for other reasons.
It matches the Target String
ONLY if there are no characters before or after it. 😎
.
- The dot/period matches any character.(?<!.)
- Negative Lookbehind - Don't match if there are any characters before theTarget String
.(?!.)
- Negative Lookahead - Don't match if there are any characters after theTarget String
.
My favorite regExp
tool for years has been RegExr.com. It's excellent! It's fast, quick, free, open source, doesn't require sign-in, shows syntax errors, and explains your regExp
to you, piece by piece! Of course, you can add your own text to test your regExp
. I still come back to it when I'm dealing with a challenging regExp
or need to check my work. Here is the GitHub repo.