Do a HTTP Post in Cypress without using a test - cypress

I have a Cypress test:
describe('Create a session ', () => {
it('creates a session', () => {
cy.request({
method: 'POST',
url: `${Cypress.env('apiURL')}/api/v1/user/login/`,
form: true,
body: {
email: Cypress.env('email'),
password: Cypress.env('password'),
},
}).then((response) => {
expect(response.status).to.eq(200);
cy.task('setKey', response.body.data.key);
});
});
});
This POST returns some session data needed to create a dummy account:
describe('Create a company ', () => {
it('creates a company', () => {
cy.task('getKey')
.then((data: Key) => {
key = data;
})
.then(() => {
createNonce();
cy.request({
method: 'POST',
url: `${Cypress.env('apiURL')}/api/v1/cli/`,
headers: {
'X-Auth-Timestamp': epochTime(),
'X-Auth-Key': key.key,
'X-Auth-Nonce': nonce,
'X-Auth-Signature': createSignature(),
},
body: {
args: ['seeder', 'create', 'abc1'],
},
}).then((response) => {
expect(response.status).to.eq(200);
// TODO: we need some REST endpoints to return a JSON object instead of a string
data = JSON.parse(response.body.substring(response.body.indexOf('{')));
cy.task('setCompany', data);
});
});
});
});
I'm not sure I need these functions to be tests since they don't test anything, but just do a POST request. Is it possible to maybe move the functionality into a cypress task?

You can add the post request in your commands file:
function postRequest() {
cy.request({
method: 'POST',
url: `${Cypress.env('apiURL')}/api/v1/cli/`,
headers: {
'X-Auth-Timestamp': epochTime(),
'X-Auth-Key': key.key,
'X-Auth-Nonce': nonce,
'X-Auth-Signature': createSignature(),
},
body: {
args: ['seeder', 'create', 'abc1'],
},
})
}
Cypress.Commands.add('postRequest', postRequest)
An assuming all the rest of your code is fine, and you want only to abstract the logic; then in your test you can invoke that command:
describe('Create a company ', () => {
it('creates a company', () => {
cy.task('getKey')
.then((data: Key) => {
key = data;
})
.then(() => {
createNonce();
cy.postRequest().then((response) => {
expect(response.status).to.eq(200);
data = JSON.parse(response.body.substring(response.body.indexOf('{')));
cy.task('setCompany', data);
});
});
});
});

You can move these into before() or beforeEach() so they will be separate from your tests.
describe('Create a company ', () => {
before(() => {
cy.task('getKey')
.then((data: Key) => {
key = data;
})
.then(() => {
createNonce();
cy.request({
method: 'POST',
url: `${Cypress.env('apiURL')}/api/v1/cli/`,
headers: {
'X-Auth-Timestamp': epochTime(),
'X-Auth-Key': key.key,
'X-Auth-Nonce': nonce,
'X-Auth-Signature': createSignature(),
},
body: {
args: ['seeder', 'create', 'abc1'],
},
}).then((response) => {
expect(response.status).to.eq(200);
// TODO: we need some REST endpoints to return a JSON object instead of a string
data = JSON.parse(response.body.substring(response.body.indexOf('{')));
cy.task('setCompany', data);
});
});
})
it('creates a company', () => {
//test code
});
});

Related

Avoid a pyramid while writing a function in commands.js

