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 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