I'm building an automation suite using Ruby/Selenium-WebDriver/Cucumber. What I want to achieve is to resume the cucumber scenario in case of any unexpected errors.
For e.g. Unexpected pop-ups
I might get a modal dialog at any point in the application. I want my code to close the pop-ups whenever the exception occurs and resume the execution.
The point of doing this is, the automation suite will run for multiple days on multiple systems. There won't be any kind of monitoring except logs and output reports. I don't want these unwanted exception to hamper the execution.
Given that the alerts can be opened at any time, the best option may be to use an AbstractEventListener. This allows you to perform actions before (or after) Selenium interacts with the browser. This means that you could call your alert closing code right before each interaction.
The sample event listener would be defined like:
class AlertListener < Selenium::WebDriver::Support::AbstractEventListener
def close_alerts(driver)
# Assuming you want to handle the dialogs using Watir code instead of Selenium,
# convert the Selenium::WebDriver to a Watir::Browser
browser = Watir::Browser.new(driver)
# Run whatever code you have for handling the dialog instances
browser.alert.ok if browser.alert.exists?
end
def before_change_value_of(element, driver)
close_alerts(driver)
end
def before_click(element, driver)
close_alerts(driver)
end
def before_close(driver)
close_alerts(driver)
end
def before_execute_script(script, driver)
close_alerts(driver)
end
def before_find(by, what, driver)
close_alerts(driver)
end
def before_navigate_back(driver)
close_alerts(driver)
end
def before_navigate_forward(driver)
close_alerts(driver)
end
def before_navigate_to(url, driver)
close_alerts(driver)
end
def before_quit(driver)
close_alerts(driver)
end
end
Note that you would replace the close_alerts method with whatever code you have already written for handling the alerts. The event listener is Selenium, which means you need to either write Selenium code or convert the element/driver to Watir (which is what is done in the example).
Once you have the listener created, you need to pass it to the browser during initialization:
listener = AlertListener.new
browser = Watir::Browser.new :chrome, :listener => listener
You could get your goal by accepting any alert that pops up on the screen.
You can do this in following steps:
Given(/^I should see the error message and accept it$/) do
def alert_accept
end
end
So whenever there is a popup it will accept it and continue forward.
You can find this step as well here:
https://github.com/ModusCreateOrg/cucumber-watir/blob/appium/features/step_definitions/appium_steps.rb
Related
I'm trying to get some rspec tests run using a mix of Capybara, Selenium, Capybara/webkit, and Poltergeist. I need it to run headless in certain cases and would rather not use xvfb to get webkit working. I am okay using selenium or poltergeist as the driver for phantomjs. The problem I am having is that my tests run fine with selenium and firefox or chrome but when I try phantomjs the elements always show as not found. After looking into it for a while and using page.save_screenshot in capybara I found out that the phantomjs browser wasn't loaded up when the driver told it to find elements so it wasn't returning anything. I was able to hack a fix to this in by editing the poltergeist source in <gem_path>/capybara/poltergeist/driver.rb as follows
def visit(url)
if #started
sleep_time = 0
else
sleep_time = 2
end
#started = true
browser.visit(url)
sleep sleep_time
end
This is obviously not an ideal solution for the problem and it doesn't work with selenium as the driver for phantomjs. Is there anyway I can tell the driver to wait for phantom to be ready?
UPDATE:
I was able to get it to run by changing where I included the Capybara::DSL. I added it to the RSpec.configure block as shown below.
RSpec.configure do |config|
config.include Capybara::DSL
I then passed the page object to all classes I created for interacting with the webpage ui.
An example class would now look like this
module LoginUI
require_relative 'webpage'
class LoginPage < WebPages::Pages
def initialize(page, values = {})
super(page)
end
def visit
browser.visit(login_url)
end
def login(username, password)
set_username(username)
set_password(password)
sign_in_button
end
def set_username(username)
edit = browser.find_element(#selectors[:login_edit])
edit.send_keys(username)
end
def set_password(password)
edit = browser.find_element(#selectors[:password_edit])
edit.send_keys(password)
end
def sign_in_button
browser.find_element(#selectors[:sign_in_button]).click
end
end
end
Webpage module looks like this
module WebPages
require_relative 'browser'
class Pages
def initialize(page)
#page = page
#browser = Browser::Browser.new
end
def browser
#browser
end
def sign_out
browser.visit(sign_out_url)
end
end
end
The Browser module looks like this
module Browser
class Browser
include Capybara::DSL
def refresh_page
page.evaluate_script("window.location.reload()")
end
def submit(locator)
find_element(locator).click
end
def find_element(hash)
page.find(hash.keys.first, hash.values.first)
end
def find_elements(hash)
page.find(hash.keys.first, hash.values.first, match: :first)
page.all(hash.keys.first, hash.values.first)
end
def current_url
return page.current_url
end
end
end
While this works I don't want to have to include the Capybara::DSL inside RSpec or have to include the page object in the classes. These classes have had some things removed for the example but show the general structure. Ideally I would like to have the Browser module include the Capybara::DSL and be able to handle all of the interaction with Capybara.
Your update completely changes the question so I'm adding a second answer. There is no need to include the Capybara::DSL in your RSpec configure if you don't call any Capybara methods from outside your Browser class, just as there is no need to pass 'page' to all your Pages classes if you limit all Capybara interaction to your Browser class. One thing to note is that the page method provided by Capybara::DSL is just an alias for Capybara.current_session so technically you could just always call that.
You don't show in your code how you're handling any assertions/expectations on the page content - so depending on how you're doing that you may need to include Capybara::RSpecMatchers in your RSpec config and/or your WebPages::Pages class.
Your example code has a couple of issues that immediately pop out, firstly your Browser#find_elements (assuming I'm reading your intention for having find first correctly) should probably just be
def find_elements(hash)
page.all(hash.keys.first, hash.values.first, minimum: 1)
end
Secondly, your LoginPage#login method should have an assertion/expectation on a visual change that indicates login succeeded as its final line (verify some message is displayed/logged in menu exists/ etc), to ensure the browser has received the auth cookies, etc before the tests move on. What that line looks like depends on exactly how you're architecting your expectations.
If this doesn't answer your question, please provide a concrete example of what exactly isn't working for you since none of the code you're showing indicates any need for Capybara::DSL to be included in either of the places you say you don't want it.
Capybara doesn't depend on visit having completed, instead the finders and matchers will retry up to a specified period of time until they succeed. You can increase this amount of time by increasing the value of Capybara.default_max_wait_time. The only methods that don't wait by default are first and all, but can be made to wait/retry by specifying any of the count options
first('.some_class', minimum: 1) # will wait up to Capybara.default_max_wait_time seconds for the element to exist on the page.
although you should always prefer find over first/all whenever possible
If increasing the maximum wait time doesn't solve your issue, add an example of a test that fails to your question.
I'm looking to create an isolate! method for our tests to isolate global state changes in tests. We're using Minitest as our test suite. My current approach is to start up DRb with the current minitest reporter, fork, and then pass the results back to the main process via DRb.
This is what I have right now:
module ForkingRunner
require 'drb'
def run(reporter, options = {})
with_drb_reporter(reporter) do |_reporter|
super _reporter, options
puts _reporter.reporters.first.count
end
end
private
def drb_path
"drbunix:#{Rails.root.join('tmp/minitest-fork')}"
end
def with_drb_reporter(reporter)
DRb.start_service drb_path, reporter
pid = fork do
yield DRbObject.new_with_uri(drb_path)
end
Process.wait pid
puts reporter.reporters.first.count
ensure
DRb.stop_service
end
end
ActiveSupport::TestCase.class_eval do
def self.isolate!
singleton_class.prepend ForkingRunner
end
end
Note that the reporter's test count is valid after I super in run in my forked process, but the reporter's test count is still 0 when queried from my main process after the fork returns in with_drb_reporter.
Any ideas why this doesn't work? I'm fairly new to DRb, so maybe it's some quirk with that. Does it have to do with copy-on-write screwing up my DRb connection?
I'm following along in the RSpec Book and I can't get the cucumber test to pass a certain spot. I've even tried running cucumber on the source code from the book and it still won't pass. I'm not sure if it's the fact that I'm using a newer version of cucumber but there must be a way to make it pass!
When I get to the point where the program should start a new game I'm told there's an undefined method 'puts'. Why is it undefined and how else should I capture what the program puts out in Cucumber? Running the test in RSpec to make sure the game puts the message works just fine, not sure how to make it pass in cucumber.
(Cucumber version 1.3.17, Ruby version 2.1.2)
Basically, I have these features:
Feature: code-breaker starts game
As a code-breaker
I want to start a game
So that I can break the code
Scenario: start game
Given I am not yet playing
When I start a new game
Then I should see "Welcome to Codebreaker!"
And I should see "Enter guess:"
and these step definitions:
class Output
def messages
#messages ||= []
end
def puts(message)
messages << message
end
end
def output
#output ||= Output.new
end
Given /^I am not yet playing$/ do
end
When /^I start a new game$/ do
game = Codebreaker::Game.new(output)
game.start
end
Then /^I should see "([^"]*)"$/ do |message|
output.messages.should include(message)
end
and this ruby code:
module Codebreaker
class Game
def initialize(output)
#output = output
end
def start
#output.puts 'Welcome to Codebreaker!'
end
end
end
and I keep getting this error:
When I start a new game # features/step_definitions/codebreaker_steps.rb:16
private method `puts' called for #<RSpec::Matchers::BuiltIn::Output:0x007fa4ea200ad0> (NoMethodError)
./lib/codebreaker/game.rb:10:in `start'
./features/step_definitions/codebreaker_steps.rb:18:in `/^I start a new game$/'
features/codebreaker_starts_game.feature:7:in `When I start a new game'
How do I fix this so that it passes in cucumber?
There are a lot of things that have changed since this RSpec Book was published but I got it working by replacing your code_break_steps.rb file with this:
Given /^I am not yet playing$/ do
end
When /^I start a new game$/ do
#messenger = StringIO.new
game = Codebreaker::Game.new(#messenger)
game.start
end
Then /^I should see "([^"]*)"$/ do |message|
expect(#messenger.string.split("\n")).to include(message)
end
RSpec 3 has a class that you're trying to overwrite called Output and it was causing way too much confusion. I just simplified this so you're tests are still talking to each other along with stdout.
You can use the same should syntax on the last line but it will be deprecated soon so better to get used to the expect syntax I used. For now if that's more confusing you can still use:
#messenger.string.split("\n").should include(message)
I'm following this book right now and tried Anthony's technique. But I got into trouble again a little later in the book (most probably because I'm kind of a beginner in Rails and couldn't translate Anthony's technique to another case ^^). But anyway, I found an easier way not to interfere with RSpec Output class and still not divert too much form the lesson flow:
rename the project Output class to something else, say OOutput
and change all output variables to o_output.
Et voilĂ ;-)
I was able to make that particular example pass in only two adjustments on the same "codebreaker_steps.rb" file, so you can still follow the book's curriculum. You don't have to touch the class name, just the output() method following it.
RSpec 3 contains an output() method that causes conflicts when you try to define another output() method. So in knowing that, take the book's instructions:
def output
#output ||= Output.new
end
and rename it to:
def see_output #new
#output ||= Output.new
end
(or name it anything that makes sense for the project)
Then go down to your When() method definition:
When /^I start a new game$/ do
game = Codebreaker::Game.new(output)
game.start
end
and update the second line to:
When ("I start a new game") do
game = Codebreaker::Game.new(see_output) #new
game.start
end
I'm using watir to automate the deleting of elements using a while loop, but when the scripts reaches the end it errors out since the element that the while loop is dependent on is no longer visible. Is there anyway to have Watir carry on with the test?
Here is an example of my code:
class Delete_element
def org_roster_remove
parameters = Tt_parameters.new
driver = Login.new.log_in(parameters.username3, parameters.password3)
while driver.a(:data_class, 'home.group.edit_group_btn').visible? == true
driver.p(:data_class, 'home.conv.messagePreview').hover
sleep(1)
driver.div(:class, 'delete').click
sleep(1)
driver.a(:data_class, 'home.conv.deleteFromRoster_btn').click
sleep(1)
end
driver.quit
end
end
This is the error:
Watir::Exception::UnknownObjectException: unable to locate element, using {:data_class=>"home.group.edit_group_btn", :tag_name=>"a"}
I would use .present? instead of .visible?
Also consider using .wait_until_visible instead of sleeping. See Watir-webdriver Waiting.
Additionally, investigate a page object framework such as Cheezy's page-object gem
By default Selenium runs as fast as possible through the scenarios I defined using Cucumber.
I would like to set it to run at a lower speed, so I am able to capture a video of the process.
I figured out that an instance of Selenium::Client::Driver has a set_speed method. Which corresponds with the Java API.
How can I obtain an instance of the Selenium::Client::Driver class? I can get as far as page.driver, but that returns an instance of Capybara::Driver::Selenium.
Thanks to http://groups.google.com/group/ruby-capybara/msg/6079b122979ffad2 for a hint.
Just a note that this uses Ruby's sleep, so it's somewhat imprecise - but should do the job for you. Also, execute is called for everything so that's why it's sub-second waiting. The intermediate steps - wait until ready, check field, focus, enter text - each pause.
Create a "throttle.rb" in your features/support directory (if using Cucumber) and fill it with:
require 'selenium-webdriver'
module ::Selenium::WebDriver::Firefox
class Bridge
attr_accessor :speed
def execute(*args)
result = raw_execute(*args)['value']
case speed
when :slow
sleep 0.3
when :medium
sleep 0.1
end
result
end
end
end
def set_speed(speed)
begin
page.driver.browser.send(:bridge).speed=speed
rescue
end
end
Then, in a step definition, call:
set_speed(:slow)
or:
set_speed(:medium)
To reset, call:
set_speed(:fast)
This will work, and is less brittle (for some small value of "less")
require 'selenium-webdriver'
module ::Selenium::WebDriver::Remote
class Bridge
alias_method :old_execute, :execute
def execute(*args)
sleep(0.1)
old_execute(*args)
end
end
end
As an update, the execute method in that class is no longer available. It is now here only:
module ::Selenium::WebDriver::Remote
I needed to throttle some tests in IE and this worked.
The methods mentioned in this thread no longer work with Selenium Webdriver v3.
You'll instead need to add a sleep to the execution command.
module Selenium::WebDriver::Remote
class Bridge
def execute(command, opts = {}, command_hash = nil)
verb, path = commands(command) || raise(ArgumentError, "unknown command: #{command.inspect}")
path = path.dup
path[':session_id'] = session_id if path.include?(':session_id')
begin
opts.each { |key, value| path[key.inspect] = escaper.escape(value.to_s) }
rescue IndexError
raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
end
Selenium::WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
res = http.call(verb, path, command_hash)
sleep(0.1) # <--- Add your sleep here.
res
end
end
end
Note this is a very brittle way to slow down the tests since you're monkey patching a private API.
I wanted to slow down the page load speeds in my Capybara test suite to see if I could trigger some intermittently failing tests. I achieved this by creating an nginx reverse proxy container and sitting it between my test container and the phantomjs container I was using as a headless browser. The speed was limited by using the limit_rate directive. It didn't help me to achieve my goal in the end, but it did work and it may be a useful strategy for others to use!