always construct networkRecords from devtoolsLog (#2133)
simplify devtoolsLog recording, url redirect tracking, and eliminate explicit network recording
This commit is contained in:
Родитель
75d8f4d1f1
Коммит
d7e4d1bb09
|
@ -44,13 +44,28 @@ class Driver {
|
|||
this._connection = connection;
|
||||
// currently only used by WPT where just Page and Network are needed
|
||||
this._devtoolsLog = new DevtoolsLog(/^(Page|Network)\./);
|
||||
connection.on('notification', event => {
|
||||
this._devtoolsLog.record(event);
|
||||
this.recordNetworkEvent(event.method, event.params);
|
||||
this._eventEmitter.emit(event.method, event.params);
|
||||
});
|
||||
this.online = true;
|
||||
this._domainEnabledCounts = new Map();
|
||||
|
||||
/**
|
||||
* Used for monitoring network status events during gotoURL.
|
||||
* @private {?NetworkRecorder}
|
||||
*/
|
||||
this._networkStatusMonitor = null;
|
||||
|
||||
/**
|
||||
* Used for monitoring url redirects during gotoURL.
|
||||
* @private {?string}
|
||||
*/
|
||||
this._monitoredUrl = null;
|
||||
|
||||
connection.on('notification', event => {
|
||||
this._devtoolsLog.record(event);
|
||||
if (this._networkStatusMonitor) {
|
||||
this._networkStatusMonitor.dispatch(event.method, event.params);
|
||||
}
|
||||
this._eventEmitter.emit(event.method, event.params);
|
||||
});
|
||||
}
|
||||
|
||||
static get traceCategories() {
|
||||
|
@ -73,13 +88,6 @@ class Driver {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Array<{method: string, params: !Object}>}
|
||||
*/
|
||||
get devtoolsLog() {
|
||||
return this._devtoolsLog.messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise<string>}
|
||||
*/
|
||||
|
@ -346,25 +354,6 @@ class Driver {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If our main document URL redirects, we will update options.url accordingly
|
||||
* As such, options.url will always represent the post-redirected URL.
|
||||
* options.initialUrl is the pre-redirect URL that things started with
|
||||
* @param {!Object} opts
|
||||
*/
|
||||
enableUrlUpdateIfRedirected(opts) {
|
||||
this._networkRecorder.on('requestloaded', redirectRequest => {
|
||||
// Quit if this is not a redirected request
|
||||
if (!redirectRequest.redirectSource) {
|
||||
return;
|
||||
}
|
||||
const earlierRequest = redirectRequest.redirectSource;
|
||||
if (earlierRequest.url === opts.url) {
|
||||
opts.url = redirectRequest.url;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves when the network has been idle for
|
||||
* `pauseAfterLoadMs` ms and a method to cancel internal network listeners and
|
||||
|
@ -380,7 +369,7 @@ class Driver {
|
|||
const promise = new Promise((resolve, reject) => {
|
||||
const onIdle = () => {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
this._networkRecorder.once('networkbusy', onBusy);
|
||||
this._networkStatusMonitor.once('networkbusy', onBusy);
|
||||
idleTimeout = setTimeout(_ => {
|
||||
cancel();
|
||||
resolve();
|
||||
|
@ -388,17 +377,17 @@ class Driver {
|
|||
};
|
||||
|
||||
const onBusy = () => {
|
||||
this._networkRecorder.once('networkidle', onIdle);
|
||||
this._networkStatusMonitor.once('networkidle', onIdle);
|
||||
clearTimeout(idleTimeout);
|
||||
};
|
||||
|
||||
cancel = () => {
|
||||
clearTimeout(idleTimeout);
|
||||
this._networkRecorder.removeListener('networkbusy', onBusy);
|
||||
this._networkRecorder.removeListener('networkidle', onIdle);
|
||||
this._networkStatusMonitor.removeListener('networkbusy', onBusy);
|
||||
this._networkStatusMonitor.removeListener('networkidle', onIdle);
|
||||
};
|
||||
|
||||
if (this._networkRecorder.isIdle()) {
|
||||
if (this._networkStatusMonitor.isIdle()) {
|
||||
onIdle();
|
||||
} else {
|
||||
onBusy();
|
||||
|
@ -490,14 +479,55 @@ class Driver {
|
|||
}
|
||||
|
||||
/**
|
||||
* Navigate to the given URL. Use of this method directly isn't advised: if
|
||||
* Set up listener for network quiet events and URL redirects. Passed in URL
|
||||
* will be monitored for redirects, with the final loaded URL passed back in
|
||||
* _endNetworkStatusMonitoring.
|
||||
* @param {string} startingUrl
|
||||
* @return {!Promise}
|
||||
* @private
|
||||
*/
|
||||
_beginNetworkStatusMonitoring(startingUrl) {
|
||||
this._networkStatusMonitor = new NetworkRecorder([]);
|
||||
|
||||
// Update startingUrl if it's ever redirected.
|
||||
this._monitoredUrl = startingUrl;
|
||||
this._networkStatusMonitor.on('requestloaded', redirectRequest => {
|
||||
// Ignore if this is not a redirected request.
|
||||
if (!redirectRequest.redirectSource) {
|
||||
return;
|
||||
}
|
||||
const earlierRequest = redirectRequest.redirectSource;
|
||||
if (earlierRequest.url === this._monitoredUrl) {
|
||||
this._monitoredUrl = redirectRequest.url;
|
||||
}
|
||||
});
|
||||
|
||||
return this.sendCommand('Network.enable');
|
||||
}
|
||||
|
||||
/**
|
||||
* End network status listening. Returns the final, possibly redirected,
|
||||
* loaded URL starting with the one passed into _endNetworkStatusMonitoring.
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
_endNetworkStatusMonitoring() {
|
||||
this._networkStatusMonitor = null;
|
||||
const finalUrl = this._monitoredUrl;
|
||||
this._monitoredUrl = null;
|
||||
return finalUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the given URL. Direct use of this method isn't advised: if
|
||||
* the current page is already at the given URL, navigation will not occur and
|
||||
* so the returned promise will only resolve after the MAX_WAIT_FOR_FULLY_LOADED
|
||||
* timeout. See https://github.com/GoogleChrome/lighthouse/pull/185 for one
|
||||
* possible workaround.
|
||||
* Resolves on the url of the loaded page, taking into account any redirects.
|
||||
* @param {string} url
|
||||
* @param {!Object} options
|
||||
* @return {!Promise}
|
||||
* @return {!Promise<string>}
|
||||
*/
|
||||
gotoURL(url, options = {}) {
|
||||
const waitForLoad = options.waitForLoad || false;
|
||||
|
@ -506,16 +536,18 @@ class Driver {
|
|||
const maxWaitMs = (options.flags && options.flags.maxWaitForLoad) ||
|
||||
Driver.MAX_WAIT_FOR_FULLY_LOADED;
|
||||
|
||||
return this.sendCommand('Page.enable')
|
||||
return this._beginNetworkStatusMonitoring(url)
|
||||
.then(_ => this.sendCommand('Page.enable'))
|
||||
.then(_ => this.sendCommand('Emulation.setScriptExecutionDisabled', {value: disableJS}))
|
||||
.then(_ => this.sendCommand('Page.navigate', {url}))
|
||||
.then(_ => waitForLoad && this._waitForFullyLoaded(pauseAfterLoadMs, maxWaitMs));
|
||||
.then(_ => waitForLoad && this._waitForFullyLoaded(pauseAfterLoadMs, maxWaitMs))
|
||||
.then(_ => this._endNetworkStatusMonitoring());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} objectId Object ID for the resolved DOM node
|
||||
* @param {string} propName Name of the property
|
||||
* @return {!Promise<string>} The property value, or null, if property not found
|
||||
* @param {string} objectId Object ID for the resolved DOM node
|
||||
* @param {string} propName Name of the property
|
||||
* @return {!Promise<string>} The property value, or null, if property not found
|
||||
*/
|
||||
getObjectProperty(objectId, propName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -538,6 +570,18 @@ class Driver {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the body of the response with the given ID.
|
||||
* @param {string} requestId
|
||||
* @return {string}
|
||||
*/
|
||||
getRequestContent(requestId) {
|
||||
return this.sendCommand('Network.getResponseBody', {
|
||||
requestId,
|
||||
// Ignoring result.base64Encoded, which indicates if body is already encoded
|
||||
}).then(result => result.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of API whose permission you wish to query
|
||||
* @return {!Promise<string>} The state of permissions, resolved in a promise.
|
||||
|
@ -618,9 +662,6 @@ class Driver {
|
|||
throw new Error('DOM domain enabled when starting trace');
|
||||
}
|
||||
|
||||
this._devtoolsLog.reset();
|
||||
this._devtoolsLog.beginRecording();
|
||||
|
||||
// Enable Page domain to wait for Page.loadEventFired
|
||||
return this.sendCommand('Page.enable')
|
||||
.then(_ => this.sendCommand('Tracing.start', tracingOpts));
|
||||
|
@ -633,7 +674,6 @@ class Driver {
|
|||
return new Promise((resolve, reject) => {
|
||||
// When the tracing has ended this will fire with a stream handle.
|
||||
this.once('Tracing.tracingComplete', streamHandle => {
|
||||
this._devtoolsLog.endRecording();
|
||||
this._readTraceFromStream(streamHandle)
|
||||
.then(traceContents => resolve(traceContents), reject);
|
||||
});
|
||||
|
@ -672,34 +712,21 @@ class Driver {
|
|||
});
|
||||
}
|
||||
|
||||
beginNetworkCollect(opts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._networkRecords = [];
|
||||
this._networkRecorder = new NetworkRecorder(this._networkRecords, this);
|
||||
this.enableUrlUpdateIfRedirected(opts);
|
||||
|
||||
this.sendCommand('Network.enable').then(resolve, reject);
|
||||
});
|
||||
/**
|
||||
* Begin recording devtools protocol messages.
|
||||
*/
|
||||
beginDevtoolsLog() {
|
||||
this._devtoolsLog.reset();
|
||||
this._devtoolsLog.beginRecording();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!string} method
|
||||
* @param {!Object<string, *>=} params
|
||||
* Stop recording to devtoolsLog and return log contents.
|
||||
* @return {!Array<{method: string, params: (!Object<string, *>|undefined)}>}
|
||||
*/
|
||||
recordNetworkEvent(method, params) {
|
||||
if (!this._networkRecorder) return;
|
||||
|
||||
const regexFilter = /^Network\./;
|
||||
if (!regexFilter.test(method)) return;
|
||||
this._networkRecorder.dispatch(method, params);
|
||||
}
|
||||
|
||||
endNetworkCollect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(this._networkRecords);
|
||||
this._networkRecorder = null;
|
||||
this._networkRecords = [];
|
||||
});
|
||||
endDevtoolsLog() {
|
||||
this._devtoolsLog.endRecording();
|
||||
return this._devtoolsLog.messages;
|
||||
}
|
||||
|
||||
enableRuntimeEvents() {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
const log = require('../lib/log.js');
|
||||
const Audit = require('../audits/audit');
|
||||
const URL = require('../lib/url-shim');
|
||||
const NetworkRecorder = require('../lib/network-recorder.js');
|
||||
|
||||
/**
|
||||
* @typedef {!Object<string, !Array<!Promise<*>>>}
|
||||
|
@ -44,15 +45,15 @@ let GathererResults; // eslint-disable-line no-unused-vars
|
|||
* 2. For each pass in the config:
|
||||
* A. GatherRunner.beforePass()
|
||||
* i. navigate to about:blank
|
||||
* ii. all gatherer's beforePass()
|
||||
* ii. all gatherers' beforePass()
|
||||
* B. GatherRunner.pass()
|
||||
* i. GatherRunner.loadPage()
|
||||
* b. beginTrace (if requested) & beginNetworkCollect
|
||||
* c. navigate to options.url (and wait for onload)
|
||||
* ii. all gatherer's pass()
|
||||
* i. beginTrace (if requested) & beginDevtoolsLog
|
||||
* ii. GatherRunner.loadPage()
|
||||
* a. navigate to options.url (and wait for onload)
|
||||
* iii. all gatherers' pass()
|
||||
* C. GatherRunner.afterPass()
|
||||
* i. endTrace (if requested) & endNetworkCollect & endThrottling
|
||||
* ii. all gatherer's afterPass()
|
||||
* i. endTrace (if requested) & endDevtoolsLog & endThrottling
|
||||
* ii. all gatherers' afterPass()
|
||||
*
|
||||
* 3. Teardown
|
||||
* A. GatherRunner.disposeDriver()
|
||||
|
@ -76,23 +77,22 @@ class GatherRunner {
|
|||
}
|
||||
|
||||
/**
|
||||
* Loads options.url with specified options.
|
||||
* Loads options.url with specified options. If the main document URL
|
||||
* redirects, options.url will be updated accordingly. As such, options.url
|
||||
* will always represent the post-redirected URL. options.initialUrl is the
|
||||
* pre-redirect starting URL.
|
||||
* @param {!Driver} driver
|
||||
* @param {!Object} options
|
||||
* @return {!Promise}
|
||||
*/
|
||||
static loadPage(driver, options) {
|
||||
return Promise.resolve()
|
||||
// Begin tracing only if requested by config.
|
||||
.then(_ => options.config.recordTrace && driver.beginTrace(options.flags))
|
||||
// Network is always recorded for internal use, even if not saved as artifact.
|
||||
.then(_ => driver.beginNetworkCollect(options))
|
||||
// Navigate.
|
||||
.then(_ => driver.gotoURL(options.url, {
|
||||
waitForLoad: true,
|
||||
disableJavaScript: !!options.disableJavaScript,
|
||||
flags: options.flags,
|
||||
}));
|
||||
return driver.gotoURL(options.url, {
|
||||
waitForLoad: true,
|
||||
disableJavaScript: !!options.disableJavaScript,
|
||||
flags: options.flags,
|
||||
}).then(finalUrl => {
|
||||
options.url = finalUrl;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,9 +199,15 @@ class GatherRunner {
|
|||
const status = 'Loading page & waiting for onload';
|
||||
log.log('status', status, gatherernames);
|
||||
|
||||
const pass = GatherRunner.loadPage(driver, options).then(_ => {
|
||||
log.log('statusEnd', status);
|
||||
});
|
||||
// Always record devtoolsLog.
|
||||
driver.beginDevtoolsLog();
|
||||
|
||||
const pass = Promise.resolve()
|
||||
// Begin tracing only if requested by config.
|
||||
.then(_ => config.recordTrace && driver.beginTrace(options.flags))
|
||||
// Navigate.
|
||||
.then(_ => GatherRunner.loadPage(driver, options))
|
||||
.then(_ => log.log('statusEnd', status));
|
||||
|
||||
return gatherers.reduce((chain, gatherer) => {
|
||||
return chain.then(_ => {
|
||||
|
@ -242,17 +248,17 @@ class GatherRunner {
|
|||
});
|
||||
}
|
||||
|
||||
const status = 'Retrieving network records';
|
||||
pass = pass.then(_ => {
|
||||
passData.devtoolsLog = driver.devtoolsLog;
|
||||
const status = 'Retrieving devtoolsLog and network records';
|
||||
log.log('status', status);
|
||||
return driver.endNetworkCollect();
|
||||
}).then(networkRecords => {
|
||||
const devtoolsLog = driver.endDevtoolsLog();
|
||||
const networkRecords = NetworkRecorder.recordsFromLogs(devtoolsLog);
|
||||
GatherRunner.assertPageLoaded(options.url, driver, networkRecords);
|
||||
// expose devtoolsLog & networkRecords to gatherers
|
||||
passData.devtoolsLog = driver.devtoolsLog;
|
||||
passData.networkRecords = networkRecords;
|
||||
log.verbose('statusEnd', status);
|
||||
|
||||
// Expose devtoolsLog and networkRecords to gatherers
|
||||
passData.devtoolsLog = devtoolsLog;
|
||||
passData.networkRecords = networkRecords;
|
||||
});
|
||||
|
||||
// Disable throttling so the afterPass analysis isn't throttled
|
||||
|
@ -351,9 +357,12 @@ class GatherRunner {
|
|||
.then(_ => GatherRunner.pass(runOptions, gathererResults))
|
||||
.then(_ => GatherRunner.afterPass(runOptions, gathererResults))
|
||||
.then(passData => {
|
||||
// If requested by config, merge trace and network data for this
|
||||
// pass into tracingData.
|
||||
const passName = config.passName || Audit.DEFAULT_PASS;
|
||||
|
||||
// networkRecords are discarded and not added onto artifacts.
|
||||
tracingData.devtoolsLogs[passName] = passData.devtoolsLog;
|
||||
|
||||
// If requested by config, add trace to pass's tracingData
|
||||
if (config.recordTrace) {
|
||||
tracingData.traces[passName] = passData.trace;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class ResponseCompression extends Gatherer {
|
|||
|
||||
if (!isContentEncoded) {
|
||||
unoptimizedResponses.push({
|
||||
record: record,
|
||||
requestId: record.requestId,
|
||||
url: record.url,
|
||||
mimeType: record.mimeType,
|
||||
resourceSize: record.resourceSize,
|
||||
|
@ -62,8 +62,9 @@ class ResponseCompression extends Gatherer {
|
|||
const networkRecords = traceData.networkRecords;
|
||||
const textRecords = ResponseCompression.filterUnoptimizedResponses(networkRecords);
|
||||
|
||||
const driver = options.driver;
|
||||
return Promise.all(textRecords.map(record => {
|
||||
return record.record.requestContent().then(content => {
|
||||
return driver.getRequestContent(record.requestId).then(content => {
|
||||
// if we don't have any content gzipSize is set to 0
|
||||
if (!content) {
|
||||
record.gzipSize = 0;
|
||||
|
|
|
@ -24,13 +24,12 @@ class NetworkRecorder extends EventEmitter {
|
|||
/**
|
||||
* Creates an instance of NetworkRecorder.
|
||||
* @param {!Array} recordArray
|
||||
* @param {!Driver=} driver
|
||||
*/
|
||||
constructor(recordArray, driver) {
|
||||
constructor(recordArray) {
|
||||
super();
|
||||
|
||||
this._records = recordArray;
|
||||
this.networkManager = NetworkManager.createWithFakeTarget(driver);
|
||||
this.networkManager = NetworkManager.createWithFakeTarget();
|
||||
|
||||
this.startedRequestCount = 0;
|
||||
this.finishedRequestCount = 0;
|
||||
|
@ -39,14 +38,6 @@ class NetworkRecorder extends EventEmitter {
|
|||
this.onRequestStarted.bind(this));
|
||||
this.networkManager.addEventListener(this.EventTypes.RequestFinished,
|
||||
this.onRequestFinished.bind(this));
|
||||
|
||||
this.onRequestWillBeSent = this.onRequestWillBeSent.bind(this);
|
||||
this.onRequestServedFromCache = this.onRequestServedFromCache.bind(this);
|
||||
this.onResponseReceived = this.onResponseReceived.bind(this);
|
||||
this.onDataReceived = this.onDataReceived.bind(this);
|
||||
this.onLoadingFinished = this.onLoadingFinished.bind(this);
|
||||
this.onLoadingFailed = this.onLoadingFailed.bind(this);
|
||||
this.onResourceChangedPriority = this.onResourceChangedPriority.bind(this);
|
||||
}
|
||||
|
||||
get EventTypes() {
|
||||
|
@ -155,6 +146,10 @@ class NetworkRecorder extends EventEmitter {
|
|||
* @param {!Object<string, *>=} params
|
||||
*/
|
||||
dispatch(method, params) {
|
||||
if (!method.startsWith('Network.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case 'Network.requestWillBeSent': return this.onRequestWillBeSent(params);
|
||||
case 'Network.requestServedFromCache': return this.onRequestServedFromCache(params);
|
||||
|
@ -169,14 +164,14 @@ class NetworkRecorder extends EventEmitter {
|
|||
|
||||
/**
|
||||
* Construct network records from a log of devtools protocol messages.
|
||||
* @param {!DevtoolsLog} log
|
||||
* @param {!DevtoolsLog} devtoolsLog
|
||||
* @return {!Array<!WebInspector.NetworkRequest>}
|
||||
*/
|
||||
static recordsFromLogs(log) {
|
||||
static recordsFromLogs(devtoolsLog) {
|
||||
const records = [];
|
||||
const nr = new NetworkRecorder(records);
|
||||
log.forEach(event => {
|
||||
nr.dispatch(event.method, event.params);
|
||||
devtoolsLog.forEach(message => {
|
||||
nr.dispatch(message.method, message.params);
|
||||
});
|
||||
return records;
|
||||
}
|
||||
|
|
|
@ -239,16 +239,12 @@ module.exports = (function() {
|
|||
* Creates a new WebInspector NetworkManager using a mocked Target.
|
||||
* @return {!WebInspector.NetworkManager}
|
||||
*/
|
||||
WebInspector.NetworkManager.createWithFakeTarget = function(driver) {
|
||||
WebInspector.NetworkManager.createWithFakeTarget = function() {
|
||||
// Mocked-up WebInspector Target for NetworkManager
|
||||
const fakeNetworkAgent = {
|
||||
enable() {},
|
||||
getResponseBody(requestId, onComplete) {
|
||||
driver.sendCommand('Network.getResponseBody', {
|
||||
requestId,
|
||||
})
|
||||
.then(response => onComplete(null, response.body, response.base64Encoded))
|
||||
.catch(err => onComplete(err));
|
||||
getResponseBody() {
|
||||
throw new Error('Use driver.getRequestContent() for network request content');
|
||||
}
|
||||
};
|
||||
const fakeConsoleModel = {
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -22,12 +22,14 @@ let sendCommandParams = [];
|
|||
const Driver = require('../../gather/driver.js');
|
||||
const Connection = require('../../gather/connections/connection.js');
|
||||
const Element = require('../../lib/element.js');
|
||||
const NetworkRecorder = require('../../lib/network-recorder');
|
||||
const assert = require('assert');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
|
||||
const connection = new Connection();
|
||||
const driverStub = new Driver(connection);
|
||||
|
||||
const redirectDevtoolsLog = require('../fixtures/wikipedia-redirect.devtoolslog.json');
|
||||
|
||||
function createOnceStub(events) {
|
||||
return (eventName, cb) => {
|
||||
if (events[eventName]) {
|
||||
|
@ -89,20 +91,6 @@ connection.sendCommand = function(command, params) {
|
|||
}
|
||||
};
|
||||
|
||||
// mock redirects to test out enableUrlUpdateIfRedirected
|
||||
const req1 = {
|
||||
url: 'http://aliexpress.com/'
|
||||
};
|
||||
const req2 = {
|
||||
redirectSource: req1,
|
||||
url: 'http://www.aliexpress.com/'
|
||||
};
|
||||
const req3 = {
|
||||
redirectSource: req2,
|
||||
url: 'http://m.aliexpress.com/?tracelog=wwwhome2mobilesitehome'
|
||||
};
|
||||
const mockRedirects = [req1, req2, req3];
|
||||
|
||||
/* eslint-env mocha */
|
||||
|
||||
describe('Browser Driver', () => {
|
||||
|
@ -153,20 +141,49 @@ describe('Browser Driver', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('will update the options.url through redirects', () => {
|
||||
const networkRecorder = driverStub._networkRecorder = new NetworkRecorder([]);
|
||||
const opts = {url: req1.url};
|
||||
driverStub.enableUrlUpdateIfRedirected(opts);
|
||||
it('will track redirects through gotoURL load', () => {
|
||||
const delay = _ => new Promise(resolve => setTimeout(resolve));
|
||||
|
||||
// Fake some reqFinished events
|
||||
const networkManager = networkRecorder.networkManager;
|
||||
mockRedirects.forEach(request => {
|
||||
networkManager.dispatchEventToListeners(networkRecorder.EventTypes.RequestFinished, request);
|
||||
class ReplayConnection extends EventEmitter {
|
||||
connect() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
disconnect() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
replayLog() {
|
||||
redirectDevtoolsLog.forEach(msg => this.emit('notification', msg));
|
||||
}
|
||||
sendCommand(method) {
|
||||
const resolve = Promise.resolve();
|
||||
|
||||
// If navigating, wait, then replay devtools log in parallel to resolve.
|
||||
if (method === 'Page.navigate') {
|
||||
resolve.then(delay).then(_ => this.replayLog());
|
||||
}
|
||||
|
||||
return resolve;
|
||||
}
|
||||
}
|
||||
const replayConnection = new ReplayConnection();
|
||||
const driver = new Driver(replayConnection);
|
||||
|
||||
// Redirect in log will go through
|
||||
const startUrl = 'http://en.wikipedia.org/';
|
||||
// then https://en.wikipedia.org/
|
||||
// then https://en.wikipedia.org/wiki/Main_Page
|
||||
const finalUrl = 'https://en.m.wikipedia.org/wiki/Main_Page';
|
||||
|
||||
const loadOptions = {
|
||||
waitForLoad: true,
|
||||
flags: {
|
||||
pauseAfterLoad: 1
|
||||
}
|
||||
};
|
||||
|
||||
return driver.gotoURL(startUrl, loadOptions).then(loadedUrl => {
|
||||
assert.equal(loadedUrl, finalUrl);
|
||||
});
|
||||
|
||||
// The above event is handled synchronously by enableUrlUpdateIfRedirected and will be all set
|
||||
assert.notEqual(opts.url, req1.url, 'opts.url changed after the redirects');
|
||||
assert.equal(opts.url, req3.url, 'opts.url matches the last redirect');
|
||||
});
|
||||
|
||||
it('will request default traceCategories', () => {
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const recordsFromLogs = require('../../lib/network-recorder').recordsFromLogs;
|
||||
|
||||
module.exports = {
|
||||
getUserAgent() {
|
||||
return Promise.resolve('Fake user agent');
|
||||
|
@ -29,7 +27,7 @@ module.exports = {
|
|||
return Promise.resolve();
|
||||
},
|
||||
gotoURL() {
|
||||
return Promise.resolve();
|
||||
return Promise.resolve('https://example.com');
|
||||
},
|
||||
beginEmulation() {
|
||||
return Promise.resolve();
|
||||
|
@ -62,13 +60,8 @@ module.exports = {
|
|||
require('../fixtures/traces/progressive-app.json')
|
||||
);
|
||||
},
|
||||
beginNetworkCollect() {},
|
||||
endNetworkCollect() {
|
||||
return Promise.resolve(
|
||||
recordsFromLogs(require('../fixtures/perflog.json'))
|
||||
);
|
||||
},
|
||||
get devtoolsLog() {
|
||||
beginDevtoolsLog() {},
|
||||
endDevtoolsLog() {
|
||||
return require('../fixtures/perflog.json');
|
||||
},
|
||||
getSecurityState() {
|
||||
|
|
|
@ -23,7 +23,6 @@ const GatherRunner = require('../../gather/gather-runner');
|
|||
const assert = require('assert');
|
||||
const Config = require('../../config/config');
|
||||
const path = require('path');
|
||||
const recordsFromLogs = require('../../lib/network-recorder').recordsFromLogs;
|
||||
const unresolvedPerfLog = require('./../fixtures/unresolved-perflog.json');
|
||||
|
||||
class TestGatherer extends Gatherer {
|
||||
|
@ -92,21 +91,23 @@ function getMockedEmulationDriver(emulationFn, netThrottleFn, cpuThrottleFn, blo
|
|||
}
|
||||
|
||||
describe('GatherRunner', function() {
|
||||
it('loads a page', () => {
|
||||
it('loads a page and updates URL on redirect', () => {
|
||||
const url1 = 'https://example.com';
|
||||
const url2 = 'https://example.com/interstitial';
|
||||
const driver = {
|
||||
gotoURL() {
|
||||
return Promise.resolve(true);
|
||||
return Promise.resolve(url2);
|
||||
},
|
||||
beginNetworkCollect() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
return GatherRunner.loadPage(driver, {
|
||||
const options = {
|
||||
url: url1,
|
||||
flags: {},
|
||||
config: {}
|
||||
}).then(res => {
|
||||
assert.equal(res, true);
|
||||
};
|
||||
|
||||
return GatherRunner.loadPage(driver, options).then(_ => {
|
||||
assert.equal(options.url, url2);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -328,20 +329,22 @@ describe('GatherRunner', function() {
|
|||
calledTrace = true;
|
||||
return Promise.resolve();
|
||||
},
|
||||
beginDevtoolsLog() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
gotoURL() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
beginNetworkCollect() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
const config = {
|
||||
recordTrace: true,
|
||||
gatherers: [{}]
|
||||
gatherers: [
|
||||
new TestGatherer()
|
||||
]
|
||||
};
|
||||
|
||||
return GatherRunner.loadPage(driver, {config}).then(_ => {
|
||||
return GatherRunner.pass({driver, config}, {TestGatherer: []}).then(_ => {
|
||||
assert.equal(calledTrace, true);
|
||||
});
|
||||
});
|
||||
|
@ -371,11 +374,11 @@ describe('GatherRunner', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('tells the driver to begin network collection', () => {
|
||||
let calledNetworkCollect = false;
|
||||
it('tells the driver to begin devtoolsLog collection', () => {
|
||||
let calledDevtoolsLogCollect = false;
|
||||
const driver = {
|
||||
beginNetworkCollect() {
|
||||
calledNetworkCollect = true;
|
||||
beginDevtoolsLog() {
|
||||
calledDevtoolsLogCollect = true;
|
||||
return Promise.resolve();
|
||||
},
|
||||
gotoURL() {
|
||||
|
@ -384,26 +387,27 @@ describe('GatherRunner', function() {
|
|||
};
|
||||
|
||||
const config = {
|
||||
gatherers: [{}]
|
||||
gatherers: [
|
||||
new TestGatherer()
|
||||
]
|
||||
};
|
||||
|
||||
return GatherRunner.loadPage(driver, {config}).then(_ => {
|
||||
assert.equal(calledNetworkCollect, true);
|
||||
return GatherRunner.pass({driver, config}, {TestGatherer: []}).then(_ => {
|
||||
assert.equal(calledDevtoolsLogCollect, true);
|
||||
});
|
||||
});
|
||||
|
||||
it('tells the driver to end network collection', () => {
|
||||
it('tells the driver to end devtoolsLog collection', () => {
|
||||
const url = 'https://example.com';
|
||||
let calledNetworkCollect = false;
|
||||
let calledDevtoolsLogCollect = false;
|
||||
|
||||
const fakeDevtoolsMessage = {method: 'Network.FakeThing', params: {}};
|
||||
const driver = Object.assign({}, fakeDriver, {
|
||||
endNetworkCollect() {
|
||||
calledNetworkCollect = true;
|
||||
return fakeDriver.endNetworkCollect()
|
||||
.then(records => {
|
||||
records.marker = 'mocked';
|
||||
return records;
|
||||
});
|
||||
endDevtoolsLog() {
|
||||
calledDevtoolsLogCollect = true;
|
||||
return [
|
||||
fakeDevtoolsMessage
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -414,8 +418,8 @@ describe('GatherRunner', function() {
|
|||
};
|
||||
|
||||
return GatherRunner.afterPass({url, driver, config}, {TestGatherer: []}).then(vals => {
|
||||
assert.equal(calledNetworkCollect, true);
|
||||
assert.strictEqual(vals.networkRecords.marker, 'mocked');
|
||||
assert.equal(calledDevtoolsLogCollect, true);
|
||||
assert.strictEqual(vals.devtoolsLog[0], fakeDevtoolsMessage);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -492,13 +496,11 @@ describe('GatherRunner', function() {
|
|||
it('doesn\'t leave networkRecords as an artifact', () => {
|
||||
const passes = [{
|
||||
blankDuration: 0,
|
||||
recordNetwork: true,
|
||||
recordTrace: true,
|
||||
passName: 'firstPass',
|
||||
gatherers: [new TestGatherer()]
|
||||
}, {
|
||||
blankDuration: 0,
|
||||
recordNetwork: true,
|
||||
recordTrace: true,
|
||||
passName: 'secondPass',
|
||||
gatherers: [new TestGatherer()]
|
||||
|
@ -827,16 +829,20 @@ describe('GatherRunner', function() {
|
|||
}];
|
||||
|
||||
// Arrange for driver to return unresolved request.
|
||||
const url = 'http://www.some-non-existing-domain.com/';
|
||||
const unresolvedDriver = Object.assign({}, fakeDriver, {
|
||||
online: true,
|
||||
endNetworkCollect() {
|
||||
return Promise.resolve(recordsFromLogs(unresolvedPerfLog));
|
||||
}
|
||||
gotoURL() {
|
||||
return Promise.resolve(url);
|
||||
},
|
||||
endDevtoolsLog() {
|
||||
return unresolvedPerfLog;
|
||||
},
|
||||
});
|
||||
|
||||
return GatherRunner.run(passes, {
|
||||
driver: unresolvedDriver,
|
||||
url: 'http://www.some-non-existing-domain.com/',
|
||||
url,
|
||||
flags: {},
|
||||
config: new Config({})
|
||||
})
|
||||
|
@ -857,16 +863,20 @@ describe('GatherRunner', function() {
|
|||
}];
|
||||
|
||||
// Arrange for driver to return unresolved request.
|
||||
const url = 'http://www.some-non-existing-domain.com/';
|
||||
const unresolvedDriver = Object.assign({}, fakeDriver, {
|
||||
online: false,
|
||||
endNetworkCollect() {
|
||||
return Promise.resolve(recordsFromLogs(unresolvedPerfLog));
|
||||
gotoURL() {
|
||||
return Promise.resolve(url);
|
||||
},
|
||||
endDevtoolsLog() {
|
||||
return unresolvedPerfLog;
|
||||
}
|
||||
});
|
||||
|
||||
return GatherRunner.run(passes, {
|
||||
driver: unresolvedDriver,
|
||||
url: 'http://www.some-non-existing-domain.com/',
|
||||
url,
|
||||
flags: {},
|
||||
config: new Config({})
|
||||
})
|
||||
|
|
|
@ -20,14 +20,16 @@
|
|||
const ResponseCompression =
|
||||
require('../../../../gather/gatherers/dobetterweb/response-compression');
|
||||
const assert = require('assert');
|
||||
const mockDriver = require('../../fake-driver.js');
|
||||
|
||||
let options;
|
||||
let optimizedResponses;
|
||||
let responseCompression;
|
||||
const traceData = {
|
||||
networkRecords: [
|
||||
{
|
||||
_url: 'http://google.com/index.js',
|
||||
_mimeType: 'text/javascript',
|
||||
_requestId: 0,
|
||||
_resourceSize: 9,
|
||||
_resourceType: {
|
||||
_isTextType: true,
|
||||
|
@ -41,6 +43,7 @@ const traceData = {
|
|||
{
|
||||
_url: 'http://google.com/index.css',
|
||||
_mimeType: 'text/css',
|
||||
_requestId: 1,
|
||||
_resourceSize: 6,
|
||||
_resourceType: {
|
||||
_isTextType: true,
|
||||
|
@ -51,6 +54,7 @@ const traceData = {
|
|||
{
|
||||
_url: 'http://google.com/index.json',
|
||||
_mimeType: 'application/json',
|
||||
_requestId: 2,
|
||||
_resourceSize: 7,
|
||||
_resourceType: {
|
||||
_isTextType: true,
|
||||
|
@ -61,6 +65,7 @@ const traceData = {
|
|||
{
|
||||
_url: 'http://google.com/index.jpg',
|
||||
_mimeType: 'images/jpg',
|
||||
_requestId: 3,
|
||||
_resourceSize: 10,
|
||||
_resourceType: {
|
||||
_isTextType: false,
|
||||
|
@ -74,14 +79,21 @@ const traceData = {
|
|||
describe('Optimized responses', () => {
|
||||
// Reset the Gatherer before each test.
|
||||
beforeEach(() => {
|
||||
optimizedResponses = new ResponseCompression();
|
||||
responseCompression = new ResponseCompression();
|
||||
const driver = Object.assign({}, mockDriver, {
|
||||
getRequestContent(id) {
|
||||
return Promise.resolve(traceData.networkRecords[id].content);
|
||||
},
|
||||
});
|
||||
|
||||
options = {
|
||||
url: 'http://google.com/',
|
||||
driver,
|
||||
};
|
||||
});
|
||||
|
||||
it('returns only text and non encoded responses', () => {
|
||||
return optimizedResponses.afterPass(options, createNetworkRequests(traceData))
|
||||
return responseCompression.afterPass(options, createNetworkRequests(traceData))
|
||||
.then(artifact => {
|
||||
assert.equal(artifact.length, 2);
|
||||
assert.ok(/index\.css$/.test(artifact[0].url));
|
||||
|
@ -90,7 +102,7 @@ describe('Optimized responses', () => {
|
|||
});
|
||||
|
||||
it('computes sizes', () => {
|
||||
return optimizedResponses.afterPass(options, createNetworkRequests(traceData))
|
||||
return responseCompression.afterPass(options, createNetworkRequests(traceData))
|
||||
.then(artifact => {
|
||||
assert.equal(artifact.length, 2);
|
||||
assert.equal(artifact[0].resourceSize, 6);
|
||||
|
@ -98,14 +110,14 @@ describe('Optimized responses', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// Change into SDK.networkRequest when examples are ready
|
||||
// Change into SDK.networkRequest when examples are ready
|
||||
function createNetworkRequests(traceData) {
|
||||
traceData.networkRecords = traceData.networkRecords.map(record => {
|
||||
record.url = record._url;
|
||||
record.mimeType = record._mimeType;
|
||||
record.resourceSize = record._resourceSize;
|
||||
record.responseHeaders = record._responseHeaders;
|
||||
record.requestContent = () => Promise.resolve(record.content);
|
||||
record.requestId = record._requestId;
|
||||
record.resourceType = () => {
|
||||
return Object.assign(
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче