mediawiki_selenium 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.yardopts +1 -0
  4. data/README.md +43 -318
  5. data/RELEASES.md +353 -0
  6. data/features/login_helper.feature +15 -0
  7. data/features/rspec.feature +64 -0
  8. data/features/saucelabs.feature +7 -2
  9. data/features/screenshots.feature +21 -0
  10. data/features/step_definitions/browser_steps.rb +4 -0
  11. data/features/step_definitions/environment_steps.rb +2 -2
  12. data/features/step_definitions/login_helper_steps.rb +11 -0
  13. data/features/step_definitions/rspec_steps.rb +32 -0
  14. data/features/step_definitions/saucelabs_steps.rb +18 -0
  15. data/features/step_definitions/screenshot_steps.rb +3 -0
  16. data/lib/mediawiki_selenium.rb +6 -3
  17. data/lib/mediawiki_selenium/browser_factory/base.rb +5 -1
  18. data/lib/mediawiki_selenium/cucumber.rb +4 -0
  19. data/lib/mediawiki_selenium/{support → cucumber}/env.rb +2 -4
  20. data/lib/mediawiki_selenium/cucumber/hooks.rb +73 -0
  21. data/lib/mediawiki_selenium/cucumber/sauce.rb +22 -0
  22. data/lib/mediawiki_selenium/{support/modules → cucumber}/strict_pending.rb +0 -0
  23. data/lib/mediawiki_selenium/environment.rb +41 -20
  24. data/lib/mediawiki_selenium/{support/modules → helpers}/api_helper.rb +15 -0
  25. data/lib/mediawiki_selenium/{support/modules → helpers}/headless_helper.rb +21 -13
  26. data/lib/mediawiki_selenium/helpers/login_helper.rb +36 -0
  27. data/lib/mediawiki_selenium/helpers/screenshot_helper.rb +58 -0
  28. data/lib/mediawiki_selenium/{support/modules → helpers}/user_factory_helper.rb +0 -0
  29. data/lib/mediawiki_selenium/pages.rb +3 -0
  30. data/lib/mediawiki_selenium/{support/pages → pages}/login_page.rb +0 -0
  31. data/lib/mediawiki_selenium/{support/pages → pages}/random_page.rb +0 -0
  32. data/lib/mediawiki_selenium/{support/pages → pages}/reset_preferences_page.rb +0 -0
  33. data/lib/mediawiki_selenium/remote_browser_factory.rb +12 -3
  34. data/lib/mediawiki_selenium/rspec.rb +51 -0
  35. data/lib/mediawiki_selenium/rspec/environment.rb +36 -0
  36. data/lib/mediawiki_selenium/rspec/features.rb +49 -0
  37. data/lib/mediawiki_selenium/step_definitions/login_steps.rb +1 -3
  38. data/lib/mediawiki_selenium/version.rb +1 -1
  39. data/mediawiki_selenium.gemspec +2 -2
  40. data/spec/environment_spec.rb +12 -0
  41. data/spec/screenshot_helper_spec.rb +95 -0
  42. data/templates/tests/browser/features/support/env.rb +2 -1
  43. metadata +52 -38
  44. data/lib/mediawiki_selenium/support.rb +0 -4
  45. data/lib/mediawiki_selenium/support/hooks.rb +0 -92
  46. data/lib/mediawiki_selenium/support/pages.rb +0 -3
  47. data/lib/mediawiki_selenium/support/sauce.rb +0 -26
@@ -27,6 +27,21 @@ module MediawikiSelenium
27
27
  lookup(:mediawiki_api_url, default: -> { api_url_from(lookup(:mediawiki_url)) })
28
28
  end
29
29
 
30
+ # Queries MW via the API to check for missing extension dependencies.
31
+ #
32
+ # @param dependencies [Array] Extension dependencies.
33
+ #
34
+ # @return [Array] Missing extensions.
35
+ #
36
+ def missing_extensions(dependencies)
37
+ return [] if dependencies.empty?
38
+
39
+ extensions = api.meta(:siteinfo, siprop: 'extensions').data['extensions']
40
+ extensions = extensions.map { |ext| ext['name'] }.compact.map(&:downcase)
41
+
42
+ dependencies - extensions
43
+ end
44
+
30
45
  # Ensures the given alternative account exists by attempting to create it
