Network-polling in Cypress - testing

I have a webapp that processes some data in the background which can take quite some time (~5 minutes). The status of the task can be checked using a simple GET request.
I would like Cypress to send said get request, then wait in case the response does not contain the proper data and repeat this process until a timeout is reached or a maximum number of retries was performed. In case I get the proper reply, I would like to stop retrying and keep on going with my tests.
Does anybody know how that can be achieved?

You can either used recurse function or use cypress-recuse which allows some nifty configs.
import { recurse } from 'cypress-recurse'
recurse ({
() => {
return cy.request('https://www.your-url.com/')
.its('response')
},
(response) => {
// whatever checks need to complete before proceeding
},
{
// can alter configurations
timeout: 30_000,
delay: 3_000,
limit: 7
}
)

Related

Abort an Updates.fetchUpdateAsync() after a certain time [Expo/React native]

Expo React Native SDK Version: 46
Platforms: Android/iOS
Package concerned : Expo.Updates
Hello everyone, I want to programmatically check for new updates, without using the fallbackToCacheTimeout in app.json that will trigger the check of the new updates when the application is launched because like that I can't put a custom loading page.
So by doing this all by code as follow :
try{
const update = await Updates.checkForUpdateAsync();
if(update.isAvailable){
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}else{}
}catch(err){}
But I want to be able to abort all those calls after a certain time (thus, the user that have a bad connection can use the app without waiting a very long time).
I check the documentation and I cannot found any method that allow this.
I dont't think it's possible to cancel a Promise for now in Javascript, or maybe any connection ?
Or does the "fallbackToCacheTimeout" value in the app.json will automatically apply to the fetch updates call of the Expo API?
Do someone have any idea how to do it ? :(
First of all I am assuming you have set updates.checkautomatically field to ON_ERROR_RECOVERY in app.json or app.config.js file. If not, please check the documentation. The reason why you need this is to avoid automatic updates which can also block your app on splash screen.
Updated Solution
Because of the limitation in javascript we can't cancel any external Promise (not created by us or when its reject method is not exposed to us). Also the function fetchUpdateAsync exposed to us is not a promise but rather contains fetch promise and returns its result.
So, here we have two options:
Cancel reloading the app to update after a timeout.
But note that updates will be fetched in background and stored on
the device. Next time whenever user restarts the app, update will
be installed. I think this is just fine as this approach doesn't
block anything for user and also there is a default timeout for http
request clients like fetch and axios so, request will error out in
case of poor/no internet connection.
Here is the code:
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
const updateFetchPromise = Updates.fetchUpdateAsync();
const timeoutInMillis = 10000; // 10 seconds
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject("timedout"), timeoutInMillis))
// This will return only one Promise
Promise.race([updateFetchPromise, timeoutPromise])
.then(() => Updates.reloadAsync())
.catch((error) => {
if (error === 'timedout') {
// Here you can show some toast as well
console.log("Updates were not cancelled but reload is stopped.")
} else if (error === 'someKnownError') {
// Handle error
} else {
// Log error and/or show a toast message
}
})
} else {
// Perform some action when update is not available
}
} catch (err) {
// Handle error
}
Change the expo-updates package just for your app using a patch
Here you can return a cancel method with Updates.fetchUpdateAsync() and use it with setTimeout to cancel the fetch request. I won't be providing any code for this part but if you are curious I can definitely provide some help.
Please refer this section to understand use of fallbackToCacheTimeout in eas updates.
Old solution:
Now, for aborting or bypassing the promise i.e. Updates.fetchUpdateAsync in your case. You can basically throw an Error in setTimeout after whatever time duration you want, so that, catch block will be executed, bypassing the promises.
Here is the old code :
try{
const update = await Updates.checkForUpdateAsync();
if(update.isAvailable){
// Throw error after 10 seconds.
const timeout = setTimeout(() => { throw Error("Unable to fetch updates. Skipping..") }, 10000)
await Updates.fetchUpdateAsync();
// Just cancel the above timeout so, no error is thrown.
clearTimeout(timeout)
await Updates.reloadAsync();
}else{}
}catch(err){}

Cypress - Unable to get the Response Headers - API Automation

