cypress-wait-until keeps retrying even though the value is true - testing

I have a Cypress test that is flaky due to the serial and heavily asynchronous nature of the thing being tested.
In the app, we have a workout card that contains a list of exercises. Each exercise has a list of sets. If all the sets are logged, the workout should display as logged as well.
So, the test has to serially expand each workout, log each set (which has to update the workout, fetch the updated fitness plan, and render the new style for the log icon) and then test that the new style was applied. This was flaky as there were often detached elements, and in some cases the log style would take too long to apply (or the response was slow), so I began implementing the cypress-wait-until package. The problem is, no matter how I try to structure the waitUntil, it retries a bunch of times even if the value evaluated is true, and then times out with a 'Timed out retrying' error.
Here is the code in question:
cy.getBySel('exerciseRow')
.each(row => {
cy.wrap(row)
.findBySel('exerciseRow-trigger')
.isAttached()
.click({ force: true });
cy.wrap(row)
.isAttached()
.findBySel('exerciseRow-trigger')
.parent()
.should('have.class', 'is-open')
.then($el => {
cy.wrap(row)
.isAttached()
.findBySel('setRow-log')
.each(setLog => {
cy.wrap(setLog)
.isAttached()
.click({ force: true })
.then(log => {
cy.waitUntil(() => {
cy.wrap(log)
.find('circle')
.then(circle => circle.css("stroke") === 'rgb(189, 249, 234)')
}, {interval: 1000, timeout: 15000})
});
});
});
})
.then(result => {
cy.getBySel('workout-card-log')
.find('circle')
.should('have.css', 'stroke', 'rgb(189, 249, 234)');
});
});

A plain .should() should be just as effective
.then(log => {
cy.wrap(log)
.find('circle', {timeout: 15000})
.should(circle => {
expect(circle.css("stroke")).to.eq('rgb(189, 249, 234)')
// or
expect(circle).to.have.css('stroke', 'rgb(189, 249, 234)')
})
// or
.should('have.css', 'stroke', 'rgb(189, 249, 234)')
})

Related

"after all" hook error while running test

This is my test body:
/// <reference types = "cypress" />
it('Testing story book button primary', function(){
cy.visit('https://storybook.prod.ublox-website.ch4.amazee.io/iframe.html?id=components-button--primary&viewMode=story')
cy.wait(1000)
cy.eyesOpen({
appName: 'Story book',
testName: 'check button primary',
});
cy.eyesCheckWindow();
cy.eyesClose();
});
I have attached a screenshot of my error at the end it displays this error( I have attached).
Can someone please let me know why I am getting this error? I am stuck.
Thanks in advance.
It would be difficult for someone to help debug this with the limited code provided I think. Mainly because I'm not sure what your after() block looks like, the error that is being throw isn't occurring in this test, it's occurring in the teardown code when body.success doesn't exist (what results in body.success existing?). Additionally eyesOpen and eyesCheckWindow and eyesClose seem to be custom commands specific to Applitools, I'd at least recommend adding that as a tag or edit your post to include that information because that isn't part of the general testers' Cypress workflow/plugin stack.
Other than that, I'd try adding cy.log(body.error) or console.log(body.error) or adding the after() block, and add the results to your question.
On a separate note, you can try using the Applitools example test structure
it('works', () => {
cy.visit('https://applitools.com/helloworld');
cy.eyesOpen({
appName: 'Hello World!',
testName: 'My first JavaScript test!',
browser: { width: 800, height: 600 },
});
cy.eyesCheckWindow('Main Page');
cy.get('button').click();
cy.eyesCheckWindow('Click!');
cy.eyesClose();
});
});
or their "best practice" example structure
describe('Hello world', () => {
beforeEach(() => {
cy.eyesOpen({
appName: 'Hello World!',
browser: { width: 800, height: 600 },
});
});
afterEach(() => {
cy.eyesClose();
});
it('My first JavaScript test!', () => {
cy.visit('https://applitools.com/helloworld');
cy.eyesCheckWindow('Main Page');
cy.get('button').click();
cy.eyesCheckWindow('Click!');
});
});
Both look like they're passing text into eyesCheckWindow but I also am not familiar with applitools so this could be useless information.

pouchdb live is a little too fast for realtime chat

