cypress.io waiting for same alias - cypress

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.
});

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:

RxJS: Second observable's input argument depends on first observable's output

I have two observables
baseObs$ = serviceCall(); // this return Observable<Foo>
secondObs$ = serviceCall(args); // this return Observable<Bar>
args in this example is public variable defined somewhere else. It doesn't need to be though if that makes this easier.
baseObs I can call whenever I want, but secondObs I can only call after baseObs is succesfully called and handled (don't know the right words so example follows):
I have now something like
baseObs$.subscribe(x => {
const args = x.args; // just example. Point is, I need x to build args.
serviceCall(args).subscribe(y => {
console.log(y); // This is fine
});
});
This suits my needs but I got feedback that no subscribe should live inside another subscribe. How would you achieve same thing using baseObs$ and secondObs$ defined above?
PS. All is pseudo code but hopefully I didn't do too much typos. I think the idea should be clear though.
In the simple case that the first observable only emits once (like a typical HTTP request), any one of switchMap, mergeMap etc. will do:
serviceCall().pipe(
switchMap(x => serviceCall(x.args))
).subscribe(console.log);
If that assumption is not true, you're going to want to read their respective documentation to understand how behavior will change between them. In fact, I'd recommend reading up on them to know their differences even just in general, as it's very valuable information when dealing with reactive code.

What is the definition of "each test" when Cypress docs say "between each test"?

The Cypress docs Best Practices section, under the heading "Is resetting the state necessary?" says the following:
Remember, Cypress already automatically clears localStorage, cookies, sessions, etc before each test.
Does "each test" mean "each it() block"?
Given the following code, and it's comments, is the second it() block technically cookie-less, and session-less, and passing just because we're lucky that the page hasn't changed?
describe('When logged in', () => {
before(() => {
cy.customLoginAndSetSomeCookies()
cy.visit('/page-for-logged-in-folks-only')
})
it('I can see some stuff!', () => {
// `before` ran and set up cookies for us.
// `visit` set the page for us.
// So this passes just fine.
cy.get('#welcome').should('contain', 'You can see me!')
})
it('Is this a new test? Am I logged out?', () => {
// Am I technically cookie-less here, but since
// I'm still on this page I can still see this?
// (Passes, but only because no page transition?)
cy.get('#welcome').should('contain', 'You can see me!')
})
})
If I'm correct, then while these may pass fine, the problem comes if we try to do some "logged in" stuff in the second it() block. So the better pattern would probably be to combine these into a larger single block, or use beforeEach instead of before... BUT, I'm not confident in that answer, because I'm not sure the definition of "each test".
I found your answer, from their pricing page :)
What counts as a test recording?
We consider each time the it() function is called to be a single test. Only test runs configured to record to the Dashboard Service when running Cypress headlessly count toward your plan limit.

How to reuse code inside another spec.js?

I have two test scripts that must be called by a third one. I don't would like to replicate the code for all test cases that uses the same two scripts and do many other things after.
I tried to use the require command but it seems to be ignored and the code after it is executed, skipping the intended script AbrirNavegador.spec.js
before(function() {require('./AbrirNavegador.spec.js')});
There's no information about error or something else. It's just skipped.
I never got that to work. But I do use another work around. What I do:
// commands.js
Cypress.Commands.add('reuseMethod1', function({
// first set of steps that need to be reused
})
Cypress.Commands.add('reuseMethod2', function({
// second set of steps that need to be reused
})
// testscript_1.js
cy.reuseMethod1()
// testscript_2.js
cy.reuseMethod1()
cy.reuseMethod2()
You can call the methods at any place, so also in a before/beforeEach/after/afterEach. So the only code duplication you have is the part of calling the method.

Resources