In latest version of Cypress Cookie preserve is deprecated so I wish to us cy.session. However I can't get this to work across multiple tests as cy.session needs to be inside a test 'it', example of how my tests are currently set up.
beforeEach(() => {
Cypress.Cookies.defaults({
preserve: /somecookie|someOtherCookie|AnotherCookie/
})
it('Navigate to URL', () => {
performance.NavigateToUrl(URL);
});
it('Login - Username/Pass', () => {
performance.LoginUserPass();
});
it('Navigate Order Page', () => {
performance.Orderpage();
});
//And so on............
Any help and suggestions welcome as i don't really want to rewrite the test structure as i create a report on it current output/design.
for the session to be kept across all tests
With recent changes, Cypress are trying to "enforce" test isolation more, and you are going against their best practice by having dependency across the different it() blocks.
A better approach would be to structure according to context
context('before logging in', () => {
it('Can navigate to home page', () => {
...
})
it('Can log in', () => {
...
})
})
context('after logging in', () => {
beforeEach(() => {
cy.session('login', () => {
// do login via cy.request()
// preserve all browser login data such as cookies, localstorage
})
})
it('Can use order page', () => {
...
})
})
Test isolation flag
There is a note on the cy.session() page that indicates you can turn off test isolation, but the paragraphs are a bit vague IMO
The page is cleared before setup when testIsolation is enabled and is not cleared when testIsolation is disabled.
Cookies, local storage and session storage in all domains are always cleared before setup runs, regardless of the testIsolation configuration.
So, it's worth trying with setting testIsolation: false - but don't call cy.session() at all.
I think you could use the cy.session with the callback function in a before hook with both the visit and login methods:
const sessionId = 'Login with valid credentials'
before(() => {
cy.session(sessionId, () => {
performance.NavigateToUrl(URL)
performance.LoginUserPass()
})
})
Then restore the session and visit the page again in a beforeEach hook.
beforeEach(() => {
cy.session(sessionId)
performance.NavigateToUrl(URL)
})
it('Navigate Order Page', () => {
performance.Orderpage();
})
//And so on............
Related
describe("Share Link", () => {
beforeEach(() => {
cy.generateLink().then(response => {
let url = response.meeting_shared_link.split("/");
cy.wrap(url[url.length - 1]).as("link");
});
describe("when I turn on link sharing", () => {
// This works. Changing it to a before hook breaks it.
beforeEach(() => {
cy.get("#link").then(link =>
cy.toggleLinkSharing({ link: link })
);
});
});
I am currently generating an alias - #link - in my first beforeEach hook, and then accessing it in the next beforeEach hook which is nested in a describe.
My problem is that I need the latter hook to be a before, rather than a beforeEach.
When I modify it to a before hook - it is unable to find the alias "link". Why?
I understand that aliases are cleared between each test, hence the need for the first beforeEach hook - but why isn't it available inside a before hook?
Edit:. I think, it may be because the latter defined before hook triggers before the first beforeEach - in which case, the alias didn't yet exist. If this is the case - it's not intuitive. The before hook should only fire after the beforeEach since it's nested in another describe.
You can change the outer beforeEach() to before(), and preserve the alias between tests with beforeEach(function() { cy.wrap(this.link).as('link') })
It works because this.link is not cleared between tests, even though the alias #link is cleared.
describe("Share Link", () => {
before(() => {
cy.wrap('my-url').as("link");
console.log('Outer before')
});
describe("when I turn on link sharing", () => {
beforeEach(function() { cy.wrap(this.link).as('link') }) // preserve the alias
before(() => {
cy.get("#link").then(link => {
console.log('Inner before', link)
})
})
it('1st test', () => {
cy.get('#link').then(link => console.log('1st test', link))
})
it('2nd test', () => {
cy.get('#link').then(link => console.log('2nd test', link))
})
})
})
Console output
Outer beforeEach
Inner before my-url
1st test my-url
2nd test my-url
You can use this.link and try out.
describe("Share Link", () => {
beforeEach(() => {
cy.generateLink().then(response => {
let url = response.meeting_shared_link.split("/");
cy.wrap(url[url.length - 1]).as("link");
});
describe("when I turn on link sharing", () => {
// This works. Changing it to a before hook breaks it.
beforeEach(() => {
cy.toggleLinkSharing(this.link)
});
});
Reference here:
https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Sharing-Context
When I run a basic test case like this:
fdescribe('Browser back button', () => {
fit('should return to previous', async () => {
const originalHref = window.location.href;
window.history.pushState({}, 'Test', '/test');
window.history.back(); // This doesn't seem to be working.
expect(window.location.href).toEqual(originalHref);
});
I get a failure with the message "Expected 'http://localhost:9876/test' to equal 'http://localhost:9876/context.html'." So clearly the pushState() worked but the back() did not.
Karma runs tests in an inner iframe. When I paste the first three lines in order into the browser's JavaScript console, the navigation works as expected. So there must be something different between the behavior of the window and the behavior of the inner iframe in this case.
This is Karma 5.1 on Chrome.
Any thoughts?
The issue is that history.back() API is asynchronous
This method is asynchronous. Add a listener for the popstate event in order to determine when the navigation has completed.
So the navigation back is triggered but the test does not wait for it to complete and the check fails.
We need to convert the test into an asynchronous one by adding done param (it's one method I know, maybe there are other ways too). And use popstate event handler to wait for navigation and complete the test.
fdescribe('Browser back button', () => {
fit('should return to previous', (done) => {
const originalHref = window.location.href;
window.history.pushState({}, 'Test', '/test');
window.history.back(); // gets executed asynchonously
window.addEventListener('popstate', () => {
// history.back() is done now
expect(window.location.href).toEqual(originalHref);
done();
})
});
});
Vitalii's answer is the most generally applicable one since it relies only on browser built-ins. Here is a basic suite for Angular users, based on their code.
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { MyComponent } from './my.component';
import { Location } from '#angular/common';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MyComponent ],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
fdescribe('Browser back button', () => {
let location: Location;
let currentUrl: string;
const previousUrl = '/test';
beforeEach(() => {
location = TestBed.inject(Location);
currentUrl = location.normalize(window.location.href);
location.go(previousUrl);
location.go(currentUrl);
});
afterEach(() => location.go(currentUrl));
fit('should return to previous', done => {
location.subscribe(e => {
expect(e.url).toEqual(previousUrl);
done();
});
location.back();
});
});
});
To customize the back button's behavior, rename 'should return to previous' to describe whatever you want the button to do instead, and update the test accordingly.
I'm writing unit tests for a vue project that uses firebase. The tests are using vue-test-utils.
The thing is I'm having trouble trying to bypass the auth, and with every test I find myself doing something like this.
...
const login = async () => {
return firebase.auth().signInWithEmailAndPassword(email, pass).then(
res => {
return res
},
err => {
return new Error(err)
})
}
...
describe('some component', () => {
it('test something', async () => {
try {
await login()
...
} catch (e) {
...
}
})
it('test something else', async () => {
try {
await login()
...
} catch (e) {
...
}
})
})
My question is. What is the recommended way to test vue components that use firebase and how to avoid having to login for every test?
Hope I explained my question correctly!
I ended up doing this, as in my case it's enough to do it once.
...
beforeAll(() => {
return login()
})
describe('some component', ...
One approach is to use a beforeEach function in your test suite to get authenticated/check if authentication is still valid, simply:
beforeEach(() => {
try {
await login()
...
} catch (e) {
...
}
})
However, are you doing this logic directly in a vue component? You ideally don't want to make these kind of calls directly from your vue components. If you move that logic out of your component and into a service class (or go a step further and use vuex to manage your state etc..), then you can test the firebase logic separately from your component logic.
I'm getting an error with the Pact JS implementation on the consumer side. When I'm trying to run my tests, I'm getting the following error:
Pact stop failed; tried calling service 10 times with no result.
Attaching snippets of my code below if that would be of any help:
import Pact from "pact";
import wrapper from "#pact-foundation/pact-node";
const mockEventsService = wrapper.createServer({
port: 1234,
spec: 2
});
let provider;
beforeEach(done => {
mockEventsService
.start()
.then(() => {
provider = Pact({
consumer: "Frontend",
provider: "Backend",
port: 1234
});
done();
})
.catch(err => catchAndContinue(err, done));
});
afterAll(() => {
wrapper.removeAllServers();
});
afterEach(done => {
mockEventsService
.delete()
.then(() => {
done();
})
.catch(err => catchAndContinue(err, done));
});
function catchAndContinue(err, done) {
fail(err);
done();
}
In the test itself:
afterEach(done => {
provider
.finalize()
.then(() => done())
.catch(err => catchAndContinue(err, done));
});
Any help would be greatly appreciated. I'm new to this and have no idea how to fix this.
Using pact: 4.2.1 and #pact-foundation/pact-node: 6.0.0
I'm guessing that this is because you're calling delete instead of stop in your afterEach. Delete will stop the service, as well as try to remove the instance of your class altogether.
Personally, I don't like creating a single service which then starts/stops since you're stopping yourself from having concurrent tests running. My preferred approach would be to create a new pact instance for every test on a random port (it does that for you) which is then deleted at the end. This way, you guarantee a fresh instance every time. This would look something like this:
import Pact from "pact";
import PactNode from "#pact-foundation/pact-node";
describe("Some Test Suite", () => {
let provider;
let mockService;
beforeEach(() => {
let mockService = wrapper.createServer({ spec: 2 });
return mockService
.start()
.then(() => {
provider = Pact({
consumer: "Frontend",
provider: "Backend",
port: mockService.options.port // pact-node listens to a randomized, unused port by default
});
}, fail);
});
afterEach(() => mockEventsService.delete().catch(fail));
}
Looks like you're using Jasmine or Mocha, either way, both of these frameworks support promises as long as you return the promise as part of the function, hence you don't need done functions everywhere. In this particular case, I'm letting pact choose which port to run on, then the provider is using the port option (it gets autofilled by pact-node when not set specifically).
Either way, this should work in your case since you're not trying to stop a server that's already been deleted :)
One of my components uses a setTimeout inside ngOnInit, e.g:
ngOnInit() {
setTimeout(() => {
// do some setup stuff using FormBuilder
}, 100);
}
In the unit tests for this component, I need to spy on one of the methods of one of the controls which are built programmatically using FormBuilder, so I am doing this in a beforeEach:
describe('testing something', () => {
beforeEach(() => {
spyOn(component.form.controls.myControl, 'enable');
});
it('does something', () => {
// test stuff
});
});
Before the timeout was added, the test was working perfectly. How can I make the beforeEach wait for the 100 ms timeout in the ngOnInit method?
I've tried adding async and fakeAsync to the outer describe, e.g. like this:
describe('testing something', <any>fakeAsync(() => {
...
}));
or
describe('testing something', async(() => {
...
}));
But in the first case, with fakeAsync I see a message in the test runner Error: Expected to be running in 'ProxyZone', but it was not found., and in the second case it just doesn't even run the test. I also tried wrapping the it methods in fakeAsync but that doesn't do anything to delay the beforeEach
I've tried wrapping the spyOn method inside the beforeEach in a setTimeout and this doesn't appear to have any effect, i.e. the test fails in the same way.
I've also tried using fakeAsync with the beforeEach, like this:
beforeEach(<any>fakeAsync(() => {
tick(100);
spyOn(component.modelForm.controls.myControl, 'enable');
}));
But this doesn't work either. It doesn't result in any errors, but the method I want to spy on doesn't exist yet, even after the tick.
How can I force the beforeEach to wait for the timeout in ngOnInit()? Is it even possible?
The following solution can be adapted for beforeEach if needed
before(() => {
jasmine.clock().install();
})
after(() => {
jasmine.clock().uninstall();
})
it('test case', () => {
spyOn(component.modelForm.controls.myControl, 'enable');
component.ngOnInit();
var timeout = 2000 // 2 seconds
jasmine.clock().tick(timeout);
expect(component.modelForm.controls.myControl.enable).toHaveBeenCalled()
})
I was able to fix this using Jasmine's done callback inside the beforeEach, like this:
beforeEach((done) => {
component.ngOnInit();
setTimeout(() => {
spyOn(component.modelForm.controls.myControl, 'enable');
done();
}, 100);
});
Seems so intuitive!