How to reuse code inside another spec.js? - cypress

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.

Related

What is the advantage of Cypress commands over vanilla functions?

It is known that in Cypress we can define custom commands in the commands.js file, with that syntax:
Cypress.Commands.add('login', (email, pw) => {})
Cypress.Commands.overwrite('visit', (orig, url, options) => {})
These commands will then become available across all our tests and can be used from the cy object.
cy.login('my#email.com', '123456')
cy.visit('www.stackoverflow.com', 'www.google.com', { redirect: true })
But I don't get the gist of it. What is the point of doing that when you could just write a regular function?
function login(email, pw) { /* ... */ }
login('my#email.com', '123456')
The only advantage I see is making the function available everywhere without having to export/import it, but you can do that with globals as well. Is that it, or am I missing something?
1. Don't make everything a custom command.
Don't be forget cypress provide API for overwriting existing commands not for only creating new commands.
JS function is great for grouping cy commands and preferable due to it simplicity and readability.
There’s no reason to add this level of complexity when you’re only wrapping a couple of commands only for avoiding Export/Import.
2. Don't overcomplicate things.
A good test automation framework will be is easily understandable, readable and maintainable.Try not to overcomplicate things and create too many abstractions. When in doubt, Always use a JS function.
check out here: https://docs.cypress.io/api/cypress-api/custom-commands#Best-Practices
Yes you are right! The goal is to write cleaner code and availability. With custom commands you can basically write the function in the form of a cypress command and also you don't have to write an import statement in the tests suites. Hence your test suite files remain uniform and cleaner.

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 command vs JS function

