spassky 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +3 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +84 -0
  5. data/LICENSE +13 -0
  6. data/README.md +47 -0
  7. data/Rakefile +5 -0
  8. data/bin/spassky +8 -0
  9. data/bin/spassky-server +9 -0
  10. data/config.ru +6 -0
  11. data/features/connection.feature +9 -0
  12. data/features/device_timeout.feature +31 -0
  13. data/features/run_html_tests.feature +65 -0
  14. data/features/run_qunit_tests.feature +45 -0
  15. data/features/server.feature +11 -0
  16. data/features/step_definitions/steps.rb +66 -0
  17. data/features/support/env.rb +31 -0
  18. data/features/support/fixtures/qunit.js +1512 -0
  19. data/lib/spassky/client/cli.rb +12 -0
  20. data/lib/spassky/client/directory_reader.rb +14 -0
  21. data/lib/spassky/client/pusher.rb +36 -0
  22. data/lib/spassky/client/test_runner.rb +44 -0
  23. data/lib/spassky/client/writer.rb +31 -0
  24. data/lib/spassky/client.rb +1 -0
  25. data/lib/spassky/server/app.rb +76 -0
  26. data/lib/spassky/server/assert.js +5 -0
  27. data/lib/spassky/server/device_list.rb +18 -0
  28. data/lib/spassky/server/html_test.rb +27 -0
  29. data/lib/spassky/server/random_string_generator.rb +7 -0
  30. data/lib/spassky/server/test_run.rb +64 -0
  31. data/lib/spassky/server.rb +1 -0
  32. data/lib/spassky/test_result.rb +110 -0
  33. data/lib/spassky/version.rb +3 -0
  34. data/lib/spassky.rb +9 -0
  35. data/spassky.gemspec +31 -0
  36. data/spec/spassky/client/cli_spec.rb +48 -0
  37. data/spec/spassky/client/directory_reader_spec.rb +35 -0
  38. data/spec/spassky/client/pusher_spec.rb +72 -0
  39. data/spec/spassky/client/test_runner_spec.rb +130 -0
  40. data/spec/spassky/client/writer_spec.rb +31 -0
  41. data/spec/spassky/server/app_spec.rb +177 -0
  42. data/spec/spassky/server/device_list_spec.rb +32 -0
  43. data/spec/spassky/server/random_string_generator_spec.rb +13 -0
  44. data/spec/spassky/server/test_run_spec.rb +98 -0
  45. data/spec/spassky/test_result_spec.rb +116 -0
  46. data/spec/spec_helper.rb +3 -0
  47. metadata +215 -0
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ tmp
2
+ *.swp
3
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.9.2@spassky
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in akki.gemspec
4
+ gemspec
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
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default do
4
+ sh "rspec spec --color"
5
+ end
data/bin/spassky ADDED
@@ -0,0 +1,8 @@
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/client/cli'
7
+
8
+ Spassky::Client::Cli.run(ARGV)
@@ -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,6 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), "lib"))
2
+
3
+ require 'spassky'
4
+ require 'spassky/server'
5
+
6
+ run Spassky::Server::App.new()
@@ -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']}"