31
46
  # via the API. Any errors related to the account already existing are
32
47
  # swallowed.
@@ -96,21 +96,29 @@ module MediawikiSelenium
96
96
  # @see Environment#teardown
97
97
  #
98
98
  def teardown(info = {})
99
- super
100
- ensure
101
- if @_headless_capture
102
- if info[:status] == :failed
103
- dir = File.absolute_path(headless_capture_path)
104
- FileUtils.mkdir_p(dir)
105
-
106
- filename = "#{(info[:name] || 'scenario').tr("#{File::SEPARATOR}\000", '-')}.mp4"
107
- filename = File.join(dir, filename)
108
-
109
- @_headless_display.video.stop_and_save(filename)
110
- else
111
- @_headless_display.video.stop_and_discard
99
+ artifacts = {}
100
+
101
+ begin
102
+ artifacts = super
103
+ ensure
104
+ if @_headless_capture
105
+ if info[:status] == :failed
106
+ dir = File.absolute_path(headless_capture_path)
107
+ FileUtils.mkdir_p(dir)
108
+
109
+ filename = "#{(info[:name] || 'scenario').tr("#{File::SEPARATOR}\000", '-')}.mp4"
110
+ filename = File.join(dir, filename)
111
+
112
+ @_headless_display.video.stop_and_save(filename)
113
+
114
+ artifacts[filename] = 'video/mp4'
115
+ else
116
+ @_headless_display.video.stop_and_discard
117
+ end
112
118
  end
113
119
  end
120
+
121
+ artifacts
114
122
  end
115
123
  end
116
124
  end
@@ -0,0 +1,36 @@
1
+ require 'uri'
2
+
3
+ module MediawikiSelenium
4
+ # Expepiates logging in to wikis by authenticating with the MW API and
5
+ # transfering the returned cookie to the current browser.
6
+ #
7
+ module LoginHelper
8
+ # Authenticate with the current wiki's API and save the resulting cookie
9
+ # in the current browser.
10
+ #
11
+ # @param cookies [Hash] Extra cookies to set.
12
+ #
13
+ def log_in(cookies = {})
14
+ visit_wiki do |url|
15
+ uri = URI.parse(url)
16
+
17
+ options = {}
18
+ options[:domain] = uri.host unless ['localhost', '127.0.0.1'].include?(uri.host)
19
+
20
+ api.cookies.each do |cookie|
21
+ browser.cookies.add cookie.name, cookie.value, options.merge({
22
+ secure: cookie.secure,
23
+ path: cookie.path,
24
+ expires: cookie.expires
25
+ })
26
+ end
27
+
28
+ cookies.each do |name, value|
29
+ browser.cookies.add name.to_s, value, options
30
+ end
31
+
32
+ browser.refresh
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,58 @@
1
+ require 'fileutils'
2
+
3
+ module MediawikiSelenium
4
+ # Adds support to {Environment} for taking screenshots of the current
5
+ # browser window. If `screenshot_failures` is set to `true` for the current
6
+ # environment, one is automatically taken at the end of each failed
7
+ # test case.
8
+ #
9
+ module ScreenshotHelper
10
+ # Takes a screenshot of the given browser window and returns the path
11
+ # to the saved image.
12
+ #
13
+ # @param browser [Watir::Browser] Browser to take a sceeenshot of.
14
+ # @param name [String] Base name to use for the saved image file.
15
+ #
16
+ # @return [String] Path to the new screenshot.
17
+ #
18
+ def screenshot(browser, name)
19
+ dir = screenshot_failures_path
20
+ FileUtils.mkdir_p dir
21
+
22
+ name = name.tr("#{File::SEPARATOR}\000", '-')
23
+
24
+ path = File.join(dir, "#{name}.png")
25
+ browser.screenshot.save(path)
26
+
27
+ path
28
+ end
29
+
30
+ # Takes screenshots for failed tests.
31
+ #
32
+ # @param info [Hash] Test case information.
33
+ #
34
+ def teardown(info = {})
35
+ screenshots = []
36
+
37
+ artifacts = super(info) do |browser|
38
+ if info[:status] == :failed && screenshot_failures?
39
+ screenshots << screenshot(browser, info[:name] || 'scenario')
40
+ end
41
+
42
+ yield browser if block_given?
43
+ end
44
+
45
+ artifacts.merge(screenshots.each.with_object({}) { |img, arts| arts[img] = 'image/png' })
46
+ end
47
+
48
+ private
49
+
50
+ def screenshot_failures?
51
+ lookup(:screenshot_failures, default: false).to_s == 'true'
52
+ end
53
+
54
+ def screenshot_failures_path
55
+ lookup(:screenshot_failures_path, default: 'screenshots')
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ require 'mediawiki_selenium/pages/login_page'
2
+ require 'mediawiki_selenium/pages/random_page'
3
+ require 'mediawiki_selenium/pages/reset_preferences_page'
@@ -1,5 +1,3 @@
1
- require 'mediawiki_selenium/support/sauce'
2
-
3
1
  require 'rest_client'
