How to read a response in Cypress? - javascript

I am trying to write an E2E test using Cypress 12.3 for a web application. During a certain part of the journey, the app makes a GET request to the endpoint "api/v2/accountApplication/getApplicationInfo?uuid=xxxxxxx". The response from this request includes a field called "abChoice.welcome" which has a value of either 'a' or 'b'. This value is used for A/B testing in my Vue app. The structure of the response is as follows:
{
"resultStatus": true,
"errorCode": 0,
"errorMessage": null,
"resultData": {
"abChoice": {
"welcome": "a"
}
}
}
I am trying to write a test that checks the response from this request and makes different assertions based on the value of "abChoice.welcome". I have attempted to use the cy.intercept command to check the response, but it is not working as expected. I also tried creating an alias for the request and using cy.wait(#myAliasName), but Cypress threw an error and said the request was never made, even though I can see the request in the logs.
describe('A/B testing', () => {
it('shows A or B pages', () => {
cy.intercept('GET', '**/accountApplication/getApplicationInfo', req => {
const { body } = req
cy.log(body)
if (body.resultData.abChoice.wlecome === 'a') {
cy.log('A')
// assert something
} else {
cy.log('B')
// assert something
}
})
})
})
The log shows the following, so the request is definitely being made. (I've removed sensitive information)
(xhr)GET 200 /api/v2/xxxx/accountApplication/getApplicationInfo?uuid=xxxx

You may have the same issue as here Cypress intercept() fails when the network call has parameters with '/'.
In fact it's not "/" that causes the issue but the parameter section (anything after ?), it would be considered as part of the URL.
As per reference question, use a regex or pathname instead of url (default).
cy.intercept('GET', /\/accountApplication\/getApplicationInfo/, req => {
or with pathname, parameters are excluded from the match
cy.intercept({method:'GET', pathname: `**/accountApplication/getApplicationInfo`}, req => {

The reason your cy.wait() does not succeed and your validation is never run is because your intercept's request url is never matched. This is because your url does not include the query parameters.
Assuming that the query parameter uuid is not relevant to your intercept, you could do something like the following, adding a wildcard onto the end of your existing url:
cy.intercept('GET', '**/accountApplication/getApplicationInfo*', (req) => { ... });
If uuid were relevant, you could simply attach it to the end of url matched on:
cy.intercept('GET', '**/accountApplication/getApplicationInfo?uuid=*', (req) => { ... });
If you weren't assured that uuid was going to be the first query parameter, you would need to update the "simple-string" url method to using a RouteMatcher.
cy.intercept({
pathname: '**/accountApplication/getApplicationInfo'
query: {
uuid: 'foo'
},
method: 'GET'
}, (req) => { ... });

Related

Cypress: How to check query params in fetch request?

I am trying to intercept a fetch request and assert that the payload / query params are correct. I have searched the cypress documentation but everything mentioned is setting query params. I for example, need to assert that a fetch request such as https://helloWorld.com/proxy/service has query parameters /payload of ?service=request&layers=demo. Is there a way to do this?
I've tried almost everything but something similar to this is what I'm shooting for. Any ideas?
cy.location("https://helloWorld/proxy/service").should((loc) => {
expect(loc.search).to.eq('?service=request&layers=demo')
})
Setting up an intercept can check the shape of the request
cy.intercept('https://helloworld.com/proxy/service').as('requestWithQuery')
// trigger the request
cy.wait('#requestWithQuery').then(interception => {
expect(interception.req.body.query.includes('service=request').to.eq(true)
})
I'm not sure if the assertion above is exactly what you need, but put a console.log(interception.req) to check out what you need to assert.
The intercept can also specify the query
cy.intercept({
pathname: 'https://helloworld.com/proxy/service',
query: {
service: 'request',
layers: 'demo'
},
}).as('requestWithQuery')
// trigger the request
cy.wait('#requestWithQuery')
By the way your use of cy.location() is incorrect, you would use
cy.location('search').should('eq', '?service=request&layers=demo')
// or
cy.location().should((loc) => {
expect(loc.href).to.eq(
'https://helloworld/proxy/service?service=request&layers=demo'
)
})
But the app would already have to have navigated to https://helloWorld/proxy/service and it's not clear from your question if that is happening.
Catching "helloWorld/proxy/service"
When your app uses fetch, the URL is converted to lower case.
So sending fetch('https://helloWorld/proxy/service') can be intercepted with
cy.intercept('https://helloworld/proxy/service') // all lower-case url
There's a clue in the Cypress log, the logged fetch is show as being all lower-case characters
(fetch) GET https://helloworld/proxy/service
BaseUrl and intercept
When baseUrl is a different domain from the intercept hostname, you can specify it with an additional option, although in practice I found that it also works with the full URL as shown above.
cy.intercept('/proxy/service*', { hostname: 'https://helloworld' })

Testing basic async http request in Node with Got, Nock & Chai

I am trying to figure out why my unit test is not working correctly. It seems that the external network request is made despite my using Nock to intercept my http request.
I have a very basic getUser service, getuser-got.js:
const got = require('got');
module.exports = {
getUser(user) {
return got(`https://api.github.com/users/${user}`)
.then(response=>JSON.parse(response.body))
.catch(error => console.log(error.response.body))
}
};
This can be called succesfully but I want a unit test for it.
Here's my code in a file named getuser-got.test.js:
const getUser = require('../getuser-got').getUser;
const expect = require('chai').expect;
const nock = require('nock');
const user_response = require('./response');
describe('GetUser-Got', () => {
beforeEach(() => {
nock('https//api.github.com')
.get('/users/octocat')
.reply(200, user_response);
});
it('Get a user by username', () => {
return getUser('octocat')
.then(user_response => {
// expect an object back
expect(typeof user_response).to.equal('object');
// test result of name and location for the response
expect(user_response.name).to.equal('The Octocat')
expect(user_response.location).to.equal('San Francisco')
})
});
});
The file named response contains a copy of the expected response from the Github API, which I am loading into the user_response variable. I have replaced the values for name and location in order to make my test fail.
module.exports = {
login: 'octocat',
...
name: 'The FooBar',
company: '#github',
blog: 'https://github.blog',
location: 'Ssjcbsjdhv',
...
}
The problem is that I can see that Nock is not intercepting my request. When I run the test it continues to make an actual call to the external API. The test therefore passes, because it is not using my local response as the return value.
I've tried adding in nock.disableNetConnect(); but this just causes the test to timeout, as it's clearly still trying to make the external call. If I run my test I get:
➜ nock-tests npm test
> nock-tests#1.0.0 test /Users/corin/Projects/nock-tests
> mocha "test/test-getuser-got.js"
GetUser-Got
✓ Get a user by username (290ms)
1 passing (296ms)
What am I doing wrong to not have Nock intercept my http request?
The value being passed to the nock function is not a valid URL, it's missing the colon in the schema.
Updating it to nock('https://api.github.com') gets the test to fail locally, as desired.

Overwriting an existing command with Cypress

I'm trying to overwrite an existing command in Cypress.io. I'm looking to log() a route response's status & the route's url to extend the functionality of the built-in route(). Unfortunately, I get this message The route undefined had a undefined status code. in the console. Note, I'm using the browser's console momentarily. Eventually, I'll use the log() built-in method. This is what I have tried so far:
cypress/support/commands.js
Cypress.Commands.overwrite('route', (originalFn, response) => {
console.log(`The route ${response.url} had a ${response.status} status code.`);
return originalFn(response);
});
Update:
I'm getting the route now, but I still don't get response or status. This is my current code:
Cypress.Commands.overwrite('route', (originalFn, url, response) => {
console.log(`The route ${url} had ${response} status code`);
return originalFn(url, response);
});
When using the pattern cy.route(method, url, response), the response parameter is use to stub the call and return the supplied response to your app, see (route() - Arguments)
response (String, Object, Array)
Supply a response body to stub in the matching route.
Note that creating an overwrite of cy.route() will be hooking into the route configuration, not the capture of the route.
The pattern cy.route(options) has an onResponse option which can be used to console.log() the response, but cy.log() does not work there, probably because we invoke a command inside a command.
Cypress.log() can be used instead.
cy.route({
url: 'http://example.com',
method: 'GET',
onResponse: (response => {
const message = `The route '${response.url}' had a ${response.status} status code.`;
const log = Cypress.log({
displayName: 'Route debugging',
message: message,
consoleProps: () => {
// return an object which will
// print to dev tools console on click
return {
message: message,
}
}
})
log.finish(); // remove log spinner
})
})
/*
Command log output:
ROUTE DEBUGGING
The route 'http://example.com' had a 200 status code.
*/
Depending on what you're trying to achieve, there are a couple of options. Richard's answer above describes one approach - I'll attempt to cover some others.
(Note: The Cypress documentation at https://docs.cypress.io/ probably will give you a better understanding than this answer. I'll try to link the relevant articles inline)
(You can skip ahead to the section on 'Inspecting Api Responses' if you don't care why your code isn't working)
What's happening in your code
Let's look at the example code from https://docs.cypress.io/api/commands/route.html#Examples
cy.server()
cy.route('**/users').as('getUsers')
cy.visit('/users')
cy.wait('#getUsers')
Without your overwrite, cy.route here just registers the route, so you can wait for it later (Remember, cy.route does not make any api calls itself). With your overwrite, cy.route is completely replaced with your callback:
Cypress.Commands.overwrite('route', (originalFn, url, response) => {
console.log(`The route ${url} had ${response} status code`);
return originalFn(url, response);
});
So when cy.route('**/users') is called, it will instead evaluate
(originalFn, url, response) => {
console.log(`The route ${url} had ${response} status code`); // Logs "The route **/users had undefined status code"
return originalFn(url, response); // Registers the route with an mock value of undefined
})(originalCypressRouteFn, '**/users')
You can see why response is undefined - it's not passed in to the route call at all, since the request hasn't even been made.
Note that if we were attempting to mock the call instead (See https://docs.cypress.io/api/commands/route.html#With-Stubbing)
cy.route('https://localhost:7777/surveys/customer?email=john#doe.com', [
{
id: 1,
name: 'john'
}
])
You would instead log
"The route https://localhost:7777/surveys/customer?email=john#doe.com had [object Object] status code"
Inspecting Api Responses
If you just want to inspect the response from an api, you can use the using the built-in debugging tools (after calling cypress open). The browser's Network tab is available (which will record all requests made during a given test run), and you can additionally click on the response recorded in the left panel, which will log the request and response to the browser console.
If you're attempting to assert on the response to an api call, you can use cy.wait (See https://docs.cypress.io/guides/guides/network-requests.html#Waiting) to get access to the underlying xhr request after it finishes:
cy.wait('#apiCheck').then((xhr) => {
assert.isNotNull(xhr.response.body.data, '1st API call has data')
})
If you want a record of the APIs calls made during a CLI run (using cypress run), you can:
Print debug info, which will give you a lot of information, including all requests and responses (See https://docs.cypress.io/guides/references/troubleshooting.html#Print-DEBUG-logs): DEBUG=cypress:* cypress run (You can change cypress:* to limit the scope of the debug to just api calls, though I don't know off the top of my head what the namespace you'll want is)
Use a plugin that records all requests (e.g. https://github.com/NeuraLegion/cypress-har-generator)

How to check lowercased URLs and redirect if they're valid with Lambda#Edge

I've got some old URLs that used to use capital letters for people's names and such, but now I've updated my site to just lowercase characters in the URLs of every page. So, I'd like to redirect people if they happen to have clicked on an old link, or typed a capital letter by accident.
I also check removing a trailing slash. Here's the code I currently use on the front end. I was hoping to switch over to using Lambda#Edge (My website is on S3 and distributed via CloudFront) for that check and redirect.
Here's the JS function I'm using on the front end:
var newUrl = window.location.href.toLowerCase().replace(/\/$/, '')
loadIfChanged(newUrl)
function loadIfChanged (newUrl) {
if (newUrl != location.href) {
fetch(newUrl, {method: 'HEAD'}).then(function (response) {
if (response.status === 200) return window.location = newUrl
}).catch(function (error) {
return console.log(error)
})
}
}
How might I write that in a Lambda#Edge function?
Maybe something like this:
exports.handler = async function (event, context) {
// Lowercase the URL and trim off a trailing slash if there is one
var path = event.Records[0].cf.request.uri.toLowerCase().replace(/\/$/, '')
// How to do a fetch here? var ok = fetch()
if (ok) {
const response = {
status: '301',
statusDescription: 'Moved Permanently',
headers: {
location: [{
key: 'Location',
value: `https://example.com/${path}`,
}],
},
}
return response
} else {
return event.Records[0].cf.request
}
}
Can Lambda#Edge functions even do I/O?
And, importantly, can this Lambda#Edge function run only on 404s?
What you wrote should work, but I think better implementation would be to simply change the URI in the OriginRequest Lambda function. The code is as simple as :
import json
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request'];
request['uri'] = "anything";
return request;
An interesting detail to note here is that CF will cache the result of modified URI against the original URI. So if a request comes again for the original URI, CloudFront will return the response directly from the cache.
Why Origin Request?
Because it is triggered only on cache misses
It helps get the benefit of cache described above
Why this way is better as compared to what you proposed?
If you decide to go via the above mentioned approach, following is the request flow:
For non-cached content:
User -> CF -> Lambda -> S3 -> Final response to the user
For cached content:
User -> CF -> Final response to the user
Whereas if you use Lambda to generate 301 the flow would be:
For non-cached content:
User -> CF -> Lambda -> User -> CF -> Lambda -> S3 -> Final response to the user
For cached content:
User -> CF(cf returns 301) -> User -> CF -> Final response to the user
So you get two advantages in this approach :
Lesser latency for your user.
Lesser lambda invocation which means lesser bill for you.
For your second question, I do not think there is any way to invoke Lambda#Edge only for 404s as of now.
Hope this helps!

How to correct this test case in mocha framework?

I am trying to write test case for node application, which is using mocha as test framework.
test.js
var register = require('../routes/users');
var request = require('request');
var baseUrl = 'http://localhost:5000';
describe('registerUser()', function() {
it('check email is already registered', function (done) {
request.post({uri:baseUrl+'/register', form :{
username: 'test',
email: 'test#test.com'
}}, function (e, res, body) {
res.should.have.property('statusCode', 201);
res.should.have.property('regErr', 'This email is already taken!');
})
});
})
The regErr is given as parameter in function registerUser on render. I expected the parameterregErr will be set as property of response and can be get in test.
Please check my registerUser function in github.
I am trying create a test case for this function.
Above code doesn't have property called regErr in response fetched in test case.
How to correct so that the rendering parameters can also be property in response?
Shall I need to change the actual function registerUser to get this? If so, how can I achieve this?
When doing an HTTP request, the callback function is called with three arguments:
err (Possible error return)
res (HTTPRequest object)
body (Buffer of the resulting body)
Therefore, regErr is in the body variable. And since you are rendering an HTML page, you're gonna have to parse the body yourself to find it. A possible solution would be to render JSON and use JSON.parse() on the resulting body buffer.

Categories