DRuby with Selenium WebDriver - ruby

I'm using distributed ruby so that I can save the selenium web-driver object in one script and use the same object in the next script when I run the clients I get an error indicating that #<Drb::DRbConnError: connection closed>.
Has anyone tried this or how do we overcome this issue?
Below are my scripts
Server.rb
require 'drb/drb'
# The URI for the server to connect to
URI="druby://localhost:8787"
# Allows sharing of variables between component runs
class TestScope
# class variable
##variables = {}
def save_variable(key, value)
##variables.store(key, value)
end
def get_variable(key)
return ##variables[key]
end
def get_size
return ##variables.size
end
end
# The object that handles requests on the server
FRONT_OBJECT=TestScope.new
DRb.start_service(URI, FRONT_OBJECT, safe_level: 1, verbose: true)
# Wait for the drb server thread to finish before exiting.
DRb.thread.join
Client1.rb
require 'drb/drb'
require 'selenium-webdriver'
# The URI to connect to
SERVER_URI="druby://localhost:8787"
# Start a local DRbServer to handle callbacks.
# Not necessary for this small example, but will be required
# as soon as we pass a non-marshallable object as an argument
# to a dRuby call.
# Note: this must be called at least once per process to take any effect.
# This is particularly important if your application forks.
DRb.start_service
test_scope = DRbObject.new_with_uri(SERVER_URI)
driver = Selenium::WebDriver::Driver.for :firefox
driver.navigate.to "http://www.google.com"
puts "Saving the driver object to the test scope"
test_scope.save_variable('driver', driver)
Client2.rb
require 'drb/drb'
require 'selenium-webdriver'
# The URI to connect to
SERVER_URI="druby://localhost:8787"
# Start a local DRbServer to handle callbacks.
# Not necessary for this small example, but will be required
# as soon as we pass a non-marshallable object as an argument
# to a dRuby call.
# Note: this must be called at least once per process to take any effect.
# This is particularly important if your application forks.
DRb.start_service
test_scope = DRbObject.new_with_uri(SERVER_URI)
puts "Fetching the driver object from the test scope"
driver1 = test_scope.get_variable('driver')
driver1.navigate.to "http://www.yahoo.com"

In order to share an object using DRb, the class must be defined in the dRb server as it allows an object in one Ruby process to invoke methods on an object in another Ruby process on the same or a different machine.
If there is a scenario where one needs to create an object on the dRb client and use that object in other DRb clients. We need to use ruby script runner and define object in the scriptrunner.rb so that multiple clients can use it.

Related

Capybara/Selenium - force to reload Selenium Driver on each call

I have a solution to parse some data from a remote websites using Firefox(46) + Capybara + Selenium. I pass path_to_firefox_profile argument on initialize, which is some real Firefox profile folder.
The problem is that if I execute this code:
a = MyParser.new(profile_a)
a.parse_something
b = MyParser.new(profile_b)
b.parse_something
... the instance b will have profile A loaded despite I specified another profile.
But, if I run these 2 lines in a separate processes, I'll get what I want. So I assume one of them - either Capybara or Selenium - stores profiles settings once per ruby process, and won't change it on demand.
Are there are ideas how to change profile within the same process?
I tries to .quit Firefox, but it doesn't help, on visiting new URL Selenium opens another Firefox window with the exact same profile instead of new one.
class MyParser
def initialize(path_to_firefox_profile)
Capybara.register_driver(:selenium) do |app|
client = Selenium::WebDriver::Remote::Http::Default.new
client.timeout = 150
Capybara::Selenium::Driver.new(app,
profile: path_to_firefox_profile,
http_client: client)
end
end
def parse_something
# perform some parsings & return result
end
end
Capybara's register_driver registers the driver by the name you assign globally, and will use those drivers (by name) in its sessions. It will also automatically manage sessions in a manner designed for ease of use by people testing web-apps. The issue here is that a new session with the newly registered driver isn't being created. This is because you're not really using Capybara for what it was designed, and therefore need to pick up a bit more of the weight of session management. If you're ever going to have more than 2 of your MyParser objects around you probably should be creating a new session per MyParser instance you create and then using that session in all of the MyParser methods. Since you're using different driver settings per MyParser instance you should probably also be naming each driver registered differently.
class MyParser
def initialize(path_to_firefox_profile)
Capybara.register_driver(self.object_id.to_s) do |app|
client = Selenium::WebDriver::Remote::Http::Default.new
client.timeout = 150
Capybara::Selenium::Driver.new(app,
profile: path_to_firefox_profile,
http_client: client)
end
#session = Capybara::Session.new(self.object_id.to_s)
end
def parse_something
#session.visit("some url")
#session.find(...) # perform some parsings & return result
end
end
You will also need to handle cleaning up the session when you're done with each MyParser instance.

How can I run a method at the end of a file from a gem?