I have an API automation test suite using Cypress and one of the issue I am facing in one of the test is to validate the response headers.
For some reason, I am not able to read the response headers using Cypress.
The code is below
cy.request({
method:'GET',
url:Cypress.env("Authorisationurl")+tokenId+'&decision=allow&acr_values=1',
followRedirect: false,
headers:{
'Accept': "/*"
}
}).then((response) => {
const rbody = (response.body);
cy.log(response.status)
//THIS GOT ASSERTED TO TRUE
expect(response.status).to.equal(302)
//OPTION1
cy.wrap(response.headers['X-Frame-Options']).then(() => {
return response.headers['X-Frame-Options'];
});
//OPTION2
return response.headers['X-Frame-Options']
//OPTION3
return response.headers
})
None of the above options gives me the header information. Infact I am confused with the order of execution too.
This is my output.
for the following code.
const rbody = (response.body);
cy.log(response.status)
cy.log(response)
expect(response.status).to.equal(302)
cy.log(response.headers)
cy.log(response.headers['X-Frame-Options'])
return response.headers['X-Frame-Options']
Also, not very sure what Object{9} indicates. Can anyone please explain what is happening here.
I am aware of Cypress flow of execution and the code is written in then block as a call back function.
Option 3 is very scary as it gives an error
cy.then() failed because you are mixing up async and sync code.
In your callback function you invoked 1 or more cy commands but then returned a synchronous value.
Cypress commands are asynchronous and it doesn't make sense to queue cy commands and yet return a synchronous value.
You likely forgot to properly chain the cy commands using another cy.then().
The value you synchronously returned was: Object{9}
Can anyone please help me here as in what is the correct way of doing it. I know Cypress is very quick and easy to use but to move away from Selenium, we need to make coding easier for developers with meaningful error message. Object{9} is not very helpful.
Also, Do I need to use Cy.log ? As the sequence of prints is not what I have written in the code. Thanks very much for your time on this.
Please use like this:
JSON.parse(JSON.stringify(response.headers))["X-Frame-Options"];
The "mixing async and sync code" message is basically saying you should keep the .then() callback simple.
But you can chain more than one .then() to run the async and sync code separately.
Use an alias to "return" the value. Since cy.request() is asynchronous, you will need to wait for the value and the alias pattern is the most straight-forward way to do this reliably.
WRT Object{9}, it's the result of the way Cypress logs complex objects.
Don't use cy.log() to debug things, use console.log().
cy.request( ... )
.then(response => {
expect(response.status).to.equal(200) // assert some properties
})
.then(response => response.headers) // convert response to response.headers
.as('headers')
cy.get('#headers')
.then(headers => {
console.log(headers)
})
Since this was originally posted a year ago and Cypress has had many versions since then, the behavior may have changed, however this works in Cypress 11.
You can access the response.headers array as you would normally expect, however the casing of the header name was not as expected, Postman reported the header as X-Frame-Options, but Cypress would only allow me to access it if I used a lower-cased version of the header name (x-frame-options).
cy.request({
url: '<Url>',
method: 'GET',
failOnStatusCode: false
})
.then((response) => {
expect(response.status).to.eq(200);
expect(response.headers["x-frame-options"]).to.equal("SameOrigin");
})
.its('body')
.then((response) => {
// Add your response testing here
});
});

Nuxt JS - reuse async call (axios get) within mounted?

If I am rendering a page with Nuxt, Vue, and Axios - is there a way to reuse the asyncData request (or data)?. For example, if I render a response, and the user takes an action on the page to filter, sort, etc. the data, can I reuse the same data to render again - or do I need to make a new call in mounted?
export default {
asyncData ({ env, params, error }) {
return axios.get(`${env.cockpit.apiUrl}/collections/get/cat_ruleset?token=${env.cockpit.apiToken}&simple=1&sort[ruleid]=1`)
.then((res) => {
return { catrules: res.data }
})
.catch((e) => {
error({ statusCode: 404, message: 'Post not found' })
})
},
mounted() {
},
methods: {
}
}
Of course you can reuse it. The simplest way would be to store the result somewhere (anywhere, really, but your store would be a good storage candidate) and change your method to:
asyncData ({ env, params, error }) {
return X ? Promise.resolve(X) : axios.get(...)
}
... where X is the stored result of your previous call.
But you don't have to.
Because, by default, the browser will do it for you. Unless you specifically disable the caching for your call, the browser will assume making the same call to the server will yield the same result if you do it within the number of seconds set in max-age of Cache-control.
Basically, the browser returns the previous result from cache without making a call to the server, so the optimization you're after is already performed by the browser itself unless you specifically disable it.
You can easily spot which calls were served from cache and which from server by looking in the Network tab of DevTools in Chrome. The ones from cache will have (memory cache) in the Size column:
... and will have a value of 0ms in Time column.
If you want control over when to call the server and when to serve a cached result — most browsers have a limit on max-age (see link above) — you could (and should) store the result of your previous call and not rely at all on the browser cache (basically the internal check inside the method, which I suggested at the top).
This would enable you to avoid making a call long time after the cache max-age has passed, because you already have the data, should you choose to do so.