How can I avoid a pyramid when doing something like this example?
Cypress.Commands.add("test", () => {
// first request
cy.request("POST", url1)
.its("body")
.then((response) => {
let userId = response.user._id
// second request
cy.request("POST", url2)
.its("body")
.then((response) => {
let adminAuth = response.accessToken
// third request
cy.request({
method: "POST",
url: url
headers: { "x-access-token": adminAuth },
body: { user: userId }
I feel like nesting like this , inside the then(), is quite inneficient.
I'm pretty sure that Alapan's answer won't work as you can't get a cypress variable value in the same continuation where it was defined. A variable has just not been calculated at this moment (it's not synchronous).
So you can go this way:
// first request
cy.request("POST", url1)
.its("body")
.then((response) => {
cy.wrap(response.user._id).as('userId')
})
// second request
cy.request("POST", url2)
.its("body")
.then((response) => {
cy.wrap(response.accessToken).as('adminAuth')
})
// accessing the variables inside a then block to let Cypress to compute them
cy.then(function() {
// third request
cy.request({
method: "POST",
url: url,
headers: {
"x-access-token": this.adminAuth
},
body: {
user: this.userId
}
})
})
You can alias to save the value and then access it using this.. Something like:
// first request
cy.request("POST", url1)
.its("body")
.then((response) => {
cy.wrap(response.user._id).as('userId')
})
// second request
cy.request("POST", url2)
.its("body")
.then((response) => {
cy.wrap(response.accessToken).as('adminAuth')
})
// third request
cy.request({
method: "POST",
url: url,
headers: {
"x-access-token": this.adminAuth
},
body: {
user: this.userId
}
})

Cypress waiting until graphql query finishes

I'm trying to wait for a specific graphql request to finish before I check for an element on the page but it doesn't seem to wait the way I have it set up
describe('CopyrightModule', () => {
it('edit, cancel, edit, save', () => {
cy.intercept(
{
method: 'POST',
url: '/graphql',
},
req => {
console.log(req.body.id);
console.log(req);
if (req.body.id === 'TakedownUserRendererQuery') {
req.alias = 'TakedownUserRendererQuery';
}
},
);
cy.visit('/');
cy.get(selectors.DMCA_LINK).click({ force: true });
cy.contains('My Requests').click();
cy.get(selectors.REQUEST_ROW)
.first()
.click();
cy.wait('#TakedownUserRendererQuery');
cy.get(selectors.COPYRIGHT_EDIT).click();
cy.contains('Cancel').click();
cy.get(selectors.COPYRIGHT_EDIT).click();
cy.contains('div', 'Owner Website URLs')
.find('input')
.first()
.type('8000-1612023');
});
});

Action with two chained requests. "Cannot read property then of undefined" error

Most of my api calls are a function that get called from an action like this
Action:
export const fetchSomethingAction = () => {
return (dispatch) => {
dispatch({ type: types.FETCH_SOMETHING_REQUEST })
return api.fetchSomething()
.then((response) => {
dispatch({ type: types.FETCH_SOMETHING_SUCCESS, data: response.data })
})
.catch(error => {
dispatch({ type: types.FETCH_SOMETHING_FAILURE, error })
})
}
}
And the axios request goes something like this:
export const fetchSomething = () => {
return axios({
method: 'GET',
url: `${api}/endpoint`
});
}
Now I need to make two chained requests, and have the action dispatch an error if any of the two requests fails
So how do I chain the request to achieve this?
I was doing for example
export const fetchSomething = () => {
axios({
method: 'GET',
url: `${api}/endpoint1`
})
.then(res => {
return fetchSomething2(res.param)
})
.catch(err => return err)
}
const fetchSomething2 = (param) => {
return axios({
method: 'GET',
url: `${api}/endpoint1?param=${param}`
});
}
But I get Cannot read property 'then' of undefined in the action.
Do I need two actions, one for each function? Is that the proper way to do it?
Thank you
Your fetchSomething function has an arrow function that is defined with braces, but does not have a return statement, and thus implicitly returns undefined. Change it to either not use braces, or have a return statement.
Without braces:
export const fetchSomething = () =>
axios({
method: 'GET',
url: `${api}/endpoint1`
})
.then(res => {
return fetchSomething2(res.param)
})
.catch(err => return err)
With a return:
export const fetchSomething = () => {
return axios({
method: 'GET',
url: `${api}/endpoint1`
})
.then(res => {
return fetchSomething2(res.param)
})
.catch(err => return err)
}
Here's a smaller example you can use to understand the difference:
> a = () => 1; a()
1
> a = () => { 1 }; a()
undefined
> a = () => { return 1 }; a()
1

Apollo Client delays request

I have a very weird issue with Apollo Client.
We are using apollo-client#1.9.3 with react (react-apollo#1.4.16).
In our project, we notice that apollo always wait for 1 to 2 seconds before sending the request.
Below is a screenshot of the situation:
This is how our client config looks like:
const customNetworkInterface = {
query: request =>
fetch('/graphql', {
method: 'POST',
credentials: 'include',
mode: 'cors',
cache: 'default',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
body: JSON.stringify({
...request,
query: print(request.query),
}),
})
.then(resp => resp.json())
.then(({ data, errors }) => {
if (errors) {
const userErrors = errors
.filter(({ code }) => +code >= 400 && +code <= 401)
.map(({ message }) => message)
.join('\n');
const serverErrors = errors
.filter(
({ code }) => !code || (+code < 400 && +code > 401)
)
.map(({ message }) => message)
.join('\n');
if (serverErrors.length > 0) {
error(serverErrors);
if (isProduction) {
window.triggerAlert(
'danger',
'The server encountered an error. Our technical team has been notified.'
);
} else {
window.triggerAlert('danger', serverErrors);
}
} else if (userErrors.length > 0) {
window.triggerAlert('danger', userErrors);
}
}
return { data, errors };
}),
};
const networkInterface = createNetworkInterface({
uri: '/graphql',
opts: {
credentials: 'same-origin',
},
});
networkInterface.useAfter([
{
applyAfterware({ response }, next) {
response
.clone()
.json()
.then(responseJson => {
if (responseJson.errors) {
error(
responseJson.errors
.map(({ message }) => message)
.join('\n')
);
}
next();
});
},
},
]);
export const client = new ApolloClient({
networkInterface: customNetworkInterface,
queryDeduplication: true,
addTypename: true,
});
Then the query code is with react-apollo:
graphql(RaceResultsQuery, {
props: ({ ownProps, data }) => ({
race_results: _.get(data, 'me.my_race_results', []),
}),
}),
This would need a complete, minimal example to provide an answer for sure (delete as much of your code as possible with the issue still happening).
My guess would be that you have a parent component with a very expensive query and it only renders the component with the delayed query after the expensive query returned.

AJAX and REST API calls

I am trying to create an AJAX script that will pull data via an API and display it on a page as HTML. Everything using the route below seem to work fine in Postman, but I can't get it to translate in my JS file.
I know I'm calling the URL incorrectly and I'm pretty sure it's because of the :id. I'm not sure how to correct it. I feel like I've tried every URL iteration, but I'm missing something.
// router.REST_VERB('EXPRESS_ROUTE', api.ACTION)
router.get('/', api.list);
router.post('/', api.create);
router.get('/:id', api.read);
router.put('/:id', api.update);
router.get('/delete/:id', api.delete);
Here are my update and delete:
/ AJAX PUT - Update Reminder
function updateReminder(id) {
$.ajax({
type: 'PUT',
url: '/api/',
data: JSON.stringify({
'id': $('#editId').val(),
'name': $('#name').val(),
'occasion': $('#occasion').val(),
'lastgift': $('#lastgift').val()
}),
success: (item) => {
document.location.href="/";
},
contentType: "application/json",
dataType: 'json'
});
}
// AJAX DELETE request - Delete Reminder
function deleteReminder(id) {
$.ajax({
type: 'DELETE',
url: '/api',
data: JSON.stringify({
'id': id
}),
success: (item) => {
document.location.href="/delete";
},
contentType: "application/json",
dataType: 'json'
});
}
EDIT
Here is the controller code for update and delete:
ApiController.update = (req, res) => {
reminderService.update(
req.params.id,
{
name: req.body.name,
occasion: req.body.occasion,
lastgift: req.body.lastgift,
prefgift: req.body.prefgift
},
{new: true}
)
.then((item) => {
res.json(item);
})
.catch((err) => {
if (err) console.log(err);
});
};
ApiController.delete = (req, res) => {
reminderService.delete(req.params.id)
.then((item) => {
res.json(item);
})
.catch((err) => {
if (err) {
res.end("Didn't delete")
}
});
};
and here is the service code:
ReminderService.update = (id, reminderObj) => {
return Reminder.findByIdAndUpdate(
id,
{
$set: reminderObj
},
{
new: true
}
)
.then((item) => {
return item;
})
.catch((err) => {
if (err) {
res.end("You're in reminderService!")
}
});
};
ReminderService.delete = (reminderId) => {
return Reminder.deleteOne({ '_id': reminderId })
.then((item) => {
return item;
})
.catch((err) => {
throw err;
});
};
Your route for delete is wrong, you should use delete instead of get.
router.get('/', api.list);
router.post('/', api.create);
router.get('/:id', api.read);
router.put('/:id', api.update);
router.delete('/:id', api.delete);
In your ajax requests, your url is missing the ids
// AJAX PUT - Update Reminder
function updateReminder(id) {
$.ajax({
type: 'PUT',
url: '/'+id,
data: JSON.stringify({
'id': $('#editId').val(),
'name': $('#name').val(),
'occasion': $('#occasion').val(),
'lastgift': $('#lastgift').val()
}),
success: (item) => {
document.location.href="/";
},
contentType: "application/json",
dataType: 'json'
});
}
// AJAX DELETE request - Delete Reminder
function deleteReminder(id) {
$.ajax({
type: 'DELETE',
url: '/'+id,
data: JSON.stringify({
'id': id
}),
success: (item) => {
document.location.href="/delete";
},
contentType: "application/json",
dataType: 'json'
});
}
One thing I'm skeptical about is that you use JSON.stringify on your data. Does your API specifically require JSON or did you do just do that to try to get it t work?

Resources