From cde0453872a9f7da8482aa09e97a7748eddb9f19 Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Mon, 7 Dec 2009 20:19:00 +0100 Subject: [PATCH 1/8] added Andrea Fazzi to collaborators --- README.rdoc | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rdoc b/README.rdoc index 628855bb..5f6b06e4 100644 --- a/README.rdoc +++ b/README.rdoc @@ -208,6 +208,7 @@ The following people have dedicated their time and effort to Capybara: * Dennis Rogenius * Rob Holland * Wincent Colaiuta +* Andrea Fazzi == License: From 0fad236eedabe2f12346ea1cf23baca4c22fd057 Mon Sep 17 00:00:00 2001 From: Andrea Fazzi Date: Tue, 8 Dec 2009 19:29:36 +0100 Subject: [PATCH 2/8] Change Session#find accessibility from private to public. --- lib/capybara/dsl.rb | 2 +- lib/capybara/session.rb | 11 ++++++----- spec/session_spec.rb | 28 ++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/lib/capybara/dsl.rb b/lib/capybara/dsl.rb index ed0a2348..ef2c3343 100644 --- a/lib/capybara/dsl.rb +++ b/lib/capybara/dsl.rb @@ -51,7 +51,7 @@ module Capybara SESSION_METHODS = [ :visit, :body, :click_link, :click_button, :fill_in, :choose, :has_xpath?, :has_css?, :check, :uncheck, :attach_file, :select, :has_content?, :within, :within_fieldset, - :within_table, :save_and_open_page, :find_field, :find_link, :find_button, + :within_table, :save_and_open_page, :find, :find_field, :find_link, :find_button, :field_labeled ] SESSION_METHODS.each do |method| diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index 19235564..25dd91ae 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -127,6 +127,11 @@ module Capybara Capybara::SaveAndOpenPage.save_and_open_page(body) end + def find(locator) + locator = current_scope.to_s + locator + driver.find(locator) + end + def find_field(locator, *kinds) kinds = FIELDS_PATHS.keys if kinds.empty? field = find_field_by_id(locator, *kinds) || find_field_by_label(locator, *kinds) @@ -142,7 +147,7 @@ module Capybara end def find_button(locator) - button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}']").first \ + button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}' or @class='#{locator}']").first \ || find("//button[@id='#{locator}' or @value='#{locator}' or contains(.,'#{locator}')]").first raise Capybara::ElementNotFound, "no button with value or id or text '#{locator}' found" unless button button @@ -193,9 +198,5 @@ module Capybara return nil end - def find(locator) - locator = current_scope.to_s + locator - driver.find(locator) - end end end diff --git a/spec/session_spec.rb b/spec/session_spec.rb index 43054bfd..d61d5c8a 100644 --- a/spec/session_spec.rb +++ b/spec/session_spec.rb @@ -541,6 +541,34 @@ shared_examples_for "session" do end end + describe '#find' do + before do + @session.visit('/with_html') + end + + it "should find any element using the given locator" do + @session.find('//p').should have(3).elements + @session.find('//h1').first.text.should == 'This is a test' + @session.find("//input[@id='test_field']").first[:value].should == 'monkey' + end + + it "should return an empty array when nothing was found" do + @session.find('//div').empty?.should be_true + end + + context "within a scope" do + before do + @session.visit('/with_scope') + end + + it "should find any element using the given locator" do + @session.within(:xpath, "//div[@id='for_bar']") do + @session.find('//li').should have(2).elements + end + end + end + end + describe '#within' do before do @session.visit('/with_scope') From 947d4a10c99fa343866c0b7f025b781c1a0b0db6 Mon Sep 17 00:00:00 2001 From: Andrea Fazzi Date: Tue, 8 Dec 2009 19:45:54 +0100 Subject: [PATCH 3/8] Merge remote branch 'jnicklas/master' --- Manifest.txt | 4 ++ README.rdoc | 4 +- lib/capybara.rb | 7 ++- lib/capybara/driver/culerity_driver.rb | 6 ++ lib/capybara/driver/rack_test_driver.rb | 75 +++++++++++++------------ lib/capybara/driver/selenium_driver.rb | 4 ++ lib/capybara/dsl.rb | 4 +- lib/capybara/node.rb | 4 ++ lib/capybara/rails.rb | 4 ++ lib/capybara/session.rb | 40 ++++++------- spec/drivers_spec.rb | 10 ++++ spec/public/jquery-ui.js | 35 ++++++++++++ spec/session_spec.rb | 42 +++++++++++++- spec/test_app.rb | 8 ++- spec/views/form.erb | 23 +++++++- spec/views/with_js.erb | 14 +++++ 16 files changed, 218 insertions(+), 66 deletions(-) create mode 100755 spec/public/jquery-ui.js diff --git a/Manifest.txt b/Manifest.txt index 3f8fdbb8..24c5a862 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -27,13 +27,17 @@ spec/drivers_spec.rb spec/dsl_spec.rb spec/fixtures/test_file.txt spec/public/jquery.js +spec/save_and_open_page_spec.rb spec/session/culerity_session_spec.rb spec/session/rack_test_session_spec.rb spec/session/selenium_session_spec.rb spec/session_spec.rb spec/spec_helper.rb spec/test_app.rb +spec/views/buttons.erb +spec/views/fieldsets.erb spec/views/form.erb +spec/views/tables.erb spec/views/with_html.erb spec/views/with_js.erb spec/views/with_scope.erb diff --git a/README.rdoc b/README.rdoc index 865c3c45..5f6b06e4 100644 --- a/README.rdoc +++ b/README.rdoc @@ -153,7 +153,7 @@ For ultimate control, you can instantiate and use a session manually. end session.click_link 'Sign in' -== XPath an CSS +== XPath and CSS Capybara does not try to guess what kind of selector you are going to give it, if you want to use CSS with your 'within' declarations for example, you'll need @@ -207,6 +207,8 @@ The following people have dedicated their time and effort to Capybara: * Jonas Nicklas * Dennis Rogenius * Rob Holland +* Wincent Colaiuta +* Andrea Fazzi == License: diff --git a/lib/capybara.rb b/lib/capybara.rb index 1cc547a8..986ca7cf 100644 --- a/lib/capybara.rb +++ b/lib/capybara.rb @@ -1,7 +1,7 @@ require 'nokogiri' module Capybara - VERSION = '0.1.2' + VERSION = '0.1.3' class CapybaraError < StandardError; end class DriverNotFoundError < CapybaraError; end @@ -9,6 +9,11 @@ module Capybara class << self attr_accessor :debug, :asset_root + attr_writer :default_selector + + def default_selector + @default_selector ||= :xpath + end def log(message) puts "[capybara] #{message}" if debug diff --git a/lib/capybara/driver/culerity_driver.rb b/lib/capybara/driver/culerity_driver.rb index 646a3027..b0f3d4d4 100644 --- a/lib/capybara/driver/culerity_driver.rb +++ b/lib/capybara/driver/culerity_driver.rb @@ -27,6 +27,12 @@ class Capybara::Driver::Culerity node.click end + def drag_to(element) + node.fire_event('mousedown') + element.node.fire_event('mousemove') + element.node.fire_event('mouseup') + end + def tag_name # FIXME: this might be the dumbest way ever of getting the tag name # there has to be something better... diff --git a/lib/capybara/driver/rack_test_driver.rb b/lib/capybara/driver/rack_test_driver.rb index 17f64435..f937527a 100644 --- a/lib/capybara/driver/rack_test_driver.rb +++ b/lib/capybara/driver/rack_test_driver.rb @@ -1,5 +1,6 @@ require 'rack/test' require 'nokogiri' +require 'cgi' class Capybara::Driver::RackTest class Node < Capybara::Node @@ -59,50 +60,39 @@ class Capybara::Driver::RackTest class Form < Node def params(button) - params = [] - params += node.xpath(".//input[@type='text']", ".//input[@type='hidden']", ".//input[@type='password']").inject([]) do |agg, input| - agg << [input['name'].to_s, input['value'].to_s] - agg + params = {} + node.xpath(".//input[@type='text' or @type='hidden' or @type='password']").map do |input| + merge_param!(params, input['name'].to_s, input['value'].to_s) end - params += node.xpath(".//textarea").inject([]) do |agg, textarea| - agg << [textarea['name'].to_s, textarea.text.to_s] - agg + node.xpath(".//textarea").map do |textarea| + merge_param!(params, textarea['name'].to_s, textarea.text.to_s) end - params += node.xpath(".//input[@type='radio']").inject([]) do |agg, input| - agg << [input['name'].to_s, input['value'].to_s] if input['checked'] - agg + node.xpath(".//input[@type='radio' or @type='checkbox']").map do |input| + merge_param!(params, input['name'].to_s, input['value'].to_s) if input['checked'] end - params += node.xpath(".//input[@type='checkbox']").inject([]) do |agg, input| - agg << [input['name'].to_s, input['value'].to_s] if input['checked'] - agg - end - params += node.xpath(".//select").inject([]) do |agg, select| + node.xpath(".//select").map do |select| option = select.xpath(".//option[@selected]").first option ||= select.xpath('.//option').first - agg << [select['name'].to_s, (option['value'] || option.text).to_s] if option - agg + merge_param!(params, select['name'].to_s, (option['value'] || option.text).to_s) if option end - params += node.xpath(".//input[@type='file']").inject([]) do |agg, input| - if multipart? - agg << [input['name'].to_s, Rack::Test::UploadedFile.new(input['value'].to_s)] - else - agg << [input['name'].to_s, File.basename(input['value'].to_s)] + node.xpath(".//input[@type='file']").map do |input| + if input['value'].to_s.any? + if multipart? + merge_param!(params, input['name'].to_s, Rack::Test::UploadedFile.new(input['value'].to_s)) + else + merge_param!(params, input['name'].to_s, File.basename(input['value'].to_s)) + end end - agg - end - params.push [button[:name], button[:value]] if button[:name] - if multipart? - params.inject({}) { |agg, (key, value)| agg[key] = value; agg } - else - params.map { |key, value| "#{key}=#{value}" }.join('&') end + merge_param!(params, button[:name], button[:value]) if button[:name] + params end def submit(button) if post? - driver.submit(node['action'].to_s, params(button)) + driver.submit(node['action'].to_s, params(button)) else - driver.visit(node['action'].to_s.split('?').first + '?' + params(button)) + driver.visit(node['action'].to_s, params(button)) end end @@ -113,6 +103,21 @@ class Capybara::Driver::RackTest def post? self[:method] =~ /post/i end + + private + + def merge_param!(params, key, value) + collection = key.sub!(/\[\]$/, '') + if collection + if params[key] + params[key] << value + else + params[key] = [value] + end + else + params[key] = value + end + end end include ::Rack::Test::Methods @@ -125,8 +130,8 @@ class Capybara::Driver::RackTest @app = app end - def visit(path) - get(path) + def visit(path, attributes = {}) + get(path, attributes) follow_redirect! while response.redirect? cache_body end @@ -134,9 +139,9 @@ class Capybara::Driver::RackTest def submit(path, attributes) post(path, attributes) follow_redirect! while response.redirect? - cache_body + cache_body end - + def find(selector) html.xpath(selector).map { |node| Node.new(self, node) } end diff --git a/lib/capybara/driver/selenium_driver.rb b/lib/capybara/driver/selenium_driver.rb index 56d5b124..b5b8db5b 100644 --- a/lib/capybara/driver/selenium_driver.rb +++ b/lib/capybara/driver/selenium_driver.rb @@ -38,6 +38,10 @@ class Capybara::Driver::Selenium node.click end + def drag_to(element) + node.drag_and_drop_on(element.node) + end + def tag_name node.tag_name end diff --git a/lib/capybara/dsl.rb b/lib/capybara/dsl.rb index ef2c3343..9c0e510f 100644 --- a/lib/capybara/dsl.rb +++ b/lib/capybara/dsl.rb @@ -22,9 +22,7 @@ module Capybara end def current_session - session_pool["#{current_driver}#{app.object_id}"] ||= begin - Capybara::Session.new(current_driver, app) - end + session_pool["#{current_driver}#{app.object_id}"] ||= Capybara::Session.new(current_driver, app) end def current_session? diff --git a/lib/capybara/node.rb b/lib/capybara/node.rb index edaf6f47..01b62237 100644 --- a/lib/capybara/node.rb +++ b/lib/capybara/node.rb @@ -30,6 +30,10 @@ class Capybara::Node raise "Not implemented" end + def drag_to(element) + raise "Not implemented" + end + def tag_name raise "Not implemented" end diff --git a/lib/capybara/rails.rb b/lib/capybara/rails.rb index b3cea762..1f2f214f 100644 --- a/lib/capybara/rails.rb +++ b/lib/capybara/rails.rb @@ -1,6 +1,10 @@ require 'capybara' require 'capybara/dsl' +require 'database_cleaner' +require 'database_cleaner/cucumber' +DatabaseCleaner.strategy = :truncation + Capybara.app = Rack::Builder.new do map "/" do use Rails::Rack::Static diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index 25dd91ae..2760e3f8 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -1,13 +1,4 @@ module Capybara - - class << self - attr_writer :default_selector - - def default_selector - @default_selector ||= :xpath - end - end - class Session FIELDS_PATHS = { @@ -82,7 +73,7 @@ module Capybara end def has_content?(content) - has_xpath?("//*[contains(.,'#{content}')]") + has_xpath?("//*[contains(.,#{sanitized_xpath_string(content)})]") end def has_xpath?(path, options={}) @@ -179,24 +170,29 @@ module Capybara end def find_field_by_id(locator, *kinds) - kinds.each do |kind| - path = FIELDS_PATHS[kind] - element = find(path.call(locator)).first + field_locator = kinds.map { |kind| FIELDS_PATHS[kind].call(locator) }.join("|") + element = find(field_locator).first + return element + end + + def find_field_by_label(locator, *kinds) + label = find("//label[text()='#{locator}']").first || find("//label[contains(.,'#{locator}')]").first + if label + element = find_field_by_id(label[:for], *kinds) return element if element end return nil end - def find_field_by_label(locator, *kinds) - kinds.each do |kind| - label = find("//label[contains(.,'#{locator}')]").first - if label - element = find_field_by_id(label[:for], kind) - return element if element - end + def sanitized_xpath_string(string) + if string.include?("'") + string = string.split("'", -1).map do |substr| + "'#{substr}'" + end.join(%q{,"'",}) + "concat(#{string})" + else + "'#{string}'" end - return nil end - end end diff --git a/spec/drivers_spec.rb b/spec/drivers_spec.rb index 6a8b6e2e..064f5196 100644 --- a/spec/drivers_spec.rb +++ b/spec/drivers_spec.rb @@ -69,4 +69,14 @@ shared_examples_for "driver with javascript support" do @driver.find('//p').first.text.should == 'I changed it' end end + + describe '#drag_to' do + it "should drag and drop an object" do + @driver.visit('/with_js') + draggable = @driver.find('//div[@id="drag"]').first + droppable = @driver.find('//div[@id="drop"]').first + draggable.drag_to(droppable) + @driver.find('//div[contains(., "Dropped!")]').should_not be_nil + end + end end diff --git a/spec/public/jquery-ui.js b/spec/public/jquery-ui.js new file mode 100755 index 00000000..db64188f --- /dev/null +++ b/spec/public/jquery-ui.js @@ -0,0 +1,35 @@ +/* + * jQuery UI 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI + */ +jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.2",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/* + * jQuery UI Draggable 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Draggables + * + * Depends: + * ui.core.js + */ +(function(a){a.widget("ui.draggable",a.extend({},a.ui.mouse,{_init:function(){if(this.options.helper=="original"&&!(/^(?:r|a|f)/).test(this.element.css("position"))){this.element[0].style.position="relative"}(this.options.addClasses&&this.element.addClass("ui-draggable"));(this.options.disabled&&this.element.addClass("ui-draggable-disabled"));this._mouseInit()},destroy:function(){if(!this.element.data("draggable")){return}this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy()},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")){return false}this.handle=this._getHandle(b);if(!this.handle){return false}return true},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b);this._cacheHelperProportions();if(a.ui.ddmanager){a.ui.ddmanager.current=this}this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(b);this.originalPageX=b.pageX;this.originalPageY=b.pageY;if(c.cursorAt){this._adjustOffsetFromHelper(c.cursorAt)}if(c.containment){this._setContainment()}this._trigger("start",b);this._cacheHelperProportions();if(a.ui.ddmanager&&!c.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,b)}this.helper.addClass("ui-draggable-dragging");this._mouseDrag(b,true);return true},_mouseDrag:function(b,d){this.position=this._generatePosition(b);this.positionAbs=this._convertPositionTo("absolute");if(!d){var c=this._uiHash();this._trigger("drag",b,c);this.position=c.position}if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}if(a.ui.ddmanager){a.ui.ddmanager.drag(this,b)}return false},_mouseStop:function(c){var d=false;if(a.ui.ddmanager&&!this.options.dropBehaviour){d=a.ui.ddmanager.drop(this,c)}if(this.dropped){d=this.dropped;this.dropped=false}if((this.options.revert=="invalid"&&!d)||(this.options.revert=="valid"&&d)||this.options.revert===true||(a.isFunction(this.options.revert)&&this.options.revert.call(this.element,d))){var b=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){b._trigger("stop",c);b._clear()})}else{this._trigger("stop",c);this._clear()}return false},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==b.target){c=true}});return c},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c])):(d.helper=="clone"?this.element.clone():this.element);if(!b.parents("body").length){b.appendTo((d.appendTo=="parent"?this.element[0].parentNode:d.appendTo))}if(b[0]!=this.element[0]&&!(/(fixed|absolute)/).test(b.css("position"))){b.css("position","absolute")}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.element.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)&&e.containment.constructor!=Array){var c=a(e.containment)[0];if(!c){return}var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}else{if(e.containment.constructor==Array){this.containment=e.containment}}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.leftthis.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.topthis.containment[3])?g:(!(g-this.offset.click.topthis.containment[2])?f:(!(f-this.offset.click.left').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css(a(this).offset()).appendTo("body")})},stop:function(b,c){a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});a.ui.plugin.add("draggable","opacity",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("opacity")){e._opacity=b.css("opacity")}b.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._opacity){a(c.helper).css("opacity",d._opacity)}}});a.ui.plugin.add("draggable","scroll",{start:function(c,d){var b=a(this).data("draggable");if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){b.overflowOffset=b.scrollParent.offset()}},drag:function(d,e){var c=a(this).data("draggable"),f=c.options,b=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!f.axis||f.axis!="x"){if((c.overflowOffset.top+c.scrollParent[0].offsetHeight)-d.pageY=0;v--){var s=g.snapElements[v].left,n=s+g.snapElements[v].width,m=g.snapElements[v].top,A=m+g.snapElements[v].height;if(!((s-y=p&&n<=k)||(m>=p&&m<=k)||(nk))&&((e>=g&&e<=c)||(d>=g&&d<=c)||(ec));break;default:return false;break}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,g){var b=a.ui.ddmanager.droppables[e.options.scope];var f=g?g.type:null;var h=(e.currentItem||e.element).find(":data(droppable)").andSelf();droppablesLoop:for(var d=0;d 'Harry Jones') + @session.click_button('awesome') + extract_results(@session)['name'].should == 'Harry Jones' + end + it "should fill in a textarea by id" do @session.fill_in('form_description', :with => 'Texty text') @session.click_button('awesome') @@ -325,6 +343,21 @@ shared_examples_for "session" do @session.should_not have_content('xxxxyzzz') @session.should_not have_content('monkey') end + + it 'should handle single quotes in the content' do + @session.visit('/with-quotes') + @session.should have_content("can't") + end + + it 'should handle double quotes in the content' do + @session.visit('/with-quotes') + @session.should have_content(%q{"No," he said}) + end + + it 'should handle mixed single and double quotes in the content' do + @session.visit('/with-quotes') + @session.should have_content(%q{"you can't do that."}) + end end describe '#has_xpath?' do @@ -471,6 +504,10 @@ shared_examples_for "session" do @session.click_button('Upload') @session.body.should include(File.read(@test_file_path)) end + + it "should not break if no file is submitted" do + @session.click_button('Upload') + end end end @@ -602,13 +639,16 @@ shared_examples_for "session" do end context "with the default selector set to CSS" do + after do + Capybara.default_selector = :xpath + end + it "should use CSS" do Capybara.default_selector = :css @session.within("ul li[contains('With Simple HTML')]") do @session.click_link('Go') end @session.body.should include('

