OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 var InspectorTest = {}; | |
chenwilliam
2017/06/19 19:09:20
Just a thought for f/u work, what do you think abo
dgozman
2017/06/19 21:49:22
Yeah, I actually want to turn this into TestRunner
| |
6 InspectorTest._outputElement = null; | |
7 InspectorTest._dumpInspectorProtocolMessages = false; | |
8 InspectorTest._sessions = new Map(); | |
9 | |
10 InspectorTest.startDumpingProtocolMessages = function() { | |
11 InspectorTest._dumpInspectorProtocolMessages = true; | |
12 }; | |
13 | |
14 InspectorTest.completeTest = function() { | |
15 testRunner.notifyDone(); | |
16 }; | |
17 | |
18 InspectorTest.log = function(text) { | |
19 if (!InspectorTest._outputElement) { | |
20 var intermediate = document.createElement('div'); | |
21 document.body.appendChild(intermediate); | |
22 var intermediate2 = document.createElement('div'); | |
23 intermediate.appendChild(intermediate2); | |
24 InspectorTest._outputElement = document.createElement('div'); | |
25 InspectorTest._outputElement.className = 'output'; | |
26 InspectorTest._outputElement.id = 'output'; | |
27 InspectorTest._outputElement.style.whiteSpace = 'pre'; | |
28 intermediate2.appendChild(InspectorTest._outputElement); | |
29 } | |
30 InspectorTest._outputElement.appendChild(document.createTextNode(text)); | |
31 InspectorTest._outputElement.appendChild(document.createElement('br')); | |
32 }; | |
33 | |
34 InspectorTest.url = function(relative) { | |
35 return InspectorTest._baseURL + relative; | |
36 }; | |
37 | |
38 InspectorTest.logMessage = function(originalMessage) { | |
39 var message = JSON.parse(JSON.stringify(originalMessage)); | |
40 if (message.id) | |
41 message.id = "<messageId>"; | |
42 const nonStableFields = new Set(['nodeId', 'objectId', 'scriptId', 'timestamp' ]); | |
43 var objects = [message]; | |
44 while (objects.length) { | |
45 var object = objects.shift(); | |
46 for (var key in object) { | |
47 if (nonStableFields.has(key)) | |
48 object[key] = `<${key}>`; | |
49 else if (typeof object[key] === "string" && object[key].match(/\d+:\d+:\d+ :debug/)) | |
50 object[key] = object[key].replace(/\d+/, '<scriptId>'); | |
51 else if (typeof object[key] === "object") | |
52 objects.push(object[key]); | |
53 } | |
54 } | |
55 InspectorTest.logObject(message); | |
56 return originalMessage; | |
57 }; | |
58 | |
59 InspectorTest.logObject = function(object, title) { | |
60 var lines = []; | |
61 | |
62 function dumpValue(value, prefix, prefixWithName) { | |
63 if (typeof value === "object" && value !== null) { | |
64 if (value instanceof Array) | |
65 dumpItems(value, prefix, prefixWithName); | |
66 else | |
67 dumpProperties(value, prefix, prefixWithName); | |
68 } else { | |
69 lines.push(prefixWithName + String(value).replace(/\n/g, " ")); | |
70 } | |
71 } | |
72 | |
73 function dumpProperties(object, prefix, firstLinePrefix) { | |
74 prefix = prefix || ""; | |
75 firstLinePrefix = firstLinePrefix || prefix; | |
76 lines.push(firstLinePrefix + "{"); | |
77 | |
78 var propertyNames = Object.keys(object); | |
79 propertyNames.sort(); | |
80 for (var i = 0; i < propertyNames.length; ++i) { | |
81 var name = propertyNames[i]; | |
82 if (!object.hasOwnProperty(name)) | |
83 continue; | |
84 var prefixWithName = " " + prefix + name + " : "; | |
85 dumpValue(object[name], " " + prefix, prefixWithName); | |
86 } | |
87 lines.push(prefix + "}"); | |
88 } | |
89 | |
90 function dumpItems(object, prefix, firstLinePrefix) { | |
91 prefix = prefix || ""; | |
92 firstLinePrefix = firstLinePrefix || prefix; | |
93 lines.push(firstLinePrefix + "["); | |
94 for (var i = 0; i < object.length; ++i) | |
95 dumpValue(object[i], " " + prefix, " " + prefix + "[" + i + "] : "); | |
96 lines.push(prefix + "]"); | |
97 } | |
98 | |
99 dumpValue(object, "", title || ""); | |
100 InspectorTest.log(lines.join("\n")); | |
101 }; | |
102 | |
103 InspectorTest._checkExpectation = function(fail, name, messageObject) { | |
104 if (fail === !!messageObject.error) { | |
105 InspectorTest.log("PASS: " + name); | |
106 return true; | |
107 } | |
108 | |
109 InspectorTest.log("FAIL: " + name + ": " + JSON.stringify(messageObject)); | |
110 InspectorTest.completeTest(); | |
111 return false; | |
112 } | |
113 InspectorTest.expectedSuccess = InspectorTest._checkExpectation.bind(null, false ); | |
114 InspectorTest.expectedError = InspectorTest._checkExpectation.bind(null, true); | |
115 | |
116 InspectorTest.die = function(message, error) { | |
117 InspectorTest.log(`${message}: ${error}\n${error.stack}`); | |
118 InspectorTest.completeTest(); | |
119 throw new Error(); | |
120 }; | |
121 | |
122 InspectorTest.createPage = async function() { | |
123 var targetId = (await DevToolsAPI._sendCommandOrDie('Target.createTarget', {ur l: 'about:blank'})).targetId; | |
124 await DevToolsAPI._sendCommandOrDie('Target.activateTarget', {targetId}); | |
125 var page = new InspectorTest.Page(targetId); | |
126 var dummyURL = window.location.href; | |
127 dummyURL = dummyURL.substring(0, dummyURL.indexOf('inspector-protocol-test.htm l')) + 'inspector-protocol-page.html'; | |
128 await page._navigate(dummyURL); | |
129 return page; | |
130 }; | |
131 | |
132 InspectorTest.Page = class { | |
133 constructor(targetId) { | |
134 this._targetId = targetId; | |
135 } | |
136 | |
137 async createSession() { | |
138 await DevToolsAPI._sendCommandOrDie('Target.attachToTarget', {targetId: this ._targetId}); | |
139 var session = new InspectorTest.Session(this); | |
140 InspectorTest._sessions.set(this._targetId, session); | |
141 return session; | |
142 } | |
143 | |
144 navigate(url) { | |
145 return this._navigate(InspectorTest.url(url)); | |
146 } | |
147 | |
148 async _navigate(url) { | |
149 if (InspectorTest._sessions.get(this._targetId)) | |
150 InspectorTest.die(`Cannot navigate to ${url} with active session`, new Err or()); | |
151 | |
152 var session = await this.createSession(); | |
153 session.Protocol.Page.enable(); | |
154 session.Protocol.Page.navigate({url: url}); | |
155 | |
156 var callback; | |
157 var promise = new Promise(f => callback = f); | |
158 session.Protocol.Page.onFrameNavigated(message => { | |
159 if (!message.params.frame.parentId) | |
160 callback(); | |
161 }); | |
162 await promise; | |
163 | |
164 await session.disconnect(); | |
165 } | |
166 | |
167 async loadHTML(html) { | |
168 if (InspectorTest._sessions.get(this._targetId)) | |
169 InspectorTest.die('Cannot loadHTML with active session', new Error()); | |
170 | |
171 html = html.replace(/'/g, "\\'").replace(/\n/g, "\\n"); | |
172 var session = await this.createSession(); | |
173 await session.Protocol.Runtime.evaluate({expression: `document.body.innerHTM L='${html}'`}); | |
174 await session.disconnect(); | |
175 } | |
176 }; | |
177 | |
178 InspectorTest.Session = class { | |
179 constructor(page) { | |
180 this._page = page; | |
181 this._requestId = 0; | |
182 this._dispatchTable = new Map(); | |
183 this._eventHandlers = new Map(); | |
184 this.Protocol = this._setupProtocol(); | |
185 } | |
186 | |
187 async disconnect() { | |
188 await DevToolsAPI._sendCommandOrDie('Target.detachFromTarget', {targetId: th is._page._targetId}); | |
189 InspectorTest._sessions.delete(this._page._targetId); | |
190 } | |
191 | |
192 sendRawCommand(requestId, message) { | |
193 DevToolsAPI._sendCommandOrDie('Target.sendMessageToTarget', {targetId: this. _page._targetId, message: message}); | |
194 return new Promise(f => this._dispatchTable.set(requestId, f)); | |
195 } | |
196 | |
197 sendCommand(method, params) { | |
198 var requestId = ++this._requestId; | |
199 var messageObject = {'id': requestId, 'method': method, 'params': params}; | |
200 if (InspectorTest._dumpInspectorProtocolMessages) | |
201 InspectorTest.log(`frontend => backend: ${JSON.stringify(messageObject)}`) ; | |
202 return this.sendRawCommand(requestId, JSON.stringify(messageObject)); | |
203 } | |
204 | |
205 async evaluate(code) { | |
206 var response = await this.Protocol.Runtime.evaluate({expression: code, retur nByValue: true}); | |
207 if (response.error) { | |
208 InspectorTest.log(`Error while evaluating '${code}': ${response.error}`); | |
209 InspectorTest.completeTest(); | |
210 } else { | |
211 return response.result.result.value; | |
212 } | |
213 } | |
214 | |
215 async evaluateAsync(code) { | |
216 var response = await this.Protocol.Runtime.evaluate({expression: code, retur nByValue: true, awaitPromise: true}); | |
217 if (response.error) { | |
218 InspectorTest.log(`Error while evaluating async '${code}': ${response.erro r}`); | |
219 InspectorTest.completeTest(); | |
220 } else { | |
221 return response.result.result.value; | |
222 } | |
223 } | |
224 | |
225 _dispatchMessage(message) { | |
226 if (InspectorTest._dumpInspectorProtocolMessages) | |
227 InspectorTest.log(`backend => frontend: ${JSON.stringify(message)}`); | |
228 if (typeof message.id === 'number') { | |
229 var handler = this._dispatchTable.get(message.id); | |
230 if (handler) { | |
231 this._dispatchTable.delete(message.id); | |
232 handler(message); | |
233 } | |
234 } else { | |
235 var eventName = message.method; | |
236 for (var handler of (this._eventHandlers.get(eventName) || [])) | |
237 handler(message); | |
238 } | |
239 } | |
240 | |
241 _setupProtocol() { | |
242 return new Proxy({}, { get: (target, agentName, receiver) => new Proxy({}, { | |
243 get: (target, methodName, receiver) => { | |
244 const eventPattern = /^(on(ce)?|off)([A-Z][A-Za-z0-9]+)/; | |
245 var match = eventPattern.exec(methodName); | |
246 if (!match) | |
247 return args => this.sendCommand(`${agentName}.${methodName}`, args || {}); | |
248 var eventName = match[3]; | |
249 eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1); | |
250 if (match[1] === 'once') | |
251 return () => this._waitForEvent(`${agentName}.${eventName}`); | |
252 if (match[1] === 'off') | |
253 return listener => this._removeEventHandler(`${agentName}.${eventName} `, listener); | |
254 return listener => this._addEventHandler(`${agentName}.${eventName}`, li stener); | |
255 } | |
256 })}); | |
257 } | |
258 | |
259 _addEventHandler(eventName, handler) { | |
260 var handlers = this._eventHandlers.get(eventName) || []; | |
261 handlers.push(handler); | |
262 this._eventHandlers.set(eventName, handlers); | |
263 } | |
264 | |
265 _removeEventHandler(eventName, handler) { | |
266 var handlers = this._eventHandlers.get(eventName) || []; | |
267 var index = handlers.indexOf(handler); | |
268 if (index === -1) | |
269 return; | |
270 handlers.splice(index, 1); | |
271 this._eventHandlers.set(eventName, handlers); | |
272 } | |
273 | |
274 _waitForEvent(eventName) { | |
275 return new Promise(callback => { | |
276 var handler = result => { | |
277 this._removeEventHandler(eventName, handler); | |
278 callback(result); | |
279 }; | |
280 this._addEventHandler(eventName, handler); | |
281 }); | |
282 } | |
283 }; | |
284 | |
285 var DevToolsAPI = {}; | |
286 DevToolsAPI._requestId = 0; | |
287 DevToolsAPI._embedderMessageId = 0; | |
288 DevToolsAPI._dispatchTable = new Map(); | |
289 | |
290 DevToolsAPI.dispatchMessage = function(messageOrObject) { | |
291 var messageObject = (typeof messageOrObject === 'string' ? JSON.parse(messageO rObject) : messageOrObject); | |
292 var messageId = messageObject.id; | |
293 try { | |
294 if (typeof messageId === 'number') { | |
295 var handler = DevToolsAPI._dispatchTable.get(messageId); | |
296 if (handler) { | |
297 DevToolsAPI._dispatchTable.delete(messageId); | |
298 handler(messageObject); | |
299 } | |
300 } else { | |
301 var eventName = messageObject.method; | |
302 if (eventName === 'Target.receivedMessageFromTarget') { | |
303 var targetId = messageObject.params.targetId; | |
304 var message = messageObject.params.message; | |
305 var session = InspectorTest._sessions.get(targetId); | |
306 if (session) | |
307 session._dispatchMessage(JSON.parse(message)); | |
308 } | |
309 } | |
310 } catch(e) { | |
311 InspectorTest.die(`Exception when dispatching message\n${JSON.stringify(mess ageObject)}`, e); | |
312 } | |
313 }; | |
314 | |
315 DevToolsAPI._sendCommand = function(method, params) { | |
316 var requestId = ++DevToolsAPI._requestId; | |
317 var messageObject = {'id': requestId, 'method': method, 'params': params}; | |
318 var embedderMessage = {'id': ++DevToolsAPI._embedderMessageId, 'method': 'disp atchProtocolMessage', 'params': [JSON.stringify(messageObject)]}; | |
319 DevToolsHost.sendMessageToEmbedder(JSON.stringify(embedderMessage)); | |
320 return new Promise(f => DevToolsAPI._dispatchTable.set(requestId, f)); | |
321 }; | |
322 | |
323 DevToolsAPI._sendCommandOrDie = function(method, params, errorMessage) { | |
324 errorMessage = errorMessage || 'Unexpected error'; | |
325 return DevToolsAPI._sendCommand(method, params).then(message => { | |
326 if (message.error) | |
327 InspectorTest.die('Error starting the test', new Error(message.error)); | |
328 return message.result; | |
329 }); | |
330 }; | |
331 | |
332 InspectorTest._start = async function(description, html, url) { | |
333 try { | |
334 InspectorTest.log(description); | |
335 var page = await InspectorTest.createPage(); | |
336 if (url) | |
337 await page.navigate(url); | |
338 if (html) | |
339 await page.loadHTML(html); | |
340 var session = await page.createSession(); | |
341 return { page: page, session: session, Protocol: session.Protocol }; | |
342 } catch (e) { | |
343 InspectorTest.die('Error starting the test', e); | |
344 } | |
345 }; | |
346 InspectorTest.startBlank = description => InspectorTest._start(description, null , null); | |
chenwilliam
2017/06/19 19:09:20
It seems like the description parameter is never a
dgozman
2017/06/19 21:49:22
It is in some tests. This is actually to force new
| |
347 InspectorTest.startHTML = (html, description) => InspectorTest._start(descriptio n, html, null); | |
348 InspectorTest.startURL = (url, description) => InspectorTest._start(description, null, url); | |
349 | |
350 InspectorTest.debugTest = function(testFunction) { | |
chenwilliam
2017/06/19 19:09:20
after this lands, let's include a short descriptio
dgozman
2017/06/19 21:49:22
Sure, thanks!
| |
351 window.test = testFunction; | |
352 InspectorTest.log = console.log; | |
353 InspectorTest.completeTest = () => console.log('Test completed'); | |
354 var dispatch = DevToolsAPI.dispatchMessage; | |
355 var messages = []; | |
356 DevToolsAPI.dispatchMessage = message => { | |
357 if (!messages.length) { | |
358 setTimeout(() => { | |
359 for (var message of messages.splice(0)) | |
360 dispatch(message); | |
361 }, 0); | |
362 } | |
363 messages.push(message); | |
364 }; | |
365 return () => {}; | |
366 }; | |
367 | |
368 InspectorTest._fetch = function(url) { | |
369 return new Promise(fulfill => { | |
370 var xhr = new XMLHttpRequest(); | |
371 xhr.open('GET', url, true); | |
372 xhr.onreadystatechange = e => { | |
373 if (xhr.readyState !== XMLHttpRequest.DONE) | |
374 return; | |
375 if ([0, 200, 304].indexOf(xhr.status) === -1) // Testing harness file:/// results in 0. | |
376 InspectorTest.die(`${xhr.status} while fetching ${url}`, new Error()); | |
377 else | |
378 fulfill(e.target.response); | |
379 }; | |
380 xhr.send(null); | |
381 }); | |
382 }; | |
383 | |
384 InspectorTest.loadScript = async function(url) { | |
385 var source = await InspectorTest._fetch(InspectorTest.url(url)); | |
386 return eval(`${source}\n//# sourceURL=${url}`); | |
387 }; | |
388 | |
389 window.addEventListener('load', async () => { | |
390 testRunner.dumpAsText(); | |
391 testRunner.waitUntilDone(); | |
392 testRunner.setCanOpenWindows(true); | |
393 try { | |
394 var testScriptURL = window.location.search.substring(1); | |
395 InspectorTest._baseURL = testScriptURL.substring(0, testScriptURL.lastIndexO f('/') + 1); | |
396 var testScript = await InspectorTest._fetch(testScriptURL); | |
397 eval(`${testScript}\n//# sourceURL=${testScriptURL}`); | |
398 } catch(e) { | |
399 InspectorTest.die('Unable to execute test script', e); | |
400 } | |
401 }, false); | |
402 | |
403 window['onerror'] = (message, source, lineno, colno, error) => { | |
404 InspectorTest.die('General error', error); | |
405 }; | |
406 | |
407 window.addEventListener('unhandledrejection', e => { | |
408 InspectorTest.die('General error', new Error(e.reason)); | |
409 }, false); | |
OLD | NEW |