Pseudo CSS selectors

These examples are based on the article Meet the Pseudo Class Selectorsopen in new window.

Some of the anchor elements below do not have href attribute, thus they are not links. Let's select the anchors with href attributes.

<div data-cy="pseudo-links">
  <a href="first">first</a>, <a>second</a>,
  <a href="third">third</a>,
</div>
cy.get('[data-cy=pseudo-links]')
  .scrollIntoView()
  .within(() => {
    // by default, "a" returns all 3 elements
    cy.get('a').should('have.length', 3)
    // select "a" elements with "href" attribute
    cy.get('a:link').should('have.length', 2)
    // which is equivalent to
    cy.get('a[href]').should('have.length', 2)
  })

Empty elements

Let's find all elements without any content using :empty pseudo selector. Notice in the markup below some <P> elements have no content.

<div data-cy="empty-elements">
  <p></p>
  <p>Has some text</p>
  <p></p>
  <p class="nothing"></p>
</div>
cy.get('[data-cy=empty-elements]')
  .scrollIntoView()
  .within(() => {
    cy.get('p:empty')
      .should('have.length', 3)
      .last()
      .should('have.class', 'nothing')
  })

First letter

<div data-cy="first-letter">
  <p>this is some text, just an example</p>
</div>
<style>
  /* make the first letter always stand out */
  [data-cy='first-letter'] p::first-letter {
    text-transform: uppercase;
    font-weight: bold;
  }
</style>
cy.get('[data-cy=first-letter]')
  .scrollIntoView()
  .within(() => {
    // Cypress does not recognize the selector "p::first-letter"
    // because the jQuery engine does not support them
    // thus the following DOES NOT WORK with error
    // "Syntax error, unrecognized expression: p::first-letter"
    // cy.get('p::first-letter')
  })

After content CSS selector ::after

<style>
  /* add a word after each paragraph */
  [data-cy='after-example'] p::after {
    content: 'Joe Smith';
    margin-left: 1em;
  }
</style>
<div data-cy="after-example">
  <p>Write more tests</p>
</div>
cy.get('[data-cy=after-example]')
  .scrollIntoView()
  .within(() => {
    // Cypress does not recognize the selector "p::after"
    // 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('more tests').then(($el) => {
        const after = win.getComputedStyle($el[0], '::after')
        const afterContent = after.getPropertyValue('content')
        // the content is a string, thus we need to quote it
        expect(afterContent).to.equal('"Joe Smith"')
      })
    })
  })

Checking the ::after content using cypress-map

You can shorten the above code snippet using cypress-mapopen in new window queries

<style>
  /* add a word after each paragraph */
  [data-cy='after-example'] p::after {
    content: 'Joe Smith';
    margin-left: 1em;
  }
</style>
<div data-cy="after-example">
  <p>Write more tests</p>
</div>
cy.get('[data-cy=after-example] p')
  // from jQuery object, get the actual element
  // and call "window.getComputedStyle(element, '::after')"
  .applyToFirstRight(window.getComputedStyle, '::after')
  .invoke('getPropertyValue', 'content')
  .should('equal', '"Joe Smith"')

Checking if ::after exists or not

<style>
  /* add a word after each paragraph */
  [data-cy='after-example'] p::after {
    margin-left: 1em;
  }
  [data-cy='after-example'] p.displayed::after {
    content: 'Joe and Amy';
  }
</style>
<div data-cy="after-example">
  <p>Write more tests</p>
  <button id="add-after">Click me</button>
</div>
<script>
  document
    .getElementById('add-after')
    .addEventListener('click', () => {
      setTimeout(() => {
        const el = document.querySelector(
          "[data-cy='after-example'] p",
        )
        el.classList.add('displayed')
      }, 1000)
    })
</script>
cy.log('**no ::after at first**')
cy.get('[data-cy=after-example] p')
  .applyToFirstRight(window.getComputedStyle, '::after')
  .invoke('getPropertyValue', 'content')
  .should('equal', 'none')
cy.contains('button', 'Click me').click()
cy.log('**new ::after content after some time**')
cy.get('[data-cy=after-example] p')
  .applyToFirstRight(window.getComputedStyle, '::after')
  .invoke('getPropertyValue', 'content')
  .should('equal', '"Joe and Amy"')

Before content CSS selector ::before

<style>
  /* add a word in front of each paragraph */
  [data-cy='before-example'] p::before {
    content: 'Greeting';
    margin-right: 1em;
  }
</style>
<div data-cy="before-example">
  <p>Hello, world!</p>
</div>
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) => {
        const before = win.getComputedStyle($el[0], '::before')
        const beforeContent = before.getPropertyValue('content')
        // the content is a string, thus we need to quote it
        expect(beforeContent).to.equal('"Greeting"')
      })
    })
  })