testdroid-api 1.0.1
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.
- 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
|