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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.yardopts +1 -0
- data/README.md +43 -318
- data/RELEASES.md +353 -0
- data/features/login_helper.feature +15 -0
- data/features/rspec.feature +64 -0
- data/features/saucelabs.feature +7 -2
- data/features/screenshots.feature +21 -0
- data/features/step_definitions/browser_steps.rb +4 -0
- data/features/step_definitions/environment_steps.rb +2 -2
- data/features/step_definitions/login_helper_steps.rb +11 -0
- data/features/step_definitions/rspec_steps.rb +32 -0
- data/features/step_definitions/saucelabs_steps.rb +18 -0
- data/features/step_definitions/screenshot_steps.rb +3 -0
- data/lib/mediawiki_selenium.rb +6 -3
- data/lib/mediawiki_selenium/browser_factory/base.rb +5 -1
- data/lib/mediawiki_selenium/cucumber.rb +4 -0
- data/lib/mediawiki_selenium/{support → cucumber}/env.rb +2 -4
- data/lib/mediawiki_selenium/cucumber/hooks.rb +73 -0
- data/lib/mediawiki_selenium/cucumber/sauce.rb +22 -0
- data/lib/mediawiki_selenium/{support/modules → cucumber}/strict_pending.rb +0 -0
- data/lib/mediawiki_selenium/environment.rb +41 -20
- data/lib/mediawiki_selenium/{support/modules → helpers}/api_helper.rb +15 -0
- data/lib/mediawiki_selenium/{support/modules → helpers}/headless_helper.rb +21 -13
- data/lib/mediawiki_selenium/helpers/login_helper.rb +36 -0
- data/lib/mediawiki_selenium/helpers/screenshot_helper.rb +58 -0
- data/lib/mediawiki_selenium/{support/modules → helpers}/user_factory_helper.rb +0 -0
- data/lib/mediawiki_selenium/pages.rb +3 -0
- data/lib/mediawiki_selenium/{support/pages → pages}/login_page.rb +0 -0
- data/lib/mediawiki_selenium/{support/pages → pages}/random_page.rb +0 -0
- data/lib/mediawiki_selenium/{support/pages → pages}/reset_preferences_page.rb +0 -0
- data/lib/mediawiki_selenium/remote_browser_factory.rb +12 -3
- data/lib/mediawiki_selenium/rspec.rb +51 -0
- data/lib/mediawiki_selenium/rspec/environment.rb +36 -0
- data/lib/mediawiki_selenium/rspec/features.rb +49 -0
- data/lib/mediawiki_selenium/step_definitions/login_steps.rb +1 -3
- data/lib/mediawiki_selenium/version.rb +1 -1
- data/mediawiki_selenium.gemspec +2 -2
- data/spec/environment_spec.rb +12 -0
- data/spec/screenshot_helper_spec.rb +95 -0
- data/templates/tests/browser/features/support/env.rb +2 -1
- metadata +52 -38
- data/lib/mediawiki_selenium/support.rb +0 -4
- data/lib/mediawiki_selenium/support/hooks.rb +0 -92
- data/lib/mediawiki_selenium/support/pages.rb +0 -3
- 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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -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
|
-
|
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
|
data/mediawiki_selenium.gemspec
CHANGED
@@ -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.
|
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
|
data/spec/environment_spec.rb
CHANGED
@@ -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
|