Stale Element Reference Error - page-object-gem

Using the page-object gem and Watir webdriver, we occasionally come across a Selenium::WebDriver::Error::StaleElementReferenceError on a page that loads some basic stuff, makes an ajax request, and repopulates with more info (for the illusion of speed on the page).
This happens because there will be an HTML element there, it disappears quickly and reappears again before the user is really aware of it.
We use page-object's ".when_present" method to wait until the object's on the page the first time before accessing it. However, even after the code finds the element, usually it will get the stale element error because the original HTML element gone and the other one has reappeared by the time it tries to access it.
We found a way using just straight Watir (not page-object) around this. We basically catch the StaleElementReferenceError inside of a Watir::Wait.until block. If it gets an exception, the block returns false, and the Wait.until tries again until it either becomes true, or times out eventually. What we've found is that this usually gets the Stale Element the first time (on line 3 below), returns false from the rescue, Wait.until executes the block again, and the 2nd time it's true and the test moves on and passes.
1 Watir::Wait.until {
2 begin
3 tds = page.my_element.when_present.element.tds
4 table_data_classes = tds.map{|cell| cell.attribute_value("class") }
5
6 # Should have at least one with a class of "xyz"
7 xyz = table_data_classes.select{|data| data.include?("xyz")}
8 xyz.size > 0
9 rescue Selenium::WebDriver::Error::StaleElementReferenceError
10 false
11 end
12 }
I'm really just wondering if there's any kind of page-object wrapper for this kind of thing. I couldn't find anything. If not, that's ok because the above works. Just curious.
Thanks

Gayle, great question. At this time there is no specific handling of a retry if the StaleElementReferenceError occurs in the page-object gem. I have heard from another person that they periodically get this error and it is something I could look into. What version of watir-webdriver are you using?
There is a way to duplicate the code you have above using page-object. I am not sure if the above code you provided is inside of your page or if it is somewhere else (like step definitions or some other place). Here's what I would do using the page-object gem:
Inside of your PageObject class
def has_element_with_class(clz)
wait_until
begin
cells = my_element.when_present.cell_elements
cells.any? { |cell| cell.attribute('class').include? clz }
rescue Selenium::WebDriver::Error::StaleElementReferenceError
false
end
end
I am simply getting all of the cells (tds) nested within my_element into an array. I am calling the any? method on that array which will return true if any of the blocks returns true and otherwise false. This has the same effect as all of the code you have above. If the error happens then you will continue another time.

Related

How to add to / amend / consolidate JRuby Profiler data?

Say I have inside my JRuby program the following loop:
loop do
x=foo()
break if x
bar()
end
and I want to collect profiling information just for the invocations of bar. How to do this? I got so far:
pd = []
loop do
x=foo()
break if x
pd << JRuby::Profiler.profile { bar() }
end
This leaves me with an array pd of profile data objects, one for each invocation of bar. Is there a way to create a "summary" data object, by combining all the pd elements? Or even better, have a single object, where profile would just add to the existing profiling information?
I googled for a documentation of the JRuby::Profiler API, but couldn't find anything except a few simple examples, none of them covering my case.
UPDATE : Here is another attempt I tried, which does not work either.
Since the profile method initially clears the profile data inside the Profiler, I tried to separate the profiling steps from the data initializing steps, like this:
JRuby::Profiler.clear
loop do
x=foo()
break if x
JRuby::Profiler.send(:current_thread_context).start_profiling
bar()
JRuby::Profiler.send(:current_thread_context).stop_profiling
end
profile_data = JRuby::Profiler.send(:profile_data)
This seems to work at first, but after investigation, I found that profile_data then contains the profiling information from the last (most recent) execution of bar, not of all executions collected together.
I figured out a solution, though I have the feeling that I'm using a ton of undocumented features to get it working. I also must add that I am using (1.7.27), so later JRuby versions might or might not need a different approach.
The problem with profiling is that start_profiling (corresponding to the Java method startProfiling in the class Java::OrgJrubyRuntime::ThreadContext) not only turns on the profiling flag, but also allocates a fresh ProfileData object. What we want to do, is to reuse the old object. stop_profiling OTOH only toggles the profiling switch and is uncritical.
Unfortunately, ThreadContext does not provide a method to manipulate the isProfiling toggle, so as a first step, we have to add one:
class Java::OrgJrubyRuntime::ThreadContext
field_writer :isProfiling
end
With this, we can set/reset the internal isProfiling switch. Now my loop becomes:
context = JRuby::Profiler.send(:current_thread_context)
JRuby::Profiler.clear
profile_data_is_allocated = nil
loop do
x=foo()
break if x
# The first time, we allocate the profile data
profile_data_is_allocated ||= context.start_profiling
context.isProfiling = true
bar()
context.isProfiling = false
end
profile_data = JRuby::Profiler.send(:profile_data)
In this solution, I tried to keep as close as possible to the capabilities of the JRuby::Profiler class, but we see, that the only public method still used is the clear method. Basically, I have reimplemented profiling in terms of the ThreadContext class; so if someone comes up with a better way to solve it, I will highly appreciate it.