4
2
  require 'uri'
5
3
 
@@ -17,6 +15,9 @@ module MediawikiSelenium
17
15
  URL = 'http://ondemand.saucelabs.com/wd/hub'
18
16
 
19
17
  class << self
18
+ # @attr [Array] SauceLabs job IDs from the most recent sessions.
19
+ attr_accessor :last_session_ids
20
+
20
21
  def extend_object(base)
21
22
  return if base.is_a?(self)
22
23
 
@@ -42,6 +43,10 @@ module MediawikiSelenium
42
43
  # Submits status and Jenkins build info to Sauce Labs.
43
44
  #
44
45
  def teardown(env, status)
46
+ artifacts = super
47
+
48
+ self.class.last_session_ids = []
49
+
45
50
  each do |browser|
46
51
  sid = browser.driver.session_id
47
52
  url = browser.driver.send(:bridge).http.send(:server_url)
@@ -61,8 +66,12 @@ module MediawikiSelenium
61
66
  }.to_json
62
67
  )
63
68
 
64
- Cucumber::Formatter::Sauce.current_session_id = sid
69
+ self.class.last_session_ids << sid
70
+
71
+ artifacts["http://saucelabs.com/jobs/#{sid}"] = 'text/url'
65
72
  end
73
+
74
+ artifacts
66
75
  end
67
76
 
68
77
  protected
