cypress each method get a child elements href - cypress

I'm using an each method in cypress to find a list of li elements.
What I'm after is to find a child inside the li and test its href value.
The child is an a tag.
Here is what I have so far:
it("should have ..... ", () => {
cy.get("ul > li")
.each((el, index, list) => {
expect(el.text()).to.be.oneOf(["hello", "world"]);
const a = cy.wrap(el).find("a");
expect(a.attr("href")).to.be.oneOf(["/hello", "/world"]);
})
.then((list) => {
expect(list).to.have.length(2);
});
});
Error: a.attr is not a function

Don't wrap the el. If you do, it becomes a Cypress command which is always async.
You would have to do
cy.wrap(el).find("a").then(a => {
expect(a.attr("href")).to.be.oneOf(["/hello", "/world"]);
})
But find works for jQuery as well.
const a = el.find("a")
expect(a.attr("href")).to.be.oneOf(["/hello", "/world"]);

The problem is you're storing the results of a Cypress (Promise-like) Chainable. Also using an expect inside each won't benefit from the retry-ability provided by should.
it("should have ..... ", () => {
cy.get("ul > li")
.should("have.length", 2)
.each(($el) => {
cy.wrap($el)
.invoke("text")
.should("be.oneOf", ["hello", "world"])
cy.wrap($el)
.find("a")
.invoke("attr", "href")
.should("be.oneOf", ["/hello", "/world"])
});
});

Related

cypress .each() .wrap() .invoke('text') takes long time

I am trying to check the items shown in my table for some assertions. My way is to put all of the items in an array and then iterate over that array.
My problem: All assertions already passed but the cypress runner still takes a lot of time to finish the cy.wrap(.invoke(text)) jobs.
Since this is a very core command of my cypress tests it would be great to have a more efficient function.
My command:
cy.get('table tbody').within(() => {
cy.get('tr').each((tr) => {
cy.wrap(tr.children().eq(index)).invoke('text').then((text) => {
text = text.trim();
arrayWithValuesOfTheList.push(text);
});
})
.then(() => {
//in here all the (quickly passing) assertions are...
});
});
Thanks for any help in advance. I appreciate you all!
You can avoid wrapping the value, will give some increase in speed but it's hard to say what is the slowest part.
const arrayWithValuesOfTheList = []
cy.get('table tbody tr')
.each($tr => {
arrayWithValuesOfTheList.push($tr.children().eq(index).text())
})
.then(() => {
//in here all the (quickly passing) assertions are...
})
})
You can do something like this. It gets the tr values one by one and matches in against a regex pattern.
cy.get('table tbody tr').each(($ele) => {
cy.wrap($ele.text().trim())
.should('match', /myregexp/)
.and('not.include', 'some text')
})
If you want to assert on individual cells, using .each($cell => {...}) is fine but if you want whole-column assertions (e.g sorted, unique-values) it gets difficult with .each().
To build something adaptable for various tests, take a look at the pattern here Sorting the table.
The idea is to create helper functions using .map() to selected table rows and columns.
const { _ } = Cypress
// helpers, reusable
const getColumn = (colIndex) => {
return (rows$) => {
const children$ = _.map(rows$, 'children')
return _.map(children$, `[${colIndex}]`)
}
}
const toStrings = (cells$) => _.map(cells$, 'textContent')
const toNumbers = (texts) => _.map(text, Number)
cy.get('table tbody tr') // rows of table
.then(getColumn(1)) // extract 2nd column
.then(toStrings) // get the text value
.then(toNumbers) // convert if assertions require numeric values
// whole-column assertion example
.should(values => {
const sorted = _.sortBy(values)
expect(values, 'cells are sorted 📈').to.deep.equal(sorted)
})
// individual value assertion
.should(values => {
values.forEach(value => {
expect(value).to.be.lt(100)
})
})
Addressing performance issue
If performance is poor, you can reduce the number of process steps at the Cypress command level by using jQuery-only commands.
This will avoid adding commands to the Cypress queue which is likely to be the slowest part
const arrayWithValuesOfTheList = []
cy.get('table tbody tr td:nth-child(2)') // 2nd col of each row
.then($tds => { // only jQuery methods inside
$tds.text((index, cellText) => {
arrayWithValuesOfTheList.push(cellText.trim())
})
})
.then(() => {
//in here all the (quickly passing) assertions are...
})
})

cypress - how to compare href to anchor text

