P D
P D

Reputation: 830

Cypress - attribute containing text and end of string

Let's assume I have such html:

<div class="my_class" id="first_case">
  <a href="/foo/bar/123"></a>
</div>

<div class="my_class" id="second_case">
  <a href="/foo/bar/1234567"></a>
</div>

I want to make assertion that there is an item with href, which ends with '/123'.

const test_id = '123'

cy.get('.my_class').find('a').should("have.attr", "href").and("contain", '/' + test_id);

It works, however I don't know how to make sure that the assertion is true only for href with exact ending ('/123', as shown in #first_case in first code snippet) and that it is false for other strings starting with such number, for example ('/1234567', as shown in #second_case in first code snippet).

In other words, the assertion should be true for #first_case, but false for #second_case.

I tried using end of string symbol or making new RegExp object, however couldn't make it work. Any help would be appreciated!

Upvotes: 10

Views: 16655

Answers (5)

Hendrik
Hendrik

Reputation: 133

Out of the box, ChaiJs provides the satisfy keyword which is followed by a predicate.

The predicate can use any Javascript expression, so for example using String.prototype.endsWith()

cy.get('.my_class')
  .find('a')
  .should('have.attr', 'href')
  .and('satisfy', href => href.endsWith('/' + test_id))

This is almost the same as Brendan's example, except he uses .then() with variations of href.endsWith(test_id). This IMO is a mistake since then() does not have any retry capability.

Using .should() or .and() for assertions is always preferable to avoid flaky tests due to asynchronous loading of items.


Reference Cypress doscs - Assertions

Chainer Example
satisfy (method) .should('satisfy', (num) => num > 0)
Aliases: satisfies expect(1).to.satisfy((num) => num > 0)

Upvotes: 10

Wulf Solter
Wulf Solter

Reputation: 714

Out-of-the box Chai does not have an 'endsWith' assertion so I added my own to support/e2e.ts as follows:

// Custom Chai assertion "endsWith" to check if a string ends with another string
// See for more details:
//   https://docs.cypress.io/app/references/assertions#Adding-New-Assertions
//   https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/extending-cypress__chai-assertions
//   https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/extending-cypress__chai-assertions/cypress/support/e2e.js
//
const customAssertionEndsWith = (_chai) => {
  function assertEndsWith(options) {

    this.assert(
      typeof this._obj === 'string' && this._obj.endsWith(options),
      'expected #{this} to be string that ends with #{exp}',
      'expected #{this} to not be string that ends with #{exp}',
      options, // expected
      this._obj, // actual
    );
  }

  _chai.Assertion.addMethod('endsWith', assertEndsWith);
};

// registers our assertion function "customAssertionEndsWith" with Chai
chai.use(customAssertionEndsWith);

which I then use in my tests like

cy.url().should('endsWith', '/settings/terms');

Upvotes: 0

martin
martin

Reputation: 96959

I think there's even easier way to do this. You can use a single cy.get() and single should() because Cypress selectors should behave the same way as jQuery selectors do.

cy.get('.my_class a[href$="/123"]').should('have.length', 1);

$= will match only attributes that end with /123.

Upvotes: 7

Brendan
Brendan

Reputation: 4659

.should() will yield you the href so you can use it in a .then() block however you like (see https://stackoverflow.com/a/48451157/2883129), so you can use .endsWith():

const test_id = '123'

cy.get('.my_class')
  .find('a')
  .should("have.attr", "href")
  .then(href => {
    expect(href.endsWith(test_id)).to.be.true;
  });

Or to make it a bit more readable and a failure message to be clearer, you could use https://www.chaijs.com/plugins/chai-string/:

const test_id = '123'

cy.get('.my_class')
  .find('a')
  .should("have.attr", "href")
  .then(href => {
    expect(href).to.endWith(test_id);
  });

Upvotes: 8

Mamun
Mamun

Reputation: 68933

In vanilla JS, you can try with querySelector() and attribute ends with selector ([name$="value"). Please note, you forgot to close the a element.

const test_id = '123'
var el = document.querySelector(`[href$="${test_id}"]`);
console.log(el);
<div class="my_class" id="first_case">
  <a href="/foo/bar/123"></a>
</div>

<div class="my_class" id="second_case">
  <a href="/foo/bar/1234567"></a>
</div>

Upvotes: 2

Related Questions