@@ -0,0 +1,51 @@
1
+ require 'rspec/core'
2
+ require 'mediawiki_selenium'
3
+ require 'mediawiki_selenium/rspec/features'
4
+
5
+ module MediawikiSelenium
6
+ module RSpec
7
+ # Returns a name for the given example metadata, derived from its example
8
+ # groups and description.
9
+ #
10
+ # @param metadata [RSpec::Core::Metadata, Hash] Base or nested metadata.
11
+ #
12
+ # @return [String]
13
+ #
14
+ def self.example_name(metadata)
15
+ name = metadata[:example_group] ? "#{example_name(metadata[:example_group])} " : ''
16
+ name += metadata[:description_args].first.to_s if metadata[:description_args].any?
17
+ name
18
+ end
19
+
20
+ # Returns a status for the given RSpec example result.
21
+ #
22
+ # @param result [Object] Result of `example.run`.
23
+ #
24
+ # @return [:passed, :failed, :skipped]
25
+ #
26
+ def self.example_status(result)
27
+ case result
28
+ when Exception
29
+ :failed
30
+ when String
31
+ :skipped
32
+ else
33
+ :passed
34
+ end
35
+ end
36
+
37
+ autoload :Environment, 'mediawiki_selenium/rspec/environment'
38
+ end
39
+ end
40
+
41
+ RSpec.configure do |config|
42
+ config.include MediawikiSelenium::RSpec::Environment
43
+
44
+ config.around(:each) do |example|
45
+ name = MediawikiSelenium::RSpec.example_name(example.metadata)
46
+
47
+ mw.setup(name: name)
48
+ result = example.run
49
+ mw.teardown(name: name, status: MediawikiSelenium::RSpec.example_status(result))
50
+ end
51
+ end
@@ -0,0 +1,36 @@
1
+ module MediawikiSelenium
2
+ module RSpec
3
+ module Environment
4
+ # Sets up and returns a new default environment.
5
+ #
6
+ # @return [Environment]
7
+ #
8
+ def mw
9
+ @_mw ||= MediawikiSelenium::Environment.load_default.extend(
10
+ MediawikiSelenium::ApiHelper,
11
+ MediawikiSelenium::PageFactory,
12
+ MediawikiSelenium::ScreenshotHelper,
13
+ MediawikiSelenium::UserFactoryHelper
14
+ )
15
+ end
16
+
17
+ private
18
+
19
+ # Allow for indirect calls to the {Environment} object.
20
+ #
21
+ # @see mw
22
+ #
23
+ def method_missing(name, *args, &block)
24
+ mw.respond_to?(name) ? mw.__send__(name, *args, &block) : super
25
+ end
26
+
27
+ # Allow for indirect calls to the {Environment} object.
28
+ #
29
+ # @see mw
30
+ #
31
+ def respond_to_missing?(name, include_all = false)
32
+ mw.respond_to?(name) || super
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ # This was "derived" from Capybara's RSpec integration
2
+ #
3
+ # https://github.com/jnicklas/capybara/blob/master/lib/capybara/rspec/features.rb
4
+ #
5
+ if RSpec::Core::Version::STRING.to_f >= 3.0
6
+ RSpec.shared_context "MW-Selenium Features", :mw_selenium_feature => true do
7
+ instance_eval do
8
+ alias background before
9
+ alias given let
10
+ alias given! let!
11
+ end
12
+ end
13
+
14
+ RSpec.configure do |config|
15
+ config.alias_example_group_to :feature, :mw_selenium_feature => true, :type => :feature
16
+ config.alias_example_to :scenario
17
+ config.alias_example_to :xscenario, :skip => "Temporarily disabled with xscenario"
18
+ config.alias_example_to :fscenario, :focus => true
19
+ end
20
+ else
21
+ module MediawikiSelenium::RSpec
22
+ module Features
23
+ def self.included(base)
24
+ base.instance_eval do
25
+ alias :background :before
26
+ alias :scenario :it
27
+ alias :xscenario :xit
28
+ alias :given :let
29
+ alias :given! :let!
30
+ alias :feature :describe
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+
37
+ def self.feature(*args, &block)
38
+ options = if args.last.is_a?(Hash) then args.pop else {} end
39
+ options[:mw_selenium_feature] = true
40
+ options[:type] = :feature
41
+ options[:caller] ||= caller
42
+ args.push(options)
43
+
44
+ #call describe on RSpec in case user has expose_dsl_globally set to false
45
+ RSpec.describe(*args, &block)
46
+ end
47
+
48
+ RSpec.configuration.include MediawikiSelenium::RSpec::Features, :mw_selenium_feature => true
49
+ end
@@ -1,5 +1,3 @@
1
1
  Given(/^I am logged in(?: as (\w+))?$/) do |user|
2
- as_user(user) do |user, password|
3
- visit(LoginPage).login_with(user, password)
4
- end
2
+ as_user(user) { log_in }
5
3
  end
@@ -1,3 +1,3 @@
1
1
  module MediawikiSelenium
2
- VERSION = '1.5.0'
2
+ VERSION = '1.6.0'
3
3
  end
@@ -30,9 +30,10 @@ Gem::Specification.new do |spec|
30
30
  spec.add_runtime_dependency 'cucumber', '~> 1.3', '>= 1.3.20'
31
31
  spec.add_runtime_dependency 'headless', '~> 2.0', '>= 2.1.0'
32
32
  spec.add_runtime_dependency 'json', '~> 1.8', '>= 1.8.1'
33
- spec.add_runtime_dependency 'mediawiki_api', '~> 0.4', '>= 0.4.1'
33
+ spec.add_runtime_dependency 'mediawiki_api', '~> 0.5', '>= 0.5.0'
34
34
  spec.add_runtime_dependency 'page-object', '~> 1.0'
35
35
  spec.add_runtime_dependency 'rest-client', '~> 1.6', '>= 1.6.7'
36
+ spec.add_runtime_dependency 'rspec-core', '~> 2.14', '>= 2.14.4'
36
37
  spec.add_runtime_dependency 'rspec-expectations', '~> 2.14', '>= 2.14.4'
37
38
  spec.add_runtime_dependency 'syntax', '~> 1.2', '>= 1.2.0'
38
39
  spec.add_runtime_dependency 'thor', '~> 0.19', '>= 0.19.1'
@@ -41,6 +42,5 @@ Gem::Specification.new do |spec|
41
42
  spec.add_development_dependency 'yard', '~> 0.8', '>= 0.8.7.4'
