sir 0.2.3

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.
@@ -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: