I have a page with a few Results panels, each panel has its own delete button.
I wrote a Cypress test to test the delete process, the test works as expected, the panel gets deleted:
cy.get('div[data-test="Results"]')
.first()
.within(() => {
cy.get('p[data-test="Contact ID"]').then($match => {
contactID = $match.html();
cy.get('button[data-test="Delete Contact"]')
.click()
.get('div[data-test="Delete Record Modal"]')
.should('be.visible')
.get('button[data-test="Confirm Deletion"]')
.click();
});
});
Next I'm trying to detect if the correct panel got deleted.
How can I iterate through all the <p />s of all panels and make sure none of them has a contactID equal to the one that was deleted?
I tried this:
cy.get('p[data-test="ContactID"]').then($match2 => {
expect($match2.text()).not.to.eq(contactID);
});
But in $match2 I get all contacts ids all together for example: 12345678 instead of 1234 and 5678
You can use each:
cy.get('p[data-test="ContactID"]').each(($match) => {
cy.wrap($match).invoke('text').should('not.eq', contactID)
})
invoke calls a function on the subject, in this case, .text()
the chained .should makes an assertion on that text
this will retry the assertion until it passes or times out (see retry-ability) due to the cy.wrap
Related
I have a search input and result display area that are being handled by Ajax call. When user enter a keyword, Ajax call the backend and return HTML string. The JS handler then appends the response HTML into result display area.
Here my steps:
Search input: Vancouver (auto input by browser location)
Result: Welcome to Vancouver
Type into search input: Calgary
Expected: Welcome to Calgary
// Test case
Cypress.Commands.add('assessHeadingInfo', (options) => {
cy.fixture('selectors/index-page').then((selectors) => {
cy.xpath(selectors.head_info).then((heading) => {
cy.searchForArea('Calgary'); // Steps to input keyword and click search
cy.get(heading)
.children('h1')
.should('be.visible');
cy.get(heading)
.children('h1')
.invoke('text')
.should('equals', 'Welcome to Calgary');
});
});
});
Test error:
AssertionError
Timed out retrying after 4000ms: expected 'Welcome to Vancouver' to equal 'Welcome to Calgary'
However, the visual screenshot showed that the 'Welcome to Calgary' text was displayed while the test could not see.
I did follow the guide from Cypress app
Timed out retrying after 4000ms: cy.should() failed because this element is detached from the DOM.
...
You typically need to re-query for the element or add 'guards' which delay Cypress from running new commands. Learn more
I added 'a guard', cy.wait()...but nothing works.
Would you please teach me how to handle this issue?
Thank you.
UPDATE SOLUTION:
Thanks for #jhelguero helped me to find out.
Solution 1:
I added this and it worked well.
cy.contains('h1', 'Welcome to Calgary')
.should('be.visible');
Because I have put all the tests in block of
cy.xpath(selectors.head_info).then((heading) => {
...
});
So, when I do cy.get(heading) it caught the element before Ajax done. It was 'Welcome to Vancouver' instead.
Solution 2:
Then, I separated test steps like this.
cy.searchForArea('Calgary');
cy.xpath(selectors.head_info)
.children('h1')
.should('have.text', 'Welcome to Calgary');
and it was successful.
Summary:
Do this:
cy.xpath({selector}).should(have.text,'ABC');
cy.xpath({selector}).find('h1').should('have.text, '123');
DON'T do this:
cy.xpath({selector}).then((ele) => {
cy.get(ele).should(have.text, 'ABC');
cy.get(ele).find('h1').should('have.text, '123');
});
It looks like your DOM rerenders after the ajax call and thus the element('Welcome to') you were previously reference has been detached.
To fix this you will need to re query using the same selector after the ajax call is completed. You will not need cy.xpath(selectors.head_info).then().
Cypress.Commands.add('assessHeadingInfo', (options) => {
cy.fixture('selectors/index-page').then((selectors) => {
cy.searchForArea('Calgary'); // Steps to input keyword and click search
// use .contains() to search a selector with regex text
cy.contains(selectors.head_info, /Welcome to Calgary/)
.should('be.visible');
});
});
If you visually confirmed the text "Welcome to Calgary", the problem might be that .should('equals', 'Welcome to Calgary') is not retrying enough of preceding steps.
Try shortening the query.
You can also increase the timeout if you notice a lag before the text appears.
cy.get(`${heading} > h1`, {timeout:10_000})
.should('have.text', 'Welcome to Calgary')
I am writing some basic cypress tests and I just want to make sure that a table we have has all of the correct columns and that any data is rendering.
I know .contains() checks the element selection AND allows text matching, but there could be all sorts of data in this table, I just want to make sure something renders from the backend.
Any idea how to check if there is any (non-specific) value?
Cheers!
describe('Data Source Table renders properly', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
cy.visit('http://localhost:3000/source')
// Assert we are on the correct page /source
cy.url().should('include', '/source')
})
it('Data Source header exists', () => {
cy.contains('Data Source')
})
it('Data Source table exists, column headers are correct and there is data', () => {
cy.get('table').should('exist')
cy.wait(2000)
cy.get('table').contains('th', 'Ip')
cy.get('table').contains('th', 'Station')
cy.get('table').contains('th', 'Table')
cy.get('table').contains('th', 'Status')
})
})
You are right, .contains() allows text matching. It also allows regex matching which you can apply for your situation.
// this will check all td's will have any text
// here is regex example regexr.com/6jmi6
cy.contains('td', /\w/g).should('be.visible')
This is more concise, IMO less prone to error
cy.get('table thead th')
.each($th => {
expect($th.text()).not.to.be.empty
})
You can remove cy.get('table').should('exist') and cy.wait(2000)
I have a table and I need to fill in each cell.
First, I am getting the first row and with find() method each cell.
But the issue am I having is that the element is somehow getting detached from the DOM:
cy.find() failed because this element is detached from the DOM.
The previous command that ran was:
cy.wrap()
This is my code:
cy.get('.ag-center-cols-container .ag-row').each(($el, index, $list) => {
cy.wrap($el).find('div[col-id="account"]').type("30000")
})
I tried different approaches but did not find the solution to it.
For example:
cy.get('#row').each(($el, index, $list) => {
cy.wrap($el).should($el => {
expect(Cypress.dom.isAttached($el), 'is attached').to.eq(true)
})
.find('div[col-id="account"]').type("30000")
})
has the error:
Timed out retrying after 10000ms: is attached: expected false to equal true
I am new to Cypress and as far as I understand, I need to use cy.wrap() in this situation, in order to be able to use Cypress commands on the element.
I have 2 rows in this table and I was trying to use each just to make a reusable function in case I want to add more rows with data later in time.
Any advice is appreciated.
Thank you!
Since all your #row get packaged in jquery elements at the get() command, some page refreshing code must happen in the background that makes these jquery elements detached when you try to recycle them in your wrap() to get your cell.
I once tried to re get() the elements inside the loop and it worked for me.
cy.get('#row').each(($el, index, $list) => {
cy.get('#row').eq(index).find('div[col-id="account"] input').type('30000');
});
There are a few things you can try:
Using a visible assertion with a timeout:
cy.get('.ag-center-cols-container.ag-row')
.should('be.visible')
.each(($el, index, $list) => {
cy.wrap($el)
.find('div[col-id="account"]')
.should('be.visible', {timeout: 6000})
.type('30000')
})
Typing with {force: true}:
cy.get('.ag-center-cols-container.ag-row')
.should('be.visible')
.each(($el, index, $list) => {
cy.wrap($el)
.find('div[col-id="account"]')
.should('be.visible', {timeout: 6000})
.type('30000', {force: true})
})
I'm having trouble recreating this test.
Problem:
It seems that Cypress is only visiting one link from this list whilst looping over each item.
Notes:
I added the length check to make sure that the array of nodes is the correct size
The code within each seems to work fine, since the test runner navigates to the first link
I was looking into the .next() methods, but that returns the next DOM nodes. Still not clear if that might be the issues here
seems like there's no iterator within the each() method
Test Case
GIVEN a personal website
WHEN when I navigate to the /blog page
THEN Cypress find the list of blog posts
AND checks the number of total posts
THEN Cypress loops over those list items
THEN Cypress collects the href
THEN Cypress visits that page
THEN Cypress checks that href includes 'posts'
AND wait 1s
Test Code
describe("Visual regression on /posts/{id}", () => {
sizes.forEach((size) => {
it(`Should match screenshot, when '${size}' resolution'`, () => {
cy.visit("/blog")
cy.get("ul > li > a")
.should("have.length", 9)
.each((element) => {
cy.wrap(element)
.invoke("attr", "href")
.then((href) => {
cy.visit(href);
});
cy.wrap(element)
.should("have.attr", "href")
.and("include", "posts"
cy.wait(1000);
});
});
});
});
Solution
cy.visit("/blog");
cy.get("ul > li > a").should("have.length", 9);
cy.get("ul > li > a").each(($element) => {
cy.wrap($element)
.invoke("attr", "href")
.then((href) => {
cy.wrap(href).should("exist")
cy.visit(href);
cy.wait(2000);
});
});
I seems like the block below is letting Cypress loose track of the current element.
.should("have.length", 9)
Once I split out the rules of the test, I'm no longer seeing any issues and Cypress is correctly navigating through all the pages I need.
I've a unpredictable list of rows to delete
I simply want to click each .fa-times icon
The problem is that, after each click, the vue.js app re-render the remaining rows.
I also tried to use .each, but in this cas I got an error because element (the parent element, I think) has been detached from DOM; cypress.io suggest to use a guard to prevent this error but I've no idea of what does it mean
How to
- get a list of icons
- click on first
- survive at app rerender
- click on next
- survive at app rerender
... etch...
?
Before showing one possible solution, I'd like to preface with a recommendation that tests should be predictable. You should create a defined number of items every time so that you don't have to do hacks like these.
You can also read more on conditional testing, here: https://docs.cypress.io/guides/core-concepts/conditional-testing.html#Definition
That being said, maybe you have a valid use case (some fuzz testing perhaps?), so let's go.
What I'm doing in the following example is (1) set up a rendering/removing behavior that does what you describe happens in your app. The actual solution (2) is this: find out how many items you need to remove by querying the DOM and checking the length, and then enqueue that same number of cypress commands that query the DOM every time so that you get a fresh reference to an element.
Caveat: After each remove, I'm waiting for the element (its remove button to be precise) to not exist in DOM before continuing. If your app re-renders the rest of the items separately, after the target item is removed from DOM, you'll need to assert on something else --- such as that a different item (not the one being removed) is removed (detached) from DOM.
describe('test', () => {
it('test', () => {
// -------------------------------------------------------------------------
// (1) Mock rendering/removing logic, just for the purpose of this
// demonstration.
// -------------------------------------------------------------------------
cy.window().then( win => {
let items = ['one', 'two', 'three'];
win.remove = item => {
items = items.filter( _item => _item !== item );
setTimeout(() => {
render();
}, 100 )
};
function render () {
win.document.body.innerHTML = items.map( item => {
return `
<div class="item">
${item}
<button class="remove" onclick="remove('${item}')">Remove</button>
</div>
`;
}).join('');
}
render();
});
// -------------------------------------------------------------------------
// (2) The actual solution
// -------------------------------------------------------------------------
cy.get('.item').then( $elems => {
// using Lodash to invoke the callback N times
Cypress._.times($elems.length, () => {
cy.get('.item:first').find('.remove').click()
// ensure we wait for the element to be actually removed from DOM
// before continuing
.should('not.exist');
});
});
});
});