Chromium Code Reviews| Index: third_party/WebKit/LayoutTests/inspector-protocol/resources/inspector-protocol-test.js |
| diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/resources/inspector-protocol-test.js b/third_party/WebKit/LayoutTests/inspector-protocol/resources/inspector-protocol-test.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..839a2a824f8aa1e9ccf7c33dfb58ac3196ebdae4 |
| --- /dev/null |
| +++ b/third_party/WebKit/LayoutTests/inspector-protocol/resources/inspector-protocol-test.js |
| @@ -0,0 +1,409 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +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
|
| +InspectorTest._outputElement = null; |
| +InspectorTest._dumpInspectorProtocolMessages = false; |
| +InspectorTest._sessions = new Map(); |
| + |
| +InspectorTest.startDumpingProtocolMessages = function() { |
| + InspectorTest._dumpInspectorProtocolMessages = true; |
| +}; |
| + |
| +InspectorTest.completeTest = function() { |
| + testRunner.notifyDone(); |
| +}; |
| + |
| +InspectorTest.log = function(text) { |
| + if (!InspectorTest._outputElement) { |
| + var intermediate = document.createElement('div'); |
| + document.body.appendChild(intermediate); |
| + var intermediate2 = document.createElement('div'); |
| + intermediate.appendChild(intermediate2); |
| + InspectorTest._outputElement = document.createElement('div'); |
| + InspectorTest._outputElement.className = 'output'; |
| + InspectorTest._outputElement.id = 'output'; |
| + InspectorTest._outputElement.style.whiteSpace = 'pre'; |
| + intermediate2.appendChild(InspectorTest._outputElement); |
| + } |
| + InspectorTest._outputElement.appendChild(document.createTextNode(text)); |
| + InspectorTest._outputElement.appendChild(document.createElement('br')); |
| +}; |
| + |
| +InspectorTest.url = function(relative) { |
| + return InspectorTest._baseURL + relative; |
| +}; |
| + |
| +InspectorTest.logMessage = function(originalMessage) { |
| + var message = JSON.parse(JSON.stringify(originalMessage)); |
| + if (message.id) |
| + message.id = "<messageId>"; |
| + const nonStableFields = new Set(['nodeId', 'objectId', 'scriptId', 'timestamp']); |
| + var objects = [message]; |
| + while (objects.length) { |
| + var object = objects.shift(); |
| + for (var key in object) { |
| + if (nonStableFields.has(key)) |
| + object[key] = `<${key}>`; |
| + else if (typeof object[key] === "string" && object[key].match(/\d+:\d+:\d+:debug/)) |
| + object[key] = object[key].replace(/\d+/, '<scriptId>'); |
| + else if (typeof object[key] === "object") |
| + objects.push(object[key]); |
| + } |
| + } |
| + InspectorTest.logObject(message); |
| + return originalMessage; |
| +}; |
| + |
| +InspectorTest.logObject = function(object, title) { |
| + var lines = []; |
| + |
| + function dumpValue(value, prefix, prefixWithName) { |
| + if (typeof value === "object" && value !== null) { |
| + if (value instanceof Array) |
| + dumpItems(value, prefix, prefixWithName); |
| + else |
| + dumpProperties(value, prefix, prefixWithName); |
| + } else { |
| + lines.push(prefixWithName + String(value).replace(/\n/g, " ")); |
| + } |
| + } |
| + |
| + function dumpProperties(object, prefix, firstLinePrefix) { |
| + prefix = prefix || ""; |
| + firstLinePrefix = firstLinePrefix || prefix; |
| + lines.push(firstLinePrefix + "{"); |
| + |
| + var propertyNames = Object.keys(object); |
| + propertyNames.sort(); |
| + for (var i = 0; i < propertyNames.length; ++i) { |
| + var name = propertyNames[i]; |
| + if (!object.hasOwnProperty(name)) |
| + continue; |
| + var prefixWithName = " " + prefix + name + " : "; |
| + dumpValue(object[name], " " + prefix, prefixWithName); |
| + } |
| + lines.push(prefix + "}"); |
| + } |
| + |
| + function dumpItems(object, prefix, firstLinePrefix) { |
| + prefix = prefix || ""; |
| + firstLinePrefix = firstLinePrefix || prefix; |
| + lines.push(firstLinePrefix + "["); |
| + for (var i = 0; i < object.length; ++i) |
| + dumpValue(object[i], " " + prefix, " " + prefix + "[" + i + "] : "); |
| + lines.push(prefix + "]"); |
| + } |
| + |
| + dumpValue(object, "", title || ""); |
| + InspectorTest.log(lines.join("\n")); |
| +}; |
| + |
| +InspectorTest._checkExpectation = function(fail, name, messageObject) { |
| + if (fail === !!messageObject.error) { |
| + InspectorTest.log("PASS: " + name); |
| + return true; |
| + } |
| + |
| + InspectorTest.log("FAIL: " + name + ": " + JSON.stringify(messageObject)); |
| + InspectorTest.completeTest(); |
| + return false; |
| +} |
| +InspectorTest.expectedSuccess = InspectorTest._checkExpectation.bind(null, false); |
| +InspectorTest.expectedError = InspectorTest._checkExpectation.bind(null, true); |
| + |
| +InspectorTest.die = function(message, error) { |
| + InspectorTest.log(`${message}: ${error}\n${error.stack}`); |
| + InspectorTest.completeTest(); |
| + throw new Error(); |
| +}; |
| + |
| +InspectorTest.createPage = async function() { |
| + var targetId = (await DevToolsAPI._sendCommandOrDie('Target.createTarget', {url: 'about:blank'})).targetId; |
| + await DevToolsAPI._sendCommandOrDie('Target.activateTarget', {targetId}); |
| + var page = new InspectorTest.Page(targetId); |
| + var dummyURL = window.location.href; |
| + dummyURL = dummyURL.substring(0, dummyURL.indexOf('inspector-protocol-test.html')) + 'inspector-protocol-page.html'; |
| + await page._navigate(dummyURL); |
| + return page; |
| +}; |
| + |
| +InspectorTest.Page = class { |
| + constructor(targetId) { |
| + this._targetId = targetId; |
| + } |
| + |
| + async createSession() { |
| + await DevToolsAPI._sendCommandOrDie('Target.attachToTarget', {targetId: this._targetId}); |
| + var session = new InspectorTest.Session(this); |
| + InspectorTest._sessions.set(this._targetId, session); |
| + return session; |
| + } |
| + |
| + navigate(url) { |
| + return this._navigate(InspectorTest.url(url)); |
| + } |
| + |
| + async _navigate(url) { |
| + if (InspectorTest._sessions.get(this._targetId)) |
| + InspectorTest.die(`Cannot navigate to ${url} with active session`, new Error()); |
| + |
| + var session = await this.createSession(); |
| + session.Protocol.Page.enable(); |
| + session.Protocol.Page.navigate({url: url}); |
| + |
| + var callback; |
| + var promise = new Promise(f => callback = f); |
| + session.Protocol.Page.onFrameNavigated(message => { |
| + if (!message.params.frame.parentId) |
| + callback(); |
| + }); |
| + await promise; |
| + |
| + await session.disconnect(); |
| + } |
| + |
| + async loadHTML(html) { |
| + if (InspectorTest._sessions.get(this._targetId)) |
| + InspectorTest.die('Cannot loadHTML with active session', new Error()); |
| + |
| + html = html.replace(/'/g, "\\'").replace(/\n/g, "\\n"); |
| + var session = await this.createSession(); |
| + await session.Protocol.Runtime.evaluate({expression: `document.body.innerHTML='${html}'`}); |
| + await session.disconnect(); |
| + } |
| +}; |
| + |
| +InspectorTest.Session = class { |
| + constructor(page) { |
| + this._page = page; |
| + this._requestId = 0; |
| + this._dispatchTable = new Map(); |
| + this._eventHandlers = new Map(); |
| + this.Protocol = this._setupProtocol(); |
| + } |
| + |
| + async disconnect() { |
| + await DevToolsAPI._sendCommandOrDie('Target.detachFromTarget', {targetId: this._page._targetId}); |
| + InspectorTest._sessions.delete(this._page._targetId); |
| + } |
| + |
| + sendRawCommand(requestId, message) { |
| + DevToolsAPI._sendCommandOrDie('Target.sendMessageToTarget', {targetId: this._page._targetId, message: message}); |
| + return new Promise(f => this._dispatchTable.set(requestId, f)); |
| + } |
| + |
| + sendCommand(method, params) { |
| + var requestId = ++this._requestId; |
| + var messageObject = {'id': requestId, 'method': method, 'params': params}; |
| + if (InspectorTest._dumpInspectorProtocolMessages) |
| + InspectorTest.log(`frontend => backend: ${JSON.stringify(messageObject)}`); |
| + return this.sendRawCommand(requestId, JSON.stringify(messageObject)); |
| + } |
| + |
| + async evaluate(code) { |
| + var response = await this.Protocol.Runtime.evaluate({expression: code, returnByValue: true}); |
| + if (response.error) { |
| + InspectorTest.log(`Error while evaluating '${code}': ${response.error}`); |
| + InspectorTest.completeTest(); |
| + } else { |
| + return response.result.result.value; |
| + } |
| + } |
| + |
| + async evaluateAsync(code) { |
| + var response = await this.Protocol.Runtime.evaluate({expression: code, returnByValue: true, awaitPromise: true}); |
| + if (response.error) { |
| + InspectorTest.log(`Error while evaluating async '${code}': ${response.error}`); |
| + InspectorTest.completeTest(); |
| + } else { |
| + return response.result.result.value; |
| + } |
| + } |
| + |
| + _dispatchMessage(message) { |
| + if (InspectorTest._dumpInspectorProtocolMessages) |
| + InspectorTest.log(`backend => frontend: ${JSON.stringify(message)}`); |
| + if (typeof message.id === 'number') { |
| + var handler = this._dispatchTable.get(message.id); |
| + if (handler) { |
| + this._dispatchTable.delete(message.id); |
| + handler(message); |
| + } |
| + } else { |
| + var eventName = message.method; |
| + for (var handler of (this._eventHandlers.get(eventName) || [])) |
| + handler(message); |
| + } |
| + } |
| + |
| + _setupProtocol() { |
| + return new Proxy({}, { get: (target, agentName, receiver) => new Proxy({}, { |
| + get: (target, methodName, receiver) => { |
| + const eventPattern = /^(on(ce)?|off)([A-Z][A-Za-z0-9]+)/; |
| + var match = eventPattern.exec(methodName); |
| + if (!match) |
| + return args => this.sendCommand(`${agentName}.${methodName}`, args || {}); |
| + var eventName = match[3]; |
| + eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1); |
| + if (match[1] === 'once') |
| + return () => this._waitForEvent(`${agentName}.${eventName}`); |
| + if (match[1] === 'off') |
| + return listener => this._removeEventHandler(`${agentName}.${eventName}`, listener); |
| + return listener => this._addEventHandler(`${agentName}.${eventName}`, listener); |
| + } |
| + })}); |
| + } |
| + |
| + _addEventHandler(eventName, handler) { |
| + var handlers = this._eventHandlers.get(eventName) || []; |
| + handlers.push(handler); |
| + this._eventHandlers.set(eventName, handlers); |
| + } |
| + |
| + _removeEventHandler(eventName, handler) { |
| + var handlers = this._eventHandlers.get(eventName) || []; |
| + var index = handlers.indexOf(handler); |
| + if (index === -1) |
| + return; |
| + handlers.splice(index, 1); |
| + this._eventHandlers.set(eventName, handlers); |
| + } |
| + |
| + _waitForEvent(eventName) { |
| + return new Promise(callback => { |
| + var handler = result => { |
| + this._removeEventHandler(eventName, handler); |
| + callback(result); |
| + }; |
| + this._addEventHandler(eventName, handler); |
| + }); |
| + } |
| +}; |
| + |
| +var DevToolsAPI = {}; |
| +DevToolsAPI._requestId = 0; |
| +DevToolsAPI._embedderMessageId = 0; |
| +DevToolsAPI._dispatchTable = new Map(); |
| + |
| +DevToolsAPI.dispatchMessage = function(messageOrObject) { |
| + var messageObject = (typeof messageOrObject === 'string' ? JSON.parse(messageOrObject) : messageOrObject); |
| + var messageId = messageObject.id; |
| + try { |
| + if (typeof messageId === 'number') { |
| + var handler = DevToolsAPI._dispatchTable.get(messageId); |
| + if (handler) { |
| + DevToolsAPI._dispatchTable.delete(messageId); |
| + handler(messageObject); |
| + } |
| + } else { |
| + var eventName = messageObject.method; |
| + if (eventName === 'Target.receivedMessageFromTarget') { |
| + var targetId = messageObject.params.targetId; |
| + var message = messageObject.params.message; |
| + var session = InspectorTest._sessions.get(targetId); |
| + if (session) |
| + session._dispatchMessage(JSON.parse(message)); |
| + } |
| + } |
| + } catch(e) { |
| + InspectorTest.die(`Exception when dispatching message\n${JSON.stringify(messageObject)}`, e); |
| + } |
| +}; |
| + |
| +DevToolsAPI._sendCommand = function(method, params) { |
| + var requestId = ++DevToolsAPI._requestId; |
| + var messageObject = {'id': requestId, 'method': method, 'params': params}; |
| + var embedderMessage = {'id': ++DevToolsAPI._embedderMessageId, 'method': 'dispatchProtocolMessage', 'params': [JSON.stringify(messageObject)]}; |
| + DevToolsHost.sendMessageToEmbedder(JSON.stringify(embedderMessage)); |
| + return new Promise(f => DevToolsAPI._dispatchTable.set(requestId, f)); |
| +}; |
| + |
| +DevToolsAPI._sendCommandOrDie = function(method, params, errorMessage) { |
| + errorMessage = errorMessage || 'Unexpected error'; |
| + return DevToolsAPI._sendCommand(method, params).then(message => { |
| + if (message.error) |
| + InspectorTest.die('Error starting the test', new Error(message.error)); |
| + return message.result; |
| + }); |
| +}; |
| + |
| +InspectorTest._start = async function(description, html, url) { |
| + try { |
| + InspectorTest.log(description); |
| + var page = await InspectorTest.createPage(); |
| + if (url) |
| + await page.navigate(url); |
| + if (html) |
| + await page.loadHTML(html); |
| + var session = await page.createSession(); |
| + return { page: page, session: session, Protocol: session.Protocol }; |
| + } catch (e) { |
| + InspectorTest.die('Error starting the test', e); |
| + } |
| +}; |
| +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
|
| +InspectorTest.startHTML = (html, description) => InspectorTest._start(description, html, null); |
| +InspectorTest.startURL = (url, description) => InspectorTest._start(description, null, url); |
| + |
| +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!
|
| + window.test = testFunction; |
| + InspectorTest.log = console.log; |
| + InspectorTest.completeTest = () => console.log('Test completed'); |
| + var dispatch = DevToolsAPI.dispatchMessage; |
| + var messages = []; |
| + DevToolsAPI.dispatchMessage = message => { |
| + if (!messages.length) { |
| + setTimeout(() => { |
| + for (var message of messages.splice(0)) |
| + dispatch(message); |
| + }, 0); |
| + } |
| + messages.push(message); |
| + }; |
| + return () => {}; |
| +}; |
| + |
| +InspectorTest._fetch = function(url) { |
| + return new Promise(fulfill => { |
| + var xhr = new XMLHttpRequest(); |
| + xhr.open('GET', url, true); |
| + xhr.onreadystatechange = e => { |
| + if (xhr.readyState !== XMLHttpRequest.DONE) |
| + return; |
| + if ([0, 200, 304].indexOf(xhr.status) === -1) // Testing harness file:/// results in 0. |
| + InspectorTest.die(`${xhr.status} while fetching ${url}`, new Error()); |
| + else |
| + fulfill(e.target.response); |
| + }; |
| + xhr.send(null); |
| + }); |
| +}; |
| + |
| +InspectorTest.loadScript = async function(url) { |
| + var source = await InspectorTest._fetch(InspectorTest.url(url)); |
| + return eval(`${source}\n//# sourceURL=${url}`); |
| +}; |
| + |
| +window.addEventListener('load', async () => { |
| + testRunner.dumpAsText(); |
| + testRunner.waitUntilDone(); |
| + testRunner.setCanOpenWindows(true); |
| + try { |
| + var testScriptURL = window.location.search.substring(1); |
| + InspectorTest._baseURL = testScriptURL.substring(0, testScriptURL.lastIndexOf('/') + 1); |
| + var testScript = await InspectorTest._fetch(testScriptURL); |
| + eval(`${testScript}\n//# sourceURL=${testScriptURL}`); |
| + } catch(e) { |
| + InspectorTest.die('Unable to execute test script', e); |
| + } |
| +}, false); |
| + |
| +window['onerror'] = (message, source, lineno, colno, error) => { |
| + InspectorTest.die('General error', error); |
| +}; |
| + |
| +window.addEventListener('unhandledrejection', e => { |
| + InspectorTest.die('General error', new Error(e.reason)); |
| +}, false); |