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
@@ -0,0 +1,79 @@
|
|
1
|
+
module TestdroidApi
|
2
|
+
class Client
|
3
|
+
class Project
|
4
|
+
class TestRun
|
5
|
+
|
6
|
+
attr_reader :project_id, :id, :name, :state
|
7
|
+
def initialize(client, project, config)
|
8
|
+
@client = client
|
9
|
+
@project_id = project.id
|
10
|
+
|
11
|
+
update(config)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Updates test_run information
|
15
|
+
def update!
|
16
|
+
res_name = "run"
|
17
|
+
endpoint = "projects/#{@project_id}/runs/#{id}"
|
18
|
+
|
19
|
+
config = @client.get_api_request(endpoint, res_name)
|
20
|
+
|
21
|
+
update(config)
|
22
|
+
end
|
23
|
+
|
24
|
+
def results
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns devices that the run was executed on.
|
29
|
+
# @return [Array<TestdroidApi::Client::TestRun::DeviceRun>]
|
30
|
+
def device_runs
|
31
|
+
res_name = "deviceRuns"
|
32
|
+
endpoint = "projects/#{@project_id}/runs/#{id}/device-runs"
|
33
|
+
|
34
|
+
results = @client.get_api_request(endpoint, res_name)
|
35
|
+
results.map{|device_run|
|
36
|
+
TestdroidApi::Client::Project::TestRun::DeviceRun.new(@client, self, device_run)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get test run's screenshots as a zip file.
|
41
|
+
def screenshots_zip
|
42
|
+
res_name = 'screenshots.zip'
|
43
|
+
endpoint = "projects/#{@project_id}/runs/#{id}/screenshots.zip"
|
44
|
+
|
45
|
+
@client.get_file(endpoint, res_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get test run's junit results as a zip file
|
49
|
+
def junit_results_zip
|
50
|
+
res_name = 'junits.zip'
|
51
|
+
endpoint = "projects/#{@project_id}/runs/#{id}/junits.zip"
|
52
|
+
|
53
|
+
@client.get_file(endpoint, res_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get test run's logs as a zip file.
|
57
|
+
def logs_zip
|
58
|
+
res_name = 'logs.zip'
|
59
|
+
endpoint = "projects/#{@project_id}/runs/#{id}/logs.zip"
|
60
|
+
|
61
|
+
@client.get_file(endpoint, res_name)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Did test run finish
|
65
|
+
# @return [Boolean]
|
66
|
+
def finished?
|
67
|
+
state == 'FINISHED'
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def update(config)
|
72
|
+
@id = config['id']
|
73
|
+
@name = config['displayName']
|
74
|
+
@state = config['groupState']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module TestdroidApi
|
2
|
+
class Client
|
3
|
+
class Project
|
4
|
+
class TestRun
|
5
|
+
class DeviceRun
|
6
|
+
|
7
|
+
attr_reader :id, :device_id, :fail_message, :name, :state, :finished
|
8
|
+
attr_reader :screenshots_url, :junit_url, :logs_url
|
9
|
+
|
10
|
+
def initialize(client, run, config)
|
11
|
+
@client = client
|
12
|
+
@project_id = run.project_id
|
13
|
+
@run_id = run.id
|
14
|
+
|
15
|
+
update(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Updates device run information
|
19
|
+
def update!
|
20
|
+
res_name = 'deviceRun'
|
21
|
+
endpoint = "projects/#{@project_id}/runs/#{@run_id}/device-runs/#{id}"
|
22
|
+
|
23
|
+
config = @client.get_api_request(endpoint, res_name)
|
24
|
+
|
25
|
+
update(config)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get device run's test results as JUnit XML
|
29
|
+
def junit_results
|
30
|
+
res_name = 'junit XML'
|
31
|
+
endpoint = "projects/#{@project_id}/runs/#{@run_id}/device-runs/#{id}_junit.xml"
|
32
|
+
|
33
|
+
@client.get_file(endpoint, res_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
#Get device run's logs
|
37
|
+
def logs
|
38
|
+
res_name = 'log'
|
39
|
+
endpoint = "projects/#{@project_id}/runs/#{@run_id}/device-runs/#{id}_log.txt"
|
40
|
+
|
41
|
+
@client.get_file(endpoint, res_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get device run's screenshots as a zip file.
|
45
|
+
def screenshots_zip
|
46
|
+
res_name = 'screenshots.zip'
|
47
|
+
endpoint = "projects/#{@project_id}/runs/#{@run_id}/device-runs/#{id}/screenshots.zip"
|
48
|
+
|
49
|
+
@client.get_file(endpoint, res_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns screenshots' ids for device run.
|
53
|
+
def screenshots
|
54
|
+
res_name = 'deviceRunScreenshots'
|
55
|
+
endpoint = "projects/#{@project_id}/runs/#{@run_id}/device-runs/#{id}/screenshots"
|
56
|
+
|
57
|
+
@client.get_api_request(endpoint, res_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get device's run screenshot.
|
61
|
+
# @param screenshot_id [Integer] screenshot id
|
62
|
+
def screenshot(screenshot_id)
|
63
|
+
res_name = 'screenshot'
|
64
|
+
endpoint = "projects/#{@project_id}/runs/#{@run_id}/device-runs/#{id}/screenshots/#{screenshot_id}"
|
65
|
+
|
66
|
+
@client.get_file(endpoint, res_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Did test run finish
|
70
|
+
def finished?
|
71
|
+
finished
|
72
|
+
end
|
73
|
+
|
74
|
+
def results
|
75
|
+
raise NotImplementedError
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def update(config)
|
80
|
+
@id = config['id']
|
81
|
+
@device_id = config['deviceId']
|
82
|
+
@fail_message = config['customerFailureMessage']
|
83
|
+
@name = config['deviceName']
|
84
|
+
@state = config['groupState']
|
85
|
+
@finished = config['finished']
|
86
|
+
@screenshots_url = config['screenshotsURI']
|
87
|
+
@junit_url = config['junitURI']
|
88
|
+
@logs_url = config['logURI']
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TestdroidApi::Client do
|
4
|
+
let(:client_class) { described_class }
|
5
|
+
let(:client) {client_class.new("user", "password")}
|
6
|
+
|
7
|
+
let(:auth_url) {"http://users.testdroid.com/api/v1/authorize"}
|
8
|
+
before do
|
9
|
+
stub_request(:post, auth_url).
|
10
|
+
to_return(:status => 200, :body => {"secretApiKey" => "1"}.to_json, :headers => {})
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#authenticate' do
|
14
|
+
it 'sends the right request' do
|
15
|
+
client.authenticate!
|
16
|
+
|
17
|
+
a_request(:post, auth_url).
|
18
|
+
with(:body => {:email => "user", :password => "password" }).
|
19
|
+
should have_been_made
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when successful' do
|
23
|
+
it 'stores apiKey' do
|
24
|
+
client.authenticate!
|
25
|
+
|
26
|
+
client.instance_variable_get('@api_key').should == '1'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when fails' do
|
31
|
+
it 'throws exception' do
|
32
|
+
stub_request(:post, "http://users.testdroid.com/api/v1/authorize").
|
33
|
+
to_return(:status => 200, :body => {}.to_json, :headers => {})
|
34
|
+
|
35
|
+
expect { client.authenticate! }.
|
36
|
+
to raise_error(RuntimeError, "Could not authenticate, are you sure you have the right credentials?")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when authenticated' do
|
42
|
+
before do
|
43
|
+
client.authenticate!
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#projects' do
|
47
|
+
let(:url) {"https://cloud.testdroid.com/api/v1/projects"}
|
48
|
+
let(:response_body) {File.new(File.dirname(__FILE__) + "/fixtures/projects.json").read}
|
49
|
+
|
50
|
+
before do
|
51
|
+
stub_request(:get, url).
|
52
|
+
to_return(:body => JSON.parse(response_body).to_json)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'sends the right request' do
|
56
|
+
client.projects
|
57
|
+
|
58
|
+
a_request(:get, url).should have_been_made
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'without any params' do
|
62
|
+
it 'should return array of projects' do
|
63
|
+
TestdroidApi::Client::Project.stub(:new).with(any_args).and_return(true)
|
64
|
+
|
65
|
+
client.projects.should == [true, true]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with project names' do
|
70
|
+
it 'finds the matching projects' do
|
71
|
+
client.projects('Project 1').first.name.should == "Project 1"
|
72
|
+
end
|
73
|
+
it 'removes unmatched projects' do
|
74
|
+
client.projects('Project 1').count.should == 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#create_project' do
|
80
|
+
let(:url) {"https://cloud.testdroid.com/api/v1/projects"}
|
81
|
+
let(:response_body) {File.new(File.dirname(__FILE__) + "/fixtures/new_project.json").read}
|
82
|
+
before do
|
83
|
+
stub_request(:post, url).
|
84
|
+
to_return(:status => 200, :body => JSON.parse(response_body).to_json, :headers => {})
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'sends the right request' do
|
88
|
+
client.create_project("Name", "Description" )
|
89
|
+
|
90
|
+
a_request(:post, url).
|
91
|
+
with(:body => {:name => "Name", :description => "Description"} ).
|
92
|
+
should have_been_made
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'returns new project' do
|
96
|
+
client.create_project(nil, nil).name.should == "New project name"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#device groups' do
|
101
|
+
let(:url) {url = "https://cloud.testdroid.com/api/v1/clusters"}
|
102
|
+
let(:response_body) {File.new(File.dirname(__FILE__) + "/fixtures/devices.json").read}
|
103
|
+
before do
|
104
|
+
stub_request(:get, url).
|
105
|
+
to_return(:status => 200, :body => JSON.parse(response_body).to_json, :headers => {})
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'sends the right request' do
|
109
|
+
client.device_groups
|
110
|
+
|
111
|
+
a_request(:get, url).
|
112
|
+
should have_been_made
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'without params' do
|
116
|
+
it 'returns all device groups' do
|
117
|
+
client.device_groups("Test devices").first.display_name.should == "Test devices"
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'removes unmatched projects' do
|
121
|
+
client.device_groups("Test devices").count.should == 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'when not authenticated' do
|
129
|
+
it 'does not allow to fetch projects' do
|
130
|
+
expect{client.projects}.
|
131
|
+
to raise_error(RuntimeError, "Are you sure you've authenticated?")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TestdroidApi::Client::Project::TestRun::DeviceRun do
|
4
|
+
|
5
|
+
let(:auth_url) { "http://users.testdroid.com/api/v1/authorize" }
|
6
|
+
let(:client) { TestdroidApi::Client.new('username', 'password') }
|
7
|
+
let(:run) { double(TestdroidApi::Client::Project::TestRun) }
|
8
|
+
let(:config) { JSON.parse(File.new(File.dirname(__FILE__) + "/fixtures/device_run.json").read)}
|
9
|
+
before do
|
10
|
+
stub_request(:post, auth_url).
|
11
|
+
to_return(:status => 200, :body => {"secretApiKey" => "1"}.to_json, :headers => {})
|
12
|
+
client.authenticate!
|
13
|
+
|
14
|
+
run.stub(:project_id).and_return(1)
|
15
|
+
run.stub(:id).and_return(100)
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when authenticated' do
|
19
|
+
let(:device_run) { described_class.new(client, run, config) }
|
20
|
+
|
21
|
+
describe '#update!' do
|
22
|
+
let(:url) { "https://cloud.testdroid.com/api/v1/projects/1/runs/100/device-runs/1000" }
|
23
|
+
let(:response_body) {File.new(File.dirname(__FILE__) + "/fixtures/waiting_device_run.json").read}
|
24
|
+
before do
|
25
|
+
stub_request(:get, url).
|
26
|
+
to_return(:status => 200, :body => JSON.parse(response_body).to_json, :headers => {})
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'sends the right request' do
|
30
|
+
device_run.update!
|
31
|
+
|
32
|
+
a_request(:get, url).
|
33
|
+
should have_been_made
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'updates run state' do
|
37
|
+
device_run.update!
|
38
|
+
device_run.state.should == "WAITING"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
describe '#finished' do
|
44
|
+
let(:url) { "https://cloud.testdroid.com/api/v1/projects/1/runs/100/device-runs/1000" }
|
45
|
+
let(:response_body) {File.new(File.dirname(__FILE__) + "/fixtures/waiting_device_run.json").read}
|
46
|
+
before do
|
47
|
+
stub_request(:get, url).
|
48
|
+
to_return(:status => 200, :body => JSON.parse(response_body).to_json, :headers => {})
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns true when run is finished' do
|
52
|
+
device_run.should be_finished
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns false when run it not finished' do
|
56
|
+
device_run.update!
|
57
|
+
device_run.should_not be_finished
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#logs_zip' do
|
62
|
+
let(:url) { "https://cloud.testdroid.com/api/v1/projects/1/runs/100/device-runs/1000_log.txt" }
|
63
|
+
let(:response_body) {File.new(File.dirname(__FILE__) + "/fixtures/logs.txt")}
|
64
|
+
before do
|
65
|
+
stub_request(:get, url).
|
66
|
+
to_return(:status => 200, :body => response_body, :headers => {})
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'sends the right request' do
|
70
|
+
device_run.logs
|
71
|
+
|
72
|
+
a_request(:get, url).should have_been_made
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#screenshots_zip' do
|
78
|
+
let(:url) { "https://cloud.testdroid.com/api/v1/projects/1/runs/100/device-runs/1000/screenshots/1000" }
|
79
|
+
|
80
|
+
before do
|
81
|
+
stub_request(:get, url).
|
82
|
+
to_return(:status => 200, :body => "", :headers => {})
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'sends the right request' do
|
86
|
+
device_run.screenshot(1000)
|
87
|
+
|
88
|
+
a_request(:get, url).should have_been_made
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#junit_results' do
|
94
|
+
let(:url) { "https://cloud.testdroid.com/api/v1/projects/1/runs/100/device-runs/1000_junit.xml" }
|
95
|
+
let(:response_body) {File.new(File.dirname(__FILE__) + "/fixtures/junit.xml")}
|
96
|
+
before do
|
97
|
+
stub_request(:get, url).
|
98
|
+
to_return(:status => 200, :body => response_body, :headers => {})
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'sends the right request' do
|
102
|
+
device_run.junit_results
|
103
|
+
|
104
|
+
a_request(:get, url).should have_been_made
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
{
|
2
|
+
"address": "",
|
3
|
+
"name": "User Surname",
|
4
|
+
"id": 1333333,
|
5
|
+
"state": "",
|
6
|
+
"country": "DE",
|
7
|
+
"displayCountry": "Germany",
|
8
|
+
"timeZone": "GMT",
|
9
|
+
"username": "132254",
|
10
|
+
"displayname": "User user",
|
11
|
+
"enabled": true,
|
12
|
+
"active": true,
|
13
|
+
"code": "",
|
14
|
+
"email": "user@soundcloud.com",
|
15
|
+
"googleId": null,
|
16
|
+
"billing": false,
|
17
|
+
"organization": {
|
18
|
+
"name": "",
|
19
|
+
"id": 1
|
20
|
+
},
|
21
|
+
"countryDisplayname": "Germany",
|
22
|
+
"city": "",
|
23
|
+
"phone": "",
|
24
|
+
"secretApiKey": "xxxxxxxxxxxxxxxxx",
|
25
|
+
"Organization role": "Other",
|
26
|
+
"vatID": "",
|
27
|
+
"accessible": true,
|
28
|
+
"activeRoles": [
|
29
|
+
{
|
30
|
+
"name": "TEST_RUN_SCREENSHOTS",
|
31
|
+
"active": true,
|
32
|
+
"timeLimitUsed": false,
|
33
|
+
"useLimit": 100,
|
34
|
+
"useLimited": true
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"name": "FREE_RUN",
|
38
|
+
"active": true,
|
39
|
+
"timeLimitUsed": false,
|
40
|
+
"useLimit": 115,
|
41
|
+
"useLimited": true
|
42
|
+
},
|
43
|
+
{
|
44
|
+
"name": "PRIORITY_GOLD",
|
45
|
+
"active": true,
|
46
|
+
"timeLimitUsed": true,
|
47
|
+
"useLimit": 0,
|
48
|
+
"useLimited": false
|
49
|
+
},
|
50
|
+
{
|
51
|
+
"name": "PAID_RUN",
|
52
|
+
"active": true,
|
53
|
+
"timeLimitUsed": true,
|
54
|
+
"useLimit": 0,
|
55
|
+
"useLimited": false
|
56
|
+
},
|
57
|
+
{
|
58
|
+
"name": "API_CLIENT",
|
59
|
+
"active": true,
|
60
|
+
"timeLimitUsed": true,
|
61
|
+
"useLimit": 0,
|
62
|
+
"useLimited": false
|
63
|
+
}
|
64
|
+
],
|
65
|
+
"lastLoginTime": 1380651077429,
|
66
|
+
"registrationIP": null,
|
67
|
+
"purchasedUserServices": [
|
68
|
+
|
69
|
+
],
|
70
|
+
"creditsLeft": "10000",
|
71
|
+
"Heard from": "Other",
|
72
|
+
"availableHearFromValues": [
|
73
|
+
"COWORKER",
|
74
|
+
"EMAIL_CAMPAIGN",
|
75
|
+
"EVENT",
|
76
|
+
"ONLINE_COMMUNITY",
|
77
|
+
"SOCIAL_NETWORK",
|
78
|
+
"WEB_SEARCH",
|
79
|
+
"OTHER"
|
80
|
+
],
|
81
|
+
"availableOrganizationRoleValues": [
|
82
|
+
"CEO_OR_OWNER",
|
83
|
+
"VP",
|
84
|
+
"MANAGER",
|
85
|
+
"INDIVIDUAL",
|
86
|
+
"QUALITY_ASSURANCE",
|
87
|
+
"DEVELOPER",
|
88
|
+
"MARKETING",
|
89
|
+
"CONSULTANT",
|
90
|
+
"OTHER"
|
91
|
+
],
|
92
|
+
"admin": false,
|
93
|
+
"accountNonExpired": true,
|
94
|
+
"accountNonLocked": true,
|
95
|
+
"credentialsNonExpired": true
|
96
|
+
}
|