capybara-js_finders 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ nbproject
6
+ README.test.html
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in capybara-js_finders.gemspec
4
+ gemspec
5
+
6
+ # gem 'bbq', :path => '/home/rupert/develop/bbq'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011 Robert Pankowecki, Gavdi Polska
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # Capybara JS Finders
2
+
3
+ Capybara JS Finders is a set of additional finders for capybara. Currently it only contains cell finder which allows
4
+ you to find a table cell based on column and row descriptions.
5
+
6
+ ## Installation
7
+
8
+ Simply add it to your Gemfile and bundle it up:
9
+
10
+ ```ruby
11
+ gem 'capybara-js_finders', '~> 0.0.1'
12
+ gem 'capybara'
13
+ ```
14
+
15
+ Make sure to add it before `capybara` in your Gemfile!
16
+
17
+ ## API
18
+
19
+ Use it like any other capybara finder.
20
+
21
+ ### find_cell
22
+
23
+ Allows you to find table cell (td, th) based on cell and row descriptions.
24
+ The method is colspan and rowspan attribute-aware which means it will be able to find
25
+ a cell even if it is under collspaned th containing a description.
26
+
27
+ #### Example
28
+
29
+ <table>
30
+ <tr>
31
+ <th>
32
+ User
33
+ </th>
34
+ <th>
35
+ Email
36
+ </th>
37
+ <th>
38
+ Permissions
39
+ </th>
40
+ </tr>
41
+
42
+ <tr>
43
+ <td>
44
+ John Smith
45
+ </td>
46
+ <td>
47
+ john@example.org
48
+ </td>
49
+ <td>
50
+ Admin
51
+ </td>
52
+ </tr>
53
+
54
+ <tr>
55
+ <td>
56
+ Andrew Bon
57
+ </td>
58
+ <td>
59
+ andrew@example.org
60
+ </td>
61
+ <td>
62
+ Moderator
63
+ </td>
64
+ </tr>
65
+
66
+ </table>
67
+
68
+ ```ruby
69
+ assert find_cell(:row => "John Smith", :column => "Permissions").has_content?("Admin")
70
+ assert find_cell(:row => "Andrew Bon", :column => "Email").has_no_content?("john")
71
+ ```
72
+
73
+ #### Example
74
+
75
+ ```ruby
76
+ assert find_cell(:row => "John Smith", :column => "January", :text => "28").has_text?("Present at work")
77
+ ```
78
+
79
+ #### Multicolumn and multirows support
80
+
81
+ If there are many rows and/or columns matching `:row` and/or `:column` parameter you can wider the search to include all of them
82
+ by using `:multirow` and/or `:multicolumn` action.
83
+
84
+ ##### Example
85
+
86
+ <table>
87
+ <tr>
88
+ <th>
89
+ User
90
+ </th>
91
+ <th>
92
+ Email
93
+ </th>
94
+ <th>
95
+ Permissions
96
+ </th>
97
+ </tr>
98
+
99
+ <tr>
100
+ <td>
101
+ John Smith
102
+ </td>
103
+ <td>
104
+ john@example.org
105
+ </td>
106
+ <td>
107
+ Admin
108
+ </td>
109
+ </tr>
110
+
111
+ <tr>
112
+ <td>
113
+ John Smith
114
+ </td>
115
+ <td>
116
+ smith@example.org
117
+ </td>
118
+ <td>
119
+ Moderator
120
+ </td>
121
+ </tr>
122
+
123
+ </table>
124
+
125
+ ```ruby
126
+ find_cell(:row => "John Smith", :column => "Permissions", :text => "Moderator") # raises an exception
127
+ find_cell(:multirow => true, :row => "John Smith", :column => "Permissions", :text => "Moderator") # will find the proper cell
128
+ ```
129
+
130
+ ## License
131
+
132
+ MIT License
133
+
134
+ ## Integration
135
+
136
+ Integrates nicely with bbq
137
+
138
+ ```ruby
139
+ user = Bbq::TestUser.new(:driver => :selenium, :session_name => :default)
140
+ user.visit '/page'
141
+ assert user.find_cell(:row => "RowDescription", :column => "ColumnDescription").has_content?("CellContent")
142
+ ```
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ Bundler::GemHelper.install_tasks
9
+ require 'rdoc/task'
10
+ require 'rake/testtask'
11
+ require 'rake'
12
+
13
+ RDoc::Task.new do |rdoc|
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = 'Capybara JS Finders'
16
+ rdoc.options << '--line-numbers' << '--inline-source'
17
+ rdoc.rdoc_files.include('README.rdoc')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'lib'
23
+ t.libs << 'test'
24
+ t.pattern = 'test/**/*_test.rb'
25
+ t.verbose = false
26
+ end
27
+
28
+
29
+ task :default => :test
30
+
31
+ task :script do
32
+ require "capybara-js_finders"
33
+ puts Capybara::JsFinders::SCRIPT
34
+ end
35
+
36
+ namespace :find_cell_tests do
37
+ task :app do
38
+ $LOAD_PATH << './test'
39
+ require 'unit/find_cell_tests/app/app'
40
+ FindCellTests::App.run!
41
+ end
42
+ end
43
+
44
+ task :readme do
45
+ #require 'redcloth'
46
+ `redcarpet README.md > README.test.html`
47
+ end
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "capybara-js_finders/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "capybara-js_finders"
7
+ s.version = Capybara::JsFinders::VERSION
8
+ s.authors = ["Robert Pankowecki"]
9
+ s.email = %w(robert.pankowecki@gmail.com rpa@gavdi.com)
10
+ s.homepage = "https://github.com/paneq/capybara-js_finders"
11
+ s.summary = %q{Additional finders for capybara that for some reason cannot use only xpath for finding nodes but needs to execute js for some calculations}
12
+ s.description = <<-DESC
13
+ Additional finders for capybara that for some reason
14
+ cannot use only xpath for finding nodes but needs to
15
+ execute js for some calculations.
16
+
17
+ Ex: I you want to find a table cell
18
+ that is under or next to other cell the easiest way to do it is to
19
+ check their position on page and compare them. This way you do not
20
+ need to wory about calculating the effects of using colspan
21
+ and rowspan attributes.
22
+
23
+ The downside is that you need to use capybara driver that runs
24
+ a browser like selenium.
25
+ DESC
26
+
27
+ s.rubyforge_project = "capybara-js_finders"
28
+
29
+ s.files = `git ls-files`.split("\n")
30
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
31
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
32
+ s.require_paths = ["lib"]
33
+
34
+ s.add_dependency "capybara", "~> 1.0.0"
35
+ s.add_development_dependency "rake", "~> 0.9.0"
36
+ s.add_development_dependency "rdoc", ">= 2.4.2"
37
+ s.add_development_dependency "sinatra", "~> 1.2.6"
38
+ s.add_development_dependency "erubis", "~> 2.6.6"
39
+ s.add_development_dependency "bbq", "~> 0.0.3"
40
+ s.add_development_dependency "redcarpet", "~> 1.17"
41
+ s.add_development_dependency "ruby-debug19"
42
+ end
@@ -0,0 +1,33 @@
1
+ require 'capybara/node/finders'
2
+
3
+ Capybara::Node::Finders.send(:include, Capybara::JsFinders)
4
+
5
+ require 'capybara/dsl'
6
+
7
+ Capybara::DSL.module_eval do
8
+ def execute_script(*args, &block)
9
+ page.execute_script(*args, &block)
10
+ end
11
+ def find_cell(*params, &block)
12
+ page.find_cell(*params, &block)
13
+ end
14
+ end
15
+
16
+ require 'capybara'
17
+
18
+ Capybara::Node::Base.module_eval do
19
+ def execute_script(script)
20
+ driver.execute_script(script)
21
+ end
22
+ def evaluate_script(script)
23
+ driver.evaluate_script(script)
24
+ end
25
+ end
26
+
27
+ Capybara::Session::NODE_METHODS << :find_cell
28
+ Capybara::Session::DSL_METHODS << :find_cell
29
+ Capybara::Session.class_eval do
30
+ def find_cell(*params, &block)
31
+ current_node.find_cell(*params, &block)
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module Capybara
2
+ module JsFinders
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ require 'xpath'
2
+
3
+ module XPath
4
+ class Expression
5
+ class Father < Multiple
6
+ def to_xpath(predicate=nil)
7
+ if @expressions.length == 1
8
+ "#{@left.to_xpath(predicate)}/../self::#{@expressions.first.to_xpath(predicate)}"
9
+ elsif @expressions.length > 1
10
+ "#{@left.to_xpath(predicate)}/../self::*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
11
+ else
12
+ "#{@left.to_xpath(predicate)}/../self::*"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def father(*expressions)
19
+ Expression::Father.new(current, expressions)
20
+ end
21
+ end
22
+
23
+ XPath::HTML.module_eval do
24
+ def cell(locator)
25
+ descendant(:td, :th)[attr(:id).equals(locator) | string.n.is(locator)]
26
+ end
27
+ end
@@ -0,0 +1,129 @@
1
+ require "capybara-js_finders/version"
2
+ require "capybara-js_finders/xpath_extensions"
3
+
4
+ module Capybara
5
+
6
+ module JsFinders
7
+
8
+ extend XPath
9
+
10
+ LR = "lr".freeze # Left range
11
+ RR = "rr".freeze # Right range
12
+ TR = "tr".freeze # Top range
13
+ BR = "br".freeze # Bottom range
14
+ XpathTrue = "(1=1)"
15
+
16
+ # TODO: This script is only prototype compatibile. Let it discover jquery or prototype and use proper methods.
17
+ SCRIPT = <<-JS
18
+ xpathResult = document.evaluate( "#{descendant(:th, :td)}", document, null, XPathResult.ANY_TYPE, null);
19
+ var ary = [];
20
+ var td;
21
+ while( td = xpathResult.iterateNext() ){
22
+ ary.push(td);
23
+ }
24
+ ary.each(function(ele){
25
+ var offset = ele.cumulativeOffset();
26
+ var lr = offset.left;
27
+ var rr = lr + ele.getWidth();
28
+ var tr = offset.top;
29
+ var br = tr + ele.getHeight();
30
+ ele.setAttribute('#{LR}', lr);
31
+ ele.setAttribute('#{RR}', rr);
32
+ ele.setAttribute('#{TR}', tr);
33
+ ele.setAttribute('#{BR}', br);
34
+ });
35
+ JS
36
+ SCRIPT.freeze
37
+
38
+ def self.overlaps_columns(columns)
39
+ return XpathTrue if columns.first == true
40
+ columns_xpath = columns.map{|column| overlaps_column(column) }.join(" or ")
41
+ columns_xpath = "( #{columns_xpath} )"
42
+ return columns_xpath
43
+ end
44
+
45
+ def self.overlaps_column(column)
46
+ overlaps_horizontal(column[LR], column[RR])
47
+ end
48
+
49
+ # clr, crr - Column left and right range
50
+ def self.overlaps_horizontal(clr, crr)
51
+ "(./@#{RR} >= #{clr} and ./@#{LR} <= #{crr})"
52
+ end
53
+
54
+ def self.overlaps_rows(rows)
55
+ return XpathTrue if rows.first == true
56
+ rows_xpath = rows.map{|row| overlaps_row(row) }.join(" or ")
57
+ rows_xpath = "( #{rows_xpath} )"
58
+ return rows_xpath
59
+ end
60
+
61
+ def self.overlaps_row(row)
62
+ overlaps_vertical(row[TR], row[BR])
63
+ end
64
+
65
+ # rtr, rbr - row top and bottom range
66
+ def self.overlaps_vertical(rtr, rbr)
67
+ "(./@#{BR} >= #{rtr} and ./@#{TR} <= #{rbr})"
68
+ end
69
+
70
+ # Condition for td to be under one of column and on the same level as one of the rows
71
+ def self.cell_condition(columns, rows)
72
+ xpath = "( #{overlaps_rows(rows)} ) and ( #{overlaps_columns(columns)} ) "
73
+ return xpath
74
+ end
75
+
76
+ # :column - text which should appear in the table column, or td/th Capybara::Node::Element object [or Array of them]
77
+ # representing the columns which narrow the search area of the table. Nil is allowed.
78
+ #
79
+ # :row - text which should appear in the row of the table, or td/th Capybara::Node::Element object [or Array of them]
80
+ # representing the columns which narrow the search area of the table. Nil is allowed.
81
+ # :text - text which should appear in the cell
82
+ # :multicolumn - set to true if finding column by test should return multiple results instead of just one
83
+ # :multirow - set to true if finding rows by test should return multiple results instead of just one
84
+ #
85
+ # :visible
86
+ # :with
87
+ def find_cell(options = {})
88
+ # TODO: Jak pierwszy arg to string to wywolaj cell zamiast tego
89
+
90
+ columns = case options[:column]
91
+ when String
92
+ method = options[:multicolumn] ? :all : :find
93
+ send(method, :xpath, XPath::HTML.cell(options[:column]) ) # TODO: jak all to musi byc conajmniej 1
94
+ when Capybara::Node::Element
95
+ options[:column]
96
+ when Array
97
+ options[:column].each{|x| raise ArgumentError unless x.is_a?(Capybara::Node::Element) }
98
+ when NilClass
99
+ true
100
+ else
101
+ raise ArgumentError
102
+ end
103
+ columns = Array.wrap(columns)
104
+
105
+ rows = case options[:row]
106
+ when String
107
+ method = options[:multirow] ? :all : :find
108
+ send(method, :xpath, XPath::HTML.cell(options[:row]) ) # TODO: jak all to musi byc conajmniej 1
109
+ when Capybara::Node::Element
110
+ options[:row]
111
+ when Array
112
+ options[:row].each{|x| raise ArgumentError unless x.is_a?(Capybara::Node::Element) }
113
+ when NilClass
114
+ true
115
+ else
116
+ raise ArgumentError
117
+ end
118
+ rows = Array.wrap(rows)
119
+
120
+ execute_script(SCRIPT)
121
+ xpath = JsFinders.cell_condition(columns, rows)
122
+ xpath = ".//td[ #{xpath} ]"
123
+ #puts xpath
124
+ find(:xpath, xpath, options)
125
+ end
126
+ end
127
+ end
128
+
129
+ require "capybara-js_finders/capybara_extensions"
@@ -0,0 +1,8 @@
1
+ require 'test/unit'
2
+
3
+ require 'sinatra'
4
+ require 'bbq/test'
5
+ require 'erubis'
6
+
7
+ require 'capybara-js_finders'
8
+ require 'capybara'
@@ -0,0 +1,13 @@
1
+ require 'sinatra'
2
+
3
+ module FindCellTests
4
+
5
+ class App < Sinatra::Base
6
+ set :views, File.join( File.expand_path( File.dirname(__FILE__) ), 'views' )
7
+ set :public, File.join( File.expand_path( File.dirname(__FILE__) ), 'public' )
8
+ get "/" do
9
+ erubis :table
10
+ end
11
+ end
12
+
13
+ end