Ever needed to make an assertion on a pseudo element in a Cypress test? It’s not as straightforward as you might expect.
Trying to access a pseudo element with cy.get(pseudoElementSelector) will fail, and if you can’t access it, how can
you test it? In this article, you’ll learn how to access pseudo elements so you can test them in Cypress.
What’s a pseudo-element?
As you can tell from the name, a pseudo element isn’t really an HTML element. It’s a CSS selector that allows you to
target a part of a real element’s content, or to insert CSS-defined content before / after the element’s content. The
most popular examples of pseudo elements are ::before and ::after (which allow you to insert content), although
there are a lot more, like
::first-line, ::first-letter, and ::marker.
In everyday work, pseudo elements are mainly used for decorative purposes. They make it easy to add little flourishes to
your markup, like giving the first line of every paragraph a slightly different look, or making irregular shapes from
HTML elements. We’ll look at a couple of examples. Here’s some code that uses ::first-line and ::first-letter to
style a web page:
Pseudo elements are perfectly valid CSS selectors, so it seems a little odd that Cypress can’t access them. What’s going
on? The answer is that Cypress uses jQuery under the hood, and the jQuery engine simply doesn’t support selecting pseudo
elements.
Support in Playwright
Playwright doesn’t support selecting pseudo elements either. Unlike Cypress, Playwright doesn’t use jQuery, but you
still can’t access pseudo elements in Playwright directly. Why? Pseudo elements aren’t real HTML elements, and thus you
can’t get a JavaScript reference to them. JavaScript treats them as parts of a real element’s content, so you can’t get
a direct JavaScript reference to ::before or ::after any more than you can directly select the first space in a p
tag.
Accessing pseudo elements in Cypress
While Cypress doesn’t provide direct support for accessing pseudo elements, not all hope is lost – there is a
workaround, and we’ll show it to you here. Here’s the HTML page we’ll be using as the base for our pseudo element tests:
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"/><metahttp-equiv="X-UA-Compatible"content="IE=edge"/><metaname="viewport"content="width=device-width, initial-scale=1.0"/><title>Pseudo Elements Demo</title></head><body><pdata-cy="p"> Is your architecture prepared for immersive driver growth? Iteratively touching base about integrating cryptoes
will make us leaders in the company-wide dot-bomb industry.
</p><p> Intelligently engineering effectively best-of-breed brands is crucial to our long-term enterprise. Our business
productizes capabilities to globally and reliably impact our actionable vertical.
</p><divclass="container"><blockquotedata-cy="block"> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
</blockquote></div><divclass="radio"><inputtype="radio"id="radio-1"name="cadet"/><labelfor="radio-1"data-cy="radio"><span>Ender</span></label></div><divclass="radio"><inputtype="radio"id="radio-2"name="cadet"/><labelfor="radio-2"data-cy="radio"><span>Petra</span></label></div><divclass="radio"><inputtype="radio"id="radio-3"name="cadet"/><labelfor="radio-3"data-cy="radio"><span>Bean</span></label></div></body><style>body{background-color:#efefef;}p{max-width:50vw;margin:15pxauto;line-height:1.5;font-size:1.2rem;}p::first-letter{font-size:1.5rem;}p::first-line{color:blue;}.container{display:flex;justify-content:center;align-items:center;}blockquote{font-style:italic;line-height:1.618;font-size:1.2em;width:30em;position:relative;padding:50px90px;}blockquote::before{content:open-quote;top:0;left:0;}blockquote::after{content:close-quote;bottom:0;right:0;}blockquote::before,blockquote::after{background-color:#cccccc;display:block;width:60px;height:60px;line-height:1.618;font-size:3em;border-radius:100%;text-align:center;position:absolute;}.radio{color:rebeccapurple;margin-bottom:10px;max-width:50%;margin-left:auto;margin-right:auto;position:relative;z-index:10;}.radiolabel{display:flex;align-items:center;}.radioinput[type="radio"]{display:none;}.radioinput+label::before{content:"";font:inherit;color:currentColor;display:inline-block;width:25px;height:25px;border:0.15emsolidcurrentColor;border-radius:50%;position:relative;left:-30px;}.radioinput:checked+label::before{background-color:currentColor;}</style></html>
This HTML renders a page that looks like this:
To follow along, simply set up a local dev server that serves this page and add Cypress to that project.
We’ve already established that we can’t directly access pseudo elements from JavaScript, so how are we going to test
them? Here’s the approach we’re going to take:
Get a reference to the parent element of the pseudo element with cy.get().
Use the parent to get a reference to the window object, then use the
getComputedStyle method to read the
computed CSS of the pseudo element
Use the getPropertyValue method to get the value of the CSS property you want to test
cy.get("[data-cy=before-example]").scrollIntoView().within(()=>{// Cypress does not recognize the selector "p::before"
// because the jQuery engine does not support them
// so we get the content through the computed style
// see https://codepen.io/chriscoyier/pen/Pzzawj
cy.window().then((win)=>{cy.contains("Hello").then(($el)=>{constbefore=win.getComputedStyle($el[0],"::before");constbeforeContent=before.getPropertyValue("content");// the content is a string, thus we need to quote it
expect(beforeContent).to.equal('"Greeting"');});});});
And we’re going to adapt this idea to test our web page. Here’s an example of a test that asserts that the color of the
p::first-line pseudo element on our page is blue:
1
2
3
4
5
6
7
8
9
10
11
it("tests the first-line pseudo element",()=>{cy.visit("url-to-your-page");cy.get('[data-cy="p"]').within(($el)=>{cy.window().then((win)=>{constfirstLine=win.getComputedStyle($el[0],"::first-line");constlineColor=firstLine.getPropertyValue("color");expect(lineColor).to.equal("rgb(0, 0, 255)");});});});
And here’s a test that checks the width of our blockquote::before pseudo element:
1
2
3
4
5
6
7
8
9
10
11
it("tests the before pseudo element",()=>{cy.visit("url-to-your-page");cy.get('[data-cy="block"]').within(($el)=>{cy.window().then((win)=>{constbefore=win.getComputedStyle($el[0],"::before");constwidth=before.getPropertyValue("width");expect(width).to.equal("60px");});});});
Both tests implement the algorithm we outlined above, and in your test runner, you should see both tests pass:
In our final example, we’ll cover how to get Cypress to click on pseudo elements. The radio buttons on the test page are
a little different from normal ones – they’re implemented with ::before pseudo-elements attached to the label tags,
instead of being implemented with input tags. You’re going to write a test that clicks on each ::before radio
button, and asserts that it has the correct styling after the click.
Add the following code after the previous it block:
1
2
3
4
5
6
7
8
9
10
11
12
13
it('clicks the before pseudo elements',()=>{cy.visit('url-to-page')cy.get('[data-cy="radio"]').each(($el)=>{cy.wrap($el).click(-25,10).within(($el)=>{cy.window().then((win)=>{constbefore=win.getComputedStyle($el[0],'::before')constbgColor=before.getPropertyValue('background-color')expect(bgColor).to.equal('rgb(102, 51, 153)')})})})
This test is largely similar to the first two. It starts by getting all the label elements with cy.get, then clicks
on each of them, and asserts on their ::before pseudo elements. The main difference in this test is the arguments
passed to cy.click(). By shifting the location of the click to be 25 pixels left and 10 pixels down from the top-left
corner of the targeted element, we are able to get Cypress to click on the pseudo element even though it cannot be
targeted directly.
After running the test, you should see that the toggle has been activated as shown below:
The bright red dot shows the location of the click event, and the test passes, showing that the radio button changed its
background color as it was supposed to.
And that’s how you test pseudo elements in Cypress!
Reflect: An alternative to Cypress with native support for pseudo-elements
Reflect is a no-code testing tool that can interact with pseudo-elements like in
the example above.
Creating a test in Reflect is easy: the tool records your actions as you use your site and automatically translates
those actions into a repeatable test that you can run any time.
Get automated test coverage for your web app today — try it for free.
Get started with Reflect today
Create your first test in 2 minutes, no installation or setup required.
Accelerate your testing efforts with fast and maintainable test suites without writing a line of code.