Cypress, page content and variables - asynchronous

Right or wrong: In Cypress, its impossible to read a value on page X, then keep this value and compare it to a value on page Y.
I can read a value from the page and log it:
cy.get('[data-e2e-selector=whatever]').then(elm => cy.log('Value from page X : ' + elm))
or, for instance, the number of elements with similar or partially matchin selectors:
cy.get('[data-e2e-selector=^whatever]').then(elm => cy.log('Number of elements like this on page X: ' + elm.length))
Hoever, I cannot create a variable of this, because of the asynchronous way Cypress runs. Right? Any value created to just be blank.
Nor can I pass the value read to a method, which in turn compares it to the value on the next page:
compareToValueOnNextPage(cy.get('[data-e2e-selector=^whatever]').then(elm => elm.length));
compareToValueOnNextPage(value: number) { // NB: Not sure if it's a number or string yet...
cy.get('[data-e2e-selector=^whateverNextPage]').then(elm => elm.length).should('have.length', 4)
}
Basically, if I want to compare values, the either have to be on the same page, or they need to be hard-coded. This is a huge limitation when actually end-to-end testing some applications. Very often, a value is created on page X, based on input which should in many cases ba random, thus creating a dynamic value in the test. Then, on page Y, that same value is fetch (from the backend) or shown in some other way, in a Summary etc. And, naturally, I want to compare the value shown on page X to the one shown on page Y/Summary. But this is not possible, due to the very unit-testing thinking that seems to be the foundation for Cypress.
Or am I missing something here? Are there ways around this that aren't ugly/smelly? I think it's possible to store the value on page X in a file, then read that file on page Y. However, Cypress seems to only have one option when reading the file, and that's reading the whole file and looking for a match. So that file would be a mess. Or I'd need several files.
I realize this is kind of trying to impose non-functional ways on a quite functional and asynchroeous technology. However, if it's not possible to "keep" a value and use it later, it's very limiting when it comes to end-to-end testing (even though it's not when the testing is unit-based on frontend components).
UPDATE:
As per Kerrry's suggestion in the answer below:
cy.get('[data-e2e-selector=dellan-accordion]')
.then(elm => cy.wrap(elm.length).as("myVariableName"));
-GO TO NEXT PAGE-
cy.get('[data-e2e-selector=betalingsplan-dellan]')
.then(elm => elm.length)
.should('have.length', myVariableName)
This yeilds "expected 4 to have property 'length'.
This means, obviously, that I cannot get the length of the length.
So I replace 'have.length' with 'eq':
cy.get('[data-e2e-selector=betalingsplan-dellan]')
.then(elm => elm.length)
.should('have.length', myVariableName)
And I get the following error:
expected 4 to equal 0
So, it seems that the first variable - myVariable - is gone after the first cy.get().
If I do everything inside one get (have another get inside that, where I go to the next page AND get the count of the elements), then it works. But the way Kerry shows it, it would be much more flexible. But, alas, the above error.

As jonrsharpe mentioned in the comments, please read the Cypress document on variables and aliases thoroughly. This is a core concept of Cypress, and it will give you a solid understanding of how to implement variables and carry the values between test steps.
The reader's digest example of what you how you can achieve is this:
cy.get('[data-e2e-selector=^whatever]')
.then(elm => cy.wrap(elm.length).as("myVariableName"));
What this is doing is cy.wrap will yield the value from elm.length in a Cypress command chain, which then allows you to assign it to an alias "myVariableName".
In your following test step where you want to compare the value on a separate page, you would then access the alias' value in one of two ways:
Using this.
cy.get('[data-e2e-selector=^whateverNextPage]')
.then(elm => elm.length)
.should('have.length', this.myVariableName)
OR
via cy.get()
cy.get("#myVariableName").then(function(variableValue){
cy.get('[data-e2e-selector=^whateverNextPage]')
.then(elm => elm.length)
.should('have.length', variableValue)
})

Related

Ambiguous match, found 2 elements matching css, how to get to the second one?

I am having problems in Capybara with the Ambiguous match problem. And the page provides no 'ids" to identify which one is which.
I am using within function.
within('.tile.tile-animation.animation-left.animation-visible.animated') do
#some code in here
end
I've used the :match option which solved my first problem.
within('.tile.tile-animation.animation-left.animation-visible.animated', :match => :first) do
#some code in here
end
The question is how to get to the SECOND css '.tile.tile-animation.animation-left.animation-visible.animated' ?
It depends on the html -- a simple solutions is
within(all('.tile.tile-animated.animation-left.animation-visible.animated')[1]) do
# some code in here
end
which will scope to the second matching element on the page, but won't be able to auto-reload if the page changes, and won't wait for the elements to appear. If you need it to wait for at least two elements to appear you can do
within(all('.tile.tile-animated.animation-left.animation-visible.animated', minimum: 2)[1]) do
....
which will wait some time for at least the 2 elements to appear on the page, but still won't be able to auto-reload if the page changes. If you need the ability to auto-reload on a dynamically changing page it will need to be possible to write a unique selector for the element (rather than indexing into the results of #all.

Trying to figure out what this line of code means

This code is from a program I use to enter and track information. Numbers are entered as work orders (WO) to track clients. But one of the tables is duplicating the WO information. So I was trying to figure out a general outline of what this code is saying so that the problem can be fixed.
Here is the original line:
wc.dll?x3~emproc~datarecord~&ACTION=DISPLAY&TABLE+WORK&KEYVALUE=<%work.wo%&KEYFIELD=WO
What I think I understand of it so far, and I could be very wrong, is:
wc.dll?x3~emproc~datarecord~&ACTION
//No clue because don't know ~ means or using & (connects?Action)
=DISPLAY&TABLE+WORK&KEYVALUE
//Display of contents(what makes it pretty) and the value inside the table at that slot
=<work.wo%&KEYFIELD
//Calling the work from the WO object
=WO
//is Assigning whatever was placed into the WO field into the left side of the statement
I'll do my best to interpret the statement, with the limited information you've provided:
wc.dll is an instruction to invoke a DLL
? is introducing a list of parameters (like a query string in HTTP)
x3~emproc~datarecord~ seems like a reference to a function in the dll
& parameter separator
ACTION=DISPLAY set the ACTION parameter to the value DISPLAY
TABLE+WORK perhaps sets a couple of flags
KEYVALUE=<%work.wo% set the KEYVALUE parameter to the value of <%work.wo%
KEYFIELD=WO set the KEYFIELD parameter to the value WO
Hope that helps.

Refactor Massive Cucumber Step Definition

My team is currently taking our old UI acceptance test scripts and automating them. To do this we are using Jruby, Cucumber and Watir-Webdriver. So far the automation process has been going pretty well. The only problem we have is that our step definitions are starting to get a bit out of hand.
For example, in most of our scenarios is a section like this:
Given I press the SEARCH_BUTTON
Then I should land on the SEARCH_PAGE
and the step definitions look like this:
Given(/I press the (.*)$/) do |buttonName|
if buttonName == 'SEARCH_BUTTON'
eval "$browser.#{$DataHash['home']['searchButton']}.when_present.click"
elsif buttonName == 'LOGIN_BUTTON'
eval "$b.#{$DataHash['loginPage']['loginButton']}.click"
elsif buttonName == 'HOME_BUTTON'
eval "$b.#{$DataHash['mainPage']['HomeButton']}.click"
elsif buttonName == 'ADD_PRODUCT_BUTTON'
#This if else ladder goes on like this for another 300+ lines
...
end
end
The $DataHash variable refers to config.yml, which uses a hash to store all of the different web elements we are using.
config.yml
home:
searchButton: "link(:id => 'searchBtn')"
searchTypeSelectBox: "select_list(:name => 'matchType')"
searchResetButton: "button(:id => 'resetSearch')"
#rest of the elements on the home page...
loginPage:
loginButton: "link(:id => 'login')"
#rest of the elements on the login page...
....
So $browser.$DataHash['home']['searchButton'].when_present.click is equivalent to $browser.link(:id => 'searchBtn').when_present.click
We are using this basic step definition for every button that a user could click, and at this point this one step definition is something like 300+ lines of code. Most of which are single lines like above. Our other step definitions have the same sort of problem. Are there any good ways of refactoring our step definitions to make them less bloated, or at least easier to search through, without making the actual steps any less re-useable?
Initially we thought we could have the same step definition in multiple files based on which page was being tested. So in searchDefinitions.rb there would be a step definition for Given(/I press the (.*)$/) do |buttonName| which only had the different buttons found on the search page. Then in homeDefinitions.rb would be the same step definition but only with code for the home page buttons. Basically breaking up the if-else ladder across multiple files. Of course, Cucumber doesn't allow the same step definition in multiple files so now we're at a bit of a loss.
As you mentioned you can reuse steps see Reuse Cucumber steps. But I personally found it pretty complicated when I tried to do it. So, from my side I suggest you to implement Page Object pattern. The idea is that you describe your pages or even some modules like separate entities which provides you with ability to interact with them. For understanding concept see here. And here you can find some example. Assuming this your step definition would like
Given(/I press the (.*)$/) do |buttonName|
#my_home_page.click_search_button
...
end
end
Where click_search_button method encapsulates your 'ladder' logic to press login button if search button is not present yet.
Hopefully it makes sense for you.
Supposing that the minor differences in the eval lines you show don't matter, extract the hash values that vary into a constant
BUTTON_KEYS = {
'search' => %w(home searchButton),
'login' => %w(loginPage loginButton)
# ...
}
and use it in your step definition:
Given(/I press the (.*) button$/) do |button_name|
keys = BUTTON_KEYS['button_name']
eval "$browser.#{$DataHash['#{keys[0]}']['#{keys[1]}']}.when_present.click"
end
Now you have half as many lines of code and less duplication.
I changed the step regexp to include "button", to remove that duplication from the button names, and the button names to be lowercase, as in normal English. Whether or not you're showing your feature files to non-programmers, Cucumber step names should read like natural language so that you can think about product requirements and not implementation details when you're reading them.
Alternative suggestion, valid if the two levels of keys in the the YAML are not really needed:
You could restructure the YAML like so
search button: "link(:id => 'searchBtn')"
search type select box: "select_list(:name => 'matchType')"
search reset button: "button(:id => 'resetSearch')"
# rest of the elements on the home page...
login button: "link(:id => 'login')"
# rest of the elements on the login page...
# ...
Then you wouldn't need the hash at all, and your step could just be
Given(/I press the (.*)$/) do |element_name|
eval "$browser.#{$DataHash['#{element_name}']}.when_present.click"
end
Or you could convert the YAML entirely into a hash (representing the method as a string and calling it with .send), which would prevent you from making some syntax errors.

Drupal 7 function menu_tree_page_data not working correctly?

Reading the documentation for menu_tree_page_data(), it seems to me that I should be able to retrieve the entire contents of my main menu by calling the function in the following way: menu_tree_page_data('main-menu'). Since param 2, $max_depth, defaults to NULL, this should recursively go through all levels. Since param 3, $only_active_trail, defaults to FALSE, this should retrieve all links, not just those on the active trail.
However, when calling this function, I end up with the following type of tree data.
All links on the first level of depth.
All links on the second level of depth.
Only active links on the third level of depth.
What's going on here? I've also tried explicitly setting params as menu_tree_page_data('main-menu', 10, FALSE) just to make sure that I was not misinterpreting the default behavior. I've had to write my own function to give me the entire menu, but it doesn't have active-trail data. Now, I'm finding myself in a situation where I'm blending the two functions to create a full menu tree WITH active-trail information, and it is extremely messy. It'd be great if one solution just worked.
Alternatively, is there a simple way of determining active trail for a given menu item? If so, I'll just add that to my already-working tree-crawler function and call it a day.
I think it's a problem with the static caching in menu_tree_page_data(). The cache ID is built up using (among other things) the $max_depth passed into the function or MENU_MAX_DEPTH, whichever is smaller.
Since MENU_MAX_DEPTH is equal to 9, when you pass 10 into the function this is automatically reset to 9, and the cache ID is built from that. If another function has already called menu_tree_page_data() with MENU_MAX_DEPTH as the $max_depth argument (which does happen earlier on in a Drupal page), and with $only_active_trail set to TRUE then the statically cached data will be returned with only active trail items.
I think the following will work:
drupal_static_reset('menu_tree_page_data');
$tree = menu_tree_page_data('main-menu');
If it doesn't, you might also need to clear the cache for another function that's involved (_menu_build_tree()) so try this:
drupal_static_reset('_menu_build_tree');
drupal_static_reset('menu_tree_page_data');
$tree = menu_tree_page_data('main-menu');
Hope that makes sense it's quite tricky to explain :)

Nested REST Routing

Simple situation: I have a server with thousands of pictures on it. I want to create a restful business layer which will allow me to add tags (categories) to each picture. That's simple. I also want to get lists of pictures that match a single tag. That's simple too. But now I also want to create a method that accepts a list of tags and which will return only pictures that match all these tags. That's a bit more complex, but I can still do that.
The problem is this, however. Say, my rest service is at pictures.example.com, I want to be able to make the following calls:
pictures.example.com/Image/{ID} - Should return a specific image
pictures.example.com/Images - Should return a list of image IDs.
pictures.example.com/Images/{TAG} - Should return a list of image IDs with this tag.
pictures.example.com/Images/{TAG}/{TAG} - Should return a list of image IDs with these tags.
pictures.example.com/Images/{TAG}/{TAG}/{TAG} - Should return a list of image IDs with these tags.
pictures.example.com/Images/{TAG}/{TAG}/{TAG}/{TAG}/{TAG} - Should return a list of image IDs with these tags.
etcetera...
So, how do I set up a RESTful web service projects that will allow me to nest tags like this and still be able to read them all? Without any limitations for the number of tags, although the URL length would be a limit. I might want to have up to 30 tags in a selection and I don't want to set up 30 different routing thingies to get it to work. I want one routing thingie that could technically allow unlimited tags.
Yes, I know there could be other ways to send such a list back and forth. Better even, but I want to know if this is possible. And if it's easy to create. So the URL cannot be different from above examples.
Must be simple, I think. Just can't come up with a good solution...
The URL structure you choose should be based on whatever is easy to implement with your web framework. I would expect something like:
http://pictures.example.com/images?tags=tag1,tag2,tag3,tag4
Is going to be much easier to handle on the server, and I can see no advantage to the path segment approach that you are having trouble with.
I assume you can figure out how to actually write the SQL or filesystem query to filter by multiple tags. In CherryPy, for example, hooking that up to a URL is as simple as:
class Images:
#cherrypy.tools.json_out()
def index(self):
return [cherrypy.url("/images/" + x.id)
for x in mylib.images()]
index.exposed = True
#cherrypy.tools.json_out()
def default(self, *tags):
return [cherrypy.url("/images/" + x.id)
for x in mylib.images(*tags)]
default.exposed = True
...where the *tags argument is a tuple of all the /{TAG} path segments the client sends. Other web frameworks will have similar options.

Resources