Cypress custom Commando with cy.origin inside - cypress

I'm tryin to do this command:
Cypress.Commands.add('login', (userId, password) => {
cy.origin('https://some-sso.page', () => {
cy.get('input[placeholder="UserID"]').type(userId);
cy.get('input[placeholder="Password"]').type(password0);
cy.contains('SIGN IN').click();
});
});
And call it with:
cy.login('someUser', '12345');
But I get the error:
userId is not defined
Variables must either be defined within the cy.origin() command or passed in using the args option.
I tried to add some const inside the cy.origin because without the custom command was working, like this:
cy.origin('https://some-sso.page', () => {
const userId = 'someUser';
const pass = '12345';
cy.get('input[placeholder="User ID"]').type(userId);
cy.get('input[placeholder="Password"]').type(pass);
cy.contains('SIGN IN').click();
});
cy.get('#div > section').contains('It works');
How could I do that custom Command?

The cy.origin() command creates a sandbox, which prevents closures from working - you have attempted to use the custom command parameter as a closure variable inside the cy.origin().
Any variables need to be explicitly passed in as a single parameter object, like this
Cypress.Commands.add("login", (userId, password) => {
cy.origin("https://some-sso.page",
{ args: { userId, password } }, // variables passed in
({ userId, password }) => { // inside, unwrapped (destructured)
cy.wrap(userId).should("eq", "someUser"); // passes
cy.get('input[placeholder="UserID"]').type(userId);
cy.get('input[placeholder="Password"]').type(password);
cy.contains("SIGN IN").click();
}
);
});

Related

Cypress env variable is undefined when it's called in different method

In first method below I set env. variable, but when it's necessary to get this variable in another method, executed under the same test run, it's returned as undefined. What can be the cause of such behavior?
setEnvVar = () => {
cy.get('tbody > tr').first().find('span').first().then(($span) => {
Cypress.env('est-id', $span.text())
})
}
fillParameters = () => {
cy.get('div[id=estimates_select]').find('input').type(Cypress.env('est-id'))
}
TLDR
Put the fillParameters() code inside a cy.then().
fillParameters = () => {
cy.then(() => {
cy.get('div[id=estimates_select]')
.find('input')
.type(Cypress.env('est-id'))
})
}
Explanation
All Cypress commands run on a queue. When the test runner runs a test, the parameters for commands like .type() and .log() are set before the command queue starts running.
In your code, that means .type(Cypress.env('est-id')) evaluates env('est-id') before it is set in the preceding function setEnvVar().
The exception is commands added inside a callback such as
cy.then(() => {
// commands added here defer parameter setting
// until this callback is executed in the queue
})
Using Cypress.env() to save values is a hack (IMO)
The .env() was designed to set values outside of the test (why it's called environment)
It's often used as a data store, but closure variables or aliases are better in the situation you describe.
Examples:
Closure variable
let est-id;
setEnvVar = () => {
cy.get('tbody > tr').first().find('span').first()
.then(($span) => est-id = $span.text() )
}
fillParameters = () => {
cy.get('div[id=estimates_select]').find('input')
.type(est-id)
}
Alias
setEnvVar = () => {
cy.get('tbody > tr').first().find('span').first()
.invoke('text').as('est-id')
}
fillParameters = () => {
cy.get('#est-id').then(est-id => {
cy.get('div[id=estimates_select]').find('input').type(est-id)
})
}

Accessing environmental variable in Cypress test

I am trying to call an external script from a Cypress test using cy.exec to create an environmental variable which I then want to access this environmental variable in the test. Is this possible using Cypress.env(<environmental variable>)?
The external script looks like:
const cypress = require('cypress');
const pkg = require('#glib/cypress-secrets');
const { createAuthApiKeyKubeSecret } = pkg;
const username = 'test-consumer';
const apikey = createAuthApiKeyKubeSecret(username);
console.log(apikey);
process.env.apikey = apikey;
This script is called by the before function in the test.
describe("Test to create a capability", function () {
before(() => {
cy.exec('node create-secret.js');
});
after(() => {
cy.exec('node delete-secret.js', {log: true});
});
it('Checks on the Create page', function() {
cy.visit(Cypress.config().baseUrl + "?apikey=" + Cypress.env('apikey'));
// We need to check if we are on the correct page
// We just need to check two elements, a label and a button.
cy.contains('About the capability').should('exist');
cy.contains('button', 'Next Step').should('exist')
});
});
The baseUrl is set correctly but the apikey environmental variable is coming back undefined.
According to the documentation (here) you have to put cypress_ or CYPRESS_ before the variable name:
process.env.cypress_apikey = apikey;

Cypress custom command wont return value

I have a function that I want to add as a command so i can reuse it.
Its on cypress/support/commands.js:
Cypress.Commands.add("generatePassword", () => {
return 'randomstring';
}
);
Then on my test I want to use it as:
it("Visits page", () => {
const password = generatePassword();
cy.log({password})
// Here it logs this:
//{password: {chainerid: chainer146, firstcall: false}}
});
Any idea on how to get the actual value? Now i get this:
{chainerid: chainer146, firstcall: false}
Thanks.
Basically cypress works in promise chain and you're returning the promise chainerid from your custom command. You have to chain it to use in next statement. Use something like below.
it("Visits page", () => {
return cy.generatePassword().then(pwd => {
cy.log(pwd);
});
});

Prompt login with cypress

currently I cannot find a way to insert username and password to a prompt window to login in page using cypress:
login prompt window
does anyone could help me with it? any help is welcome, thanks!
Let say your testing website is www.yourtestingwebsite.com
and your username=username password=password
Your script should be like this if they prompt upon loading your testing site.
cy.visit('https://username:password#yourtestingwebsite.com')
Else,
your may just use cy.visit ('/') in your test.js file but do include following inside your integration/command.js file:
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options)
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options)
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
});
Cypress.Commands.overwrite('visit', async (orig, url, options) => {
let localBool = Cypress.config().baseUrl.includes('local');
if (!localBool) {
const auth = {
username: 'username',
password: 'password'
};
if (options) {
options.auth = auth;
} else {
options = { auth };
}
}
return await orig(url, options);
});
You do this in two chained steps. First you need to get hold of your input. The easiest is usually by name, then use the type() method to enter some data, so...
//html
<input type='text' name='username'/>
// test script
cy.get('input[name="username"]').type('john.doe#email.com')