Understanding heisenbug example: "Debuggers cause additional source code to be executed stealthily"

I read wikipedia page about heisunbug, but don't understand this example. Can anyone explain it in detail?
Debuggers also commonly provide watches or other user interfaces that cause additional source code (such as property accessors) to be executed stealthily, which can, in turn, change the state of the program.
I think what it's getting at is that the debugger itself may call code (such as getters) to retrieve the value of a property you might have placed a watch on.
Consider the getter:
def getter fahrenheit:
return celsius * 9 / 5 + 32;
and what would happen if you put a watch on the fahrenheit property.
That code would normally only be called if your code itself tried to access the fahrenheit propery but, if a debugger is calling it to maintain the watch, it may be called outside of the control of your program.
A simple example, let's say the getter has a (pretty obvious) bug which means that it returns the wrong result the first time it's called:
class temperature:
variable state
def init:
state = 1
def getter fahrenheit:
if state == 1:
state = 0
return -42
return celsius * 9 / 5 + 32;
So running your code without a debugger exhibits a problem in that it will return a weird value the first time your code calls it.
But, if your debugger is actually calling the getter to extract a value that it's watching (and it's probably doing this after every single-step operation you perform), that means the getter will be well and truly returning the correct value by the time your code calls it for what it thinks is the first time.
Hence the problem will disappear when you try to look closer at it, and that's the very definition of a Heisenbug, despite the fact that Heisenberg's actual uncertainty principle has little to do with the observer effect.

Nokogiri returns "no method error"

I keep getting the same error in my program. I've written a method that takes some messy HTML and turns it into neater strings. This works fine on its own, however when I run the whole program I get the following error:
kamer.rb:9:in `normalise_instrumentation': undefined method `split' for #<Nokogiri::XML::NodeSet:0x007f92cb93bfb0> (NoMethodError)
I'd be really grateful for any info or advice on why this happens and how to stop it.
The code is here:
require 'nokogiri'
require 'open-uri'
def normalise_instrumentation(instrumentation)
messy_array = instrumentation.split('.')
normal_array = []
messy_array.each do |section|
if section =~ /\A\d+\z/
normal_array << section
end
end
return normal_array
end
doc = Nokogiri::HTML(open('http://www.cs.vu.nl/~rutger/vuko/nl/lijst_van_ooit/complete-solo.html'))
table = doc.css('table[summary=works] tr')
work_value = []
work_hash = {}
table.each do |row|
piece = [row.css('td[1]'), row.css('td[2]'), row.css('td[3]')].map { |r|
r.text.strip!
}
work_value = work_value.push(piece)
work_key = normalise_instrumentation(row.css('td[3]'))
work_hash[work_key] = work_value
end
puts work_hash
The problem is here:
row.css('td[3]')
Here's why:
row.css('td[3]').class
# => Nokogiri::XML::NodeSet < Object
You're creating your piece array which then becomes an array of NodeSets, which is probably not what you want, because text against a NodeSet often returns a weird String of concatenated text from multiple nodes. You're not seeing that happen here because you're searching inside a row (<tr>) but if you were to look one level up, in the <table>, you'd have a cocked gun pointed at your foot.
Passing a NodeSet to your normalise_instrumentation method is a problem because NodeSet doesn't have a split method, which is the error you're seeing.
But, it gets worse before it gets better. css, like search and xpath returns a NodeSet, which is akin to an Array. Passing an array-like critter to the method will still result in confusion, because you really want just the Node found, not a set of Nodes. So I'd probably use:
row.at('td[3]')
which will return only the node.
At this point you probably want the text of that node, something like
row.at('td[3]').text
would make more sense because then the method would receive a String, which does have a split method.
However, it appears there are additional problems, because some of the cells you want don't exist, so you'll get nil values also.
This isn't one of my better answers, because I'm still trying to grok what you're doing. Providing us with a minimal example of the HTML you need to parse, and the output you want to capture, will help us fine-tune your code to get what you want.
I had a similar error (undefined method) for a different reason, in my case it was due to an extra dot (put by mistake) like this:
status = data.css.("status font-large").text
where it was fixed by removing the extra dot after the css as shown below
status = data.css("status font-large").text
I hope this helps someone else