42
43
  spec.add_development_dependency 'redcarpet', '~> 3.2', '>= 3.2.0'
43
44
  spec.add_development_dependency 'rubocop', '~> 0.29.1'
44
- spec.add_development_dependency 'rspec-core', '~> 2.14', '>= 2.14.4'
45
45
  spec.add_development_dependency 'rspec-mocks', '~> 2.14', '>= 2.14.4'
46
46
  end
@@ -396,6 +396,18 @@ module MediawikiSelenium
396
396
  end
397
397
  end
398
398
 
399
+ describe '#setup' do
400
+ subject { env.setup(name: 'Some test name') }
401
+
402
+ it 'configures the browser factory to accept `:job_name` and `:build_number`' do
403
+ expect(env.browser_factory).to receive(:configure)
404
+ expect(env.browser_factory).to receive(:configure).with(:job_name)
405
+ expect(env.browser_factory).to receive(:configure).with(:build_number)
406
+
407
+ subject
408
+ end
409
+ end
410
+
399
411
  describe '#teardown' do
400
412
  subject { env.teardown(status: status) }
401
413
 
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ module MediawikiSelenium
4
+ describe ScreenshotHelper do
5
+ let(:env) { Environment.new(config).extend(ScreenshotHelper) }
6
+
7
+ let(:config) { {} }
8
+
9
+ describe '#screenshot' do
10
+ subject { env.screenshot(browser, name) }
11
+
12
+ let(:config) { { screenshot_failures_path: 'screenshots' } }
13
+
14
+ let(:browser) { double('Watir::Browser') }
15
+ let(:name) { 'Some test name' }
16
+
17
+ it 'takes a screenshot of the browser' do
18
+ screenshot = double('Watir::Screenshot')
19
+ expect(browser).to receive(:screenshot).and_return(screenshot)
20
+ expect(screenshot).to receive(:save).with('screenshots/Some test name.png')
21
+
22
+ subject
23
+ end
24
+
25
+ it 'returns the path to the image file' do
26
+ screenshot = double('Watir::Screenshot')
27
+ allow(browser).to receive(:screenshot).and_return(screenshot)
28
+ allow(screenshot).to receive(:save)
29
+
30
+ expect(subject).to eq('screenshots/Some test name.png')
31
+ end
32
+ end
33
+
34
+ describe '#teardown' do
35
+ subject { env.teardown(info) }
36
+
37
+ let(:info) { { name: 'Some test name', status: status } }
38
+ let(:status) { :passed }
39
+
40
+ let(:browser) { double(Watir::Browser) }
41
+
42
+ before do
43
+ # mock {Environment#teardown}
44
+ allow(env.browser_factory).to receive(:each) { |&blk| [browser].each(&blk) }
45
+ allow(env.browser_factory).to receive(:teardown)
46
+ allow(browser).to receive(:close)
47
+ end
48
+
49
+ it 'invokes the given block with each browser' do
50
+ allow(env).to receive(:screenshot)
51
+
52
+ expect { |blk| env.teardown(info, &blk) }.to yield_with_args(browser)
53
+ end
54
+
55
+ context 'configured to take screenshots of failures' do
56
+ let(:config) { { screenshot_failures: true } }
57
+
58
+ context 'when the test case has failed' do
59
+ let(:status) { :failed }
60
+
61
+ it 'takes a screenshot of each browser' do
62
+ expect(env).to receive(:screenshot).with(browser, 'Some test name')
63
+ subject
64
+ end
65
+
66
+ it 'includes each screenshot in the returned artifacts' do
67
+ allow(env).to receive(:screenshot).and_return('Some test name.png')
68
+
69
+ expect(subject).to include('Some test name.png')
70
+ expect(subject['Some test name.png']).to eq('image/png')
71
+ end
72
+ end
73
+
74
+ context 'when the test case has not failed' do
75
+ let(:status) { :passed }
76
+
77
+ it 'takes no screenshot' do
78
+ expect(env).not_to receive(:screenshot)
79
+ subject
80
+ end
81
+ end
82
+ end
83
+
84
+ context 'configured to not take screenshots of failures' do
85
+ let(:config) { { screenshot_failures: false } }
86
+ let(:status) { :failed }
87
+
88
+ it 'takes no screenshot' do
89
+ expect(env).not_to receive(:screenshot)
90
+ subject
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end