Cypress: testing that nothing more than asserted exists - cypress

Is it possible to test that nothing more than asserted exists? E.g. I have elements A, B and C. I'm asserting that
cy.get('element-a').should('exist');
cy.get('element-b').should('exist');
cy.get('element-c').should('exist');
Then I remove A and assert that
cy.get('element-a').should('not.exist');
But is there any way to test that no other elements e.g. D or Q, of which I might not even be aware, exist on the page?

You can get every descendent element (child, grandchild, great-grandchild, etc) using .find('*').
For example,
cy.visit('http://example.com');
cy.get('body')
.find('*')
.then(descendents => {
return [...descendents].map(d => d.tagName)
})
.should('deep.eq', ['DIV', 'H1', 'P', 'P', 'A'])
Note the tagNames are allcaps.
But it seems doubtful that you have ONLY tags such as element-a in your HTML. You can apply a filter, the exact code depends on what your selectors are (tagNames, classes, ids, data-cy attributes).
You may be better off using cypress snapshot or similar package listed on that page.

Related

How to Assert from multiple possibilities in Cypress? One option could be true out of multiple

I have a scenario, where i need to put Assertion on an element's text which could be true OR pass the test case, if the any 1 value is present out of many.
Let say, an element can contain multiple status' : 'Open', 'Create', 'In Progress' any of these could be true.
How can i implement this scenario and Assert with OR logical operator or any other way?
cy.get('element').should('have.text', 'Open' | 'Create')
It sounds like a one-of assertion, something like the following:
cy.get('element')
.invoke('text')
.should('be.oneOf', ['Open', 'Create'])
To do it you need to extract the text before the assertion.
For reference see chaijs oneOf
Asserts that the target is a member of the given array list. However, it’s often best to assert that the target is equal to its expected value.
These both Worked:
cy.get('element')
.invoke('text')
.should('satisfy', (text) => text === 'option1' || text === 'option2')
OR
cy.get('element')
.invoke('text')
.should('be.oneOf', ['option1', 'option2']);
Wodens answer is most valuable if you wish to make an assertion and have the test fail if one of them doesn't exist
However, if you want to check the text of the element that is visible and do separate things based on which one is available:
cy
.get('element')
.filter(':visible')
.invoke('text')
.then((text) => {
if(text.includes('Open')){
// Things that only occur when Open is present
} else if (text.includes('Create')){
// Things that only occur when Create is present
} else if (text.includes('In Progress')){
// Things that only occur when In Progress is present
} else {
// Things that happen when the options are exhausted
}
});

Cypress assert label and checkbox child elements

