spassky 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +84 -0
- data/LICENSE +13 -0
- data/README.md +47 -0
- data/Rakefile +5 -0
- data/bin/spassky +8 -0
- data/bin/spassky-server +9 -0
- data/config.ru +6 -0
- data/features/connection.feature +9 -0
- data/features/device_timeout.feature +31 -0
- data/features/run_html_tests.feature +65 -0
- data/features/run_qunit_tests.feature +45 -0
- data/features/server.feature +11 -0
- data/features/step_definitions/steps.rb +66 -0
- data/features/support/env.rb +31 -0
- data/features/support/fixtures/qunit.js +1512 -0
- data/lib/spassky/client/cli.rb +12 -0
- data/lib/spassky/client/directory_reader.rb +14 -0
- data/lib/spassky/client/pusher.rb +36 -0
- data/lib/spassky/client/test_runner.rb +44 -0
- data/lib/spassky/client/writer.rb +31 -0
- data/lib/spassky/client.rb +1 -0
- data/lib/spassky/server/app.rb +76 -0
- data/lib/spassky/server/assert.js +5 -0
- data/lib/spassky/server/device_list.rb +18 -0
- data/lib/spassky/server/html_test.rb +27 -0
- data/lib/spassky/server/random_string_generator.rb +7 -0
- data/lib/spassky/server/test_run.rb +64 -0
- data/lib/spassky/server.rb +1 -0
- data/lib/spassky/test_result.rb +110 -0
- data/lib/spassky/version.rb +3 -0
- data/lib/spassky.rb +9 -0
- data/spassky.gemspec +31 -0
- data/spec/spassky/client/cli_spec.rb +48 -0
- data/spec/spassky/client/directory_reader_spec.rb +35 -0
- data/spec/spassky/client/pusher_spec.rb +72 -0
- data/spec/spassky/client/test_runner_spec.rb +130 -0
- data/spec/spassky/client/writer_spec.rb +31 -0
- data/spec/spassky/server/app_spec.rb +177 -0
- data/spec/spassky/server/device_list_spec.rb +32 -0
- data/spec/spassky/server/random_string_generator_spec.rb +13 -0
- data/spec/spassky/server/test_run_spec.rb +98 -0
- data/spec/spassky/test_result_spec.rb +116 -0
- data/spec/spec_helper.rb +3 -0
- metadata +215 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm ruby-1.9.2@spassky
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
spassky (0.1.0)
|
5
|
+
json
|
6
|
+
rainbow
|
7
|
+
rest-client
|
8
|
+
sinatra
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: http://rubygems.org/
|
12
|
+
specs:
|
13
|
+
aruba (0.4.3)
|
14
|
+
bcat (>= 0.6.1)
|
15
|
+
childprocess (>= 0.1.9)
|
16
|
+
cucumber (>= 0.10.7)
|
17
|
+
rdiscount (>= 1.6.8)
|
18
|
+
rspec (>= 2.6.0)
|
19
|
+
bcat (0.6.1)
|
20
|
+
rack (~> 1.0)
|
21
|
+
builder (3.0.0)
|
22
|
+
capybara (1.0.0)
|
23
|
+
mime-types (>= 1.16)
|
24
|
+
nokogiri (>= 1.3.3)
|
25
|
+
rack (>= 1.0.0)
|
26
|
+
rack-test (>= 0.5.4)
|
27
|
+
selenium-webdriver (~> 0.2.0)
|
28
|
+
xpath (~> 0.1.4)
|
29
|
+
childprocess (0.1.9)
|
30
|
+
ffi (~> 1.0.6)
|
31
|
+
cucumber (1.0.0)
|
32
|
+
builder (>= 2.1.2)
|
33
|
+
diff-lcs (>= 1.1.2)
|
34
|
+
gherkin (~> 2.4.1)
|
35
|
+
json (>= 1.4.6)
|
36
|
+
term-ansicolor (>= 1.0.5)
|
37
|
+
diff-lcs (1.1.2)
|
38
|
+
ffi (1.0.9)
|
39
|
+
gherkin (2.4.5)
|
40
|
+
json (>= 1.4.6)
|
41
|
+
json (1.5.3)
|
42
|
+
json_pure (1.5.3)
|
43
|
+
mime-types (1.16)
|
44
|
+
nokogiri (1.5.0)
|
45
|
+
rack (1.3.1)
|
46
|
+
rack-test (0.6.0)
|
47
|
+
rack (>= 1.0)
|
48
|
+
rainbow (1.1.1)
|
49
|
+
rake (0.9.2)
|
50
|
+
rdiscount (1.6.8)
|
51
|
+
rest-client (1.6.1)
|
52
|
+
mime-types (>= 1.16)
|
53
|
+
rspec (2.6.0)
|
54
|
+
rspec-core (~> 2.6.0)
|
55
|
+
rspec-expectations (~> 2.6.0)
|
56
|
+
rspec-mocks (~> 2.6.0)
|
57
|
+
rspec-core (2.6.4)
|
58
|
+
rspec-expectations (2.6.0)
|
59
|
+
diff-lcs (~> 1.1.2)
|
60
|
+
rspec-mocks (2.6.0)
|
61
|
+
rubyzip (0.9.4)
|
62
|
+
selenium-webdriver (0.2.2)
|
63
|
+
childprocess (>= 0.1.9)
|
64
|
+
ffi (>= 1.0.7)
|
65
|
+
json_pure
|
66
|
+
rubyzip
|
67
|
+
sinatra (1.2.6)
|
68
|
+
rack (~> 1.1)
|
69
|
+
tilt (< 2.0, >= 1.2.2)
|
70
|
+
term-ansicolor (1.0.5)
|
71
|
+
tilt (1.3.2)
|
72
|
+
xpath (0.1.4)
|
73
|
+
nokogiri (~> 1.3)
|
74
|
+
|
75
|
+
PLATFORMS
|
76
|
+
ruby
|
77
|
+
|
78
|
+
DEPENDENCIES
|
79
|
+
aruba
|
80
|
+
capybara
|
81
|
+
cucumber
|
82
|
+
rake
|
83
|
+
rspec
|
84
|
+
spassky!
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2011 British Broadcasting Corporation
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Spassky #
|
2
|
+
A distributed web testing tool. We use it at the BBC for testing our web apps on a wide range of mobile devices.
|
3
|
+
|
4
|
+
# Installation #
|
5
|
+
|
6
|
+
```
|
7
|
+
gem install spassky
|
8
|
+
```
|
9
|
+
|
10
|
+
# Usage #
|
11
|
+
|
12
|
+
Start the server:
|
13
|
+
|
14
|
+
```
|
15
|
+
spassky-server
|
16
|
+
```
|
17
|
+
|
18
|
+
Run a test:
|
19
|
+
|
20
|
+
```
|
21
|
+
spassky http://localhost:9191 html_test.html
|
22
|
+
```
|
23
|
+
|
24
|
+
Run a test with colour:
|
25
|
+
|
26
|
+
```
|
27
|
+
spassky http://localhost:9191 html_test.html --colour
|
28
|
+
```
|
29
|
+
|
30
|
+
Run a directory that contains a test (the first .html file will be used as the test)
|
31
|
+
|
32
|
+
```
|
33
|
+
spassky http://localhost:9191 test_directory
|
34
|
+
```
|
35
|
+
|
36
|
+
## Why? ##
|
37
|
+
We need to run automated tests on a wide range of web-enabled devices with very mixed capabilities. Some of them have JavaScript, but in many cases it's not standard and buggy. That means web testing tools targeted at desktop browsers don't tend to work very well, if at all. Spassky uses legacy techniques and as little client-side JavaScript as possible to ensure we can run tests on as many devices as possible.
|
38
|
+
|
39
|
+
## How it works ##
|
40
|
+
Physical devices act as test agents, connected permanently to a central server using meta refresh tags. Using a command-line utility, developers execute tests against those browsers by posting to the central server. The tests themselves are plain HTML pages, that are expected to call an assert URL (e.g. by embedding an image) within a time frame. That means even devices without any JavaScript can run automated tests of some kind.
|
41
|
+
|
42
|
+
## Some features that would be nice to have ##
|
43
|
+
- Conveniently run QUnit / Jasmine / other tests
|
44
|
+
- Aliases for user agents
|
45
|
+
- Run tests on a subset of agents
|
46
|
+
- List disconnected devices
|
47
|
+
- Assertions on network activity (e.g. for caching tests)
|
data/Rakefile
ADDED
data/bin/spassky
ADDED
data/bin/spassky-server
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'spassky'
|
6
|
+
require 'spassky/server/app'
|
7
|
+
|
8
|
+
Spassky::Server::App.set :port, 9191
|
9
|
+
Spassky::Server::App.run!
|
data/config.ru
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Feature: Connection
|
2
|
+
In order to run tests with minimal effort
|
3
|
+
As a web developer
|
4
|
+
I want to connect devices to a constantly running service
|
5
|
+
|
6
|
+
Scenario: Connect a device
|
7
|
+
Given a connected mobile device "ipad"
|
8
|
+
Then it should wait for a test to run
|
9
|
+
And the word "Idle" should appear on the device
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Feature: Device Timeout
|
2
|
+
In order for a clean test run
|
3
|
+
As a Developer-in-Test
|
4
|
+
I want to ignore devices that haven't connected recently
|
5
|
+
|
6
|
+
Background: One passing test
|
7
|
+
Given a file named "timed-out.html" with:
|
8
|
+
"""
|
9
|
+
<html>
|
10
|
+
<head>
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<h1>My first passing test!</h1>
|
14
|
+
<script type="text/javascript">
|
15
|
+
assert(true, 'this test should pass');
|
16
|
+
</script>
|
17
|
+
</body>
|
18
|
+
</html>
|
19
|
+
"""
|
20
|
+
|
21
|
+
Scenario: One device times out
|
22
|
+
Given a connected mobile device "ipad"
|
23
|
+
When the device disconnects
|
24
|
+
And I run "spassky <host> timed-out.html" with the server host
|
25
|
+
Then the output should contain:
|
26
|
+
"""
|
27
|
+
TIMED OUT timed-out.html on ipad
|
28
|
+
"""
|
29
|
+
And the exit status should be 2
|
30
|
+
|
31
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
Feature: Run HTML Tests
|
2
|
+
|
3
|
+
In order to inform design decisions
|
4
|
+
As a web developer
|
5
|
+
I want to test HTML code on different web browsers
|
6
|
+
|
7
|
+
Background: Two tests, one passes, one fails
|
8
|
+
Given a file named "passing.html" with:
|
9
|
+
"""
|
10
|
+
<html>
|
11
|
+
<head>
|
12
|
+
</head>
|
13
|
+
<body>
|
14
|
+
<h1>A PASSING test!</h1>
|
15
|
+
<script type="text/javascript">
|
16
|
+
assert(true, 'this test should pass');
|
17
|
+
</script>
|
18
|
+
</body>
|
19
|
+
</html>
|
20
|
+
"""
|
21
|
+
And a file named "failing.html" with:
|
22
|
+
"""
|
23
|
+
<html>
|
24
|
+
<head>
|
25
|
+
</head>
|
26
|
+
<body>
|
27
|
+
<h1>A FAILING test!</h1>
|
28
|
+
<script type="text/javascript">
|
29
|
+
assert(false, 'this test should fail');
|
30
|
+
</script>
|
31
|
+
</body>
|
32
|
+
</html>
|
33
|
+
"""
|
34
|
+
|
35
|
+
Scenario: One passing test on one device
|
36
|
+
Given a connected mobile device "blackberry"
|
37
|
+
When I run "spassky <host> passing.html" with the server host
|
38
|
+
Then the output should contain:
|
39
|
+
"""
|
40
|
+
PASS passing.html on blackberry
|
41
|
+
"""
|
42
|
+
And the exit status should be 0
|
43
|
+
|
44
|
+
Scenario: One passing test on two devices
|
45
|
+
Given a connected mobile device "blackberry"
|
46
|
+
And a connected mobile device "iphone"
|
47
|
+
When I run "spassky <host> passing.html" with the server host
|
48
|
+
Then the output should contain:
|
49
|
+
"""
|
50
|
+
PASS passing.html on blackberry
|
51
|
+
"""
|
52
|
+
Then the output should contain:
|
53
|
+
"""
|
54
|
+
PASS passing.html on iphone
|
55
|
+
"""
|
56
|
+
Then the exit status should be 0
|
57
|
+
|
58
|
+
Scenario: Failing test
|
59
|
+
Given a connected mobile device "blackberry"
|
60
|
+
When I run "spassky <host> failing.html" with the server host
|
61
|
+
Then the output should contain:
|
62
|
+
"""
|
63
|
+
FAIL failing.html on blackberry
|
64
|
+
"""
|
65
|
+
And the exit status should be 1
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Feature: Run QUnit Tests
|
2
|
+
|
3
|
+
In order to inform design decisions
|
4
|
+
As a QUnit user
|
5
|
+
I want to test JavaScript code on different web browsers
|
6
|
+
|
7
|
+
Background: Two tests, one passes, one fails
|
8
|
+
Given a file named "qunit_suite/qunit_test/passing.js" with:
|
9
|
+
"""
|
10
|
+
QUnit.done = function(failed, passed, total, runtime){
|
11
|
+
assert(true, "pass");
|
12
|
+
|
13
|
+
if (failed.length() > 0) {
|
14
|
+
assert(false, "qunit failed");
|
15
|
+
} else {
|
16
|
+
assert(true, "qunit passed");
|
17
|
+
}
|
18
|
+
};
|
19
|
+
|
20
|
+
test("should pass", function() {
|
21
|
+
ok(true, "it passed!");
|
22
|
+
});
|
23
|
+
"""
|
24
|
+
And a file named "qunit_suite/qunit_test/qunit.js" with QUnit.js in it
|
25
|
+
And a file named "qunit_suite/qunit_test/suite.html" with:
|
26
|
+
"""
|
27
|
+
<html>
|
28
|
+
<head>
|
29
|
+
</head>
|
30
|
+
<body>
|
31
|
+
<h1>A QUnit Suite</h1>
|
32
|
+
<script type="text/javascript" src="qunit.js"></script>
|
33
|
+
<script type="text/javascript" src="passing.js"></script>
|
34
|
+
</body>
|
35
|
+
</html>
|
36
|
+
"""
|
37
|
+
|
38
|
+
Scenario: One passing suite on one device
|
39
|
+
Given a connected mobile device "blackberry"
|
40
|
+
When I run "spassky <host> qunit_suite/qunit_test" with the server host
|
41
|
+
Then the output should contain:
|
42
|
+
"""
|
43
|
+
PASS qunit_test on blackberry
|
44
|
+
"""
|
45
|
+
And the exit status should be 0
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Feature: Server
|
2
|
+
In order to spawn a spassky server
|
3
|
+
As a developer
|
4
|
+
I want to be able to type 'spassky-server' to launch a server
|
5
|
+
|
6
|
+
Scenario: Launch Server
|
7
|
+
Given I run spassky-server and then exit it
|
8
|
+
Then I should see the output:
|
9
|
+
"""
|
10
|
+
Sinatra/1.2.6 has taken the stage on 9191
|
11
|
+
"""
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
Given /^a connected mobile device "([^"]*)"$/ do |user_agent|
|
4
|
+
register_driver_with_user_agent user_agent
|
5
|
+
using_session(user_agent) do
|
6
|
+
visit '/device/connect'
|
7
|
+
@uri = URI.parse(current_url)
|
8
|
+
end
|
9
|
+
@last_user_agent = user_agent
|
10
|
+
end
|
11
|
+
|
12
|
+
Given /^a file named "([^"]*)" with ([^\s]*) in it$/ do |file_name, fixture_name|
|
13
|
+
fixture_path = File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "fixtures", fixture_name))
|
14
|
+
fixture_content = File.read(fixture_path)
|
15
|
+
write_file(file_name, fixture_content)
|
16
|
+
end
|
17
|
+
|
18
|
+
When /^I run "([^"]*)" with the server host$/ do |command_line|
|
19
|
+
run_simple(unescape(command_line.gsub('<host>', "http://#{@uri.host}:#{@uri.port}")), false)
|
20
|
+
end
|
21
|
+
|
22
|
+
Then /^it should wait for a test to run$/ do
|
23
|
+
using_session(@last_user_agent) do
|
24
|
+
urls = [page.current_url]
|
25
|
+
sleep 2
|
26
|
+
urls << page.current_url
|
27
|
+
sleep 2
|
28
|
+
urls << page.current_url
|
29
|
+
urls.uniq.size.should eq(3), "expected 3 different urls, got:\n" + urls.join("\n")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Then /^the word "Idle" should appear on the device$/ do
|
34
|
+
using_session(@last_user_agent) do
|
35
|
+
page.html.should include("Idle")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
When /^the device disconnects$/ do
|
40
|
+
using_session(@last_user_agent) do
|
41
|
+
visit "/device/disconnect"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Given /^I run spassky\-server and then exit it$/ do
|
46
|
+
@output = Tempfile.new("output")
|
47
|
+
process = ChildProcess.build('./bin/spassky-server')
|
48
|
+
process.io.stdout = @output
|
49
|
+
process.start
|
50
|
+
sleep 1
|
51
|
+
process.stop
|
52
|
+
end
|
53
|
+
|
54
|
+
Then /^I should see the output:$/ do |string|
|
55
|
+
text = ""
|
56
|
+
sleep_count = 0
|
57
|
+
while text == ""
|
58
|
+
@output.rewind
|
59
|
+
text = @output.read
|
60
|
+
break if sleep_count = 10
|
61
|
+
sleep 0.5
|
62
|
+
sleep_count += 1
|
63
|
+
end
|
64
|
+
|
65
|
+
text.should include string
|
66
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
require 'capybara/dsl'
|
3
|
+
require 'capybara/cucumber'
|
4
|
+
|
5
|
+
require 'aruba/cucumber'
|
6
|
+
|
7
|
+
$:.unshift(File.join(File.dirname(__FILE__), '../../lib'))
|
8
|
+
require 'spassky'
|
9
|
+
require 'spassky/server'
|
10
|
+
|
11
|
+
World(Capybara::DSL)
|
12
|
+
|
13
|
+
Capybara.app = Spassky::Server::App
|
14
|
+
|
15
|
+
Before do
|
16
|
+
Capybara.default_driver = :selenium
|
17
|
+
@aruba_timeout_seconds = 8
|
18
|
+
visit "/devices/clear"
|
19
|
+
end
|
20
|
+
|
21
|
+
def register_driver_with_user_agent user_agent
|
22
|
+
Capybara.register_driver user_agent.to_sym do |app|
|
23
|
+
require 'selenium/webdriver'
|
24
|
+
profile = Selenium::WebDriver::Firefox::Profile.new
|
25
|
+
profile['general.useragent.override'] = user_agent
|
26
|
+
Capybara::Selenium::Driver.new(app, :profile => profile)
|
27
|
+
end
|
28
|
+
Capybara.current_driver = user_agent.to_sym
|
29
|
+
end
|
30
|
+
|
31
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|