saucelabs-adapter 0.7.7 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|