kookaburra 0.18.3 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -1
- data/README.markdown +114 -135
- data/VERSION +1 -1
- data/kookaburra.gemspec +8 -10
- data/lib/kookaburra.rb +22 -31
- data/lib/kookaburra/api_driver.rb +95 -11
- data/lib/kookaburra/given_driver.rb +32 -16
- data/lib/kookaburra/json_api_driver.rb +43 -70
- data/lib/kookaburra/{test_data.rb → mental_model.rb} +15 -9
- data/lib/kookaburra/null_browser.rb +4 -0
- data/lib/kookaburra/test_helpers.rb +2 -4
- data/lib/kookaburra/ui_driver.rb +15 -11
- data/lib/kookaburra/ui_driver/ui_component.rb +22 -5
- data/spec/integration/test_a_rack_application_spec.rb +171 -137
- data/spec/kookaburra/api_driver_spec.rb +126 -0
- data/spec/kookaburra/json_api_driver_spec.rb +85 -30
- data/spec/kookaburra/{test_data_spec.rb → mental_model_spec.rb} +6 -6
- data/spec/kookaburra/ui_driver_spec.rb +5 -3
- data/spec/kookaburra_spec.rb +9 -41
- metadata +9 -11
- data/lib/kookaburra/rack_driver.rb +0 -109
- data/lib/kookaburra/utils/active_record_shared_connection.rb +0 -14
- data/spec/kookaburra/rack_driver_spec.rb +0 -42
| @@ -1,6 +1,10 @@ | |
| 1 1 | 
             
            require 'basic_object'
         | 
| 2 2 |  | 
| 3 3 | 
             
            class Kookaburra
         | 
| 4 | 
            +
              # If you don't specify a browser in your {Kookaburra#configuration} but you
         | 
| 5 | 
            +
              # try to access one in your {Kookaburra::UIDriver}, you'll get this instead.
         | 
| 6 | 
            +
              # It gives a slightly better error message than complaining about calling
         | 
| 7 | 
            +
              # stuff on `nil`.
         | 
| 4 8 | 
             
              class NullBrowser < BasicObject
         | 
| 5 9 | 
             
                def method_missing(*args)
         | 
| 6 10 | 
             
                  raise NullBrowserError, 
         | 
| @@ -9,14 +9,13 @@ class Kookaburra | |
| 9 9 | 
             
              # @example RSpec setup
         | 
| 10 10 | 
             
              #   # in 'spec/support/kookaburra_setup.rb'
         | 
| 11 11 | 
             
              #   require 'kookaburra/test_helpers'
         | 
| 12 | 
            -
              #   require 'my_app/kookaburra/api_driver'
         | 
| 13 12 | 
             
              #   require 'my_app/kookaburra/given_driver'
         | 
| 14 13 | 
             
              #   require 'my_app/kookaburra/ui_driver'
         | 
| 15 14 | 
             
              #   
         | 
| 16 15 | 
             
              #   Kookaburra.configuration = {
         | 
| 17 16 | 
             
              #     :given_driver_class => MyApp::Kookaburra::GivenDriver,
         | 
| 18 | 
            -
              #     :api_driver_class => MyApp::Kookaburra::APIDriver,
         | 
| 19 17 | 
             
              #     :ui_driver_class => MyApp::Kookaburra::UIDriver,
         | 
| 18 | 
            +
              #     :app_host => 'http://my_app.example.com:12345',
         | 
| 20 19 | 
             
              #     :browser => Capybara,
         | 
| 21 20 | 
             
              #     :server_error_detection => lambda { |browser|
         | 
| 22 21 | 
             
              #       browser.has_css?('h1', text: 'Internal Server Error')
         | 
| @@ -47,14 +46,13 @@ class Kookaburra | |
| 47 46 | 
             
              # @example Cucumber setup
         | 
| 48 47 | 
             
              #   # in 'features/support/kookaburra_setup.rb'
         | 
| 49 48 | 
             
              #   require 'kookaburra/test_helpers'
         | 
| 50 | 
            -
              #   require 'my_app/kookaburra/api_driver'
         | 
