Using the Page Object model and gem I want to access an element that is nested 3 layers deep. I've successfully accessed nested elements that are 2 elements deep but for 3 the same method isn't working.
3 elements defined in my Page Object:
div(:serv_info, :class => "service-info")
div(:validate_method, :class => "validate-method")
div(:scar_input_group, :class => "input-group")
So I tried to chain those 3 elements to access the div class input-container input-left-half round like this:
div(:scar_first_name_error){validate_method_element.serv_info_element.scar_input_group_element.div_element(:class => "input-container input-left-half round")}
But I got the error that serv_info_element is an undefined method, which makes sense, but is it possible to chain the 3 predefined elements I stated above to access the input-container input-left-half round?
I read this: https://github.com/cheezy/page-object/wiki/Nested-Elements but didn't want to have to repeat any code if I can help it.
Assuming that the nesting is always the same, rather than having the :scar_first_name_error map through each ancestor, you could define each element with respect to its parent (or ancestor).
Let us assume the HTML is:
<html>
<body>
<div class="validate-method">
<div class="service-info">
<div class="input-group">
<div class="input-container input-left-half round">
text
</div>
</div>
</div>
</div>
</body>
</html>
You could define the page as:
class MyPage
include PageObject
div(:serv_info) { validate_method_element.div_element(:class => "service-info") }
div(:validate_method, :class => "validate-method")
div(:scar_input_group) { serv_info_element.div_element(:class => "input-group") }
div(:scar_first_name_error) { scar_input_group_element.div_element(:class => "input-container input-left-half round") }
end
Notice that the :serv_info is defined with respect to its parent :validate_method, :scar_input_group is defined with respect ot its parent :serv_info, etc.
With this page object, we can see we can get the lower element's text:
page = MyPage.new(browser)
p page.scar_first_name_error
#=> "text"
Related
I need to load different data in a div when I change a Select option, not only a simple string but I list/array/map of strings. My code:
View:
<div id="contextMenu">
<div id="testeDiv">${result}</div>
<form class="selecionarConta" name="formulario">
<g:select id="selecionaConta"
name="selecionarConta"
class="selectConta"
from="${menuDropDown}"
optionValue="string"
optionKey="value"
onchange="${remoteFunction(controller:'dashboard',action:'teste', update:'testeDiv', params:'\'varteste=\'+this.value')}"/>
</form>
</div>
Controller:
def teste() {
def result = [["Lemon":"${params.varteste}"],["Orange":"5"],["Grapefruit":"10"]]
[result:result]
}
Let's suppose I want to load an array or even an object in this div "testeDiv" and then any content I want, e.g. a list, or even a single element from this list e.g. <div id=testeDiv>${result[1]}</div>
I have a form that contains 3 div for addresses (Main Address, Secondary Address, Add Address).
Each div contains the following elements (text_field): Name, Address, Phone.
Depending on the scenario, I want to edit a text field for a specific div.
So I have this scenario:
Given I am at the form for editing addresses
When the div "main address" is visible
Then fill the "name" element for the "main address" div
And erase the "address" for the "main address" div
And the following steps definitions:
Then(/^fill the "([^"]+)" element for the "([^"]+)" div$/) do |element, div|
on_page(AddressPage).fill_element element div
end
And now the part I'm not sure about - the Address Page
class AddressPage
include PageObject
include DataMagic
div(:main_address, :id => 'main_address')
div(:secondary_address, :id => 'secondary_address')
div(:add_address, :id => 'add_address')
def fill_element(element, div)
current_div = self.send("#{div}_element")
# now the part I don't know how to do:
current_element = div.search_within(current_div, element)
fill_with_correct_data current_element
end
def search_within(div, element)
# How to do this?
end
end
How can I do this so that I don't have to define all the elements for all the divs (the number of div is dynamic)?
What I need to know is if there's a way to search an element to be inside another one.
Thanks :)
Edit The html will look something like this:
<div id='main_address'>
<input id='name'>
</input>
<input id='address'>
</input>
<input id='phone'>
</input>
</div>
<div id='secondary_address'>
<input id='name'>
</input>
<input id='address'>
</input>
<input id='phone'>
</input>
</div>
<div id='add_address'>
<input id='name'>
</input>
<input id='address'>
</input>
<input id='phone'>
</input>
</div>
Second Edit The point was to also declare this:
select_list(:name, :id => 'name')
select_list(:address, :id => 'address')
select_list(:phone, :id => 'phone')
#Is this possible?
current_name_element = main_address.name
#?
#Also, at the end the point is to call the Datamagic populate_page_with method
populate_page_with data_for 'new_user'
Where the 'default.yml" looks like this:
new_user:
name: ~first_name
address: ~address
phone: ~phone
Where I can choose which div this will be filled. Is this possible?
You can find an element within an element by using the element locators. These methods use the same type of locator you would use in the accessor methods.
For example, for a given current_div, you could get its related text fields:
current_element = current_div.text_field_element(:id => 'name')
current_element = current_div.text_field_element(:id => 'address')
current_element = current_div.text_field_element(:id => 'phone')
Note: You might need to change these locators. Presumably these ids are not exactly correct since ids should be unique on the page.
Applying the above to your page object, you could define the fill_element method as:
def fill_element(element, div)
current_div = self.send("#{div}_element")
current_element = current_div.text_field_element(:id => element)
# I assume this is a method you have already
fill_with_correct_data current_element
end
Calling the method would be like:
page.fill_element('name', 'main_address')
page.fill_element('address', 'secondary_address')
page.fill_element('phone', 'add_address')
Update - Created Scoped Locator:
This seems a bit messy, but is the best I can think of given the current implementation of the page object gem. I think there are some backlog feature requests for the gem that would make this cleaner.
What you could do is:
class AddressPage
include PageObject
class ScopedLocator
def initialize(name, page_object)
#name = name
#page = page_object
end
def populate_with(data)
scoped_data = Hash[data.map {|k, v| ["#{#name}_#{k}", v] }]
#page.populate_page_with scoped_data
end
def method_missing(m, *args)
#page.send("#{#name}_#{m}", *args)
end
end
['main_address', 'secondary_address', 'add_address'].each do |name|
define_method(name) { ScopedLocator.new(name, self) }
text_field("#{name}_name"){ div_element(:id => name).text_field_element(:id => 'name') }
text_field("#{name}_address"){ div_element(:id => name).text_field_element(:id => 'address') }
text_field("#{name}_phone"){ div_element(:id => name).text_field_element(:id => 'phone') }
end
end
This does 2 things:
It will create the normal accessors for each address. For example, it creates a :main_address_name, :main_address_address and :main_address_phone (and again for the other address types).
It creates a method, eg main_address that delegates actions to the normal accessors. This is to give the main_address.populate_with syntax.
With this page object, you could do the populate_with for a given address section:
page.main_address.populate_with data_for 'new_user'
page.secondary_address.populate_with data_for 'new_user'
page.add_address.populate_with data_for 'new_user'
As well as be able to update individual fields of an address. For example:
page.main_address.name = 'user'
I am having trouble clicking on a checkbox using the page-object-gem. The html code looks like this:
<label class='cc pointer value-apple'>
<input class='hidden' type='checkbox' value='apple'></input>
<div style="background-image:url(/images/fruit-apple-3453452346.jpg)"><div>
</label>
<label class='cc pointer value-banana'>
<input class='hidden' type='checkbox' value='banana'></input>
<div style="background-image:url(/images/fruit-banana-2359235674.jpg)"><div>
</label>
Using watir-webdriver I have no issues clicking on the label or div since the checkbox is hidden. Those work fine. However this does not seem to work using the page-object-gem. I have tried the following:
label(:select_fruit_apple, :class => /apple/)
on(FruitsPage).select_fruit_apple
div(:select_fruit_apple, :style => /apple/)
on(FruitsPage).select_fruit_apple
Any suggestions on how to do this is much appreciated.
Since Watir-Webdriver requires you to click the label or div, you will have to do the same with the page object.
The label and div accessor methods do not create methods to click the element. For the code you tried, when you call select_fruit_apple, it is just returning the text of the label or div element.
To click the label (or div), you will need to:
Get the label/div element. This can be done by either defining a method that uses a NestedElement or creating the label/div accessor and using the _element method.
Call the click method for the element. This will call Watir-Webdriver's click method.
Here is what the class would look like if you were using NestedElement directly:
class MyPage
include PageObject
def select_fruit_apple()
label_element(:class => 'value-apple').click
end
end
Alternatively, if you need the label/div element for multiple things, you might want to create an accessor for them:
class MyPage
include PageObject
label(:apple, :class => 'value-apple')
def select_fruit_apple()
apple_element.click
end
end
You can then call this method to set the checkbox:
on(FruitsPage).select_fruit_apple
Of course, if you want to save having to define a method for each fruit, you could use a method that takes a parameter:
class MyPage
include PageObject
def select_fruit(fruit)
label_element(:class => 'value-' + fruit).click
end
end
Then the method call would be:
on(FruitsPage).select_fruit('apple')
I'm having an issue of selecting the content I want to store as a string, as the data has the same div class name and there is no set ID to use instead.
I'm aware of the first and last operator in ruby, but is there anyway to select the options in between?
So for example
<html>
<body>
<div class="example">1111</div>
<div class="example">2222</div>
<div class="example">3333</div>
<div class="example">4444</div>
<div class="example">5555</div>
</body>
</html>
How can I get ruby to select the 4th class of the same class name, so I can store 4444 as my string?
Another way (using Watir API):
browser.div(:class => "example", :index => 3)
browser.divs(:class => "example")[3]
I have a number of custom EditorTemplates for various model classes. Inside these templates I obviously need to reference the properties of the model. My problem is that when I use the direct syntax of Model.Id (for example), the value is null. Another example is Model.Name which returns an empty string. However, when I reference the model in an expression (eg. #Html.TextBoxFor(i => i.Name)) then the values are there.
To further illustrate, here is a code snippet:
#model Vigilaris.Booking.Services.CompanyDTO
<div>
<fieldset class="editfieldset">
<legend class="titlelegend">Company Details</legend>
<ol>
<li>
#Html.TextBox("tb1", #Model.Id)
#Html.TextBox("tb2", #Model.Name)
</li>
<li>
#Html.LabelFor(i => i.CreatedDate)
#Html.DisplayFor(i => i.CreatedDate)
</li>
<li>
#Html.LabelFor(i => i.Name)
#Html.TextBoxFor(i => i.Name)
</li>
<li>
#Html.LabelFor(i => i.Description)
#Html.TextAreaFor(i => i.Description)
</li>
<li>
#Html.LabelFor(i => i.Phone)
#Html.TextBoxFor(i => i.Phone)
</li>
</ol>
</fieldset>
</div>
In this example, all the code that is using the LabelFor and DisplayFor helper functions displays the data from the model. However, the Html.TextBox code portion returns 0 for Model.Id and empty string for Name.
Why does it not access the actual model data when I reference Model directly?
I am unable to reproduce this. You might need to provide more context (controllers, views, ...). Also shouldn't your textbox be named like this:
#Html.TextBox("Id", Model.Id)
#Html.TextBox("Name", Model.Name)
and also why not using the strongly typed version directly:
#Html.TextBoxFor(x => x.Id)
#Html.TextBox(x => x.Name)
I managed to figure this one out. One thing I left out in my problem description was that I am using Telerik MVC Grid extension and the EditorTemplate is being using for In-form editing. So, the Model properties are not available at this point and this is understandable behaviour. I had to use a client side onEdit event on the Telerik MVC Grid and then set these values as necessary.
How I remember solving this is that I added a ClientEvent in my Telerik MVC Grid as follows:
.ClientEvents(events => events.OnEdit("Users_onEdit"))
This tells the grid to run my javascript function called Users_onEdit when an edit is triggered. Then, in my javascript function I find the field I want and then set its value. Here is an code excerpt:
function Users_onEdit(e) {
if (e.mode == "insert") {
$("#UserName").removeAttr("readonly");
$("#UserName").removeAttr("title");
$("#divNewUserMessage").show();
var formId = String(e.form.id);
var formIndex = formId.indexOf("form");
var companyId = formId.substr(6, formIndex -6);
var hiddenCompanyId = $(e.form).find("#CompanyId");
hiddenCompanyId.val(companyId);
}
}
I hope this helps others out there.