rainforest_ruby_runtime 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,8 @@
1
1
  module RainforestRubyRuntime
2
2
  class DSL
3
+ include RSpec::Matchers
4
+ include Capybara::DSL
5
+
3
6
  def initialize(callback: )
4
7
  @callback = callback
5
8
  end
@@ -8,14 +11,25 @@ module RainforestRubyRuntime
8
11
  RainforestRubyRuntime::Test.new(id: id, title: title, callback: @callback, &block)
9
12
  end
10
13
 
14
+ def step(options, &block)
15
+ RainforestRubyRuntime::Step.new(options.merge(callback: @callback), &block).tap(&:run)
16
+ end
17
+
11
18
  def define_variable_scope(name, &block)
12
19
  scope = Variables::Scope.new(name, &block)
13
- Variables.scope_registery.register(scope)
20
+ Variables.scope_registry.register(scope)
14
21
  end
15
22
 
16
23
  def run_code(code)
17
24
  eval(code)
18
25
  end
26
+
27
+ def method_missing(name, *args, &block)
28
+ if Variables.scope_registry.has_variable?(name)
29
+ Variables.scope_registry[name]
30
+ else
31
+ super
32
+ end
33
+ end
19
34
  end
20
35
  end
21
-
@@ -1,12 +1,15 @@
1
1
  module RainforestRubyRuntime
2
2
  class Runner
3
3
  attr_reader :config_options, :logger
4
+ attr_accessor :browser
4
5
 
5
6
  FAILURE_EXCEPTIONS = [
6
7
  RSpec::Expectations::ExpectationNotMetError,
7
8
  Capybara::ElementNotFound,
8
9
  ].freeze
9
10
 
11
+ BROWSERS = %w(chrome firefox ie edge safari).freeze
12
+
10
13
  def initialize(options = {})
11
14
  @config_options = options.dup.freeze
12
15
  @step_variables = options[:step_variables]
@@ -14,64 +17,55 @@ module RainforestRubyRuntime
14
17
  @logger = options.fetch(:logger) { Logger.new(StringIO.new) }
15
18
  end
16
19
 
17
- def run(code)
18
- logger.debug "Running code:\n#{code}\nDriver: #{driver}"
19
- Capybara.default_driver = :"#{driver}"
20
+ def run(codes)
21
+ logger.debug "Running code:\n#{codes.join('\n')}\nDriver: #{driver_type}"
22
+ Capybara.default_driver = :"#{driver_type}"
20
23
  Capybara.default_max_wait_time = wait_time
21
24
 
22
- apply_config!
23
- setup_scope_registery!
25
+ setup_scope_registry!
24
26
 
25
27
  dsl = RainforestRubyRuntime::DSL.new(callback: @callback)
26
28
 
27
- test = dsl.run_code(code)
28
- if Test === test
29
- test.run
29
+ tests = codes.map { |code| dsl.run_code(code) }
30
+ if tests.all? { |test| test.is_a?(Test) }
31
+ describe = driver_klass.new(config_options).to_rspec(tests)
32
+ run_rspec(describe)
30
33
  else
31
- raise WrongReturnValueError, test
34
+ raise WrongReturnValueError, tests.reject { |test| test.is_a?(Test) }
32
35
  end
33
- test
36
+ tests
34
37
  ensure
35
38
  terminate_session!
36
39
  end
37
40
 
38
- def extract_results(code, fake_session_id: nil)
39
- stdout = stderr = nil
40
- payload = nil
41
- begin
42
- stdout, stderr = capture_output2 do
43
- run(code)
41
+ def run_rspec(describe)
42
+ if ENV['RUNTIME_ENV'] == 'test' && ENV['SHOW_OUTPUT'] != 'true'
43
+ # if we're in tests, don't mix output from here with tests output
44
+ # and don't include this describe block in the test count
45
+ describe.run
46
+ RSpec.world.example_groups.pop
47
+ else
48
+ RSpec.configure do |config|
49
+ config.color = true
50
+ config.formatter = :documentation
51
+ end
52
+ RSpec.configuration.reporter.report(RSpec.world.example_count([describe])) do |reporter|
53
+ describe.run(reporter)
44
54
  end
45
- rescue *FAILURE_EXCEPTIONS => e
46
- payload = exception_to_payload e, status: 'failed'
47
- rescue StandardError => e
48
- payload = exception_to_payload e, status: 'error'
49
- rescue SyntaxError, Exception => e
50
- payload = exception_to_payload e, status: 'fatal_error'
51
55
  end
52
-
53
- payload ||= { status: 'passed' }
54
-
55
- sid = fake_session_id || session_id
56
-
57
- payload = payload.merge({
58
- stdout: stdout,
59
- stderr: stderr,
60
- session_id: sid,
61
- driver: driver,
62
- })
63
-
64
-
65
- logger.debug("Payload")
66
- logger.debug(payload.inspect)
67
-
68
- payload
69
56
  end
