Cypress access alias with this.* doesn't works - testing

I'm having a little problem understanding Cypress documentation. In the alias section they've added a use case of accessing alias with fixtures using the this.* reference:
beforeEach(() => {
// alias the users fixtures
cy.fixture("users.json").as("users");
});
it("utilize users in some way", function () {
// access the users property
const user = this.users[0];
// make sure the header contains the first
// user's name
cy.get("header").should("contain", user.name);
});
But when I try to reproduce it, I keep getting the error: Cannot read property 'SOAP_body' of undefined.
I don't understand where is my error. Here is my spec:
/// <reference types="cypress"/>
describe("SOAP API Test", () => {
beforeEach(() => {
cy.fixture("SOAP_body.xml").as("SOAP_body");
});
it("Test with task", function () {
const body = this.SOAP_body;
cy.request({
method: "POST",
headers: {
"content-type": "text/xml; charset=utf-8",
Authorization: "Token myVerySecretToken",
SOAPAction: "http://tempuri.org/TrackingFull",
},
url: `https://path.of/the/application.asmx`,
body: body,
failOnStatusCode: false,
}).then((result) => {
expect(result.status).to.equal(200);
cy.task("XMLtoJSON", result.body).then((response) => {
expect(
response.elements[0].elements[1].elements[0].elements[0]
.elements[1].elements[0].elements[0].elements[0]
.elements[0].elements[0].text
).to.equal("something");
});
});
});
});
and my task
/**
* #type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
on("task", {
XMLtoJSON(XML_body) {
var convert = require("xml-js");
let result = convert.xml2js(XML_body, {
compact: false,
spaces: 2,
});
return result;
},
});
};
Using debugger just before the const definition I can see that the variables are undefined
I do know about cy.get(), but I just wanted to learn how to use the this.* pattern.

After fiddling with the code I've realized that I was using an arrow function in the step definition:
it("Test with task", () => { ... }
I've done it simply because I use a lot of code snippets in VSC, and never paid attention to the syntax is used.
So, after seeing it, I've remembered that it would never work, as the MDN documentation says:
An arrow function expression is a compact alternative to a traditional
function expression, but is limited and can't be used in all
situations.
Differences & Limitations:
Does not have its own bindings to this or super, and should not be used as methods.
Does not have arguments, or new.target keywords.
Not suitable for call, apply and bind methods, which generally rely on establishing a scope.
Can not be used as constructors.
Can not use yield, within its body.
The solution was simple as replacing it with a function definition:
it("Test with task", function () { ... }
and the this context was as expected
Moral of the history, don't trust blindly in your code editor (even if its VSC)

Related

Cypress: login through magic link error with cy.origin()

Devs at my startup have switched login to a magic link system, in which you get inside after clicking a link on the email body.
I have set up a Mailsac email to receive mails containing magic links but I haven't been able to actually follow those links because of the following:
cy.request({
method: "GET",
url: "https://mailsac.com/api/addresses/xxxx#mailsac.com/messages",
headers: {
"Mailsac-Key": "here-goes-the-key",
},
}).then((res) => {
const magicLink = res.body[0].links[0];
cy.origin(magicLink, () => {
cy.visit('/')
});
});
I wasn't able to use cy.visit() either because the magic link URL is slightly different from the baseURL in this testing environment.
So my question is:
How could I follow this cumbersome link to find myself logged in home, or else, is there another way to deal with magic links?
Thanks
The docs say
A URL specifying the secondary origin in which the callback is to be executed. This should at the very least contain a hostname, and may also include the protocol, port number & path. Query params are not supported.
Not sure if this means the cy.visit() argument should not have query params, of just the cy.origin() parameter.
Try passing in the link
cy.request({
...
}).then((res) => {
const magicLink = res.body[0].links[0];
const magicOrigin = new URL(magicLink).origin
cy.origin(magicOrigin, { args: { magicLink } }, ({ magicLink }) => {
cy.visit(magicLink)
});
});
If that doesn't fix it, you could try using cy.request() but you'll have to observe where the token is stored after using the magicLink.
cy.request({
...
}).then((res) => {
const magicLink = res.body[0].links[0];
cy.request(magicLink).then(response =>
const token = response??? // find out where the auth token ends up
cy.setCookie(name, value) // for example
});
});
You need to pass the domain as the first parameter to origin, and do the visit within the callback function, something like this:
const magicLinkDomain = new Url(magicLink).hostname
cy.origin(magicLinkDomain, {args: magicLink}, ({ magicLink }) => {
cy.visit(magicLink);
//...
})
Reference: https://docs.cypress.io/api/commands/origin#Usage

How to run method inside a response function using Vue.js

I'm trying to run a method when I get success response from API, but the method dont run. I made a quick example here to show.
The test() function should be executed after i get the response, since its calling another API endpoint. Here is the vue.js code.
var app = new Vue({
el: "#contents",
data: {
id: null,
details: [],
},
methods: {
fetchProductDetails: function(){
let vue = this;
axios.post("/api/get-details", {
id : id
})
.then(function (response) {
vue.details = response.data;
this.test();
})
.catch(function (error) {});
},
test: function () {
console.log(app.details);
}
},
mounted: function(){
this.fetchProductDetails();
},
});
You should run vue.test() instead of this.test(), just like you use vue.details = response.data instead of this.details = response.data.
When using an unnamed function in .then(), this no longer refers to your vue application, but to the unnamed function. You could use ES6 arrow function syntax in order to avoid having to set this to a specific variable, as arrow functions use their parent's scope for this instead of setting this to refer to themselves:
axios.post("/api/get-details", { id: this.id })
.then(response => {
this.details = response.data;
this.test();
})
.catch(error => { console.log(error)});
Arrow functions (and ES6 in general) are not supported by IE11 however. so you'd need to use Babel to compile it back to something ES5 JavaScript if you need to support older browsers.

Restify: Set default formatter

Also asked in official Restify repo: #1224
Hi,
Is it possible to have one default formatter that can handle any accept type that is not defined.
For Example:
restify.createServer({
formatters: {
'application/json': () => {},
// All other requests that come in are handled by this, instead of throwing error
'application/every-thing-else': () => {}
}
});
By all appearances, this is impossible. Since the formatters are stored in a dictionary, there is no way to create a key that matches every input (that would kind of defeat the point of a dictionary anyway...) The only way to accomplish this kind of thing outside of JSON would be with a regular expression, and regular expressions don't work with JSON.
Here is a program I wrote to test this.
var restify = require("restify");
var server = restify.createServer({
formatters: {
'application/json': () => { console.log("JSON") },
"[\w\W]*": () => { console.log("Everything else") } // Does not work
}
});
server.get("/", (req, res, next) => {
console.log("Root");
res.setHeader("Content-Type", "not/supported");
res.send(200, {"message": "this is a test"});
next()
});
server.listen(10000);
Also here is a link to the documentation on this in case you can find some hint that I couldn't see.
Restify documentation

How does CasperJs bind variables?

I have the following code which does not post data as expected
var casper = require('casper').create();
var myData;
var utils = require('utils');
casper.start();
casper.then(function () {
myData = {"source":"casperjs"};
utils.dump(myData);
});
casper.thenOpen('http://my-api/api/upload/', {
method: "post",
data: JSON.stringify(myData),
headers: {
"Content-Type":"application/json"
}
}, function () {
utils.dump(myData);
});
casper.run();
The message was sent to my server but without valid data. However, if I move the thenOpen(...) into the then(...) like this
casper.then(function () {
myData = {"source":"casperjs"};
utils.dump(myData);
this.thenOpen('http://my-api/api/upload/', {
method: "post",
data: JSON.stringify(myData),
headers: {
"Content-Type":"application/json"
}
}, function () {
utils.dump(myData);
});
});
Then the post would succeed. Or If i change the thenOpen part in the original code (i.e., without moving it in to the casper.then(...) part, like this
casper.thenOpen('http://my-api/api/upload/', {
method: "post",
data: JSON.stringify({"source":"casperjs"}),
headers: {
"Content-Type":"application/json"
}
}, function () {
utils.dump(myData);
});
Then the post would also be successful. So it looks like myData has to be initialized when the thenOpen(...) is seen. So is this expected or I have done something wrong? I could not find reference about this behavior. Thanks!
This is expected behavior as casperjs schedules the steps before running them. This means for your first listing that JSON.stringify(undefinded) will be sent to the server. The reason is that your first casper.then block was not yet executed when the object is evaluated for your casper.thenOpen block. Therefore your data was not yet assigned properly to myData, which happens inside the step.
The evaluation of the POST data on the other hand is done for the thenOpen call and not inside, so it is executed synchronously.
You already provided some good alternatives.

Accessing ioArgs from callback functions

I'm upgrading a bunch of old dojo to 1.8. For our ajax request handling we've got a decorator (well, function wrapper) that will perform redirects in certain cases based on the response content, for example:
// Decorator func:
var redirectDecorator = function(func) {
var f = function(data, ioArgs) {
if(data.redirect) {
// A manual location redirect:
window.location.href = data.redirect;
if(data.redirect_xhr) {
// clone ioArgs, spawn new request to follow redirect etc
// <snip>
} else {
func(response);
}
}
return f;
}
// Used like so:
dojo.xhrPost({
url: url
handleAs: "json",
form: form,
load: redirectDecorator(function(data, ioArgs) {
// do stuff
})
});
Now, in dojo 1.8 (the dojo/request/xhr module) xhr() returns a Deferred for chaining and the callbacks are only supplied the data argument (no ioArgs - apparently these are attached to the promise - see http://bugs.dojotoolkit.org/ticket/12126).
In other words, the above ajax call becomes:
xhr.post(url, {
handleAs: "json",
form: form
}).then(function(data) {
// do stuff
});
Problem is, I can no longer wrap the anonymous function because ioArgs are not supplied. Inspecting the deferred (by breaking the chaining) doesn't appear to work either and would require more re-engineering than I'd like.
Any ideas?
Thanks Ken (for your help at #dojo too). To elaborate, the solution is to use dojo/request and use the .response deferred promise instead, which provides the necessary info:
// Decorator func:
var redirectDecorator = function(func) {
var f = function(response) {
var data = response.data;
if(data.redirect) {
// A manual location redirect:
window.location.href = data.redirect;
if(data.redirect_xhr) {
request(data.redirect_xhr, response.options).then(func);
} // more conditions follow.
}
return f;
}
request.post(url, {
handleAs: "json",
form: form
}).response.then(redirectDecorator(function(response) { // <-- note .response.then(
// do stuff where data is response.data
}));
Promises returned from dojo/request are actually objects with an additional response promise that provides more information. See the following places for information:
http://www.sitepen.com/blog/2012/08/21/introducing-dojorequest/
http://dojotoolkit.org/reference-guide/1.8/dojo/request.html
http://dojotoolkit.org/documentation/tutorials/1.8/ajax/