| 51 49 | 
             
              #   require 'my_app/kookaburra/given_driver'
         | 
| 52 50 | 
             
              #   require 'my_app/kookaburra/ui_driver'
         | 
| 53 51 | 
             
              #   
         | 
| 54 52 | 
             
              #   Kookaburra.configuration = {
         | 
| 55 53 | 
             
              #     :given_driver_class => MyApp::Kookaburra::GivenDriver,
         | 
| 56 | 
            -
              #     :api_driver_class => MyApp::Kookaburra::APIDriver,
         | 
| 57 54 | 
             
              #     :ui_driver_class => MyApp::Kookaburra::UIDriver,
         | 
| 55 | 
            +
              #     :app_host => 'http://my_app.example.com:12345',
         | 
| 58 56 | 
             
              #     :browser => Capybara,
         | 
| 59 57 | 
             
              #     :server_error_detection => lambda { |browser|
         | 
| 60 58 | 
             
              #       browser.has_css?('h1', text: 'Internal Server Error')
         | 
    
        data/lib/kookaburra/ui_driver.rb
    CHANGED
    
    | @@ -4,9 +4,9 @@ require 'kookaburra/ui_driver/ui_component' | |
| 4 4 | 
             
            class Kookaburra
         | 
| 5 5 | 
             
              # You UIDriver subclass is where you define the DSL for testing your
         | 
| 6 6 | 
             
              # application via its user interface. Methods defined in your DSL should
         | 
| 7 | 
            -
              # represent business  | 
| 7 | 
            +
              # represent business actions rather than user interface manipulations. A
         | 
| 8 8 | 
             
              # good test of this is whether the names of your methods would need to change
         | 
| 9 | 
            -
              # significantly if the  | 
| 9 | 
            +
              # significantly if the application needed to be implemented in a vastly
         | 
| 10 10 | 
             
              # different manner (a text-only terminal app vs. a web app, for instance).
         | 
| 11 11 | 
             
              #
         | 
| 12 12 | 
             
              # @abstract Subclass and implement your UI testing DSL
         | 
| @@ -48,7 +48,8 @@ class Kookaburra | |
| 48 48 | 
             
                  #   this component.
         | 
| 49 49 | 
             
                  def ui_component(component_name, component_class)
         | 
| 50 50 | 
             
                    define_method(component_name) do
         | 
