saucelabs-adapter 0.7.7 → 0.8.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.
- data/README.html +180 -0
- data/README.markdown +17 -4
- data/Rakefile +5 -1
- data/VERSION +1 -1
- data/generators/saucelabs_adapter/templates/selenium.yml +17 -5
- data/lib/saucelabs_adapter.rb +5 -1
- data/lib/saucelabs_adapter/jsunit_selenium_support.rb +18 -13
- data/lib/saucelabs_adapter/selenium_config.rb +38 -19
- data/lib/saucelabs_adapter/test_unit_adapter.rb +4 -3
- data/lib/saucelabs_adapter/tunnel.rb +29 -0
- data/lib/saucelabs_adapter/tunnels/other_tunnel.rb +7 -0
- data/lib/saucelabs_adapter/{sauce_tunnel.rb → tunnels/sauce_tunnel.rb} +10 -21
- data/lib/saucelabs_adapter/tunnels/ssh_tunnel.rb +25 -0
- data/lib/saucelabs_adapter/utilities.rb +23 -0
- data/spec/selenium_config_spec.rb +9 -6
- metadata +59 -26
data/README.html
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
|
2
|
+
<html>
|
3
|
+
<head><title>README.markdown</title></head>
|
4
|
+
<body>
|
5
|
+
<h1>Saucelabs-Adapter</h1>
|
6
|
+
|
7
|
+
<p>Saucelabs-adapter provides the glue to connect Rails Selenium tests to saucelabs.com.</p>
|
8
|
+
|
9
|
+
<p>Currently it supports tests written using Webrat, Polonium and JSUnit.</p>
|
10
|
+
|
11
|
+
<h2>Getting Started - Webrat or Polonium test suites</h2>
|
12
|
+
|
13
|
+
<ol>
|
14
|
+
<li><p>Prerequisites:</p>
|
15
|
+
|
16
|
+
<p>You must be able to run selenium tests locally using test/selenium/selenium_suite.rb</p></li>
|
17
|
+
<li><p>Install the gem:</p>
|
18
|
+
|
19
|
+
<pre><code>gem install saucelabs-adapter
|
20
|
+
</code></pre></li>
|
21
|
+
<li><p>Run the saucelabs_adapter generator in your project:</p>
|
22
|
+
|
23
|
+
<pre><code>cd your_project
|
24
|
+
|
25
|
+
script/generate saucelabs_adapter
|
26
|
+
</code></pre></li>
|
27
|
+
<li><p>Configure it. In config/selenium.yml, replace YOUR-SAUCELABS-USERNAME and
|
28
|
+
YOUR-SAUCELABS-ACCESS-KEY with your saucelabs.com account information.</p></li>
|
29
|
+
<li><p>Run Tests</p>
|
30
|
+
|
31
|
+
<p>To run Selenium Test::Unit tests locally:</p>
|
32
|
+
|
33
|
+
<pre><code>rake selenium:local
|
34
|
+
</code></pre>
|
35
|
+
|
36
|
+
<p>To run Selenium Test::Unit tests using saucelabs.com:</p>
|
37
|
+
|
38
|
+
<pre><code>rake selenium:sauce
|
39
|
+
</code></pre></li>
|
40
|
+
</ol>
|
41
|
+
|
42
|
+
|
43
|
+
<h2>Getting Started - JsUnit test suite</h2>
|
44
|
+
|
45
|
+
<ol>
|
46
|
+
<li><p>Prerequisites:</p>
|
47
|
+
|
48
|
+
<p>Install the latest JsUnit from http://github.com/pivotal/jsunit</p>
|
49
|
+
|
50
|
+
<p>JsUnit must be installed in RAILS_ROOT/public/jsunit as follows:</p>
|
51
|
+
|
52
|
+
<pre><code>public/jsunit/jsunit_jar/jsunit.jar -- the compiled jar
|
53
|
+
public/jsunit/jsunit/build.xml etc... -- jsunit sources
|
54
|
+
</code></pre></li>
|
55
|
+
<li><p>Install the saucelabs-adapter gem:</p>
|
56
|
+
|
57
|
+
<pre><code>gem install saucelabs-adapter
|
58
|
+
</code></pre></li>
|
59
|
+
<li><p>Run the saucelabs_adapter generator in your project:</p>
|
60
|
+
|
61
|
+
<pre><code>cd your_project
|
62
|
+
|
63
|
+
script/generate saucelabs_adapter --jsunit
|
64
|
+
</code></pre></li>
|
65
|
+
<li><p>Configure it.</p>
|
66
|
+
|
67
|
+
<p>In config/selenium.yml, replace YOUR-SAUCELABS-USERNAME and
|
68
|
+
YOUR-SAUCELABS-ACCESS-KEY with your saucelabs.com account information.</p>
|
69
|
+
|
70
|
+
<p>Rename RAILS_ROOT/test/jsunit/jsunit_suite_example.rb to RAILS_ROOT/test/jsunit/jsunit_suite.rb
|
71
|
+
and modify it if necessary:
|
72
|
+
test_page needs to be set to the path under /public where your JsUnit test page (suite.html or similar) lives,
|
73
|
+
with '/jsunit' prepended. e.g. if your JsUnit suite runs from RAILS_ROOT/public/javascripts/test-pages/suite.html
|
74
|
+
then test_page needs to be set to '/jsunit/javascripts/test-pages/suite.html'.</p></li>
|
75
|
+
<li><p>Run Tests</p>
|
76
|
+
|
77
|
+
<p>To run JsUnit tests locally:</p>
|
78
|
+
|
79
|
+
<pre><code>rake jsunit:selenium_rc:local
|
80
|
+
</code></pre>
|
81
|
+
|
82
|
+
<p>To run JsUnit tests using saucelabs.com:</p>
|
83
|
+
|
84
|
+
<pre><code>rake jsunit:selenium_rc:sauce
|
85
|
+
</code></pre></li>
|
86
|
+
</ol>
|
87
|
+
|
88
|
+
|
89
|
+
<h2>What You Should See</h2>
|
90
|
+
|
91
|
+
<p>When running tests, intermixed with your test output you should see the following lines:</p>
|
92
|
+
|
93
|
+
<pre><code> [saucelabs-adapter] Setting up tunnel from Saucelabs (yourhostname-12345.com:80) to localhost:4000
|
94
|
+
[saucelabs-adapter] Tunnel ID 717909c571b8319dc5ae708b689fd7f5 for yourhostname-12345.com is up.
|
95
|
+
Started
|
96
|
+
....................
|
97
|
+
[saucelabs-adapter] Shutting down tunnel to Saucelabs...
|
98
|
+
[saucelabs-adapter] done.
|
99
|
+
</code></pre>
|
100
|
+
|
101
|
+
<h2>In Case of Problems</h2>
|
102
|
+
|
103
|
+
<p>Try setting environment variable SAUCELABS_ADAPTER_DEBUG to "true". This enables more verbose output.</p>
|
104
|
+
|
105
|
+
<h2>Continuous Integration</h2>
|
106
|
+
|
107
|
+
<p>Sauce Labs now lets you set the name of a test job.
|
108
|
+
By default the SaucelabsAdapter will set this to the name of the machine it is currently running on,
|
109
|
+
however you may override this by setting the environment variable SAUCELABS_JOB_NAME.</p>
|
110
|
+
|
111
|
+
<p>This can be useful if you run many tests from the same CI machine and would like to differentiate between
|
112
|
+
them without actually viewing the video.</p>
|
113
|
+
|
114
|
+
<h2>What it Does</h2>
|
115
|
+
|
116
|
+
<p>The saucelabs-adapter performs two functions when it detects you are running a test that will use saucelabs.com:</p>
|
117
|
+
|
118
|
+
<ol>
|
119
|
+
<li><p>It sets up a SauceTunnel before the test run starts and tears it down after the test ends. This happens once for the entire test run.</p></li>
|
120
|
+
<li><p>It configures the selenium client to connect to the correct address at saucelabs.com. This happens at the start of each test.</p></li>
|
121
|
+
</ol>
|
122
|
+
|
123
|
+
<h1>Resources</h1>
|
124
|
+
|
125
|
+
<ul>
|
126
|
+
<li><a href="http://gemcutter.org/gems/saucelabs-adapter">The gem</a></li>
|
127
|
+
<li><a href="http://github.com/pivotal/saucelabs-adapter">Source code</a></li>
|
128
|
+
<li><a href="http://www.pivotaltracker.com/projects/59050">Tracker project</a></li>
|
129
|
+
<li><a href="http://ci.pivotallabs.com:3333/builds/SaucelabsCanary">Canary CI build</a></li>
|
130
|
+
<li><a href="http://github.com/pivotal/saucelabs-canary">Canary project source code</a></li>
|
131
|
+
</ul>
|
132
|
+
|
133
|
+
|
134
|
+
<h1>CHANGES</h1>
|
135
|
+
|
136
|
+
<h2>0.7.6</h2>
|
137
|
+
|
138
|
+
<ul>
|
139
|
+
<li>Added saucelabs_max_duration configuration option.</li>
|
140
|
+
</ul>
|
141
|
+
|
142
|
+
|
143
|
+
<h2>0.7.0</h2>
|
144
|
+
|
145
|
+
<ul>
|
146
|
+
<li><p>The gem has been reorganized to better conform with Gem best-practices.</p></li>
|
147
|
+
<li><p>The rakefile generator has changed. If you are upgrading, you will need to rerun the generator and overwrite lib/tasks/saucelabs_adapter.rake,
|
148
|
+
or just change line 1 of that file to read:</p>
|
149
|
+
|
150
|
+
<pre><code>require 'saucelabs_adapter/run_utils'
|
151
|
+
</code></pre></li>
|
152
|
+
<li><p>The selenium.yml syntax has changed to break out all the saucelabs info into separate lines, and the tunnel method is now explicitly stated:</p>
|
153
|
+
|
154
|
+
<ul>
|
155
|
+
<li><p>Old:</p>
|
156
|
+
|
157
|
+
<pre><code> selenium_browser_key: '{"username": "YOUR-SAUCELABS-USERNAME", "access-key": "YOUR-SAUCELABS-ACCESS-KEY", "os": "Linux", "browser": "firefox", "browser-version": "3."}'
|
158
|
+
#
|
159
|
+
localhost_app_server_port: "4000"
|
160
|
+
tunnel_startup_timeout: 240
|
161
|
+
</code></pre></li>
|
162
|
+
<li><p>New:</p>
|
163
|
+
|
164
|
+
<pre><code> saucelabs_username: "YOUR-SAUCELABS-USERNAME"
|
165
|
+
saucelabs_access_key: "YOUR-SAUCELABS-ACCESS-KEY"
|
166
|
+
saucelabs_browser_os: "Linux"
|
167
|
+
saucelabs_browser: "firefox"
|
168
|
+
saucelabs_browser_version: "3."
|
169
|
+
#
|
170
|
+
tunnel_method: :saucetunnel
|
171
|
+
tunnel_to_localhost_port: 4000
|
172
|
+
tunnel_startup_timeout: 240
|
173
|
+
</code></pre></li>
|
174
|
+
</ul>
|
175
|
+
</li>
|
176
|
+
<li><p>The dependency on Python has been removed.</p></li>
|
177
|
+
</ul>
|
178
|
+
|
179
|
+
</body>
|
180
|
+
</html>
|
data/README.markdown
CHANGED
@@ -14,7 +14,7 @@ Getting Started - Webrat or Polonium test suites
|
|
14
14
|
|
15
15
|
2. Install the gem:
|
16
16
|
|
17
|
-
gem install saucelabs-adapter
|
17
|
+
gem install saucelabs-adapter
|
18
18
|
|
19
19
|
3. Run the saucelabs_adapter generator in your project:
|
20
20
|
|
@@ -49,7 +49,7 @@ Getting Started - JsUnit test suite
|
|
49
49
|
|
50
50
|
2. Install the saucelabs-adapter gem:
|
51
51
|
|
52
|
-
gem install saucelabs-adapter
|
52
|
+
gem install saucelabs-adapter
|
53
53
|
|
54
54
|
3. Run the saucelabs_adapter generator in your project:
|
55
55
|
|
@@ -113,8 +113,21 @@ The saucelabs-adapter performs two functions when it detects you are running a t
|
|
113
113
|
|
114
114
|
2. It configures the selenium client to connect to the correct address at saucelabs.com. This happens at the start of each test.
|
115
115
|
|
116
|
-
|
117
|
-
|
116
|
+
Resources
|
117
|
+
=========
|
118
|
+
* [The gem](http://gemcutter.org/gems/saucelabs-adapter)
|
119
|
+
* [Source code](http://github.com/pivotal/saucelabs-adapter)
|
120
|
+
* [Tracker project](http://www.pivotaltracker.com/projects/59050)
|
121
|
+
* [Canary CI build](http://ci.pivotallabs.com:3333/builds/SaucelabsCanary)
|
122
|
+
* [Canary project source code](http://github.com/pivotal/saucelabs-canary)
|
123
|
+
|
124
|
+
NOTABLE CHANGES
|
125
|
+
===============
|
126
|
+
0.8
|
127
|
+
---
|
128
|
+
- Added new tunnel type SshTunnel (a generic reverse SSH tunnel), see selenium.yml for now to configure.
|
129
|
+
- Added jsunit_polling_interval_seconds configuratin option.
|
130
|
+
|
118
131
|
0.7.6
|
119
132
|
-----
|
120
133
|
- Added saucelabs_max_duration configuration option.
|
data/Rakefile
CHANGED
@@ -30,8 +30,12 @@ begin
|
|
30
30
|
"VERSION",
|
31
31
|
"lib/saucelabs_adapter.rb",
|
32
32
|
"lib/saucelabs-adapter.rb",
|
33
|
+
"lib/saucelabs_adapter/utilities.rb",
|
33
34
|
"lib/saucelabs_adapter/run_utils.rb",
|
34
|
-
"lib/saucelabs_adapter/
|
35
|
+
"lib/saucelabs_adapter/tunnel.rb",
|
36
|
+
"lib/saucelabs_adapter/tunnels/sauce_tunnel.rb",
|
37
|
+
"lib/saucelabs_adapter/tunnels/ssh_tunnel.rb",
|
38
|
+
"lib/saucelabs_adapter/tunnels/other_tunnel.rb",
|
35
39
|
"lib/saucelabs_adapter/selenium_config.rb",
|
36
40
|
"lib/saucelabs_adapter/test_unit_adapter.rb",
|
37
41
|
"lib/saucelabs_adapter/jsunit_selenium_support.rb",
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
@@ -32,12 +32,23 @@ saucelabs: &saucelabs
|
|
32
32
|
saucelabs_browser_version: "3."
|
33
33
|
saucelabs_max_duration_seconds: 1800
|
34
34
|
# Selenium RC browser connects to and tests the app at this URL:
|
35
|
-
application_address: "this will be ovewritten if tunnel_method == :saucetunnel
|
35
|
+
application_address: "testhost.com" # this will be ovewritten if tunnel_method == :saucetunnel
|
36
36
|
application_port: 80
|
37
|
-
# App host
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
# App host can actually be a tunnel that tunnels from <application_address>:<application_port> to localhost:<tunnel_to_localhost_port>
|
38
|
+
# There are 3 kinds of tunnels:
|
39
|
+
#
|
40
|
+
# tunnel_method: :saucetunnel
|
41
|
+
# tunnel_to_localhost_port: 4000 # Warning: application_port and tunnel_to_localhost_port must be identical if you are using Webrat
|
42
|
+
# tunnel_startup_timeout: 240
|
43
|
+
#
|
44
|
+
# tunnel_method: :sshtunnel
|
45
|
+
# application_address: proxy.mycompany.com
|
46
|
+
# application_port: 12345
|
47
|
+
# tunnel_to_localhost_port: 4000 # Warning: application_port and tunnel_to_localhost_port must be identical if you are using Webrat
|
48
|
+
# tunnel_username: fred
|
49
|
+
# tunnel_keyfile: "/path/to/keyfile" # or tunnel_password: "password"
|
50
|
+
#
|
51
|
+
# tunnel_method: :othertunnel You're managing your tunnel independently
|
41
52
|
|
42
53
|
saucelabs_jsunit: &saucelabs_jsunit
|
43
54
|
<<: *saucelabs
|
@@ -52,6 +63,7 @@ saucelabs_jsunit_ie:
|
|
52
63
|
saucelabs_browser_os: "Windows 2003"
|
53
64
|
saucelabs_browser: "iexplore"
|
54
65
|
saucelabs_browser_version: "7."
|
66
|
+
jsunit_polling_interval_seconds: 300
|
55
67
|
|
56
68
|
saucelabs_jsunit_safari:
|
57
69
|
<<: *saucelabs_jsunit
|
data/lib/saucelabs_adapter.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
require 'saucelabs_adapter/utilities'
|
2
|
+
require 'saucelabs_adapter/tunnel'
|
3
|
+
require 'saucelabs_adapter/tunnels/sauce_tunnel'
|
4
|
+
require 'saucelabs_adapter/tunnels/ssh_tunnel'
|
5
|
+
require 'saucelabs_adapter/tunnels/other_tunnel'
|
1
6
|
require 'saucelabs_adapter/selenium_config'
|
2
|
-
require 'saucelabs_adapter/sauce_tunnel'
|
3
7
|
require 'saucelabs_adapter/test_unit_adapter'
|
4
8
|
require 'saucelabs_adapter/jsunit_selenium_support'
|
5
9
|
module SaucelabsAdapter
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module SaucelabsAdapter
|
2
2
|
module JsunitSeleniumSupport
|
3
|
+
include Utilities
|
3
4
|
|
4
5
|
def requires
|
5
6
|
require 'saucelabs_adapter/run_utils'
|
@@ -8,13 +9,14 @@ module SaucelabsAdapter
|
|
8
9
|
end
|
9
10
|
|
10
11
|
def setup_jsunit_selenium(options = {})
|
12
|
+
@diagnostics_prefix = '[JsunitSeleniumSupport]'
|
11
13
|
requires
|
12
14
|
@selenium_config = SeleniumConfig.new(ENV['SELENIUM_ENV'])
|
13
15
|
start_app_server(options)
|
14
16
|
@selenium_driver = @selenium_config.create_driver(options)
|
15
|
-
|
17
|
+
debug "calling @selenium_driver.start"
|
16
18
|
@selenium_driver.start
|
17
|
-
|
19
|
+
debug "@selenium_driver.start done"
|
18
20
|
end
|
19
21
|
|
20
22
|
def teardown_jsunit_selenium
|
@@ -24,7 +26,7 @@ module SaucelabsAdapter
|
|
24
26
|
|
25
27
|
def run_jsunit_test(jsunit_params, options = {})
|
26
28
|
if $:.detect{ |x| x =~ /Selenium/}
|
27
|
-
|
29
|
+
raise_with_message 'Selenium gem should not be in path! (deprecated in favor of selenium-client, which we require)'
|
28
30
|
end
|
29
31
|
|
30
32
|
default_jsunit_params = {
|
@@ -37,6 +39,7 @@ module SaucelabsAdapter
|
|
37
39
|
jsunit_params.reverse_merge!(default_jsunit_params)
|
38
40
|
|
39
41
|
test_url = "/jsunit/javascripts/jsunit/jsunit/testRunner.html?" + jsunit_params.map { |k,v| "#{k}=#{v}" }.join("&")
|
42
|
+
options.reverse_merge!(:polling_interval => @selenium_config.jsunit_polling_interval_seconds) if @selenium_config.jsunit_polling_interval_seconds
|
40
43
|
run_suite(@selenium_driver, test_url, options)
|
41
44
|
end
|
42
45
|
|
@@ -57,7 +60,7 @@ module SaucelabsAdapter
|
|
57
60
|
|
58
61
|
def start_app_server(options = {})
|
59
62
|
stop_app_server
|
60
|
-
|
63
|
+
say "starting application server:"
|
61
64
|
app_server_logfile_path = options[:app_server_logfile_path] || "#{RAILS_ROOT}/log/jsunit_jetty_app_server.log"
|
62
65
|
RunUtils.run "ant -f #{RAILS_ROOT}/public/javascripts/jsunit/jsunit/build.xml start_server " +
|
63
66
|
"-Dport=#{local_app_server_port} " +
|
@@ -66,9 +69,9 @@ module SaucelabsAdapter
|
|
66
69
|
end
|
67
70
|
|
68
71
|
def stop_app_server
|
69
|
-
|
72
|
+
raise_with_message "oops don't know port app server is running on" unless local_app_server_port
|
70
73
|
while Lsof.running?(local_app_server_port)
|
71
|
-
|
74
|
+
say "Killing app server at #{local_app_server_port}..."
|
72
75
|
Lsof.kill(local_app_server_port)
|
73
76
|
sleep 1
|
74
77
|
end
|
@@ -76,7 +79,8 @@ module SaucelabsAdapter
|
|
76
79
|
|
77
80
|
def run_suite(selenium_driver, suite_path, options = {})
|
78
81
|
default_options = {
|
79
|
-
:timeout_in_seconds => 1200
|
82
|
+
:timeout_in_seconds => 1200,
|
83
|
+
:polling_interval => 5
|
80
84
|
}
|
81
85
|
options.reverse_merge!(default_options)
|
82
86
|
|
@@ -89,9 +93,10 @@ module SaucelabsAdapter
|
|
89
93
|
tests_completed = false
|
90
94
|
begin_time = Time.now
|
91
95
|
status = ""
|
92
|
-
|
96
|
+
say "Starting to poll JsUnit (every #{options[:polling_interval]}s)..." if options[:verbose]
|
93
97
|
while (Time.now - begin_time) < options[:jsunit_suite_timeout_seconds] && !tests_completed
|
94
|
-
sleep
|
98
|
+
sleep options[:polling_interval]
|
99
|
+
debug "polling now...", 2
|
95
100
|
status = selenium_driver.js_eval("window.mainFrame.mainStatus.document.getElementById('content').innerHTML")
|
96
101
|
status.gsub!(/^<[bB]>Status:<\/[bB]> /, '')
|
97
102
|
# Long form: window.frames['mainFrame'].frames['mainCounts'].frames['mainCountsRuns'].document.getElementById('content').innerHTML
|
@@ -101,18 +106,18 @@ module SaucelabsAdapter
|
|
101
106
|
run_count = runs.match(/\d+$/)[0].to_i
|
102
107
|
fail_count = fails.match(/\d+$/)[0].to_i
|
103
108
|
error_count = errors.match(/\d+$/)[0].to_i
|
104
|
-
|
109
|
+
say "runs/fails/errors: #{run_count}/#{fail_count}/#{error_count} status: #{status}" if options[:verbose]
|
105
110
|
if status =~ /^Done /
|
106
111
|
tests_completed = true
|
107
112
|
end
|
108
113
|
end
|
109
|
-
|
114
|
+
raise_with_message "Tests failed to complete after #{options[:jsunit_suite_timeout_seconds]}, status was '#{status}'" unless tests_completed
|
110
115
|
|
111
|
-
|
116
|
+
say "********** JSUnit tests complete, Runs: #{run_count}, Fails: #{fail_count}, Errors: #{error_count} **********"
|
112
117
|
|
113
118
|
if (fail_count + error_count > 0)
|
114
119
|
error_messages = selenium_driver.js_eval("window.mainFrame.mainErrors.document.getElementsByName('problemsList')[0].innerHTML")
|
115
|
-
|
120
|
+
say "Error messages: #{error_messages}"
|
116
121
|
end
|
117
122
|
|
118
123
|
(fail_count + error_count) == 0
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module SaucelabsAdapter
|
2
2
|
class SeleniumConfig
|
3
|
+
|
4
|
+
include Utilities
|
5
|
+
|
3
6
|
attr_reader :configuration
|
4
7
|
|
5
8
|
def initialize(configuration_name = nil, selenium_yml_path = nil)
|
@@ -17,7 +20,9 @@ module SaucelabsAdapter
|
|
17
20
|
:saucelabs_username, :saucelabs_access_key,
|
18
21
|
:saucelabs_browser_os, :saucelabs_browser, :saucelabs_browser_version,
|
19
22
|
:saucelabs_max_duration_seconds,
|
20
|
-
:tunnel_method, :tunnel_to_localhost_port, :tunnel_startup_timeout
|
23
|
+
:tunnel_method, :tunnel_to_localhost_port, :tunnel_startup_timeout,
|
24
|
+
:tunnel_username, :tunnel_password, :tunnel_keyfile,
|
25
|
+
:jsunit_polling_interval_seconds ].each do |attr|
|
21
26
|
define_method(attr) do
|
22
27
|
@configuration[attr.to_s]
|
23
28
|
end
|
@@ -40,7 +45,7 @@ module SaucelabsAdapter
|
|
40
45
|
end
|
41
46
|
|
42
47
|
def application_address
|
43
|
-
if
|
48
|
+
if start_tunnel? && @configuration['tunnel_method'].to_sym == :saucetunnel
|
44
49
|
# We are using Sauce Labs and Sauce Tunnel.
|
45
50
|
# We need to use a masquerade hostname on the EC2 end of the tunnel that will be unique within the scope of
|
46
51
|
# this account (e.g. pivotallabs). Therefore we mint a fairly unique hostname here.
|
@@ -78,17 +83,17 @@ module SaucelabsAdapter
|
|
78
83
|
end
|
79
84
|
end
|
80
85
|
|
81
|
-
def create_driver(selenium_args = {}
|
86
|
+
def create_driver(selenium_args = {})
|
82
87
|
args = selenium_client_driver_args.merge(selenium_args)
|
83
|
-
|
84
|
-
|
88
|
+
debug "Connecting to Selenium RC server at #{args[:host]}:#{args[:port]} (testing app at #{args[:url]})"
|
89
|
+
debug "args = #{args.inspect}"
|
85
90
|
driver = ::Selenium::Client::Driver.new(args)
|
86
|
-
|
91
|
+
debug "done"
|
87
92
|
driver
|
88
93
|
end
|
89
94
|
|
90
|
-
def
|
91
|
-
tunnel_method.to_sym
|
95
|
+
def start_tunnel?
|
96
|
+
!tunnel_method.nil? && tunnel_method.to_sym != :othertunnel
|
92
97
|
end
|
93
98
|
|
94
99
|
def self.parse_yaml(selenium_yml_path)
|
@@ -111,18 +116,26 @@ module SaucelabsAdapter
|
|
111
116
|
errors << require_attributes([ :saucelabs_username, :saucelabs_access_key,
|
112
117
|
:saucelabs_browser_os, :saucelabs_browser, :saucelabs_browser_version,
|
113
118
|
:saucelabs_max_duration_seconds ],
|
114
|
-
"when selenium_server_address is saucelabs.com")
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
:when => "when selenium_server_address is saucelabs.com")
|
120
|
+
if tunnel_method
|
121
|
+
errors << require_attributes([:tunnel_to_localhost_port ], :when => "if tunnel_method is set")
|
122
|
+
case tunnel_method.to_sym
|
123
|
+
when nil, ""
|
124
|
+
when :saucetunnel
|
125
|
+
when :othertunnel
|
126
|
+
errors << require_attributes([:application_address], :when => "when tunnel_method is :othertunnel")
|
127
|
+
when :sshtunnel
|
128
|
+
errors << require_attributes([:application_address], :when => "when tunnel_method is :sshtunnel")
|
129
|
+
errors << require_attributes([:tunnel_password, :tunnel_keyfile],
|
130
|
+
:when => "when tunnel_method is :sshtunnel",
|
131
|
+
:any_or_all => :any)
|
132
|
+
else
|
133
|
+
errors << "Unknown tunnel_method: #{tunnel_method}"
|
134
|
+
end
|
122
135
|
end
|
123
136
|
else
|
124
137
|
errors << require_attributes([:selenium_browser_key, :application_address ],
|
125
|
-
"unless server is saucelab.com")
|
138
|
+
:when => "unless server is saucelab.com")
|
126
139
|
end
|
127
140
|
|
128
141
|
errors.flatten!.compact!
|
@@ -131,11 +144,17 @@ module SaucelabsAdapter
|
|
131
144
|
end
|
132
145
|
end
|
133
146
|
|
134
|
-
def require_attributes(names,
|
147
|
+
def require_attributes(names, options = {})
|
148
|
+
default_options = {
|
149
|
+
:when => "",
|
150
|
+
:any_or_all => :all
|
151
|
+
}
|
152
|
+
options.reverse_merge!(default_options)
|
135
153
|
errors = []
|
136
154
|
names.each do |attribute|
|
137
|
-
errors << "#{attribute} is required #{
|
155
|
+
errors << "#{attribute} is required #{options[:when]}" if send(attribute).nil?
|
138
156
|
end
|
157
|
+
errors = [] if options[:any_or_all] == :any && errors.size < names.size
|
139
158
|
errors
|
140
159
|
end
|
141
160
|
|
@@ -6,7 +6,7 @@ if defined?(ActiveSupport)
|
|
6
6
|
setup :configure_selenium # 'before_setup' callback from ActiveSupport::TestCase
|
7
7
|
|
8
8
|
def configure_selenium
|
9
|
-
puts "[saucelabs-adapter]
|
9
|
+
puts "[saucelabs-adapter] configuring selenium..." if ENV['SAUCELABS_ADAPTER_DEBUG'] && ENV['SAUCELABS_ADAPTER_DEBUG'].to_i >= 2
|
10
10
|
selenium_config = SaucelabsAdapter::SeleniumConfig.new(ENV['SELENIUM_ENV'])
|
11
11
|
if defined?(Polonium)
|
12
12
|
polonium_config = Polonium::Configuration.instance
|
@@ -30,7 +30,7 @@ if defined?(Test)
|
|
30
30
|
def attach_to_mediator_with_sauce_tunnel
|
31
31
|
attach_to_mediator_without_sauce_tunnel
|
32
32
|
@selenium_config = SaucelabsAdapter::SeleniumConfig.new(ENV['SELENIUM_ENV'])
|
33
|
-
if @selenium_config.
|
33
|
+
if @selenium_config.start_tunnel?
|
34
34
|
@mediator.add_listener(Test::Unit::UI::TestRunnerMediator::STARTED, &method(:setup_tunnel))
|
35
35
|
@mediator.add_listener(Test::Unit::UI::TestRunnerMediator::FINISHED, &method(:teardown_tunnel))
|
36
36
|
end
|
@@ -39,7 +39,8 @@ if defined?(Test)
|
|
39
39
|
alias_method_chain :attach_to_mediator, :sauce_tunnel unless private_method_defined?(:attach_to_mediator_without_sauce_tunnel)
|
40
40
|
|
41
41
|
def setup_tunnel(suite_name)
|
42
|
-
@tunnel = SaucelabsAdapter::
|
42
|
+
@tunnel = SaucelabsAdapter::Tunnel.factory(@selenium_config)
|
43
|
+
@tunnel.start_tunnel
|
43
44
|
end
|
44
45
|
|
45
46
|
def teardown_tunnel(suite_name)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module SaucelabsAdapter
|
2
|
+
|
3
|
+
class Tunnel
|
4
|
+
|
5
|
+
def self.factory(selenium_config)
|
6
|
+
tunnels = {
|
7
|
+
:saucetunnel => SauceTunnel,
|
8
|
+
:sshtunnel => SshTunnel,
|
9
|
+
:othertunnel => OtherTunnel
|
10
|
+
}
|
11
|
+
raise_with_message "Unknown tunnel type #{selenium_config.tunnel_method}" unless tunnels[selenium_config.tunnel_method.to_sym]
|
12
|
+
|
13
|
+
return tunnels[selenium_config.tunnel_method].new(selenium_config)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(se_config)
|
17
|
+
raise "#{self.class.name}.new requires a SeleniumConfig argument" unless se_config.is_a?(SeleniumConfig)
|
18
|
+
@se_config = se_config
|
19
|
+
end
|
20
|
+
|
21
|
+
def start_tunnel
|
22
|
+
raise "You need to override this method"
|
23
|
+
end
|
24
|
+
|
25
|
+
def shutdown
|
26
|
+
raise "You need to override this method"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -4,17 +4,14 @@ require 'saucerest-ruby/saucerest'
|
|
4
4
|
require 'saucerest-ruby/gateway'
|
5
5
|
|
6
6
|
module SaucelabsAdapter
|
7
|
-
class SauceTunnel
|
8
|
-
DEFAULT_TUNNEL_STARTUP_TIMEOUT = 240
|
7
|
+
class SauceTunnel < Tunnel
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
connect_to_rest_api
|
14
|
-
start_tunnel
|
15
|
-
end
|
9
|
+
include Utilities
|
10
|
+
|
11
|
+
DEFAULT_TUNNEL_STARTUP_TIMEOUT = 240
|
16
12
|
|
17
13
|
def start_tunnel
|
14
|
+
connect_to_rest_api
|
18
15
|
say "Setting up tunnel from Saucelabs (#{@se_config.application_address}:#{@se_config.application_port}) to localhost:#{@se_config.tunnel_to_localhost_port} (timeout #{tunnel_startup_timeout}s)..."
|
19
16
|
boot_tunnel_machine
|
20
17
|
setup_ssh_reverse_tunnel
|
@@ -72,9 +69,9 @@ module SaucelabsAdapter
|
|
72
69
|
end
|
73
70
|
rescue Timeout::Error
|
74
71
|
error_message = "Tunnel did not come up in #{tunnel_startup_timeout} seconds."
|
75
|
-
|
72
|
+
say error_message
|
76
73
|
shutdown_tunnel_machine
|
77
|
-
|
74
|
+
raise_with_message error_message
|
78
75
|
end
|
79
76
|
|
80
77
|
def shutdown_tunnel_machine
|
@@ -91,9 +88,9 @@ module SaucelabsAdapter
|
|
91
88
|
end
|
92
89
|
rescue Timeout::Error
|
93
90
|
# Do not raise here, or else you give false negatives from test runs
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
say "*" * 80
|
92
|
+
say "Sauce Tunnel failed to shut down! Go visit http://saucelabs.com/tunnels and shut down the tunnel for #{@se_config.application_address}"
|
93
|
+
say "*" * 80
|
97
94
|
end
|
98
95
|
|
99
96
|
def setup_ssh_reverse_tunnel
|
@@ -110,13 +107,5 @@ module SaucelabsAdapter
|
|
110
107
|
debug "done."
|
111
108
|
end
|
112
109
|
end
|
113
|
-
|
114
|
-
def say(what)
|
115
|
-
STDOUT.puts "[saucelabs-adapter] " + what
|
116
|
-
end
|
117
|
-
|
118
|
-
def debug(what)
|
119
|
-
STDOUT.puts "[saucelabs-adapter] " + what if ENV['SAUCELABS_ADAPTER_DEBUG']
|
120
|
-
end
|
121
110
|
end
|
122
111
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/ssh/gateway'
|
3
|
+
require 'saucerest-ruby/gateway'
|
4
|
+
|
5
|
+
module SaucelabsAdapter
|
6
|
+
class SshTunnel < Tunnel
|
7
|
+
include Utilities
|
8
|
+
|
9
|
+
def start_tunnel
|
10
|
+
say "Setting up SSH reverse tunnel from #{@se_config.application_address}:#{@se_config.application_port} to localhost:#{@se_config.tunnel_to_localhost_port}"
|
11
|
+
options = @se_config.tunnel_password ? { :password => @se_config.tunnel_password } : { :keys => @se_config.tunnel_keyfile }
|
12
|
+
@gateway = Net::SSH::Gateway.new(@se_config.application_address, @se_config.tunnel_username, options)
|
13
|
+
@port = @gateway.open_remote(@se_config.tunnel_to_localhost_port.to_i, "127.0.0.1", @se_config.application_port.to_i, "0.0.0.0")
|
14
|
+
end
|
15
|
+
|
16
|
+
def shutdown
|
17
|
+
if @gateway
|
18
|
+
say "Shutting down ssh reverse tunnel"
|
19
|
+
@gateway.close(@port) if @port
|
20
|
+
@gateway.shutdown! if @gateway
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SaucelabsAdapter
|
2
|
+
module Utilities
|
3
|
+
|
4
|
+
def diagnostics_prefix
|
5
|
+
@diagnostics_prefix ||= '[saucelabs-adapter]'
|
6
|
+
end
|
7
|
+
|
8
|
+
def say(what)
|
9
|
+
STDOUT.puts "#{diagnostics_prefix} #{what}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def debug(what, print_if_level_ge = 0)
|
13
|
+
if ENV['SAUCELABS_ADAPTER_DEBUG']
|
14
|
+
actual_level = ENV['SAUCELABS_ADAPTER_DEBUG'].to_i
|
15
|
+
STDOUT.puts "#{diagnostics_prefix} #{what}" if print_if_level_ge >= actual_level
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def raise_with_message(message)
|
20
|
+
raise "#{diagnostics_prefix} #{message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'active_support
|
2
|
+
require 'active_support'
|
3
|
+
# Don't pull in the entire saucelabs-adapter otherwise it will complain about: undefined method `setup' for ActiveSupport::TestCase:Class
|
4
|
+
# Apparently this is added from outside
|
5
|
+
require 'saucelabs_adapter/utilities'
|
6
|
+
require 'saucelabs_adapter/selenium_config'
|
3
7
|
|
4
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'saucelabs_adapter')
|
5
8
|
SELENIUM_YML_FIXTURE_FILE = File.join(File.dirname(__FILE__), 'fixtures', 'selenium.yml')
|
6
9
|
|
7
10
|
# Doing this to capture args because I seriously doubt we can mock out .new()
|
@@ -23,9 +26,9 @@ describe "SeleniumConfig" do
|
|
23
26
|
@selenium_config = SaucelabsAdapter::SeleniumConfig.new('local', SELENIUM_YML_FIXTURE_FILE)
|
24
27
|
end
|
25
28
|
|
26
|
-
describe "#
|
29
|
+
describe "#start_tunnel?" do
|
27
30
|
it "should return false" do
|
28
|
-
@selenium_config.
|
31
|
+
@selenium_config.start_tunnel?.should be_false
|
29
32
|
end
|
30
33
|
end
|
31
34
|
|
@@ -73,9 +76,9 @@ describe "SeleniumConfig" do
|
|
73
76
|
@selenium_config = SaucelabsAdapter::SeleniumConfig.new('stanza_saucelabs_firefox_linux_saucetunnel', SELENIUM_YML_FIXTURE_FILE)
|
74
77
|
end
|
75
78
|
|
76
|
-
describe "#
|
79
|
+
describe "#start_tunnel?" do
|
77
80
|
it "should return true" do
|
78
|
-
@selenium_config.
|
81
|
+
@selenium_config.start_tunnel?.should be_true
|
79
82
|
end
|
80
83
|
end
|
81
84
|
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saucelabs-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 8
|
8
|
+
- 0
|
9
|
+
version: 0.8.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Kelly Felkins, Chad Woolley & Sam Pierson
|
@@ -9,59 +14,79 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-02-
|
17
|
+
date: 2010-02-24 00:00:00 -08:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: rest-client
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
25
|
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 2
|
30
|
+
- 0
|
23
31
|
version: 1.2.0
|
24
|
-
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
25
34
|
- !ruby/object:Gem::Dependency
|
26
35
|
name: net-ssh
|
27
|
-
|
28
|
-
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
38
|
requirements:
|
31
39
|
- - ">="
|
32
40
|
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 2
|
43
|
+
- 0
|
44
|
+
- 12
|
33
45
|
version: 2.0.12
|
34
|
-
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
35
48
|
- !ruby/object:Gem::Dependency
|
36
49
|
name: net-ssh-gateway
|
37
|
-
|
38
|
-
|
39
|
-
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
52
|
requirements:
|
41
53
|
- - ">="
|
42
54
|
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 1
|
57
|
+
- 0
|
58
|
+
- 1
|
43
59
|
version: 1.0.1
|
44
|
-
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
45
62
|
- !ruby/object:Gem::Dependency
|
46
63
|
name: selenium-client
|
47
|
-
|
48
|
-
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
50
66
|
requirements:
|
51
67
|
- - ">="
|
52
68
|
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 1
|
71
|
+
- 2
|
72
|
+
- 17
|
53
73
|
version: 1.2.17
|
54
|
-
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
55
76
|
- !ruby/object:Gem::Dependency
|
56
77
|
name: lsof
|
57
|
-
|
58
|
-
|
59
|
-
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
60
80
|
requirements:
|
61
81
|
- - ">="
|
62
82
|
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
- 3
|
86
|
+
- 0
|
63
87
|
version: 0.3.0
|
64
|
-
|
88
|
+
type: :runtime
|
89
|
+
version_requirements: *id005
|
65
90
|
description: "This gem augments Test::Unit and Polonium/Webrat to run Selenium tests in the cloud. "
|
66
91
|
email: pair+kelly+sam@pivotallabs.com
|
67
92
|
executables: []
|
@@ -70,6 +95,7 @@ extensions: []
|
|
70
95
|
|
71
96
|
extra_rdoc_files:
|
72
97
|
- LICENSE
|
98
|
+
- README.html
|
73
99
|
- README.markdown
|
74
100
|
files:
|
75
101
|
- LICENSE
|
@@ -84,11 +110,16 @@ files:
|
|
84
110
|
- lib/saucelabs_adapter.rb
|
85
111
|
- lib/saucelabs_adapter/jsunit_selenium_support.rb
|
86
112
|
- lib/saucelabs_adapter/run_utils.rb
|
87
|
-
- lib/saucelabs_adapter/sauce_tunnel.rb
|
88
113
|
- lib/saucelabs_adapter/selenium_config.rb
|
89
114
|
- lib/saucelabs_adapter/test_unit_adapter.rb
|
115
|
+
- lib/saucelabs_adapter/tunnel.rb
|
116
|
+
- lib/saucelabs_adapter/tunnels/other_tunnel.rb
|
117
|
+
- lib/saucelabs_adapter/tunnels/sauce_tunnel.rb
|
118
|
+
- lib/saucelabs_adapter/tunnels/ssh_tunnel.rb
|
119
|
+
- lib/saucelabs_adapter/utilities.rb
|
90
120
|
- lib/saucerest-ruby/gateway.rb
|
91
121
|
- lib/saucerest-ruby/saucerest.rb
|
122
|
+
- README.html
|
92
123
|
- README.markdown
|
93
124
|
has_rdoc: true
|
94
125
|
homepage: http://github.com/pivotal/saucelabs-adapter
|
@@ -103,18 +134,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
134
|
requirements:
|
104
135
|
- - ">="
|
105
136
|
- !ruby/object:Gem::Version
|
137
|
+
segments:
|
138
|
+
- 0
|
106
139
|
version: "0"
|
107
|
-
version:
|
108
140
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
141
|
requirements:
|
110
142
|
- - ">="
|
111
143
|
- !ruby/object:Gem::Version
|
144
|
+
segments:
|
145
|
+
- 0
|
112
146
|
version: "0"
|
113
|
-
version:
|
114
147
|
requirements: []
|
115
148
|
|
116
149
|
rubyforge_project:
|
117
|
-
rubygems_version: 1.3.
|
150
|
+
rubygems_version: 1.3.6
|
118
151
|
signing_key:
|
119
152
|
specification_version: 3
|
120
153
|
summary: Adapter for running Selenium tests using SauceLabs.com
|