Bar

') - Capybara.default_selector = :xpath end end diff --git a/spec/test_app.rb b/spec/test_app.rb index 69249366..c8394f9c 100644 --- a/spec/test_app.rb +++ b/spec/test_app.rb @@ -24,7 +24,11 @@ class TestApp < Sinatra::Base get '/landed' do "You landed" end - + + get '/with-quotes' do + %q{"No," he said, "you can't do that."} + end + get '/form/get' do '
' + params[:form].to_yaml + '
' end @@ -46,7 +50,7 @@ class TestApp < Sinatra::Base end post '/upload' do - params[:form][:document][:tempfile].read + params[:form][:document][:tempfile].read rescue '' end end diff --git a/spec/views/form.erb b/spec/views/form.erb index 2c79dbfa..48e0e294 100644 --- a/spec/views/form.erb +++ b/spec/views/form.erb @@ -11,11 +11,27 @@

+

+ + +

+ +

+ + +

+

- + +

+ + + +

+

@@ -101,6 +117,11 @@

+

+ + +

+

diff --git a/spec/views/with_js.erb b/spec/views/with_js.erb index 8519326b..975e4ee4 100644 --- a/spec/views/with_js.erb +++ b/spec/views/with_js.erb @@ -3,10 +3,18 @@ with_js + @@ -16,5 +24,11 @@

