Check if an element with varying text casing is displayed in Appium and perform logic accordingly - ruby

I'm using Appium with the Cucumber Framework and utilizing the Ruby language.
My scenario is my app has multiple buttons that I want to call with a single Gherkin statement "I press the "*" button" where * is modular. I use it for Save, Back, Cancel, Expand, Yes, No, etc buttons with simple text. Here's an example:
And I press the "Create a Note" FAB button
And I am on the Create a Note Screen
Then I tap Back
>> And I press the "Yes" button
Now currently with my app on android there are multiple versions of the "Back" button and they're not all formatted the same. Some have texts that reads "BACK", some are "Back", and some are "back". I designed a series of if statements to handle this discrepancy until I can get the developers to standardize the text they use for these buttons:
Then(/^I press the "([^"]*)" button$/) do |button_text|
sleep 1
#button_text = button_text
binding.pry
if find_element(xpath: "//android.widget.Button[#text='#{#button_text}']").displayed? == true
find_element(xpath: "//android.widget.Button[#text='#{#button_text}']").click()
else
#button_text.capitalize()
if find_element(xpath: "//android.widget.Button[#text='#{#button_text}']").displayed? == true
find_element(xpath: "//android.widget.Button[#text='#{#button_text}']").click()
else
#button_text.upcase()
if find_element(xpath: "//android.widget.Button[#text='#{#button_text}']").displayed? == true
find_element(xpath: "//android.widget.Button[#text='#{#button_text}']").click()
else
#button_text.downcase()
if find_element(xpath: "//android.widget.Button[#text='#{#button_text}']").displayed? == true
find_element(xpath: "//android.widget.Button[#text='#{#button_text}']").click()
else
fail("Could not find a permutation of that button")
end
end
end
end
sleep 2
end
My thinking is that I'll save the button_text argument to a variable, #button_text, and then use the .displayed? method to check if that version of the button is available. If it is I pass the .click() method, and if it isn't I alter the text of the variable via the .capitalize, .upcase, and .downcase methods. I'm sure that using nested if statements isn't the most ideal way to do this so I would love some assistance on a better way to write that statement.
The issues I receive is I get "An element could not be located using the given search parameters" error whenever I get to this line of code. It seems like the .displayed? method doesn't return a value and triggers appium to stop the test.
I want appium to check if that version of the button is present, then adjust the variable and check the new version for upcase, downcase, and capitalize. Any and all assistance would be greatly appreciated.

When you're finding the elements, try setting their text to all uppercase, and then checking for one version of the word that you're searching for.
I.e. BacK or back or BACK would all become "BACK"

Related

How can I iterate through an array of links and check information on the page to which each refers?

