Cypress. Why is my route alias not matching? - cypress

My POST request to ocr/receipt is never matched. I've...
created a route, matching **/ocr/**, specifying POST, and given it an alias.
called wait() with a long timeout.
I can watch the request complete in the network pane while the wait spinner turns happily in the test pane. Why is Cypress not matching this route?
beforeEach(function () {
cy.route('POST','**/ocr/**').as('ocr');
});
it('Création frais depuis le bouton « appareil photo »', function () {
cy.get('.in-progress').first().click()
cy.wait('#ocr', {'timeout':15000});
cy.get('#grpChoices > :nth-child(1)').click();
});

Well who would have guessed. Method is case sensitive, and it only works in lower case. So...
route('post','**/ocr/**').as('ocr')
fixed it.
The doc won't help you.
The other recurring reason for routes not triggering is if your app uses the fetch api. Fetch is not compatible with cypress.

In my case I defined the same alias in cy.route().as('acme') and cy.request().as('acme'). Even though the Cypress docs points out that cy.wait does not support requests it does not point out that aliases should be unique. Rename or removing cy.request.as('foo') solves this problem.

Make sure for all requests, you have a response.

Related

Cypress: Switching from cy.route() to cy.intercept()

It seems that most people I read about experence zero trouble with this. I, on the other hand, have a test suite which someone else wrote, in which I'm trying to replace route() with intercept(). The API intercepts are done to handle button clicks etc., and about 99.9% percent of them fails if I just replace it. So, there's obviously some syntax in/use of intercept() I've not found a description for.
Example:
This works:
cy.route('POST', getApiPrefix() + '/prosjektfinansiering/'+ pfId +'/eiendom', result);
This does not work. The button click is not executed:
cy.intercept('POST', getApiPrefix() + '/prosjektfinansiering/'+ pfId +'/eiendom', result);
I've tried adding '**' in front of "/prosjekt...", and I've tried removing 'POST', with no luck.
Any ideas? I'll gladly post more info if necessary.
UPDATE:
Futher attempts:
Getting some hints here and there, it seems that this is a more correct way of using intercept():
return cy.intercept('POST', getApiPrefix() + '/prosjektfinansiering/'+ pfId +'/eiendom', {
body: result
});
This doesn't work, either.
The variables result in these examples is an object describing what is sent back to the frontend of the POST-request in the route matches the api path.
For troubleshooting, I can see that when using intercept(), there is ONE route that is not working when using intercept (the bottom one in the picture). However, I cannot for the life of me see why, and how the route match can be written differently?
Most likely, you're mixing the old use of cy.route() and cy.server(). In my experience, those two won't work well together. It's easier when you're starting fresh with just cy.intercept().
Your update is correct too; You have to encapsulate the return value you want mocked in {body: value}.
from what I am seeing in your circled screenshot, the API is not called after you try to intercept it. (the count under # column is -)
You need to track when the API is to be called and ensure you intercept before the call is made. Cypres can help you with this. You can go through the run steps in the cypress window.
You could also share this if you don't mind.
If you are 100% certain the button makes the call. Steps should be:
cy.intercept()
cy.get('button').click()
In the cypress window, right after the click, you should see the API being called.

Cypress: How to capture text from a selector on one page to use as text on another page

New cypress user here, I am aware that cypress does not handle variables like how testcafe and others do due to the asyn nature of it. Using the example given and what I could find I have this as an example:
cy.get('selector').invoke('text').as('text_needed')
cy.get('#text_needed')
const txtneeded = this.text_needed
cy.log(txtneeded)
This looks at a given selector, takes what it finds and uses it as text and set it as a variable usable at any time in the test and outputs it to the log. The plan is to use that text in a search filter in another page to find the item it references.
The problem is that it fails with Cannot read properties of undefined (reading 'text_needed')
Is this because the content of the selector is not assigned to text properly, the outer html is <a data-v-78d50a00="" data-v-3d3629a7="" href="#">PO90944</a> The PO90944 is what I want to capture.
Your help would be appreciated!
You cannot save an alias and access it via this.* in the same execution context (callback) because it's a synchronous operation and your alias is not yet resolved at this time.
This is a correct way to go:
cy.get('selector').invoke('text').as('text_needed')
cy.get('#text_needed').then(txtneeded => {
cy.log(txtneeded)
})
First, make sure to define it as traditional function, not as an arrow function as this context doesn't work as you'd expect there, more info here.
Next, typically in a single test you should use .then() callback to perform additional actions on elements, and use aliases when you need to share context between hooks or different tests, so please consider the following:
// using aliases together with this within the single test won't work
cy.get(<selector>).invoke('text').as('text_needed')
cy.get('#text_needed').should('contain', 'PO90944') // works fine
cy.log(this.text_needed) // undefined
// this will work as expected
cy.get(<selector>).invoke('text').then(($el) => {
cy.wrap($el).should('contain', 'PO90944'); // works fine
cy.log($el) // works fine
});
Setting alias in beforeEach hook for example, would let you access this.text_needed in your tests without problems.
Everything nicely explained here.
Edit based on comments:
it('Some test', function() {
cy.visit('www.example.com');
cy.get('h1').invoke('text').as('someVar');
});
it('Some other test', function() {
cy.visit('www.example.com');
cy.log('I expect "Example Domain" here: ' + this.someVar);
});
And here's the output from cypress runner:

cypress.io waiting for same alias

cy.server();
cy.route('POST', 'my/api').as('myApi');
...
cy.wait('#myApi');
...
cy.route('POST', 'my/api').as('myApi');
cy.wait('#myApi');
When my app calls the same API twice within the same test, from the above code, the 2nd cy.wait finishes immediately since it sees that the first API is already finished. To get around this, I append a random number behind all my route aliases. Is this the correct way?
You might be able to do better. The cy.route() command is just a definition, so you should group all your routes at the top of the file. Routes only need to be defined once. Then try chaining your waits, as in cy.wait().otherStuff().wait() or at least chaining your waits with other stuff that has to succeed first.
Thank you for the question! I think the previous answer is totally right. By default, cypress routing is just aliasing. You could find a similar example in the cypress documentation here.
So, you code should be something like that:
cy.server();
cy.route('POST', 'my/api').as('myApi');
cy.wait('#myApi').then(() => {
// You probably want to add some assertions here
});
// Do your stuff here
cy.wait('#myApi').then(() => {
// Second assertion. Probably, something should be changed in the second request.
});
In this case, for the second wait, you can try the following.
cy.server();
cy.route('POST', 'my/api').as('myApi');
cy.wait('#myApi').then(() => {
// You probably want to add some assertions here
});
// Do your stuff here
cy.wait(['#myApi', '#myApi']).then(() => {
// Second assertion. Probably, something should be changed in the second request.
});

Can we handle ngxs #Action errors via ofActionErrored() without going to default "ErrorHandler"?

I have an async ngxs action that throwsError().
I would like the default error handling mechanism to ignore this thrown error because I will be handling it in my code via ofActionErrored(). However, for other actions, default error handling should still take place.
Right now, both the ofActionErrored() and default error handling (via Angular/Ionic) tries to deal with the error.
The alternative I can think of is to dispatch Xxx_SUCCESS and Xxx_ERROR actions from within the initially dispatched action, something I would like to avoid if i can help it.
Advice appreciated.
There is a feature request that raised a similar concern at the NGXS repo. We've discussed in the core team meeting and we'll focus that for the next release. You can provide your feedback there: https://github.com/ngxs/store/issues/1691
You can use ofActionCompleted which as a result can provide the error, if there is one. An example taken from the code I am working on:
this.actions$.pipe(
ofActionCompleted(GetMe)
).subscribe((data) => {
const errorStatus = data.result.error['status'];
if (!data.result.successful && errorStatus === 403) {
this.snackbar.openFromComponent(TranslateSnakeBarComponent, {
data: {message: 'USER_DISABLED'}
});
}
});

Modernizr.load() confusion with Yepnope

I'm more than a little confused about Modernizr and it's relation to Yepnope.js. As I understand it, Modernizr comes with Yepnope.js (assuming you select the Modernizr.load() option). According to the Yepnope documentation, there are optional prefix plugins which can used. For example, you can test for versions of IE (assuming you also load the yepnope.ie-prefix.js script). However, when I attempt to run the following, I get 'undefined' alert:
Modernizr.load({
load: 'ie!my-ie-specific.js',
complete : function (url, result, key){
alert(url, result, key);
}
});
What am I doing wrong? Does Modernizr include Yepnope completely or only bits and pieces?
I got stuck on this too. Struggled for an hour.
The complete callback doesn't support tests. Change it to callback which fires when the external script is loaded.
complete runs when all scripts are loaded, OR if nothing is loaded.
I wish complete took the result variable though. I could use that.

Resources