| 51 | 
            -
                      component_class.new(:browser => @browser, :server_error_detection => @server_error_detection | 
| 51 | 
            +
                      component_class.new(:browser => @browser, :server_error_detection => @server_error_detection,
         | 
| 52 | 
            +
                                          :app_host => @app_host)
         | 
| 52 53 | 
             
                    end
         | 
| 53 54 | 
             
                  end
         | 
| 54 55 | 
             
                end
         | 
| @@ -56,21 +57,24 @@ class Kookaburra | |
| 56 57 | 
             
                # It is unlikely that you would instantiate your UIDriver on your own; the
         | 
| 57 58 | 
             
                # object is configured for you when you call {Kookaburra#ui}.
         | 
| 58 59 | 
             
                #
         | 
| 59 | 
            -
                # @option options [ | 
| 60 | 
            -
                #   instance.
         | 
| 61 | 
            -
                # @option options [Kookaburra:: | 
| 62 | 
            -
                # @option options [ | 
| 63 | 
            -
                #    | 
| 60 | 
            +
                # @option options [Capybara::Session] :browser Most likely a
         | 
| 61 | 
            +
                #   `Capybara::Session` instance.
         | 
| 62 | 
            +
                # @option options [Kookaburra::MentalModel] :mental_model
         | 
| 63 | 
            +
                # @option options [String] :app_host The root URL of your running
         | 
| 64 | 
            +
                #   application (e.g. "http://my_app.example.com:12345")
         | 
| 65 | 
            +
                # @option options [Proc] :server_error_detection A lambda that is passed the
         | 
| 66 | 
            +
                #   `:browser` object and should return `true` if the page indicates a server
         | 
| 64 67 | 
             
                #   error has occured
         | 
| 65 68 | 
             
                def initialize(options = {})
         | 
| 66 69 | 
             
                  @browser = options[:browser]
         | 
| 67 | 
            -
                  @ | 
| 70 | 
            +
                  @app_host = options[:app_host]
         | 
| 71 | 
            +
                  @mental_model = options[:mental_model]
         | 
| 68 72 | 
             
                  @server_error_detection = options[:server_error_detection]
         | 
| 69 73 | 
             
                end
         | 
| 70 74 |  | 
| 71 75 | 
             
                protected
         | 
| 72 76 |  | 
| 73 | 
            -
                # @attribute [r]  | 
| 74 | 
            -
                dependency_accessor : | 
| 77 | 
            +
                # @attribute [r] mental_model
         | 
| 78 | 
            +
                dependency_accessor :mental_model
         | 
| 75 79 | 
             
              end
         | 
| 76 80 | 
             
            end
         | 
| @@ -70,11 +70,17 @@ class Kookaburra | |
| 70 70 | 
             
                  #
         | 
| 71 71 | 
             
                  # @see Kookaburra::UIDriver.ui_component
         | 
| 72 72 | 
             
                  #
         | 
| 73 | 
            -
                  # @option options [ | 
| 74 | 
            -
                  #    | 
| 75 | 
            -
                  # @option options [ | 
| 73 | 
            +
                  # @option options [Capybara::Session] :browser This is the browser driver
         | 
| 74 | 
            +
                  #   that allows you to interact with the web application's interface.
         | 
| 75 | 
            +
                  # @option options [String] :app_host The root URL of your running
         | 
| 76 | 
            +
                  #   application (e.g. "http://my_app.example.com:12345")
         | 
| 77 | 
            +
                  # @option options [Proc] :server_error_detection A proc that will receive
         | 
| 78 | 
            +
                  #   the object passed in to the :browser option as an argument and must
         | 
| 79 | 
            +
                  #   return `true` if the server responded with an unexpected error or
         | 
| 80 | 
            +
                  #   `false` if it did not.
         | 
| 76 81 | 
             
                  def initialize(options = {})
         | 
| 77 82 | 
             
                    @browser = options[:browser]
         | 
| 83 | 
            +
                    @app_host = options[:app_host]
         | 
| 78 84 | 
             
                    @server_error_detection = options[:server_error_detection]
         | 
| 79 85 | 
             
                  end
         | 
| 80 86 |  | 
| @@ -94,7 +100,8 @@ class Kookaburra | |
| 94 100 | 
             
                  end
         | 
| 95 101 |  | 
| 96 102 | 
             
                  # @private
         | 
| 97 | 
            -
                  #  | 
| 103 | 
            +
                  # (Not really private, but YARD seemingly lacks RDoc's :nodoc tag, and the
         | 
| 104 | 
            +
                  # semantics here don't differ from Object#respond_to?)
         | 
| 98 105 | 
             
                  def respond_to?(name)
         | 
| 99 106 | 
             
                    super || browser.respond_to?(name)
         | 
| 100 107 | 
             
                  end
         | 
| @@ -116,7 +123,7 @@ class Kookaburra | |
| 116 123 | 
             
                  #   to make it so.
         | 
| 117 124 | 
             
                  def show(*args)
         | 
| 118 125 | 
             
                    return if visible?
         | 
| 119 | 
            -
                    browser.visit  | 
| 126 | 
            +
                    browser.visit component_url(*args)
         | 
| 120 127 | 
             
                    assert visible?, "The #{self.class.name} component is not visible!"
         | 
| 121 128 | 
             
                  end
         | 
| 122 129 |  | 
| @@ -157,12 +164,22 @@ class Kookaburra | |
| 157 164 |  | 
| 158 165 | 
             
                  # @abstract
         | 
| 159 166 | 
             
                  # @return [String] the URL path that should be loaded in order to reach this component
         | 
| 167 | 
            +
                  # @raise [Kookaburra::ConfigurationError] raised if you haven't provided
         | 
| 168 | 
            +
                  #   an implementation
         | 
| 160 169 | 
             
                  def component_path
         | 
| 161 170 | 
             
                    raise ConfigurationError, "You must define #{self.class.name}#component_path."
         | 
| 162 171 | 
             
                  end
         | 
| 163 172 |  | 
| 173 | 
            +
                  # Returns the full URL by appending {#component_path} to the value of the
         | 
| 174 | 
            +
                  # :app_host option passed to {#initialize}.
         | 
| 175 | 
            +
                  def component_url(*args)
         | 
| 176 | 
            +
                    "#{@app_host}#{component_path(*args)}"
         | 
| 177 | 
            +
                  end
         | 
| 178 | 
            +
             | 
| 164 179 | 
             
                  # @abstract
         | 
| 165 180 | 
             
                  # @return [String] the CSS3 selector that will find the element in the DOM
         | 
| 181 | 
            +
                  # @raise [Kookaburra::ConfigurationError] raised if you haven't provided
         | 
| 182 | 
            +
                  #   an implementation
         | 
| 166 183 | 
             
                  def component_locator
         | 
| 167 184 | 
             
                    raise ConfigurationError, "You must define #{self.class.name}#component_locator."
         | 
| 168 185 | 
             
                  end
         | 
| @@ -1,135 +1,22 @@ | |
| 1 1 | 
             
            require 'kookaburra'
         | 
| 2 | 
            +
            require 'kookaburra/json_api_driver'
         | 
| 2 3 | 
             
            require 'capybara'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # These are required for the Rack app used for testing
         | 
| 3 6 | 
             
            require 'sinatra/base'
         | 
| 7 | 
            +
            require 'active_support/json'
         | 
| 4 8 | 
             
            require 'active_support/hash_with_indifferent_access'
         | 
| 5 9 |  | 
| 6 10 | 
             
            describe "testing a Rack application with Kookaburra" do
         | 
| 7 11 | 
             
              describe "with an HTML interface" do
         | 
| 8 12 | 
             
                describe "with a JSON API" do
         | 
| 9 | 
            -
                  require 'kookaburra/json_api_driver'
         | 
| 10 | 
            -
                  class MyAPIDriver < Kookaburra::JsonApiDriver
         | 
| 11 | 
            -
                    def create_user(user_data)
         | 
| 12 | 
            -
                      post '/users', user_data
         | 
| 13 | 
            -
                    end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                    def create_widget(widget_data)
         | 
| 16 | 
            -
                      post '/widgets', widget_data
         | 
| 17 | 
            -
                    end
         | 
| 18 | 
            -
                  end
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                  class MyGivenDriver < Kookaburra::GivenDriver
         | 
| 21 | 
            -
                    def a_user(name)
         | 
| 22 | 
            -
                      user = {'email' => 'bob@example.com', 'password' => '12345'}
         | 
| 23 | 
            -
                      result = api.create_user(user)
         | 
| 24 | 
            -
                      test_data.users[name] = result
         | 
| 25 | 
            -
                    end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                    def a_widget(name, attributes = {})
         | 
| 28 | 
            -
                      widget = {'name' => 'Foo'}.merge(attributes)
         | 
| 29 | 
            -
                      result = api.create_widget(widget)
         | 
| 30 | 
            -
                      test_data.widgets[name] = result
         | 
| 31 | 
            -
                    end
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                  class SignInScreen < Kookaburra::UIDriver::UIComponent
         | 
| 35 | 
            -
                    def component_path
         | 
| 36 | 
            -
                      '/session/new'
         | 
| 37 | 
            -
                    end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                    def component_locator
         | 
| 40 | 
            -
                      '#sign_in_screen'
         | 
| 41 | 
            -
                    end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                    def sign_in(user_data)
         | 
| 44 | 
            -
                      fill_in 'Email:', :with => user_data['email']
         | 
| 45 | 
            -
                      fill_in 'Password:', :with => user_data['password']
         | 
| 46 | 
            -
                      click_button 'Sign In'
         | 
| 47 | 
            -
                    end
         | 
| 48 | 
            -
                  end
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                  class WidgetList < Kookaburra::UIDriver::UIComponent
         | 
| 51 | 
            -
                    def component_path
         | 
| 52 | 
            -
                      '/widgets'
         | 
| 53 | 
            -
                    end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                    def component_locator
         | 
| 56 | 
            -
                      '#widget_list'
         | 
| 57 | 
            -
                    end
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                    def widgets
         | 
| 60 | 
            -
                      all('.widget_summary').map do |el|
         | 
| 61 | 
            -
                        extract_widget_data(el)
         | 
| 62 | 
            -
                      end
         | 
| 63 | 
            -
                    end
         | 
| 64 | 
            -
             | 
| 65 | 
            -
                    def last_widget_created
         | 
| 66 | 
            -
                      element = find('.last_widget.created')
         | 
| 67 | 
            -
                      extract_widget_data(element)
         | 
| 68 | 
            -
                    end
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                    def choose_to_create_new_widget
         | 
| 71 | 
            -
                      click_on 'New Widget'
         | 
| 72 | 
            -
                    end
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                    def choose_to_delete_widget(widget_data)
         | 
| 75 | 
            -
                      find("#delete_#{widget_data['id']}").click_button('Delete')
         | 
| 76 | 
            -
                    end
         | 
| 77 | 
            -
             | 
| 78 | 
            -
                    private
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                    def extract_widget_data(element)
         | 
| 81 | 
            -
                      {
         | 
| 82 | 
            -
                        'id' => element.find('.id').text,
         | 
| 83 | 
            -
                        'name' => element.find('.name').text
         | 
| 84 | 
            -
                      }
         | 
| 85 | 
            -
                    end
         | 
| 86 | 
            -
                  end
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                  class WidgetForm < Kookaburra::UIDriver::UIComponent
         | 
| 89 | 
            -
                    def component_locator
         | 
| 90 | 
            -
                      '#widget_form'
         | 
| 91 | 
            -
                    end
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                    def submit(widget_data)
         | 
| 94 | 
            -
                      fill_in 'Name:', :with => widget_data['name']
         | 
| 95 | 
            -
                      click_on 'Save'
         | 
| 96 | 
            -
                    end
         | 
| 97 | 
            -
                  end
         | 
| 98 | 
            -
             | 
| 99 | 
            -
                  class MyUIDriver < Kookaburra::UIDriver
         | 
| 100 | 
            -
                    ui_component :sign_in_screen, SignInScreen
         | 
| 101 | 
            -
                    ui_component :widget_list, WidgetList
         | 
| 102 | 
            -
                    ui_component :widget_form, WidgetForm
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                    def sign_in(name)
         | 
| 105 | 
            -
                      sign_in_screen.show
         | 
| 106 | 
            -
                      sign_in_screen.sign_in(test_data.users[name])
         | 
| 107 | 
            -
                    end
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                    def create_new_widget(name, attributes = {})
         | 
| 110 | 
            -
                      widget_list.show
         | 
| 111 | 
            -
                      widget_list.choose_to_create_new_widget
         | 
| 112 | 
            -
                      widget_form.submit('name' => 'My Widget')
         | 
| 113 | 
            -
                      test_data.widgets[name] = widget_list.last_widget_created
         | 
| 114 | 
            -
                    end
         | 
| 115 | 
            -
             | 
| 116 | 
            -
                    def delete_widget(name)
         | 
| 117 | 
            -
                      widget_list.show
         | 
| 118 | 
            -
                      widget_list.choose_to_delete_widget(test_data.widgets[name])
         | 
| 119 | 
            -
                    end
         | 
| 120 | 
            -
                  end
         | 
| 121 | 
            -
             | 
| 122 13 | 
             
                  # This is the fixture Rack application against which the integration
         | 
| 123 14 | 
             
                  # test will run. It uses class variables to persist data, because
         | 
| 124 15 | 
             
                  # Sinatra will instantiate a new instance of TestRackApp for each
         | 
| 125 16 | 
             
                  # request.
         | 
| 126 | 
            -
                  class  | 
| 17 | 
            +
                  class JsonApiApp < Sinatra::Base
         | 
| 127 18 | 
             
                    enable :sessions
         | 
| 128 | 
            -
             | 
| 129 | 
            -
                    # we want error handling to behave as it would for a production
         | 
| 130 | 
            -
                    # deployment rather than development
         | 
| 131 | 
            -
                    set :raise_errors, false
         | 
| 132 | 
            -
                    set :show_exceptions, false
         | 
| 19 | 
            +
                    disable :show_exceptions
         | 
| 133 20 |  | 
| 134 21 | 
             
                    def parse_json_req_body
         | 
| 135 22 | 
             
                      request.body.rewind
         | 
| @@ -241,20 +128,20 @@ describe "testing a Rack application with Kookaburra" do | |
| 241 128 | 
             
                        </head>
         | 
| 242 129 | 
             
                        <body>
         | 
| 243 130 | 
             
                          <div id="widget_list">
         | 
| 244 | 
            -
             | 
| 245 | 
            -
             | 
| 246 | 
            -
             | 
| 131 | 
            +
                      EOF
         | 
| 132 | 
            +
                      if last_widget_created
         | 
| 133 | 
            +
                        content << <<-EOF
         | 
| 247 134 | 
             
                              <div class="last_widget created">
         | 
| 248 135 | 
             
                                <span class="id">#{last_widget_created[:id]}</span>
         | 
| 249 136 | 
             
                                <span class="name">#{last_widget_created[:name]}</span>
         | 
| 250 137 | 
             
                              </div>
         | 
| 251 | 
            -
             | 
| 252 | 
            -
             | 
| 253 | 
            -
             | 
| 138 | 
            +
                        EOF
         | 
| 139 | 
            +
                      end
         | 
| 140 | 
            +
                      content << <<-EOF
         | 
| 254 141 | 
             
                            <ul>
         | 
| 255 | 
            -
             | 
| 256 | 
            -
             | 
| 257 | 
            -
             | 
| 142 | 
            +
                      EOF
         | 
| 143 | 
            +
                      @@widgets.each do |w|
         | 
| 144 | 
            +
                        content << <<-EOF
         | 
| 258 145 | 
             
                              <li class="widget_summary">
         | 
| 259 146 | 
             
                                <span class="id">#{w[:id]}</span>
         | 
| 260 147 | 
             
                                <span class="name">#{w[:name]}</span>
         | 
| @@ -262,9 +149,9 @@ describe "testing a Rack application with Kookaburra" do | |
| 262 149 | 
             
                                  <button type="submit" value="Delete" />
         | 
| 263 150 | 
             
                                </form>
         | 
| 264 151 | 
             
                              </li>
         | 
| 265 | 
            -
             | 
| 266 | 
            -
             | 
| 267 | 
            -
             | 
| 152 | 
            +
                        EOF
         | 
| 153 | 
            +
                      end
         | 
| 154 | 
            +
                      content << <<-EOF
         | 
| 268 155 | 
             
                            </ul>
         | 
| 269 156 | 
             
                            <a href="/widgets/new">New Widget</a>
         | 
| 270 157 | 
             
                          </div>
         | 
| @@ -273,22 +160,169 @@ describe "testing a Rack application with Kookaburra" do | |
| 273 160 | 
             
                      EOF
         | 
| 274 161 | 
             
                      body content
         | 
| 275 162 | 
             
                    end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                    error do
         | 
| 165 | 
            +
                      e = request.env['sinatra.error']
         | 
| 166 | 
            +
                      body << <<-EOF
         | 
| 167 | 
            +
                      <html>
         | 
| 168 | 
            +
                        <head>
         | 
| 169 | 
            +
                          <title>Internal Server Error</title>
         | 
| 170 | 
            +
                        </head>
         | 
| 171 | 
            +
                        <body>
         | 
| 172 | 
            +
                          <pre>
         | 
| 173 | 
            +
                      #{e.to_s}\n#{e.backtrace.join("\n")}
         | 
| 174 | 
            +
                          </pre>
         | 
| 175 | 
            +
                        </body>
         | 
| 176 | 
            +
                      </html>
         | 
| 177 | 
            +
                      EOF
         | 
| 178 | 
            +
                    end
         | 
| 276 179 | 
             
                  end
         | 
| 277 180 |  | 
| 181 | 
            +
                  class MyAPIDriver < Kookaburra::JsonApiDriver
         | 
| 182 | 
            +
                    def create_user(user_data)
         | 
| 183 | 
            +
                      post '/users', user_data
         | 
| 184 | 
            +
                    end
         | 
| 278 185 |  | 
| 279 | 
            -
             | 
| 280 | 
            -
             | 
| 186 | 
            +
                    def create_widget(widget_data)
         | 
| 187 | 
            +
                      post '/widgets', widget_data
         | 
| 188 | 
            +
                    end
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                  class MyGivenDriver < Kookaburra::GivenDriver
         | 
| 192 | 
            +
                    def api
         | 
| 193 | 
            +
                      MyAPIDriver.new(:app_host => initialization_options[:app_host])
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    def a_user(name)
         | 
| 197 | 
            +
                      user = {'email' => 'bob@example.com', 'password' => '12345'}
         | 
| 198 | 
            +
                      result = api.create_user(user)
         | 
| 199 | 
            +
                      mental_model.users[name] = result
         | 
| 200 | 
            +
                    end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                    def a_widget(name, attributes = {})
         | 
| 203 | 
            +
                      widget = {'name' => 'Foo'}.merge(attributes)
         | 
| 204 | 
            +
                      result = api.create_widget(widget)
         | 
| 205 | 
            +
                      mental_model.widgets[name] = result
         | 
| 206 | 
            +
                    end
         | 
| 207 | 
            +
                  end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                  class SignInScreen < Kookaburra::UIDriver::UIComponent
         | 
| 210 | 
            +
                    def component_path
         | 
| 211 | 
            +
                      '/session/new'
         | 
| 212 | 
            +
                    end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                    def component_locator
         | 
| 215 | 
            +
                      '#sign_in_screen'
         | 
| 216 | 
            +
                    end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                    def sign_in(user_data)
         | 
| 219 | 
            +
                      fill_in 'Email:', :with => user_data['email']
         | 
| 220 | 
            +
                      fill_in 'Password:', :with => user_data['password']
         | 
| 221 | 
            +
                      click_button 'Sign In'
         | 
| 222 | 
            +
                    end
         | 
| 223 | 
            +
                  end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                  class WidgetList < Kookaburra::UIDriver::UIComponent
         | 
| 226 | 
            +
                    def component_path
         | 
| 227 | 
            +
                      '/widgets'
         | 
| 228 | 
            +
                    end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                    def component_locator
         | 
| 231 | 
            +
                      '#widget_list'
         | 
| 232 | 
            +
                    end
         | 
| 281 233 |  | 
| 234 | 
            +
                    def widgets
         | 
| 235 | 
            +
                      all('.widget_summary').map do |el|
         | 
| 236 | 
            +
                        extract_widget_data(el)
         | 
| 237 | 
            +
                      end
         | 
| 238 | 
            +
                    end
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                    def last_widget_created
         | 
| 241 | 
            +
                      element = find('.last_widget.created')
         | 
| 242 | 
            +
                      extract_widget_data(element)
         | 
| 243 | 
            +
                    end
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                    def choose_to_create_new_widget
         | 
| 246 | 
            +
                      click_on 'New Widget'
         | 
| 247 | 
            +
                    end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                    def choose_to_delete_widget(widget_data)
         | 
| 250 | 
            +
                      find("#delete_#{widget_data['id']}").click_button('Delete')
         | 
| 251 | 
            +
                    end
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                    private
         | 
| 254 | 
            +
             | 
| 255 | 
            +
                    def extract_widget_data(element)
         | 
| 256 | 
            +
                      {
         | 
| 257 | 
            +
                        'id' => element.find('.id').text,
         | 
| 258 | 
            +
                        'name' => element.find('.name').text
         | 
| 259 | 
            +
                      }
         | 
| 260 | 
            +
                    end
         | 
| 261 | 
            +
                  end
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                  class WidgetForm < Kookaburra::UIDriver::UIComponent
         | 
| 264 | 
            +
                    def component_locator
         | 
| 265 | 
            +
                      '#widget_form'
         | 
| 266 | 
            +
                    end
         | 
| 267 | 
            +
             | 
| 268 | 
            +
                    def submit(widget_data)
         | 
| 269 | 
            +
                      fill_in 'Name:', :with => widget_data['name']
         | 
| 270 | 
            +
                      click_on 'Save'
         | 
| 271 | 
            +
                    end
         | 
| 272 | 
            +
                  end
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                  class MyUIDriver < Kookaburra::UIDriver
         | 
| 275 | 
            +
                    ui_component :sign_in_screen, SignInScreen
         | 
| 276 | 
            +
                    ui_component :widget_list, WidgetList
         | 
| 277 | 
            +
                    ui_component :widget_form, WidgetForm
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                    def sign_in(name)
         | 
| 280 | 
            +
                      sign_in_screen.show
         | 
| 281 | 
            +
                      sign_in_screen.sign_in(mental_model.users[name])
         | 
| 282 | 
            +
                    end
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                    def create_new_widget(name, attributes = {})
         | 
| 285 | 
            +
                      widget_list.show
         | 
| 286 | 
            +
                      widget_list.choose_to_create_new_widget
         | 
| 287 | 
            +
                      widget_form.submit('name' => 'My Widget')
         | 
| 288 | 
            +
                      mental_model.widgets[name] = widget_list.last_widget_created
         | 
| 289 | 
            +
                    end
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                    def delete_widget(name)
         | 
| 292 | 
            +
                      widget_list.show
         | 
| 293 | 
            +
                      widget_list.choose_to_delete_widget(mental_model.widgets[name])
         | 
| 294 | 
            +
                    end
         | 
| 295 | 
            +
                  end
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                  before(:all) do
         | 
| 298 | 
            +
                    @rack_server_port = 3339
         | 
| 299 | 
            +
                    @rack_server_pid = fork do
         | 
| 300 | 
            +
                      Rack::Server.start(
         | 
| 301 | 
            +
                        :app => JsonApiApp.new,
         | 
| 302 | 
            +
                        :server => :webrick,
         | 
| 303 | 
            +
                        :Host => '127.0.0.1',
         | 
| 304 | 
            +
                        :Port => @rack_server_port,
         | 
| 305 | 
            +
                        :environment => 'production'
         | 
| 306 | 
            +
                      )
         | 
| 307 | 
            +
                    end
         | 
| 308 | 
            +
                    sleep 1 # Give the server a chance to start up.
         | 
| 309 | 
            +
                  end
         | 
| 310 | 
            +
             | 
| 311 | 
            +
                  after(:all) do
         | 
| 312 | 
            +
                    Process.kill(9, @rack_server_pid)
         | 
| 313 | 
            +
                    Process.wait
         | 
| 314 | 
            +
                  end
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                  it "runs the tests against the app" do
         | 
| 282 317 | 
             
                    server_error_detection = lambda { |browser|
         | 
| 283 | 
            -
                      browser.has_css?(' | 
| 318 | 
            +
                      browser.has_css?('head title', :text => 'Internal Server Error')
         | 
| 284 319 | 
             
                    }
         | 
| 285 320 |  | 
| 286 321 | 
             
                    k = Kookaburra.new({
         | 
| 287 322 | 
             
                      :ui_driver_class        => MyUIDriver,
         | 
| 288 323 | 
             
                      :given_driver_class     => MyGivenDriver,
         | 
| 289 | 
            -
                      : | 
| 290 | 
            -
                      :browser                => Capybara::Session.new(: | 
| 291 | 
            -
                      :rack_app               => my_app,
         | 
| 324 | 
            +
                      :app_host               => 'http://127.0.0.1:%d' % @rack_server_port,
         | 
| 325 | 
            +
                      :browser                => Capybara::Session.new(:selenium),
         | 
| 292 326 | 
             
                      :server_error_detection => server_error_detection
         | 
| 293 327 | 
             
                    })
         | 
| 294 328 |  |