sir 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +2 -0
- data/bin/sir +9 -0
- data/lib/sir.rb +38 -0
- data/lib/sir/capybara_ext.rb +12 -0
- data/lib/sir/parser.rb +185 -0
- data/lib/sir/version.rb +3 -0
- data/lib/sir/wrapper.rb +347 -0
- data/lib/sir/yaml/commands_mapping.yml +24 -0
- data/sir.gemspec +26 -0
- data/spec/fixtures/commands.yml +60 -0
- data/spec/fixtures/commands_info.yml +4 -0
- data/spec/fixtures/initial_state.yml +16 -0
- data/spec/fixtures/line_with_equation.yml +5 -0
- data/spec/fixtures/line_with_regex.yml +5 -0
- data/spec/fixtures/line_with_variable.yml +8 -0
- data/spec/fixtures/short_test_result.yml +27 -0
- data/spec/parser_spec.rb +137 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/fixtures.rb +14 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
data/bin/sir
ADDED
data/lib/sir.rb
ADDED
@@ -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
|
data/lib/sir/parser.rb
ADDED
@@ -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
|
data/lib/sir/version.rb
ADDED
data/lib/sir/wrapper.rb
ADDED
@@ -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
|
data/sir.gemspec
ADDED
@@ -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,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,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
|
data/spec/parser_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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:
|