js_test_core 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +11 -0
- data/Rakefile +1 -1
- data/lib/js_test_core.rb +10 -2
- data/lib/js_test_core/client.rb +85 -18
- data/lib/js_test_core/extensions.rb +3 -0
- data/lib/js_test_core/extensions/time.rb +6 -0
- data/lib/js_test_core/resources.rb +4 -4
- data/lib/js_test_core/resources/dir.rb +22 -7
- data/lib/js_test_core/resources/file.rb +24 -16
- data/lib/js_test_core/resources/file_not_found.rb +11 -0
- data/lib/js_test_core/resources/runner.rb +107 -0
- data/lib/js_test_core/resources/session.rb +44 -0
- data/lib/js_test_core/resources/session_finish.rb +17 -0
- data/lib/js_test_core/resources/specs/spec_dir.rb +10 -14
- data/lib/js_test_core/resources/specs/spec_file.rb +1 -1
- data/lib/js_test_core/resources/web_root.rb +51 -39
- data/lib/js_test_core/selenium_server_configuration.rb +48 -0
- data/lib/js_test_core/server.rb +3 -64
- data/lib/js_test_core/thin/js_test_core_connection.rb +4 -38
- data/spec/unit/js_test_core/client_spec.rb +167 -0
- data/spec/unit/{js_spec → js_test_core}/rails_server_spec.rb +0 -0
- data/spec/unit/js_test_core/resources/dir_spec.rb +52 -0
- data/spec/unit/js_test_core/resources/file_not_found_spec.rb +16 -0
- data/spec/unit/js_test_core/resources/file_spec.rb +90 -0
- data/spec/unit/js_test_core/resources/runners/runner_spec.rb +303 -0
- data/spec/unit/js_test_core/resources/session_finish_spec.rb +79 -0
- data/spec/unit/js_test_core/resources/session_spec.rb +82 -0
- data/spec/unit/js_test_core/resources/specs/spec_dir_spec.rb +105 -0
- data/spec/unit/{js_spec → js_test_core}/resources/specs/spec_file_spec.rb +5 -5
- data/spec/unit/js_test_core/resources/web_root_spec.rb +32 -0
- data/spec/unit/js_test_core/selenium_server_configuration_spec.rb +49 -0
- data/spec/unit/{js_spec → js_test_core}/server_spec.rb +18 -32
- data/spec/unit/thin/js_test_core_connection_spec.rb +0 -86
- data/spec/unit/unit_spec_helper.rb +58 -22
- metadata +39 -33
- data/lib/js_test_core/resources/runners.rb +0 -15
- data/lib/js_test_core/resources/runners/firefox_runner.rb +0 -73
- data/lib/js_test_core/resources/suite.rb +0 -24
- data/lib/js_test_core/resources/suite_finish.rb +0 -19
- data/spec/unit/js_spec/client_spec.rb +0 -137
- data/spec/unit/js_spec/resources/dir_spec.rb +0 -42
- data/spec/unit/js_spec/resources/file_spec.rb +0 -88
- data/spec/unit/js_spec/resources/runner_spec.rb +0 -24
- data/spec/unit/js_spec/resources/runners/firefox_runner_spec.rb +0 -161
- data/spec/unit/js_spec/resources/specs/spec_dir_spec.rb +0 -79
- data/spec/unit/js_spec/resources/suite_finish_spec.rb +0 -94
- data/spec/unit/js_spec/resources/suite_spec.rb +0 -44
- data/spec/unit/js_spec/resources/web_root_spec.rb +0 -67
data/CHANGES
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
0.2.0
|
2
|
+
- Renamed Suite to Session to follow Selenium conventions
|
3
|
+
- Renamed SuiteFinish to SessionFinish to follow Selenium conventions
|
4
|
+
- Added /session, which uses the session_id cookie to derive the current session.
|
5
|
+
- Added /session/finished to be used to simplify client/server interactions.
|
6
|
+
- Remove File caching because it doesn't cause a noticable performance benefit over a local network and because it sometimes doesn't write over stale files.
|
7
|
+
- Client accepts selenium_browser_start_command parameter, which allows the user to parameterize the selenium_browser_start_command
|
8
|
+
- Added support for running specs in Internet Explorer via POST /runners/iexplore
|
9
|
+
- Resource file download performance improvements via caching and chunked sending
|
10
|
+
- Fixed false positive bug when Client connection times out
|
11
|
+
|
1
12
|
0.1.1
|
2
13
|
- SuiteFinish#post immediately closes the connection
|
3
14
|
|
data/Rakefile
CHANGED
data/lib/js_test_core.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
gem "thin", ">=0.8.0"
|
3
3
|
|
4
|
-
|
4
|
+
dir = File.dirname(__FILE__)
|
5
|
+
$LOAD_PATH.unshift File.expand_path("#{dir}/../vendor/thin-rest/lib")
|
6
|
+
require "thin_rest"
|
7
|
+
|
8
|
+
# This causes errors to be printed to STDOUT.
|
9
|
+
Thin::Logging.silent = false
|
10
|
+
Thin::Logging.debug = true
|
11
|
+
|
5
12
|
require "fileutils"
|
6
13
|
require "tmpdir"
|
7
14
|
require "timeout"
|
@@ -10,13 +17,14 @@ require "net/http"
|
|
10
17
|
require "selenium"
|
11
18
|
require "optparse"
|
12
19
|
|
13
|
-
dir
|
20
|
+
require "#{dir}/js_test_core/extensions"
|
14
21
|
require "#{dir}/js_test_core/thin"
|
15
22
|
require "#{dir}/js_test_core/rack"
|
16
23
|
require "#{dir}/js_test_core/resources"
|
17
24
|
require "#{dir}/js_test_core/selenium"
|
18
25
|
|
19
26
|
require "#{dir}/js_test_core/client"
|
27
|
+
require "#{dir}/js_test_core/selenium_server_configuration"
|
20
28
|
require "#{dir}/js_test_core/server"
|
21
29
|
require "#{dir}/js_test_core/rails_server"
|
22
30
|
|
data/lib/js_test_core/client.rb
CHANGED
@@ -1,24 +1,17 @@
|
|
1
1
|
module JsTestCore
|
2
2
|
class Client
|
3
|
-
class
|
4
|
-
|
5
|
-
data = []
|
6
|
-
data << "selenium_host=#{CGI.escape(params[:selenium_host] || 'localhost')}"
|
7
|
-
data << "selenium_port=#{CGI.escape((params[:selenium_port] || 4444).to_s)}"
|
8
|
-
data << "spec_url=#{CGI.escape(params[:spec_url])}" if params[:spec_url]
|
9
|
-
response = Net::HTTP.start(DEFAULT_HOST, DEFAULT_PORT) do |http|
|
10
|
-
http.post('/runners/firefox', data.join("&"))
|
11
|
-
end
|
3
|
+
class ClientException < Exception
|
4
|
+
end
|
12
5
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
class InvalidStatusResponse < ClientException
|
7
|
+
end
|
8
|
+
|
9
|
+
class SessionNotFound < ClientException
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def run(parameters={})
|
14
|
+
new(parameters).run
|
22
15
|
end
|
23
16
|
|
24
17
|
def run_argv(argv)
|
@@ -28,6 +21,10 @@ module JsTestCore
|
|
28
21
|
o.banner << "\nUsage: #{$0} [options] [-- untouched arguments]"
|
29
22
|
|
30
23
|
o.on
|
24
|
+
o.on('-s', '--selenium_browser_start_command=selenium_browser_start_command', "The Selenium server command to start the browser. See http://selenium-rc.openqa.org/") do |selenium_browser_start_command|
|
25
|
+
params[:selenium_browser_start_command] = selenium_browser_start_command
|
26
|
+
end
|
27
|
+
|
31
28
|
o.on('-h', '--selenium_host=SELENIUM_HOST', "The host name of the Selenium Server relative to where this file is executed") do |host|
|
32
29
|
params[:selenium_host] = host
|
33
30
|
end
|
@@ -46,5 +43,75 @@ module JsTestCore
|
|
46
43
|
run params
|
47
44
|
end
|
48
45
|
end
|
46
|
+
|
47
|
+
attr_reader :parameters, :query_string, :http, :session_start_response, :last_poll_status, :last_poll_reason, :last_poll
|
48
|
+
def initialize(parameters)
|
49
|
+
@parameters = parameters
|
50
|
+
@query_string = SeleniumServerConfiguration.query_string_from(parameters)
|
51
|
+
end
|
52
|
+
|
53
|
+
def run
|
54
|
+
Net::HTTP.start(DEFAULT_HOST, DEFAULT_PORT) do |@http|
|
55
|
+
start_runner
|
56
|
+
wait_for_session_to_finish
|
57
|
+
end
|
58
|
+
report_result
|
59
|
+
end
|
60
|
+
|
61
|
+
def parts_from_query(query)
|
62
|
+
query.split('&').inject({}) do |acc, key_value_pair|
|
63
|
+
key, value = key_value_pair.split('=')
|
64
|
+
acc[key] = value
|
65
|
+
acc
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
def start_runner
|
71
|
+
@session_start_response = http.post('/runners', query_string)
|
72
|
+
end
|
73
|
+
|
74
|
+
def wait_for_session_to_finish
|
75
|
+
while session_not_completed?
|
76
|
+
poll
|
77
|
+
sleep 0.25
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def report_result
|
82
|
+
case last_poll_status
|
83
|
+
when Resources::Session::SUCCESSFUL_COMPLETION
|
84
|
+
STDOUT.puts "SUCCESS"
|
85
|
+
true
|
86
|
+
when Resources::Session::FAILURE_COMPLETION
|
87
|
+
STDOUT.puts "FAILURE"
|
88
|
+
STDOUT.puts last_poll_reason
|
89
|
+
false
|
90
|
+
else
|
91
|
+
raise InvalidStatusResponse, "Invalid Status: #{last_poll_status}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def session_not_completed?
|
96
|
+
last_poll_status.nil? || last_poll_status == Resources::Session::RUNNING
|
97
|
+
end
|
98
|
+
|
99
|
+
def poll
|
100
|
+
@last_poll = http.get("/sessions/#{session_id}")
|
101
|
+
ensure_session_exists!
|
102
|
+
parts = parts_from_query(last_poll.body)
|
103
|
+
@last_poll_status = parts['status']
|
104
|
+
@last_poll_reason = parts['reason']
|
105
|
+
end
|
106
|
+
|
107
|
+
def ensure_session_exists!
|
108
|
+
if (400..499).include?(Integer(last_poll.code))
|
109
|
+
raise SessionNotFound, "Could not find session with id #{session_id}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def session_id
|
114
|
+
@session_id ||= parts_from_query(session_start_response.body)['session_id']
|
115
|
+
end
|
49
116
|
end
|
50
117
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
dir = File.dirname(__FILE__)
|
2
|
-
require "#{dir}/resources/
|
3
|
-
require "#{dir}/resources/runners/firefox_runner"
|
2
|
+
require "#{dir}/resources/runner"
|
4
3
|
require "#{dir}/resources/file"
|
5
4
|
require "#{dir}/resources/dir"
|
5
|
+
require "#{dir}/resources/file_not_found"
|
6
6
|
require "#{dir}/resources/specs/spec_file"
|
7
7
|
require "#{dir}/resources/specs/spec_dir"
|
8
8
|
require "#{dir}/resources/web_root"
|
9
|
-
require "#{dir}/resources/
|
10
|
-
require "#{dir}/resources/
|
9
|
+
require "#{dir}/resources/session"
|
10
|
+
require "#{dir}/resources/session_finish"
|
@@ -1,25 +1,32 @@
|
|
1
1
|
module JsTestCore
|
2
2
|
module Resources
|
3
3
|
class Dir < File
|
4
|
-
|
4
|
+
route ANY do |env, name|
|
5
5
|
if file = file(name)
|
6
6
|
file
|
7
7
|
elsif subdir = subdir(name)
|
8
8
|
subdir
|
9
9
|
else
|
10
|
-
|
10
|
+
FileNotFound.new(env.merge(:name => name))
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def get
|
15
|
-
|
14
|
+
def get
|
15
|
+
connection.send_head
|
16
|
+
connection.send_body(::Dir.glob("#{absolute_path}/*").inject("") do |html, file|
|
17
|
+
file_basename = ::File.basename(file)
|
18
|
+
html << %Q|<a href="#{file_basename}">#{file_basename}</a>\n|
|
19
|
+
end)
|
16
20
|
end
|
17
21
|
|
18
22
|
def glob(pattern)
|
19
23
|
expanded_pattern = absolute_path + pattern
|
20
24
|
::Dir.glob(expanded_pattern).map do |absolute_globbed_path|
|
21
25
|
relative_globbed_path = absolute_globbed_path.gsub(absolute_path, relative_path)
|
22
|
-
File.new(
|
26
|
+
File.new(env.merge(
|
27
|
+
:absolute_path => absolute_globbed_path,
|
28
|
+
:relative_path => relative_globbed_path
|
29
|
+
))
|
23
30
|
end
|
24
31
|
end
|
25
32
|
|
@@ -31,18 +38,26 @@ module JsTestCore
|
|
31
38
|
end
|
32
39
|
|
33
40
|
def file(name)
|
41
|
+
# N.B. Absolute_path and relative_path are methods. Do not shadow.
|
34
42
|
absolute_file_path, relative_file_path = determine_child_paths(name)
|
35
43
|
if ::File.exists?(absolute_file_path) && !::File.directory?(absolute_file_path)
|
36
|
-
Resources::File.new(
|
44
|
+
Resources::File.new(env.merge(
|
45
|
+
:absolute_path => absolute_file_path,
|
46
|
+
:relative_path => relative_file_path
|
47
|
+
))
|
37
48
|
else
|
38
49
|
nil
|
39
50
|
end
|
40
51
|
end
|
41
52
|
|
42
53
|
def subdir(name)
|
54
|
+
# N.B. Absolute_path and relative_path are methods. Do not shadow.
|
43
55
|
absolute_dir_path, relative_dir_path = determine_child_paths(name)
|
44
56
|
if ::File.directory?(absolute_dir_path)
|
45
|
-
Resources::Dir.new(
|
57
|
+
Resources::Dir.new(env.merge(
|
58
|
+
:absolute_path => absolute_dir_path,
|
59
|
+
:relative_path => relative_dir_path
|
60
|
+
))
|
46
61
|
else
|
47
62
|
nil
|
48
63
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module JsTestCore
|
2
2
|
module Resources
|
3
|
-
class File
|
3
|
+
class File < ThinRest::Resource
|
4
4
|
MIME_TYPES = {
|
5
5
|
'.js' => 'text/javascript',
|
6
6
|
'.css' => 'text/css',
|
@@ -8,25 +8,33 @@ module JsTestCore
|
|
8
8
|
'.jpg' => 'image/jpeg',
|
9
9
|
'.jpeg' => 'image/jpeg',
|
10
10
|
'.gif' => 'image/gif',
|
11
|
-
|
11
|
+
}
|
12
12
|
|
13
|
-
|
13
|
+
property :absolute_path, :relative_path
|
14
14
|
|
15
|
-
def
|
16
|
-
@absolute_path = absolute_path
|
17
|
-
@relative_path = relative_path
|
18
|
-
end
|
19
|
-
|
20
|
-
def get(request, response)
|
15
|
+
def get
|
21
16
|
extension = ::File.extname(absolute_path)
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
content_type = MIME_TYPES[extension] || 'text/html'
|
18
|
+
|
19
|
+
connection.terminate_after_sending do
|
20
|
+
connection.send_head(
|
21
|
+
200,
|
22
|
+
'Content-Type' => content_type,
|
23
|
+
'Last-Modified' => ::File.mtime(absolute_path).rfc822,
|
24
|
+
'Content-Length' => ::File.size(absolute_path)
|
25
|
+
)
|
26
|
+
::File.open(absolute_path) do |file|
|
27
|
+
while !file.eof?
|
28
|
+
connection.send_data(file.read(1024))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
25
32
|
|
26
|
-
|
27
|
-
|
28
|
-
|
33
|
+
def ==(other)
|
34
|
+
return false unless other.class == self.class
|
35
|
+
absolute_path == other.absolute_path && relative_path == other.relative_path
|
36
|
+
end
|
29
37
|
end
|
30
38
|
end
|
31
39
|
end
|
32
|
-
end
|
40
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module JsTestCore
|
2
|
+
module Resources
|
3
|
+
class Runner < ThinRest::Resource
|
4
|
+
class Collection < ThinRest::Resource
|
5
|
+
property :selenium_browser_start_command
|
6
|
+
route 'firefox' do |env, name|
|
7
|
+
self.selenium_browser_start_command = "*firefox"
|
8
|
+
self
|
9
|
+
end
|
10
|
+
route 'iexplore' do |env, name|
|
11
|
+
self.selenium_browser_start_command = "*iexplore"
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def after_initialize
|
16
|
+
super
|
17
|
+
self.selenium_browser_start_command = rack_request['selenium_browser_start_command']
|
18
|
+
end
|
19
|
+
|
20
|
+
def post
|
21
|
+
spec_url = rack_request['spec_url'].to_s == "" ? full_spec_suite_url : rack_request['spec_url']
|
22
|
+
parsed_spec_url = URI.parse(spec_url)
|
23
|
+
selenium_host = rack_request['selenium_host'].to_s == "" ? 'localhost' : rack_request['selenium_host'].to_s
|
24
|
+
selenium_port = rack_request['selenium_port'].to_s == "" ? 4444 : Integer(rack_request['selenium_port'])
|
25
|
+
http_address = "#{parsed_spec_url.scheme}://#{parsed_spec_url.host}:#{parsed_spec_url.port}"
|
26
|
+
driver = Selenium::SeleniumDriver.new(
|
27
|
+
selenium_host,
|
28
|
+
selenium_port,
|
29
|
+
selenium_browser_start_command,
|
30
|
+
http_address
|
31
|
+
)
|
32
|
+
begin
|
33
|
+
driver.start
|
34
|
+
rescue Errno::ECONNREFUSED => e
|
35
|
+
raise Errno::ECONNREFUSED, "Cannot connect to Selenium Server at #{http_address}. To start the selenium server, run `selenium`."
|
36
|
+
end
|
37
|
+
runner = Runner.new(:driver => driver)
|
38
|
+
Runner.register(runner)
|
39
|
+
Thread.start do
|
40
|
+
driver.open("/")
|
41
|
+
driver.create_cookie("session_id=#{runner.session_id}")
|
42
|
+
driver.open(parsed_spec_url.path)
|
43
|
+
end
|
44
|
+
connection.send_head
|
45
|
+
connection.send_body("session_id=#{runner.session_id}")
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
def full_spec_suite_url
|
50
|
+
"#{Server.root_url}/specs"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
def find(id)
|
56
|
+
instances[id.to_s]
|
57
|
+
end
|
58
|
+
|
59
|
+
def finalize(session_id, text)
|
60
|
+
if runner = find(session_id)
|
61
|
+
runner.finalize(text)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def register(runner)
|
66
|
+
instances[runner.session_id] = runner
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
def instances
|
71
|
+
@instances ||= {}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
include FileUtils
|
76
|
+
property :driver
|
77
|
+
attr_reader :profile_dir, :session_run_result
|
78
|
+
|
79
|
+
def after_initialize
|
80
|
+
profile_base = "#{::Dir.tmpdir}/js_test_core/#{self.class.name}"
|
81
|
+
mkdir_p profile_base
|
82
|
+
@profile_dir = "#{profile_base}/#{Time.now.to_i}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def finalize(session_run_result)
|
86
|
+
driver.stop
|
87
|
+
@session_run_result = session_run_result.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
def running?
|
91
|
+
driver.session_started?
|
92
|
+
end
|
93
|
+
|
94
|
+
def successful?
|
95
|
+
!running? && session_run_result.empty?
|
96
|
+
end
|
97
|
+
|
98
|
+
def failed?
|
99
|
+
!running? && !successful?
|
100
|
+
end
|
101
|
+
|
102
|
+
def session_id
|
103
|
+
driver.session_id
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|