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
}
})
Related
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
});
});
I make request to server for login, and then before redirect user to home page I try to
open indexedDB connection in order to see this page, bacause home page go to the indexedDB
and get some data. So below is my code and photo of error
beforeEach(() => {
cy.request({
method: 'POST',
url :'http://localhost:3000/api/auth/login',
body : {
email: "email",
password: "password"
}
}).then(function(response) {
window.indexedDB.open("testDB");
localforage.config({
driver: [localforage.INDEXEDDB],
name: 'testDB',
storeName: 'testDB',
version: '1.0',
});
localforage.clear().then(() => {
localforage.setItem('jobs', [{name: 'fdf'}]);
});
}).then(()=>{
cy.visit('http://localhost:3000/');
})
})
I also try this way but it doesnt works too, what I do wrong?
function open() {
var request = window.indexedDB.open("testDB", 1);
request.onerror = function(event) {
};
request.onsuccess = function(event) {
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
var objectStore = db.createObjectStore("jobs", { keyPath: "name" });
objectStore.createIndex("name", "name", { unique: false });
objectStore.add({name: 'fsdf'});
}
}
describe('The Login Page', () => {
beforeEach(() => {
open()
cy.request({
method: 'POST',
url :'http://localhost:3000/api/auth/login',
body : {
email: "gfdgfdg",
password: "gdfgfdg"
}
})
.then(function(response) {
})
.then(()=>{
cy.visit('http://localhost:3000/');
})
})
One thing that is (probably) incorrect is the window reference.
Cypress runs the browser window as an automation "shell", which is what you get when you use
window.indexedDB.open("testDB")
but the app window is inside an iframe.
You can access either with
cy.window().then(win => win.indexedDB.open("testDB"))
or
cy.state('window').indexedDB.open("testDB") // undocumented!
I would like to use Cypress for API testing. My goal is to extract a part of the API response and pass it to another API request. Here's a sample code:
Cypress.Commands.add('createCustomer', () => {
return cy.request({
method: 'POST',
url: 'api/v1/Customers',
headers: {
'Content-Type': 'application/json'
},
body: {
// sample content
}
}).then((response) => {
return new Promise(resolve => {
expect(response).property('status').to.equal(201)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const jsonData = response.body;
const memberId = jsonData.id
resolve(memberId)
return memberId
})
})
})
With this code, I am getting [object%20Object] as the result.
Hoping for some feedback.
So you are adding the id generated by the POST to a subsequent GET request?
Try returning the id without using a Promise, I don't think you need one at that point since the response has already arrived.
}).then((response) => {
expect(response).property('status').to.equal(201)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const jsonData = response.body;
const memberId = jsonData.id;
return memberId;
})
Url for GET
cy.createCustomer().then(id => {
const url = `api/v1/Customers${id}`;
...
or
cy.createCustomer().then($id => {
const id = $id[0]; // Not quite sure of the format, you may need to "unwrap" it
const url = `api/v1/Customers${id}`;
...
If you want to pass response from API Request 1 to API Request 2, you can do something like this:
describe('Example to demonstrate API Chaining in Cypress', function () {
it('Chain two API requests and validate the response', () => {
//Part 1
cy.request({
method: 'GET',
url: 'https://www.metaweather.com/api/location/search/?query=sn',
}).then((response) => {
const location = response.body[0].title
return location
})
//Part 2
.then((location) => {
cy.request({
method: 'GET',
url: 'https://www.metaweather.com/api/location/search/?query=' + location
}).then((response) => {
expect(response.status).to.eq(200)
expect(response.body[0]).to.have.property('title', location)
})
})
})
})
Your code seems to be failing during the initial request, not during the subsequent actions. I am far from a Javascript expert, but you seem to have some unnecessary complexity in there. Try simplifying your command like this and see if you can at least get a successful request to go through:
Cypress.Commands.add('createCustomer', () => {
cy.request({
method: 'POST',
url: 'api/v1/Customers',
headers: {
'Content-Type': 'application/json'
},
body: {
// sample content
}
})
})
And if that works, keep going:
Cypress.Commands.add('createCustomer', () => {
cy.request({
method: 'POST',
url: 'api/v1/Customers',
headers: {
'Content-Type': 'application/json'
},
body: {
// sample content
}
}).then((response) => {
expect(response).property('status').to.equal(201)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const jsonData = response.body;
const memberId = jsonData.id
return memberId
})
})
I was searching for my answer in SO, but could not find any suitable one. So here i go with my questions...
In my redux action creator i am fetching API call from isomorphic-unfetch but I am getting the message Error: Actions must be plain objects. Use custom middleware for async actions each time.Though i defined dispatch in my action...
My action code is
const exchangeBuy = ({btc, usdt, id}, url) => {
return (dispatch) => {
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( { btc, usdt, id } )
}).then(
r => r.json()
).then(
r => dispatch({
type: 'EXCHANGE_BUY',
payload: r //here r return an object from mongoDB
})
)
}
}
Also the code that invokes this is
submitExchange(e){
e.preventDefault()
const btc = e.target.btcamount.value
const usdt = e.target.usdprice.value
this.props.exchangeBuy( //Here it is
{
btc: btc,
usdt: usdt,
id: this.props.users.id
},
this.props.apiurl )
}
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?