js2 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|