testdroid-api 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ script: bundle exec rspec spec
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 1.0.0
2
+
3
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in testdroid-api.gemspec
4
+ gemspec
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