Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(100)

Side by Side Diff: third_party/WebKit/LayoutTests/inspector-protocol/resources/inspector-protocol-test.js

Issue 2950713002: [DevTools] New harness for inspector-protocol layout tests (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 TestRunner = class {
6 constructor(baseURL, log, completeTest, fetch) {
7 this._dumpInspectorProtocolMessages = false;
8 this._baseURL = baseURL;
9 this._log = log;
10 this._completeTest = completeTest;
11 this._fetch = fetch;
12 }
13
14 startDumpingProtocolMessages() {
15 this._dumpInspectorProtocolMessages = true;
16 };
17
18 completeTest() {
19 this._completeTest.call(null);
20 }
21
22 log(text) {
23 this._log.call(null, text);
24 }
25
26 logMessage(originalMessage) {
27 var message = JSON.parse(JSON.stringify(originalMessage));
28 if (message.id)
29 message.id = '<messageId>';
30 const nonStableFields = new Set(['nodeId', 'objectId', 'scriptId', 'timestam p']);
31 var objects = [message];
32 while (objects.length) {
33 var object = objects.shift();
34 for (var key in object) {
35 if (nonStableFields.has(key))
36 object[key] = `<${key}>`;
37 else if (typeof object[key] === 'string' && object[key].match(/\d+:\d+:\ d+:debug/))
38 object[key] = object[key].replace(/\d+/, '<scriptId>');
39 else if (typeof object[key] === 'object')
40 objects.push(object[key]);
41 }
42 }
43 this.logObject(message);
44 return originalMessage;
45 }
46
47 logObject(object, title) {
48 var lines = [];
49
50 function dumpValue(value, prefix, prefixWithName) {
51 if (typeof value === 'object' && value !== null) {
52 if (value instanceof Array)
53 dumpItems(value, prefix, prefixWithName);
54 else
55 dumpProperties(value, prefix, prefixWithName);
56 } else {
57 lines.push(prefixWithName + String(value).replace(/\n/g, ' '));
58 }
59 }
60
61 function dumpProperties(object, prefix, firstLinePrefix) {
62 prefix = prefix || '';
63 firstLinePrefix = firstLinePrefix || prefix;
64 lines.push(firstLinePrefix + '{');
65
66 var propertyNames = Object.keys(object);
67 propertyNames.sort();
68 for (var i = 0; i < propertyNames.length; ++i) {
69 var name = propertyNames[i];
70 if (!object.hasOwnProperty(name))
71 continue;
72 var prefixWithName = ' ' + prefix + name + ' : ';
73 dumpValue(object[name], ' ' + prefix, prefixWithName);
74 }
75 lines.push(prefix + '}');
76 }
77
78 function dumpItems(object, prefix, firstLinePrefix) {
79 prefix = prefix || '';
80 firstLinePrefix = firstLinePrefix || prefix;
81 lines.push(firstLinePrefix + '[');
82 for (var i = 0; i < object.length; ++i)
83 dumpValue(object[i], ' ' + prefix, ' ' + prefix + '[' + i + '] : ' );
84 lines.push(prefix + ']');
85 }
86
87 dumpValue(object, '', title || '');
88 this.log(lines.join('\n'));
89 }
90
91 url(relative) {
92 return this._baseURL + relative;
93 }
94
95 _checkExpectation(fail, name, messageObject) {
96 if (fail === !!messageObject.error) {
97 this.log('PASS: ' + name);
98 return true;
99 }
100
101 this.log('FAIL: ' + name + ': ' + JSON.stringify(messageObject));
102 this.completeTest();
103 return false;
104 }
105
106 expectedSuccess(name, messageObject) {
107 return this._checkExpectation(false, name, messageObject);
108 }
109
110 expectedError(name, messageObject) {
111 return this._checkExpectation(true, name, messageObject);
112 }
113
114 die(message, error) {
115 this.log(`${message}: ${error}\n${error.stack}`);
116 this.completeTest();
117 throw new Error();
einbinder 2017/06/21 01:21:48 Put the message in this error?
dgozman 2017/06/21 18:11:47 Done.
118 }
119
120 async loadScript(url) {
121 var source = await this._fetch(this.url(url));
122 return eval(`${source}\n//# sourceURL=${url}`);
123 };
124
125 async createPage() {
126 var targetId = (await DevToolsAPI._sendCommandOrDie('Target.createTarget', { url: 'about:blank'})).targetId;
127 await DevToolsAPI._sendCommandOrDie('Target.activateTarget', {targetId});
128 var page = new TestRunner.Page(this, targetId);
129 var dummyURL = window.location.href;
130 dummyURL = dummyURL.substring(0, dummyURL.indexOf('inspector-protocol-test.h tml')) + 'inspector-protocol-page.html';
131 await page._navigate(dummyURL);
132 return page;
133 }
134
135 async _start(description, html, url) {
136 try {
137 this.log(description);
138 var page = await this.createPage();
139 if (url)
140 await page.navigate(url);
141 if (html)
142 await page.loadHTML(html);
143 var session = await page.createSession();
144 return { page: page, session: session, dp: session.protocol };
145 } catch (e) {
146 this.die('Error starting the test', e);
147 }
148 };
149
150 startBlank(description) {
151 return this._start(description, null, null);
152 }
153
154 startHTML(html, description) {
155 return this._start(description, html, null);
156 }
157
158 startURL(url, description) {
159 return this._start(description, null, url);
160 }
161 };
162
163 TestRunner.Page = class {
164 constructor(testRunner, targetId) {
165 this._testRunner = testRunner;
166 this._targetId = targetId;
167 }
168
169 async createSession() {
170 await DevToolsAPI._sendCommandOrDie('Target.attachToTarget', {targetId: this ._targetId});
171 var session = new TestRunner.Session(this);
172 DevToolsAPI._sessions.set(this._targetId, session);
173 return session;
174 }
175
176 navigate(url) {
177 return this._navigate(this._testRunner.url(url));
178 }
179
180 async _navigate(url) {
181 if (DevToolsAPI._sessions.get(this._targetId))
182 this._testRunner.die(`Cannot navigate to ${url} with active session`, new Error());
183
184 var session = await this.createSession();
185 session.protocol.Page.enable();
186 session.protocol.Page.navigate({url: url});
187
188 var callback;
189 var promise = new Promise(f => callback = f);
190 session.protocol.Page.onFrameNavigated(message => {
191 if (!message.params.frame.parentId)
192 callback();
193 });
194 await promise;
195
196 await session.disconnect();
197 }
198
199 async loadHTML(html) {
200 if (DevToolsAPI._sessions.get(this._targetId))
201 this._testRunner.die('Cannot loadHTML with active session', new Error());
202
203 html = html.replace(/'/g, "\\'").replace(/\n/g, '\\n');
204 var session = await this.createSession();
205 await session.protocol.Runtime.evaluate({expression: `document.body.innerHTM L='${html}'`});
206 await session.disconnect();
207 }
208 };
209
210 TestRunner.Session = class {
211 constructor(page) {
212 this._testRunner = page._testRunner;
213 this._page = page;
214 this._requestId = 0;
215 this._dispatchTable = new Map();
216 this._eventHandlers = new Map();
217 this.protocol = this._setupProtocol();
218 }
219
220 async disconnect() {
221 await DevToolsAPI._sendCommandOrDie('Target.detachFromTarget', {targetId: th is._page._targetId});
222 DevToolsAPI._sessions.delete(this._page._targetId);
223 }
224
225 sendRawCommand(requestId, message) {
226 DevToolsAPI._sendCommandOrDie('Target.sendMessageToTarget', {targetId: this. _page._targetId, message: message});
227 return new Promise(f => this._dispatchTable.set(requestId, f));
228 }
229
230 sendCommand(method, params) {
231 var requestId = ++this._requestId;
232 var messageObject = {'id': requestId, 'method': method, 'params': params};
233 if (this._testRunner._dumpInspectorProtocolMessages)
234 this._testRunner.log(`frontend => backend: ${JSON.stringify(messageObject) }`);
235 return this.sendRawCommand(requestId, JSON.stringify(messageObject));
236 }
237
238 async evaluate(code) {
239 var response = await this.protocol.Runtime.evaluate({expression: code, retur nByValue: true});
240 if (response.error) {
241 this._testRunner.log(`Error while evaluating '${code}': ${response.error}` );
242 this._testRunner.completeTest();
243 } else {
244 return response.result.result.value;
245 }
246 }
247
248 async evaluateAsync(code) {
249 var response = await this.protocol.Runtime.evaluate({expression: code, retur nByValue: true, awaitPromise: true});
250 if (response.error) {
251 this._testRunner.log(`Error while evaluating async '${code}': ${response.e rror}`);
252 this._testRunner.completeTest();
253 } else {
254 return response.result.result.value;
255 }
256 }
257
258 _dispatchMessage(message) {
259 if (this._testRunner._dumpInspectorProtocolMessages)
260 this._testRunner.log(`backend => frontend: ${JSON.stringify(message)}`);
261 if (typeof message.id === 'number') {
262 var handler = this._dispatchTable.get(message.id);
263 if (handler) {
264 this._dispatchTable.delete(message.id);
265 handler(message);
266 }
267 } else {
268 var eventName = message.method;
269 for (var handler of (this._eventHandlers.get(eventName) || []))
270 handler(message);
271 }
272 }
273
274 _setupProtocol() {
275 return new Proxy({}, { get: (target, agentName, receiver) => new Proxy({}, {
276 get: (target, methodName, receiver) => {
277 const eventPattern = /^(on(ce)?|off)([A-Z][A-Za-z0-9]+)/;
278 var match = eventPattern.exec(methodName);
279 if (!match)
280 return args => this.sendCommand(`${agentName}.${methodName}`, args || {});
281 var eventName = match[3];
282 eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1);
283 if (match[1] === 'once')
284 return () => this._waitForEvent(`${agentName}.${eventName}`);
285 if (match[1] === 'off')
286 return listener => this._removeEventHandler(`${agentName}.${eventName} `, listener);
287 return listener => this._addEventHandler(`${agentName}.${eventName}`, li stener);
288 }
289 })});
290 }
291
292 _addEventHandler(eventName, handler) {
293 var handlers = this._eventHandlers.get(eventName) || [];
294 handlers.push(handler);
295 this._eventHandlers.set(eventName, handlers);
296 }
297
298 _removeEventHandler(eventName, handler) {
299 var handlers = this._eventHandlers.get(eventName) || [];
300 var index = handlers.indexOf(handler);
301 if (index === -1)
302 return;
303 handlers.splice(index, 1);
304 this._eventHandlers.set(eventName, handlers);
305 }
306
307 _waitForEvent(eventName) {
308 return new Promise(callback => {
309 var handler = result => {
310 this._removeEventHandler(eventName, handler);
311 callback(result);
312 };
313 this._addEventHandler(eventName, handler);
314 });
315 }
316 };
317
318 var DevToolsAPI = {};
319 DevToolsAPI._requestId = 0;
320 DevToolsAPI._embedderMessageId = 0;
321 DevToolsAPI._dispatchTable = new Map();
322 DevToolsAPI._sessions = new Map();
323 DevToolsAPI._outputElement = null;
324
325 DevToolsAPI._log = function(text) {
326 if (!DevToolsAPI._outputElement) {
327 var intermediate = document.createElement('div');
328 document.body.appendChild(intermediate);
329 var intermediate2 = document.createElement('div');
330 intermediate.appendChild(intermediate2);
331 DevToolsAPI._outputElement = document.createElement('div');
332 DevToolsAPI._outputElement.className = 'output';
333 DevToolsAPI._outputElement.id = 'output';
334 DevToolsAPI._outputElement.style.whiteSpace = 'pre';
335 intermediate2.appendChild(DevToolsAPI._outputElement);
336 }
337 DevToolsAPI._outputElement.appendChild(document.createTextNode(text));
338 DevToolsAPI._outputElement.appendChild(document.createElement('br'));
339 };
340
341 DevToolsAPI._completeTest = function() {
342 window.testRunner.notifyDone();
343 };
344
345 DevToolsAPI._die = function(message, error) {
346 DevToolsAPI._log(`${message}: ${error}\n${error.stack}`);
347 DevToolsAPI._completeTest();
348 throw new Error();
349 };
350
351 DevToolsAPI.dispatchMessage = function(messageOrObject) {
352 var messageObject = (typeof messageOrObject === 'string' ? JSON.parse(messageO rObject) : messageOrObject);
353 var messageId = messageObject.id;
354 try {
355 if (typeof messageId === 'number') {
356 var handler = DevToolsAPI._dispatchTable.get(messageId);
357 if (handler) {
358 DevToolsAPI._dispatchTable.delete(messageId);
359 handler(messageObject);
360 }
361 } else {
362 var eventName = messageObject.method;
363 if (eventName === 'Target.receivedMessageFromTarget') {
364 var targetId = messageObject.params.targetId;
365 var message = messageObject.params.message;
366 var session = DevToolsAPI._sessions.get(targetId);
367 if (session)
368 session._dispatchMessage(JSON.parse(message));
369 }
370 }
371 } catch(e) {
372 DevToolsAPI._die(`Exception when dispatching message\n${JSON.stringify(messa geObject)}`, e);
373 }
374 };
375
376 DevToolsAPI._sendCommand = function(method, params) {
377 var requestId = ++DevToolsAPI._requestId;
378 var messageObject = {'id': requestId, 'method': method, 'params': params};
379 var embedderMessage = {'id': ++DevToolsAPI._embedderMessageId, 'method': 'disp atchProtocolMessage', 'params': [JSON.stringify(messageObject)]};
380 DevToolsHost.sendMessageToEmbedder(JSON.stringify(embedderMessage));
381 return new Promise(f => DevToolsAPI._dispatchTable.set(requestId, f));
382 };
383
384 DevToolsAPI._sendCommandOrDie = function(method, params, errorMessage) {
385 errorMessage = errorMessage || 'Unexpected error';
einbinder 2017/06/21 01:21:48 errorMessage doesn't appear to be used anywhere.
dgozman 2017/06/21 18:11:47 Removed.
386 return DevToolsAPI._sendCommand(method, params).then(message => {
387 if (message.error)
388 DevToolsAPI._die('Error communicating with harness', new Error(message.err or));
389 return message.result;
390 });
391 };
392
393 function debugTest(testFunction) {
394 var dispatch = DevToolsAPI.dispatchMessage;
395 var messages = [];
396 DevToolsAPI.dispatchMessage = message => {
397 if (!messages.length) {
398 setTimeout(() => {
399 for (var message of messages.splice(0))
400 dispatch(message);
401 }, 0);
402 }
403 messages.push(message);
404 };
405 return testRunner => {
406 testRunner.log = console.log;
407 testRunner.completeTest = () => console.log('Test completed');
408 window.test = () => testFunction(testRunner);
409 };
410 };
411
412 DevToolsAPI._fetch = function(url) {
413 return new Promise(fulfill => {
414 var xhr = new XMLHttpRequest();
415 xhr.open('GET', url, true);
416 xhr.onreadystatechange = e => {
417 if (xhr.readyState !== XMLHttpRequest.DONE)
418 return;
419 if ([0, 200, 304].indexOf(xhr.status) === -1) // Testing harness file:/// results in 0.
420 DevToolsAPI._die(`${xhr.status} while fetching ${url}`, new Error());
421 else
422 fulfill(e.target.response);
423 };
424 xhr.send(null);
425 });
426 };
427
428 if (window.testRunner) {
einbinder 2017/06/21 01:21:48 Throw if no testRunner?
dgozman 2017/06/21 18:11:47 Done.
429 window.testRunner.dumpAsText();
430 window.testRunner.waitUntilDone();
431 window.testRunner.setCanOpenWindows(true);
432 }
433
434 window.addEventListener('load', () => {
435 var testScriptURL = window.location.search.substring(1);
436 var baseURL = testScriptURL.substring(0, testScriptURL.lastIndexOf('/') + 1);
437 DevToolsAPI._fetch(testScriptURL).then(testScript => {
438 var testRunner = new TestRunner(baseURL, DevToolsAPI._log, DevToolsAPI._comp leteTest, DevToolsAPI._fetch);
439 var testFunction = eval(`${testScript}\n//# sourceURL=${testScriptURL}`);
440 return testFunction(testRunner);
441 }).catch(reason => {
442 DevToolsAPI._log(`Error while executing test script: ${reason}\n${reason.sta ck}`);
443 DevToolsAPI._completeTest();
444 });
445 }, false);
446
447 window['onerror'] = (message, source, lineno, colno, error) => {
448 DevToolsAPI._log(`${error}\n${error.stack}`);
einbinder 2017/06/21 01:21:48 Check that error isn't null/undefined here. Otherw
dgozman 2017/06/21 18:11:47 I think this one is an Error instance. I do check
449 DevToolsAPI._completeTest();
450 };
451
452 window.addEventListener('unhandledrejection', e => {
453 DevToolsAPI._log(`Promise rejection: ${e.reason}\n${e.reason ? e.reason.stack : ''}`);
454 DevToolsAPI._completeTest();
455 }, false);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698