I have a gem (e.g. mygem) and as is normal, I add mygem to a file by putting require "mygem" at the top. What if I have a method in mygem called finish_jobs and I want it to run in the following location:
require "mygem"
# code, code code
finish_jobs
How would I do that without forcing the user to add the method every time they use the gem?
Specifically, what I am trying to do is write a server app (with rack) and I need the methods in the body of the file to be processed before the server is started.
This is certainly possible.
Why not just add the code directly into the Gem (since it sounds like it is under your control and is not an external dependency)?
module MyGem
def printSomething
p 2 + 2
end
module_function :printSomething
printSomething()
# => 4
end
If this isn't what you had in mind, let me know and I can update the solution.
Also, see Kernel#at_exit
A more explanatory guide on Kernel#at_exit
I don't know of a way to do what you're describing.
One workaround would be to provide an API which accepts a block. This approach allows you to run code after the user's setup without exposing implementation details to them.
A user could call your library method, providing a block to set up their server:
require "mygem"
MyGem.code_code_code {
# user's code goes here
}
Then, your library code would:
Accept the block
Call some library code
Here's an example implementation:
module MyGem
# Run some user-provided code by `yield`-ing the block
# Then run the gem's finalizer
def self.code_code_code
# Execute the block:
yield
# Finalize:
finish_jobs
end
end
This way, you can accept code from the user but still control setup and finalization.
I hope it helps!

rails persist objects over requests in development mode

I am trying to interact with Matlab.Application.Single win32ole objects in my rails application. The problem I am running into is that while I am developing my application, each separate request reloads my win32ole objects so I loose the connection to my matlab orignal instances and new instances are made. Is there a way to persist live objects between requests in rails? or is there a way to reconnect to my Matlab.Application.Single instances?
In production mode I use module variables to store my connection between requests, but in development mode Module variables are reloaded every request.
here is a snippet of my code
require 'win32ole'
module Calculator
#engine2 = nil
#engine3 = nil
def self.engine2
if #engine2.nil?
#engine2 = WIN32OLE.new("Matlab.Application.Single")
#engine2.execute("run('setup_path.m')")
end
#engine2
end
def self.engine3
if #engine3.nil?
#engine3 = WIN32OLE.new("Matlab.Application.Single")
#engine3.execute("run('setup_path.m')")
end
#engine3
end
def self.load_CT_image(file)
Calculator.engine2.execute("spm_image('Init','#{file}')")
end
def self.load_MR_image(file)
Calculator.engine3.execute("spm_image('Init','#{file}')")
end
end
I am then able to use my code in my controllers like this:
Calculator.load_CT_image('Post_Incident_CT.hdr')
Calculator.load_MR_image('Post_Incident_MRI.hdr')
You can keep an app-wide object in a constant that won't be reset for every request. Add this to a new file in config/initializers/:
ENGINE_2 = WIN32OLE.new("Matlab.Application.Single")
You might also need to include the .execute("run('setup_path.m')") line here as well (I'm not familiar with WIN32OLE). You can then assign that object to your instance variables in your Calculator module (just replace the WIN32OLE.new("Matlab.Application.Single") call with ENGINE_2, or simply refer to them directly.
I know this is beyond the scope of your question, but you have a lot of duplicated code here, and you might want to think about creating a class or module to manage your Matlab instances -- spinning up new ones as needed, and shutting down old ones that are no longer in use.

Is it possible to access Ruby EventMachine Channels from Thin/Rack/Sinatra?

I'm looking to build a simple, RESTful notification system for an internal project leveraging Sinatra. I've used EventMachine channels in the past to subscribe/publish to events, but in all my previous cases I was using EventMachine directly.
Does anyone know if it's possible to create, subscribe, and publish to EventMachine channels (running in Thin) from a Sinatra application, or even from some Rack middleware for that matter?
Have a look at async_sinatra.
Basically, to make it possible to use EventMachine when running in Thin you need to make it aware that you want to serve requests asynchronously. The Rack protocol is synchronous by design, and Thin expects a request to be done when the handler returns. There are ways to make Thin aware that you want to handle the request asynchronously (see think_async for an example how), and async_sinatra makes it very easy.
Bryan,
You can use the em-http-request library (https://github.com/igrigorik/em-http-request), this will allow you to reference a specific EventMachine application running on either A. the same server, B. a different server, or C. wherever you want really.
require 'eventmachine'
require 'em-http-request'
require 'sinatra/base'
require 'thin'
class ServerClass < EventMachine::Connection
def initialize(*args)
# ruby singleton - store channel data in global hash
($channels ||= [])
end
def post_init
puts "initialized"
$cb.call("initialized");
end
def receive_data(data)
# got information from client connection
end
def channel_send(msg,channel)
$channels[channel].send_data(msg)
end
def channels_send(msg)
$channels.each{|channel| channel.send_data(msg)}
end
def unbind
# puts user left
end
end
EventMachine.run do
$cb = EM.callback {|msg| puts msg #do something creative}
$ems = EventMachine::start_server('0.0.0.0',ServerClass,args)
class App < Sinatra::Base
set :public, File.dirname(__FILE__) + '/public'
get '/' do
erb :index
end
end
App.run!({:port => 3000})
end
Above is a basic wireframe. Depending on how you want to go about sending data, you can use WebSockets (em-websocket) and bind each user on login (have to add a login system), or you can use this for whatever. As long as you have a global reference to the Eventmachine Object (connection, websocket, channel) you can pass messages from within your application.
BTW - It is optional to add the EventMachine.run do;....end loop, since Thin will do this anyways. It helps to know how it works though.
Good Luck

How can I run Selenium (used through Capybara) at a lower speed?

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!

Resources