I am using Capybara-Selenium in Ruby to run some automated tests and I want to add logging to the Capybara actions.
To do this I have overwritten the basic methods such as click_on and has_text?
These methods now look like this:
def has_text? text, options = {}
if page.has_text? text, options
#step.log :pass, "Looking for text \"#{text}\""
true
else
#step.log :fail, "Looking for text \"#{text}\""
false
end
end
def has_field? locator, options = {}
if page.has_field? locator, options
#step.log :pass, "Looking for field \"#{locator}\""
true
else
#step.log :fail, "Looking for field \"#{locator}\""
false
end
end
This works fine. However these methods are only useful when the web element being used has an ID. When there is no ID the element is found using the find method and then manipulated directly such as:
find(:css, 'some.locator').click
I want to add logging to these events as well. I could overwrite find and add generic logging, but that wont be very useful if I don't know what the element was used for after being found. I could add another parameter to my find method, but it would be clunky to write and read.
find(:css, 'some.locator', 'click').click
Before I succumb to that solution, I wanted to check if there was any easier or better implementation I could be using? I was thinking along the lines of some kind of sudo-class that replaced the object being returned by find, and had logging on all its methods, or even better if I could attach an event of some kind to various methods that would tell my logger when it needs to add something... I have no idea if these approaches are possible, and/or feasible.
Related
I'm configuring the Content Security Policy for our Rails 5.2 app. I need to whitelist some domains in our CSP. I'd like to put the list of domains elsewhere so I can reference them in other places in the application, then generate the CSP headers programmatically from that list.
Looking at the source code for the Content Security Policy configuration mechanisms in Rails 5, it looks like there's some magic metaprogramming going on, so it's not clear to me how to accomplish what I need to do. It looks like the functions I need to call to set headers might be picky about how exactly they want to be called. In particular, it's not clear to me if I can pass them arrays or safely call them multiple times, or if they do some metaprogramming magic that only works if the domains are passed in as individual function arguments.
Can I pass in an array to the header I want to set, like this?
whitelisted_domains = ['https://example.com', 'self']
Rails.application.configure do
config.content_security_policy do |csp|
csp.child_src whitelisted_domains
end
end
Or can I call the same function multiple times, like this?
whitelisted_domains = ['https://example.com', 'self']
Rails.application.configure do
config.content_security_policy do |csp|
whitelisted_domains.each {|domain| csp.child_src domain}
end
end
If neither of those will work, what's the best way of accomplishing what I want to do?
From what I can tell from sourcecode and documentation, it takes an array. From the edgeguides at rails, posting following
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
...
end
and the sourcecode, using *sources as param; it believe it takes any number of arguments, meaning you could do something along the lines of;
whitelisted_domains = ['https://example.com', 'self']
Rails.application.configure do
config.content_security_policy do |csp|
csp.child_src(*whitelisted_domains)
end
end
https://blog.sqreen.io/integrating-content-security-policy-into-your-rails-applications-4f883eed8f45/
https://edgeguides.rubyonrails.org/security.html#content-security-policy
Sourcecode of define_method for each directive
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/content_security_policy.rb#L151
(note: None of this has been tested in a Rails app, simple looking guides and source code of Rails)
I'm testing a nightmarish website that in most situations sticks all the important stuff in an iframe.
However, there are other common situations where the system will, annoyingly, open a page in a new tab, but not wrapped in the iframe.
I'm trying to figure out a conditional method that will check for the existence of the iframe and use it, otherwise not.
Here's what I've come up with, so far:
# The browser object...
#br = Watir::Browser.new
"frm" is the conditional method I'm trying to get working...
# Just an example element definition...
def click_my_button
#br.frm.button(id: "button").click
end
I define it in Watir's Container module, like so:
module Watir
module Container
def frm
if frame(id: "iframeportlet").exist?
frame(id: "iframeportlet")
else
# This is the part that I can't figure out.
end
end
end
end
That works fine when the iframe is there, but not surprisingly I get a NilClass error when it's not.
So, my question is: what can go into the else clause to make it work? More broadly, is there perhaps a better way to accomplish this? As you can imagine, I really want to avoid having to define every element in the web site twice.
I figured it out, and it's quite simple. The frm method's else clause just needs a "self"...
else
self
end
That's it. I'd love to know if there are any hidden pitfalls with this approach, though.
I have a page object called LineItemsPage
class LineItemsPage
attr_accessor :add_line_item_button
def initialize(test_env)
#browser = test_env[:browser]
#action_bar = #browser.div(:id => 'lineitems_win').div(:class => 'window-body').div(:class => 'actionbar')
#add_line_item_button = #action_bar.img(:class => 'button add')
end
def method_missing(sym, *args, &block)
#browser.send sym, *args, &block
end
end
I use it like so:
When /^I click on Add Item and enter the following values:$/ do |table|
#line_items_page = LineItemsPage.new(#test_env)
#line_items_page.add_line_item_button.when_present.click
end
I'm wondering if I should be abstracting the click, by adding something like the following to my LineItemsPage class:
def add_item
self.add_line_item_button.when_present.click
end
And then using it like so:
#line_items_page.add_item
I'm looking for best practices, either with regards to Page Object in particular or Ruby in general. I feel that encapsulating the interface by using add_item() is going a bit far, but I'm wondering if I'm unaware of issues I might run into down the road if I don't do that.
Personally, I try to make my page object methods be in the domain language with no reference to the implementation.
I used to do something like #line_items_page.add_line_item_button.when_present.click, however it has caused problems in the following scenarios:
1) The add line item was changed from a button to a link.
2) The process for adding a line item has changed - say its now done by a right-click or it has become a two step process (like open some dropdown and then click the add line).
In either case, you would have to locate all the places you add line items and update them. If you had all the logic in the add_item page object method, you would only have to update the one place.
From an implementation perspective, I have found that Cheezy's page object accessors work pretty well. However, for image buttons (or any of your app's custom controls), I would add additional methods to the PageObject::Accessors module. Or if they are one off controls, you can add the methods directly to the specific page object.
Update - Reply to Comment Regarding Some Starting Points:
I have not come across too much documentation, but here are a couple links that might help:
1) The Cheezy Page Object project wiki - Gives a simple example to get started
2) Cheezy's blog posts where the page object gem first started. Note that the content here might not be exactly how the gem is currently implemented, but I think it gives a good foundation to understanding what he is trying to achieve. This in turn makes it easier to understand what is happening when you have to open up and modify the gem to fit you needs.
I'm automating a site that has a page with a list of options selected by a radio button. When selecting one of the radios, a text field and a select list are presented.
I created a file (test_contracting.rb) that is the one through which I execute the test (ruby test_contracting.rb) and some other classes to represent my page.
On my class ContractPage, I have the following element declaration:
checkbox(:option_sub_domain, :id => "option_sub_domain")
text_field(:domain, :id => "domain_text")
select_list(:tld, :id => "domain_tld")
I've created in the ContractPage a method that sets the configuration of the domain like this:
def configure_domain(config={})
check_option_sub_domain
domain = config[:domain]
tld = config[:tld]
end
When I call the method configure_domain from the test_contracting.rb, it selects the radio button, but it doesn't fill the field with the values. The params are getting into the method correctly. I've checked it using "puts". Even if I change the params to a general string like "bla" it doesnt work. The annoying point is that if on test_contracting.rb I call the exact same components, it works.
my_page_instance = ContractPage.new(browser)
my_page_instance.domain = "bla"
my_page_instance.tld = ".com"
What I found to work was to in the configure_domain method, implement the following:
domain_element.value = config[:domain]
tld_element.send_keys config[:locaweb_domain]
Then it worked.
The documentation for the PageObjects module that I'm using as reference can be found here: http://rubydoc.info/github/cheezy/page-object/master/PageObject/Accessors#select_list-instance_method
Do you guys have any explation on why the method auto generated by the pageobject to set the value of the object didnt work in this scope/context ?
By the way, a friend tried the same thing with Java and it failed as well.
In ruby all equals methods (methods that end with the = sign) need to have a receiver. Let me show you some code that will demonstrate why. Here is the code that sets a local variable to a value:
domain = "blah"
and here is the code that calls the domain= method:
domain = "blah"
In order for ruby to know that you are calling a method instead of setting a local variable you need to add a receiver. Simply change your method above to this and it will work:
def configure_domain(config={})
check_option_sub_domain
self.domain = config[:domain]
self.tld = config[:tld]
end
I'm pretty new to this world of Selenium and page objects but maybe one of my very recent discoveries might help you.
I found that that assignment methods for the select_list fields only worked for me once I started using "self" in front. This is what I have used to access it within my page object code. e.g., self.my_select_list="my select list value"
Another note - The send_keys workaround you mention is clever and might do the trick for a number of uses, but in my case the select list values are variable and may have several options starting with the same letter.
I hope something in here is useful to you.
UPDATE (Jan 3/12)
On diving further into the actual Ruby code for the page object I discovered that the select_list set is also using send_keys, so in actuality I still have the same limitation here as the one I noted using the send_keys workaround directly. sigh So much to learn, so little time!
I've created a driver from wsdl
When I invoke my request, I would like the header to contain an element, i.e, I want to see something like the following:
REPLACE_WITH_ACTUAL
blah blah blah
However, looking around, everyone talks about subclassing SOAP::Header::SimpleHandler and then injecting an instance into the driver.headerhandler
However, if I do that, then I end up with a nested header, i.e,
REPLACE_WITH_ACTUAL
So there must be a way to just add an element to the existing headerhandler so I can do something like
driver.headerhandler.AddElement("session", "123")
but I can't find any way to do that. I also tried things like
driver.headerhandler["session"]="123" and other such tricks, but I can't find any way to make this work.
Looking at driver.headerhandler.methods, I cannot see any obvious mechanism.
Would really appreciate a pointer to how to to this.
Well, a colleague in my team solved the problem above after looking at some of the typical examples that I had previously found including the one at http://dev.ctor.org/soap4r/browser/trunk/sample/soapheader/authheader/client2.rb
Turns out that the trivial (sigh) solution is to replace
def on_simple_outbound
if #sessionid
{ "sessionid" => #sessionid }
end
end
with
def on_simple_outbound
if #sessionid
#sessionid
end
end
Now, if you just name the header "session" (in the QName creation), you get the unnested header, exactly what I wanted.
I thought I'd paste my solution to my own problem on the assumption that others might be running into the same problem.