js2 0.0.1
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/Changelog +0 -0
- data/History.txt +4 -0
- data/LICENSE +0 -0
- data/Manifest.txt +43 -0
- data/PostInstall.txt +7 -0
- data/README +0 -0
- data/README.rdoc +48 -0
- data/Rakefile +42 -0
- data/bin/js2 +61 -0
- data/examples/js2.yml +8 -0
- data/examples/test.yml +5 -0
- data/lib/javascript/sel_marker.js2 +139 -0
- data/lib/javascript/test.js2 +73 -0
- data/lib/js2/ast/class_node.rb +105 -0
- data/lib/js2/ast/comment_node.rb +2 -0
- data/lib/js2/ast/haml_node.rb +22 -0
- data/lib/js2/ast/include_node.rb +11 -0
- data/lib/js2/ast/inherited_node.rb +7 -0
- data/lib/js2/ast/member_node.rb +18 -0
- data/lib/js2/ast/method_node.rb +29 -0
- data/lib/js2/ast/module_node.rb +6 -0
- data/lib/js2/ast/node.rb +14 -0
- data/lib/js2/ast/nodes.rb +123 -0
- data/lib/js2/ast/stuff_node.rb +6 -0
- data/lib/js2/config.rb +34 -0
- data/lib/js2/daemon.rb +35 -0
- data/lib/js2/decorator/app.rb +7 -0
- data/lib/js2/decorator/cleanser.rb +54 -0
- data/lib/js2/decorator/standard.rb +127 -0
- data/lib/js2/decorator/test.rb +148 -0
- data/lib/js2/parser/fast.rb +3959 -0
- data/lib/js2/parser/haml.rb +123 -0
- data/lib/js2/process/file_handler.rb +84 -0
- data/lib/js2/process/haml_engine.rb +19 -0
- data/lib/js2/process/universe.rb +57 -0
- data/lib/js2/processor.rb +150 -0
- data/lib/js2/test/selenium.rb +111 -0
- data/lib/js2/test/selenium_element.rb +220 -0
- data/lib/js2/test/selenium_helper.rb +27 -0
- data/lib/js2.rb +95 -0
- data/lib/tasks/js2.rake +9 -0
- data/meta/c_tokenizer.rl.erb +322 -0
- data/meta/replace.rb +126 -0
- metadata +110 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
require 'haml'
|
4
|
+
require 'json'
|
5
|
+
rescue Exception
|
6
|
+
puts "HAML is not supported"
|
7
|
+
end
|
8
|
+
|
9
|
+
class JS2::Parser::Haml
|
10
|
+
|
11
|
+
def initialize (haml_engine)
|
12
|
+
@haml_engine = haml_engine
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_out_js (file)
|
16
|
+
padding = nil
|
17
|
+
js = []
|
18
|
+
|
19
|
+
File.read(file).split(/\r?\n/).each do |line|
|
20
|
+
if m = line.match(/^(\s*):js2/)
|
21
|
+
padding = m[1]
|
22
|
+
elsif padding
|
23
|
+
if m = line.match(/^#{padding}/)
|
24
|
+
js << line
|
25
|
+
elsif ! line.match(/^\s*$/)
|
26
|
+
padding = nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
return js.join("\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse (file)
|
35
|
+
lines = File.read(file).split(/\r?\n/)
|
36
|
+
result = Hash.new
|
37
|
+
|
38
|
+
klass = Hash.new
|
39
|
+
key = nil
|
40
|
+
|
41
|
+
until lines.empty?
|
42
|
+
line = lines.shift()
|
43
|
+
|
44
|
+
if m = line.match(/^\.?([\w\.]+)/)
|
45
|
+
klass = Hash.new
|
46
|
+
result[m[1]] = klass
|
47
|
+
elsif m = line.match(/^ sass/)
|
48
|
+
key = 'sass'
|
49
|
+
klass[key] = [ [ ], '' ]
|
50
|
+
elsif m = line.match(/^ \.?([\w]+)(\(([^)]+)\))?/)
|
51
|
+
key = m[1]
|
52
|
+
klass[key] = [ [], m[3] ]
|
53
|
+
elsif key
|
54
|
+
if key == 'sass'
|
55
|
+
klass[key][0] << line.sub(/^ /, '')
|
56
|
+
else
|
57
|
+
klass[key][0] << line.sub(/^ /, '')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
result.keys.each do |class_name|
|
63
|
+
klass = result[class_name]
|
64
|
+
klass.each_pair do |key, array|
|
65
|
+
if key == 'sass'
|
66
|
+
array[0].unshift ".#{class_name.sub(/\.\w+$/, '').sub(/\./, '')}"
|
67
|
+
klass[key] = sassify(array[0].join("\n"))
|
68
|
+
else
|
69
|
+
klass[key] = functionize(array[0].join("\n"), array[1])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
return result
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def sassify (string)
|
80
|
+
begin
|
81
|
+
css = @haml_engine.sassify(string)
|
82
|
+
rescue
|
83
|
+
raise string
|
84
|
+
end
|
85
|
+
return css.gsub(/\n/, '').to_json
|
86
|
+
end
|
87
|
+
|
88
|
+
def functionize (string, params)
|
89
|
+
begin
|
90
|
+
html = @haml_engine.hamlize(string)
|
91
|
+
rescue
|
92
|
+
raise string
|
93
|
+
end
|
94
|
+
|
95
|
+
if params
|
96
|
+
return functionize_with_params(html, params)
|
97
|
+
end
|
98
|
+
|
99
|
+
counter = 0
|
100
|
+
segments = html.split(%r|#\w+#|).collect { |seg| seg.to_json }
|
101
|
+
ret = Array.new
|
102
|
+
segments.each_with_index do |seg, i|
|
103
|
+
ret.push(seg)
|
104
|
+
ret.push(%{arguments[#{i}]}) if (segments.length-1) > i
|
105
|
+
end
|
106
|
+
return %{function(){return #{ret.join('+')}}}
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def functionize_with_params (string, params)
|
111
|
+
args = []
|
112
|
+
string.gsub!(/#([^#]+)#/) do |m|
|
113
|
+
args << m.gsub(/#/, '')
|
114
|
+
'###'
|
115
|
+
end
|
116
|
+
|
117
|
+
segments = string.split(%r|###|).collect { |seg| seg.to_json }
|
118
|
+
ret = segments.zip(args).flatten.reject { |a| a.nil? }.join('+');
|
119
|
+
return %{function(#{params}){return #{ret}}}
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class JS2::Process::FileHandler
|
2
|
+
def initialize (params = {})
|
3
|
+
@read_dir = params[:read_dir]
|
4
|
+
@write_dir = params[:write_dir]
|
5
|
+
@haml_dir = params[:haml_dir]
|
6
|
+
@view_dir = params[:view_dir]
|
7
|
+
|
8
|
+
@mtimes = Hash.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def class_file
|
12
|
+
return @write_dir + '/classes.js'
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_view_files
|
16
|
+
return [] unless @view_dir
|
17
|
+
return Dir.glob(@view_dir + '/**/*.haml')
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_haml_files
|
21
|
+
return [] unless @haml_dir
|
22
|
+
return Dir.glob(@haml_dir + '/**/*/*.js2.haml') + Dir.glob(@haml_dir + '/*.js2.haml')
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_files
|
26
|
+
return Dir.glob(@read_dir + '/**/*/*.js2') + Dir.glob(@read_dir + '/*.js2')
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_file (filename, str)
|
30
|
+
to_write = filename.sub(/^#{@read_dir}/, @write_dir).sub(/\.js2$/, '.js')
|
31
|
+
FileUtils.mkpath(File.dirname(to_write))
|
32
|
+
File.open(to_write, 'w') do |f|
|
33
|
+
f << str
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_class_file (str)
|
38
|
+
FileUtils.mkpath(File.dirname(class_file))
|
39
|
+
File.open(class_file, 'w') do |f|
|
40
|
+
f << str
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def need_update!
|
45
|
+
ret = false
|
46
|
+
did = Hash.new
|
47
|
+
self.get_files.each do |file|
|
48
|
+
mtime = File.mtime(file)
|
49
|
+
if @mtimes[file] != mtime
|
50
|
+
@mtimes[file] = mtime
|
51
|
+
ret = true
|
52
|
+
end
|
53
|
+
did[file] = mtime
|
54
|
+
end
|
55
|
+
|
56
|
+
self.get_haml_files.each do |file|
|
57
|
+
mtime = File.mtime(file)
|
58
|
+
if @mtimes[file] != mtime
|
59
|
+
@mtimes[file] = mtime
|
60
|
+
ret = true
|
61
|
+
end
|
62
|
+
did[file] = mtime
|
63
|
+
end
|
64
|
+
|
65
|
+
self.get_view_files.each do |file|
|
66
|
+
mtime = File.mtime(file)
|
67
|
+
if @mtimes[file] != mtime
|
68
|
+
@mtimes[file] = mtime
|
69
|
+
ret = true
|
70
|
+
end
|
71
|
+
did[file] = mtime
|
72
|
+
end
|
73
|
+
|
74
|
+
@mtimes.keys.each do |file|
|
75
|
+
ret = true unless did[file]
|
76
|
+
end
|
77
|
+
|
78
|
+
@mtimes = did
|
79
|
+
return ret
|
80
|
+
rescue
|
81
|
+
return true
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
require 'haml'
|
4
|
+
require 'sass'
|
5
|
+
require 'json'
|
6
|
+
rescue Exception
|
7
|
+
puts "HAML is not supported"
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
class JS2::Process::HamlEngine
|
12
|
+
def hamlize (string)
|
13
|
+
return Haml::Engine.new(string, { :ugly => true }).render(self).gsub(/\n/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
def sassify (string)
|
17
|
+
return Sass::Engine.new(string, { :ugly => true }).render
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class JS2::Process::Universe
|
2
|
+
def initialize
|
3
|
+
@class_lookup = Hash.new
|
4
|
+
@haml_lookup = Hash.new
|
5
|
+
@node_containers = Array.new
|
6
|
+
@mod_lookup = Hash.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_nodes (nodes)
|
10
|
+
nodes.select { |n| n.is_a? JS2::AST::ClassNode }.each do |k|
|
11
|
+
@class_lookup[k.name] = k
|
12
|
+
end
|
13
|
+
|
14
|
+
nodes.select { |n| n.is_a? JS2::AST::ModuleNode }.each do |k|
|
15
|
+
@class_lookup[k.name] = k
|
16
|
+
end
|
17
|
+
|
18
|
+
@node_containers << nodes
|
19
|
+
end
|
20
|
+
|
21
|
+
def node_containers
|
22
|
+
return @node_containers
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_haml_hash (hash)
|
26
|
+
hash.each_pair do |name, haml_hash|
|
27
|
+
@haml_lookup[name] ||= JS2::AST::HamlNode.new
|
28
|
+
haml = @haml_lookup[name]
|
29
|
+
|
30
|
+
haml_hash.each_pair do |k,v|
|
31
|
+
haml[k] = v
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def finalize! ()
|
37
|
+
@class_lookup.values.each do |k|
|
38
|
+
# set parents
|
39
|
+
k.parent = @class_lookup[k.extends] if k.extends
|
40
|
+
included_mods = []
|
41
|
+
k.includes.each do |mod_node|
|
42
|
+
m = @class_lookup[mod_node.name]
|
43
|
+
included_mods << m if m
|
44
|
+
end
|
45
|
+
|
46
|
+
k.included_mods = included_mods
|
47
|
+
|
48
|
+
# set haml
|
49
|
+
k.haml = @haml_lookup[k.name] if @haml_lookup[k.name]
|
50
|
+
end
|
51
|
+
|
52
|
+
@class_lookup.values.each do |k|
|
53
|
+
k.finalize!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class JS2::Processor
|
4
|
+
attr_accessor :fh, :haml_engine, :decorator
|
5
|
+
|
6
|
+
# ----------------------------------------------------------------------------
|
7
|
+
# Rails support
|
8
|
+
# ----------------------------------------------------------------------------
|
9
|
+
def self.from_rails
|
10
|
+
hash = YAML.load_file("#{RAILS_ROOT}/config/js2.yml")[RAILS_ENV]
|
11
|
+
config = JS2::Config.new(hash)
|
12
|
+
return self.get(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.daemon_from_rails
|
16
|
+
processor = self.from_rails
|
17
|
+
return JS2::Daemon.new(processor)
|
18
|
+
end
|
19
|
+
|
20
|
+
# ----------------------------------------------------------------------------
|
21
|
+
# yaml support
|
22
|
+
# ----------------------------------------------------------------------------
|
23
|
+
def self.from_yaml(file)
|
24
|
+
config = JS2::Config.new(YAML.load_file(file))
|
25
|
+
return self.get(config)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.daemon_from_yaml(file)
|
29
|
+
p = self.from_yaml(file)
|
30
|
+
return JS2::Daemon.new(p)
|
31
|
+
end
|
32
|
+
|
33
|
+
# ----------------------------------------------------------------------------
|
34
|
+
# legacy
|
35
|
+
# ----------------------------------------------------------------------------
|
36
|
+
def self.from_file (file, env = nil)
|
37
|
+
hash = YAML.load_file(file)
|
38
|
+
hash = hash[env] if env
|
39
|
+
config = JS2::Config.new(hash)
|
40
|
+
return self.get(config)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.processor_from_file (file, env = nil)
|
44
|
+
return self.from_file(file, env)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.daemon_from_file (file, env = nil)
|
48
|
+
p = self.from_file(file, env)
|
49
|
+
return JS2::Daemon.new(p)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.from_hash (hash)
|
53
|
+
config = JS2::Config.new(hash)
|
54
|
+
return self.get(config)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.daemon_from_hash (hash)
|
58
|
+
p = self.from_hash(hash)
|
59
|
+
return p
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def self.get (config = nil)
|
64
|
+
# get config file
|
65
|
+
config ||= JS2::Config.new
|
66
|
+
|
67
|
+
# haml engine
|
68
|
+
haml_engine_class = config.haml_engine_class ?
|
69
|
+
config.haml_engine_class.constantize :
|
70
|
+
JS2::Process::HamlEngine
|
71
|
+
|
72
|
+
haml_engine = haml_engine_class.new
|
73
|
+
|
74
|
+
# file handler
|
75
|
+
js2_dir = config.js2_dir
|
76
|
+
write_dir = config.write_dir
|
77
|
+
js2_haml_dir = config.js2_haml_dir
|
78
|
+
haml_dir = config.haml_dir
|
79
|
+
|
80
|
+
file_handler = JS2::Process::FileHandler.new(
|
81
|
+
:read_dir => js2_dir,
|
82
|
+
:write_dir => write_dir,
|
83
|
+
:haml_dir => js2_haml_dir,
|
84
|
+
:view_dir => haml_dir
|
85
|
+
)
|
86
|
+
|
87
|
+
decorator_klass = config.test_mode ?
|
88
|
+
JS2::Decorator::Test :
|
89
|
+
JS2::Decorator::Standard
|
90
|
+
|
91
|
+
return self.new(
|
92
|
+
:test_mode => config.test_mode,
|
93
|
+
:reference_dir => config.reference_dir,
|
94
|
+
:haml_engine => haml_engine,
|
95
|
+
:file_handler => file_handler,
|
96
|
+
:decorator_klass => decorator_klass
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize (params)
|
101
|
+
@haml_engine = params[:haml_engine]
|
102
|
+
@fh = params[:file_handler]
|
103
|
+
@decorator_klass = params[:decorator_klass]
|
104
|
+
@reference_dir = params[:reference_dir]
|
105
|
+
@test_mode = params[:test_mode]
|
106
|
+
end
|
107
|
+
|
108
|
+
def write_files
|
109
|
+
universe = JS2::Process::Universe.new()
|
110
|
+
|
111
|
+
parser ||= JS2::Parser::Fast.new()
|
112
|
+
haml_parser ||= JS2::Parser::Haml.new(@haml_engine)
|
113
|
+
universe = JS2::Process::Universe.new
|
114
|
+
|
115
|
+
decorator = @decorator_klass.new
|
116
|
+
|
117
|
+
@fh.get_files.each do |filename|
|
118
|
+
nodes = parser.parse(filename)
|
119
|
+
universe.add_nodes(nodes)
|
120
|
+
end
|
121
|
+
|
122
|
+
@fh.get_haml_files.each do |filename|
|
123
|
+
hash = haml_parser.parse(filename)
|
124
|
+
universe.add_haml_hash(hash)
|
125
|
+
end
|
126
|
+
|
127
|
+
universe.finalize!
|
128
|
+
|
129
|
+
universe.node_containers.each do |nc|
|
130
|
+
str = decorator.draw_nodes(nc)
|
131
|
+
@fh.write_file(nc.filename, str)
|
132
|
+
end
|
133
|
+
|
134
|
+
str = decorator.draw_classes(universe.node_containers.flatten)
|
135
|
+
@fh.write_class_file(str)
|
136
|
+
|
137
|
+
if @test_mode
|
138
|
+
decorator.write_references(@reference_dir)
|
139
|
+
end
|
140
|
+
|
141
|
+
@fh.get_view_files.each do |filename|
|
142
|
+
js = haml_parser.parse_out_js(filename)
|
143
|
+
nodes = parser.parse_string(js)
|
144
|
+
decorator = @decorator_klass.new
|
145
|
+
decorator.draw_nodes(nodes)
|
146
|
+
decorator.write_references(@reference_dir)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
gem "selenium-client", ">=1.2.16"
|
4
|
+
require "selenium/client"
|
5
|
+
require "yaml"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
|
9
|
+
class JS2::Test::Selenium < Selenium::Client::Driver
|
10
|
+
attr_accessor :reference_dir, :child_selector
|
11
|
+
|
12
|
+
RESET_SEL_MARKER = "if (window.JS2) { window.USE_SEL_MARKER = window.JS2.SEL_MARKER; }"
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def logger
|
17
|
+
return @logger || nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def logger= (a_logger)
|
21
|
+
@logger = a_logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect (config_file, env = nil)
|
25
|
+
config = YAML.load_file(config_file)
|
26
|
+
config = config[env] if env
|
27
|
+
|
28
|
+
reference_dir = config['reference_dir'] || './spec/js2refs'
|
29
|
+
config = config['selenium']
|
30
|
+
|
31
|
+
ret = self.new( :host => config['host'] || 'localhost',
|
32
|
+
:port => config['port'] || 4444,
|
33
|
+
:browser => config['browser'] || '*firefox',
|
34
|
+
:url => config['url'] || 'http://google.com',
|
35
|
+
|
36
|
+
:timeout_in_second => config[:timeout] || 60,
|
37
|
+
:highlight_located_element => config['highlight'] || false,
|
38
|
+
:javascript_framework => config['framework'] || 'jquery' )
|
39
|
+
|
40
|
+
# hack to get an instance var in.
|
41
|
+
ret.reference_dir = reference_dir
|
42
|
+
ret.child_selector = nil
|
43
|
+
|
44
|
+
return ret
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_sel_markers ()
|
49
|
+
json = self.execute("JS2.SEL_MARKER.toJson()")
|
50
|
+
struct = JSON.parse(json)
|
51
|
+
puts YAML::dump(struct)
|
52
|
+
end
|
53
|
+
|
54
|
+
def open!(uri)
|
55
|
+
ret = self.open(uri)
|
56
|
+
self.wait_for_page
|
57
|
+
self.set_sel_marker!
|
58
|
+
return ret
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute (js)
|
62
|
+
puts js
|
63
|
+
return self.js_eval("window.eval(#{js.to_json});")
|
64
|
+
end
|
65
|
+
|
66
|
+
def uri
|
67
|
+
return self.location.sub(%r|^https?://(.*)?/|, '/')
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_helper (klass_name)
|
71
|
+
klass = klass_name
|
72
|
+
ref_file = %|#{@reference_dir}/#{klass_name}.yml|
|
73
|
+
lookup = if File.exist? ref_file
|
74
|
+
YAML.load_file(ref_file)
|
75
|
+
else
|
76
|
+
Hash.new
|
77
|
+
end
|
78
|
+
|
79
|
+
return JS2::Test::SeleniumHelper.new(klass_name, lookup, self)
|
80
|
+
end
|
81
|
+
|
82
|
+
def child_scope (selector)
|
83
|
+
js = "if (window.JS2) { window.USE_SEL_MARKER = window.JS2.SEL_MARKER."
|
84
|
+
js << selector.split('>').collect { |s| "children.#{s}" }.join('.')
|
85
|
+
js << '};'
|
86
|
+
@keep_selector = js
|
87
|
+
self.set_sel_marker!
|
88
|
+
yield()
|
89
|
+
@keep_selector = nil
|
90
|
+
self.set_sel_marker!
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_sel_marker!
|
94
|
+
self.execute(@keep_selector || RESET_SEL_MARKER)
|
95
|
+
end
|
96
|
+
|
97
|
+
def scope(klass_name)
|
98
|
+
self.set_sel_marker!
|
99
|
+
helper = get_helper(klass_name)
|
100
|
+
if block_given?
|
101
|
+
yield(helper)
|
102
|
+
else
|
103
|
+
return helper
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
rescue Exception => e
|
110
|
+
warn "No support for selenium"
|
111
|
+
end
|