sir 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c2411c7dd813a1cccfe7b38b331086ff52c5fd7b
4
+ data.tar.gz: 1f68a77c0eafb6de9d0736c096702db61bc1653e
5
+ SHA512:
6
+ metadata.gz: c43839ae12e2ebaa0d35457478b1b2501cc8d215efeae7d19313091eff26bbef61344cedb811a6a897cc2d061e4faccf22ab097902748721863ae36ee1385af9
7
+ data.tar.gz: 92a7868f66d2a04dd7efc481420715af3ad0ca82037909c3dcf55b19572976f4220fe3739c1b377981d3cd3effb377b092dedd3d862414c1905aec95ecb3723c
@@ -0,0 +1,20 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+
16
+ *.swp
17
+ *.swo
18
+ *~
19
+ *.png
20
+ *.html
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sir.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Sergey Smagin
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.
@@ -0,0 +1,33 @@
1
+ # Sir
2
+
3
+ Sir is Selenium InterpreteR. It allows to run your selenium tests without firefox => right on server. And it's pretty cool.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'sir'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install sir
20
+
21
+ ## Usage
22
+
23
+ You can either run executable file or include as library.
24
+
25
+ To run executable:
26
+
27
+ ./bin/sir path/to/file path/to/initial_state
28
+
29
+ To use it inside your code:
30
+
31
+ Sir.run!(path_to_file, initial_state)
32
+ #=> run commands, return hash with that commands
33
+
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/sir ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/sir'
4
+ require 'pry-byebug'
5
+
6
+ file_path, initial_state_path = *ARGV
7
+ initial_state = YAML.load_file(initial_state_path) if initial_state_path
8
+
9
+ pp Sir.run!(file_path, initial_state || {})
@@ -0,0 +1,38 @@
1
+ require 'yaml'
2
+ require 'nokogiri'
3
+ require 'execjs'
4
+ require 'capybara/poltergeist'
5
+ require_relative 'sir/capybara_ext'
6
+ require 'pp'
7
+
8
+ Capybara.register_driver :poltergeist do |app|
9
+ options = { timeout: 180,
10
+ js_errors: false,
11
+ phantomjs_options: ['--ignore-ssl-errors=yes'],
12
+ window_size: [1680, 1050] }
13
+ Capybara::Poltergeist::Driver.new(app, options)
14
+ end
15
+ Capybara.default_driver = :poltergeist
16
+
17
+ module Sir
18
+ class << self
19
+ attr_accessor :state
20
+ end
21
+ end
22
+
23
+ require_relative 'sir/version'
24
+ require_relative 'sir/parser'
25
+ require_relative 'sir/wrapper'
26
+
27
+ module Sir
28
+ def self.run!(path, initial_state = {})
29
+ parser = Sir::Parser.new(path, initial_state)
30
+ result = parser.run
31
+ destroy_session parser.wrapper.session rescue nil
32
+ result
33
+ end
34
+
35
+ def self.destroy_session(session)
36
+ session.driver.quit
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ class Capybara::Poltergeist::Browser
2
+ def find(method, selector)
3
+ result = command('find', method, selector)
4
+
5
+ # FIXME workaround for this bug https://github.com/teampoltergeist/poltergeist/issues/482
6
+ if result == true
7
+ result = command('find', method, selector)
8
+ end
9
+
10
+ result['ids'].map { |id| [result['page_id'], id] }
11
+ end
12
+ end
@@ -0,0 +1,185 @@
1
+ # Sir::Parser is class that parse html and generate commands that may be sent
2
+ # into Capybara.
3
+ #
4
+ # @example
5
+ # Sir::Parser.new('path/to/file').parse!
6
+ # #=> [ { command: 'select', args: [ { id: 'ccid', label: /expire/ } ] } ]
7
+ #
8
+ class Sir::Parser
9
+ attr_reader :document, :wrapper
10
+
11
+ # Map selenium methods to wrapper's ones.
12
+ METHODS = YAML.load_file File.expand_path('yaml/commands_mapping.yml', __dir__)
13
+ NO_EQUATIONS_METHODS = %w(visit expect_location wait_for_loading)
14
+
15
+ def initialize(path, initial_state = nil, wrapper = nil)
16
+ @path = path
17
+ @document = Nokogiri::HTML File.read path
18
+ @wrapper = wrapper || Sir::Wrapper.new
19
+ @wrapper.state = initial_state if initial_state
20
+ end
21
+
22
+ # Make it possible to run command right from Sir::Parser.
23
+ #
24
+ def self.run!(path, initial_state = {}, wrapper = nil)
25
+ new(path, initial_state, wrapper).run
26
+ end
27
+
28
+ # Run commands from file.
29
+ # It makes interpretation itself.
30
+ #
31
+ def run
32
+ result = commands.inject([]) do |r, x|
33
+ begin
34
+ command = get_command x
35
+ next (r) unless command
36
+ r << wrapper.send_command(command)
37
+ rescue NoMethodError => e
38
+ if command[:args].last == "*[aria-label=Delete]" # workaround for gmail
39
+ return {
40
+ success: false,
41
+ output: (r << command),
42
+ }
43
+ else
44
+ binding.pry
45
+ return {
46
+ success: false,
47
+ output: (r << command),
48
+ error: { message: e.message, backtrace: e.backtrace }
49
+ }
50
+ end
51
+ rescue => e
52
+ binding.pry
53
+ return {
54
+ success: false,
55
+ output: (r << command),
56
+ error: { message: e.message, backtrace: e.backtrace }
57
+ }
58
+ end
59
+ end
60
+ failed = result.any? { |x| x[:command] == 'run' && x[:result][:success] == false }
61
+ { success: !failed, output: result }
62
+ end
63
+
64
+ # Parse one command.
65
+ # @param c [Nokogiri::Element] one command on selenium language.
66
+ #
67
+ def get_command(c)
68
+ if path = external_script_link(c)
69
+ { command: 'run', args: [full_path(path)] }
70
+ elsif title?(c)
71
+ nil
72
+ else parse_command(*c.css('td').map(&:text))
73
+ end
74
+ end
75
+
76
+ # Get commands from html structure and parse them.
77
+ #
78
+ def commands
79
+ document.css('tbody tr')
80
+ end
81
+
82
+ # Parse command from array to hash
83
+ #
84
+ # @param command [String] selenium command name
85
+ # @param args [Array] selenium arguments
86
+ #
87
+ # @example
88
+ # parse_command(*["open", "${BOX}/application", ""])
89
+ # #=> { command: 'visit', args: ['#http://value/of/box/application'] }
90
+ #
91
+ def parse_command(command, *args)
92
+ method = METHODS[command]
93
+ fail "Unrecognized command: #{command}" if method.nil?
94
+ arguments = args.reject(&:empty?).map do |line|
95
+ with_vars = parse_js(replace_variables(line))
96
+ next with_vars if NO_EQUATIONS_METHODS.include?(method)
97
+ EQUATION_WITH_REGEX.match(with_vars) ?
98
+ parse_equation_with_regex(with_vars) : parse_equation(with_vars)
99
+ end.flatten
100
+ { command: method, args: arguments }
101
+ end
102
+
103
+ protected
104
+
105
+ # Search link to other file in tr.
106
+ # @params tr [Nokogiri::Node] if fact any Nokogiri node.
107
+ #
108
+ # @returns nil when no link founded.
109
+ # @returns String with link if founded.
110
+ #
111
+ def external_script_link(tr)
112
+ tr.css('td a').map { |l| l['href'] }.first
113
+ end
114
+
115
+ # Check if tr is title. Title is written with bold font.
116
+ #
117
+ def title?(tr)
118
+ !!tr.css('td b').text[0]
119
+ end
120
+
121
+ # Expand path relative to path of file that we interpret.
122
+ #
123
+ # @params link [String] relative path
124
+ # @returns [String] full path
125
+ #
126
+ def full_path(link)
127
+ File.expand_path('../' + link, @path)
128
+ end
129
+
130
+ VARIABLE = /\$\{(\w*)\}/
131
+ EQUATION = /(\w*)=(.*)/
132
+ EQUATION_WITH_REGEX = /(\w*)=regexp:(.*)/
133
+ JS = /javascript\{(.*)\}/
134
+ ATTRIBUTE = /(.+)@([a-z\-\_]+)$/
135
+ PSEUDO_SELECTOR = /(.+):(.+)\('(.+)'\)/
136
+
137
+ # Replace variables that we have in state.
138
+ # Pattern of variable is '${var}'.
139
+ #
140
+ def replace_variables(line)
141
+ line.scan(VARIABLE).flatten
142
+ .reduce(line) { |r, x| r.gsub("${#{x}}", (wrapper.state[x] || '')) }
143
+ end
144
+
145
+ # Split equation into variable and value.
146
+ #
147
+ def parse_equation(line)
148
+ match_data = EQUATION.match(line)
149
+ return line unless match_data
150
+ left = match_data[1].to_sym
151
+ right = parse_pseudo_selector(match_data[2])
152
+ right = Array === right ? right : parse_attribute(match_data[2])
153
+ [left, *right]
154
+ end
155
+
156
+ def parse_equation_with_regex(line)
157
+ match_data = EQUATION_WITH_REGEX.match(line)
158
+ match_data ? [match_data[1].to_sym, /#{match_data[2]}/] : line
159
+ end
160
+
161
+ def parse_js(line)
162
+ match_data = JS.match(line)
163
+ match_data ? ExecJS.eval(match_data[1]).to_s : line
164
+ end
165
+
166
+ # Parse attribute. In selenium attribute is written like 'link@href'.
167
+ #
168
+ def parse_attribute(line)
169
+ match_data = ATTRIBUTE.match(line)
170
+ match_data ? match_data[1..2] : [line]
171
+ end
172
+
173
+ # Parse pseudo selector.
174
+ # For example: "div[role=link] span:contains('Purchase')"
175
+ #
176
+ def parse_pseudo_selector(line)
177
+ match_data = PSEUDO_SELECTOR.match(line)
178
+ replace_pseudo = { 'contains' => :text }
179
+ return line unless match_data
180
+ [
181
+ match_data[1],
182
+ { (replace_pseudo[match_data[2]] || match_data[2]).to_sym => match_data[3] }
183
+ ]
184
+ end
185
+ end
@@ -0,0 +1,3 @@
1
+ module Sir
2
+ VERSION = "0.2.3"
3
+ end
@@ -0,0 +1,347 @@
1
+ # Capybara wrapper. Run capybara, rspec and ruby commands.
2
+ #
3
+ class Sir::Wrapper
4
+ class ExpectationError < StandardError
5
+ attr_accessor :message
6
+
7
+ def initialize(msg, expected, got)
8
+ @message = "#{msg}\n\texpected: #{expected}\n\tgot: #{got}"
9
+ end
10
+ end
11
+
12
+ attr_accessor :session, :state
13
+
14
+ def initialize(session = nil)
15
+ @session = session || Capybara::Session.new(:poltergeist)
16
+ end
17
+
18
+ # Example of hash:
19
+ #
20
+ # @example
21
+ # send_command({ command: 'visit',
22
+ # args: ['http://value/of/box/application'] })
23
+ # #=> `visit 'http://value/of/box/application'` execution
24
+ #
25
+ def send_command(hash)
26
+ r = send(hash[:command], *hash[:args])
27
+ r = true if r == ''
28
+ hash.merge(result: r)
29
+ end
30
+
31
+ # Run script by given path.
32
+ #
33
+ # @params path [String] path to script
34
+ # @return hash of commands that it run.
35
+ #
36
+ def run(path)
37
+ Sir::Parser.run!(path, state, self)
38
+ end
39
+
40
+ # Wrapper of clearAllVisible cookies. Clear cookies.
41
+ #
42
+ def clean_cookies
43
+ session.driver.clear_cookies
44
+ end
45
+
46
+ # Simple capybara visit, except we use session to run it.
47
+ #
48
+ def visit(address)
49
+ tries ||= 0
50
+ session.visit address
51
+ try_box_crutch(address)
52
+ pp session.current_url
53
+ rescue Capybara::Poltergeist::TimeoutError => e
54
+ cookies = session.driver.cookies
55
+ session.driver.quit
56
+ @session = Capybara::Session.new(:poltergeist)
57
+ cookies.each { |k, v| session.driver.cookies[k] = v }
58
+ if retries < 2
59
+ retry
60
+ else
61
+ raise e
62
+ end
63
+ end
64
+
65
+ def try_box_crutch(address)
66
+ url = session.current_url
67
+ if address =~ /auth\/box\?content_id=(\d+)/ && !url.include?('#videos')
68
+ visit url + "#videos/#{$~[1]}"
69
+ end
70
+ end
71
+
72
+ # Wrapper of verifyText.
73
+ # @example
74
+ # expect_text :id, 'user_id', 'John'
75
+ #
76
+ def expect_text(criterium, selector, text)
77
+ message = 'Failed to find text:'
78
+ expected = text
79
+ got = session.find(criterium, selector).text
80
+ fail ExpectationError.new(message, expected, got) unless expected == got
81
+ end
82
+
83
+ # Wrapper of assertLocation.
84
+ # @example
85
+ # expect_location 'ya.ru'
86
+ #
87
+ def expect_location(address)
88
+ message = 'Failed to be on expected page:'
89
+ expected = address
90
+ got = session.current_url
91
+ fail ExpectationError.new(message, expected, got) unless expected == got
92
+ end
93
+
94
+ def expect_attribute(criterium, selector, attribute, expected)
95
+ tries ||= 0
96
+ message = 'Failed to check attribute:'
97
+ got = session.first(criterium, selector, visible: [true, false])[attribute]
98
+ fail ExpectationError.new(message, expected, got) unless File.fnmatch(expected, got)
99
+ rescue => e
100
+ if (tries < 3)
101
+ retry
102
+ else
103
+ raise e
104
+ end
105
+ end
106
+
107
+ # Wrapper of sleep.
108
+ # @example
109
+ # sleep "5000" #=> do nothing 5 seconds.
110
+ #
111
+ def sleep(milliseconds)
112
+ Kernel.sleep(milliseconds.to_i / 1000)
113
+ end
114
+
115
+ # Wrapper of select.
116
+ # @example
117
+ # select :css, '.hey.there', :id, 'username'
118
+ #
119
+ def select(*args)
120
+ session.find(*args.first(2)).select(args.last)
121
+ end
122
+
123
+ # Wrapper of type.
124
+ # @example
125
+ # find_and_set :id, 'username', 'pahom'
126
+ #
127
+ def find_and_set(criterium, selector, value)
128
+ is_angular = false # !session.evaluate_script('angular').nil?
129
+ if selector.include?('file') && is_angular
130
+ angular_find_and_set selector, value
131
+ else
132
+ session.find(criterium, selector, visible: [true, false]).set value
133
+ end
134
+ end
135
+
136
+ def angular_find_and_set(selector, value)
137
+ sleep 1000
138
+ unless session.first(:id, 'fakeFileInput')
139
+ session.execute_script "fakeFileInput = window.$('<input/>').attr({id: 'fakeFileInput', type:'file'}).appendTo('body');"
140
+ end
141
+ session.first(:id, 'fakeFileInput').set value
142
+ session.execute_script("angular.element(document.getElementById('#{selector}')).scope().scanProcess.fileSelect($('#fakeFileInput')[0].files)")
143
+ sleep 5000
144
+ end
145
+
146
+ # Wrapper of click.
147
+ # @example
148
+ # find_and_click :id, 'submit_form'
149
+ #
150
+ def find_and_click(criterium, *selectors)
151
+ tries ||= 0
152
+ session.first(criterium, *selectors, visible: [true, false]).click
153
+ rescue Capybara::Poltergeist::ObsoleteNode
154
+ sleep 1000
155
+ session.evaluate_script("$('#{selectors.join(' ')}').show()")
156
+ session.first(criterium, *selectors).click
157
+ rescue => e
158
+ if (tries < 3)
159
+ retry
160
+ else
161
+ raise e
162
+ end
163
+ end
164
+
165
+ # Same as find_and_click, but sleep 5 seconds after it.
166
+ # what the hell should it do in selenium?
167
+ #
168
+ def click_and_wait(criterium, *selectors)
169
+ sleep 2000
170
+ session.first(criterium, *selectors).click
171
+ rescue
172
+ case criterium
173
+ when :css then session.evaluate_script("$('#{selectors.join(' ')}').show()")
174
+ when :id then session.evaluate_script("$('##{selectors.join(' ')}').show()")
175
+ end
176
+ session.first(criterium, *selectors).click
177
+ ensure
178
+ sleep 5000
179
+ end
180
+
181
+ # Add url to global state. Wrapper of storeLocation.
182
+ #
183
+ def state_set_url(name)
184
+ state[name] = session.current_url
185
+ end
186
+
187
+ # Wrapper of storeText. Add finded text to global state.
188
+ # @example
189
+ # state_set_text :id, 'username', 'user_name'
190
+ #
191
+ def state_set_text(*args)
192
+ case args.size
193
+ when 2
194
+ criterium, selector, name = :xpath, *args
195
+ when 3
196
+ criterium, selector, name = *args
197
+ end
198
+ sleep 500
199
+ # workaround for purchase tickets.
200
+ if %w(with_membership without_membership).include?(selector)
201
+ text = (session.first(:id, 'with_membership') || session.first(:id, 'without_membership')).text
202
+ else
203
+ text = session.first(criterium, selector, visible: [true, false]).text
204
+ if text == true
205
+ text = session.first(criterium, selector, visible: [true, false]).text
206
+ end
207
+ end
208
+ state[name] = text
209
+ end
210
+
211
+ # Wrapper of storeAttribute. Add text at attribute to global state.
212
+ # @example
213
+ # state_set_text :id, 'username', 'href', 'user_name'
214
+ #
215
+ def state_set_attribute(criterium, selector, attribute, name)
216
+ x ||= 0
217
+ text = session.first(criterium, selector, visible: [true, false])[attribute]
218
+ state[name] = text
219
+ rescue
220
+ (x += 1; retry) if x < 3
221
+ end
222
+
223
+ def state_set_eval(code, name)
224
+ state[name] = session.evaluate_script(code)
225
+ end
226
+
227
+ def state_set(value, name)
228
+ state[name] = value
229
+ end
230
+
231
+ def keyup(criterium, selector, keycode)
232
+ selector = case criterium
233
+ when :css then selector
234
+ when :id then '#' + selector
235
+ else selector
236
+ end
237
+ keypress_script = "var e = $.Event('keyup', { keyCode: #{keycode} }); $('#{selector}').trigger(e);"
238
+ session.execute_script(keypress_script)
239
+ end
240
+
241
+ def wait_for_loading(address)
242
+ # session.visit address
243
+ sleep 5000
244
+ wait_for { File.fnmatch(address, session.current_url) }
245
+ end
246
+
247
+ # Wrapper of waitForElementPresent.
248
+ # @example
249
+ # wait_for_element :id, 'username'
250
+ #
251
+ def wait_for_element(criterium, *selectors)
252
+ wait_for { session.first(criterium, *selectors, visible: [true, false]) }
253
+ true
254
+ rescue Timeout::Error
255
+ url = session.current_url
256
+ url = session.current_url unless String === url
257
+ session.visit url
258
+ sleep 10000
259
+ session.first(criterium, *selectors, visible: [true, false])
260
+ ensure
261
+ true
262
+ end
263
+
264
+ # Wrapper of waitForVisible.
265
+ # @example
266
+ # wait_for_visible :id, 'username'
267
+ #
268
+ def wait_for_visible(criterium, selector, value = nil)
269
+ wait_for do
270
+ element = session.first(criterium, selector, visible: [true])
271
+ if element && value
272
+ File.fnmatch(value.to_s, element.text)
273
+ else
274
+ true
275
+ end
276
+ end
277
+ end
278
+
279
+ # Wrapper of waitForText.
280
+ # @example
281
+ # wait_for_text :id, 'username', 'Pahom'
282
+ #
283
+ def wait_for_text(criterium, *selectors, text)
284
+ wait_for do
285
+ (x = session.first(criterium, *selectors, visible: [true, false])) &&
286
+ File.fnmatch(text, x.text)
287
+ end
288
+ rescue
289
+ # Fucking poltergeist can do this:
290
+ # [42] pry(#<Sir::Parser>)> wrapper.session.text
291
+ # NoMethodError: undefined method `map' for nil:NilClass
292
+ # from /home/s/work/sir/lib/sir/capybara_ext.rb:10:in `find'
293
+ # [43] pry(#<Sir::Parser>)> wrapper.session.text
294
+ # => "true"
295
+ # [44] pry(#<Sir::Parser>)> wrapper.session.current_url
296
+ # => "+You\nSearch\nImages\nMaps\nPlay\nYouTube\nGmail\nDrive\nMore\nrmtest011@gmail.com\n×\n\tA faster way to browse the web\nInstall Google Chrome\n\nRussia\n\n \t\n\n\nAdvanced search\nLanguage tools\n\nGoogle.ru offered in: русском\nAdvertising ProgramsAbout GoogleGoogle.com\n© 2015 - Privacy - Terms\n\n"
297
+ # FIXME: so just sleep 5 seconds and hope it will be fine.
298
+ sleep 5000
299
+ end
300
+
301
+ def wait_for_text_present(text)
302
+ wait_for do
303
+ session.text.include? text
304
+ end
305
+ end
306
+
307
+ # Wrapper of waitForValue.
308
+ # @example
309
+ # wait_for_value :id, 'username', 'Pahom'
310
+ #
311
+ def wait_for_value(criterium, selector, text)
312
+ wait_for do
313
+ session.first(criterium, selector, text)
314
+ end
315
+ true
316
+ end
317
+
318
+ def wait_for_content(content)
319
+ wait_for(CONTENT_TIMEOUT) { session.has_text? content }
320
+ end
321
+
322
+ def wait_for_ajax
323
+ previous = active_queries_count
324
+ wait_for(AJAX_TIMEOUT) { active_queries_count < previous }
325
+ end
326
+
327
+ protected
328
+
329
+ AJAX_TIMEOUT = 100
330
+ CONTENT_TIMEOUT = 100
331
+ STANDARD_TIMEOUT = 50
332
+
333
+ def wait_for(timeout = STANDARD_TIMEOUT)
334
+ result = nil
335
+ Timeout.timeout(timeout) do
336
+ loop do
337
+ sleep 1
338
+ break if result = yield
339
+ end
340
+ end
341
+ result
342
+ end
343
+
344
+ def active_queries_count
345
+ session.evaluate_script('jQuery.active')
346
+ end
347
+ end
@@ -0,0 +1,24 @@
1
+ open: visit
2
+ pause: sleep
3
+ select: select
4
+ type: find_and_set
5
+ click: find_and_click
6
+ clickAt: find_and_click
7
+ clickAndWait: click_and_wait
8
+ waitForElementPresent: wait_for_element
9
+ waitForLocation: wait_for_loading
10
+ waitForText: wait_for_text
11
+ waitForTextPresent: wait_for_text_present
12
+ waitForValue: wait_for_value
13
+ waitForVisible: wait_for_visible
14
+ storeAttribute: state_set_attribute
15
+ storeLocation: state_set_url
16
+ storeText: state_set_text
17
+ storeEval: state_set_eval
18
+ store: state_set
19
+ gotolabel: goto_label
20
+ verifyText: expect_text
21
+ verifyAttribute: expect_attribute
22
+ assertLocation: expect_location
23
+ deleteAllVisibleCookies: clean_cookies
24
+ keyUp: keyup
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sir/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sir"
8
+ spec.version = Sir::VERSION
9
+ spec.authors = ["Sergey Smagin"]
10
+ spec.email = ["smaginsergey1310@gmail.com"]
11
+ spec.summary = %q{Run selenium tests with webkit. Headless.}
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.7"
20
+ spec.add_development_dependency "rake", "~> 10.0"
21
+ spec.add_dependency "rspec"
22
+ spec.add_dependency "poltergeist"
23
+ spec.add_dependency "nokogiri"
24
+ spec.add_dependency "execjs"
25
+ spec.add_dependency "therubyracer"
26
+ end
@@ -0,0 +1,60 @@
1
+ :open:
2
+ :input: ['open', '${BOX}/application']
3
+ :result:
4
+ :command: visit
5
+ :args: [ 'box/application' ]
6
+ :pause:
7
+ :input: ['pause', '5000']
8
+ :result:
9
+ :command: sleep
10
+ :args: [ '5000' ]
11
+ :select:
12
+ :input: ['select', 'id=ccid', 'label=regexp:.*expire.*']
13
+ :result:
14
+ :command: select
15
+ :args: [ !ruby/sym id, 'ccid', !ruby/sym label, !ruby/regexp /.*expire.*/ ]
16
+ :find_and_set:
17
+ :input: ['type', 'id=amount', 'attestationAmount']
18
+ :result:
19
+ :command: find_and_set
20
+ :args: [ !ruby/sym id, 'amount', 'attestationAmount' ]
21
+ :click:
22
+ :input: ['click', "xpath=(//input[@name='can-share'])[2]"]
23
+ :result:
24
+ :command: find_and_click
25
+ :args: [ !ruby/sym xpath, "(//input[@name='can-share'])[2]" ]
26
+ :click_and_wait:
27
+ :input: ['clickAndWait', "xpath=(//input[@name='can-share'])[2]"]
28
+ :result:
29
+ :command: click_and_wait
30
+ :args: [ !ruby/sym xpath, "(//input[@name='can-share'])[2]" ]
31
+ :wait_for_element:
32
+ :input: ['waitForElementPresent', 'css=.icos']
33
+ :result:
34
+ :command: wait_for_element
35
+ :args: [ !ruby/sym css, '.icos' ]
36
+ :wait_for_loading:
37
+ :input: ['waitForLocation', '${BOX}/account/attestate']
38
+ :result:
39
+ :command: wait_for_loading
40
+ :args: [ 'box/account/attestate' ]
41
+ :wait_for_text:
42
+ :input: ['waitForText', 'css=h6.fcolor-green', 'Credit card approved']
43
+ :result:
44
+ :command: wait_for_text
45
+ :args: [ !ruby/sym css, 'h6.fcolor-green', 'Credit card approved' ]
46
+ :state_set_url:
47
+ :input: ['storeLocation', 'verify']
48
+ :result:
49
+ :command: state_set_url
50
+ :args: [ 'verify' ]
51
+ :state_set_text:
52
+ :input: ['storeText', 'css=td.def', 'attestationAmount']
53
+ :result:
54
+ :command: state_set_text
55
+ :args: [ !ruby/sym css, 'td.def', 'attestationAmount' ]
56
+ :expect_text:
57
+ :input: ['verifyText', 'css=button', 'I accept agreement']
58
+ :result:
59
+ :command: expect_text
60
+ :args: [ !ruby/sym css, 'button', 'I accept agreement' ]
@@ -0,0 +1,4 @@
1
+ :commands_amount: 31
2
+ :first_command:
3
+ :command: visit
4
+ :args: [box/application]
@@ -0,0 +1,16 @@
1
+ LV: "http://lv.gtflix.com"
2
+ IDCERT: "https://gtfpass.gtflix.com"
3
+ PAYGATE: "https://paygate.gtflix.com"
4
+ BOX: "https://box.gtflix.com:9090"
5
+ CART: "http://cart.gtflix.com"
6
+ QA: "http://qa.gtflix.com"
7
+ CM2: "http://devcm2.gtflix.com"
8
+ gmailAccount: "amorpro3"
9
+ CCHolderName: "John Doe"
10
+ CCNumber: "4000000000000002"
11
+ CCCVV: "123"
12
+ CCYear: "2017"
13
+ CCMonth: "01"
14
+ cm2_user: "dan"
15
+ cm2_pass: "qwe123"
16
+
@@ -0,0 +1,5 @@
1
+ :in: key=value
2
+ :out: [!ruby/sym key, 'value']
3
+ :incorrect_in: key value whatever
4
+ :with_attr: key=value@hi
5
+ :out_with_attr: [!ruby/sym key, 'value', 'hi']
@@ -0,0 +1,5 @@
1
+ :in: key=regexp:.*value.*
2
+ :out:
3
+ - !ruby/sym key
4
+ - !ruby/regexp /.*value.*/
5
+ :incorrect_in: key value whatever
@@ -0,0 +1,8 @@
1
+ :state:
2
+ BOX: box
3
+ LV: lv
4
+ :in: '${BOX}/vasya/${LV}'
5
+ :out: 'box/vasya/lv'
6
+ :incorrect_in: '${BOX}/vasya/${lv}'
7
+ :incorrect_out: 'box/vasya/'
8
+
@@ -0,0 +1,27 @@
1
+ ---
2
+ :success: true
3
+ :output:
4
+ - :command: visit
5
+ :args:
6
+ - http://ya.ru
7
+ :result:
8
+ status: success
9
+ - :command: find_and_click
10
+ :args:
11
+ - :id
12
+ - text
13
+ :result:
14
+ position:
15
+ x: 437
16
+ y: 353
17
+ - :command: find_and_set
18
+ :args:
19
+ - :id
20
+ - text
21
+ - good for you
22
+ :result: true
23
+ - :command: click_and_wait
24
+ :args:
25
+ - :css
26
+ - button.button_theme_websearch
27
+ :result: 0
@@ -0,0 +1,137 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Sir::Parser do
4
+ let(:state) { Sir::Fixtures::LINE_WITH_VARIABLE[:state] }
5
+ let(:path) { File.expand_path '../fixtures/commands.html', __FILE__ }
6
+ subject { Sir::Parser.new(path, state) }
7
+
8
+ describe '.get_commands' do
9
+ it 'Get as many commands as file have' do
10
+ expect(subject.get_commands.size).
11
+ to eq(Sir::Fixtures::COMMANDS_INFO[:commands_amount])
12
+ end
13
+
14
+ it 'Get first command properly' do
15
+ expect(subject.get_commands.first).
16
+ to eq(Sir::Fixtures::COMMANDS_INFO[:first_command])
17
+ end
18
+ end
19
+
20
+ describe '.replace_variables' do
21
+ it 'can replace varible like #{YO}' do
22
+ input = Sir::Fixtures::LINE_WITH_VARIABLE[:in]
23
+ out = Sir::Fixtures::LINE_WITH_VARIABLE[:out]
24
+ expect(subject.send(:replace_variables, input)).to eq(out)
25
+ end
26
+
27
+ it 'can handle incorrect variables' do
28
+ input = Sir::Fixtures::LINE_WITH_VARIABLE[:incorrect_in]
29
+ out = Sir::Fixtures::LINE_WITH_VARIABLE[:incorrect_out]
30
+ expect(subject.send(:replace_variables, input)).to eq(out)
31
+ end
32
+ end
33
+
34
+ describe '.parse_equation' do
35
+ it 'can split variables with equation' do
36
+ input = Sir::Fixtures::LINE_WITH_EQUATION[:in]
37
+ out = Sir::Fixtures::LINE_WITH_EQUATION[:out]
38
+ expect(subject.send(:parse_equation, input)).to eq(out)
39
+ end
40
+
41
+ it 'can handle incorrect variables' do
42
+ input = Sir::Fixtures::LINE_WITH_EQUATION[:incorrect_in]
43
+ out = Sir::Fixtures::LINE_WITH_EQUATION[:incorrect_in]
44
+ expect(subject.send(:parse_equation, input)).to eq(out)
45
+ end
46
+
47
+ it 'parse attributes' do
48
+ input = Sir::Fixtures::LINE_WITH_EQUATION[:with_attr]
49
+ out = Sir::Fixtures::LINE_WITH_EQUATION[:out_with_attr]
50
+ expect(subject.send(:parse_equation, input)).to eq(out)
51
+ end
52
+ end
53
+
54
+ describe '.parse_equation_with_regex' do
55
+ it 'can split variables with equation' do
56
+ input = Sir::Fixtures::LINE_WITH_REGEX[:in]
57
+ out = Sir::Fixtures::LINE_WITH_REGEX[:out]
58
+ expect(subject.send(:parse_equation_with_regex, input)).to eq(out)
59
+ end
60
+
61
+ it 'can handle incorrect variables' do
62
+ input = Sir::Fixtures::LINE_WITH_REGEX[:incorrect_in]
63
+ out = Sir::Fixtures::LINE_WITH_REGEX[:incorrect_in]
64
+ expect(subject.send(:parse_equation_with_regex, input)).to eq(out)
65
+ end
66
+ end
67
+
68
+ describe '.parse_command' do
69
+ it 'parse "open ${BOX}/application"' do
70
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:open][:input])).
71
+ to eq(Sir::Fixtures::COMMANDS[:open][:result])
72
+ end
73
+
74
+ it 'parse pause' do
75
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:pause][:input])).
76
+ to eq(Sir::Fixtures::COMMANDS[:pause][:result])
77
+ end
78
+
79
+ it 'parse select' do
80
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:select][:input])).
81
+ to eq(Sir::Fixtures::COMMANDS[:select][:result])
82
+ end
83
+
84
+ it 'parse click' do
85
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:click][:input])).
86
+ to eq(Sir::Fixtures::COMMANDS[:click][:result])
87
+ end
88
+
89
+ it 'parse click_and_wait' do
90
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:click_and_wait][:input])).
91
+ to eq(Sir::Fixtures::COMMANDS[:click_and_wait][:result])
92
+ end
93
+
94
+ it 'parse wait_for_element' do
95
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:wait_for_element][:input])).
96
+ to eq(Sir::Fixtures::COMMANDS[:wait_for_element][:result])
97
+ end
98
+
99
+ it 'parse wait_for_loading' do
100
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:wait_for_loading][:input])).
101
+ to eq(Sir::Fixtures::COMMANDS[:wait_for_loading][:result])
102
+ end
103
+
104
+ it 'parse wait_for_text' do
105
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:wait_for_text][:input])).
106
+ to eq(Sir::Fixtures::COMMANDS[:wait_for_text][:result])
107
+ end
108
+
109
+ it 'parse state_set_url' do
110
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:state_set_url][:input])).
111
+ to eq(Sir::Fixtures::COMMANDS[:state_set_url][:result])
112
+ end
113
+
114
+ it 'parse state_set_text' do
115
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:state_set_text][:input])).
116
+ to eq(Sir::Fixtures::COMMANDS[:state_set_text][:result])
117
+ end
118
+
119
+ it 'parse expect_text' do
120
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:expect_text][:input])).
121
+ to eq(Sir::Fixtures::COMMANDS[:expect_text][:result])
122
+ end
123
+
124
+ it 'parse find_and_set' do
125
+ expect(subject.parse_command(*Sir::Fixtures::COMMANDS[:find_and_set][:input])).
126
+ to eq(Sir::Fixtures::COMMANDS[:find_and_set][:result])
127
+ end
128
+ end
129
+
130
+ context '#run' do
131
+ it 'summarize script work' do
132
+ path = File.expand_path 'fixtures/short_test.html', __dir__
133
+ expect(Sir::Parser.run!(path)).to eq(Sir::Fixtures::SHORT_TEST_RESULT)
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,3 @@
1
+ require 'rspec'
2
+ require_relative '../lib/sir'
3
+ require_relative 'support/fixtures'
@@ -0,0 +1,14 @@
1
+ require 'yaml'
2
+
3
+ # When this file is loaded, for each fixture file, a singleton method is
4
+ # created within the Sir::Fixtures module with the same name as the fixture
5
+ # file, returning the value of the fixture.
6
+ #
7
+ module Sir::Fixtures; end
8
+
9
+ fixtures_path = File.expand_path('../../fixtures', __FILE__)
10
+
11
+ Dir[File.expand_path('*.yml', fixtures_path)].each do |filename|
12
+ const_name = File.basename(filename, '.*').upcase
13
+ Sir::Fixtures.const_set(const_name, YAML.load_file(filename))
14
+ end
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sir
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Sergey Smagin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: poltergeist
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: execjs
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: therubyracer
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email:
113
+ - smaginsergey1310@gmail.com
114
+ executables:
115
+ - sir
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - bin/sir
125
+ - lib/sir.rb
126
+ - lib/sir/capybara_ext.rb
127
+ - lib/sir/parser.rb
128
+ - lib/sir/version.rb
129
+ - lib/sir/wrapper.rb
130
+ - lib/sir/yaml/commands_mapping.yml
131
+ - sir.gemspec
132
+ - spec/fixtures/commands.html
133
+ - spec/fixtures/commands.yml
134
+ - spec/fixtures/commands_info.yml
135
+ - spec/fixtures/initial_state.yml
136
+ - spec/fixtures/line_with_equation.yml
137
+ - spec/fixtures/line_with_regex.yml
138
+ - spec/fixtures/line_with_variable.yml
139
+ - spec/fixtures/short_test.html
140
+ - spec/fixtures/short_test_result.yml
141
+ - spec/parser_spec.rb
142
+ - spec/spec_helper.rb
143
+ - spec/support/fixtures.rb
144
+ homepage:
145
+ licenses:
146
+ - MIT
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.4.5
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: Run selenium tests with webkit. Headless.
168
+ test_files:
169
+ - spec/fixtures/commands.html
170
+ - spec/fixtures/commands.yml
171
+ - spec/fixtures/commands_info.yml
172
+ - spec/fixtures/initial_state.yml
173
+ - spec/fixtures/line_with_equation.yml
174
+ - spec/fixtures/line_with_regex.yml
175
+ - spec/fixtures/line_with_variable.yml
176
+ - spec/fixtures/short_test.html
177
+ - spec/fixtures/short_test_result.yml
178
+ - spec/parser_spec.rb
179
+ - spec/spec_helper.rb
180
+ - spec/support/fixtures.rb
181
+ has_rdoc: