Showing posts with label customink. Show all posts
Showing posts with label customink. Show all posts

Friday, November 9, 2007

Tutorial: Adding a Markup field to a Rails App

Recently we added a Textile-based text markup field to an internal webpage. We used the RedCloth library. It was very easy:

1. Install RedCloth
This is dead simple using gem:
# gem install RedCloth
Bulk updating Gem source index for: http://gems.rubyforge.org
Successfully installed RedCloth-3.0.4

2. Add a Database Column to Hold the Text
We'll make a Rails migration so that this change can be tracked:
$ cat db/migrate/053_add_constraints_to_styles.rb
class AddConstraintsToStyles < ActiveRecord::Migration
def self.up
add_column :styles, :constraints, :string
end

def self.down
remove_column :styles, :constraints
end
end

3. Add Test Data
This part is so that we can see if our view will work before adding the editing capability (which I'll show next time):
# ./script/console
Loading development environment.
>> p = Style.find("04600")
>> p.constraints
=> nil
>> p.constraints = "Test: _this_ string +is+ *textile*"
=> "Test: _this_ string +is+ *textile*"
>> p.save
=> true
>> exit

4. Modify the View
In the view I first add just
<%= @style.constraints %>
to make sure the model is pulling the right data. Looks good, so now go for the gold!
<%= RedCloth.new(@style.constraints).to_html %>

Friday, October 19, 2007

Distributing Selenium Tests

One of the nice things about using Selenium is that it runs in the browser, which allows you to test multiple browsers using the same test suite. The problem with doing this is that it is time consuming for any test suite of significant size. The best way to solve this problem is to run the test suite on multiple browsers in parallel. This greatly cuts down on the amount of time that it takes to run the tests while still giving you the same coverage.

Generally speaking there are two ways to parallelize the execution of Selenium test suites, one is to use the Selenium Remote Control and the other is to use the resultsUrl post back. The way that the Selenium Remote Control works is to allow the execution of Selenium commands on a remote server one command at a time. This involves installing a small server-type Java program on all of the machines that you want to remotely run Selenium commands on. Then authoring a program (or using one of the pre-baked ones) to connect to one of the remote machines and execute a Selenium test one command at a time, so the execution loop looks like this:
  1. Connect to remote server
  2. Load Selenium test file
  3. Pop command off of the top of the Selenium test file stack
  4. Send command to remote server
  5. Wait for response
  6. If fail, then fail
  7. else goto 2
The Selenium Remote Control server has nice hooks build into it that allow you to control almost any browser on most platforms, so it is straight forward to have one test be run on multiple machines using multiple browsers at once.

At CustomInk this was the first approach that was tried, executing one command at a time through custom Selenium test files. It is highly customizable because you are, more or less, developing everything from scratch. The Selenium Remote Control gives you a basic framework on which to develop a broader system but the actual execution and coordination of the tests is up to the client program.

After playing around with this approach it was not used at CustomInk for two main reasons. First, the fact that the Selenium Remote Control sends off one command at a time is a very fragile way to do things. If there are any timing problems on either the client or the server, or the client just gets bogged down for whatever reason, the tests will fail spectacularly. Second, developers needed to be able to use individual tests or sets of tests to test changes that they were making, much like unit tests are used. At CustomInk Selenium is not only used as part of QA but also as part of the development process to keep code stable and robust. The fact that running things locally and running things in the distributed environment end up working differently, due to both different test formats (one is html tables and the other is something else) and the fact that they execute so differently, one locally in browser and the other through the Remote Control, turned out to be a problem. Both because it was necessary to maintain different file formats and because things began to get hard to reproduce in both environments so the ability to use Selenium as a debugging tool was decreased.

The other approach is to use the resultsUrl mechanism to build a distributed method for executing Selenium tests on multiple machines over multiple browsers. The resultsUrl method is a technique by which a whole test suite is run to completion on a remote server and then the results of all of those tests are posted back to a central location. The advantage of doing this is that it is both more stable, since entire suites are running remotely rather than one command at a time so the communication coordination is much less between client and server and the fact that the same tests that developers are running locally in their browsers are being executed remotely in a browser. This leads to only a single test format and the ability to reproduce problems much more easily.

This technique is still built on the Selenium Remote Control, so there is a server running remotely and a client program that sends Selenium commands remotely to the server. The difference is the only command that the client sends to each server is to open and execute a whole test suite, using a specific browser, deployed on a staging server passing in a parameter called results resultsUrl. The resultsUrl parameter tells Selenium Core to post the results of the test suite execution back to that URL. An example of the URL that the Selenium Remote Control client would tell the server to open, in a specific browser, would be: http://staging/selenium/TestRunner.html?test=tests&auto=true&resultsUrl=http://seleniumhub?execution_number=53. The auto parameter tells Selenium Core to simply execute the test suite automatically and the resultsUrl tells Selenium Core to post the results back to that URL. So the execution loop would look like this:
  1. Connect to remote server
  2. Send command to open the URL http://staging/selenium/TestRunner.html?test=tests&auto=true&resultsUrl=http://seleniumhub?execution_number=53 in IE6
  3. Client code finishes
  4. On the server, IE6 opens and runs the test suite to the end
  5. The results of the tests are posted back to the URL specified through resultsUrl
This approach involves much less communication between the client and server portions of the Selenium Remote Control, meaning that there are less timing and network issues, but it also involves having the tests deployed on some centralized server as well as having some URL that can collect and display the results of individual tests. Here is a how the all of the pieces work together:

Since all of the code that Selenium is testing, including the test suite itself, is hosted on the Staging Box, reproducing problems is straight forward. Anyone can point their browser at the Staging Box and run the exact same test as the distributed test suite just ran.

The piece of code that resultsUrl posts back to can be very simple. Here at CustomInk we have a simple Rails project that just accepts results (along with the execution_number) and stores all the results with the same execution number to disk. It then allows people to go in and browse the results from the whole distributed test suite by execution number. CustomInk calls this the SeleniumHub. The downside of this is that it is code that you have to develop yourself, the upside is that it isn't very complex code.

CustomInk also integrated the execution of these tests with Cruise Control RB. This is straight forward to do but a script has to be written to kick of the tests and then return passed or failed based on the results that come back. The biggest problem with doing this is that a complete integration test suite built in Selenium normally takes to long to execute to be running in a continuous way. To get around this problem it is straight forward to create "Smoke Test" test suite that runs a subset of the whole suite and can run in a reasonable amount of time. Again, this is not difficult and allows our Continuous Integration Server to not only run our unit tests but also to run a

Overall this approach has proven to be robust and repeatable. The largest problems occur when individual tests having timing issues built into them, especially if they are testing AJAX applications, and tests fail intermittently. This is not a problem with this approach however, but rather a problem with the your tests.

Tags: , ,,
, , , , ,

Friday, October 5, 2007

Selenium on Rails for Java

In our previous blog posts we have gone over using Selenium on Rails to write custom DSLs to make in browser web based testing much easier. The largest complaint that we hear about this technique is that it uses Selenium on Rails and most people out there are not in an environment where they can use Ruby and/or Rails. The solution to this problem, at least for Java development houses, is to use JRuby and gold spike to make a Selenium on Rails installation that packages itself up as a war. This allows you to develop DSLs using the convenience of Ruby and Selenium on Rails but never having to actually install Ruby. It also allows you to deploy the test suite to your application server of choice. By doing this you also solve the cross-domain scripting problem that often plagues selenium test suites (the need for the tests and the application to run on the same domain).

In this post we attempt to go over how to get Selenium on Rails up and running in an Application Server in such a way that you can easily write your own custom DSLs. For the purposes of this example we are going to use a Red Hat Linux box and Apache Tomcat 6. More detail on troubleshooting a JRuby installation can be found here.

Getting Started

1. Install Jruby:
The easiest way to do this is with a direct download:

wget http://dist.codehaus.org/jruby/jruby-bin-1.0.1.tar.gz
Untar this wherever you want to install jruby, I am installing it in /home/apache/jruby so my examples will be using that.
2. Set your JRUBY_HOME and PATH variables to make jruby accessible everywhere:

export JRUBY_HOME=/home/apache/jruby/
export PATH=$JRUBY_HOME/bin:$PATH
3. Test your jruby installation
  • Create a file test_jruby.rb with the following contents: puts "I t-shirts"
  • Run your simple ruby program: jruby test_jruby.rb -> This should output I t-shirts.
4. Create a testing Ruby on Rails project
  • Install the gem for rails (this will take a few minutes):
    $JRUBY_HOME/bin/gem install rails -y --no-rdoc --no-ri
  • Now create a selenium-tests rails project:
    jruby $JRUBY_HOME/bin/rails selenium-tests 
    This should result in a directory and a whole bunch of files called selenium-tests being created
  • Next we want to make our rails project self contained, not dependent on having the rails gem installed. To do this do the following:
    cd selenium-tests
    rake rails:freeze:gems
    This will leave you with the rails gem installed in your rails project.
  • Finally install something called golden spike. This is what allows a rails project to be compiled into a war file. This is done by running the following command from your selenium-tests directory:

    jruby script/plugin install http://jruby-extras.rubyforge.org/svn/trunk/rails-integration/plugins/goldspike
    This will install the golden spike plugin into Rails.
Selenium on Rails

Now that Jruby in install and Ruby on Rails is running (without ruby actually be installed) we can get on with installing Selenium on Rails and then testing everything from within a Tomcat instance.

1. Installing Selenium on Rails
  • Selenium on Rails is nothing more than a Rails plugin, like golden spike. To install it we use the script/plugin command. To be specific, from your selenium-tests directory run the following:
    jruby script/plugin install http://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails
    When this command completes selenium-on-rails will be present in your vendors/plugin directory.
  • Finally generate a basic selenium test that you can use for double checking that everything is working. To do this use the selenium generator. From in your selenium-tests directory type the following:

    jruby script/generate selenium test
  • To test to make sure everything is working up to this point you can start the internal Ruby on Rails web server and see if you can connect to the default selenium suite. To do this type tho following in your selenium-tests directory:
    jruby script/server -e test
    Now the ruby web server is running on port 3000 so point your web browser at: http://localhost:3000/selenium. You should see a selenium test suite with your single test in it.
2. Running everything under Tomcat
  • The way that the golden spike plugin works is that will compile your entire project into a war. This war can then be put under an Apache Tomcat instance and have the tests run in the same domain (and same environment) as your normal web based java application. The first step is to install the Activerecord-jdbc gem. To do this type the following:

    $JRUBY_HOME/bin/gem install activerecord-jdbc --no-ri --no-rdoc
    This should cause the gem service to start and it to say that Activerecord-jdbc was successfully installed.
  • One of the ways that selenium-on-rails operates is that it only exists when the rails environment is set to test. Unfortunately golden spike (the war creator) does not support RAILS_ENV very well right now. The easiest way to do this is to edit line 77 of selenium-tests/vendor/plugins/goldenspike/lib/war_config.rb and change the @rails_env variable from "production" to "test". Hopefully golden spike will respect the RAILS_ENV variable a bit better in the future and this step will not be necessary.
  • Now it is time to build the war! To do this issue the following command from your selenium-tests directory:
    $JRUBY_HOME/bin/rake war:standalone:create 
    This will take a moment to run as it downloads the appropriate jars and builds them into a single war. For more information on this process, and the wide variety of options that can be passed in to optimize the process, look at JRuby Wiki. You should also notice that we are doing all of this in test mode, this is how selenium-on-rails prefers to operate, though it can be tweaked to run in any mode. The result of this command should be a selenium-test.war file.
  • Now lets test this war. To do this just copy the selenium-tests.war to the webapp directory of a running instance of Apache Tomcat. Then point your web browser at http://localhost:8080/selenium-tests/selenium. You should now see the basic selenium test suite running in selenium-on-rails in a Java application server.
Once a basic installation of selenium-on-rails is running inside Tomcat it is then possible to follow our previous blog posts and create domain specific languages (DSL) for testing or do any of the other nice tricks that Ruby + Selenium give you.

Tags: , ,,
, , , , , , ,

Monday, September 17, 2007

Getting started writing DSLs using Selenium on Rails

In previous posts we have discussed the reasons for using Domain Specific Languages (DSLs) to make implementation of Selenium tests much simpler and more maintainable. This post is going to talk about how to go about actually starting to write Selenium DSLs using Selenium Core and Selenium on Rails. This technique relies on the fact that either you are testing a rails application or you have a method for ensuring that the domain that your application to be tested is on the same domain as your rails app that will host Selenium on Rails (the way that we have done this is by using Apache and mod_proxy).

For this example we will use one CustomInks internal Vendor Management system as the demonstration. Exactly what is being tested is not as important here, this same set of steps should work for anything - just how you lay out your DSL commands will differ.

Step 1: Install Selenium on Rails

The first thing to do is to install selenium on rails. This is easily done:
script plugin/install \
http://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails
This will install both Selenium on Rails and Selenium-Core into your rails project. Next what we typically do (at least for the development stage of the DSL) is to make it so that Selenium will work in development mode as well as in testing mode. Normally Selenium on Rails will only function when rails is started in testing mode both to make sure that it was using the right database and to be sure that the tests were not accessible in production. This is accomplished by first copying the config.yml.example that comes with Selenium on Rails to just config.yml, so:
mv vendor/plugins/selenium-on-rails/config.yml.example \
vendor/plugins/selenium-on-rails/config.yml
Once this has been accomplished you then edit config.yml and uncomment the line under 'environments' that says development (line 9 in my version). So it would look like:
environments:
- test
- development
Now start webrick(or your rails server of choice) and point your browser to http://localhost:3000/selenium. You will see the basic Selenium startup page with no tests to write.

Step 2: Write a very simple test

Now lets write a simple test to make sure that things are working. We are going to author a RSel file, which is really nothing more than a ruby file with the Selenium API automatically loaded. Note: There are several additional ways to write tests in Selenium on Rails, using RSel is the way that we have found to be the most effective.

Selenium on Rails expects all of the RSel files to be in the directory: test/selenium. Selenium on Rails does not automatically create that directory so go ahead and create it:
mkdir test/selenium
Now create a new file in the test/selenium directory called yahoo.rsel. yahoo.rsel should contain one line:
open 'http://www.yahoo.com'
Save this file and point your browser back to http://localhost:3000/selenium. Now you should see one test called 'Yahoo'. Hit the Play button the right side and you should see the Yahoo home page load.

Step 3: Install basic infrastructure for your DSL

The simple Yahoo test case shows basically how Selenium on Rails works. The basic flow is as follows:
  1. Write RSel test case
  2. Load Test in Browser
    1. Browser makes request to Selenium on Rails for Test Case
    2. Selenium on Rails reads RSel file and compiles it to Selenium-Core table based structure
  3. Execute Selenium Test in Browser
    1. Javascript parses table
    2. Evaluates Command
    3. Loads www.yahoo.com
  4. Tests finish
The important thing to realize here is that the RSel files are compiled into a static HTML table before the test starts to execute. This means that dynamic conditions in tests are not possible with this method (look into using Selenium Remote Control if you are interested in doing this).

A DSL really is just a series of custom commands that emulate your business language and model your domain. In this case we are writing a DSL for testing but the actual task of writing tests in your own DSL will happen in the RSel files. The way that CustomInk built our DSL was to expose language commands using a Ruby module, then automatically mix that module into all of the Rsel files. This allows your Rsel files to look like this:
load_lab
bird_clipart = add_clipart_to_product( '26602' )
verify_location on product_of bird_clipart, 0, 0
click_on bird_clipart
click_button 'Center'
verify_centered bird_clipart
To do this we first need to create a module in our Rails project where we can put all of our commands. To do this first make a directory under your Rails lib folder called Selenium:
mkdir lib/selenium
Now create a dsl.rb in the lib/selenium directory. We normally start with a template for our DSLs that looks like this:

module Selenium
module DSL
module DSLCommands
# Insert your commands here
end
def method_missing( symbol, *args )
if( ( not @delegate) )
@delegate = Delegate.new( self)
end
args_for_print = (args.size == 1) ? args.first() : args.join(', ')
header "#{symbol.to_s.titleize} #{args_for_print.to_s}"
to_return = @delegate.method( symbol ).call( *args )
header "End #{symbol.to_s.titleize}: #{args_for_print.to_s}"
to_return
end
# Add a comment into the table
def comment text
@xml.comment! text
end
# Add a pretty header
def header text
command 'echo', text
end
class Delegate
include Selenium::DSL::DSLCommands
def initialize( parent_delegate )
@parent_delegate = parent_delegate
end
def method_missing( symbol, *args )
@parent_delegate.method( symbol ).call( *args )
end
def open( uri )
@parent_delegate.open(uri)
end
def type( id, text )
@parent_delegate.type(id,text)
end
end
end
end
This template does a couple of things. First it adds in two additional methods to the Selenium on Rails API, header and comment. These actually used to be methods in Selenium on Rails but aren't anymore (for whatever reason). Header inserts a header into the table (creating comments that can be seen) and comment inserts a HTML comment that can not bee seen. Both of these are used as debugging methods. This template also makes it so that every DSL command that you write will be generated with a header comment before and after all of the individual Selenium commands. This also allows for easier debugging.

Finally we need to modify Selenium or Rails to automatically include our DSL so that all of the tests we write have access to the new commands. To do this you need to monkey patch: vendor/plugins/selenium-on-rails/lib/selenium_on_rails/rselenese.rb. You need to make two edits. First, on the first line of rselenes.rb add:
require 'selenium/dsl'

Then around line 15, make it so that SeleniumOnRails::RSelenese includes Selenium::DSL. So the class declaration should now look like this:
class SeleniumOnRails::RSelenese
attr_accessor :view
include Selenium::DSL
Or approximately anyway. What this does is to make all of your DSL command top level Rsel commands that can be accessed as though they were built directly into Selenium.

Step 4: Write your first DSL commands

Now everything should be in place to write your first DSL command. A DSL command in this context is nothing more than a Ruby method that is designed to fit together with a set of other methods to make a coherent sub-language. At the end of the day your Rsel files are still Ruby (meaning that you get all of the built in Ruby language constructs) but you have developed your own set of commands (or keywords) that more closely model your business language. Remember that Selenium is all Javascript and in order to avoid cross-domain security problems with Javascript your Selenium suite and your application must be on the same domain. Hence the reason that the examples that follow do not test www.yahoo.com.

Lets start with something to load our application, since we are testing CustomInks internal vendor manager the command will be: load_vendor_manager. More broadly lets imagine that we want to test the fact that vendor capacity (the amount of work that a given vendor can be given) is set correctly. Before we even start writing DSL commands we imagine what the test should look like and then write the test with no implementation. This is an important step. Always remember that the goal of your DSL is to make test writing easy, not to make DSL implementation easy. So here is what we want our test to look like:
load_vendor_manager
select_group "East"
select_vendor "Digital"
verify_vendor_capacity "15"
The nice thing about this test is that it seems to be obvious about what it is doing, it works in the language of the business and introduces commands that should be useful in other tests. To start the actual implementation go back to lib/selenium/dsl.rb and find the comment that says: Insert your commands here (around line 4). This is where the Ruby code that implements the DSL belongs. Here is our implementation for load_vendor_manager:

def load_vendor_manager()
command 'setTimeout', '60000'
open '/backend/vendor/details'
wait_for_element_to_not_exist 'id=vendorLoader_c'
assert_element_not_present 'id=vendorLoader_c'
assert_element_present 'id=vendorGroups'
end

This is a simple command that makes first sets the timeout, opens the page, waits for the pre-loader to disappear and then verifies that the page loaded correctly. Notice how we directly refer to element id's in this command but not in the tests. This is important as it separates out the actual implementation of the application from the tests so that as the application changes the tests don't have to be modified, only the details of the DSL have to be modified. Now if we add another Rsel file under test/selenium with the command load_vendor_manager in it Selenium on Rails will execute the more finely grained Selenium commands provided in the implementation and load the vendor manager.

Notice that in this command we also verify that the vendor manager is in a steady state - we wait for the page to completely load and then assert that it is correct. This is important to do. Since the complexity is hidden behind by the DSL, the more asserts that are baked into the DSL the more confident you can be of the state of your application.

The actual code that we implemented for load_vendor_manager is not what is really important here though. What is important is that this provides a very simple framework that you can add an arbitrary amount of complexity to in order to make test writing ease. The resulting tests are straight forward and complexity is hidden in the DSL.

Hopefully this helps people get started writing DSLs and easy to maintain Selenium tests. Our next post will cover more techniques for the implementation of DSL commands in Selenium on Rails.


Tags: , ,,
, , , , , ,