70
57
 
71
- def driver
58
+ def driver_type
72
59
  ENV.fetch("CAPYBARA_DRIVER") { "selenium" }
73
60
  end
74
61
 
62
+ def driver_klass
63
+ {
64
+ 'selenium' => Drivers::Selenium,
65
+ 'sauce' => Drivers::Sauce,
66
+ }.fetch(driver_type)
67
+ end
68
+
75
69
  def current_browser
76
70
  current_driver.browser
77
71
  end
@@ -93,17 +87,6 @@ module RainforestRubyRuntime
93
87
  })
94
88
  end
95
89
 
96
- def apply_config!
97
- config = {
98
- "selenium" => Drivers::Selenium,
99
- "sauce" => Drivers::Sauce,
100
- "testingbot" => Drivers::TestingBot,
101
- "browser_stack" => Drivers::BrowserStack,
102
- }.fetch(driver)
103
-
104
- config.new(config_options).call
105
- end
106
-
107
90
  def current_driver
108
91
  Capybara.current_session.driver
109
92
  end
@@ -115,12 +98,12 @@ module RainforestRubyRuntime
115
98
  elsif current_driver.respond_to?(:quit)
116
99
  current_driver.quit
117
100
  else
118
- logger.warn "Cannot terminate session. Driver #{driver}" and return
101
+ logger.warn "Cannot terminate session. Driver #{driver_type}" and return
119
102
  end
120
103
  logger.debug "Session successfuly terminated"
121
104
  rescue Selenium::WebDriver::Error::WebDriverError => e
122
105
  # Ignore
123
- logger.warn "Exception while terminating session. Driver #{driver}. Class: #{e.class}"
106
+ logger.warn "Exception while terminating session. Driver #{driver_type}. Class: #{e.class}"
124
107
  logger.warn "#{e.message}\n#{e.backtrace.join("\n")}"
125
108
  end
126
109
 
@@ -128,23 +111,12 @@ module RainforestRubyRuntime
128
111
  ENV.fetch("CAPYBARA_WAIT_TIME", 20).to_i
129
112
  end
130
113
 
131
- def capture_output2
132
- previous_stdout, $stdout = $stdout, StringIO.new
133
- previous_stderr, $stderr = $stderr, StringIO.new
134
- yield
135
- [$stdout.string, $stderr.string]
136
- ensure
137
- # Restore the previous value of stdout (typically equal to STDERR).
138
- $stdout = previous_stdout
139
- $stderr = previous_stderr
140
- end
141
-
142
- def setup_scope_registery!
114
+ def setup_scope_registry!
143
115
  # TODO this should not be set globally, but passed in the DSL
144
116
  if @step_variables.nil?
145
- Variables.scope_registery = Variables::Registery.new
117
+ Variables.scope_registry = Variables::Registry.new
146
118
  else
147
- Variables.scope_registery = Variables::StaticVariableRegistery.new(@step_variables)
119
+ Variables.scope_registry = Variables::StaticVariableRegistry.new(@step_variables)
148
120
  end
149
121
  end
150
122
  end
@@ -10,28 +10,11 @@ module RainforestRubyRuntime
10
10
  @callback = callback
11
11
  end
12
12
 
13
- def step(options, &block)
14
- step = Step.new(options.merge(callback: @callback), &block)
15
- @steps << step
16
- step.run
17
- step
18
- end
19
-
20
13
  def run
21
- extend RSpec::Matchers
22
- extend Capybara::DSL
23
14
  @callback.before_test(self)
24
- instance_eval &@block
15
+ @block.call
25
16
  @callback.after_test(self)
26
17
  end
27
-
28
- def method_missing(name, *args, &block)
29
- if Variables.scope_registery.has_variable?(name)
30
- Variables.scope_registery[name]
31
- else
32
- super
33
- end
34
- end
35
18
  end
36
19
 
37
20
  class Step
@@ -1,6 +1,6 @@
1
1
  module RainforestRubyRuntime
2
2
  module Variables
3
- class Registery
3
+ class Registry
4
4
  def initialize(variables = {})
5
5
  @variables = {}
6
6
  end
@@ -22,7 +22,7 @@ module RainforestRubyRuntime
22
22
  attr_reader :variables
23
23
  end
24
24
 
25
- class StaticVariableRegistery
25
+ class StaticVariableRegistry
26
26
  def initialize(variables)
27
27
  @variables = variables.inject({}) do |variables, (name, var_and_values)|
28
28
  scope = Scope.new(name)
@@ -47,12 +47,12 @@ module RainforestRubyRuntime
47
47
  end
48
48
  end
49
49
 