Cypress - log response data from an request after a click()

Although I know this may not be considered as a best practice, but what I want to achieve is to silently delete a record from a database after the same was created throughout UI. In htat way I want to keep our test environment clear as much as possible and reduce the noise of test data.
After my tests create a new record by clicking over the UI, I wait for POST request to finish and then I would like to extract the id from the response (so I can reuse it to silently delete that record by calling the cy.request('DELETE', '/id')).
Here's a sample test I have put on as a showcase. I'm wondering why nothing is logged in this example.
it('GET cypress and log', () => {
cy.server()
.route('**/repos/cypress-io/cypress*')
.as('getSiteInfo');
cy.visit('https://www.cypress.io/dashboard');
cy.get('img[alt="Cypress.io"]')
.click()
.wait('#getSiteInfo')
.then((response) => {
cy.log(response.body)
})
})
As far as I can see from here https://docs.cypress.io/api/commands/wait.html#Alias this should be fine.
your code contains two problems.
First:
The click triggers a new page to be loaded but cypress does not wait until the PageLoad event is raised (because you do not use visit). On my PC the Request takes about 5 seconds until it is triggered after the click. So you should use wait(..., { timeout: 10000 }).
Second:
wait() yields the XHR object, not the response. So your code within then is not correct. Also the body is passed as object. So you should use JSON.stringify() to see the result in the command log.
This code works:
describe("asda", () => {
it('GET cypress and log', () => {
cy.server()
.route('**/repos/cypress-io/cypress*')
.as('getSiteInfo');
cy.visit('https://www.cypress.io/dashboard');
cy
.get('img[alt="Cypress.io"]')
.click()
.wait('#getSiteInfo', { timeout: 20000 })
.then((xhr) => {
cy.log(JSON.stringify(xhr.response.body))
})
})
})
Instead of route and server method, try intercept directly

pouchdb + vuex update live with changes

I have an app that takes updates into VUEX store and syncs those change from pouchdb to couchdb. Which is great but now I need to have two clients connected and see the change in near realtime.
So I have the https://pouchdb.com/guides/changes.html API which I can use to listen for changes to the DB and when that happens call a action which mutates the vuex state on Client 2. Code below.
However the bit I cannot seem to work out is this code is not just listening all the time ? So where should I put this in Vue to ensure that it hears any changes. I can call it when I make a state change and I see that it hears the change but ofcourse I want to trigger a state change on client 2, without them having to make change. Do I need a timer ? The pouch docs seem to suggest this changes api should be able to update UI based on a change to the data, which I can probably call with a button press to check for changes ...but I want to listen in near realtime ?
pouchdb
.changes({
since: 'now',
include_docs: true
})
.on('change', function(change) {
// received a change
store.commit('CHANGE_STATE', change.doc.flavour)
})
.on('error', function(err) {
// handle errors
console.log(err)
})
Your explanation is a bit fuzzy in that you talk about client 2 without ever mentioning client 1. I assume client 2 is a passive listener and client 1 is where data is changed. If I remember correctly from when I was building my Vue / PouchDB project last year I looked into how to coordinate the Store and the Database, and then thought, "Why bother? They're just two kinds of local storage". As long as changes in client 1 replicate to your Couch server and client 2 detects those server side changes and writes them into reactive variables, they'll propagate to the UI.
I used replicate.to() for storing client-side changes and replicate.from() to detect server-side changes. The replicate() functions have their own timer, constantly monitoring the changes queue, so you don't need to roll your own.
This is what I ended up doing !
actions: {
SYNC_DB() {
// do one way, one-off sync from the server until completion
pouchdb.replicate.from(remote).on('complete', function(info) {
// then two-way, continuous, retriable sync
pouchdb
.sync(remote, { live: true, retry: true })
.on('change', function(info) {
store.commit('CHANGE_STATE', info.change.docs[0].flavour)
})
.on('paused', function(err) {
// replication paused (e.g. replication up to date, user went offline)
})
.on('active', function() {
// replicate resumed (e.g. new changes replicating, user went back online)
})
.on('denied', function(err) {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function(info) {
// handle complete
})
.on('error', function(err) {
// handle error
})
})
},