mediawiki_selenium 0.4.3 → 1.0.0.pre.1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitreview +1 -1
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +1 -1
  6. data/README.md +108 -55
  7. data/bin/mediawiki-selenium-init +5 -0
  8. data/lib/mediawiki_selenium.rb +10 -19
  9. data/lib/mediawiki_selenium/browser_factory.rb +24 -0
  10. data/lib/mediawiki_selenium/browser_factory/base.rb +212 -0
  11. data/lib/mediawiki_selenium/browser_factory/chrome.rb +27 -0
  12. data/lib/mediawiki_selenium/browser_factory/firefox.rb +34 -0
  13. data/lib/mediawiki_selenium/browser_factory/phantomjs.rb +21 -0
  14. data/lib/mediawiki_selenium/configuration_error.rb +4 -0
  15. data/lib/mediawiki_selenium/environment.rb +494 -0
  16. data/lib/mediawiki_selenium/initializer.rb +19 -0
  17. data/lib/mediawiki_selenium/page_factory.rb +38 -0
  18. data/lib/mediawiki_selenium/remote_browser_factory.rb +87 -0
  19. data/lib/mediawiki_selenium/step_definitions.rb +5 -0
  20. data/lib/mediawiki_selenium/support.rb +3 -0
  21. data/lib/mediawiki_selenium/support/env.rb +3 -127
  22. data/lib/mediawiki_selenium/support/hooks.rb +23 -34
  23. data/lib/mediawiki_selenium/support/modules/api_helper.rb +44 -5
  24. data/lib/mediawiki_selenium/support/pages.rb +4 -0
  25. data/lib/mediawiki_selenium/support/pages/api_page.rb +1 -0
  26. data/lib/mediawiki_selenium/support/pages/login_page.rb +3 -12
  27. data/lib/mediawiki_selenium/support/pages/random_page.rb +2 -12
  28. data/lib/mediawiki_selenium/support/pages/reset_preferences_page.rb +3 -12
  29. data/lib/mediawiki_selenium/version.rb +1 -1
  30. data/mediawiki_selenium.gemspec +9 -3
  31. data/spec/api_helper_spec.rb +84 -0
  32. data/spec/browser_factory/base_spec.rb +211 -0
  33. data/spec/browser_factory/chrome_spec.rb +36 -0
  34. data/spec/browser_factory/firefox_spec.rb +60 -0
  35. data/spec/browser_factory/phantomjs_spec.rb +38 -0
  36. data/spec/environment_spec.rb +474 -0
  37. data/spec/page_factory_spec.rb +61 -0
  38. data/spec/remote_browser_factory_spec.rb +50 -0
  39. data/spec/spec_helper.rb +4 -0
  40. data/templates/tests/browser/environments.yml +35 -0
  41. data/templates/tests/browser/features/support/env.rb +6 -0
  42. metadata +122 -20
  43. data/lib/mediawiki_selenium/support/modules/sauce_helper.rb +0 -13
  44. data/lib/mediawiki_selenium/support/modules/url_module.rb +0 -21
  45. data/spec/README +0 -2
