mediawiki_selenium 0.4.3 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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"