testdroid-api 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.md +19 -0
- data/README.md +130 -0
- data/Rakefile +1 -0
- data/lib/testdroid-api.rb +8 -0
- data/lib/testdroid-api/client.rb +127 -0
- data/lib/testdroid-api/client/device_group.rb +18 -0
- data/lib/testdroid-api/client/project.rb +107 -0
- data/lib/testdroid-api/client/project/test_run.rb +79 -0
- data/lib/testdroid-api/client/project/test_run/device_run.rb +94 -0
- data/lib/testdroid-api/version.rb +3 -0
- data/spec/client_spec.rb +134 -0
- data/spec/device_run_spec.rb +108 -0
- data/spec/fixtures/apiUsers.json +96 -0
- data/spec/fixtures/app.apk +1 -0
- data/spec/fixtures/device_run.json +11 -0
- data/spec/fixtures/device_runs.json +35 -0
- data/spec/fixtures/devices.json +34 -0
- data/spec/fixtures/finished_run.json +5 -0
- data/spec/fixtures/junit.xml +24 -0
- data/spec/fixtures/junit.zip +0 -0
- data/spec/fixtures/logs.txt +1 -0
- data/spec/fixtures/logs.zip +0 -0
- data/spec/fixtures/new_project.json +7 -0
- data/spec/fixtures/new_run.json +5 -0
- data/spec/fixtures/projects.json +22 -0
- data/spec/fixtures/test_runs.json +13 -0
- data/spec/fixtures/waiting_device_run.json +11 -0
- data/spec/project_spec.rb +167 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/test_run_spec.rb +159 -0
- data/testdroidapi.gemspec +29 -0
- metadata +214 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright © 2013 SoundCloud Ltd., Slawomir Smiechura
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# Testdroid-api
|
2
|
+
|
3
|
+
The testdroid-api gem is a client for the [Testdroid Cloud API](http://docs.testdroid.com/_pages/client.html).
|
4
|
+
|
5
|
+
## Installing
|
6
|
+
|
7
|
+
In your application's Gemfile, add `gem 'testdroid-api'`. Then, execute the following command:
|
8
|
+
|
9
|
+
$ bundle
|
10
|
+
|
11
|
+
Alternatively, you can install it yourself:
|
12
|
+
|
13
|
+
$ gem install testdroid-api
|
14
|
+
|
15
|
+
## Using
|
16
|
+
|
17
|
+
The testdroid-api gem allows you to create, manage, and delete projects on testdroid Cloud.
|
18
|
+
For each project, you can trigger a test run. You can get collective or device-specific test results.
|
19
|
+
|
20
|
+
### Authentication
|
21
|
+
```ruby
|
22
|
+
require 'testdroid-api'
|
23
|
+
|
24
|
+
client = TestdroidApi::Client.new('user_email', 'password')
|
25
|
+
client.authenticate!
|
26
|
+
=> "api_token"
|
27
|
+
```
|
28
|
+
|
29
|
+
### TestDroid projects
|
30
|
+
```ruby
|
31
|
+
project = client.projects.first
|
32
|
+
|
33
|
+
# create new project
|
34
|
+
client.create_project('Name', 'Description')
|
35
|
+
=> <TestdroidApi::Client::Project @name=Name, @description=Description>
|
36
|
+
|
37
|
+
# list all projects
|
38
|
+
client.projects
|
39
|
+
=> [
|
40
|
+
<TestdroidApi::Client::Project>,
|
41
|
+
<TestdroidApi::Client::Project>
|
42
|
+
]
|
43
|
+
|
44
|
+
# find projects by name
|
45
|
+
client.projects('Name')
|
46
|
+
=> [ <TestdroidApi::Client::Project, @name=Name> ]
|
47
|
+
|
48
|
+
# delete project
|
49
|
+
project.delete!
|
50
|
+
|
51
|
+
# upload application
|
52
|
+
project.upload_app_file(absolute_path_to_application)
|
53
|
+
|
54
|
+
# upload instrumentation
|
55
|
+
project.upload_test_file(absolute_path_to_tests)
|
56
|
+
```
|
57
|
+
|
58
|
+
### TestDroid test runs
|
59
|
+
```ruby
|
60
|
+
project = client.projects.first
|
61
|
+
run = project.test_runs.first
|
62
|
+
|
63
|
+
# list all test runs
|
64
|
+
project.test_runs
|
65
|
+
=> [
|
66
|
+
<TestdroidApi::Client::Project::TestRun> ,
|
67
|
+
<TestdroidApi::Client::Project::TestRun>
|
68
|
+
]
|
69
|
+
|
70
|
+
# create new test run
|
71
|
+
project.create_test_run
|
72
|
+
=> <TestdroidApi::Client::Project::TestRun>
|
73
|
+
|
74
|
+
# update test run state
|
75
|
+
run.update!
|
76
|
+
=> <TestdroidApi::Client::Project::TestRun>
|
77
|
+
|
78
|
+
# get test run's results as junit xml (zip)
|
79
|
+
File.open(path_to_file, 'w') { |file| file.write(run.junit_results_zip) }
|
80
|
+
|
81
|
+
# get test run's screenshots (zip)
|
82
|
+
File.open(path_to_file, 'w') { |file| file.write(run.screenshots_zip) }
|
83
|
+
|
84
|
+
# get test run's logs (zip)
|
85
|
+
File.open(path_to_file, 'w') { |file| file.write(run.logs_zip) }
|
86
|
+
|
87
|
+
# get test run results
|
88
|
+
run.results
|
89
|
+
=> NotImplemented
|
90
|
+
```
|
91
|
+
|
92
|
+
### TestDroid device runs
|
93
|
+
```ruby
|
94
|
+
project = client.projects.first
|
95
|
+
run = project.test_runs.first
|
96
|
+
device_run = run.device_runs.first
|
97
|
+
|
98
|
+
# get device runs
|
99
|
+
run.device_runs
|
100
|
+
=> [
|
101
|
+
<TestdroidApi::Client::Project::TestRun::DeviceRun>,
|
102
|
+
<TestdroidApi::Client::Project::TestRun::DeviceRun>
|
103
|
+
]
|
104
|
+
|
105
|
+
# get device's results as junit xml
|
106
|
+
File.open(path_to_file, 'w') { |file| file.write(device_run.junit_results) }
|
107
|
+
|
108
|
+
# get device's logs
|
109
|
+
device_run.logs
|
110
|
+
|
111
|
+
# list device's screenshots
|
112
|
+
device_run.screenshots
|
113
|
+
=> [ { "id": 108144 }, { "id": 108145 } ]
|
114
|
+
|
115
|
+
# get a specific screenshot
|
116
|
+
File.open(path_to_file, 'w') { |file| file.write(device_run.screenshot(108144)) }
|
117
|
+
|
118
|
+
# get device run results
|
119
|
+
device_run.results
|
120
|
+
=> NotImplemented
|
121
|
+
```
|
122
|
+
|
123
|
+
## Contributing
|
124
|
+
|
125
|
+
1. Fork it.
|
126
|
+
2. Create a feature branch (`git checkout -b <my-feature-branch>`).
|
127
|
+
3. Commit your changes (`git commit -am 'Add new feature'`).
|
128
|
+
4. Run tests (`rspec`).
|
129
|
+
5. Push your changes to the feature branch (`git push origin <my-feature-branch>`).
|
130
|
+
6. Create a new pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'testdroid-api/version'
|
4
|
+
require 'testdroid-api/client'
|
5
|
+
require 'testdroid-api/client/device_group'
|
6
|
+
require 'testdroid-api/client/project'
|
7
|
+
require 'testdroid-api/client/project/test_run'
|
8
|
+
require 'testdroid-api/client/project/test_run/device_run'
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module TestdroidApi
|
2
|
+
class Client
|
3
|
+
USERS_URL = "http://users.testdroid.com/api/v1/authorize"
|
4
|
+
CLOUD_URL = "https://cloud.testdroid.com"
|
5
|
+
|
6
|
+
# Initialize a Client object with TestDroid credentials
|
7
|
+
#
|
8
|
+
# @param username [String] Username
|
9
|
+
# @param password [String] Password
|
10
|
+
def initialize(username, password)
|
11
|
+
@username = username
|
12
|
+
@password = password
|
13
|
+
@api_key = ""
|
14
|
+
end
|
15
|
+
|
16
|
+
# Authenticate client and retrieve apiKey
|
17
|
+
# @return [String] token
|
18
|
+
def authenticate!
|
19
|
+
response = post(USERS_URL, { "email" => @username, "password" => @password})
|
20
|
+
raise 'Could not authenticate, are you sure you have the right credentials?' if !response['secretApiKey']
|
21
|
+
|
22
|
+
@api_key = response['secretApiKey']
|
23
|
+
end
|
24
|
+
|
25
|
+
# List all projects
|
26
|
+
# @param name [String] project name to match
|
27
|
+
# @return [Array<TestdroidApi::Client::Project>]
|
28
|
+
def projects(name = nil)
|
29
|
+
configs = get_api_request('projects')
|
30
|
+
|
31
|
+
name ? find_projects_by(name, configs) : create_projects_from(configs)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create new project
|
35
|
+
# @param name [String] name of the project
|
36
|
+
# @param description [String] project's description
|
37
|
+
# @return [TestdroidApi::Client::Project]
|
38
|
+
def create_project(name, description)
|
39
|
+
config = post_api_request('projects', { :name => name, :description => description })
|
40
|
+
|
41
|
+
Project.new(self, config)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get user’s clusters
|
45
|
+
# @param name [String] if given only matching device clusters will be returned
|
46
|
+
# @return [Array<TestdroidApi::Client::DeviceGroup>]
|
47
|
+
def device_groups(name = nil)
|
48
|
+
devices = get_api_request('clusters')
|
49
|
+
|
50
|
+
name ? find_device_groups_by(name, devices) : create_device_groups_from(devices)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def post_api_request(endpoint, params = nil, resource_name = endpoint, extra_headers = {})
|
55
|
+
check_api_key
|
56
|
+
post(get_endpoint(endpoint), params, get_auth_header(resource_name).merge(extra_headers))
|
57
|
+
end
|
58
|
+
|
59
|
+
# @api private
|
60
|
+
def get_api_request(endpoint, resource_name = endpoint)
|
61
|
+
check_api_key
|
62
|
+
get(get_endpoint(endpoint), get_auth_header(resource_name) )
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
def get_file(endpoint, resource_name = endpoint)
|
67
|
+
check_api_key
|
68
|
+
RestClient.get(get_endpoint(endpoint), get_auth_header(resource_name))
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
private
|
73
|
+
def post(url, params, headers = nil)
|
74
|
+
JSON.parse(RestClient.post(url, params, headers))
|
75
|
+
end
|
76
|
+
|
77
|
+
def get(url, params=nil)
|
78
|
+
JSON.parse(RestClient.get(url, params))
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_auth_header(resourceName)
|
82
|
+
nonce = get_nonce
|
83
|
+
digestdata = @api_key + ":" + nonce + ":" + resourceName
|
84
|
+
digest = Digest::SHA256.hexdigest(digestdata)
|
85
|
+
{'X-Testdroid-Authentication' => @username + " " + nonce + " " + digest}
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_nonce
|
89
|
+
chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
90
|
+
password = ''
|
91
|
+
6.times do password << chars[rand(chars.size)] end
|
92
|
+
password
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_projects_from(configs)
|
96
|
+
configs.map{|project_config|
|
97
|
+
Project.new(self, project_config)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_projects_by(name, configs)
|
102
|
+
create_projects_from(configs).delete_if{|project|
|
103
|
+
project.name != name
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def find_device_groups_by(name, devices)
|
108
|
+
create_device_groups_from(devices).delete_if{|group|
|
109
|
+
group.display_name != name
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def create_device_groups_from(devices)
|
114
|
+
devices.map{|group|
|
115
|
+
DeviceGroup.new(self, group)
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def check_api_key
|
120
|
+
raise("Are you sure you've authenticated?") if @api_key.empty?
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_endpoint(name)
|
124
|
+
"#{CLOUD_URL}/api/v1/#{name}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module TestdroidApi
|
2
|
+
class Client
|
3
|
+
class DeviceGroup
|
4
|
+
|
5
|
+
attr_reader :id, :name, :display_name, :count, :price, :coverage
|
6
|
+
def initialize(client, config)
|
7
|
+
@client = client
|
8
|
+
@id = config['id']
|
9
|
+
@name = config['name']
|
10
|
+
@display_name = config['displayName']
|
11
|
+
@count = config['deviceCount']
|
12
|
+
@price = config['creditsPrice']
|
13
|
+
@coverage = config['coverage']
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module TestdroidApi
|
2
|
+
class Client
|
3
|
+
class Project
|
4
|
+
|
5
|
+
attr_reader :id, :name, :description, :app_file, :test_file
|
6
|
+
def initialize(client, config)
|
7
|
+
@client = client
|
8
|
+
@id = config['id']
|
9
|
+
@name = config['name']
|
10
|
+
@description = config['description']
|
11
|
+
|
12
|
+
#TODO: Add uploadTimeslate
|
13
|
+
@app_file = config['appFile'] && config['appFile']['originalName']
|
14
|
+
|
15
|
+
#TODO: Add uploadTime
|
16
|
+
@test_file = config['testFile'] && config['testFile']['originalName']
|
17
|
+
end
|
18
|
+
|
19
|
+
# Upload application binary
|
20
|
+
# @param path path to application file
|
21
|
+
def upload_app_file(path)
|
22
|
+
endpoint = "projects/#{id}/apks/application"
|
23
|
+
|
24
|
+
file = File.new(path)
|
25
|
+
digest = mh5digest(file)
|
26
|
+
params = { :file => file, :multipart => true }
|
27
|
+
|
28
|
+
res_name = "upload#{id}application#{digest}"
|
29
|
+
extra_headers = {'X-Testdroid-MD5' => digest}
|
30
|
+
|
31
|
+
@client.post_api_request(endpoint, params, res_name, extra_headers )
|
32
|
+
end
|
33
|
+
|
34
|
+
# Upload test binary
|
35
|
+
# @param path path to test file
|
36
|
+
def upload_test_file(path)
|
37
|
+
endpoint = "projects/#{id}/apks/instrumentation"
|
38
|
+
|
39
|
+
file = File.new(path)
|
40
|
+
digest = mh5digest(file)
|
41
|
+
params = { :file => file, :multipart => true }
|
42
|
+
|
43
|
+
res_name = "upload#{id}instrumentation#{digest}"
|
44
|
+
extra_headers = {'X-Testdroid-MD5' => digest}
|
45
|
+
|
46
|
+
@client.post_api_request(endpoint, params, res_name, extra_headers )
|
47
|
+
end
|
48
|
+
|
49
|
+
# Delete project
|
50
|
+
def delete!
|
51
|
+
res_name = "project/#{id}"
|
52
|
+
endpoint = "projects/#{id}/delete"
|
53
|
+
|
54
|
+
@client.post_api_request(endpoint, nil, res_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Start new test run
|
58
|
+
# @return [TestdroidApi::Client::Project::TestRun]
|
59
|
+
#
|
60
|
+
# @param device_group [TestdroidApi::Client::DeviceGroup] devices to be tested
|
61
|
+
# @param instatest [Boolean] run project in instatest mode
|
62
|
+
# @param screenshots [Boolean] take screenshots automatically
|
63
|
+
def create_test_run(device_group = 'all devices', instatest = false, screenshots = false)
|
64
|
+
res_name = "project/run/#{id}"
|
65
|
+
endpoint = "projects/#{id}/run"
|
66
|
+
params = {
|
67
|
+
:instatestMode => instatest,
|
68
|
+
:autoScreenshots => screenshots,
|
69
|
+
:usedClusterId => get_group_id(device_group)
|
70
|
+
}
|
71
|
+
test_run = @client.post_api_request(endpoint, params, res_name)
|
72
|
+
|
73
|
+
TestdroidApi::Client::Project::TestRun.new(@client, self, test_run)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns project runs from project
|
77
|
+
# @return [Array<Project>]
|
78
|
+
def test_runs
|
79
|
+
res_name = "runs"
|
80
|
+
endpoint = "projects/#{id}/runs"
|
81
|
+
|
82
|
+
configs = @client.get_api_request(endpoint, res_name)
|
83
|
+
|
84
|
+
configs.map{|config|
|
85
|
+
TestdroidApi::Client::Project::TestRun.new(@client, self, config)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def config
|
90
|
+
raise NotImplementedError
|
91
|
+
end
|
92
|
+
|
93
|
+
def update_config(new_config)
|
94
|
+
raise NotImplementedError
|
95
|
+
end
|
96
|
+
private
|
97
|
+
def mh5digest(file)
|
98
|
+
Digest::MD5.hexdigest(file.read)
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_group_id(object)
|
102
|
+
(object.respond_to? :id) ? object.id : object
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|