blob: 5b325bd8393d59ac68098ffcf59d4ae4d6293ebe [file] [log] [blame]
andresp@webrtc.org468516c2014-09-02 10:52:54 +00001// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
2//
3// Use of this source code is governed by a BSD-style license
4// that can be found in the LICENSE file in the root of the source
5// tree. An additional intellectual property rights grant can be found
6// in the file PATENTS. All contributing project authors may
7// be found in the AUTHORS file in the root of the source tree.
8//
9// botmanager.js module allows a test to spawn bots that expose an RPC API
10// to be controlled by tests.
houssainy@google.come9b7d032014-10-22 16:34:25 +000011var https = require('https');
12var fs = require('fs');
andresp@webrtc.org468516c2014-09-02 10:52:54 +000013var child = require('child_process');
14var Browserify = require('browserify');
15var Dnode = require('dnode');
16var Express = require('express');
17var WebSocketServer = require('ws').Server;
18var WebSocketStream = require('websocket-stream');
19
houssainy@google.come9b7d032014-10-22 16:34:25 +000020// BotManager runs a HttpsServer that serves bots assets and and WebSocketServer
andresp@webrtc.org468516c2014-09-02 10:52:54 +000021// that listens to incoming connections. Once a connection is available it
22// connects it to bots pending endpoints.
23//
24// TODO(andresp): There should be a way to control which bot was spawned
25// and what bot instance it gets connected to.
26BotManager = function () {
27 this.webSocketServer_ = null;
28 this.bots_ = [];
29 this.pendingConnections_ = [];
houssainy@google.com96005192014-09-08 13:01:40 +000030 this.androidDeviceManager_ = new AndroidDeviceManager();
andresp@webrtc.org468516c2014-09-02 10:52:54 +000031}
32
houssainy@google.comc77e4d62014-09-08 10:36:11 +000033BotManager.BotTypes = {
34 CHROME : 'chrome',
houssainy@google.com96005192014-09-08 13:01:40 +000035 ANDROID_CHROME : 'android-chrome',
houssainy@google.comc77e4d62014-09-08 10:36:11 +000036};
37
andresp@webrtc.org468516c2014-09-02 10:52:54 +000038BotManager.prototype = {
houssainy@google.comc77e4d62014-09-08 10:36:11 +000039 createBot_: function (name, botType, callback) {
40 switch(botType) {
41 case BotManager.BotTypes.CHROME:
42 return new BrowserBot(name, callback);
houssainy@google.com96005192014-09-08 13:01:40 +000043 case BotManager.BotTypes.ANDROID_CHROME:
44 return new AndroidChromeBot(name, this.androidDeviceManager_,
45 callback);
houssainy@google.comc77e4d62014-09-08 10:36:11 +000046 default:
47 console.log('Error: Type ' + botType + ' not supported by rtc-Bot!');
48 process.exit(1);
49 }
50 },
51
52 spawnNewBot: function (name, botType, callback) {
andresp@webrtc.org468516c2014-09-02 10:52:54 +000053 this.startWebSocketServer_();
houssainy@google.comc77e4d62014-09-08 10:36:11 +000054 var bot = this.createBot_(name, botType, callback);
andresp@webrtc.org468516c2014-09-02 10:52:54 +000055 this.bots_.push(bot);
56 this.pendingConnections_.push(bot.onBotConnected.bind(bot));
57 },
58
59 startWebSocketServer_: function () {
60 if (this.webSocketServer_) return;
61
62 this.app_ = new Express();
63
houssainy@google.com38ef6642014-09-04 13:44:47 +000064 this.app_.use('/bot/api.js',
andresp@webrtc.org468516c2014-09-02 10:52:54 +000065 this.serveBrowserifyFile_.bind(this,
houssainy@google.com38ef6642014-09-04 13:44:47 +000066 __dirname + '/bot/api.js'));
andresp@webrtc.org468516c2014-09-02 10:52:54 +000067
houssainy@google.com38ef6642014-09-04 13:44:47 +000068 this.app_.use('/bot/', Express.static(__dirname + '/bot'));
andresp@webrtc.org468516c2014-09-02 10:52:54 +000069
houssainy@google.come9b7d032014-10-22 16:34:25 +000070 var options = options = {
71 key: fs.readFileSync('configurations/priv.pem', 'utf8'),
72 cert: fs.readFileSync('configurations/cert.crt', 'utf8')
73 };
74 this.server_ = https.createServer(options, this.app_);
andresp@webrtc.org468516c2014-09-02 10:52:54 +000075
76 this.webSocketServer_ = new WebSocketServer({ server: this.server_ });
77 this.webSocketServer_.on('connection', this.onConnection_.bind(this));
78
79 this.server_.listen(8080);
80 },
81
82 onConnection_: function (ws) {
83 var callback = this.pendingConnections_.shift();
84 callback(new WebSocketStream(ws));
85 },
86
87 serveBrowserifyFile_: function (file, request, result) {
88 // TODO(andresp): Cache browserify result for future serves.
89 var browserify = new Browserify();
90 browserify.add(file);
91 browserify.bundle().pipe(result);
92 }
93}
94
95// A basic bot waits for onBotConnected to be called with a stream to the actual
96// endpoint with the bot. Once that stream is available it establishes a dnode
97// connection and calls the callback with the other endpoint interface so the
98// test can interact with it.
99Bot = function (name, callback) {
100 this.name_ = name;
101 this.onbotready_ = callback;
102}
103
104Bot.prototype = {
105 log: function (msg) {
106 console.log("bot:" + this.name_ + " > " + msg);
107 },
108
109 name: function () { return this.name_; },
110
111 onBotConnected: function (stream) {
112 this.log('Connected');
113 this.stream_ = stream;
114 this.dnode_ = new Dnode();
115 this.dnode_.on('remote', this.onRemoteFromDnode_.bind(this));
116 this.dnode_.pipe(this.stream_).pipe(this.dnode_);
117 },
118
119 onRemoteFromDnode_: function (remote) {
120 this.onbotready_(remote);
121 }
122}
123
houssainy@google.come9b7d032014-10-22 16:34:25 +0000124// BrowserBot spawns a process to open "https://localhost:8080/bot/browser".
andresp@webrtc.org468516c2014-09-02 10:52:54 +0000125//
126// That page once loaded, connects to the websocket server run by BotManager
127// and exposes the bot api.
128BrowserBot = function (name, callback) {
129 Bot.call(this, name, callback);
130 this.spawnBotProcess_();
131}
132
133BrowserBot.prototype = {
134 spawnBotProcess_: function () {
135 this.log('Spawning browser');
houssainy@google.come9b7d032014-10-22 16:34:25 +0000136 child.exec('google-chrome "https://localhost:8080/bot/browser/"');
andresp@webrtc.org468516c2014-09-02 10:52:54 +0000137 },
138
139 __proto__: Bot.prototype
140}
141
houssainy@google.com96005192014-09-08 13:01:40 +0000142// AndroidChromeBot spawns a process to open
houssainy@google.come9b7d032014-10-22 16:34:25 +0000143// "https://localhost:8080/bot/browser/" on chrome for Android.
houssainy@google.com96005192014-09-08 13:01:40 +0000144AndroidChromeBot = function (name, androidDeviceManager, callback) {
145 Bot.call(this, name, callback);
146 androidDeviceManager.getNewDevice(function (serialNumber) {
147 this.serialNumber_ = serialNumber;
148 this.spawnBotProcess_();
149 }.bind(this));
150}
151
152AndroidChromeBot.prototype = {
153 spawnBotProcess_: function () {
154 this.log('Spawning Android device with serial ' + this.serialNumber_);
155 var runChrome = 'adb -s ' + this.serialNumber_ + ' shell am start ' +
156 '-n com.android.chrome/com.google.android.apps.chrome.Main ' +
houssainy@google.come9b7d032014-10-22 16:34:25 +0000157 '-d https://localhost:8080/bot/browser/';
houssainy@google.com96005192014-09-08 13:01:40 +0000158 child.exec(runChrome, function (error, stdout, stderr) {
159 if (error) {
160 this.log(error);
161 process.exit(1);
162 }
163 this.log('Opening Chrome for Android...');
164 this.log(stdout);
165 }.bind(this));
166 },
167
168 __proto__: Bot.prototype
169}
170
houssainy@google.comc77e4d62014-09-08 10:36:11 +0000171AndroidDeviceManager = function () {
172 this.connectedDevices_ = [];
173}
174
175AndroidDeviceManager.prototype = {
176 getNewDevice: function (callback) {
177 this.listDevices_(function (devices) {
178 for (var i = 0; i < devices.length; i++) {
179 if (!this.connectedDevices_[devices[i]]) {
180 this.connectedDevices_[devices[i]] = devices[i];
181 callback(this.connectedDevices_[devices[i]]);
182 return;
183 }
184 }
185 if (devices.length == 0) {
186 console.log('Error: No connected devices!');
187 } else {
188 console.log('Error: There is no enough connected devices.');
189 }
190 process.exit(1);
191 }.bind(this));
192 },
193
194 listDevices_: function (callback) {
195 child.exec('adb devices' , function (error, stdout, stderr) {
196 var devices = [];
197 if (error || stderr) {
houssainy@google.com96005192014-09-08 13:01:40 +0000198 console.log(error || stderr);
houssainy@google.comc77e4d62014-09-08 10:36:11 +0000199 }
200 if (stdout) {
201 // The first line is "List of devices attached"
202 // and the following lines:
203 // <serial number> <device/emulator>
houssainy@google.com96005192014-09-08 13:01:40 +0000204 var tempList = stdout.split("\n").slice(1);
houssainy@google.comc77e4d62014-09-08 10:36:11 +0000205 for (var i = 0; i < tempList.length; i++) {
206 if (tempList[i] == "") {
207 continue;
208 }
209 devices.push(tempList[i].split("\t")[0]);
210 }
211 }
212 callback(devices);
213 });
214 },
215}
andresp@webrtc.org468516c2014-09-02 10:52:54 +0000216module.exports = BotManager;