Chromium Code Reviews| 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 |