Invalid method when method is valid

I have just started a new version of my Crysis Wars Server Side Modification called InfinityX. For better management, I have put the functions inside tables as it looks neater and I can group functions together (like Core.PlayerHandle:GetIp(player)), but I have ran into a problem.
The problem is that the specified method to get the players' name, player:GetName() is being seen as an invalid method, when the method actually is completely valid.
I would like to know if using the below structure is causing a problem and if so, how to fix it. This is the first time I've used this structure for functions, but it is already proving easier than the old method I was using.
The Code:
Event =
{
PlayerConnect = function(player)
Msg.All:CenteredConsole("$4Event$8 (Connect)$9: $3"..player:GetName().." on channel "..player.actor:GetChannel());
System.LogAlways(Default.Tag.."Incoming Connect on Channel "..player.actor:GetChannel());
Event:Log("Connect", player);
end;
};
The below code works when I bypass the function and put the code directly where it's needed:
Msg.All:CenteredConsole("$4Event$8 (Connect)$9: $3"..player:GetName().." on channel "..player.actor:GetChannel());
System.LogAlways(Default.Tag.."Incoming Connect on Channel "..player.actor:GetChannel());
The Error:
[Warning] [Lua Error] infinityx/main/core.events.lua:23: attempt to call method 'GetName' (a nil value)
PlayerConnect, (infinityx/main/core.events.lua: 23)
ConnectScript, (infinityx/main/core.main.lua: 52)
OnClientEnteredGame, (scripts/gamerules/instantaction.lua: 511)
(null) (scripts/gamerules/teaminstantaction.lua: 520)
Any clarification would be appreciated.
Thanks :)
Well, as PlayerConnect is inside the table Event, and you are calling with a ":", add self as first arg in the function, like:
PlayerConnect = function(self, player)
Clearly, player in the first block of code is not the same as player in the second block of code. The problem must be that the caller of Event.PlayerConnect is not passing the same value.
To test that your Event.PlayerConnect function works, try this in the same place as your second block of code:
Event.PlayerConnect(player)
That should work as you expect.
So, the problem comes down to how Event.PlayerConnect is called without the second block of code. I'm not familiar with that game engine so I don't know how it is done. Perhaps reviewing the documentation and/or debugging that area would help. If you print(player) or call the equivalent log function in both cases, you should see they are different. If you can't run in a debugger, you can still get a stack trace with print(debug.traceback("Accessing player, who's value is: "..player)). If there is indeed some kind of table-based player object in both cases, you can try comparing their fields to see how they are different. You might need to write a simple dumping function to help with that.

Three rows of almost the same code behave differently

I have three dropdown boxes on a Main_Form. I will add the chosen content into three fields on the form, Form_Applications.
These three lines are added :
Form_Applications.Classification = Form_Main_Form.Combo43.Value
Form_Applications.Countryname_Cluster = Form_Main_Form.Combo56.Value
Form_Applications.Application = Form_Main_Form.Combo64.Value
The first two work perfectly but the last one gives error code 438!
I can enter in the immediate window :
Form_Applications.Classification = "what ever"
Form_Applications.Countryname_Cluster = "what ever"
but not for the third line. Then, after enter, the Object doesn't support this property or method error appears.
I didn't expect this error as I do exactly the same as in the first two lines.
Can you please help or do you need more info ?
In VBA Application is a special word and should not be used to address fields.
FormName.Application will return an object that points to the application instance that is running that form as opposed to an object within that form.
From the Application object you can do all sorts of other things such as executing external programs and other application level stuff like saving files/
Rename your Application field to something else, perhaps ApplicationCombo and change your line of code to match the new name. After doing this the code should execute as you expect.
Form_Applications.Application is referring to the application itself. It is not a field, so therefore it is not assignable (at least with a string).
You really haven't provided enough code to draw any real conclusions though. But looking at what you have posted, you definitely need to rethink your approach.
It's to say definitely but you are not doing the same. It looks like you are reading a ComboBox value the same (I will assume Combo64 is the same as 43 and 56) but my guess is that what you are assigning that value to is the problem:
Form_Applications.Application =
Application is not assignable. Is there another field you meant to use there?