@@ -0,0 +1,19 @@
1
+ require "thor"
2
+
3
+ module MediawikiSelenium
4
+ # Creates the directory structure and configuration for a brand new
5
+ # MediaWiki related test suite.
6
+ #
7
+ class Initializer < Thor
8
+ include Thor::Actions
9
+
10
+ def self.source_root
11
+ File.expand_path("../../../templates", __FILE__)
12
+ end
13
+
14
+ desc "install", "Creates tests/browser directory structure and default configuration"
15
+ def install
16
+ directory("tests")
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ require "page-object"
2
+
3
+ module MediawikiSelenium
4
+ # Handles on-demand browser instantiation and assignment of the `@browser`
5
+ # instance variable before delegating to `PageObject::PageFactory` methods.
6
+ #
7
+ module PageFactory
8
+ include ::PageObject::PageFactory
9
+
10
+ # Instantiates a new browser before delegating to
11
+ # `PageObject::PageFactory#on_page`. All page URLs are also qualified
12
+ # using {Environment#wiki_url}.
13
+ #
14
+ # @see http://www.rubydoc.info/github/cheezy/page-object
15
+ #
16
+ def on_page(page_class, params = { using_params: {} }, visit = false)
17
+ @browser = browser if visit
18
+
19
+ super(page_class, params, false).tap do |page|
20
+ if page.respond_to?(:goto)
21
+ wiki_url = method(:wiki_url)
22
+
23
+ page.define_singleton_method(:page_url_value) do
24
+ wiki_url.call(super())
25
+ end
26
+
27
+ page.goto if visit
28
+ end
29
+
30
+ yield page if block_given?
31
+ end
32
+ end
33
+
34
+ # @see #on_page
35
+ alias on on_page
36
+
37
+ end
38
+ end
@@ -0,0 +1,87 @@
1
+ require "rest_client"
2
+ require "uri"
3
+
4
+ module MediawikiSelenium
5
+ # Constructs remote browser sessions to be run via Sauce Labs. Adds the
6
+ # following configuration bindings to the factory.
7
+ #
8
+ # - sauce_ondemand_username
9
+ # - sauce_ondemand_access_key
10
+ # - platform
11
+ # - version
12
+ #
13
+ module RemoteBrowserFactory
14
+ REQUIRED_CONFIG = [:sauce_ondemand_username, :sauce_ondemand_access_key]
15
+ URL = "http://ondemand.saucelabs.com/wd/hub"
16
+
17
+ class << self
18
+ def extend_object(factory)
19
+ return if factory.is_a?(self)
20
+
21
+ super
22
+
23
+ factory.bind(:sauce_ondemand_username, :sauce_ondemand_access_key) do |user, key, options|
24
+ options[:url] = URI.parse(URL)
25
+
26
+ options[:url].user = user
27
+ options[:url].password = key
28
+ end
29
+
30
+ factory.bind(:platform) do |platform, options|
31
+ options[:desired_capabilities].platform = platform
32
+ end
33
+
34
+ factory.bind(:version) do |version, options|
35
+ options[:desired_capabilities].version = version
36
+ end
37
+ end
38
+ end
39
+
40
+ # Submits status and Jenkins build info to Sauce Labs.
41
+ #
42
+ def teardown(env, status)
43
+ each do |browser|
44
+ sid = browser.driver.session_id
45
+ url = browser.driver.send(:bridge).http.send(:server_url)
46
+ username = url.user
47
+ key = url.password
48
+
49
+ RestClient::Request.execute(
50
+ method: :put,
51
+ url: "https://saucelabs.com/rest/v1/#{username}/jobs/#{sid}",
52
+ user: username,
53
+ password: key,
54
+ headers: { content_type: "application/json" },
55
+ payload: {
56
+ public: true,
57
+ passed: status == :passed,
58
+ build: env.lookup(:build_number, default: nil),
59
+ }.to_json
60
+ )
61
+ end
62
+ end
63
+
64
+ protected
65
+
66
+ def finalize_options!(options)
67
+ case @browser_name
68
+ when :firefox
69
+ options[:desired_capabilities][:firefox_profile] = options.delete(:profile)
70
+ when :chrome
71
+ options[:desired_capabilities]["chromeOptions"] ||= {}
72
+ options[:desired_capabilities]["chromeOptions"]["prefs"] = options.delete(:prefs)
73
+ options[:desired_capabilities]["chromeOptions"]["args"] = options.delete(:args)
74
+ end
75
+ end
76
+
77
+ def new_browser(options)
78
+ Watir::Browser.new(:remote, options).tap do |browser|
79
+ browser.driver.file_detector = lambda do |args|
80
+ # args => ["/path/to/file"]
81
+ str = args.first.to_s
82
+ str if File.exist?(str)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ require "mediawiki_selenium/step_definitions/login_steps"
2
+ require "mediawiki_selenium/step_definitions/navigation_steps"
3
+ require "mediawiki_selenium/step_definitions/preferences_steps"
4
+ require "mediawiki_selenium/step_definitions/resource_loader_steps"
5
+ require "mediawiki_selenium/step_definitions/upload_file_steps"
@@ -0,0 +1,3 @@
1
+ require "mediawiki_selenium/support/env"
2
+ require "mediawiki_selenium/support/hooks"
3
+ require "mediawiki_selenium/support/sauce"
@@ -11,138 +11,14 @@ https://git.wikimedia.org/blob/mediawiki%2Fselenium/HEAD/CREDITS.
11
11
 
12
12
  # before all
13
13
  require "bundler/setup"
14
- require "page-object"
15
14
  require "page-object/page_factory"
16
- require "rest_client"
17
15
  require "watir-webdriver"
18
16
 
19
17
  require "mediawiki_selenium/support/modules/api_helper"
20
- require "mediawiki_selenium/support/modules/sauce_helper"
21
18
  require "mediawiki_selenium/support/modules/strict_pending"
22
19
 
