capybara-js_finders 0.2.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/.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