50
- def self.scope_registery
51
- @scope_registery ||= Registery.new
50
+ def self.scope_registry
51
+ @scope_registry ||= Registry.new
52
52
  end
53
53
 
54
- def self.scope_registery=(registery)
55
- @scope_registery = registery
54
+ def self.scope_registry=(registry)
55
+ @scope_registry = registry
56
56
  end
57
57
  end
58
58
  end
@@ -5,7 +5,7 @@ module RainforestRubyRuntime
5
5
 
6
6
  def initialize(*, &block)
7
7
  super
8
- @registry = Registery.new
8
+ @registry = Registry.new
9
9
  instance_eval &block if block_given?
10
10
  end
11
11
 
@@ -1,3 +1,3 @@
1
1
  module RainforestRubyRuntime
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -13,19 +13,30 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://www.rainforestqa.com/"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.files = `git ls-files -z`.split("\x0")
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f =~ %r(^vendor/sauce/) }
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.14"
22
- spec.add_development_dependency "rake"
21
+ spec.post_install_message = <<-EOS
22
+ Thanks for installing!
23
+ If you plan to run scripts against Sauce Labs, please run
24
+ `rainforest_test --prepare-sauce`
25
+ and set the SAUCE_USERNAME, SAUCE_ACCESS_KEY, and CAPYBARA_DRIVER
26
+ environment variables.
27
+ EOS
28
+
29
+ spec.add_development_dependency "pry"
30
+ spec.add_development_dependency "byebug"
31
+ spec.add_development_dependency "awesome_print"
32
+ spec.add_development_dependency "rspec_junit_formatter"
23
33
  spec.add_dependency "sauce", "~> 3.7"
24
34
  spec.add_dependency "sauce-connect", "~> 3.6"
25
- spec.add_dependency "rspec-expectations", "~> 3.0"
35
+ spec.add_dependency "rspec", "3.5.0"
26
36
  spec.add_dependency "capybara", "~> 2.7.1"
27
37
  spec.add_dependency "selenium-client", "~> 1.2.0"
28
38
  spec.add_dependency "selenium-webdriver", "~> 3.3"
29
39
  spec.add_dependency "net-http-persistent", "~> 3.0"
30
- spec.add_dependency "testingbot", "~> 0.1.7"
40
+ spec.add_dependency "bundler", "~> 1.16"
41
+ spec.add_dependency "rake", "~> 12.3"
31
42
  end
@@ -1,2 +1,2 @@
1
- test(id: 1, title: "") do
1
+ test(id: 1, title: "empty") do
2
2
  end
