UNPKG

13 kBJavaScriptView Raw
1/* global SockJS */
2/*
3 * Copyright 2012 Amadeus s.a.s.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17(function () {
18 var window = this;
19 var config = window.attesterConfig || {}; // a config object can be set by the PhantomJS script
20 window.attesterConfig = null;
21 var AttesterAPI = function (taskExecutionId) {
22 this.__taskExecutionId = taskExecutionId || -1;
23 this.currentTask = {};
24 };
25 var attesterPrototype = AttesterAPI.prototype = {};
26
27 var location = window.location;
28 var document = window.document;
29 var statusBar = document.getElementById('status_text');
30 var statusInfo = document.getElementById('status_info');
31 var pauseResume = document.getElementById('pause_resume');
32 var logs = document.getElementById('status_logs');
33 var paused = false;
34 var iframeParent = document.getElementById('content');
35 var iframe;
36 var baseUrl = location.protocol + "//" + location.host;
37 var socketStatus = "loading";
38 var testStatus = "waiting";
39 var testInfo = "loading";
40 var currentTask = null;
41 var pendingTestStarts = null;
42 var slaveId = (function () {
43 var match = /(?:\?|\&)id=([^&]+)/.exec(location.search);
44 if (match) {
45 return match[1];
46 }
47 return null;
48 })();
49 var flags = (function () {
50 var match = /(?:\?|\&)flags=([^&]+)/.exec(location.search);
51 if (match) {
52 return decodeURIComponent(match[1]);
53 }
54 return null;
55 })();
56 var beginning = new Date();
57
58 var log = window.location.search.indexOf("log=true") !== -1 ?
59 function (message) {
60 var time = (new Date() - beginning) + "ms";
61 // Log to the browser console
62 if (window.console && window.console.log) {
63 console.log(time, message);
64 }
65 // Log to the div console (useful for remote slaves or when console is missing)
66 logs.innerHTML = "<p><span class='timestamp'>" + time + "</span>" + message + "</p>" + logs.innerHTML;
67 logs.firstChild.scrollIntoView(false);
68 } : function () {};
69
70 var reportLogToServer = function (level, args, taskExecutionId) {
71 var time = new Date().getTime();
72 var msg = [];
73 for (var i = 0, l = args.length; i < l; i++) {
74 msg.push(String(args[i]));
75 }
76 socket.send(JSON.stringify({
77 type: 'log',
78 taskExecutionId: taskExecutionId,
79 event: {
80 time: time,
81 level: level,
82 message: msg.join(" ")
83 }
84 }));
85 };
86
87 var updateStatus = function () {
88 statusBar.innerHTML = socketStatus + " - " + testStatus;
89 pauseResume.innerHTML = paused ? "Resume" : "Pause";
90 statusInfo.innerHTML = "<span id='_info_" + testInfo + "'></span>";
91 };
92
93 var socketStatusUpdate = function (status, info) {
94 socketStatus = status;
95 testInfo = info || status;
96 updateStatus();
97 };
98
99 var removeIframe = function () {
100 if (iframe) {
101 window.attester = new AttesterAPI();
102 iframeParent.removeChild(iframe);
103 iframe = null;
104 }
105 };
106
107 var createIframe = function (src, taskExecutionId) {
108 removeIframe();
109 window.attester = new AttesterAPI(taskExecutionId);
110 iframe = document.createElement("iframe");
111 iframe.setAttribute("id", "iframe");
112 iframe.setAttribute("src", src);
113 // IE 7 needs frameBorder with B in upper case
114 iframe.setAttribute("frameBorder", "0");
115 iframeParent.appendChild(iframe);
116 };
117
118 var stop = function () {
119 currentTask = null;
120 pendingTestStarts = null;
121 removeIframe();
122 testStatus = paused ? "paused" : "waiting";
123 testInfo = "idle";
124 updateStatus();
125 };
126
127 log("creating a socket");
128
129 var socket;
130
131 var onSocketOpen = function () {
132 log("slave connected");
133 socketStatusUpdate('connected');
134 attesterPrototype.connected = true;
135 stop();
136 socket.send(JSON.stringify({
137 type: 'slave',
138 id: slaveId,
139 paused: paused,
140 userAgent: window.navigator.userAgent,
141 documentMode: document.documentMode,
142 flags: flags
143 }));
144 };
145
146 var onSocketClose = function () {
147 log("slave disconnected");
148 socketStatusUpdate('disconnected');
149 attesterPrototype.connected = false;
150 stop();
151 if (config.onDisconnect) {
152 config.onDisconnect();
153 }
154 setTimeout(createConnection, 1000);
155 };
156
157 var messages = {
158 slaveExecute: function (data) {
159 currentTask = data;
160 pendingTestStarts = {};
161 removeIframe();
162 testStatus = "executing " + data.name + " remaining " + data.stats.remainingTasks + " tasks in this campaign";
163 if (data.stats.browserRemainingTasks != data.stats.remainingTasks) {
164 testStatus += ", including " + data.stats.browserRemainingTasks + " tasks for this browser";
165 }
166 testInfo = "executing";
167 updateStatus();
168 log("<i>slave-execute</i> task <i>" + data.name + "</i>, " + data.stats.remainingTasks + " tasks left");
169 createIframe(baseUrl + data.url, data.taskExecutionId);
170 },
171 slaveStop: stop,
172 dispose: function () {
173 if (config.onDispose) {
174 config.onDispose();
175 }
176 }
177 };
178
179 var onSocketMessage = function (message) {
180 var data = JSON.parse(message.data);
181 var type = data.type;
182 if (messages.hasOwnProperty(type)) {
183 messages[type](data);
184 }
185 };
186
187 var createConnection = function () {
188 socketStatusUpdate('connecting');
189 socket = new SockJS(location.protocol + '//' + location.host + '/sockjs');
190 socket.onopen = onSocketOpen;
191 socket.onclose = onSocketClose;
192 socket.onmessage = onSocketMessage;
193 };
194
195 createConnection();
196
197 pauseResume.onclick = function () {
198 log("toggle pause status");
199 paused = !paused;
200 updateStatus();
201 if (attesterPrototype.connected) {
202 socket.send(JSON.stringify({
203 type: 'pauseChanged',
204 paused: paused
205 }));
206 }
207 return false;
208 };
209
210 var checkTaskExecutionId = function (scope, name) {
211 var res = currentTask && scope.__taskExecutionId === currentTask.taskExecutionId;
212 if (!res) {
213 var message = "ignoring call to attester." + name + " for a task that is not (or no longer) valid.";
214 reportLogToServer("warn", [message]);
215 log("<i>warning</i> " + message);
216 }
217 return res;
218 };
219
220 var sendTestUpdate = function (scope, name, info) {
221 if (!checkTaskExecutionId(scope, name)) {
222 return false;
223 }
224 if (!info) {
225 info = {};
226 }
227 if (!info.time) {
228 info.time = new Date().getTime();
229 }
230 info.event = name;
231 log("sending test update <i class='event'>" + name + "</i> for test <i>" + info.name + "</i>");
232 if (name === "testStarted") {
233 if (pendingTestStarts.hasOwnProperty(info.testId)) {
234 log("<i>warning</i> this <i>testStarted</i> is (wrongly) reusing a previous testId: <i>" + info.testId + "</i>");
235 }
236 pendingTestStarts[info.testId] = info;
237 } else if (name === "testFinished") {
238 var previousTestStart = pendingTestStarts[info.testId];
239 if (!previousTestStart) {
240 log("<i>warning</i> this <i>testFinished</i> is ignored as it has no previous <i>testStarted</i>");
241 return false;
242 }
243 pendingTestStarts[info.testId] = false;
244 info.duration = info.time - previousTestStart.time;
245 }
246 if (name === "error") {
247 log("<i class='error'>error</i> message: <i>" + info.error.message + "</i>");
248 }
249 socket.send(JSON.stringify({
250 type: 'testUpdate',
251 event: info,
252 taskExecutionId: currentTask.taskExecutionId
253 }));
254 return true;
255 };
256
257 attesterPrototype.testStart = function (info) {
258 sendTestUpdate(this, 'testStarted', info);
259 };
260 attesterPrototype.testEnd = function (info) {
261 sendTestUpdate(this, 'testFinished', info);
262 };
263 attesterPrototype.testError = function (info) {
264 sendTestUpdate(this, 'error', info);
265 };
266 attesterPrototype.taskFinished = function () {
267 var self = this;
268 if (!checkTaskExecutionId(this, "taskFinished")) {
269 return;
270 }
271 whenPendingXHRsFinished(function () {
272 if (!checkTaskExecutionId(self, "taskFinished (callback)")) {
273 return;
274 }
275 socket.send(JSON.stringify({
276 type: 'taskFinished',
277 taskExecutionId: currentTask.taskExecutionId
278 }));
279 });
280 };
281 attesterPrototype.stackTrace = function (exception) {
282 // this function is re-defined in stacktrace.js
283 return [];
284 };
285
286 var emptyFunction = function () {};
287 var replaceConsoleFunction = function (console, name, scope) {
288 var oldFunction = config.localConsole === false ? emptyFunction : console[name] || emptyFunction;
289 console[name] = function () {
290 var res;
291 try {
292 // IE < 9 compatible: http://stackoverflow.com/questions/5538972/console-log-apply-not-working-in-ie9#comment8444540_5539378
293 res = Function.prototype.apply.call(oldFunction, this, arguments);
294 } catch (e) {}
295 var taskExecutionId = scope.__taskExecutionId;
296 if (!currentTask || currentTask.taskExecutionId !== taskExecutionId) {
297 taskExecutionId = -1;
298 }
299 reportLogToServer(name, arguments, taskExecutionId);
300 return res;
301 };
302 };
303
304 attesterPrototype.installConsole = function (window) {
305 var console = window.console;
306 if (!console) {
307 console = window.console = {};
308 }
309 replaceConsoleFunction(console, "debug", this);
310 replaceConsoleFunction(console, "log", this);
311 replaceConsoleFunction(console, "info", this);
312 replaceConsoleFunction(console, "warn", this);
313 replaceConsoleFunction(console, "error", this);
314 };
315
316 var pendingXHRs = 0;
317 var pendingXHRsCallbacks = [];
318 var whenPendingXHRsFinished = function (callback) {
319 if (pendingXHRs > 0) {
320 pendingXHRsCallbacks.push(callback);
321 } else {
322 callback();
323 }
324 };
325 var callPendingXHRsCallbacks = function () {
326 while (pendingXHRsCallbacks.length > 0) {
327 var callback = pendingXHRsCallbacks.shift();
328 callback();
329 }
330 };
331
332 // To send coverage, using a POST request rather than using sockjs is better for performance reasons
333 var send = function (url, data) {
334 pendingXHRs++;
335 var xhr = (window.ActiveXObject) ? new window.ActiveXObject("Microsoft.XMLHTTP") : new window.XMLHttpRequest();
336 xhr.open('POST', url);
337 xhr.setRequestHeader('Content-Type', 'application/json');
338 xhr.onreadystatechange = function () {
339 if (xhr && xhr.readyState === 4) {
340 xhr = null;
341 pendingXHRs--;
342 if (pendingXHRs === 0) {
343 callPendingXHRsCallbacks();
344 }
345 }
346 };
347 xhr.send(data);
348 };
349
350 attesterPrototype.coverage = function (window) {
351 if (!checkTaskExecutionId(this, "coverage")) {
352 return false;
353 }
354 var $$_l = window.$$_l;
355 if ($$_l) {
356 send('/__attester__/coverage/data/' + currentTask.campaignId + '/' + currentTask.taskId, JSON.stringify({
357 name: "",
358 run: $$_l.run,
359 staticInfo: $$_l.staticInfo
360 }));
361 }
362 };
363
364 // Creating an empty iframe so that IE can replace its url without any harm
365 // (when refreshing the page, IE tries to restore the previous URL of the iframe)
366 createIframe(baseUrl + location.pathname.replace(/\/[^\/]+$/, "/empty.html"));
367})();