Cypress basic authentication in all cy.visit requests - cypress

I have a cy.visit() calls with basic authentication which looks like this:
it('launch website', () => {
cy.visit('url', {
auth: {
username: '....',
password: '....',
},
})
And it works fine. But in order to make this code more useable, I dont want to hard code my credentials in every test, but I want to create a command so every time I visit that page, it uses my basic auth. I tried to implement solutions from Adding basic auth to all requests in Cypress , but it didnt work for me. It doesnt accept credentials that are stored in command. file. Any ideas other then those already mentioned?
Many thanks :)

You can use Cypress custom commands for this.
Go to cypress/support/commands.js and wrte:
Cypress.Commands.add('authenticateUrl', (url, user, pass) => {
cy.visit(url, {
auth: {
username: user,
password: pass,
},
})
})
In your test you can just write this. You can pass url, username and passwords as parameters.
cy.authenticateUrl('https://example.com', 'admin', 'pass123')
In case you don't want to use paramters, you can directly harcode url, username and password inside the custom command.
Cypress.Commands.add('authenticateUrl', () => {
cy.visit('https://example.com', {
auth: {
username:'admin',
password:'pass123',
},
})
})
In your test just use:
it('launch website', () => {
cy.authenticateUrl() //Launch URL and authenticate
})

This can be avoided by overwriting the visit function. Add this to cypress/support/index.js
Cypress.Commands.overwrite('visit', (originalVisit, url) => {
originalVisit(url, {
auth: {
username: 'username',
password: 'password'
}
})
});
Notice that this code only working with cy.visit(url). If you pass another arguments you should change it according to your situation.

Related

cy.origin() unable to find any elements on Auth0 page

I'm trying to automate a login process for our site which uses Auth0 & Google Sign in. On the login page if you click Google sign in you get sent to an Auto0 page with a form and another Google sign in link, the page contains a URL something like:
https://OURDOMAIN.auth0.com/login?state=*REMOVED*
It's the first time I'm trying to use cy.origin() In my test I'm trying this:
cy.get("a[testid='googleSignInButton']").click()
cy.origin('https://OURDOMAIN.auth0.com/', () => {
cy.get("input[type='email']").type('anEmail#me.com')
})
The problem is whatever I try to do in the origin block just returns a timeout trying to find the element.
I've set experimentalSessionAndOrigin: true is there something I'm doing wrong? Unfortuantly the way we are using Auth0 and Google Sign in means it's not possible to do it via API calls.
try without cy.origin()
I had the same problem, and it was fixed by removing the cy.origin()
Try this one:
Cypress.Commands.add('loginSession', (email, password) => { cy.session([email, password], () => {
cy.visit('/').then(() => {
//cy.origin('auth0.com', { args: [email, password] }, ([email, password]) => {
cy.get('#username').type(email)
cy.get('#password').type(password)
cy.get("button[type='submit']").click({ force: true })
// })
})
cy.get('[data-cy="logo"]').should('be.visible')
})
});

Cypress cy.visit() is not redirecting to correct URL

I am trying to bypass the UI login by using cy.request() to log a user in and cy.visit() to go to the restricted route. I have followed this doc: https://docs.cypress.io/guides/end-to-end-testing/testing-your-app#Bypassing-your-UI
However, the test fails because the visit URL ('http://localhost:3000/join-or-create') is not loaded and instead the home page URL is loaded (http://localhost:3000/).
This is my test code:
describe('when user is signed in but has not joined a group', () => {
beforeEach(() => {
cy.request('POST', 'http://localhost:5000/api/testing/reset');
const user = {
name: 'Joe Bloggs',
email: 'Joe#Bloggs.com',
password: 'Password',
};
cy.request('POST', 'http://localhost:5000/register', user);
cy.request('POST', 'http://localhost:5000/sign-in', user);
cy.visit('http://localhost:3000/join-or-create');
});
it.only('should allow a logged in user to join or create a group', () => {
cy.contains('Join a group');
cy.contains('Create a group');
});
});
If I change cy.contains('Join a group'); to cy.contains('Welcome'); (which is content on the URL 'http://localhost:3000/') then the test passes.
If I use:
cy.visit('http://localhost:3000');
cy.get('[data-testid="email"]').type('Joe#Bloggs.com');
cy.get('[data-testid="password"]').type('Password');
cy.contains('sign in').click();
instead of cy.visit('http://localhost:3000/join-or-create'); the test passes.
The output of the test body shows that is redirecting to a new URL 'http://localhost:3000/' (as shown in the screenshot below) but I can't figure out why.
Thanks for any help.
In the bypass code, check the response from the POST request
cy.request('POST', 'http://localhost:5000/sign-in', user)
.then(response => console.log(response))
to see what tokens are returned.
Then in the form-login code, look at what happens to the same token after cy.contains('sign in').click() and see where the browser stores same token.
That's probably the step missing in the bypass code. You'll need to add something to do the same, e.g
cy.request('POST', 'http://localhost:5000/sign-in', user)
.then(response => {
const token = response.body.token; // figure out where the token is in response
cy.setCookie(token); // figure out where the app want's the token
})
It's also difficult to tell from the question what URL you need to cy.visit() in the bypass code, but there's only a couple of them so try both permutations.

Cypress: browser authentication not working inside a custom command

I have a website which needs browser authentication on all page visits. If I use the below code on all page visits it works fine:
describe('WW2-3461 validate patches for contrib modules and core', () => {
it('should visit my base URL', () => {
cy.visit(Cypress.env('base_url'), {
// Add basic auth headers
auth: {
username: Cypress.env('browserAuthentication').username,
password: Cypress.env('browserAuthentication').password
},
failOnStatusCode: false
})
})
})
I am passing the base_url as an environment through CLI:
npx cypress run --spec file.spec.js --env base_url=$base_url
I don't want to use browserauthentication on all page visits, so I created a custom command like below (in integration/project/utility.js file):
Cypress.Commands.add('addBrowserAuthentication', (url) => {
cy.visit(url, {
// Add basic auth headers
auth: {
username: Cypress.env('browserAuthentication').username,
password: Cypress.env('browserAuthentication').password
},
failOnStatusCode: false
})
})
However if I call this command inside a spec file, like below:
import './utility.spec'
describe('WW2-3461 validate patches for contrib modules and core', () => {
beforeEach(() => {
cy.addBrowserAuthentication(Cypress.env('cypress_host'))
})
it('should visit my base URL', () => {
cy.visit(Cypress.env('cypress_host'), {
})
})
the browser authentication does not seem to work (tried to call it inside before() hook or directly inside the test scenario) as it returns
> 401: Unauthorized
This was considered a failure because the status code was not `2xx`.
This http request was redirected '1' time to:
- 302: https://mysite.local
If you do not want status codes to cause failures pass the option: `failOnStatusCode: false`
I can't seem to work around this issue. Tried using cy.request() but getting the same issue. Has someone faced this issue?
You don't need to pass env variable as a parameter, you can directly access it inside the custom command.
Cypress.Commands.add('addBrowserAuthentication', () => {
cy.visit(Cypress.env('base_url'), {
// Add basic auth headers
auth: {
username: Cypress.env('browserAuthentication').username,
password: Cypress.env('browserAuthentication').password,
},
failOnStatusCode: false,
})
})
And in your test directly use:
cy.addBrowserAuthentication()
Also I am not sure if its required to visit the website two times. So you can remove cy.visit(Cypress.env('cypress_host') if you are already using cy.addBrowserAuthentication().
Since this works
cy.visit(Cypress.env('base_url'), {
// Add basic auth headers
auth: {
username: Cypress.env('browserAuthentication').username,
password: Cypress.env('browserAuthentication').password
},
failOnStatusCode: false
})
but this doesn't
cy.addBrowserAuthentication(Cypress.env('cypress_host'))
it looks like Cypress.env('cypress_host') is not the same as Cypress.env('base_url').
Everything else looks good in your command, so I'd try with
cy.addBrowserAuthentication(Cypress.env('base_url'))
When you cy.visit(Cypress.env('cypress_host') without headers inside the test, the auth headers from addBrowserAuthentication are lost. I suggest removing that cy.visit().

Mock Graphql server with multiple stubs in Cypress

Problem:
I’m using cypress with angular and apollo graphQl. I’m trying to mock the graph server so I write my tests using custom responses. The issue here is that all graph calls go on a single endpoint and that cypress doesn’t have default full network support yet to distinguish between these calls.
An example scenario would be:
access /accounts/account123
when the api is hit two graph calls are sent out - a query getAccountDetails and another one with getVehicles
Tried:
Using one stub of the graph endpoint per test. Not working as it stubs with the same stub all calls.
Changing the app such that the query is appended 'on the go' to the url where I can intercept it in cypress and therefore have a unique url for each query. Not possible to change the app.
My only bet seems to be intercepting the XHR call and using this, but I don't seem to be able to get it working Tried all options using XHR outlined here but to no luck (it picks only the stub declared last and uses that for all calls) https://github.com/cypress-io/cypress-documentation/issues/122.
The answer from this question uses Fetch and therefore doesn't apply:
Mock specific graphql request in cypress when running e2e tests
Anyone got any ideas?
With cypress 6.0 route and route2 are deprecated, suggesting the use of intercept. As written in the docs (https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-GraphQL-requests) you can mock the GraphQL requests in this way:
cy.intercept('POST', '/api', (req) => {
if (req.body.operationName === 'operationName') {
req.reply({ fixture: 'mockData.json'});
}
For anyone else hitting this issue, there is a working solution with the new cypress release using cy.route2()
The requests are sent to the server but the responses are stubbed/ altered on return.
Later Edit:
Noticed that the code version below doesn't alter the status code. If you need this, I'd recommend the version I left as a comment below.
Example code:
describe('account details', () => {
it('should display the account details correctly', () => {
cy.route2(graphEndpoint, (req) => {
let body = req.body;
if (body == getAccountDetailsQuery) {
req.reply((res) => {
res.body = getAccountDetailsResponse,
res.status = 200
});
} else if (body == getVehiclesQuery) {
req.reply((res) => {
res.body = getVehiclesResponse,
res.status = 200
});
}
}).as('accountStub');
cy.visit('/accounts/account123').wait('#accountStub');
});
});
Both your query and response should be in string format.
This is the cy command I'm using:
import * as hash from 'object-hash';
Cypress.Commands.add('stubRequest', ({ request, response, alias }) => {
const previousInteceptions = Cypress.config('interceptions');
const expectedKey = hash(
JSON.parse(
JSON.stringify({
query: request.query,
variables: request.variables,
}),
),
);
if (!(previousInteceptions || {})[expectedKey]) {
Cypress.config('interceptions', {
...(previousInteceptions || {}),
[expectedKey]: { alias, response },
});
}
cy.intercept('POST', '/api', (req) => {
const interceptions = Cypress.config('interceptions');
const receivedKey = hash(
JSON.parse(
JSON.stringify({
query: req.body.query,
variables: { ...req.body.variables },
}),
),
);
const match = interceptions[receivedKey];
if (match) {
req.alias = match.alias;
req.reply({ body: match.response });
}
});
});
With that is posible to stub exact request queries and variables:
import { MUTATION_LOGIN } from 'src/services/Auth';
...
cy.stubRequest({
request: {
query: MUTATION_LOGIN,
variables: {
loginInput: { email: 'test#user.com', password: 'test#user.com' },
},
},
response: {
data: {
login: {
accessToken: 'Bearer FakeToken',
user: {
username: 'Fake Username',
email: 'test#user.com',
},
},
},
});
...
Cypress.config is what make it possible, it is kind of a global key/val getter/setter in tests which I'm using to store interceptions with expected requests hash and fake responses
This helped me https://www.autoscripts.net/stubbing-in-cypress/
But I'm not sure where the original source is
A "fix" that I use is to create multiple aliases, with different names, on the same route, with wait on the alias between the different names, as many as requests you have.
I guess you can use aliases as already suggested in Answer by #Luis above like this. This is given in documentation too. Only thing you need to use here is multiple aliases as you have multiple calls and have to manage the sequence between them . Please correct me if i understood you question in other way ??
cy.route({
method: 'POST',
url: 'abc/*',
status: 200.
response: {whatever response is needed in mock }
}).as('mockAPI')
// HERE YOU SHOULD WAIT till the mockAPI is resolved.
cy.wait('#mockAPI')

testing custom redux middleware, which handles ajax request

I am building a react-redux app, using custom redux middleware.
In the definition of my project, action only provides an object to define action type and necessary parameters for middleware and reducer. All the ajax request will be handle by middleware. This is the life cycle would look like:
action -> middleware(if action is intercepted) -> reducer -> store
When the user tries to log in, the operation on the react component will fire an action, which would look like this:
export function login(username, password) {
return {
type: 'LOGIN',
username: username,
password: password
}
}
export function authSucceed(username, isAdmin) {
return {
type: 'AUTHSUCCEED',
username: username,
isAdmin: isAdmin
}
}
export function authFail(text) {
return {
type: 'AUTHFAIL',
errorMessage: text
}
}
Then middleware will use the parameters passed in action to send ajax request, which would be like this.
export function customedMiddleware(store) {
return next => action => {
if (action.type === 'LOGIN') {
axios.post(url + '/api/login', {
username: action.username,
password: action.password
})
.then(res => {
if (res.status === 200) {
store.dispatch(actions.authSucceed(res.data.username, res.data.isAdmin));
} else {
store.dispatch(actions.authFail(res.data));
}
})
.catch(error => console.log(error));
}
return next(action);
};
}
After the middleware sends login request to server, depending on whether the authentication succeeds or not, the middleware will dispatch some action in reducer correspondingly. Since authSucceed and authFail would not be intercepted by middleware, reducer will process accordingly.
export default function(state = false, action) {
switch(action.type) {
case 'AUTHSUCCEED':
return true;
case 'AUTHFAIL':
return false;
case 'LOGOUT':
return false;
}
return state;
}
What has been done here in reducer is to change the system state. If the state is true, the front-end will render the information page. If the state is false, the front-end will remain in the login page.
I like system definition this way. Every MVC part is well isolated. However, it's very difficult to test the middleware. Currently, I am testing this way:
it('should dispatch authSucceed if signup with correct info', () => {
nock('http://localhost:8080')
.post('/api/signup', {
username: 'bruce',
password: 'Gx1234'
})
.reply(200, {
username: 'bruce',
isAdmin: false
});
const createStoreWithMiddleware = applyMiddleware(customedMiddleware)(createStore);
const store = createStoreWithMiddleware(reducers);
const dispatch = sinon.spy(store, 'dispatch');
store.dispatch(actions.login('bruce', 'Gx1234'));
setTimeout(() => {
expect(dispatch.calledWith({
type: 'AUTHSUCCEED',
username: 'bruce',
isAdmin: false
})).to.be.true;
}, 100);
});
I dispatch login action. Then spy the whether authSucceed action and authFail action will be called correctly within 100ms. This method works if there is only one test to be run. If there are more then one test running in sequence, they might affect each other. I have to adjust the time delay of the setTimeout to make it work for all cases, which is 10ms.
I don't feel comfortable this way. I can't make sure whether it just work for me or for everybody too, since absolute time is related to hardware.
I would really appreciate if anybody can give me some advice on how to test this custom middleware.
Your code works fine, but you shouldn't need a setTimeout with such a long time since using nock makes the remote resquest response instantly. The problem is promises enqueue microtasks and they only run after a macrotask is finished (in your case, it()), in the same event loop.
That's why you need setTimeout to enqueue another macrotask, the time doesn't make a difference. I believe setImmediate should work as well.

Resources