SauceLabs.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. var crypto = require('crypto');
  2. var https = require('https');
  3. var querystring = require('querystring');
  4. var utils = require('./utils');
  5. var HttpsProxyAgent = require('https-proxy-agent');
  6. var url = require('url');
  7. var extend = utils.extend;
  8. var replace = utils.replace;
  9. var DEFAULTS = {
  10. username: null,
  11. password: null,
  12. proxy: null,
  13. hostname: 'saucelabs.com',
  14. base: '/rest/v1/',
  15. port: '443'
  16. };
  17. function SauceLabs(options) {
  18. this.options = extend({}, DEFAULTS, options);
  19. this.options.auth = this.options.username + ':' + this.options.password;
  20. }
  21. module.exports = SauceLabs;
  22. // API
  23. SauceLabs.prototype.getAccountDetails = function (callback) {
  24. this.send({
  25. method: 'GET',
  26. path: 'users/:username'
  27. }, callback);
  28. };
  29. SauceLabs.prototype.getAccountLimits = function (callback) {
  30. this.send({
  31. method: 'GET',
  32. path: ':username/limits'
  33. }, callback);
  34. };
  35. SauceLabs.prototype.getUserActivity = function (start, end, callback) {
  36. if (typeof start === 'function') {
  37. callback = start;
  38. start = null;
  39. end = null;
  40. } else if (typeof end === 'function') {
  41. callback = end;
  42. end = null;
  43. }
  44. var dates = (start != null || end != null) ? {} : null;
  45. if (start != null) {
  46. dates.start = formatDate(start);
  47. }
  48. if (end != null) {
  49. dates.end = formatDate(end);
  50. }
  51. this.send({
  52. method: 'GET',
  53. path: ':username/activity',
  54. query: dates
  55. }, callback);
  56. };
  57. SauceLabs.prototype.getUserConcurrency = function (callback) {
  58. this.send({
  59. method: 'GET',
  60. path: 'users/:username/concurrency'
  61. }, callback);
  62. };
  63. SauceLabs.prototype.getAccountUsage = function (callback) {
  64. this.send({
  65. method: 'GET',
  66. path: 'users/:username/usage'
  67. }, callback);
  68. };
  69. SauceLabs.prototype.getJobs = function (callback) {
  70. this.send({
  71. method: 'GET',
  72. path: ':username/jobs',
  73. query: { full: true }
  74. }, callback);
  75. };
  76. SauceLabs.prototype.showJob = function (id, callback) {
  77. this.send({
  78. method: 'GET',
  79. path: ':username/jobs/:id',
  80. args: { id: id }
  81. }, callback);
  82. };
  83. SauceLabs.prototype.showJobAssets = function (id, callback) {
  84. this.send({
  85. method: 'GET',
  86. path: ':username/jobs/:id/assets',
  87. args: { id: id }
  88. }, callback);
  89. };
  90. SauceLabs.prototype.updateJob = function (id, data, callback) {
  91. this.send({
  92. method: 'PUT',
  93. path: ':username/jobs/:id',
  94. args: { id: id },
  95. data: data
  96. }, callback);
  97. };
  98. SauceLabs.prototype.stopJob = function (id, data, callback) {
  99. this.send({
  100. method: 'PUT',
  101. path: ':username/jobs/:id/stop',
  102. args: { id: id },
  103. data: data
  104. }, callback);
  105. };
  106. SauceLabs.prototype.deleteJob = function (id, callback) {
  107. this.send({
  108. method: 'DELETE',
  109. path: ':username/jobs/:id',
  110. args: { id: id }
  111. }, callback);
  112. };
  113. SauceLabs.prototype.getActiveTunnels = function (callback) {
  114. this.send({
  115. method: 'GET',
  116. path: ':username/tunnels'
  117. }, callback);
  118. };
  119. SauceLabs.prototype.getTunnel = function (id, callback){
  120. this.send({
  121. method: 'GET',
  122. path: ':username/tunnels/:id',
  123. args: { id: id }
  124. }, callback);
  125. };
  126. SauceLabs.prototype.deleteTunnel = function (id, callback){
  127. this.send({
  128. method: 'DELETE',
  129. path: ':username/tunnels/:id',
  130. args: { id: id }
  131. }, callback);
  132. };
  133. SauceLabs.prototype.getServiceStatus = function (callback) {
  134. this.send({
  135. method: 'GET',
  136. path: 'info/status'
  137. }, callback);
  138. };
  139. SauceLabs.prototype.getBrowsers = function (callback) {
  140. this.send({
  141. method: 'GET',
  142. path: 'info/browsers'
  143. }, callback);
  144. };
  145. SauceLabs.prototype.getAllBrowsers = function (callback) {
  146. this.send({
  147. method: 'GET',
  148. path: 'info/browsers/all'
  149. }, callback);
  150. };
  151. SauceLabs.prototype.getSeleniumBrowsers = function (callback) {
  152. this.send({
  153. method: 'GET',
  154. path: 'info/browsers/selenium-rc'
  155. }, callback);
  156. };
  157. SauceLabs.prototype.getWebDriverBrowsers = function (callback) {
  158. this.send({
  159. method: 'GET',
  160. path: 'info/browsers/webdriver'
  161. }, callback);
  162. };
  163. SauceLabs.prototype.getTestCounter = function (callback) {
  164. this.send({
  165. method: 'GET',
  166. path: 'info/counter'
  167. }, callback);
  168. };
  169. SauceLabs.prototype.updateSubAccount = function (data, callback) {
  170. this.send({
  171. method: 'POST',
  172. path: 'users/:username/subscription',
  173. data: data
  174. }, callback);
  175. };
  176. SauceLabs.prototype.deleteSubAccount = function (callback) {
  177. this.send({
  178. method: 'DELETE',
  179. path: 'users/:username/subscription'
  180. }, callback);
  181. };
  182. SauceLabs.prototype.createSubAccount = function (data, callback) {
  183. this.send({
  184. method: 'POST',
  185. path: 'users/:username',
  186. data: data
  187. }, callback);
  188. };
  189. SauceLabs.prototype.createPublicLink = function (id, date, useHour, callback) {
  190. if (typeof date === 'function') {
  191. callback = date;
  192. date = null;
  193. useHour = false;
  194. } else if (typeof useHour === 'function') {
  195. callback = useHour;
  196. useHour = false;
  197. }
  198. if (date != null) {
  199. date = formatDate(date, useHour);
  200. }
  201. var link = generateLink(this.options.hostname, this.options.auth, date, id);
  202. callback(null, link);
  203. };
  204. SauceLabs.prototype.send = function (message, callback) {
  205. var method = message.method,
  206. path = message.path,
  207. args = message.args,
  208. query = message.query,
  209. data = message.data,
  210. body = JSON.stringify(data);
  211. // Build path with base, placeholders, and query.
  212. path = this.options.base + replace(path, extend({}, this.options, args));
  213. if (query != null) {
  214. path += '?' + querystring.stringify(query);
  215. }
  216. // Make the request.
  217. var options = extend({}, this.options, {
  218. method: method,
  219. path: path,
  220. headers: {
  221. 'Accept': 'application/json',
  222. 'Content-Type': 'application/json',
  223. 'Content-Length': body != null ? Buffer.byteLength(body) : 0
  224. }
  225. });
  226. makeRequest(options, body, callback);
  227. };
  228. SauceLabs.prototype.getSubAccountList = function (callback) {
  229. this.send({
  230. method: 'GET',
  231. path: 'users/:username/list-subaccounts'
  232. }, callback);
  233. };
  234. SauceLabs.prototype.getSubAccounts = function (callback) {
  235. this.send({
  236. method: 'GET',
  237. path: 'users/:username/subaccounts'
  238. }, callback);
  239. };
  240. // Helpers
  241. function formatDate(date, useHour) {
  242. return date.toISOString().replace(/T(\d+).*/, (useHour ? '-$1' : ''));
  243. }
  244. function generateToken(auth, date, job) {
  245. var key = auth + (date ? ':' + date : '');
  246. return crypto
  247. .createHmac('md5', key)
  248. .update(job)
  249. .digest('hex');
  250. }
  251. function generateLink(hostname, auth, date, job) {
  252. return replace('https://:hostname/jobs/:id?auth=:token', {
  253. hostname: hostname,
  254. id: job,
  255. token: generateToken(auth, date, job)
  256. });
  257. }
  258. function makeRequest(options, body, callback) {
  259. if(options.proxy){
  260. options.agent = new HttpsProxyAgent(url.parse(options.proxy));
  261. }
  262. var request = https.request(options, function (response) {
  263. var result = '';
  264. if (callback) {
  265. response
  266. .on('data', function (chunk) {
  267. result += chunk;
  268. })
  269. .on('end', function () {
  270. var res;
  271. try {
  272. res = JSON.parse(result);
  273. } catch (e) {
  274. callback('Could not parse response: ' + result);
  275. return;
  276. }
  277. if (response.statusCode === 200) {
  278. callback(null, res);
  279. } else {
  280. callback(res);
  281. }
  282. });
  283. }
  284. });
  285. request
  286. .on('error', function (err) {
  287. callback('Could not send request: ' + err.message);
  288. });
  289. if (body != null) {
  290. request.write(body);
  291. }
  292. request.end();
  293. }