Issue I'm having is I have a list of items (all with the same selector)and I want to loop through these items and assert a text label on one child and a checkbox on another child of the same parent element match what I expect. I can easily solve this solution in Selenium, but struggling to find the answer in Cypress.
I found the answer to this myself, essentially using filters, which I hadn't used before, to find the parent with the child text I wanted, before checking the checkbox of another child. Ignore the If as it's not necessary for the solution
if (parentSelector) {
cy.get(parentSelector).filter((_, element) => {
return element.querySelector('h2').textContent === labelValue
}).then( element => {
cy.wrap(element).find(selector).should('not.be.checked')
})

How to query data-attribute for a whitespace-separated value in Cypress component tests?

When testing a complex component, I want to assign multiple whitespace-separated values to data-cy attribute, for example
<div data-cy="my-component disabled" />
and use the ~= attribute selector to query for the element:
cy.get('[data-cy~="my-component"]')
Now, having already queried for my-component, how can I further assert that it:
does contain "disabled" in data-cy
does not contain "disabled" in data-cy
in broader sense, does or does not satisfy a css selector?
I know I can explicitly re-query with all parameters for each assertion, eg.:
cy.get('[data-cy~="my-component"]:not([data-cy~="disabled"])').should('exist')
but this feels overly complicated and doesn't read very well - I want to query the element first, and further assert against it in a later step - for example:
cy.get('...').should(el => {
// assert here
})
The approach seems like a good one. If you used individual attributes, likely they would clash with other "native" attributes.
For example if data-cy="disabled" means the "Person" has a disability, but used unwrapped the browser would disable the element.
Ref Using data attributes
data-* attributes allow us to store extra information on standard, semantic HTML elements without other hacks such as non-standard attributes, or extra properties on DOM.
Also some frameworks (React) are fussy about the attributes allowed on an element.
You might be looking for a function to provide the selector for the test.
const asDataCy = (attrs) => {
return attrs.split(' ').map(attr => {
let op = '~'
if (item.charAt(0) === '!') {
op = '!'
attr = attr.slice(1)
}
return `[data-cy${op}="${attr}"]`
).join('')
}
cy.get(asDataCy('my-component !disabled'))
// [data-cy~="my-component"][data-cy!="disabled"])
The Chai-jQuery assertion expect($el).match(selector) does exactly that!
// should form
cy.get('[data-cy~="my-component"]')
.should('match', '[data-cy~="some-value"]')
.should('match', ':not([data-cy~="something-else"])')
// expect form
cy.get('[data-cy~="my-component"]')
.then(el => expect(el).to.match('[data-cy~="some-value"]'))
.then(el => expect(el).to.match(':not([data-cy~="something-else"])'))

How to get multiple elements with data-cy attributes

I have a table with all cells marked with the data-cy attribute as so: [data-cy="cell-${row}-${column}].
Individually I have no trouble to get each cell with the classic cy.get, for example cy.get("[data-cy=cell-2-17]").click(), but I try to test the click on multiple cells holding the control key but I couldn't achieve that.
In this answer it says to simply concat the selectors passed to cy.get but it didn't work for me, I tried with and without the " around the data-cy names:
cy.get('[data-cy="cell-2-17"][data-cy="cell-4-10"]').click({multiple: true, ctrlKey: true})`
Could you tell me how to get multiple elements using the data-cy attribute?
If you concatenate selectors (without space or + between), it means the element must have all those selectors.
There's not really a concise way to provide a list of required attributes.
Two things that help are wildcards and filters
const cellsWanted = ['cell-2-17', 'cell-4-10']
cy.get('[data-cy^="cell-"]') // wildcard anything that starts with "cell-"
.filter((index, el) => cellsWanted.some(cell => el.getAttribute('data-cy') === cell))
.click({multiple: true, ctrlKey: true})`
Multiple selectors with comma between
Just found an easier way using jquery multiple selectors
cy.get('[data-cy="cell-2-17"], [data-cy="cell-4-10"]')
.should('have.length', 2)
.click({multiple: true, ctrlKey: true})

Cypress - counting number of elements in an array that contain a specific string

Attempting to confirm that of all the schema in the head of a page exactly 3 of them should have a specific string within them. These schemas have no tags or sub classes to differentiate themselves from each other, only the text within them. I can confirm that the text exists within any of the schema:
cy.get('head > script[type="application/ld+json"]').should('contain', '"#type":"Product"')
But what I need is to confirm that that string exists 3 times, something like this:
cy.get('head > script[type="application/ld+json"]').contains('"#type":"Product"').should('have.length', 3)
And I can't seem to find a way to get this to work since .filter, .find, .contains, etc don't filter down the way I need them to. Any suggestions? At this point it seems like I either need to import a custom library or get someone to add ids to these specific schema. Thanks!
The first thing to note is that .contains() always yields a single result, even when many element match.
It's not very explicit in the docs, but this is what it says
Yields
.contains() yields the new DOM element it found.
If you run
cy.get('head > script[type="application/ld+json"]')
.contains('"#type":"Product"')
.then(console.log) // logs an object with length: 1
and open up the object logged in devtools you'll see length: 1, but if you remove the .contains('"#type":"Product"') the log will show a higher length.
You can avoid this by using the jQuery :contains() selector
cy.get('script[type="application/ld+json"]:contains("#type\": \"Product")')
.then(console.log) // logs an object with length: 3
.should('have.length', 3);
Note the inner parts of the search string have escape chars (\) for quote marks that are part of the search string.
If you want to avoid escape chars, use a bit of javascript inside a .then() to filter
cy.get('script[type="application/ld+json"]')
.then($els => $els.filter((index, el) => el.innerText.includes('"#type": "Product"')) )
.then(console.log) // logs an object with length: 3
.should('have.length', 3);

Resources