feat(network): ignore favicon requests - these are too unpredictable (#533)

This commit is contained in:
Dmitry Gozman 2020-01-17 17:14:39 -08:00 committed by Yury Semikhatsky
parent 879ce66f5e
commit bb3f12245c
7 changed files with 42 additions and 55 deletions

View file

@ -3565,6 +3565,8 @@ Browser websocket endpoint which can be used as an argument to [firefoxPlaywrigh
#### webkitPlaywright.defaultArgs([options]) #### webkitPlaywright.defaultArgs([options])
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields: - `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
- `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
- `userDataDir` <[string]> Path to a User Data Directory, which stores browser session data like cookies and local storage.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance.
- returns: <[Array]<[string]>> - returns: <[Array]<[string]>>
@ -3575,6 +3577,7 @@ The default flags that WebKit will be launched with.
- `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`. - `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
- `executablePath` <[string]> Path to a WebKit executable to run instead of the bundled WebKit. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled WebKit, use at your own risk. - `executablePath` <[string]> Path to a WebKit executable to run instead of the bundled WebKit. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled WebKit, use at your own risk.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. - `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `userDataDir` <[string]> Path to a User Data Directory, which stores browser session data like cookies and local storage.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance.
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`webKitPlaywright.defaultArgs()`](#webkitplaywrightdefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`. - `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`webKitPlaywright.defaultArgs()`](#webkitplaywrightdefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`. - `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
@ -3598,6 +3601,7 @@ const browser = await playwright.launch({
- `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`. - `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
- `executablePath` <[string]> Path to a WebKit executable to run instead of the bundled WebKit. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled WebKit, use at your own risk. - `executablePath` <[string]> Path to a WebKit executable to run instead of the bundled WebKit. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled WebKit, use at your own risk.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. - `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `userDataDir` <[string]> Path to a User Data Directory, which stores browser session data like cookies and local storage.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance.
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`webKitPlaywright.defaultArgs()`](#webkitplaywrightdefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`. - `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`webKitPlaywright.defaultArgs()`](#webkitplaywrightdefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`. - `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.

View file

@ -191,16 +191,19 @@ export class FrameManager {
for (const watcher of this._lifecycleWatchers) for (const watcher of this._lifecycleWatchers)
watcher._onNavigationRequest(frame, request); watcher._onNavigationRequest(frame, request);
} }
this._page.emit(Events.Page.Request, request); if (!request._isFavicon)
this._page.emit(Events.Page.Request, request);
} }
requestReceivedResponse(response: network.Response) { requestReceivedResponse(response: network.Response) {
this._page.emit(Events.Page.Response, response); if (!response.request()._isFavicon)
this._page.emit(Events.Page.Response, response);
} }
requestFinished(request: network.Request) { requestFinished(request: network.Request) {
this._inflightRequestFinished(request); this._inflightRequestFinished(request);
this._page.emit(Events.Page.RequestFinished, request); if (!request._isFavicon)
this._page.emit(Events.Page.RequestFinished, request);
} }
requestFailed(request: network.Request, canceled: boolean) { requestFailed(request: network.Request, canceled: boolean) {
@ -216,7 +219,8 @@ export class FrameManager {
watcher._onAbortedNewDocumentNavigation(frame, request._documentId, errorText); watcher._onAbortedNewDocumentNavigation(frame, request._documentId, errorText);
} }
} }
this._page.emit(Events.Page.RequestFailed, request); if (!request._isFavicon)
this._page.emit(Events.Page.RequestFailed, request);
} }
provisionalLoadFailed(documentId: string, error: string) { provisionalLoadFailed(documentId: string, error: string) {
@ -236,7 +240,7 @@ export class FrameManager {
private _inflightRequestFinished(request: network.Request) { private _inflightRequestFinished(request: network.Request) {
const frame = request.frame(); const frame = request.frame();
if (!frame || request.url().endsWith('favicon.ico')) if (!frame || request._isFavicon)
return; return;
if (!frame._inflightRequests.has(request)) if (!frame._inflightRequests.has(request))
return; return;
@ -249,7 +253,7 @@ export class FrameManager {
private _inflightRequestStarted(request: network.Request) { private _inflightRequestStarted(request: network.Request) {
const frame = request.frame(); const frame = request.frame();
if (!frame || request.url().endsWith('favicon.ico')) if (!frame || request._isFavicon)
return; return;
frame._inflightRequests.add(request); frame._inflightRequests.add(request);
if (frame._inflightRequests.size === 1) if (frame._inflightRequests.size === 1)

View file

@ -97,6 +97,7 @@ export class Request {
_redirectChain: Request[]; _redirectChain: Request[];
_finalRequest: Request; _finalRequest: Request;
readonly _documentId?: string; readonly _documentId?: string;
readonly _isFavicon: boolean;
private _failureText: string | null = null; private _failureText: string | null = null;
private _url: string; private _url: string;
private _resourceType: string; private _resourceType: string;
@ -127,6 +128,7 @@ export class Request {
this._headers = headers; this._headers = headers;
this._waitForResponsePromise = new Promise(f => this._waitForResponsePromiseCallback = f); this._waitForResponsePromise = new Promise(f => this._waitForResponsePromiseCallback = f);
this._waitForFinishedPromise = new Promise(f => this._waitForFinishedPromiseCallback = f); this._waitForFinishedPromise = new Promise(f => this._waitForFinishedPromiseCallback = f);
this._isFavicon = url.endsWith('/favicon.ico');
} }
_setFailureText(failureText: string) { _setFailureText(failureText: string) {

View file

@ -28,10 +28,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should intercept', async({page, server}) => { it('should intercept', async({page, server}) => {
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', request => { page.on('request', request => {
if (utils.isFavicon(request)) {
request.continue();
return;
}
expect(request.url()).toContain('empty.html'); expect(request.url()).toContain('empty.html');
expect(request.headers()['user-agent']).toBeTruthy(); expect(request.headers()['user-agent']).toBeTruthy();
expect(request.method()).toBe('GET'); expect(request.method()).toBe('GET');
@ -94,8 +90,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests = [];
page.on('request', request => { page.on('request', request => {
if (!utils.isFavicon(request)) requests.push(request);
requests.push(request);
request.continue(); request.continue();
}); });
await page.goto(server.PREFIX + '/one-style.html'); await page.goto(server.PREFIX + '/one-style.html');
@ -217,8 +212,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const requests = []; const requests = [];
page.on('request', request => { page.on('request', request => {
request.continue(); request.continue();
if (!utils.isFavicon(request)) requests.push(request);
requests.push(request);
}); });
server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html'); server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html');
server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html'); server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html');
@ -245,8 +239,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const requests = []; const requests = [];
page.on('request', request => { page.on('request', request => {
request.continue(); request.continue();
if (!utils.isFavicon(request)) requests.push(request);
requests.push(request);
}); });
server.setRedirect('/one-style.css', '/two-style.css'); server.setRedirect('/one-style.css', '/two-style.css');
server.setRedirect('/two-style.css', '/three-style.css'); server.setRedirect('/two-style.css', '/three-style.css');
@ -274,10 +267,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
let spinner = false; let spinner = false;
// Cancel 2nd request. // Cancel 2nd request.
page.on('request', request => { page.on('request', request => {
if (utils.isFavicon(request)) {
request.continue();
return;
}
spinner ? request.abort() : request.continue(); spinner ? request.abort() : request.continue();
spinner = !spinner; spinner = !spinner;
}); });
@ -292,8 +281,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests = [];
page.on('request', request => { page.on('request', request => {
if (!utils.isFavicon(request)) requests.push(request);
requests.push(request);
request.continue(); request.continue();
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
@ -306,8 +294,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests = [];
page.on('request', request => { page.on('request', request => {
if (!utils.isFavicon(request)) requests.push(request);
requests.push(request);
request.continue(); request.continue();
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
@ -319,8 +306,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setRequestInterception(true); await page.setRequestInterception(true);
const requests = []; const requests = [];
page.on('request', request => { page.on('request', request => {
if (!utils.isFavicon(request)) requests.push(request);
requests.push(request);
request.continue(); request.continue();
}); });
const response = await page.goto(server.EMPTY_PAGE + '#hash'); const response = await page.goto(server.EMPTY_PAGE + '#hash');
@ -351,8 +337,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const requests = []; const requests = [];
page.on('request', request => { page.on('request', request => {
request.continue(); request.continue();
if (!utils.isFavicon(request)) requests.push(request);
requests.push(request);
}); });
const response = await page.goto(`data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>`); const response = await page.goto(`data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>`);
expect(response).toBe(null); expect(response).toBe(null);

View file

@ -280,7 +280,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
}); });
it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => {
const requests = []; const requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const response = await page.goto(dataURL); const response = await page.goto(dataURL);
expect(response).toBe(null); expect(response).toBe(null);
@ -288,7 +288,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
}); });
it('should navigate to URL with hash and fire requests without hash', async({page, server}) => { it('should navigate to URL with hash and fire requests without hash', async({page, server}) => {
const requests = []; const requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
const response = await page.goto(server.EMPTY_PAGE + '#hash'); const response = await page.goto(server.EMPTY_PAGE + '#hash');
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.url()).toBe(server.EMPTY_PAGE); expect(response.url()).toBe(server.EMPTY_PAGE);

View file

@ -27,20 +27,20 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
describe('Page.Events.Request', function() { describe('Page.Events.Request', function() {
it('should fire for navigation requests', async({page, server}) => { it('should fire for navigation requests', async({page, server}) => {
const requests = []; const requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
}); });
it('should fire for iframes', async({page, server}) => { it('should fire for iframes', async({page, server}) => {
const requests = []; const requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(2); expect(requests.length).toBe(2);
}); });
it('should fire for fetches', async({page, server}) => { it('should fire for fetches', async({page, server}) => {
const requests = []; const requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => fetch('/empty.html')); await page.evaluate(() => fetch('/empty.html'));
expect(requests.length).toBe(2); expect(requests.length).toBe(2);
@ -50,7 +50,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
describe('Request.frame', function() { describe('Request.frame', function() {
it('should work for main frame navigation request', async({page, server}) => { it('should work for main frame navigation request', async({page, server}) => {
const requests = []; const requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame()); expect(requests[0].frame()).toBe(page.mainFrame());
@ -58,7 +58,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
it('should work for subframe navigation request', async({page, server}) => { it('should work for subframe navigation request', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const requests = []; const requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.frames()[1]); expect(requests[0].frame()).toBe(page.frames()[1]);
@ -66,7 +66,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
it('should work for fetch requests', async({page, server}) => { it('should work for fetch requests', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
let requests = []; let requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
await page.evaluate(() => fetch('/digits/1.png')); await page.evaluate(() => fetch('/digits/1.png'));
requests = requests.filter(request => !request.url().includes('favicon')); requests = requests.filter(request => !request.url().includes('favicon'));
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
@ -151,7 +151,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
page.on('requestfinished', r => requestFinished = requestFinished || r.url().includes('/get')); page.on('requestfinished', r => requestFinished = requestFinished || r.url().includes('/get'));
// send request and wait for server response // send request and wait for server response
const [pageResponse] = await Promise.all([ const [pageResponse] = await Promise.all([
page.waitForEvent('response', { predicate: r => !utils.isFavicon(r.request()) }), page.waitForEvent('response'),
page.evaluate(() => fetch('./get', { method: 'GET'})), page.evaluate(() => fetch('./get', { method: 'GET'})),
server.waitForRequest('/get'), server.waitForRequest('/get'),
]); ]);
@ -207,7 +207,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
describe('Network Events', function() { describe('Network Events', function() {
it('Page.Events.Request', async({page, server}) => { it('Page.Events.Request', async({page, server}) => {
const requests = []; const requests = [];
page.on('request', request => !utils.isFavicon(request) && requests.push(request)); page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE); expect(requests[0].url()).toBe(server.EMPTY_PAGE);
@ -220,7 +220,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
// FIXME: WebKit doesn't provide remoteIPAddress in the response. // FIXME: WebKit doesn't provide remoteIPAddress in the response.
it.skip(WEBKIT)('Page.Events.Response', async({page, server}) => { it.skip(WEBKIT)('Page.Events.Response', async({page, server}) => {
const responses = []; const responses = [];
page.on('response', response => !utils.isFavicon(response.request()) && responses.push(response)); page.on('response', response => responses.push(response));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(responses.length).toBe(1); expect(responses.length).toBe(1);
expect(responses[0].url()).toBe(server.EMPTY_PAGE); expect(responses[0].url()).toBe(server.EMPTY_PAGE);
@ -261,7 +261,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
}); });
it('Page.Events.RequestFinished', async({page, server}) => { it('Page.Events.RequestFinished', async({page, server}) => {
const requests = []; const requests = [];
page.on('requestfinished', request => !utils.isFavicon(request) && requests.push(request)); page.on('requestfinished', request => requests.push(request));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1); expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE); expect(requests[0].url()).toBe(server.EMPTY_PAGE);
@ -271,18 +271,18 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
}); });
it('should fire events in proper order', async({page, server}) => { it('should fire events in proper order', async({page, server}) => {
const events = []; const events = [];
page.on('request', request => !utils.isFavicon(request) && events.push('request')); page.on('request', request => events.push('request'));
page.on('response', response => !utils.isFavicon(response.request()) && events.push('response')); page.on('response', response => events.push('response'));
page.on('requestfinished', request => !utils.isFavicon(request) && events.push('requestfinished')); page.on('requestfinished', request => events.push('requestfinished'));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(events).toEqual(['request', 'response', 'requestfinished']); expect(events).toEqual(['request', 'response', 'requestfinished']);
}); });
it('should support redirects', async({page, server}) => { it('should support redirects', async({page, server}) => {
const events = []; const events = [];
page.on('request', request => !utils.isFavicon(request) && events.push(`${request.method()} ${request.url()}`)); page.on('request', request => events.push(`${request.method()} ${request.url()}`));
page.on('response', response => !utils.isFavicon(response.request()) && events.push(`${response.status()} ${response.url()}`)); page.on('response', response => events.push(`${response.status()} ${response.url()}`));
page.on('requestfinished', request => !utils.isFavicon(request) && events.push(`DONE ${request.url()}`)); page.on('requestfinished', request => events.push(`DONE ${request.url()}`));
page.on('requestfailed', request => !utils.isFavicon(request) && events.push(`FAIL ${request.url()}`)); page.on('requestfailed', request => events.push(`FAIL ${request.url()}`));
server.setRedirect('/foo.html', '/empty.html'); server.setRedirect('/foo.html', '/empty.html');
const FOO_URL = server.PREFIX + '/foo.html'; const FOO_URL = server.PREFIX + '/foo.html';
const response = await page.goto(FOO_URL); const response = await page.goto(FOO_URL);

View file

@ -122,19 +122,11 @@ const utils = module.exports = {
frame.src = url; frame.src = url;
frame.id = frameId; frame.id = frameId;
document.body.appendChild(frame); document.body.appendChild(frame);
// TODO(einbinder) do this right
// Access a scriptable global object to ensure JS context is
// initialized. WebKit will create it lazily only as need be.
frame.contentWindow;
await new Promise(x => frame.onload = x); await new Promise(x => frame.onload = x);
return frame; return frame;
} }
}, },
isFavicon: function(request) {
return request.url().includes('favicon.ico');
},
/** /**
* @param {!Page} page * @param {!Page} page
* @param {string} frameId * @param {string} frameId