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
@@ -0,0 +1,15 @@
1
+ Feature: Login Helper
2
+
3
+ Background:
4
+ Given I have configured my environment from `ENV` and with:
5
+ """
6
+ user_factory: true
7
+ """
8
+ And I have set "MEDIAWIKI_API_URL" in my shell
9
+ And I have set "MEDIAWIKI_URL" in my shell
10
+ And I am using the user factory
11
+ And I am using the login helper
12
+
13
+ Scenario: Login helper logs in in via the API
14
+ When I log in via the helper method
15
+ Then I should be logged in to the wiki
@@ -0,0 +1,64 @@
1
+ @integration
2
+ Feature: RSpec integration
3
+
4
+ As a developer, I want the option to use a test framework with less
5
+ indirection so that my tests are more straightforward to implement and
6
+ easier to reason about.
7
+
8
+ Background:
9
+ Given I have set up my `environments.yml`
10
+ And the following RSpec support file:
11
+ """
12
+ require 'bundler/setup'
13
+ require 'mediawiki_selenium/rspec'
14
+ """
15
+
16
+ Scenario: RSpec examples have access to the `Environment` via `#mw`
17
+ Given the following RSpec examples:
18
+ """
19
+ describe 'my feature' do
20
+ describe 'my component' do
21
+ it 'can access `mw` to implement its tests' do
22
+ expect(mw).to be_a(MediawikiSelenium::Environment)
23
+ end
24
+ end
25
+ end
26
+ """
27
+ When I run `rspec` against my examples
28
+ Then I should see 1 passing example
29
+
30
+ Scenario: Calls to `mw` methods can be unqualified/indirect
31
+ Given the following RSpec examples:
32
+ """
33
+ describe 'my feature' do
34
+ describe 'my component' do
35
+ it 'can call `mw` methods indirectly via `self`' do
36
+ expect(mw).to respond_to(:on_wiki)
37
+ expect(self).to respond_to(:on_wiki)
38
+ end
39
+ end
40
+ end
41
+ """
42
+ When I run `rspec` against my examples
43
+ Then I should see 1 passing example
44
+
45
+ Scenario: An alternative feature/scenario syntax is supported
46
+ Given the following RSpec examples:
47
+ """
48
+ feature 'my feature' do
49
+ background do
50
+ # do common stuff
51
+ @stuff = 'stuff'
52
+ end
53
+
54
+ scenario 'my scenario' do
55
+ expect(@stuff).to eq('stuff')
56
+ end
57
+
58
+ scenario 'my other scenario' do
59
+ expect(@stuff).to eq('stuff')
60
+ end
61
+ end
62
+ """
63
+ When I run `rspec` against my examples
64
+ Then I should see 2 passing examples
@@ -6,9 +6,14 @@ Feature: Remote browser sessions through SauceLabs
6
6
  browser: firefox
7
7
  platform: linux
