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
@@ -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?