23
- World(PageObject::PageFactory)
20
+ World { MediawikiSelenium::Environment.load_default }
21
+
24
22
  World(MediawikiSelenium::ApiHelper)
25
- World(MediawikiSelenium::SauceHelper)
23
+ World(MediawikiSelenium::PageFactory)
26
24
  World(MediawikiSelenium::StrictPending)
27
-
28
- def browser(test_name, configuration = nil)
29
- if environment == :saucelabs
30
- sauce_browser(test_name, configuration)
31
- else
32
- local_browser(configuration)
33
- end
34
- end
35
- def browser_name
36
- if ENV["BROWSER"]
37
- ENV["BROWSER"].to_sym
38
- else
39
- :firefox
40
- end
41
- end
42
- def environment
43
- if ENV["SAUCE_ONDEMAND_USERNAME"] and ENV["SAUCE_ONDEMAND_ACCESS_KEY"] and ENV["BROWSER"] != "phantomjs" and ENV["HEADLESS"] != "true"
44
- :saucelabs
45
- else
46
- :local
47
- end
48
- end
49
- def local_browser(configuration)
50
- if ENV["BROWSER_TIMEOUT"] && browser_name == :firefox
51
- timeout = ENV["BROWSER_TIMEOUT"].to_i
52
-
53
- client = Selenium::WebDriver::Remote::Http::Default.new
54
- client.timeout = timeout
55
-
56
- profile = Selenium::WebDriver::Firefox::Profile.new
57
- profile["dom.max_script_run_time"] = timeout
58
- profile["dom.max_chrome_script_run_time"] = timeout
59
- browser = Watir::Browser.new browser_name, :http_client => client, :profile => profile
60
- elsif configuration && configuration[:language] && browser_name == :firefox
61
- profile = Selenium::WebDriver::Firefox::Profile.new
62
- profile["intl.accept_languages"] = configuration[:language]
63
- browser = Watir::Browser.new browser_name, profile: profile
64
- elsif configuration && configuration[:language] && browser_name == :chrome
65
- prefs = {intl: {accept_languages: configuration[:language]}}
66
- browser = Watir::Browser.new browser_name, prefs: prefs
67
- elsif configuration && configuration[:language] && browser_name == :phantomjs
68
- capabilities = Selenium::WebDriver::Remote::Capabilities.phantomjs
69
- capabilities["phantomjs.page.customHeaders.Accept-Language"] = configuration[:language]
70
- browser = Watir::Browser.new browser_name, desired_capabilities: capabilities
71
- elsif configuration && configuration[:user_agent] && browser_name == :firefox
72
- profile = Selenium::WebDriver::Firefox::Profile.new
73
- profile["general.useragent.override"] = configuration[:user_agent]
74
- browser = Watir::Browser.new browser_name, profile: profile
75
- else
76
- browser = Watir::Browser.new browser_name
77
- end
78
-
79
- browser.window.resize_to 1280, 1024
80
- set_cookie(browser)
81
- browser
82
- end
83
- def sauce_api(json, session_id)
84
- RestClient::Request.execute(
85
- :method => :put,
86
- :url => "https://saucelabs.com/rest/v1/#{ENV['SAUCE_ONDEMAND_USERNAME']}/jobs/#{session_id}",
87
- :user => ENV["SAUCE_ONDEMAND_USERNAME"],
88
- :password => ENV["SAUCE_ONDEMAND_ACCESS_KEY"],
89
- :headers => {:content_type => "application/json"},
90
- :payload => json
91
- )
92
- end
93
- def sauce_browser(test_name, configuration)
94
- abort "Environment variables BROWSER, PLATFORM and VERSION have to be set" if (ENV["BROWSER"] == nil) or (ENV["PLATFORM"] == nil) or (ENV["VERSION"] == nil)
95
-
96
- client = Selenium::WebDriver::Remote::Http::Default.new
97
-
98
- if ENV["BROWSER_TIMEOUT"] && ENV["BROWSER"] == "firefox"
99
- timeout = ENV["BROWSER_TIMEOUT"].to_i
100
- client.timeout = timeout
101
-
102
- profile = Selenium::WebDriver::Firefox::Profile.new
103
- profile["dom.max_script_run_time"] = timeout
104
- profile["dom.max_chrome_script_run_time"] = timeout
105
- caps = Selenium::WebDriver::Remote::Capabilities.firefox(:firefox_profile => profile)
106
- elsif configuration && configuration[:language] && ENV["BROWSER"] == "firefox"
107
- profile = Selenium::WebDriver::Firefox::Profile.new
108
- profile["intl.accept_languages"] = configuration[:language]
109
- caps = Selenium::WebDriver::Remote::Capabilities.firefox(:firefox_profile => profile)
110
- elsif configuration && configuration[:language] && ENV["BROWSER"] == "chrome"
111
- profile = Selenium::WebDriver::Chrome::Profile.new
112
- profile["intl.accept_languages"] = configuration[:language]
113
- caps = Selenium::WebDriver::Remote::Capabilities.chrome("chrome.profile" => profile.as_json["zip"])
114
- elsif configuration && configuration[:user_agent] && ENV["BROWSER"] == "firefox"
115
- profile = Selenium::WebDriver::Firefox::Profile.new
116
- profile["general.useragent.override"] = configuration[:user_agent]
117
- caps = Selenium::WebDriver::Remote::Capabilities.firefox(:firefox_profile => profile)
118
- else
119
- caps = Selenium::WebDriver::Remote::Capabilities.send(ENV["BROWSER"])
120
- end
121
-
122
- caps.platform = ENV["PLATFORM"]
123
- caps.version = ENV["VERSION"]
124
- caps[:name] = "#{test_name} #{ENV['JOB_NAME']}##{ENV['BUILD_NUMBER']}"
125
-
126
- browser = Watir::Browser.new(
127
- :remote,
128
- http_client: client,
129
- url: "http://#{ENV['SAUCE_ONDEMAND_USERNAME']}:#{ENV['SAUCE_ONDEMAND_ACCESS_KEY']}@ondemand.saucelabs.com:80/wd/hub",
130
- desired_capabilities: caps)
131
- browser.wd.file_detector = lambda do |args|
132
- # args => ["/path/to/file"]
133
- str = args.first.to_s
134
- str if File.exist?(str)
135
- end
136
-
137
- browser
138
- end
139
- def set_cookie(browser)
140
- # implement this method in env.rb of the repository where it is needed
141
- end
142
- def test_name(scenario)
143
- if scenario.respond_to? :feature
144
- "#{scenario.feature.title}: #{scenario.title}"
145
- elsif scenario.respond_to? :scenario_outline
146
- "#{scenario.scenario_outline.feature.title}: #{scenario.scenario_outline.title}: #{scenario.name}"
147
- end
148
- end
@@ -13,12 +13,6 @@ Before("@custom-browser") do |scenario|
13
13
  @scenario = scenario
