Is `#browser` the obligatory name for the ChromeDriver object when using Watir/Chrome? - cucumber

I want to change the name of the #browser variable because I'm tired of typing it.
In env.rb:
before do
#b = Watir::Browser.new :chrome
end
This throws error:
Unable to pick a platform for the provided browser or element: nil.
nil was passed to the PageObject constructor instead of a valid browser or element object. (RuntimeError)
before do
#browser = Watir::Browser.new :chrome
end
works as expected.
#browser = Watir::Browser.new :chrome
#b = #browser
doesn't work either. Is there any way to modify this variable name?

In general, you can store your browser instance in a variable of any name. Watir-Webdriver does not care, nor does Cheezy's page objects. The initialization of the page object only cares what the actual object passed in is - ie it must be a Watir::Browser or Watir::Element (or the Selenium-WebDriver equivalents).
However, this is not true when using the PageObject::PageFactory. In the on_page method, and implicitly visit_page and if_page, the code is hard-coded to look for a #browser variable.
def on_page(page_class, params={:using_params => {}}, visit=false, &block)
page_class = class_from_string(page_class) if page_class.is_a? String
return super(page_class, params, visit, &block) unless page_class.ancestors.include? PageObject
merged = page_class.params.merge(params[:using_params])
page_class.instance_variable_set("#merged_params", merged) unless merged.empty?
#current_page = page_class.new(#browser, visit)
block.call #current_page if block
#current_page
end
The initialization of the page object is always done with #browser. When you stored the browser instance in #b, it meant that #browser was nil. This then led to the exception you saw. If you want to use the PageFactory as written, you will need to stick to #browser.
If the variable really bothers you, there is always the option for monkey patching. Simply re-define the on_page and on method to use whatever variable you like. The following redefines the method to use #b:
require 'page-object'
module PageObject
module PageFactory
def on_page(page_class, params={:using_params => {}}, visit=false, &block)
page_class = class_from_string(page_class) if page_class.is_a? String
return super(page_class, params, visit, &block) unless page_class.ancestors.include? PageObject
merged = page_class.params.merge(params[:using_params])
page_class.instance_variable_set("#merged_params", merged) unless merged.empty?
#current_page = page_class.new(#b, visit)
block.call #current_page if block
#current_page
end
alias_method :on, :on_page
end
end

The issue is somewhere else in your code. If you run just the line you provided (after requiring watir)
require 'watir-webdriver'
#b = Watir::Browser.new :chrome
in a IRB session it will work just fine..
Some other code dependency (page objects library perhaps) needs a browser object and is expecting a specific name, is getting a nill (un-initialized) object instead and complaining.
Also, unless you want the overhead of opening and closing the browser for each test (can add a ton of time) you may want to initialize the browser earlier, such as in a before_all hook, or early in the startup of your test code. Then just do something like clear cache and cookies in the before hook

Related

For Geb Page Object, when is the static content block initialized?

When following the geb page object pattern, it is encouraged to create a static block of values that represent the elements on the page you are attempting to interface with. It is also good to create an at checker. Here is an example:
class SomePage extends Page{
static at = {$(By.xpath("some header or something")).displayed}
static content = {
element1 {$(By.xpath("some xpath1"))}
element2 {$(By.xpath("some xpath2"))}
//so on and so forth
}
}
Now I think I know the answer this question already but could not find the docs to back it up. I am pretty sure that the static content block is initialized once the "at checker" is called from a test script, but I am not sure. If what I am suggesting is true then that would mean something like this:
class SomePage extends Page{
static at = {$(By.xpath("some header or something")).displayed}
def someString
static content = {
element1 {$(By.xpath("//*[contains(text(), ${someString}"))}
element2 {$(By.xpath("some xpath2"))}
//so on and so forth
}
def setSomeString(String x){
this.someString = x
}
}
would be impractical right, or maybe even impossible? This is because in order to call "setSomeString" you need to call the at checker to tell the script which class to reference for method and variable calls, but if you call the at checker that means the static content becomes initialized.
Furthermore, if a webpage has content that doesn't show up upon initial arrival to the webpage, then you would not be able to put that content into the static content block either, since the at checker should be called as soon as you arrive on said webPage.
I am simply looking to confirm or deny that this is the behavior of geb page object. and I would be happy to hear answers describing best practice given the above situation.
also if there is a way to re-initialize the content, I would like to know that too, but I figure there is not.
A content definition is evaluated every time the result of the definition is requested:
def somePage = to SomePage //at checker is executed but the defintion for element1 isn't
somePage.element1 // closure defined for element1 is executed
somePage.element1 // and here again
// many lines of code which trigger additional content to show up on the page
somePage.element1 // closure defined for element1 is executed again

