I have this program I wrote to automate a task I have at work. This is my first attempt at programming, with zero experience or training so forgive any silly mistakes. (I removed the website and my username/password)
require "rubygems"
require "selenium-webdriver"
require "nokogiri"
browser = Selenium::WebDriver.for :firefox
wait = Selenium::WebDriver::Wait.new(:timeout => 15)
#loads to unassigned results page
browser.get "-----------------"
p browser.title
browser.find_element(name: "user[username]").send_keys "--------"
browser.find_element(name: "user[password]").send_keys "--------"
browser.find_element(name: "commit").click
p browser.title
browser.find_element(class_name: "status_notification").click
begin
browser.find_element(:xpath => ".//*[#id='sub_nav_content']/table/tbody/tr[2]/td[3]/a").click
table = wait.until {
element = browser.find_element(id: "possible_matched")
element if element.displayed?
}
if table
puts "Table Found"
else
puts "Table Error"
end
#creates an 2D array containing patient name, admit date and prints to screen
names = browser.find_elements(:xpath => ".//*[#id='possible_matched']/table/tbody/tr/td[1]")
name_array = []
names.each { |name| name_array << name.text}
admits = browser.find_elements(:xpath => ".//*[#id='possible_matched']/table/tbody/tr/td[5]")
admit_array = []
admits.each { |date| admit_array << date.text }
name_admit_array = name_array.zip(admit_array)
name_admit_array.each do |name, date|
puts "#{name}: #{date}"
end
#finds the location of the sub-array containing patient name and collection associated admit date
patient_name = browser.find_element(:xpath => ".//*[#id='dialog-modal-cancel-hl7-preview']/table/tbody/tr[2]/td[1]").text
collected_date = browser.find_element(:xpath => ".//*[#id='dialog-modal-cancel-hl7-preview']/table/tbody/tr[2]/td[4]").text
mo, da, yr = collected_date.split('/').map(&:to_i)
cd = [yr, mo, da]
bl = name_admit_array.each_with_index.select { |(name, date), i|
m, d, y = date.split('/').map(&:to_i)
dt = [y, m, d]
name.downcase == patient_name.downcase and (dt <=> cd)<0
}.map {|x, i| i }
# presses the button associated with the correct sub-array
blf = name_admit_array.values_at(*bl)
if bl.any?
bf = blf.rindex(blf.max) + 2
browser.find_element(:xpath => ".//*[#id='possible_matched']/table/tbody/tr[#{bf}]/td[6]/div/a").click
else
browser.find_element(:xpath => "html/body/div[6]/div[1]/a/span").click
end
end while bl.any?
puts "no name :("
So it runs the loop until there is nothing found in the array bl. What I want to do is have this loop run again but with the next link on the list of links. So at the beginning of the loop it should do browser.find_element(:xpath => ".//*[#id='sub_nav_content']/table/tbody/tr[3]/td[3]/a").clickinstead of browser.find_element(:xpath => ".//*[#id='sub_nav_content']/table/tbody/tr[2]/td[3]/a").click. Then is should run the rest of the loop in the same way. I want it to continue to increment tr[] each time the loop runs into br.any? => false.
you can create an array of xpaths, and run this code for each of them:
[
".//*[#id='sub_nav_content']/table/tbody/tr[3]/td[3]/a",
".//*[#id='sub_nav_content']/table/tbody/tr[2]/td[3]/a"
].each do |path|
begin
browser.find_element(:xpath => path).click
// etc....
end while bl.any?
end
I believe that more changes need to adjust your code reusable, depending on your needs
I have a script that runs perfectly in the ChromeWebDriver but fails on PhantomJS. When I check if an element exists i get the following error:
[ERROR - 2014-01-07T19:31:55.878Z] WebElementLocator - _handleLocateCommand - El
ement(s) NOT Found: GAVE UP. Search Stop Time: 1389123115867
This doesn't really seem like an issue as the script continues. However, later on the script will fail unable to locate the following element:
question.div(:class => "choices")
This particular script visits a page that has test questions on it. They are in random order. The script decided what kind of question it is and chooses a random answer.
Thanks for any help. Here is the relevant code:
def QuestionType(question)
if question.div(:class => "questionPrompt").text_field.exists?
puts "FITB"
FITB(question)
#elsif question.div(:class => "choices").ul(:class =>"choices-list").li(:index => 1).checkbox.exists?
elsif question.checkbox.exists?
puts "Checkbox"
Checkbox(question)
else
puts "Radio"
Radio(question)
end
end
def FITB(question)
arn = Random.new.rand(0..10)
if arn == 0
answers.li(:index => arn).radio.set
else
idx = 0
begin
question.div(:class => "questionPrompt").text_field(:index => idx).set("Test #{idx}")
idx = idx + 1;
end while question.div(:class => "questionPrompt").text_field(:index => idx).exists?
end
puts "FITB Complete"
end
def Checkbox(question)
allAnswers = question.div(:class => "choices")
answers = allAnswers.ul
max = answers.lis.length - 1
arn = Random.new.rand(0..max)
if arn == 0
answers.li(:index => arn).radio.set
else
for i in 1..arn
answers.li(:index => i).checkbox.set
end
end
puts "Checkbox Complete"
end
def Radio(question)
allAnswers = question.div(:class => "choices")
answers = allAnswers.ul
max = answers.lis.length - 1
arn = Random.new.rand(0..max)
answers.li(:index => arn).radio.set
puts "Radio Complete"
end
what's wrong here? I wanna to wait with the next task until my page is completely loaded.
Problem: No errors, but the Driver doesn't wait :-(
# wait for page load
wait2 = Selenium::WebDriver::Wait.new(:timeout => 20) # seconds
count = 0
begin
raise("maximum attempt crossed #{count} times") if count > 3
wait2.until {
self.getDriver.execute_script("return document.readyState;") == "complete"
}
rescue Timeout::Error
count +=1
retry
end
#do sth
I would handle the issue as below :
wait = Selenium::WebDriver::Wait.new(:timeout => 20) # seconds
count = 0
begin
raise("maximum attempt crossed #{count} times") if count > 3
# if the page having title 'page_title' is not loaded within 20 seconds
# time out error will be thrown,which will be handled by the rescue clause.
wait.until { driver.title == 'page_title' }
rescue Timeout::Error
count +=1
retry
end
This seems to be working for me.
DEFAULT_TIMEOUT = 10
def wait_for_page_to_load(options = {})
options[:timeout] ||= DEFAULT_TIMEOUT
wait = Selenium::WebDriver::Wait.new(options)
wait.until {current_driver.execute_script('var browserState = document.readyState; return browserState;') == "complete" }
end
http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/checkboxes/defaultcs.aspx
I am able to get total number of options but not able to get text.
Try with following. It's working at my end:
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :firefox
driver.get "http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/checkboxes/defaultcs.aspx"
el = driver.find_element(:id, "ctl00_ContentPlaceHolder1_RadComboBox1_Arrow")
el.click
list = driver.find_element(:xpath, "//ul[#class='rcbList']")
options = list.find_elements(:xpath, ".//li/label")
options.each do |option|
puts option.text
end
Works fine for me with or without using Selenium::WebDriver::Wait.
Here is the complete code (I commented out the version without using WebDriverWait, both versions should work anyway):
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :firefox
driver.get('http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/checkboxes/defaultcs.aspx')
wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
# without wait
# combo = driver.find_element(:id => 'ctl00_ContentPlaceHolder1_RadComboBox1_Input')
# combo.click
# with wait
combo = wait.until { driver.find_element(:id => "ctl00_ContentPlaceHolder1_RadComboBox1_Input") }
combo.click
# without wait
# all_labels = driver.find_elements(:css => '#ctl00_ContentPlaceHolder1_RadComboBox1_DropDown .rcbItem label')
# with wait
all_labels = wait.until { driver.find_elements(:css => '#ctl00_ContentPlaceHolder1_RadComboBox1_DropDown .rcbItem label') }
all_labels.each do |label|
puts label.text
end
puts all_labels.count
Hey, all. I'm working on making a GUI for a Ruby project using Shoes.
I've got a class called Manager (as in memory manager) that loads a 'process list' from a file, splits it up and assigns things to different 'pages' in memory when a certain execution call is made. I really don't think this part matters too much, though. It all works as a terminal application just fine.
However, Shoes is just baffling me. Here's what I've got so far:
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
#file_button.click {
filename = ask_open_file
# #manager.set_exec_list filename
# alert "this makes no sense"
#exec_lines.text = #manager.exec_list.join "\n"
# exec_lines.text = File.read filename
}
}
end
end
What happens when I run this:
The program view loads as expected. I get a header, a paragraph that says "click button....", and a button. I click the button and I select the file. But this is where things get weird.
If I run the last commented line exec_lines.text = File.read filename it does as I would like, but my manager doesn't get any of the information it needs.
If I run the #manager.set_exec_list filename line, nothing from that line on in the block gets run, including the alert, or any other code I try to put in there.
if I run as shown above, however, I get the output I expect, but I don't get to set my data from the file that I select.
I've tried to figure this out from the Shoes Rules page, but this doesn't seem to be an issue that they address, and their "it changes/doesn't change self" I think I grasp, but it's confusing and I don't think it's exactly related to this problem.
Does anyone have any idea how to get this to work? I'm kind of down to crunch time on this project and I can't seem to get any other Ruby GUI toolkit to even run, so I think I'm pretty stuck with Shoes.
Thanks.
Update
I've tried running ruby-debug on the code when I make the call to #manager.set_exec_list filename, and stepping through it shows that this call is made, but the code never actually (from what I can tell) jumps into that method, and acts like it's the last line of code in the block. Do I need to include these classes inside the Shoes.app block?
Update Nope. That does nothing different.
update fullsource code follows:
#!/usr/bin/env shoes
require 'rubygems'
require 'ruby-debug'
class MemSegment
attr_accessor :filled, :pid, :seg, :seg_id
def initialize(filled=false, pid=nil, seg=nil, seg_id=0)
#filled = filled
#pid = pid.to_i
#seg = seg.to_s
#seg_id = seg_id.to_i
self
end
def fill(pid, seg, seg_id)
#filled = true; #pid = pid; #seg = seg; #seg_id = seg_id;
self
end
def clear
self.filled = false; self.pid = nil; self.seg = nil;
self
end
def filled?
#filled
end
def to_s
filled? ? "#{seg} #{seg_id} for pid #{pid}" : "Free"
end
end
class SimProc
include Enumerable
attr_accessor :pid, :code, :data
def initialize(pid, code, data)
#pid = pid.to_i
#code = code.to_i
#data = data.to_i
end
def each
yield :code, code
yield :data, data
end
def to_s
"[SimProc :pid => #{pid}, :code => #{code}, :data => #{data}]"
end
def to_a
[#pid, #code, #data]
end
end
class Manager
attr_reader :segments, :processes, :exec_list, :exec_object
def initialize
#exec_list = [[1, 2], [3, 4], [5, 6]]
#processes = {}
#segments = Array.new(8) { MemSegment.new }
end
def print_activity
#segments.each_with_index {|s, index| puts "Seg #{index} => #{s}" }
#processes.each_value {|s| puts s }
end
def load_process(pcb, exec_index)
if pcb.size == 3
p = SimProc.new(*pcb)
bad_load = false
#processes.store p.pid, p
#processes[p.pid].each do |proc_seg, bsize|
(bsize / 512.0).ceil.times do |seg_id|
#segments.each_with_index do |s, index|
if !s.filled
#find the first empty memory segment
s.fill p.pid, proc_seg, seg_id
break
# if all slots are filled and we couldn't place a proc block
elsif index == #segments.size - 1
bad_load = true
puts "Cannot find a place for #{proc_seg} segment of size #{bsize}. Requeueing..."
break;
end
end
break if bad_load
end
end
# recover pages and queue the process for later
if bad_load
#segments.each_with_index do |seg, seg_index|
# clear any segments that didn't get loaded properly
if seg.pid == p.pid
seg.clear
puts "Seg #{seg_index} => segment cleared: #{seg}"
end
end
# reinsert this process after the next in the execution list
# it will attempt to load and run after the next process is performed
#exec_list.insert(exec_index + 2, p.to_a)
end
print_activity
elsif pcb.size == 2 and pcb[1] == -1
# a process is exiting
puts "removing pid #{pcb[0]}"
#segments.each { |s| s.clear if s.pid == pcb[0] }
#processes.delete pcb[0]
print_activity
end
end
def set_exec_list(filename)
file = File.open filename
file.each { |pcb| #exec_list << pcb.split.map(&:to_i) } unless file.nil?
filename
end
def main
exseq = File.open('exseq2.txt')
set_exec_list exseq
# this is the object that will be used to run each process with .next
#exec_object = #exec_list.each_with_index
# #exec_list.each_with_index { |pcb, exec_index| load_process(pcb, exec_index) }
(#exec_list.size + 1).times do
load_process(*#exec_object.next)
end
end
end
=begin
manager = Manager.new
manager.main
=end
#=begin
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
debugger
#file_button.click {
filename = ask_open_file
#manager.set_exec_list filename
# alert "this makes no sense"
# #exec_lines.text = #manager.exec_list
# #exec_lines.text = File.read filename
#exec_lines.text = #manager.exec_list.join "\n"
}
}
end
end
#=end
So, a few things:
#1, I don't have the implementation of Manager, so I can't tell you why it breaks. Did you try checking the Shoes console for any errors? Hit control-/ to bring that up. If 'nothing runs after it hits that line,' that's probably the issue.
#2, this does work for me, as long as you change exec_lines to #exec_lines on the last line. Here's what I tried:
class Manager;end
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
#file_button.click {
filename = ask_open_file
#alert "this makes no sense"
#exec_lines.text = File.read filename
}
}
end
end
Hope that helps!