8
8
  """
9
+ And I have set "SAUCE_ONDEMAND_USERNAME" in my shell
10
+ And I have set "SAUCE_ONDEMAND_ACCESS_KEY" in my shell
9
11
 
10
12
  Scenario: SauceLabs sessions start just like a normal browser
11
- Given I have set "SAUCE_ONDEMAND_USERNAME" in my shell
12
- And I have set "SAUCE_ONDEMAND_ACCESS_KEY" in my shell
13
13
  When I start interacting with the browser
14
14
  Then the browser is open
15
+
16
+ Scenario: Failed scenarios mark SauceLabs jobs as failed
17
+ Given I have started interacting with the browser
18
+ When the scenario fails
19
+ Then the SauceLabs job should be marked as failed
@@ -0,0 +1,21 @@
1
+ @integration
2
+ Feature: Screenshots of failed scenarios
3
+
4
+ As a developer writing and running tests, it would be helpful to have a
5
+ screenshot of the browser window at the point where each scenario has
6
+ failed.
7
+
8
+ Background:
9
+ Given I have configured my environment with:
10
+ """
11
+ screenshot_failures: true
12
+ screenshot_failures_path: tmp/screenshots
13
+ """
14
+ And the "tmp/screenshots" directory exists
15
+ And I am using the screenshot helper
16
+
17
+ Scenario: A screenshot is taken for failed scenarios
18
+ Given the current scenario name is "Some scenario"
19
+ And I have started a browser
20
+ When the scenario fails
21
+ Then the file "tmp/screenshots/Some scenario.png" should exist
@@ -8,6 +8,10 @@ Given(/^I am using a local browser$/) do
8
8
  allow(@env).to receive(:remote?).and_return(false)
9
9
  end
10
10
 
11
+ When(/^I have started interacting with the browser$/) do
12
+ @env.browser
13
+ end
14
+
11
15
  When(/^I start interacting with the browser$/) do
12
16
  @env.browser
13
17
  end
@@ -3,7 +3,7 @@ Before do
3
3
  end
4
4
 
5
5
  After do
6
- @env.teardown unless @env.nil?
6
+ @env.teardown unless @env.nil? || @env_torndown
7
7
  @tmp_files.each { |path| FileUtils.rm_r(path) if File.exist?(path) }
8
8
  end
9
9
 
@@ -56,7 +56,7 @@ When(/^the scenario ends$/) do
56
56
  sleep 0.3
57
57
  @env.teardown(name: @scenario_name, status: @scenario_status)
58
58
  ensure
59
- @env = nil
59
+ @env_torndown = true
60
60
  end
61
61
  end
62
62
 
@@ -0,0 +1,11 @@
1
+ Given(/^I am using the login helper$/) do
2
+ @env.extend(MediawikiSelenium::LoginHelper)
3
+ end
4
+
5
+ When(/^I log in via the helper method$/) do
6
+ @env.log_in
7
+ end
8
+
9
+ Then(/^I should be logged in to the wiki$/) do
10
+ expect(@env.browser.element(id: 'pt-logout')).to be_present
11
+ end
@@ -0,0 +1,32 @@
1
+ require 'yaml'
2
+ require 'open3'
3
+
4
+ Given(/^I have set up my `environments\.yml`$/) do
5
+ step 'the "tmp/rspec/spec" directory exists'
6
+ File.open('tmp/rspec/environments.yml', 'w') { |io| io.write(YAML.dump({ 'default' => {} })) }
7
+ end
8
+
9
+ Given(/^the following RSpec support file:$/) do |content|
10
+ File.open('tmp/rspec/spec/spec_helper.rb', 'w') { |io| io.write(content) }
11
+ end
12
+
13
+ Given(/^the following RSpec examples:$/) do |content|
14
+ content = "require 'spec_helper'\n#{content}"
15
+ File.open('tmp/rspec/spec/examples_spec.rb', 'w') { |io| io.write(content) }
16
+ end
17
+
18
+ Given(/^$the following "(.+)" file for use with RSpec$/) do |path, content|
19
+ File.open(File.join('tmp/rspec', path), 'w') { |io| io.write(content) }
20
+ end
21
+
22
+ When(/^I run `rspec` against my examples$/) do
23
+ env = { 'MEDIAWIKI_ENVIRONMENT' => 'default' }
24
+ cmd = "'#{Gem.bin_path('bundler', 'bundle')}' exec rspec"
25
+
26
+ @rspec_output, @rspec_status = Open3.capture2e(env, cmd, chdir: 'tmp/rspec')
27
+ end
28
+
29
+ Then(/^I should see (\d+) passing examples?$/) do |n|
30
+ expect(@rspec_status).to be_success, "Examples did not pass. Output:\n---\n#{@rspec_output}---\n"
31
+ expect(@rspec_output).to match(/^#{n} examples?, 0 failures/)
32
+ end
@@ -0,0 +1,18 @@
1
+ require 'net/https'
2
+
3
+ Then(/^the SauceLabs job should be marked as failed$/) do
4
+ job_id = @env.browser.driver.session_id
5
+
6
+ username = @env[:sauce_ondemand_username]
7
+ access_key = @env[:sauce_ondemand_access_key]
8
+
9
+ job_uri = URI.parse("https://saucelabs.com/rest/v1/#{username}/jobs/#{job_id}")
10
+
11
+ job_uri.user = username
12
+ job_uri.password = access_key
13
+
14
+ job = JSON.parse(Net::HTTP.get(job_uri))
15
+
16
+ expect(job).to include('passed')
17
+ expect(job['passed']).to be(false)
18
+ end
@@ -0,0 +1,3 @@
1
+ Given(/^I am using the screenshot helper$/) do
2
+ @env.extend(MediawikiSelenium::ScreenshotHelper)
3
+ end
@@ -1,14 +1,17 @@
1
1
  module MediawikiSelenium
2
2
  autoload :VERSION, 'mediawiki_selenium/version'
3
- autoload :ApiHelper, 'mediawiki_selenium/support/modules/api_helper'
3
+ autoload :ApiHelper, 'mediawiki_selenium/helpers/api_helper'
4
4
  autoload :BrowserFactory, 'mediawiki_selenium/browser_factory'
5
5
  autoload :ConfigurationError, 'mediawiki_selenium/configuration_error'
6
6
  autoload :Environment, 'mediawiki_selenium/environment'
7
- autoload :HeadlessHelper, 'mediawiki_selenium/support/modules/headless_helper'
7
+ autoload :HeadlessHelper, 'mediawiki_selenium/helpers/headless_helper'
8
8
  autoload :Initializer, 'mediawiki_selenium/initializer'
9
+ autoload :LoginHelper, 'mediawiki_selenium/helpers/login_helper'
9
10
  autoload :PageFactory, 'mediawiki_selenium/page_factory'
10
11
  autoload :Raita, 'mediawiki_selenium/raita'
11
12
  autoload :RemoteBrowserFactory, 'mediawiki_selenium/remote_browser_factory'
13
+ autoload :ScreenshotHelper, 'mediawiki_selenium/helpers/screenshot_helper'
14
+ autoload :StrictPending, 'mediawiki_selenium/cucumber/strict_pending'
12
15
  autoload :UserFactory, 'mediawiki_selenium/user_factory'
13
- autoload :UserFactoryHelper, 'mediawiki_selenium/support/modules/user_factory_helper'
16
+ autoload :UserFactoryHelper, 'mediawiki_selenium/helpers/user_factory_helper'
14
17
  end
@@ -200,8 +200,12 @@ module MediawikiSelenium
200
200
  # @param _env [Environment] Environment.
201
201
  # @param _status [Symbol] Status of the executed scenario.
202
202
  #
203
+ # @return [Hash{String => String}] Artifacts.
204
+ #
205
+ # @see Environment#teardown
206
+ #
203
207
  def teardown(_env, _status)
204
- # abstract
208
+ {}
205
209
  end
206
210
 
207
211
  protected
@@ -0,0 +1,4 @@
1
+ require 'mediawiki_selenium/cucumber/env'
2
+ require 'mediawiki_selenium/cucumber/hooks'
3
+ require 'mediawiki_selenium/cucumber/sauce'
4
+ require 'mediawiki_selenium/cucumber/strict_pending'
@@ -1,14 +1,12 @@
1
- # before all
2
1
  require 'bundler/setup'
3
2
  require 'page-object/page_factory'
4
3
  require 'watir-webdriver'
5
4
 
6
- require 'mediawiki_selenium/support/modules/api_helper'
7
- require 'mediawiki_selenium/support/modules/strict_pending'
8
-
9
5
  World { MediawikiSelenium::Environment.load_default }
10
6
 
11
7
  World(MediawikiSelenium::ApiHelper)
8
+ World(MediawikiSelenium::LoginHelper)
12
9
  World(MediawikiSelenium::PageFactory)
10
+ World(MediawikiSelenium::ScreenshotHelper)
13
11
  World(MediawikiSelenium::StrictPending)
14
12
  World(MediawikiSelenium::UserFactoryHelper)
@@ -0,0 +1,73 @@
1
+ Before('@custom-browser') do |scenario|
2
+ @scenario = scenario
3
+ end
4
+
5
+ AfterConfiguration do |config|
6
+ # Install a formatter that can be used to show feature-related warnings
7
+ pretty_format, io = config.formats.find { |(format, _io)| format == 'pretty' }
8
+ config.formats << ['MediawikiSelenium::WarningsFormatter', io] if pretty_format
9
+
10
+ # Set up Raita logging if RAITA_DB_URL is set. Include any useful
11
+ # environment variables that Jenkins would have set.
12
+ env = MediawikiSelenium::Environment.load_default
13
+ raita_url = env.lookup(:raita_url, default: nil)
14
+
15
+ if raita_url
16
+ raita_build = MediawikiSelenium::Raita.build_from(env)
17
+ config.formats << ['MediawikiSelenium::Raita::Logger', { url: raita_url, build: raita_build }]
18
+ end
19
+ end
20
+
21
+ # Determine scenario name and setup the environment
22
+ Before do |scenario|
23
+ @scenario_name =
24
+ if scenario.respond_to? :feature
25
+ "#{scenario.feature.title}: #{scenario.title}"
26
+ elsif scenario.respond_to? :scenario_outline
27
+ outline = scenario.scenario_outline
28
+ "#{outline.feature.title}: #{outline.title}: #{scenario.name}"
29
+ else
30
+ scenario.name
31
+ end
32
+
33
+ setup(name: @scenario_name)
34
+ end
35
+
36
+ # Enforce a dependency check for all scenarios tagged with @extension- tags
37
+ Before do |scenario|
38
+ # Backgrounds themselves don't have tags, so get them from the feature
39
+ if scenario.is_a?(Cucumber::Ast::Background)
40
+ tag_source = scenario.feature
41
+ else
42
+ tag_source = scenario
43
+ end
44
+
45
+ tags = tag_source.source_tag_names
46
+ dependencies = tags.map { |tag| tag.match(/^@extension-(.+)$/) { |m| m[1].downcase } }.compact
47
+ missing = missing_extensions(dependencies)
48
+
49
+ if missing.any?
50
+ scenario.skip_invoke!
51
+
52
+ if scenario.feature.respond_to?(:mw_warn)
53
+ warning = "Skipped feature due to missing MediaWiki extensions: #{missing.join(", ")}"
54
+ scenario.feature.mw_warn(warning, 'missing MediaWiki extensions')
55
+ end
56
+ end
57
+ end
58
+
59
+ Before do
60
+ # Create a unique random string for this scenario
61
+ @random_string = Random.new.rand.to_s
62
+ end
63
+
64
+ After do |scenario|
65
+ artifacts =
66
+ if scenario.respond_to?(:status)
67
+ teardown(name: @scenario_name, status: scenario.status)
68
+ else
69
+ teardown(name: @scenario_name)
70
+ end
71
+
72
+ artifacts.each { |path, mime_type| embed(path, mime_type) }
73
+ end
@@ -0,0 +1,22 @@
1
+ require 'cucumber/formatter/junit'
2
+ require 'mediawiki_selenium/remote_browser_factory'
3
+
4
+ module Cucumber::Formatter
5
+ class Sauce < Junit
6
+ private
7
+
8
+ def format_exception(exception)
9
+ sids = MediawikiSelenium::RemoteBrowserFactory.last_session_ids
10
+
11
+ if sids.nil? || sids.empty?
12
+ message = 'Uh-oh. Could not find link to Sauce Labs job URL.'
13
+ else
14
+ message = sids.map { |sid| "Sauce Labs job URL: http://saucelabs.com/jobs/#{sid}\n" }.join
15
+ end
16
+
17
+ msgs = [message] + ["#{exception.message} (#{exception.class})"] + exception.backtrace
18
+
19
+ msgs.join("\n")
20
+ end
21
+ end
22
+ end
@@ -298,7 +298,7 @@ module MediawikiSelenium
298
298
  # @option options [Symbol] :id Alternative ID.
299
299
  # @option options [Object] :default Default if no configuration is found.
300
300
  #
301
- # @return [Array<String>]
301
+ # @return [Hash{Symbol => String}]
302
302
  #
303
303
  # @see #lookup
304
304
  #
@@ -346,12 +346,43 @@ module MediawikiSelenium
346
346
  RemoteBrowserFactory::REQUIRED_CONFIG.all? { |name| lookup(name, default: false) }
347
347
  end
348
348
 
349
+ # Executes setup tasks, annotating the Selenium session with any
350
+ # configured `job_name` and `build_number`.
351
+ #
352
+ # Additional helpers may perform their own tasks by implementing this
353
+ # method.
354
+ #
355
+ # @example Setup the environment before each scenario starts
356
+ # Before do |scenario|
357
+ # setup(name: scenario.name)
358
+ # end
359
+ #
360
+ # @param info [Hash] Hash of test case information.
361
+ #
362
+ def setup(info = {})
363
+ browser_factory.configure do |options|
364
+ options[:desired_capabilities][:name] = info[:name] || 'scenario'
365
+ end
366
+
367
+ browser_factory.configure(:job_name) do |job, options|
368
+ options[:desired_capabilities][:name] += " #{job}"
369
+ end
370
+
371
+ browser_factory.configure(:build_number) do |build, options|
372
+ options[:desired_capabilities][:name] += "##{build}"
373
+ end
374
+ end
375
+
349
376
  # Executes teardown tasks including instructing all browser factories to
350
377
  # close any open browsers and perform their own teardown tasks.
351
378
  #
379
+ # Teardown tasks may produce artifacts, which will be returned in the form
380
+ # `{ path => mime_type }`.
381
+ #
352
382
  # @example Teardown environment resources after each scenario completes
353
383
  # After do |scenario|
354
- # teardown(name: scenario.name, status: scenario.status)
384
+ # artifacts = teardown(name: scenario.name, status: scenario.status)
385
+ # artifacts.each { |path, mime_type| embed(path, mime_type) }
355
386
  # end
356
387
  #
357
388
  # @param info [Hash] Hash of test case information.
@@ -359,32 +390,22 @@ module MediawikiSelenium
359
390
  # @yield [browser]
360
391
  # @yieldparam browser [Watir::Browser] Browser object, before it's closed.
361
392
  #
393
+ # @return [Hash{String => String}] Hash of path/mime-type artifacts.
394
+ #
362
395
  def teardown(info = {})
396
+ artifacts = {}
397
+
363
398
  @_factory_cache.each do |(_, browser_name), factory|
364
399
  factory.each do |browser|
365
400
  yield browser if block_given?
366
401
  browser.close unless keep_browser_open? && browser_name != :phantomjs
367
402
  end
368
403
 
369
- factory.teardown(self, info[:status] || :passed)
404
+ factory_artifacts = factory.teardown(self, info[:status] || :passed)
405
+ artifacts.merge!(factory_artifacts) if factory_artifacts.is_a?(Hash)
370
406
  end
371
- end
372
407
 
373
- # Returns a name from the given scenario.
374
- #
375
- # @param scenario [Cucumber::Ast::Scenario]
376
- #
377
- # @return [String]
378
- #
379
- def test_name(scenario)
380
- if scenario.respond_to? :feature
381
- "#{scenario.feature.title}: #{scenario.title}"
382
- elsif scenario.respond_to? :scenario_outline
383
- outline = scenario.scenario_outline
384
- "#{outline.feature.title}: #{outline.title}: #{scenario.name}"
385
- else
386
- scenario.name
387
- end
408
+ artifacts
388
409
  end
389
410
 
390
411
  # Returns the current value for `:mediawiki_user` or the value for the
@@ -416,7 +437,7 @@ module MediawikiSelenium
416
437
  # @yield [url]
417
438
  # @yieldparam url [String] Wiki URL.
418
439
  #
419
- def visit_wiki(id)
440
+ def visit_wiki(id = nil)
420
441
  on_wiki(id) do |url|
421
442
  browser.goto url
422
443
  yield url if block_given?