FooBar

This is text

+
+

This is a draggable element.

+
+
+

It should be dropped here.

+
\ No newline at end of file From 679226ff1bfe9db776f781f3f68d08a45a75e8b3 Mon Sep 17 00:00:00 2001 From: Andrea Fazzi Date: Tue, 8 Dec 2009 23:23:04 +0100 Subject: [PATCH 4/8] Make #find specs more idiomatic --- spec/session_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/session_spec.rb b/spec/session_spec.rb index 9f3ecd8c..495deedc 100644 --- a/spec/session_spec.rb +++ b/spec/session_spec.rb @@ -590,7 +590,7 @@ shared_examples_for "session" do end it "should return an empty array when nothing was found" do - @session.find('//div').empty?.should be_true + @session.find('//div').should be_empty end context "within a scope" do From 6cc0b350f4c9aae67e60d878057d42b9e12170ba Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Wed, 9 Dec 2009 16:26:48 +0100 Subject: [PATCH 5/8] Don't find buttons by class. I'm assuming this was added accidentally --- lib/capybara/session.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index 81c988cd..54893314 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -138,7 +138,7 @@ module Capybara end def find_button(locator) - button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}' or @class='#{locator}']").first \ + button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}']").first \ || find("//button[@id='#{locator}' or @value='#{locator}' or contains(.,'#{locator}')]").first raise Capybara::ElementNotFound, "no button with value or id or text '#{locator}' found" unless button button From 2d4f8b0bee4484c2b3bc65a92b0527a3262e69db Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Wed, 9 Dec 2009 16:37:28 +0100 Subject: [PATCH 6/8] Return nil if no links or buttons found --- lib/capybara/session.rb | 18 +++++++++--------- spec/session_spec.rb | 12 ++++-------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index 54893314..33abbead 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -37,11 +37,15 @@ module Capybara end def click_link(locator) - find_link(locator).click + link = find_link(locator) + raise Capybara::ElementNotFound, "no link with title, id or text '#{locator}' found" unless link + link.click end def click_button(locator) - find_button(locator).click + button = find_button(locator) + raise Capybara::ElementNotFound, "no button with value or id or text '#{locator}' found" unless button + button.click end def fill_in(locator, options={}) @@ -132,16 +136,12 @@ module Capybara alias_method :field_labeled, :find_field def find_link(locator) - link = find("//a[@id='#{locator}' or contains(.,'#{locator}') or @title='#{locator}']").first - raise Capybara::ElementNotFound, "no link with title, id or text '#{locator}' found" unless link - link + find("//a[@id='#{locator}' or contains(.,'#{locator}') or @title='#{locator}']").first end def find_button(locator) - button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}']").first \ - || find("//button[@id='#{locator}' or @value='#{locator}' or contains(.,'#{locator}')]").first - raise Capybara::ElementNotFound, "no button with value or id or text '#{locator}' found" unless button - button + button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}']").first + button || find("//button[@id='#{locator}' or @value='#{locator}' or contains(.,'#{locator}')]").first end private diff --git a/spec/session_spec.rb b/spec/session_spec.rb index 495deedc..7c9e73d3 100644 --- a/spec/session_spec.rb +++ b/spec/session_spec.rb @@ -554,10 +554,8 @@ shared_examples_for "session" do @session.find_link('labore')[:href].should == "/with_simple_html" end - it "should raise an error if the field doesn't exist" do - running { - @session.find_link('Does not exist') - }.should raise_error(Capybara::ElementNotFound) + it "should return nil if the field doesn't exist" do + @session.find_link('Does not exist').should be_nil end end @@ -571,10 +569,8 @@ shared_examples_for "session" do @session.find_button('crap321').value.should == "crappy" end - it "should raise an error if the field doesn't exist" do - running { - @session.find_button('Does not exist') - }.should raise_error(Capybara::ElementNotFound) + it "should return nil if the field doesn't exist" do + @session.find_button('Does not exist').should be_nil end end From 43f65f073816c96957fddc5656933f3af4931937 Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Wed, 9 Dec 2009 16:52:30 +0100 Subject: [PATCH 7/8] Don't raise error when field not found --- lib/capybara/session.rb | 28 ++++++++++++++++++--------- spec/session_spec.rb | 43 +++++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index 33abbead..0d768cde 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -49,27 +49,39 @@ module Capybara end def fill_in(locator, options={}) - find_field(locator, :text_field, :text_area, :password_field).set(options[:with]) + field = find_field(locator, :text_field, :text_area, :password_field) + raise Capybara::ElementNotFound, "cannot fill in, no text field, text area or password field with id or label '#{locator}' found" unless field + field.set(options[:with]) end def choose(locator) - find_field(locator, :radio).set(true) + field = find_field(locator, :radio) + raise Capybara::ElementNotFound, "cannot choose field, no radio button with id or label '#{locator}' found" unless field + field.set(true) end def check(locator) - find_field(locator, :checkbox).set(true) + field = find_field(locator, :checkbox) + raise Capybara::ElementNotFound, "cannot check field, no checkbox with id or label '#{locator}' found" unless field + field.set(true) end def uncheck(locator) - find_field(locator, :checkbox).set(false) + field = find_field(locator, :checkbox) + raise Capybara::ElementNotFound, "cannot uncheck field, no checkbox with id or label '#{locator}' found" unless field + field.set(false) end def select(value, options={}) - find_field(options[:from], :select).select(value) + field = find_field(options[:from], :select) + raise Capybara::ElementNotFound, "cannot select option, no select box with id or label '#{options[:from]}' found" unless field + field.select(value) end def attach_file(locator, path) - find_field(locator, :file_field).set(path) + field = find_field(locator, :file_field) + raise Capybara::ElementNotFound, "cannot attach file, no file field with id or label '#{locator}' found" unless field + field.set(path) end def body @@ -129,9 +141,7 @@ module Capybara def find_field(locator, *kinds) kinds = FIELDS_PATHS.keys if kinds.empty? - field = find_field_by_id(locator, *kinds) || find_field_by_label(locator, *kinds) - raise Capybara::ElementNotFound, "no field of kind #{kinds.inspect} with id or label '#{locator}' found" unless field - field + find_field_by_id(locator, *kinds) || find_field_by_label(locator, *kinds) end alias_method :field_labeled, :find_field diff --git a/spec/session_spec.rb b/spec/session_spec.rb index 7c9e73d3..73ff908a 100644 --- a/spec/session_spec.rb +++ b/spec/session_spec.rb @@ -272,6 +272,12 @@ shared_examples_for "session" do @session.click_button('awesome') extract_results(@session)['gender'].should == 'both' end + + context "with a locator that doesn't exist" do + it "should raise an error" do + running { @session.choose('does not exist') }.should raise_error(Capybara::ElementNotFound) + end + end end describe "#check" do @@ -290,6 +296,12 @@ shared_examples_for "session" do @session.click_button('awesome') extract_results(@session)['pets'].should include('dog', 'cat', 'hamster') end + + context "with a locator that doesn't exist" do + it "should raise an error" do + running { @session.check('does not exist') }.should raise_error(Capybara::ElementNotFound) + end + end end describe "#uncheck" do @@ -310,6 +322,12 @@ shared_examples_for "session" do extract_results(@session)['pets'].should include('dog') extract_results(@session)['pets'].should_not include('hamster') end + + context "with a locator that doesn't exist" do + it "should raise an error" do + running { @session.uncheck('does not exist') }.should raise_error(Capybara::ElementNotFound) + end + end end describe "#select" do @@ -328,6 +346,12 @@ shared_examples_for "session" do @session.click_button('awesome') extract_results(@session)['locale'].should == 'fi' end + + context "with a locator that doesn't exist" do + it "should raise an error" do + running { @session.select('foo', :from => 'does not exist') }.should raise_error(Capybara::ElementNotFound) + end + end end describe '#has_content?' do @@ -509,7 +533,12 @@ shared_examples_for "session" do @session.click_button('Upload') end end - + + context "with a locator that doesn't exist" do + it "should raise an error" do + running { @session.attach_file('does not exist', 'foo.txt') }.should raise_error(Capybara::ElementNotFound) + end + end end describe '#find_field' do @@ -524,23 +553,17 @@ shared_examples_for "session" do end it "should raise an error if the field doesn't exist" do - running { - @session.find_field('Does not exist') - }.should raise_error(Capybara::ElementNotFound) + @session.find_field('Does not exist').should be_nil end it "should find only given kind of field" do @session.find_field('form_description', :text_field, :text_area).text.should == 'Descriptive text goes here' - running { - @session.find_field('form_description', :password_field) - }.should raise_error(Capybara::ElementNotFound) + @session.find_field('form_description', :password_field).should be_nil end it "should be aliased as 'field_labeled' for webrat compatibility" do @session.field_labeled('Dog').value.should == 'dog' - running { - @session.field_labeled('Does not exist') - }.should raise_error(Capybara::ElementNotFound) + @session.field_labeled('Does not exist').should be_nil end end From 30267737841da7e17977827352e7094ca5e526ad Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Wed, 9 Dec 2009 17:00:49 +0100 Subject: [PATCH 8/8] Add all method, make find return a single element --- lib/capybara/dsl.rb | 2 +- lib/capybara/session.rb | 20 ++++++++++++-------- spec/session_spec.rb | 41 ++++++++++++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/lib/capybara/dsl.rb b/lib/capybara/dsl.rb index 9c0e510f..b86a7b79 100644 --- a/lib/capybara/dsl.rb +++ b/lib/capybara/dsl.rb @@ -50,7 +50,7 @@ module Capybara :visit, :body, :click_link, :click_button, :fill_in, :choose, :has_xpath?, :has_css?, :check, :uncheck, :attach_file, :select, :has_content?, :within, :within_fieldset, :within_table, :save_and_open_page, :find, :find_field, :find_link, :find_button, - :field_labeled + :field_labeled, :all ] SESSION_METHODS.each do |method| class_eval <<-RUBY, __FILE__, __LINE__+1 diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index 0d768cde..967c4d53 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -93,7 +93,7 @@ module Capybara end def has_xpath?(path, options={}) - results = find(path) + results = all(path) if options[:text] results = filter_by_text(results, options[:text]) end @@ -111,7 +111,7 @@ module Capybara def within(kind, scope=nil) kind, scope = Capybara.default_selector, kind unless scope scope = css_to_xpath(scope) if kind == :css - raise Capybara::ElementNotFound, "scope '#{scope}' not found on page" if find(scope).empty? + raise Capybara::ElementNotFound, "scope '#{scope}' not found on page" unless find(scope) scopes.push(scope) yield scopes.pop @@ -134,10 +134,14 @@ module Capybara Capybara::SaveAndOpenPage.save_and_open_page(body) end - def find(locator) + def all(locator) locator = current_scope.to_s + locator driver.find(locator) end + + def find(locator) + all(locator).first + end def find_field(locator, *kinds) kinds = FIELDS_PATHS.keys if kinds.empty? @@ -146,12 +150,12 @@ module Capybara alias_method :field_labeled, :find_field def find_link(locator) - find("//a[@id='#{locator}' or contains(.,'#{locator}') or @title='#{locator}']").first + find("//a[@id='#{locator}' or contains(.,'#{locator}') or @title='#{locator}']") end def find_button(locator) - button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}']").first - button || find("//button[@id='#{locator}' or @value='#{locator}' or contains(.,'#{locator}')]").first + button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}']") + button || find("//button[@id='#{locator}' or @value='#{locator}' or contains(.,'#{locator}')]") end private @@ -181,12 +185,12 @@ module Capybara def find_field_by_id(locator, *kinds) field_locator = kinds.map { |kind| FIELDS_PATHS[kind].call(locator) }.join("|") - element = find(field_locator).first + element = find(field_locator) return element end def find_field_by_label(locator, *kinds) - label = find("//label[text()='#{locator}']").first || find("//label[contains(.,'#{locator}')]").first + label = find("//label[text()='#{locator}']") || find("//label[contains(.,'#{locator}')]") if label element = find_field_by_id(label[:for], *kinds) return element if element diff --git a/spec/session_spec.rb b/spec/session_spec.rb index 73ff908a..2b3d5910 100644 --- a/spec/session_spec.rb +++ b/spec/session_spec.rb @@ -597,19 +597,19 @@ shared_examples_for "session" do end end - describe '#find' do + describe '#all' do before do @session.visit('/with_html') end - it "should find any element using the given locator" do - @session.find('//p').should have(3).elements - @session.find('//h1').first.text.should == 'This is a test' - @session.find("//input[@id='test_field']").first[:value].should == 'monkey' + it "should find all elements using the given locator" do + @session.all('//p').should have(3).elements + @session.all('//h1').first.text.should == 'This is a test' + @session.all("//input[@id='test_field']").first[:value].should == 'monkey' end it "should return an empty array when nothing was found" do - @session.find('//div').should be_empty + @session.all('//div').should be_empty end context "within a scope" do @@ -619,7 +619,34 @@ shared_examples_for "session" do it "should find any element using the given locator" do @session.within(:xpath, "//div[@id='for_bar']") do - @session.find('//li').should have(2).elements + @session.all('//li').should have(2).elements + end + end + end + end + + describe '#find' do + before do + @session.visit('/with_html') + end + + it "should find the first element using the given locator" do + @session.find('//h1').text.should == 'This is a test' + @session.find("//input[@id='test_field']")[:value].should == 'monkey' + end + + it "should return nil when nothing was found" do + @session.find('//div').should be_nil + end + + context "within a scope" do + before do + @session.visit('/with_scope') + end + + it "should find the first element using the given locator" do + @session.within(:xpath, "//div[@id='for_bar']") do + @session.find('//li').text.should =~ /With Simple HTML/ end end end