calliper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +1 -0
- data/.travis.yml +16 -0
- data/LICENSE +20 -0
- data/README.md +11 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/calliper.gemspec +23 -0
- data/certs/ysbaddaden.pem +21 -0
- data/lib/calliper.rb +17 -0
- data/lib/calliper/config.rb +44 -0
- data/lib/calliper/minitest.rb +11 -0
- data/lib/calliper/page.rb +37 -0
- data/lib/calliper/rack.rb +19 -0
- data/lib/calliper/rails.rb +2 -0
- data/lib/calliper/server.rb +60 -0
- data/lib/calliper/version.rb +10 -0
- data/lib/calliper/webdriver.rb +76 -0
- data/test/sample/application.rb +22 -0
- data/test/sample/config.ru +2 -0
- data/test/sample/public/javascripts/angular.js +20757 -0
- data/test/sample/public/javascripts/application.js +26 -0
- data/test/sample/views/index.erb +27 -0
- data/test/sample_test.rb +48 -0
- data/test/test_helper.rb +24 -0
- metadata +139 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6744c434f323e9ef6685ecb0ac6eb918fa8c5711
|
4
|
+
data.tar.gz: 3384b9224c9767b45edc52233c707ecd060ca059
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 07b6681def6e25bfb470f39f4250b3a904fa0fd20067ca3c9da52e35dffc42b41719573f3668ce57706b11a2a533b914c76eb52cbade3f50786d94b4ccb31ae3
|
7
|
+
data.tar.gz: b62ef597f198e12f311487a2b6d5131ccdafbc771e9145913355b075ef27335cf0cd85213cc927993ae0d434bf54556ef3b00da5dbc31bd9451ac794cb0c39ee
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.log
|
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
script: bundle exec rake test
|
4
|
+
|
5
|
+
rvm:
|
6
|
+
- 2.0.0
|
7
|
+
- 2.1.0
|
8
|
+
|
9
|
+
env:
|
10
|
+
global:
|
11
|
+
- secure: "naP+5ZG38Ie1/QGxMfrI9kLnGPnqRbIuNw0rFebCOcqeKj9MadBSSQjOi7c0gZBmNaV+js01R9U7RClNFWqHY+tGDWWlYL4gGDT0I2vIgCmnjGnIprD+iuS7ry1tISKjTOn7nfDvVxppK7R2xpuFFjrQgLmYyloPJMacT/n5E9Q="
|
12
|
+
- secure: "Q6wwGwwGfrKBEzoSwylLFjIKj8B5FNHoYEtEtivmHQYrqG+OyiL4YAOxFjpuaiWUJCYKeo6CWhTG9V0NVDgOZY54gxmJhqUpQCFZ53B1MylxANOykADOiXr5xHC9O2VBBF5OU1rkLL5fR8daA/pBOmoidXSD+Psn53ZWnReqZOc="
|
13
|
+
|
14
|
+
addons:
|
15
|
+
sauce_connect: true
|
16
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Julien Portalier
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Calliper
|
2
|
+
|
3
|
+
Protractor for Ruby, or testing your Angular application with elegance.
|
4
|
+
|
5
|
+
Thought this is only but a (working) hack for now.
|
6
|
+
|
7
|
+
## Support
|
8
|
+
|
9
|
+
It currently supports Rack and Rails applications with Minitest, but it should
|
10
|
+
be possible to hack in support for other frameworks (eg: Sinatra, RSpec).
|
11
|
+
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/calliper.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/calliper/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Julien Portalier"]
|
6
|
+
gem.email = ["julien@portalier.com"]
|
7
|
+
gem.description = gem.summary = "Protractor for Ruby, or testing your Angular application with elegance."
|
8
|
+
gem.homepage = "http://github.com/ysbaddaden/calliper"
|
9
|
+
gem.license = "MIT"
|
10
|
+
|
11
|
+
gem.files = `git ls-files | grep -Ev '^(Gemfile|test)'`.split("\n")
|
12
|
+
gem.test_files = `git ls-files -- test/*`.split("\n")
|
13
|
+
gem.name = "calliper"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = Calliper::VERSION::STRING
|
16
|
+
|
17
|
+
gem.cert_chain = ['certs/ysbaddaden.pem']
|
18
|
+
gem.signing_key = File.expand_path('~/.ssh/gem-private_key.pem') if $0 =~ /gem\z/
|
19
|
+
|
20
|
+
gem.add_dependency 'selenium-webdriver'
|
21
|
+
gem.add_development_dependency 'rack'
|
22
|
+
gem.add_development_dependency 'minitest'
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMQ8wDQYDVQQDDAZqdWxp
|
3
|
+
ZW4xGTAXBgoJkiaJk/IsZAEZFglwb3J0YWxpZXIxEzARBgoJkiaJk/IsZAEZFgNj
|
4
|
+
b20wHhcNMTQwMTE0MjIzMTQ4WhcNMTUwMTE0MjIzMTQ4WjBBMQ8wDQYDVQQDDAZq
|
5
|
+
dWxpZW4xGTAXBgoJkiaJk/IsZAEZFglwb3J0YWxpZXIxEzARBgoJkiaJk/IsZAEZ
|
6
|
+
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpxWuWRJXEz2+p
|
7
|
+
2EW4NOPzkKloRLWoj+WQnqhQKT46GbH3ToDId8AMELTDIKpTQFiG2ty6D7S4IBFv
|
8
|
+
7ceFKNk/EJc17mSYE1DzrtItor2/eeGC1zeNfvLjyDtyHKyKUZ891C1D0so5coUx
|
9
|
+
2YbDW5npFkJkPaA5GneH7DFaCoIFLrD7ekbzaZAjlH+EH2fhd1XLhSsPEIiE+OnD
|
10
|
+
ilWnsPoRJAZwQOiVAtvh7xuc+29uSNndIIm2rU00SxbJnzsAq9ZddwPpMU/UcQpD
|
11
|
+
4gCBCaNGzrLz4+upQdYEOuggM7rR3P934qfhIwb+aRGglqdNunmUrdCuhsGXrxq2
|
12
|
+
FvqwDvFZAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
13
|
+
BBQoESDCnNz3LmbpUzOrGeXOpk9sqjAfBgNVHREEGDAWgRRqdWxpZW5AcG9ydGFs
|
14
|
+
aWVyLmNvbTAfBgNVHRIEGDAWgRRqdWxpZW5AcG9ydGFsaWVyLmNvbTANBgkqhkiG
|
15
|
+
9w0BAQUFAAOCAQEAML4w0F/VF0gi5JqMqYSO05TakAauG8jQX0hov5H8M0Xhl79G
|
16
|
+
BdUllH0QEw0cP6J2g46zAk0FGHIGthx0OKKi5YMYTs/KPqOVIAcJslt2sGIC1Ukm
|
17
|
+
wpOWIg1XMe68+JVTktBKcBFAvc0pLtty1TgdSd2wr7KQgfmBU9I8G6AoPYhJOhkG
|
18
|
+
SHTTSX3ms2/XePuSnyOfir/AQC7U0NalnKLNdwY9gkEdNwiTf5Ga/lZVDQ607bow
|
19
|
+
KVqCN//9bevjMk5OiMi9X3Wu/GtVWDwC6OTWFWKd54KgbuWlakO8LC1SMmStnCIF
|
20
|
+
W4qpyMWMZMcB4ZN/0mUVzY5xwrislBtsmQVUSw==
|
21
|
+
-----END CERTIFICATE-----
|
data/lib/calliper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'selenium/webdriver'
|
2
|
+
require 'calliper/config'
|
3
|
+
require 'calliper/page'
|
4
|
+
require 'calliper/server'
|
5
|
+
require 'calliper/webdriver'
|
6
|
+
require 'calliper/rails' if defined?(Rails)
|
7
|
+
require 'calliper/minitest' if defined?(Minitest)
|
8
|
+
|
9
|
+
module Calliper
|
10
|
+
def self.server?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.enable!
|
15
|
+
server.start
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Calliper
|
2
|
+
module Config
|
3
|
+
extend self
|
4
|
+
|
5
|
+
attr_accessor :application
|
6
|
+
attr_accessor :base_host
|
7
|
+
attr_accessor :base_url
|
8
|
+
attr_accessor :browser_name
|
9
|
+
attr_accessor :capabilities
|
10
|
+
attr_accessor :driver
|
11
|
+
attr_accessor :port
|
12
|
+
attr_accessor :remote_url
|
13
|
+
|
14
|
+
def base_url
|
15
|
+
@base_url ||= if base_host
|
16
|
+
build_base_url_from_host
|
17
|
+
else
|
18
|
+
Calliper.server.url
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def browser_name
|
23
|
+
@browser_name ||= :firefox
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_base_url_from_host
|
29
|
+
url = "http://#{base_host}"
|
30
|
+
|
31
|
+
if Calliper.server?
|
32
|
+
url + ":#{Calliper.server.port}"
|
33
|
+
elsif port
|
34
|
+
url + ":#{port}"
|
35
|
+
else
|
36
|
+
url
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.setup
|
42
|
+
yield Config
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Calliper
|
2
|
+
class Page
|
3
|
+
attr_writer :base_url
|
4
|
+
|
5
|
+
def self.get(*args)
|
6
|
+
page = new
|
7
|
+
page.get(*args)
|
8
|
+
page
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(path, sync: true)
|
12
|
+
url = path =~ %r(^http://) ? path : "#{base_url}#{path}"
|
13
|
+
driver.get(url)
|
14
|
+
|
15
|
+
if sync
|
16
|
+
wait = Selenium::WebDriver::Wait.new(timeout: 10)
|
17
|
+
wait.until { driver.find_element(css: "[ng-app]") }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def base_url
|
22
|
+
@base_url ||= Config.base_url
|
23
|
+
end
|
24
|
+
|
25
|
+
def driver
|
26
|
+
Calliper.driver
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(method_name, *args)
|
30
|
+
if driver.respond_to?(method_name)
|
31
|
+
driver.__send__(method_name, *args)
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'calliper/server'
|
2
|
+
|
3
|
+
module Calliper
|
4
|
+
def self.application
|
5
|
+
@app
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.application=(app)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.server
|
13
|
+
@server ||= Server.new(application)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.server?
|
17
|
+
!!@server
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'calliper/config'
|
2
|
+
|
3
|
+
module Calliper
|
4
|
+
def self.server
|
5
|
+
@server ||= Server.new(Config.application)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.server?
|
9
|
+
!!@server
|
10
|
+
end
|
11
|
+
|
12
|
+
# NOTE: Borrows code from the fantastic Teaspoon gem:
|
13
|
+
# http://github.com/modeset/teaspoon
|
14
|
+
class Server
|
15
|
+
attr_reader :application, :port, :logger
|
16
|
+
|
17
|
+
def initialize(application, port = Config.port, logger = nil)
|
18
|
+
@application = application
|
19
|
+
@port = port
|
20
|
+
@logger = logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
@thread = Thread.new do
|
25
|
+
server = Rack::Server.new(rack_options)
|
26
|
+
server.start
|
27
|
+
end
|
28
|
+
|
29
|
+
Timeout.timeout(60) do
|
30
|
+
@thread.join(0.1) until responsive?
|
31
|
+
end
|
32
|
+
rescue Timeout::Error
|
33
|
+
raise "Server failed to start within 60 seconds."
|
34
|
+
rescue => e
|
35
|
+
raise "Cannot start server: #{e.message}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def responsive?
|
39
|
+
return false if @thread && @thread.join(0)
|
40
|
+
TCPSocket.new('127.0.0.1', port).close
|
41
|
+
return true
|
42
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
def rack_options
|
47
|
+
{
|
48
|
+
app: application,
|
49
|
+
environment: 'test',
|
50
|
+
Port: port,
|
51
|
+
AccessLog: [],
|
52
|
+
Logger: logger
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def url
|
57
|
+
"http://127.0.0.1:#{port}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Calliper
|
4
|
+
# TODO: connect to any remote server (eg: custom, saucelabs, browserstack)
|
5
|
+
# FIXME: only firefox quits correctly when used directly
|
6
|
+
def self.driver
|
7
|
+
@driver ||= if local_server_running?
|
8
|
+
Selenium::WebDriver.for(:remote,
|
9
|
+
url: "http://localhost:4444/wd/hub",
|
10
|
+
desired_capabilities: { browserName: Config.driver.to_s }
|
11
|
+
)
|
12
|
+
elsif Config.driver == :remote
|
13
|
+
Selenium::WebDriver.for(:remote,
|
14
|
+
url: Config.remote_url,
|
15
|
+
desired_capabilities: Config.capabilities
|
16
|
+
)
|
17
|
+
else
|
18
|
+
Selenium::WebDriver.for(Config.driver)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.local_server_running?
|
23
|
+
begin
|
24
|
+
TCPSocket.new('localhost', 4444).close
|
25
|
+
true
|
26
|
+
rescue
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.driver?
|
32
|
+
!!@driver
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# We're hacking our way into WebDriver locators in order to emulate some
|
37
|
+
# nice Protractor locators.
|
38
|
+
#
|
39
|
+
# FIXME: use real locators that use protractor's client-side finders:
|
40
|
+
# https://github.com/angular/protractor/blob/master/lib/clientsidescripts.js
|
41
|
+
module Selenium::WebDriver::SearchContext
|
42
|
+
%i(find_element find_elements).each do |name|
|
43
|
+
alias_method "#{name}_without_angular", name
|
44
|
+
|
45
|
+
define_method name do |*args|
|
46
|
+
__send__("#{name}_without_angular", *angular_custom_locator(args))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def angular_custom_locator(args)
|
53
|
+
case args.size
|
54
|
+
when 2
|
55
|
+
how, what = args
|
56
|
+
when 1
|
57
|
+
how = args.first.keys.first
|
58
|
+
what = args.first[how]
|
59
|
+
else
|
60
|
+
return args
|
61
|
+
end
|
62
|
+
|
63
|
+
case how
|
64
|
+
when :model
|
65
|
+
[:css, angular_prefixes.map { |prefix| "[#{prefix}model='#{what}']" }.join(", ")]
|
66
|
+
when :repeater
|
67
|
+
[:css, angular_prefixes.map { |prefix| "[#{prefix}repeat^='#{what}']" }.join(", ")]
|
68
|
+
else
|
69
|
+
args
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def angular_prefixes
|
74
|
+
@angular_prefixes ||= %w(ng- ng_ data-ng- x-ng- ng\\:)
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class SampleApplication < Sinatra::Base
|
5
|
+
configure do
|
6
|
+
disable :protection
|
7
|
+
disable :sessions
|
8
|
+
enable :static
|
9
|
+
|
10
|
+
set :public_folder, File.expand_path('../public', __FILE__)
|
11
|
+
set :view, File.expand_path('../views', __FILE__)
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/' do
|
15
|
+
erb :index
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/notifications.json' do
|
19
|
+
content_type 'application/json'
|
20
|
+
(1..20).map { |id| { id: id, message: "this is notification ##{id}" } }.reverse.to_json
|
21
|
+
end
|
22
|
+
end
|