I'm wondering how to test using cypress libary if the href attribute include the text from anchor tag. The code is as follow:
I have a div with a lot of anchors with data attribute.
<div>
#hash1
#hash2
#hash3
// and many more
</div>
I stared testing with check if each element have "#", href attribute with "/tag/" :
describe('Test', () => {
it('should navigate to link from hashtag', () => {
cy.visit('/');
cy.get('[data-cy="hashtag"]').each((item) => {
cy.wrap(item).contains('#').should('have.attr', 'href').and('contain', '/tag/');
})
})
});
but how can I check if href include the displayed text from a tag?
Once you've yielded the element from your cy.get(), you can reference it as a jQuery object. In this case, we'll want to get the text.
describe('Test', () => {
it('should navigate to link from hashtag', () => {
cy.visit('/');
cy.get('[data-cy="hashtag"]').each((item) => {
cy.wrap(item).contains('#').should('have.attr', 'href', `/tag/${item.text().replace('#', '')}`)
})
})
});
If you simply wanted to see if the href contains the text, you could use the jQuery attr function.
describe('Test', () => {
it('should navigate to link from hashtag', () => {
cy.visit('/');
cy.get('[data-cy="hashtag"]').each((item) => {
expect(item.text()).to.contain('#');
expect(item.attr('href')).to.contain(item.text().replace('#', ''));
})
})
});

Accessing text of the element using invoke() in each(). Cypress

Want to access a text from the group of the elements.
this approach doesn't work, the runner is giving an error saying that invoke is not a function
cy.get('div[class^="lalala"]')
.each(function($sec, i, $sects) {
$sec.find('header[class^="tatata"]')
.invoke('text').then((text) => {
let secText = text
cy.log(secText);
});
})
But without each() it is working when I am accessing any of the elements:
cy.get('div[class^="lalala"]').first()
.find('header[class^="tatata"]')
.invoke('text')
.then((text) => {
let secText = text
cy.log(secText);
});
})
How can I handle that?
$sec is a wrapped jQuery element and $sec.find() returns a jQuery element as well. You have to use cy.wrap to call invoke on it.
cy.get('div[class^="lalala"]').each(function ($sec, i, $sects) {
cy.wrap($sec.find('header[class^="tatata"]'))
.invoke('text')
.then((secText) => {
cy.log(secText)
})
})

Cypress custom command wont return value

I have a function that I want to add as a command so i can reuse it.
Its on cypress/support/commands.js:
Cypress.Commands.add("generatePassword", () => {
return 'randomstring';
}
);
Then on my test I want to use it as:
it("Visits page", () => {
const password = generatePassword();
cy.log({password})
// Here it logs this:
//{password: {chainerid: chainer146, firstcall: false}}
});
Any idea on how to get the actual value? Now i get this:
{chainerid: chainer146, firstcall: false}
Thanks.
Basically cypress works in promise chain and you're returning the promise chainerid from your custom command. You have to chain it to use in next statement. Use something like below.
it("Visits page", () => {
return cy.generatePassword().then(pwd => {
cy.log(pwd);
});
});

Looking for a way tu use Cypress fixtures for all my custom commands outside an it block

I'm building some custom commands and trying to use my fixtures data for all my commands. Right now I'm forced to define it inside an it block.
Looks similar to this:
it("Commands", () => {
cy.fixture("fixtureFile").as("data");
cy.get("#data").then((data) => {
Cypress.Commands.add('login', () => {
cy.visit("/login");
cy.get('#login-email').type(data.userEmail);
cy.get('#login-pass').type(data.userPass, {log: false});
cy.get('.btn').debug().click();
})
Cypress.Commands.add('createTagMedia', () => {
cy.get(".close").click();
cy.get("#form-field-name").type(data.releaseVersion);
cy.get(".form-group-adTag > .CodeMirror > .CodeMirror-scroll").type(data.mediaTag);
cy.get("#media-save-btn").click();
})
})
})
This it block is being count as a test case, Is there a better way to pass this for more than one command at the same time?
The workaround I found was to put everything inside a before block, for example:
before(() => {
cy.fixture("fixtureFile").as("data");
cy.get("#data").then((data) => {
Cypress.Commands.add('login', () => {
cy.visit("/login");
cy.get('#login-email').type(data.userEmail);
cy.get('#login-pass').type(data.userPass, {log: false});
cy.get('.btn').debug().click();
})
Cypress.Commands.add('createTagMedia', () => {
cy.get(".close").click();
cy.get("#form-field-name").type(data.releaseVersion);
cy.get(".form-group-adTag > .CodeMirror > .CodeMirror-scroll").type(data.mediaTag);
cy.get("#media-save-btn").click();
})
})
})
Is there a reason why you won't use the following:
import {data} from '../fixtures/FixtureFile'
Considering you have the following JSON file:
{
"data": {
"userEmail": "blah",
"userPass": "blah",
"releaseVersion": "1"
}
}
You can include this on your tests, commands (Cypress.custom.commands), etc.
before(() => {
const data = cy.fixture("fixtureFile");
cy.login(data);
cy.createTagMedia(data);
})
You could literally do something like the above. With your Cypress.Commands in your command.ts or js whichever you're using.
And make the commands take in a parameter. Then the above before hook would just be in your tests.

Resources