I am using the current vuex mutation to sync to my pouch/ couchdb which is excellent. However
As I am typing into text box I can type too fast and this can mean letters are not sent to sync, which is annoying but not a killer however if I edit in the middle and type at speed sometimes cursor will jump to the end, I want live as its live text but I would like to poll a little slower does anyone have any suggests.... there was a suggestion of using since : 'now' but that doesnt seem to slow it down
syncDB: () => {
pouchdb.replicate.from(remote).on('complete', function () {
store.commit('GET_ALL_NODES')
store.commit('GET_MY_NODES')
store.commit('GET_POSITIONS')
store.commit('GET_CONNECTIONS')
store.commit('GET_EMOJI')
// turn on two-way, continuous, retriable sync
pouchdb
.sync(remote, {
live: true,
retry: true,
attachments: true,
})
.on('change', function () {
// pop info into function to find out more
store.commit('GET_ALL_NODES')
store.commit('GET_MY_NODES')
store.commit('GET_POSITIONS')
store.commit('GET_CONNECTIONS')
store.commit('GET_EMOJI')
})
.on('paused', function () {
// replication paused (e.g. replication up to date, user went offline)
// console.log('replication paused')
})
.on('active', function () {
// replicate resumed (e.g. new changes replicating, user went back online)
//console.log('back active')
})
.on('denied', function () {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function () {
// handle complete
})
.on('error', function (err) {
console.log(err)
})
})
},
I added a debounce via lodash to the text input which helps a lot it’s not perfect especially if you are editing in the middle of text but less jumps to the end and general start speedier typing isn’t an issue

Assert element exists after all XHR requests finished

I'm visiting a page which is fetching data Asynchronously (multiple XHR requests), and then asserting if a certain DOM element is visible/exists in the page.
So far I was only able to get the page and the data fetched with using cy.wait() either with an arbitrary time, or by aliasing the actual request, and using the promise-like syntax to make sure my cy.get() is done after the XHR response has completed.
Here is what doesn't work:
before(() => {
cy.login();
cy.server();
cy.route('/v0/real-properties/*').as('getRealPropertyDetails');
cy.visit('/real-properties/1/real-property-units-table');
});
beforeEach(() => {
Cypress.Cookies.preserveOnce('platform_session');
});
after(() => {
cy.clearCookies();
});
context('when viewport is below 1367', () => {
it('should be closed by default', () => {
cy.wait('#getRealPropertyDetails'); // the documentation says this is the way to go
sSizes.forEach((size) => {
cy.viewport(size[0], size[1]);
cy.get('.v-navigation-drawer--open.real-property-details-sidebar').should('not.exist');
});
});
Adding cy.wait(1000); in the before() or beforeEach() hooks also works, but this is not really an acceptable solution.
What works, but not sure if this is the way to do this (I would have to add this for every page, would be quite annoying) :
it('should be closed by default', () => {
cy.wait('#getRealPropertyDetails').then(() => {
sSizes.forEach((size) => {
cy.viewport(size[0], size[1]);
cy.get('.real-property-details-sidebar').should('not.be.visible');
});
});
});
I see that you have browser reloads there (beforeEach), which could potentially wipe out the route spy, but not sure why cy.wait().then would work. I would try switching from before to beforeEach though, creating things once is always trickier than letting them be created before each test

Jest unresolved promise do not fail

Jest docs says:
Unresolved Promises
If a promise doesn't resolve at all, this error might be thrown:
(and so on)
In my case this not happen.
I have this test:
test('detect infinite loop', () => {
expect.assertions(1);
const vastPromise = VastUtils.parseFromUrl(infiniteLoopUrl);
const expectedError =
new VastError(VastErrorCodes.WRAPPER_LIMIT_REACHED);
return expect(vastPromise).rejects.toEqual(expectedError);
});
VastUtils simply fetch an XML located at infiniteLoopUrl, parse it, and if this xml point to another xml, VastUtils follow the link, parse the new xml, merge them and repeat the process.
Now, infiniteLoopUrl point to an XML that refers itself, so it is an infinite loop.
"correctly", the code follow xml link infinitely, and never resolve or reject the promise.
I expect above test fail after a certain timeout, but it didn't.
Someone can help me?
Thanks
EDIT:
I'm trying to reproduce an infinite Promise loop with a smaller example, and this is what i've noticed:
this test correctly FAIL after 5s:
test('Promise2', () => {
const genPromise = (): Promise<void> => {
return new Promise((res) => {
setTimeout(() => {
res();
}, 200);
})
.then(() => {
return genPromise();
});
};
const vastPromise = genPromise();
const expectedError =
new VastError(VastErrorCodes.WRAPPER_LIMIT_REACHED);
return expect(vastPromise).rejects.toEqual(expectedError);
});
This test DO NOT FAIL after 5s (jest remain in an infinite loop)
test('Promise', () => {
const genPromise = (prom: Promise<void>): Promise<void> => {
return prom
.then(() => {
return genPromise(Promise.resolve());
});
};
const vastPromise = genPromise(Promise.resolve());
const expectedError =
new VastError(VastErrorCodes.WRAPPER_LIMIT_REACHED);
return expect(vastPromise).rejects.toEqual(expectedError);
});
Apparently these are similar, but I don't understand the difference that cause the jest infinite loop...
Ok, I've understand the problem.
The cause is the mono thread nature of js.
In the two examples of the edit section, te first one have a timeout so there is a moment whent jest take the control and could check the timeout.
In the second one no, so jest never check the timeout.
In my real case, the problem was the fake server: it was created as:
server = sinon.fakeServer.create({
respondImmediately: true
});
respondImmediately make sinon respond syncroniously, so jest never have the control.
Creating it as:
server = sinon.fakeServer.create({
autoRespond: true
});
sinon respond after 10ms and jest can check the time passing

Cycle.js HTTP sending multiple requests after adding loading indicator

I have been attempting to create some cycle.js examples as nested dialogues, and switching between them using a select box.
One of the dialogues is a clone of the official Github HTTP Search example.
The other dialogue a more basic one which does not have HTTP, only DOM.
I feel like I wrapped my head around switching between the 2, but I'm fairly new to Rx so that may be done incorrectly or naively.
It all seemed to work well until I added a loading indicator to the search page.
To do that, I turned this:
const vTree$ = responses.HTTP
.filter(res$ => res$.request.indexOf(GITHUB_SEARCH_API) === 0)
.flatMapLatest(x => x)
.map(res => res.body.items)
.startWith([])
.map(results =>
h('div.wrapper', {}, [
h('label.label', {}, 'Search:'),
h('input.field', {type: 'text'}),
h('hr'),
h('section.search-results', {}, results.map(resultView)),
])
)
Into this:
const searchResponse$ = responses.HTTP
.filter(res$ => res$.request.indexOf(GITHUB_SEARCH_API) === 0)
.flatMapLatest(x => x)
.map(res => res.body.items)
.startWith([])
const loading$ = searchRequest$.map(true).merge(searchResponse$.map(false))
// Convert the stream of HTTP responses to virtual DOM elements.
const vtree$ = loading$.withLatestFrom(searchResponse$, (loading, results) =>
h('div.wrapper', {}, [
h('label.label', {}, 'Search:'),
h('input.field', {type: 'text'}),
h('span', {}, loading ? 'Loading...' : 'Done'),
h('hr'),
h('section.search-results', {}, results.map(resultView)),
])
)
I now have 2 issues
The 'checkbox value set to' and 'route changed' messages are logged
twice for every change of the checkbox.
The HTTP request log only
fires once, but if you watch the network activity in Dev Tools
you'll see two GET requests simultaneously.
Thanks for any help!
EDIT: Solved my own problem. See answer below.
I ended up solving this problem by rebuilding my entire application from scratch until I found the breaking point.
What I learned is that you need to add .share() to any observable stream which will be subscribed/mapped/etc by more than one downstream observable.
const searchRequest$ = DOM.select('.field').events('input')
.debounce(500)
.map(ev => ev.target.value.trim())
.filter(query => query.length > 0)
.map(q => GITHUB_SEARCH_API + encodeURI(q))
.share() //needed because multiple observables will subscribe
// Get search results from HTTP response.
const searchResponse$ = HTTP
.filter(res$ => res$ && res$.request.url.indexOf(GITHUB_SEARCH_API) === 0)
.flatMapLatest(x => x) //Needed because HTTP gives an Observable when you map it
.map(res => res.body.items)
.startWith([])
.share() //needed because multiple observables will subscribe
//loading indication. true if request is newer than response
const loading$ = searchRequest$.map(true).merge(searchResponse$.map(false))
.startWith(false)
.share()
//Combined state observable which triggers view updates
const state$ = Rx.Observable.combineLatest(searchResponse$, loading$,
(res, loading) => {
return {results: res, loading: loading}
})
//Generate HTML from the current state
const vtree$ = state$
.map(({results, loading}) =>
.........