mixpaneltesting 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/mixpaneltesting.rb +57 -0
- data/lib/mixpaneltesting/docker.rb +97 -0
- data/lib/mixpaneltesting/localsite.rb +40 -0
- data/lib/mixpaneltesting/mixpanel.rb +111 -0
- data/lib/mixpaneltesting/selenium.rb +145 -0
- data/lib/mixpaneltesting/version.rb +3 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 092af6d2591ed376475a1341d7e15c156c367514
|
4
|
+
data.tar.gz: e785e205cdecfff7434f7f0459b01d50cc43cfcf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c2fdb697bd0a79c76cc9a4e60fb236c9d45e0730c3ca5eb18313de843e75c56d57b44d49347783deef8b912432c9ee3dd81f354eecc8ecb88f0162b7bd339c2a
|
7
|
+
data.tar.gz: 9d4298732c0bc9080c518ed61362759320f391bc9985bc3b53b478ddd510e2950ef36369a02724266631daf6d34fe7d346e7d732d20a5e165641530e477c8888
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
require 'mixpaneltesting/version'
|
5
|
+
require 'mixpaneltesting/selenium'
|
6
|
+
require 'mixpaneltesting/mixpanel'
|
7
|
+
require 'mixpaneltesting/docker'
|
8
|
+
require 'mixpaneltesting/localsite'
|
9
|
+
require 'mixpaneltesting/rspec_context'
|
10
|
+
|
11
|
+
module MixpanelTesting
|
12
|
+
|
13
|
+
class MixpanelTesting
|
14
|
+
|
15
|
+
def self.version
|
16
|
+
puts MixpanelTesting::VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class Settings
|
22
|
+
@@generic_timeout = 20
|
23
|
+
@@generic_mixpanel_api_key = nil
|
24
|
+
@@generic_mixpanel_api_secret = nil
|
25
|
+
|
26
|
+
def self.timeout
|
27
|
+
@@generic_timeout
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.mixpanel_api_key
|
31
|
+
@@generic_mixpanel_api_key
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.mixpanel_api_secret
|
35
|
+
@@generic_mixpanel_api_secret
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def self.load_settings(file)
|
40
|
+
|
41
|
+
settings = YAML.load(
|
42
|
+
ERB.new(
|
43
|
+
File.read(
|
44
|
+
RSpec.configuration.mixpanelfilesettings
|
45
|
+
)
|
46
|
+
).result
|
47
|
+
)
|
48
|
+
|
49
|
+
@@generic_mixpanel_api_key = settings['mixpanel']['api_key']
|
50
|
+
@@generic_mixpanel_api_secret = settings['mixpanel']['api_secret']
|
51
|
+
@@generic_timeout == settings['generic_timeout'] if
|
52
|
+
settings['generic_timeout']
|
53
|
+
|
54
|
+
return settings
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'docker'
|
2
|
+
require 'excon'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
|
6
|
+
module MixpanelTesting
|
7
|
+
|
8
|
+
class DockerProviderBrowserNotAvailable < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class DockerProvider
|
12
|
+
|
13
|
+
def initialize(browser, version=nil, debug=false)
|
14
|
+
if !['firefox', 'chrome'].include?(browser)
|
15
|
+
raise DockerProviderBrowserNotAvailable, "#{browser} not available"
|
16
|
+
end
|
17
|
+
|
18
|
+
@log = Logger.new(STDOUT)
|
19
|
+
@docker_uri = URI(ENV['DOCKER_HOST'])
|
20
|
+
@browser = browser
|
21
|
+
@debug = debug
|
22
|
+
@version = version.nil? ? "" : ":#{version}"
|
23
|
+
@image_name = (@debug ?
|
24
|
+
"selenium/standalone-#{@browser}-debug#{@version}" :
|
25
|
+
"selenium/standalone-#{@browser}#{@version}")
|
26
|
+
|
27
|
+
@threads = []
|
28
|
+
|
29
|
+
@log.info ["Creating selenium docker, if you don't see Docker started",
|
30
|
+
"message, try to remove mixpaneltesting docker with:",
|
31
|
+
"docker rm -rf mixpaneltesting"].join('/n')
|
32
|
+
# This settings is fully wired for boot2docker/docker-machines
|
33
|
+
# We should change this to make compatible with other
|
34
|
+
@container = Docker::Container.create(
|
35
|
+
'Image' => @image_name,
|
36
|
+
'name' => 'mixpaneltesting', # Name given for helping with debug
|
37
|
+
'ExposedPorts' => {
|
38
|
+
'4444/tcp' => {},
|
39
|
+
'5900/tcp' => {},
|
40
|
+
},
|
41
|
+
'HostConfig' => {
|
42
|
+
'PortBindings' => {
|
43
|
+
'4444/tcp' => [{ 'HostPort' => '4444'}], # Selenium Port
|
44
|
+
'5900/tcp' => [{ 'HostPort' => '5900'}], # VNC Port for everyone
|
45
|
+
}
|
46
|
+
},
|
47
|
+
"OomKillDisable": false,
|
48
|
+
)
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def start
|
53
|
+
@container.start
|
54
|
+
|
55
|
+
(1..Settings.timeout).each { |i|
|
56
|
+
sleep 1
|
57
|
+
@log.info "Waiting to docker ready: #{i}"
|
58
|
+
break if ready?
|
59
|
+
}
|
60
|
+
@log.info "Docker started"
|
61
|
+
|
62
|
+
open_vnc if @debug
|
63
|
+
end
|
64
|
+
|
65
|
+
def kill
|
66
|
+
@container.kill!
|
67
|
+
@container.delete(:force => true)
|
68
|
+
|
69
|
+
@threads.each { |thr|
|
70
|
+
@log.info "Killing thread"
|
71
|
+
thr.exit
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def ready?
|
76
|
+
puts selenium_uri
|
77
|
+
Excon.get(selenium_uri).status == 302 rescue false
|
78
|
+
end
|
79
|
+
|
80
|
+
def selenium_uri
|
81
|
+
"http://#{@docker_uri.host}:4444/wd/hub"
|
82
|
+
end
|
83
|
+
|
84
|
+
def vnc_uri
|
85
|
+
"vnc://:secret@#{@docker_uri.host}:5900"
|
86
|
+
end
|
87
|
+
|
88
|
+
def open_vnc
|
89
|
+
@threads.push Thread.new {
|
90
|
+
`open #{vnc_uri}`
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'excon'
|
2
|
+
|
3
|
+
|
4
|
+
module MixpanelTesting
|
5
|
+
|
6
|
+
class LocalSiteProvider
|
7
|
+
|
8
|
+
def initialize(cmd, uri)
|
9
|
+
@cmd = cmd
|
10
|
+
@uri = uri
|
11
|
+
@timeout = Settings.timeout
|
12
|
+
@log = Logger.new(STDOUT)
|
13
|
+
@pid = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
@pid = Process.spawn @cmd
|
18
|
+
puts "Spawn #{@cmd} with pid #{@pid}"
|
19
|
+
|
20
|
+
(1..@timeout).each { |i|
|
21
|
+
sleep 1
|
22
|
+
break if ready?
|
23
|
+
}
|
24
|
+
@log.info "Site should be available on #{@uri}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def kill
|
28
|
+
return nil if @pid.nil?
|
29
|
+
@log.info "Killing subprocess with localsite"
|
30
|
+
Process.kill('KILL', @pid)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ready?
|
34
|
+
!Excon.get(@uri).status.nil? rescue false
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'mixpanel_client'
|
5
|
+
|
6
|
+
module MixpanelTesting
|
7
|
+
|
8
|
+
class MixpanelProvider
|
9
|
+
|
10
|
+
def initialize(session_id)
|
11
|
+
@log = Logger.new(STDOUT)
|
12
|
+
@log.info "Login at Mixpanel: #{Settings.mixpanel_api_key}"
|
13
|
+
@client = Mixpanel::Client.new(
|
14
|
+
api_key: Settings.mixpanel_api_key,
|
15
|
+
api_secret: Settings.mixpanel_api_secret
|
16
|
+
)
|
17
|
+
@today = Date.today.strftime("%Y-%m-%d")
|
18
|
+
@yesterday = (Date.today - 1).strftime("%Y-%m-%d")
|
19
|
+
@session_id = session_id
|
20
|
+
puts ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate_events(expected_results)
|
24
|
+
# Arguments:
|
25
|
+
# expected_results: is a hash of event names (string) related with
|
26
|
+
# expected result
|
27
|
+
# Return:
|
28
|
+
# true: if succesfull.
|
29
|
+
# false: if doesn't. Some info messages can go to stdout with this state.
|
30
|
+
|
31
|
+
correct = false
|
32
|
+
|
33
|
+
(1..10).each {
|
34
|
+
mixpanel_result = events_segmentation expected_results.keys
|
35
|
+
|
36
|
+
result = expected_results.each { |event, expected_value|
|
37
|
+
if expected_value != mixpanel_result[event]
|
38
|
+
@log.info "\"#{event}\": expected value #{expected_value} received #{mixpanel_result[event]}"
|
39
|
+
break
|
40
|
+
end
|
41
|
+
}
|
42
|
+
correct = !result.nil?
|
43
|
+
|
44
|
+
break if correct
|
45
|
+
puts "Retrying mixpanel queries in two seconds"
|
46
|
+
sleep(3)
|
47
|
+
}
|
48
|
+
|
49
|
+
correct
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_complex_query(event, extra_query, expected)
|
53
|
+
# Arguments:
|
54
|
+
# event: The event name to use in the query
|
55
|
+
# extra_query: String with params for mixpanel query.
|
56
|
+
# expected: The integer result to be expected
|
57
|
+
# Return:
|
58
|
+
# true: if succesfull.
|
59
|
+
# false: if doesn't. Some info messages can go to stdout with this state.
|
60
|
+
correct = false
|
61
|
+
|
62
|
+
(1..10).each {
|
63
|
+
received = event_complex_query(event, extra_query)
|
64
|
+
@log.info "\"#{event}\": expected value #{expected} received #{received}" unless received == expected
|
65
|
+
correct = received == expected
|
66
|
+
|
67
|
+
break if correct
|
68
|
+
puts "Retrying mixpanel queries in two seconds"
|
69
|
+
sleep(3)
|
70
|
+
}
|
71
|
+
|
72
|
+
correct
|
73
|
+
end
|
74
|
+
|
75
|
+
def events_segmentation(events)
|
76
|
+
# Arguments:
|
77
|
+
# events: is a list of event names.
|
78
|
+
@log.debug "Request to mixpanel: #{events}"
|
79
|
+
Hash[events.map { |event|
|
80
|
+
response = @client.request(
|
81
|
+
'segmentation',
|
82
|
+
event: event,
|
83
|
+
type: 'general',
|
84
|
+
unit: 'day',
|
85
|
+
from_date: @yesterday,
|
86
|
+
to_date: @today,
|
87
|
+
where: "properties[\"mp_session_id\"] == \"#{@session_id}\""
|
88
|
+
)
|
89
|
+
@log.debug JSON.pretty_generate(response)
|
90
|
+
[event, response['data']['values'][event][@today]]
|
91
|
+
}]
|
92
|
+
end
|
93
|
+
|
94
|
+
def event_complex_query(event, extra_query)
|
95
|
+
# Arguments:
|
96
|
+
# event: The event to validate dates.
|
97
|
+
@log.debug "Request to mixpanel: #{event}"
|
98
|
+
response = @client.request(
|
99
|
+
'segmentation',
|
100
|
+
event: event,
|
101
|
+
type: 'general',
|
102
|
+
unit: 'day',
|
103
|
+
from_date: @yesterday,
|
104
|
+
to_date: @today,
|
105
|
+
where: "properties[\"mp_session_id\"] == \"#{@session_id}\" and (#{extra_query})"
|
106
|
+
)
|
107
|
+
@log.debug JSON.pretty_generate(response)
|
108
|
+
response['data']['values'][event][@today]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'selenium-webdriver'
|
5
|
+
|
6
|
+
|
7
|
+
module MixpanelTesting
|
8
|
+
|
9
|
+
class SeleniumProvider
|
10
|
+
|
11
|
+
def initialize(selenium_url, capabilities = :firefox)
|
12
|
+
@selenium_url = selenium_url
|
13
|
+
@log = Logger.new(STDOUT)
|
14
|
+
@log.info "Selenium initializer"
|
15
|
+
if ![:chrome, :firefox].include? capabilities
|
16
|
+
@caps = Selenium::WebDriver::Remote::Capabilities.new
|
17
|
+
if capabilities['device'].nil?
|
18
|
+
@log.info "Creating capabilities, desktop environment"
|
19
|
+
# REQUIRED capabilities
|
20
|
+
['os', 'os_version', 'browser',
|
21
|
+
'browser_version', 'resolution'].each { |key|
|
22
|
+
@caps[key] = capabilities[key]
|
23
|
+
}
|
24
|
+
|
25
|
+
# NOT REQUIRED capabilities
|
26
|
+
[ 'platform', 'browserName'].each { |key|
|
27
|
+
@caps[key.gsub('_','.')] = capabilities[key] unless
|
28
|
+
capabilities[key].nil?
|
29
|
+
}
|
30
|
+
else
|
31
|
+
@log.info "Creating capabilities, mobile environment"
|
32
|
+
@caps["device"] = capabilities['device']
|
33
|
+
@caps[:platform] = capabilities['platform']
|
34
|
+
@caps[:browserName] = capabilities['browserName']
|
35
|
+
end
|
36
|
+
['build', 'project', 'browserstack_local', 'browserstack_debug',
|
37
|
+
'browserstack_localIdentifier' ].each { |key|
|
38
|
+
@caps[key.gsub('_','.')] = capabilities[key] unless
|
39
|
+
capabilities[key].nil?
|
40
|
+
}
|
41
|
+
|
42
|
+
else
|
43
|
+
@caps = capabilities
|
44
|
+
end
|
45
|
+
|
46
|
+
@driver = nil
|
47
|
+
@test_cases = []
|
48
|
+
@wait = 2
|
49
|
+
@log.info "Ready to connect"
|
50
|
+
end
|
51
|
+
|
52
|
+
def connect!
|
53
|
+
@log.info "Connecting to selenium through #{@selenium_url}"
|
54
|
+
@log.debug @caps.inspect
|
55
|
+
@driver = Selenium::WebDriver.for(
|
56
|
+
:remote,
|
57
|
+
:url => @selenium_url,
|
58
|
+
:desired_capabilities => @caps)
|
59
|
+
@log.info('Connected to selenium')
|
60
|
+
@driver.manage.timeouts.implicit_wait = Settings.timeout
|
61
|
+
end
|
62
|
+
|
63
|
+
def start_session(site_url)
|
64
|
+
@session_id = SecureRandom.uuid
|
65
|
+
@session_timestamp = Time.now.getutc
|
66
|
+
connect! if @driver.nil?
|
67
|
+
@site_url = site_url
|
68
|
+
|
69
|
+
@log.info "Start mixpanel session #{@session_id}"
|
70
|
+
|
71
|
+
start_url = site_url.include?('?') ? "#{site_url}&" : "#{site_url}?"
|
72
|
+
start_url = "#{start_url}mp_session_start=#{@session_id}"
|
73
|
+
@driver.get start_url
|
74
|
+
waitfor()
|
75
|
+
end
|
76
|
+
|
77
|
+
def reset_cookies
|
78
|
+
@log.info "Reset cookies!!"
|
79
|
+
@driver.deleteAllCookies
|
80
|
+
end
|
81
|
+
|
82
|
+
def navigate(url)
|
83
|
+
@driver.get url.start_with?('http') ? url : "#{@site_url}#{url}"
|
84
|
+
waitfor()
|
85
|
+
end
|
86
|
+
|
87
|
+
def end_session(site_url = nil)
|
88
|
+
puts @site_url
|
89
|
+
site_url = site_url.nil? ? @site_url : site_url
|
90
|
+
end_url = site_url.include?('?') ? "#{site_url}&" : "#{site_url}?"
|
91
|
+
end_url = "#{end_url}mp_session_end=#{@session_id}"
|
92
|
+
@driver.get end_url
|
93
|
+
waitfor()
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_page_source
|
97
|
+
@driver.page_source
|
98
|
+
end
|
99
|
+
|
100
|
+
def quit
|
101
|
+
@log.info "Clossing selenium connection BYE!!"
|
102
|
+
@driver.quit
|
103
|
+
end
|
104
|
+
|
105
|
+
def session_id
|
106
|
+
@session_id
|
107
|
+
end
|
108
|
+
|
109
|
+
def driver
|
110
|
+
@driver
|
111
|
+
end
|
112
|
+
|
113
|
+
def waitfor(n=false)
|
114
|
+
# Use waitfor for correct mixpanel js loading and tracking
|
115
|
+
wait = n ? n : @wait
|
116
|
+
(1..wait).each {
|
117
|
+
print "."
|
118
|
+
sleep(1)
|
119
|
+
}
|
120
|
+
print "\r"
|
121
|
+
end
|
122
|
+
|
123
|
+
def waitfor_object_displayed(*selector)
|
124
|
+
# Use this method to tell selenium to wait until one element is displayed
|
125
|
+
# Arguments:
|
126
|
+
# selector: is selenium find_element selector
|
127
|
+
# ex: waitfor_object_displayed(:class, 'cookies-eu-ok')
|
128
|
+
@log.debug "Waiting for #{selector} to be displayed"
|
129
|
+
return if @driver.find_element(*selector).displayed?
|
130
|
+
wait = Selenium::WebDriver::Wait.new(:timeout => Settings.timeout)
|
131
|
+
wait.until { !@driver.find_element(*selector).displayed? }
|
132
|
+
end
|
133
|
+
|
134
|
+
def click(selector)
|
135
|
+
# Click in the object given by selector. It should be visible in the
|
136
|
+
# browser area.
|
137
|
+
@log.debug "Clicking at #{selector}"
|
138
|
+
link = @driver.find_element(selector)
|
139
|
+
@driver.execute_script("arguments[0].scrollIntoView(true);", link)
|
140
|
+
link = @driver.find_element(selector)
|
141
|
+
link.click
|
142
|
+
waitfor()
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mixpaneltesting
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Antonio Perez-Aranda Alcaide
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: selenium-webdriver
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.46.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.46.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mixpanel_client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: docker-api
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dotenv
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Mixpanel integration testing with Selenium Driver (local, docker, browserstack
|
70
|
+
compatible)
|
71
|
+
email:
|
72
|
+
- ant30tx@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- lib/mixpaneltesting.rb
|
78
|
+
- lib/mixpaneltesting/docker.rb
|
79
|
+
- lib/mixpaneltesting/localsite.rb
|
80
|
+
- lib/mixpaneltesting/mixpanel.rb
|
81
|
+
- lib/mixpaneltesting/selenium.rb
|
82
|
+
- lib/mixpaneltesting/version.rb
|
83
|
+
homepage: http://rubygems.org/gems/mixpaneltesting
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.4.5
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Mixpanel integration testing with Selenium
|
107
|
+
test_files: []
|