14
14
  end
15
15
 
16
- Before("@login") do
17
- ENV["MEDIAWIKI_PASSWORD"] = ENV[ENV["MEDIAWIKI_PASSWORD_VARIABLE"]] if ENV["MEDIAWIKI_PASSWORD_VARIABLE"]
18
- puts "MEDIAWIKI_USER environment variable is not defined! Please export a value for that variable before proceeding." unless ENV["MEDIAWIKI_USER"]
19
- puts "MEDIAWIKI_PASSWORD environment variable is not defined! Please export a value for that variable before proceeding." unless ENV["MEDIAWIKI_PASSWORD"]
20
- end
21
-
22
16
  AfterConfiguration do |config|
23
17
  # Install a formatter that can be used to show feature-related warnings
24
18
  pretty_format, io = config.formats.find { |(format, io)| format == "pretty" }
@@ -68,44 +62,39 @@ Before do |scenario|
68
62
  end
69
63
 
70
64
  Before do |scenario|
65
+ # Create a unique random string for this scenario
71
66
  @random_string = Random.new.rand.to_s
72
67
 
73
- # CirrusSearch and VisualEditor need this
74
- if ENV["REUSE_BROWSER"] == "true" && $browser
75
- @browser = $browser
76
- elsif scenario.source_tag_names.include? "@custom-browser"
77
- # browser will be started in Cucumber step
78
- else
79
- @browser = browser(test_name(scenario))
80
- $browser = @browser # CirrusSearch and VisualEditor need this
68
+ # Annotate sessions with the scenario name and Jenkins build info
69
+ browser_factory.bind do |options|
70
+ options[:desired_capabilities][:name] = test_name(scenario)
81
71
  end
82
72
 
83
- $session_id = sauce_session_id
73
+ browser_factory.bind(:job_name) do |job, options|
74
+ options[:desired_capabilities][:name] += " #{job}"
75
+ end
76
+
77
+ browser_factory.bind(:build_number) do |build, options|
78
+ options[:desired_capabilities][:name] += "##{build}"
79
+ end
84
80
  end
85
81
 
86
82
  After do |scenario|