Excel crash when typing open parenthesis

Here's one I don't understand.
Given this class module (stripped down to the bare minimum necessary to reproduce the crash):
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "TestCrashClass"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Public Function Init() As TestCrashClass
Attribute Init.VB_UserMemId = 0
Dim tcc As New TestCrashClass
Set Init = tcc
End Function
Public Property Get Data() As String
Data = "test data"
End Property
Can anyone tell me why Excel totally craps out when I type in this code:
Sub MakeExcelCrash()
With TestCrashClass(
At this point, I this lovely message:
Even if I type in a full procedure without the offending parentheses and then try to add them later, I get the same crash.
The only way I can get Excel not to crash is to copy/paste a set of () from somewhere else to this line of code.
Sub MakeExcelCrash()
With TestCrashClass()
Debug.Print .Data
End With
End Sub
If the Init() method has a parameter—even an optional one—it won't crash when the opening paren is typed.
I'm more curious about why this happens than ways around it; it doesn't actually come up that often in my code and when it does I can fix it with a change in approach, but I'm really frustrated that I don't know what's causing these crashes. So maybe someone who knows more about the inner working of VBA can explain it to me?
You don't even need the With block. Any attempt to type ( after the class name takes Excel down.
The problem is that you have the VB_PredeclaredId set to true and the default member is trying to return itself. When you attach a debugger to the dying Excel instance, you can see that the underlying issue is a stack overflow:
Unhandled exception at 0x0F06EC84 (VBE7.DLL) in EXCEL.EXE: 0xC00000FD:
Stack overflow (parameters: 0x00000001, 0x00212FFC).
When you type With TestCrashClass(, what happens is that VBA starts looking for an indexer on the default property, because Init() doesn't have any properties. For example, consider a Collection. You can use the default property's (Item) indexer like this:
Dim x As Collection
Set x = New Collection
x.Add 42
Debug.Print x(1) '<--indexed access via default member.
This is exactly equivalent to Debug.Print x.Items(1). This is where you start running into problems. Init() doesn't have parameters, so VBA starts drilling down through the default members to find the first one that has an indexer so IntelliSense can display the parameter list. It starts doing this:
x.[default].[default].[default].[default].[default]...
In your case, it's creating an infinite loop because [default] returns x. The same thing happens in the Collection code above (except it finds one):
Throw in the fact that you have a default instance, and the end result is something like this:
Private Sub Class_Initialize()
Class_Initialize
End Sub
As #TimWilliams points out, having a default member that returns an instance of the same class (or a class loop eg. ParentClass.ChildClass.ParentClass.ChildClass... where ParentClass and ChildClass both have default members), and when used in certain syntax cases, such as a With block, will cause VBE to try and resolve the default member.
The first parenthesis makes VBE assume there must be a method, indexed get or array index that will take an argument, so it sets off to resolve the ultimate target member.
So the incomplete line, with a cursor located after the parenthesis:
With TestCrashClass(
Is effectively the same as:
With TestCrashClass.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init.Init '....You're inquisitive scrolling this far over, but you get the point.
At some point, your system or VBE runs out of resources and exits with the grace and poise of a thermonuclear group-hug.
+1 for improvising with a copy/pasta of a parentheses pair.
Sounds like some sort of corruption. I've had Excel behave irrationally like this before, normally in large projects, and the only way to get around it is to drag all of your classes etc into a new project.
I suspect it happens because Excel doesn't truly delete classes, modules, worksheets etc that have been removed. You can tell this because of the file size.
There is no Compact and Repair functionality, as in Access, as far as i'm aware

Setting input text field with watir-jquery

I am trying to set the value of a text input using watir-jquery selectors. I find that it only works with watir's text_field selector. My questions are:
How to set input fields using watir-jquery
What is watir's text_field selector, and why does its result have different methods than the same field found by the input selector?
The relevant code follows:
require 'watir'
require 'watir-jquery'
browser = Watir::Browser.new
browser.goto 'www.yandex.com'
jq_inp = browser.jq('input#searchInput')
jq_inp.value = 'Vlad the Impaler'
NoMethodError: undefined method `value=' for #<Watir::Input:0x007fd7f3c3cee0>
from gems/ruby-2.0.0-p247/gems/watir-webdriver-0.6.4/lib/watir-webdriver/elements/element.rb:553:in `method_missing'
wt_inp = browser.input(:id, 'searchInput')
wt_inp.value = 'Vlad the Impaler'
NoMethodError: undefined method `value=' for #<Watir::Input:0x007fd7f3c97b60>
from gems/ruby-2.0.0-p247/gems/watir-webdriver-0.6.4/lib/watir-webdriver/elements/element.rb:553:in `method_missing'
wt_tf = browser.text_field(:id, 'searchInput')
wt_tf.value = 'Vlad the Impaler' # This works!
# So, let us try text_field with jquery
jq_tf = browser.jq('text_field#searchInput')
=> nil
# No search thing as text_field in jquery
Watir has a TextField object that is a subtype of Input, which supports set. So both
browser.jq('input#searchInput').to_subtype.set
and
browser.input(:id, 'searchInput').to_subtype.set
work.

Passing parameter via PageFactory

I'm using page-object gem with watir and RSpec to test my web application.
Here is my page object.
require 'page-object'
class UserEditPage
include PageObject
page_url "#{$context}/user/edit?id=#{#params[:id]}"
text_field :user_id, name: 'userName'
text_field :pw, name: 'password'
text_field :pw_retype, name: 'password2'
# snip uninteresting stuff
button :submit, index: 0
end
I would like to parameterize :id in page_url via PageObject::PageFactory like this:
visit UserEditPage, using_params: {id: 1000} do |page|
# operation on page
end
This document implies that above usage is possible, but I couldn't understand how exactly this is achieved.
Question: how can I pass parameter id from visit method to UserEditPage?
Running code results in
***/user/edit_page.rb:8:in `<class:UserEditPage>': undefined method `[]' for nil:NilClass (NoMethodError)
from ***/user/edit_page.rb:5:in `<top (required)>'
probably because #params is nil when evaluating page_url.
Changing using_params: {id: 1000} to id: 1000 with no luck.
The page_url needs to be:
page_url "#{$context}/user/edit?id=<%=params[:id]%>"
Note how the parameters need to be included differently - ie <%=params[:id]%> instead of #{params[:id]}.
The problem is that the page_url is evaluated when the page object class is evaluated. This means that when the class is evaluated, params[:id] must already exist. This is not going to be true when you want to include the parameters dynamically.
The page object gem uses ERB to allow you to create a template, which allows substitution of the params later - ie when calling visit.
In summary, when creating a page_url:
Use string interpolation if the value is known when the page object class is evaluated.
Use ERB if the value is to be dynamically specified on visit.
Working Example
Below is a working example of the dynamic parameters.
require 'watir-webdriver'
require 'page-object'
$context = 'http://testurl.com'
class UserEditPage
include PageObject
page_url "#{$context}/user/edit?id=<%=params[:id]%>"
end
include PageObject::PageFactory
#browser = Watir::Browser.new
visit UserEditPage, using_params: {id: 1000} do |page|
p page.current_url
#=> "http://testurl.com/user/edit?id=1000"
end

Method call on page object does not return result

I am having problem to use page object method call to return an object on the page.
Here is my example codes in test_log_in.rb
...
class TestLogIn < Test::Unit::TestCase
.....
def test_failed_log_in
#log_in_page= LogIn.new(#browser)
#log_in_page.go_to_log_in
#log_in_page.log_in("174773476","test","aaa111as")
puts #log_in_page.error_message
puts #log_in_page.get_error_message
end
end
My log in class is defined below:
class LogIn
include PageObject
...
h3(:error_message, :class => 'no-margin white-text')
...
def log_in (access_number, user_id, password)
self.access_number = access_number
self.user_id = user_id
self.password = password
log_me_in
AccountSummary.new(#browser)
end
....
def get_error_message
self.error_message
end
....
end
Why will the following lines returns no output?
puts #log_in_page.error_message
puts #log_in_page.get_error_message
Can you please help me?
Thanks.
My guess is that there is a timing issue due to the error message being displayed through javascript. It is likely that the error message element is always in the html of the page, but when login fails, the error message element's style is changed to be something visible.
When a page object (or watir) element returns its text, it only includes the visible text. So if the error message is not yet visible, you get no text.
Try waiting to ensure the element is visible to the user:
def get_error_message
error_message_element.when_present.text
end
when_present will wait up to 5 seconds for the element to become visible to the user and then return the text.
Update - Multiple h3s:
It looks like the actual problem is that there are multiple h3 elements that have that class. Watir always returns the first match, which in this case is the cookie error heading. Given that it is not being displayed, you get the blank text.
The solution would be to reduce the search scope so that you the first h3 is the one you want. This can be done by only looking in the error message list. I would define the page object as follows. Note that you do not need to specify the class of the h3 element since it is the only h3 element in the list.
class LogIn
include PageObject
div(:error_message_list, :id => 'errorMessageList')
h3(:error_message){ error_message_list_element.h3_element }
end
This will then find the right h3 element:
browser = Watir::Browser.new
browser.goto 'https://www.bnz.co.nz/ib4b/app/login'
browser.button(:text => 'Login').click
page = LogIn.new(browser)
page.error_message
#=> "There were 3 errors on the page."

Resources