Pass data from one step to the next synchronously

Running Cypress 3.1.1 with cypress-cucumber-preprocessor 1.5.1. I need to pass some static data from one step to another (in the same scenario/test). I can do this using an alias, like this:
cy.wrap(someString).as('myString'), but then I have to access it asynchronously:
cy.get('#myString').then(myString => ...)
This is rather cumbersome, particularly when I have to pass multiple values, requiring multiple wrapped closures, for no apparent benefit. (Currently I'm working around this by aliasing an object, but I shouldn't need to do this.)
How can I pass primitive values from one step to another synchronously?
I thought I might be able to simply set this.myString='' to set the value on the Mocha shared context object, but in that case, the property exists but is set to undefined when accessed in later steps.
Even creating my own context variable with let outside of the step definition does not work. Is this simply a limitation of Cypress and/or the cypress-cucumber-preprocessor?
I managed to get it working the following way:
Add 2 tasks to the /plugins/index.js
const testStore = {}
module.exports = (on, config) => {
on('task', {
pushValue({ name, value }) {
console.log(name, value)
testStore[name] = value
console.log(testStore)
return true
},
})
on('task', {
getValue(name) {
return testStore[name]
},
})
Then you can add a variable in any test and reach it in any other place:
it('test', ()=>{
cy.task('pushValue', { name: 'orderNumber', value: orderNumber })
})
it('test 2', ()=>{
cy.task('getValue', 'orderNumber').then((order) => {
cy.visit(`/bookings/${order}`)
})
})
Here is a slightly more complicated (and not fully tested) method. A custom command can be added to save values to a global object.
In the Cypress test runner, all the tests seem to run sequentially, but you may have to be careful if using CI and parallel execution.
In /support/commands.js
export const testStore = {}
Cypress.Commands.add('saveAs', { prevSubject: true }, (value, propName) => {
console.log('saveAs', value, propName)
testStore[propName] = value;
return value;
})
In myTest.spec.js
import { testStore } from '../support/commands.js'
...
it('should have a title', () => {
cy.title()
.saveAs('title') // save for next test
.should('contain', 'myTitle) // this test's expectation
});
it('should test something else', () => {
cy.get('.myElement').contains(testStore.title);
});

Resources