The Cypress documentation suggests that commands are the right way to reuse fragments of code, e.g.
Cypress.Commands.add("logout", () => {
cy.get("[data-cy=profile-picture]").click();
cy.contains("Logout").click();
});
cy.logout();
For simple cases like this, why would I use a command over a plain JS function (and all the nice IDE assistance that comes with it). What are the drawbacks of rewriting the above snippet as
export function logout(){
cy.get("[data-cy=profile-picture]").click();
cy.contains("Logout").click();
}
// and now somewhere in a test
logout();
Based on my experience with Cypress (one year project and several hundred test cases), I can say that a plan JS function is great for grouping cy commands.
From my point of view, a custom cy command may be really useful only if it is incorporated into the chain processing (utilizes the subject parameter or returns a Chainable to be used further in the chain). Otherwise, a plain JS function is preferable due to it simplicity and full IDE support (unless you're using an additional plugin).
If you for any reason need to do something inside the cypress loop, you can always wrap you code by cy.then() in a plain JS function:
function myFunction() {
cy.then(() => {
console.log(("I'm inside the Cypress event loop"))
})
}
Commands are for behavior that is needed across all tests. For example, cy.setup or cy.login. Otherwise, use functions.
See official docs: https://docs.cypress.io/api/cypress-api/custom-commands#1-Don-t-make-everything-a-custom-command

Prevent re-loading of javascript if functions already exist. Otherwise ensure synchronous loading

Using JQuery.load(), I change the content of my website's mainWindow to allow the user to switch between tabs. For each tab, there is one or multiple scipts that contain functions that are executed once the tab content is loaded.
Obviously when switching to the tab for the first time, the script has to be fetched from the server and interpreted, but this shouldn't happen if the user switches back to the tab later on. So, to put it short:
Load() html
make sure javascript functions exist, otherwise load script and interpret it.
call a a function on the javascript after the DOM is rebuilt.
Step one and two have to be complete before step 3 is performed.
At the moment, I am using nested callbacks to realize this:
function openFirstTab(){
$("#mainWindow").load("firstTab.php", function(){
if(typeof(onloadfFirstTab) != "function"){
jQuery.getScript("assets/js/FirstTab.js", function(){
onloadFirstTab();
});
}
else{
onloadFirstTab();
}
} );
}
but I feel that there should be a better way.
You can't write the code entirely synchronously since you can't load script synchronously after page load ( unless you do a synchronous XHR request and eval the results - not recommended ).
You've got a couple of choices. There are pre-existing dependency management libs like RequireJS which may fit the bill here, or if you just need to load a single file you can do something like this to clean up your code a bit rather than using if/else:
function loadDependencies() {
// For the sake of example, the script adds "superplugin" to the jQuery prototype
return $.getScript( "http://mysite.com/jquery.superplugin.js" );
}
function action() {
// If superplugin hasn't been loaded yet, then load it
$.when( jQuery.fn.superplugin || loadDependencies() ).done(function() {
// Your dependencies are loaded now
});
}
This makes use of jQuery Deferreds/Promises to make the code much nicer.
If you don't want to load the JS more than once and you are going to dynamically load it, then the only way to know whether it's already loaded is to test for some condition that indicates it has already been loaded. The choices I'm aware of are:
The simplest I know of is what you are already doing (check for the existence of a function that is defined in the javascript).
You could also use a property on each tab (using jQuery's .data() that you set to true after you load the script.
You could write the dynamically loaded code so that it knows how to avoid re-initializing itself if it has already been loaded. In that case, you just load it each time, but the successive times don't do anything. Hint, you can't have any statically defined globals and you have to test if it's already been loaded before it runs its own initialization code.
(Haven't tested it yet, so I am not sure if it works, especially since I didn't yet really understand scope in javascript:)
function require(scripts, callback){
var loadCount = 0;
function done(){
loadCount -=1;
if (loadCount==0){
callback();
}
}
for ( var script in scripts){
if (!script.exitsts()){
loadCount +=1;
jQuery.getScript(script.url, done);
}
}
}
This function takes an array of scripts that are required and makes sure all of them are interpreted before it calls the callback().
The "script" class:
function script(url, testFunc){
this.url =url;
this.testFunction = testFunc;
this.exists = function(){
if(typeof(testFunction)=="function"){
return true;
}
else{
return false;
}
}
}
Where the test-function is a function that is defined (only) in the concerned script.
PS:
To enable caching in JQuery and thus prevent the browser from doing a GET request every time getScript() is called, you can use one of the methods that are presented here.
Even though unnecessary GET - requests are avoided, the script is still getting interpreted every time getScript() is called. This might sometimes be the desired behavior. But in many cases, there is no need to re-interpret library functions. In these cases it makes sense to avoid calling getScript() if the required library functions are already available. (As it is done in this example with script.exists().

Can modernizr load scripts asynchronously but execute them in order?

I'm experimenting with Modernizer.load.
I have this:
Modernizr.load([
{
load : ['/js/jquery-1.6.1.js', '/js/jquery.tools.min.js', '/js/myscript.js']
}
]);
If I understand correctly, I can use code like this to load scripts asynchronously. However, can I then execute them in order? What if myscript.js requires the jquery object to be loaded first?
In the example in the modernizr documentation, load([]) can take a 'complete' property, the parameter of which can be a function that can load another script when everything else is done. However, if I use a function here to load my post-dependancy script, then it loads in serial. The docs specifically say that this can harm perfomance.
However, if I load everything asynchronously, I don't have any idea about the order in which they run. And of course, I need my dependancies to run first.
If you use Modernizr.load, all the files you include via the embedded list/hash will be loaded asynchronously, but they’ll each be executed in the order that you put them in.
So, your example will load the files asynchronously but execute them in this order:
1: /js/jquery-1.6.1.js
2: /js/jquery.tools.min.js
3: /js/myscript.js`
You can simplify your example, by the way:
Modernizr.load(['/js/jquery-1.6.1.js', '/js/jquery.tools.min.js', '/js/myscript.js']);
For more details, see the Modernizr.load() tutorial in the Documentation, or check out Yepnopejs.com (which is what Modernizr.load() basically is, at this time).

Resources