I have a page that lists job openings at my company. I want to use Capybara tests to click on each of the links and determine if the target page contains certain content.
I have the step below. The next step in the process checks for the content on the new page.
When("I click on each job title") do
page.all('.job-box').each do |item|
within(item) do
find('a').click
end
end
end
Once the first link is clicked the browser goes to a different page. At that point, I would like to check the content on that page, then return to the previous step to check the next link. What actually happens is that the code clicks the first link, and then attempts to click the second link. Since the first link sent it to another page, the second link is no longer present, so it returns a stale element error.
Obviously, I need a way to deal with multiple pages before continuing with the next step in the iteration, but I haven't found any documentation that addresses that problem.
You probably want to use page.go_back (or page.driver.go_back depending on the driver you're using). Something like this:
When("I click on each job title") do
page.all('.job-box').each do |item|
within(item) do
find('a').click
# do stuff
end
# roll back (granting you went but one click away)
page.go_back
end
end
As you've recognized the issue here is that the elements are stale when you return to the previous page. Depending on what limitations you're willing to accept there are multiple ways to deal with this. The bigger issue you have is that you're doing this in Cucumber which really doesn't have support for scoping/looping in its test structure. If you're willing for this to only work on a specific browser/platform you can use the respective key modifiers on click to have the browser open a new tab for each job entry and then your next step could run through all the open tabs checking those
When("I click on each job title") do
page.all('.job-box').each do |item|
item.find('a').click(:alt, :cmd) # alt,cmd click in Chrome MacOS opens in new tab
end
end
But really you're going to be better off doing something like
When("I click on each job title and verify info") do
page.all('.job-box').each do |item|
window = page.window_opened_by do
item.find('a').click(:alt, :cmd)
end
page.within_window(window) do
# verify whatever needs to be verified
end
window.close
end
end
Thomas Walpole's answer got me close enough to figure it out. Here is what ended up working:
When("I click on each job title and verify info") do
page.all('.job-box').each do |item|
new_window = window_opened_by do
url = item.find('a')[:href]
within_window open_new_window do
visit url
end
end
page.within_window(new_window) do
# do stuff
end
end
end
It doesn't close each new tab after it checks it, which could be a problem if you are working with a lot of links. I only have about 15, so it wasn't an issue for me. They all closed when the browser closed after the test finished.

Capybara wait until button is enabled?

Surprised I actually haven't come across this, but I have a simple button that is disabled until a dropdown is selected. Sometimes the page isn't fast enough to "enable" the button to be clicked on after the previous dropdown is selected causing it to fail.
I could throw in a "sleep" of a second or two and fix this, but that seems like a lazy/poor way to do this.
Is there a way in capybara (or purely selenium) that I can make it wait until the button is actually enabled? I'd like to throw this is the page model method for this button (as im trying to avoid API specific methods/selenium/etc... in the actual test specs (Although I can if I need to).
FWIW this is specifically for Ruby's capybara framework but pure selenium is fine as well.
Assuming the button you're referring to is actually a button (<button> element, or <input> element with type submit, reset, image, or button) then Capybaras :button selector will (by default) wait for it to be non-disabled.
click_button('Something')
or
find_button('button_id').click
or
find(:button, 'button_value').click
If any of the finder or action methods aren't waiting long enough for a specific element you can always increase the maximum wait time for a specific finder/action by passing a :wait option
find(:button, 'Something', wait: 10).click
If you're not using selector types (if not, why not) and instead are just using raw CSS to locate the element then you can use the :enabled pseudo class along with your existing CSS and something like
find('#my_button:enabled', wait: 10).click
If the element you're calling a button isn't actually a button but some other type of element (<a> etc) styled to look like a button, then you'll need to explain exactly how you're disabling the "button".
In Python you can do something like this:
def wait_until_clickable(driver, xpath, timeout = 1):
while timeout > 0:
try:
element = driver.find_element_by_xpath(xpath)
element.click()
return element
except:
time.sleep(0.1)
timeout = timeout - 0.1
return False

How to handle windows pop up in IE using Ruby

Please help me how to handle this pop ups.
Based on your last question, I assume you are using Watir-Classic (even though you have also listed Watir-Webdriver).
As #orde mentioned in the comments, Watir has an Alert class for handling these types of dialogs. Unfortunately, in terms of clicking buttons, Watir-Classic only has an #ok method defined:
# Press the "OK" button on the JavaScript dialog.
def ok
dialog.button(:value => "OK").click
wait_until_not_exists
end
This will not work for this dialog as there is a "Yes" and "No" button rather than an "OK" button. You will need to duplicate this functionality with the right value.
Note that the dialog is a RAutomation window and no longer Watir specific code. As a result, the button values are not always intuitive - it is not always just the text you see. To get the right values, you should ask the dialog what values it sees:
browser.alert.send(:dialog).buttons.map(&:value)
#=> ["&Yes", "&No"]
We can then make the same calls as the #ok method, but with the correct value:
alert = browser.alert
alert.send(:dialog).button(:value => '&Yes').click
alert.wait_while_present
This code is working fine to handle this type of pop ups:
save_dialog = WIN32OLE.new("AutoItX3.Control") save_dialog.ControlClick("Windows Internet Explorer", "Yes", "[CLASS:Button;INSTANCE:1]")

how to reload page until a button appears using capybara and ruby

I want to click a button after an action and button does not appear until and unless I reload the page. And some times it takes some time to appear the button and I have to reload page for more than once. I don't want to put static delays. So is there a way to achieve following using capybara and ruby:
do
page.evaluate_script("window.location.reload()")
until a button appears
While Mesut's code should work fine, I would re-write it as:
Timeout.timeout(Capybara.default_max_wait_time) do
loop do
page.evaluate_script("window.location.reload()")
break if page.has_selector?(...)
end
end
This will make sure to fail if it will have to wait more than timeout defined in Capybara settings. It can be useful when for example specs are running on the CI server.
Be aware that it can still lead to unexpected behaviors in some drivers, because it can interrupt while some scripts are evaluating.
reoload until page.has_selector? returns true, check this:
while true
page.evaluate_script 'window.location.reload()'
if page.has_selector?("css_selector")
break
end
end

Removing events in Shoes

The is any way to remove the events in Shoes? I searched around many websites and references but I can't found any way to remove a event... I minding that I will need to create my own event manager... its really nescessary? or there is a way to desattach event listeners?
I have found that Shoes only allows a single listener method on a given event, so you can remove a previous listener by calling the event and not passing a block to it.
For example, this Shoes app will clear the click event after it is clicked one time. If you remove the call to click inside the block, then it will fire repeatedly.
Shoes.app
#p = para "Empty"
#click_count = 0
click do |b,x,y|
#click_count += 1
#p.replace "Clicked #{#click_count} time(s)."
click # remove the click handler
end
end
Which keybindings do you want to remove? All events on the Shoes app or just the default bindings?
If you want to override bindings reserved for shoes like "alt-/", "alt-.", "alt-?", paste the following code into the file which contains your application code
class Shoes::App
def Shoes.show_log # gets called on alt-/
end
def Shoes.show_manual # gets called on alt-?
end
def Shoes.show_selector # gets called on alt-.
end
end
The above code monkey-patches the shoes code and in-turn does nothing when the corresponding keys are pressed.
You can use the same technique for rest of the default bindings. grep the shoes source for the key bindings, find the corresponding method and define an empty method within your app to override the built-in method.
Have you tried method.unbind(obj)?
Can you clarify what you mean by "remove a event"? Are you wanting to ignore a specific event or are you wanting to "turn off" an event listener?
If the former, I'd suggest writing a listener that just ignore the event.
If the later, why not make the body of the listener conditional on some externally accessible value, giving yourself an on/off switch.
If you are wanting something else, edit the question to clarify and I'll stop back later and edit my answer.
In response to your comment, I'd re-suggest the second of the above alternatives. If you wanted to get really fancy you could write something like this:
$keypress_listeners = {}
keypress do |key|
$keypress_listeners.values.each { |l| l.call(key)
end
$keypress_listeners[:hero_controller] = lambda { |key| ... }
:
:
$keypress_listeners.delete[:hero_controller]
and likewise for any other events, but that is probably overkill. On the other hand, it would give you total control of the event processing.

Resources