blob: b949cec7146c8d83cc263f163de256b3c56d0c00 [file] [log] [blame]
// Copyright 2021 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.
import {assert} from 'chai';
import {type ElementHandle} from 'puppeteer';
import {expectError} from '../../conductor/events.js';
import {
$,
$$,
click,
enableExperiment,
step,
typeText,
waitFor,
waitForAria,
waitForElementWithTextContent,
waitForFunction,
getBrowserAndPages,
getResourcesPath,
assertNotNullOrUndefined,
} from '../../shared/helper.js';
import {describe, it} from '../../shared/mocha-extensions.js';
import {CONSOLE_TAB_SELECTOR, focusConsolePrompt} from '../helpers/console-helpers.js';
import {triggerLocalFindDialog} from '../helpers/memory-helpers.js';
import {
getAllRequestNames,
navigateToNetworkTab,
selectRequestByName,
waitForSomeRequestsToAppear,
} from '../helpers/network-helpers.js';
const SIMPLE_PAGE_REQUEST_NUMBER = 2;
const SIMPLE_PAGE_URL = `requests.html?num=${SIMPLE_PAGE_REQUEST_NUMBER}`;
describe('The Network Request view', async () => {
it('re-opens the same tab after switching to another panel and navigating back to the "Network" tab (https://crbug.com/1184578)',
async () => {
await navigateToNetworkTab(SIMPLE_PAGE_URL);
await step('wait for all requests to be shown', async () => {
await waitForSomeRequestsToAppear(SIMPLE_PAGE_REQUEST_NUMBER + 1);
});
await step('select the first SVG request', async () => {
await selectRequestByName('image.svg?id=0');
});
await step('select the "Timing" tab', async () => {
const networkView = await waitFor('.network-item-view');
const timingTabHeader = await waitFor('[aria-label=Timing][role="tab"]', networkView);
await click(timingTabHeader);
await waitFor('[aria-label=Timing][role=tab][aria-selected=true]', networkView);
});
await step('open the "Console" panel', async () => {
await click(CONSOLE_TAB_SELECTOR);
await focusConsolePrompt();
});
await step('open the "Network" panel', async () => {
await click('#tab-network');
await waitFor('.network-log-grid');
});
await step('ensure that the "Timing" tab is shown', async () => {
const networkView = await waitFor('.network-item-view');
const selectedTabHeader = await waitFor('[role=tab][aria-selected=true]', networkView);
const selectedTabText = await selectedTabHeader.evaluate(element => element.textContent || '');
assert.strictEqual(selectedTabText, 'Timing');
});
});
it('shows webbundle content on preview tab', async () => {
await navigateToNetworkTab('resources-from-webbundle.html');
await waitForSomeRequestsToAppear(3);
await selectRequestByName('webbundle.wbn');
const networkView = await waitFor('.network-item-view');
const previewTabHeader = await waitFor('[aria-label=Preview][role=tab]', networkView);
await click(previewTabHeader);
await waitFor('[aria-label=Preview][role=tab][aria-selected=true]', networkView);
await waitForElementWithTextContent('webbundle.wbn', networkView);
await waitForElementWithTextContent('uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae', networkView);
await waitForElementWithTextContent('uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720', networkView);
});
it('prevents requests on the preview tab.', async () => {
await navigateToNetworkTab('embedded_requests.html');
// For the issue to manifest it's mandatory to load the stylesheet by absolute URL. A relative URL would be treated
// relative to the data URL in the preview iframe and thus not work. We need to generate the URL because the
// resources path is dynamic, but we can't have any scripts in the resource page since they would be disabled in the
// preview. Therefore, the resource page contains just an iframe and we're filling it dynamically with content here.
const stylesheet = `${getResourcesPath()}/network/style.css`;
const contents = `<head><link rel="stylesheet" href="${stylesheet}"></head><body><p>Content</p></body>`;
const {target} = getBrowserAndPages();
await waitForFunction(async () => (await target.$('iframe')) ?? undefined);
const dataUrl = `data:text/html,${contents}`;
await target.evaluate((dataUrl: string) => {
(document.querySelector('iframe') as HTMLIFrameElement).src = dataUrl;
}, dataUrl);
await waitForSomeRequestsToAppear(3);
const names = await getAllRequestNames();
const name = names.find(v => v && v.startsWith('data:'));
assertNotNullOrUndefined(name);
await selectRequestByName(name);
const styleSrcError = expectError(`Refused to load the stylesheet '${stylesheet}'`);
const networkView = await waitFor('.network-item-view');
const previewTabHeader = await waitFor('[aria-label=Preview][role=tab]', networkView);
await click(previewTabHeader);
await waitFor('[aria-label=Preview][role=tab][aria-selected=true]', networkView);
const frame = await waitFor('.html-preview-frame');
const content = await waitForFunction(async () => (await frame.contentFrame() ?? undefined));
const p = await waitForFunction(async () => (await content.$('p') ?? undefined));
const color = await p.evaluate(e => getComputedStyle(e).color);
assert.deepEqual(color, 'rgb(0, 0, 0)');
await waitForFunction(async () => await styleSrcError.caught);
});
it('permits inline styles on the preview tab.', async () => {
await navigateToNetworkTab('embedded_requests.html');
const contents = '<head><style>p { color: red; }</style></head><body><p>Content</p></body>';
const {target} = getBrowserAndPages();
await waitForFunction(async () => (await target.$('iframe')) ?? undefined);
const dataUrl = `data:text/html,${contents}`;
await target.evaluate((dataUrl: string) => {
(document.querySelector('iframe') as HTMLIFrameElement).src = dataUrl;
}, dataUrl);
await waitForSomeRequestsToAppear(2);
const names = await getAllRequestNames();
const name = names.find(v => v && v.startsWith('data:'));
assertNotNullOrUndefined(name);
await selectRequestByName(name);
const networkView = await waitFor('.network-item-view');
const previewTabHeader = await waitFor('[aria-label=Preview][role=tab]', networkView);
await click(previewTabHeader);
await waitFor('[aria-label=Preview][role=tab][aria-selected=true]', networkView);
const frame = await waitFor('.html-preview-frame');
const content = await waitForFunction(async () => (await frame.contentFrame() ?? undefined));
const p = await waitForFunction(async () => (await content.$('p') ?? undefined));
const color = await p.evaluate(e => getComputedStyle(e).color);
assert.deepEqual(color, 'rgb(255, 0, 0)');
});
it('stores websocket filter', async () => {
const navigateToWebsocketMessages = async () => {
await navigateToNetworkTab('websocket.html');
await waitForSomeRequestsToAppear(2);
await selectRequestByName('localhost');
const networkView = await waitFor('.network-item-view');
const messagesTabHeader = await waitFor('[aria-label=Messages][role=tab]', networkView);
await click(messagesTabHeader);
await waitFor('[aria-label=Messages][role=tab][aria-selected=true]', networkView);
return waitFor('.websocket-frame-view');
};
let messagesView = await navigateToWebsocketMessages();
const waitForMessages = async (count: number) => {
return waitForFunction(async () => {
const messages = await $$('.data-column.websocket-frame-view-td', messagesView);
if (messages.length !== count) {
return undefined;
}
return Promise.all(messages.map(message => {
return message.evaluate(message => message.textContent || '');
}));
});
};
let messages = await waitForMessages(4);
const filterInput =
await waitFor('[aria-label="Enter regex, for example: (web)?socket"][role=textbox]', messagesView);
filterInput.focus();
void typeText('ping');
messages = await waitForMessages(2);
assert.deepEqual(messages, ['ping', 'ping']);
messagesView = await navigateToWebsocketMessages();
messages = await waitForMessages(2);
assert.deepEqual(messages, ['ping', 'ping']);
});
async function assertOutlineMatches(expectedPatterns: string[], outline: ElementHandle<Element>[]) {
const regexpSpecialChars = /[-\/\\^$*+?.()|[\]{}]/g;
for (const item of outline) {
const actualText = await item.evaluate(el => el.textContent || '');
const expectedPattern = expectedPatterns.shift();
if (expectedPattern) {
assert.match(actualText, new RegExp(expectedPattern.replace(regexpSpecialChars, '\\$&').replace(/%/g, '.*')));
} else {
assert.fail('Unexpected text: ' + actualText);
}
}
}
it('shows request headers and payload', async () => {
await navigateToNetworkTab('headers-and-payload.html');
await waitForSomeRequestsToAppear(2);
await selectRequestByName('image.svg?id=42&param=a%20b');
const networkView = await waitFor('.network-item-view');
const headersTabHeader = await waitFor('[aria-label=Headers][role="tab"]', networkView);
await click(headersTabHeader);
await waitFor('[aria-label=Headers][role=tab][aria-selected=true]', networkView);
const headersView = await waitFor('.request-headers-view');
const headersOutline = await $$('[role=treeitem]:not(.hidden)', headersView);
const expectedHeadersContent = [
'General',
[
'Request URL: https://localhost:%/test/e2e/resources/network/image.svg?id=42&param=a%20b',
'Request Method: POST',
'Status Code: 200 OK',
'Remote Address: [::1]:%',
'Referrer Policy: strict-origin-when-cross-origin',
],
'Response Headers (7)View source',
[
'Cache-Control: max-age=%',
'Connection: keep-alive',
'Content-Type: image/svg+xml; charset=utf-8',
'Date: %',
'Keep-Alive: timeout=5',
'Transfer-Encoding: chunked',
'Vary: Origin',
],
'Request Headers (17)View source',
[
'accept: */*',
'Accept-Encoding: gzip, deflate, br',
'Accept-Language: en-US',
'Connection: keep-alive',
'Content-Length: 32',
'content-type: application/x-www-form-urlencoded;charset=UTF-8',
'Host: localhost:%',
'Origin: https://localhost:%',
'Referer: https://localhost:%/test/e2e/resources/network/headers-and-payload.html',
'sec-ch-ua',
'sec-ch-ua-mobile: ?0',
'sec-ch-ua-platform',
'Sec-Fetch-Dest: empty',
'Sec-Fetch-Mode: cors',
'Sec-Fetch-Site: same-origin',
'User-Agent: Mozilla/5.0 %',
'x-same-domain: 1',
],
].flat();
await assertOutlineMatches(expectedHeadersContent, headersOutline);
const payloadTabHeader = await waitFor('[aria-label=Payload][role="tab"]', networkView);
await click(payloadTabHeader);
await waitFor('[aria-label=Payload][role=tab][aria-selected=true]', networkView);
const payloadView = await waitFor('.request-payload-view');
const payloadOutline = await $$('[role=treeitem]:not(.hidden)', payloadView);
const expectedPayloadContent = [
'Query String Parameters (2)view sourceview URL-encoded',
['id: 42', 'param: a b'],
'Form Data (4)view sourceview URL-encoded',
[
'foo: alpha',
'bar: beta:42:0',
'baz: ',
'(empty)',
],
].flat();
await assertOutlineMatches(expectedPayloadContent, payloadOutline);
});
it('shows raw headers', async () => {
await navigateToNetworkTab('headers-and-payload.html');
await waitForSomeRequestsToAppear(2);
await selectRequestByName('image.svg?id=42&param=a%20b');
const networkView = await waitFor('.network-item-view');
const headersTabHeader = await waitFor('[aria-label=Headers][role="tab"]', networkView);
await click(headersTabHeader);
await waitFor('[aria-label=Headers][role=tab][aria-selected=true]', networkView);
const headersView = await waitFor('.request-headers-view');
const responseHeadersTitle = await waitForElementWithTextContent('Response Headers (7)View source');
const rawHeadersToggle = await waitFor('.header-toggle', responseHeadersTitle);
await click(rawHeadersToggle);
const headersOutline = await $$('[role=treeitem]:not(.hidden)', headersView);
const expectedHeadersContent = [
'General',
[
'Request URL: https://localhost:%/test/e2e/resources/network/image.svg?id=42&param=a%20b',
'Request Method: POST',
'Status Code: 200 OK',
'Remote Address: [::1]:%',
'Referrer Policy: strict-origin-when-cross-origin',
],
'Response Headers (7)View parsed',
[
'HTTP/1.1 200 OK',
'Content-Type: image/svg+xml; charset=utf-8',
'Cache-Control: max-age=%',
'Vary: Origin',
'Date: %',
'Connection: keep-alive',
'Keep-Alive: timeout=5',
'Transfer-Encoding: chunked',
].join('\r\n'),
'Request Headers (17)View source',
[
'accept: */*',
'Accept-Encoding: gzip, deflate, br',
'Accept-Language: en-US',
'Connection: keep-alive',
'Content-Length: 32',
'content-type: application/x-www-form-urlencoded;charset=UTF-8',
'Host: localhost:%',
'Origin: https://localhost:%',
'Referer: https://localhost:%/test/e2e/resources/network/headers-and-payload.html',
'sec-ch-ua',
'sec-ch-ua-mobile: ?0',
'sec-ch-ua-platform',
'Sec-Fetch-Dest: empty',
'Sec-Fetch-Mode: cors',
'Sec-Fetch-Site: same-origin',
'User-Agent: Mozilla/5.0 %',
'x-same-domain: 1',
],
].flat();
await assertOutlineMatches(expectedHeadersContent, headersOutline);
});
it('payload tab selection is preserved', async () => {
await navigateToNetworkTab('headers-and-payload.html');
await waitForSomeRequestsToAppear(3);
await selectRequestByName('image.svg?id=42&param=a%20b');
const networkView = await waitFor('.network-item-view');
const payloadTabHeader = await waitFor('[aria-label=Payload][role="tab"]', networkView);
await click(payloadTabHeader);
await waitFor('[aria-label=Payload][role=tab][aria-selected=true]', networkView);
await selectRequestByName('image.svg');
await waitForElementWithTextContent('foo: gamma');
});
it('no duplicate payload tab on headers update', async () => {
await navigateToNetworkTab('requests.html');
const {target} = getBrowserAndPages();
target.evaluate(() => fetch('image.svg?delay'));
await waitForSomeRequestsToAppear(2);
await selectRequestByName('image.svg?delay');
await target.evaluate(async () => await fetch('/?send_delayed'));
});
it('can create header overrides', async () => {
await enableExperiment('headerOverrides');
await navigateToNetworkTab('hello.html');
await selectRequestByName('hello.html', {button: 'right'});
const createHeaderOverrideMenuEntry = await waitForAria('Create response header override');
await click(createHeaderOverrideMenuEntry);
const infoBar = await waitForAria('Select a folder to store override files in.');
const button = await waitFor('.infobar-main-row .infobar-button', infoBar);
await click(button);
await selectRequestByName('hello.html');
const networkView = await waitFor('.network-item-view');
const headersTabHeader = await waitFor('#tab-headersComponent', networkView);
await click(headersTabHeader);
await waitFor('#tab-headersComponent[role=tab][aria-selected=true]', networkView);
let responseHeaderSection = await waitFor('[aria-label="Response Headers"]', networkView);
const getTextFromRow = async (row: ElementHandle<Element>) => {
const headerNameElement = await waitFor('.header-name', row);
const headerNameText = await headerNameElement.evaluate(el => el.textContent || '');
const headerValueElement = await waitFor('.header-value', row);
const headerValueText = await headerValueElement.evaluate(el => el.textContent || '');
return [headerNameText.trim(), headerValueText.trim()];
};
let row = await waitFor('.row', responseHeaderSection);
assert.deepStrictEqual(await getTextFromRow(row), ['cache-control:', 'max-age=3600']);
await waitForFunction(async () => {
await click('.header-name', {root: row});
await click('.header-value', {root: row});
await typeText('Foo');
return (await getTextFromRow(row))[1] === 'Foo';
});
await click('[title="Reveal header override definitions"]');
const headersView = await waitFor('devtools-sources-headers-view');
const headersViewRow = await waitFor('.row.padded', headersView);
assert.deepStrictEqual(await getTextFromRow(headersViewRow), ['cache-control', 'Foo']);
await navigateToNetworkTab('hello.html');
await selectRequestByName('hello.html');
responseHeaderSection = await waitFor('[aria-label="Response Headers"]');
row = await waitFor('.row.header-overridden', responseHeaderSection);
assert.deepStrictEqual(await getTextFromRow(row), ['cache-control:', 'Foo']);
});
it('can search by headers name', async () => {
await navigateToNetworkTab('headers-and-payload.html');
await waitForSomeRequestsToAppear(2);
await selectRequestByName('image.svg?id=42&param=a%20b');
const SEARCH_QUERY = '[aria-label="Search Query"]';
const SEARCH_RESULT = '.search-result';
const {frontend} = getBrowserAndPages();
await triggerLocalFindDialog(frontend);
await waitFor(SEARCH_QUERY);
const inputElement = await $(SEARCH_QUERY);
if (!inputElement) {
assert.fail('Unable to find search input field');
}
await inputElement.focus();
await inputElement.type('Cache-Control');
await frontend.keyboard.press('Enter');
await waitFor(SEARCH_RESULT);
});
});