@@ -0,0 +1,89 @@
1
+ require 'open3'
2
+ require_relative './spec_helper'
3
+
4
+ module RainforestRubyRuntime
5
+ describe 'rainforest_test', show_output: true do
6
+ subject do
7
+ if browsers
8
+ Open3.capture2e("ruby bin/rainforest_test #{browsers} sample_tests/empty.rb")
9
+ else
10
+ Open3.capture2e('ruby bin/rainforest_test sample_tests/empty.rb')
11
+ end
12
+ end
13
+
14
+ context 'with no browsers' do
15
+ let(:browsers) { nil }
16
+
17
+ it 'defaults to chrome' do
18
+ output, status = subject
19
+
20
+ expect(status).to be_success
21
+ expect(output).to match(/chrome/)
22
+ end
23
+ end
24
+
25
+ context 'with a browser' do
26
+ context 'from the whitelist' do
27
+ let(:browser) { Runner::BROWSERS.sample }
28
+ let(:browsers) { "--browsers #{browser}" }
29
+
30
+ it 'passes it to Runner' do
31
+ output, status = subject
32
+
33
+ expect(status).to be_success
34
+ expect(output).to match(browser)
35
+ end
36
+ end
37
+
38
+ context 'that is unknown' do
39
+ let(:browsers) { "--browsers opera" }
40
+
41
+ it 'exits with status 1' do
42
+ output, status = subject
43
+
44
+ expect(status).to_not be_success
45
+ expect(output).to match(/invalid option: #{browsers}/)
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'with multiple browsers' do
51
+ context 'from the whitelist' do
52
+ let(:browser_values) { Runner::BROWSERS.sample(2) }
53
+ let(:browsers) { "--browsers #{browser_values.join(',')}" }
54
+
55
+ it 'passes them to Runner' do
56
+ output, status = subject
57
+
58
+ expect(status).to be_success
59
+ browser_values.each { |b| expect(output).to match(b) }
60
+ end
61
+ end
62
+
63
+ context 'that is unknown' do
64
+ let(:browsers) { "--browsers #{Runner::BROWSERS.sample},opera" }
65
+
66
+ it 'exits with status 1' do
67
+ output, status = subject
68
+
69
+ expect(status).to_not be_success
70
+ expect(output).to match(/invalid option: --browsers opera/)
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'with multiple files' do
76
+ subject do
77
+ Open3.capture2e('ruby bin/rainforest_test sample_tests/empty.rb sample_tests/simple.rb')
78
+ end
79
+
80
+ it 'should work' do
81
+ output, status = subject
82
+
83
+ expect(status).to be_success
84
+ expect(output).to match(/\[1\] empty/)
85
+ expect(output).to match(/\[1\] My Sample Test/)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,132 @@
1
+ require_relative './spec_helper'
2
+
3
+ module RainforestRubyRuntime
4
+ describe Runner do
5
+ let(:codes) { [read_sample('empty')] }
6
+
7
+ subject { Runner.new(browsers: %i(chrome)) }
8
+
9
+ before do
10
+ allow(subject).to receive(:driver).and_return('selenium')
11
+ end
12
+
13
+ describe "#session_id" do
14
+ before { allow(subject).to receive(:current_driver) { OpenStruct.new(browser: OpenStruct.new(session_id: 'session')) } }
15
+
16
+ it "returns a session id" do
17
+ expect(subject.session_id).to eq('session')
18
+ end
19
+ end
20
+
21
+ describe "#run" do
22
+ describe "with a limited browser set" do
23
+ before do
24
+ allow(subject).to receive(:driver_type).and_return('sauce')
25
+ expect_any_instance_of(Drivers::Sauce).to receive(:to_rspec) do |driver|
26
+ driver.send(:apply_config)
27
+ double(run: nil)
28
+ end
29
+ end
30
+
31
+ it "applies the correct configuration" do
32
+ subject.run(codes)
33
+ expect(Sauce.get_config.browsers).to eq([['Windows 7', 'Chrome', 'latest']])
34
+ end
35
+ end
36
+
37
+ describe "for a rainforest test" do
38
+ let(:codes) { [read_sample('simple')] }
39
+
40
+ it "runs the content of the step" do
41
+ tests = subject.run(codes)
42
+ expect(tests).to all be_instance_of(Test)
43
+ expect($simple_test_was).to eq(:run)
44
+ end
45
+ end
46
+
47
+ describe "for another type of test" do
48
+ it "raises an exception" do
49
+ expect { subject.run(['']) }.to raise_error(WrongReturnValueError)
50
+ end
51
+ end
52
+
53
+ describe "with custom step variables" do
54
+ let(:codes) { [read_sample('step_variables')] }
55
+
56
+ it "makes the variable accessible in the test" do
57
+ subject.run(codes)
58
+ expect($step_variable_1_was).to_not be_nil
59
+ expect($step_variable_2_was).to match(/time is: .*/)
60
+ expect($step_variable_3_was).to eq('1')
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "callbacks" do
66
+ let(:callback) do
67
+ Class.new do
68
+ attr_reader :before_steps, :before_tests, :after_steps, :after_tests
69
+
70
+ def initialize
71
+ @before_steps = []
72
+ @after_steps = []
73
+ @after_tests = []
74
+ @before_tests = []
75
+ end
76
+
77
+ def before_step(step)
78
+ @before_steps << step
79
+ end
80
+
81
+ def after_step(step)
82
+ @after_steps << step
83
+ end
84
+
85
+ def after_test(test)
86
+ @after_tests << test
87
+ end
88
+
89
+ def before_test(test)
90
+ @before_tests << test
91
+ end
92
+ end.new
93
+ end
94
+
95
+ let(:codes) { [read_sample('two_steps')] }
96
+
97
+ subject { Runner.new(browsers: %i(chrome), callback: callback) }
98
+
99
+ it "calls the right method on the callback object" do
100
+ subject.run(codes)
101
+ expect(callback.before_tests.size).to eq(1)
102
+ expect(callback.after_tests.size).to eq(1)
103
+ expect(callback.before_steps.size).to eq(2)
104
+ expect(callback.after_steps.size).to eq(2)
105
+
106
+ expect(callback.before_steps.map(&:id)).to eq([1, 2])
107
+ expect(callback.after_steps.map(&:id)).to eq([1, 2])
108
+
109
+ expect(callback.before_steps.map(&:action)).to eq(["action 1", "action 2"])
110
+ end
111
+
112
+ describe "a partially define callback object" do
113
+ let(:callback) do
114
+ Class.new do
115
+ end.new
116
+ end
117
+
118
+ it "should not raise a method missing exception" do
119
+ subject.run(codes)
120
+ end
121
+ end
122
+ end
123
+
124
+ def read_sample(name)
125
+ File.read(File.expand_path("../sample_tests/#{name}.rb", __dir__))
126
+ end
127
+
128
+ def format_step(code)
129
+ read_sample("one_step").gsub("#REPLACE", code)
130
+ end
131
+ end
132
+ end