mediawiki_selenium 1.5.0 → 1.6.0

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