How do I extract the Selenium Element from a Capybara Element? - ruby

I am more familiar with Java and Selenium than Ruby and Capybara and SitePrism, so apologies if this question is rather thick.
Selenium has a very useful class for managing Select tags, Selenium::WebDriver::Support::Select, which can be created by passing it the Selenium Element (Selenium::WebDriver::Element) representing the select. I would like to get a Select object so I can use its convenient methods.
Using SitePrism and Capybara, however, the standard method of defining elements gives me access to a select modeled by Capybara's Element class, Capybara::Node::Element, and I can't seem to find an easy way to extract the underlying Selenium Element from the Capybara Element.
I've searched around for alternatives and found Capybara's #select method, but that seems very limiting to me, since it looks like it forces you to select by value and has very narrow parameters for defining the select on the page.
Is there an easy way to create a Selenium Select from SitePrism/Capybara? Or is there a better way of doing this entirely? Thanks!

Ah, I found it. It was right in Capybara::Node::Element all along. The #native method returns the native element from the driver. This can then be passed into the Selenium Select's initialize method to successfully create the Select.

Related

Where exactly the data-cy or data-test-id attribute to the element to test can be added?

I am a beginner for the Cypress automation testing. I have gone through the good practice document for Cypress E2E where it suggests to use data-cy or data-test-id for element selection. This might be a simple and dumb question but I am really curious to know the answer from you guys. I tried googling but no luck.
Where exactly the data-cy or data-test-id attribute to the element to test can be added?
a. Do I have to checkout the code from the developers team, then add the attribute in there?
b. Can I use the invoke command in cypress (just like we can add/remove attr using invoke in jquery)?
c. Any other method?
If the option a. is your answer, then
what are the steps you follow?
Does the app code and testing code need to be in one project?
What hierarchy of element selection you follow?
(More info: I have frontend in Typescript and React, backend in sql, test project in cypress)
If data-cy or data-test-id attributes are not added to the elements in your project, you can not access the elements using these attributes.
Basically, it is a decision that team should take while developing front end application that all testable elements should have proper data-cy or data-test-id which gives more information about the element and it makes easy to write more stable and readable tests.
So I would suggest to discuss within a team to define strategy to add these attributes to the existing code and new code.
Read more about it at best practices for selecting elements in cypress

Where is the list of the cheezy page-object dynamic element finders?

As a reference I am referring to this post.
It basically says instead of saying text_field(:tf_elem, id:'id1'). We can do text_field_element(id: 'id1'). I definitely prefer the latter. It also works for multiple elements so text_field_elements. I can't seem to figure out how to do it for radio buttons. I'm trying radio_elements but not working. Undefined method. Frustratingly enough I can't find this information documented anywhere else. How do I get it to work for radio buttons and is there a "cheat sheat" for future reference for all the elements using the text_field_element/text_field_elements style?
The dynamic locators are included:
For the PageObject by ElementLocators
For the Elements by NestedElements
For the bulk of the methods, they use the shared LocatorGenerator. In there, the list of BASIC_ELEMENTS and ADVANCED_ELEMENTS suffixed by "_element" and "elements" are the dynamic locator methods.
As an example, for radio elements, there is a "radio_button" in the ADVANCED_ELEMENTS array. The dynamic methods would then be:
radio_button_element for the first match and
radio_button_elements for all matches.

Is there any benefit of defining radio buttons in a Page Object model (eg. SitePrism) rather than using Capybara directly?