87
- if @browser && scenario.failed? && (ENV["SCREENSHOT_FAILURES"] == "true")
83
+ if scenario.respond_to?(:status)
88
84
  require "fileutils"
89
- screen_dir = ENV["SCREENSHOT_FAILURES_PATH"] || "screenshots"
90
- FileUtils.mkdir_p screen_dir
91
- name = test_name(scenario).gsub(/ /, '_')
92
- path = "#{screen_dir}/#{name}.png"
93
- @browser.screenshot.save path
94
- embed path, "image/png"
95
- end
96
85
 
97
- if environment == :saucelabs
98
- sid = $session_id || sauce_session_id
86
+ teardown(scenario.status) do |browser|
87
+ if scenario.failed? && lookup(:screenshot_failures, default: false) == "true"
88
+ screen_dir = lookup(:screenshot_failures_path, default: "screenshots")
89
+ FileUtils.mkdir_p screen_dir
90
+ name = test_name(scenario).gsub(/ /, '_')
91
+ path = "#{screen_dir}/#{name}.png"
92
+ browser.screenshot.save path
93
+ embed path, "image/png"
94
+ end
99
95
 
100
- unless sid.nil?
101
- sauce_api(%Q{{"passed": #{scenario.passed?}}}, sid)
102
- sauce_api(%Q{{"public": true}}, sid)
103
- sauce_api(%Q{{"build": #{ENV["BUILD_NUMBER"]}}}, sid) if ENV["BUILD_NUMBER"]
104
96
  end
105
- end
106
-
107
- if @browser
108
- # CirrusSearch and VisualEditor need this
109
- @browser.close unless ENV["KEEP_BROWSER_OPEN"] == "true" || ENV["REUSE_BROWSER"] == "true"
97
+ else
98
+ teardown
110
99
  end
111
100
  end
@@ -5,16 +5,55 @@ module MediawikiSelenium
5
5
  # definitions.
6
6
  #
7
7
  module ApiHelper
8
- # A pre-authenticated API client.
8
+ # An authenticated MediaWiki API client.
9
9
  #
10
10
  # @return [MediawikiApi::Client]
11
11
  #
12
12
  def api
13
- return @api if defined?(@api)
13
+ @_api_cache ||= {}
14
14
 
15
- @api = MediawikiApi::Client.new(ENV["MEDIAWIKI_API_URL"])
16
- @api.log_in(*ENV.values_at("MEDIAWIKI_USER", "MEDIAWIKI_PASSWORD")) unless @api.logged_in?
17
- @api
15
+ url = lookup(:mediawiki_api_url, default: api_url_from(lookup(:mediawiki_url)))
16
+
17
+ @_api_cache[[url, user]] ||= MediawikiApi::Client.new(url).tap do |client|
18
+ client.log_in(user, password)
19
+ end
20
+ end
21
+
22
+ # Ensures the given alternative account exists by attempting to create it
23
+ # via the API. Any errors related to the account already existing are
24
+ # swallowed.
25
+ #
26
+ # @param id [Symbol] ID of alternative user.
27
+ #
28
+ def ensure_account(id)
29
+ begin
30
+ api.create_account(user(id), password(id))
31
+ rescue MediawikiApi::ApiError => e
32
+ raise e unless e.code == "userexists"
33
+ end
34
+ end
35
+
36
+ # Extends parent implementation to also override the API URL. If no API
37
+ # URL is explicitly defined for the given alternative, one is constructed
38
+ # relative to the wiki URL.
39
+ #
40
+ # @yield [wiki_url, api_url]
41
+ # @yieldparam wiki_url [String] Alternative wiki URL.
42
+ # @yieldparam api_url [String] Alternative API URL.
43
+ #
44
+ # @see Environment#on_wiki
45
+ #
46
+ def on_wiki(id, &blk)
47
+ super(id) do |wiki_url|
48
+ api_url = lookup(:mediawiki_api_url, id: id, default: -> { api_url_from(wiki_url) })
49
+ return with(mediawiki_url: wiki_url, mediawiki_api_url: api_url, &blk)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def api_url_from(wiki_url)
56
+ URI.parse(wiki_url).merge("/w/api.php").to_s
18
57
  end
19
58
  end
20
59
  end
@@ -0,0 +1,4 @@
1
+ require "mediawiki_selenium/support/pages/api_page"
2
+ require "mediawiki_selenium/support/pages/login_page"
3
+ require "mediawiki_selenium/support/pages/random_page"
4
+ require "mediawiki_selenium/support/pages/reset_preferences_page"