I was using a cucumber/ruby/capybara/siteprism framework and implementing the test pages at present. I've reached a point where there are a lot of radio buttons (over 20) per page in several pages, and I was thinking if there's really any benefit in trying to map all those as static elements in my page object model?
Ie, thinking about it, it seems much more convenient to just use the text of the radio button in the step definition and call the capybara 'choose' method directly, something like the following, so that I don't need to do anything else for those 20+ radio buttons, it should all just work by changing the parameter we're passing in the feature:
cucumber feature:
When I select that "I am over 18"
capybara step:
When /^I select that "(.*)"$/ |option|
choose(option)
Whereas with a page object model like siteprism, I guess the implementation would need to define and maintain all those elements independently in a format similar to:
element :over_18_button, :radio_button, "I am over 18"
element :over_12_button, :radio_button, "I am over 12"
etc x50times
And for using it, one should create the page, call the element, which doesn't seem as straight forward to me?
siteprism step:
When /^I select that "(.*)"$/ |option|
case option
when 'I am over 18'
over_18_button.click
when 'I am over 12'
over_12_button.click
I guess one could create 'elements' or a 'section' with an array to all the buttons, but then, we'll have to put extra logic to parse them and click on the relevant one anyway somewhere in the code, whilst it would be all done neatly and without the need for any extra code or maintenance with the 'choose' method from capybara.
Am I right to assume that in this example using Capybara is a better option?
or if it'd be better to define 'ALL' web elements in the page object model, what would the benefit of that be? and could the page object code be done in a different way to take advantage of any possible benefit?
That complicated case statement is unnecessary.
When /^I select that I am over "(\d\d)"$/ |age|
#page_object.select_age(age)
I'm not familiar with site_prism. My watir_drops gem would let you define everything with the same pattern like this:
element(:age_button) { |age| browser.radio_button(text: "I am over #{age}")
with this method in the page object:
def select_age(age)
age_button(age).set
end
We could also get into a whole long discussion on using declarative instead of imperative steps. Also, best practice Page Object usage avoids directly calling defined elements. Call methods that accomplish the business logic and those methods do all the implementation including element definitions and actions on them.
Old question but adding important missing info
The way in which site_prism works allows you to define your selector using anything that can be queried in Capybara. So if you want to define your radio using the text, you can do that. Or any other locating strategy you want to use.
Obviously traditionally I would advise using css locators (
element :my_radio, text: 'foo'), because they're easiest to debug and re-use. Furthermore the OP advised he had 50+ of these. If they were identical, they could be abstracted out into a Helper Module or he could even meta-program them in using a loop and index (If they followed a simple index_naming_pattern)

Is there a way to combine page object gem and javascript calls

Im using the page object gem and selenium,
when filling in a sign up form, the form fills in correctly, but when clicking apply it errors saying the fields are required even though they are filled in.
this seems to be caused because the page object/selenium method isn't firing the javascript change method which is needed for the application to know the field has been filled in
this can be fixed by using code such as
on(SettingsPage).payment_method_account_number = number
#browser.execute_script("$('input[name=account_number]').change()")
but this is obviously not ideal and breaks the whole point of using page object in the first place by having to declare the fields name attribute again
is there a way better way to solve this problem than what i have shown?
To avoid duplicating an element definition within the page object as well as the execute_script script, you can pass the page object element to the script.
The underlying Selenium-WebDriver (and therefore the Page-Object gem) supports an arguments array within the script being executed. This arguments array basically takes a Selenium-WebDriver elements and converts them to something usable by the script. The Page-Object execute_script method handles the conversion of elements to the right type, so you simply need to:
Declare a script that uses the arguments array
Pass in a PageObject::Element
For example, let us assume your page object has used the accessor:
text_field(:payment_method_account_number, :name => 'account_number')
Therefore, the page object will have a payment_method_account_number_element method that returns the PageObject::Element for this text field.
Within the script you want to execute, you can replace how you locate the element with the arguments array and pass in the PageObject::Element to the execute_script method:
execute_script("$(arguments[0]).change();", payment_method_account_number_element)
Then you can re-write the call to on as:
on(SettingsPage) do |page|
page.payment_method_account_number = number
page.execute_script("$(arguments[0]).change();", page.payment_method_account_number_element)
end
(Or, as Dane pointed out, put this into a method in the page object.)
I have had a similar problem but the event was "onblur" instead of "onchange". I would imagine the on change would fire, but if it doesn't you could use an approach similar to mine. I ended up creating a widget that redefined the "#{name}=" method to also call the event on the spot. It's a little bit more complicated, but it centralizes all the magic to one class and keeps the code brief.

Ruby way of writing this spec in "Rspec"

I was wondering if there is any Ruby way of writing the following views spec(without using Capybara/Cucumber/Webrat helpers. Should be just in rspec or rspec-rails):
expect(rendered).to include("<input class='toggle_m' name='result_configuration[information]' type='checkbox' value='1'>")
expect(rendered).to include("<textarea class=details' disabled='disabled' name=result_configuration[info][]'></textarea>")
Thing is, I need to see if the the checkbox is checked(means the value is "1", value is set to "0" when it is unchecked) then textarea should be disabled. Any idea?
Or How would you write this expectation in a more readable way? Suggestions are most welcome.
Thanks.
You could try a regex, but I think your method is good enough.
expect(rendered).should =~ /<input[^>]*name='result_configuration[information]'[^>]*value='1'[^>]*>/
expect(rendered).should =~ /<textarea[^>]*disabled='disabled'[^>]*name=result_configuration[info][][^>]*>
Limitations of this method are that if there are any checked checkboxes and any disabled textareas it will pass, to do anything more I would definitely require capybara or something to actually parse the html (regexes are not parsers)
EDIT: Added the name= part into both regexes as a response to the comment. Only advantage of this method is that it won't break if you change the class of the elements. Unfortunately I don't know any better solution other than external gems.
Writing such tests for the sake of testing and coverage will only make it difficult for someone inheriting the codebase. I have written such tests only to remove them later as changes to UI are more frequent and having such tests slows down developer. if i had to still write them in OO way i would design it on the lines of pageobjects (that's a gem) - wrapper class over dom and few generic helper functions. Also adding a new gem in test group using Gemfile/bundler would not affect production servers.

Resources