blob: 38ce864386594c64a5de8b1f10c74a9e1781461f [file] [log] [blame]
// Copyright 2020 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 {expectError} from '../../conductor/events.js';
import {
$$,
click,
getBrowserAndPages,
getTestServerPort,
goToResource,
pressKey,
waitFor,
waitForFunction,
} from '../../shared/helper.js';
import {
getFrameTreeTitles,
getTrimmedTextContent,
navigateToApplicationTab,
navigateToFrame,
navigateToFrameServiceWorkers,
navigateToOpenedWindows,
navigateToWebWorkers,
unregisterServiceWorker,
} from '../helpers/application-helpers.js';
import {setIgnoreListPattern} from '../helpers/settings-helpers.js';
const OPENED_WINDOWS_SELECTOR = '[aria-label="Opened Windows"]';
const EXPAND_STACKTRACE_BUTTON_SELECTOR = '.arrow-icon-button';
const STACKTRACE_ROW_SELECTOR = '.stack-trace-row';
const STACKTRACE_ROW_LINK_SELECTOR = '.stack-trace-row .link';
const APPLICATION_PANEL_SELECTED_SELECTOR = '.tabbed-pane-header-tab.selected[aria-label="Application"]';
const getTrailingURL = (text: string) => {
const match = text.match(/http.*$/);
return match ? match[0] : '';
};
const ensureApplicationPanel = async () => {
if ((await $$(APPLICATION_PANEL_SELECTED_SELECTOR)).length === 0) {
await waitForFunction(async () => {
await click('#tab-resources');
return (await $$(APPLICATION_PANEL_SELECTED_SELECTOR)).length === 1;
});
}
};
declare global {
interface Window {
iFrameWindow: Window|null|undefined;
}
}
const getFieldValuesTextContent = async () => {
const fieldValues = await getTrimmedTextContent('devtools-report-value');
if (fieldValues[0]) {
// This contains some CSS from the svg icon link being rendered. It's
// system-specific, so we get rid of it and only look at the (URL) text.
fieldValues[0] = getTrailingURL(fieldValues[0]);
}
if (fieldValues[10]?.includes('accelerometer')) {
fieldValues[10] = 'accelerometer';
}
// Make sure the length is equivalent to the expected value below
if (fieldValues.length === 11) {
return fieldValues;
}
return undefined;
};
describe('The Application Tab', () => {
// Update and reactivate when the whole FrameDetailsView is a custom component
it.skip('[crbug.com/1519420]: shows details for a frame when clicked on in the frame tree', async () => {
await navigateToApplicationTab('frame-tree');
await click('#tab-resources');
await navigateToFrame('top');
const fieldValuesTextContent = await waitForFunction(getFieldValuesTextContent);
const expected = [
`https://localhost:${getTestServerPort()}/test/e2e/resources/application/frame-tree.html`,
`https://localhost:${getTestServerPort()}`,
'<#document>',
'Yes\xA0Localhost is always a secure context',
'No',
'None',
'UnsafeNone',
'None',
'unavailable\xA0requires cross-origin isolated context',
'unavailable\xA0Learn more',
'accelerometer',
];
assert.deepEqual(fieldValuesTextContent, expected);
});
it('shows stack traces for OOPIF', async () => {
expectError('Request CacheStorage.requestCacheNames failed. {"code":-32602,"message":"Invalid security origin"}');
await navigateToApplicationTab('js-oopif');
await waitForFunction(async () => {
await navigateToFrame('top');
await navigateToFrame('iframe.html');
return (await $$(EXPAND_STACKTRACE_BUTTON_SELECTOR)).length === 1;
});
const stackTraceRowsTextContent = await waitForFunction(async () => {
await ensureApplicationPanel();
await click(EXPAND_STACKTRACE_BUTTON_SELECTOR);
const stackTraceRows = await getTrimmedTextContent(STACKTRACE_ROW_SELECTOR);
// Make sure the length is equivalent to the expected value below
if (stackTraceRows.length === 3) {
return stackTraceRows;
}
return undefined;
});
const expected = [
'second\xA0@\xA0js-oopif.html:13',
'first\xA0@\xA0js-oopif.js:3',
'(anonymous)\xA0@\xA0js-oopif.js:6',
];
assert.deepEqual(stackTraceRowsTextContent, expected);
});
it('stack traces for OOPIF with ignore listed frames can be expanded and collapsed', async () => {
expectError('Request CacheStorage.requestCacheNames failed. {"code":-32602,"message":"Invalid security origin"}');
await setIgnoreListPattern('js-oopif.js');
await navigateToApplicationTab('js-oopif');
await waitForFunction(async () => {
await navigateToFrame('top');
await navigateToFrame('iframe.html');
return (await $$(EXPAND_STACKTRACE_BUTTON_SELECTOR)).length === 1;
});
let stackTraceRowsTextContent = await waitForFunction(async () => {
await ensureApplicationPanel();
await click(EXPAND_STACKTRACE_BUTTON_SELECTOR);
const stackTraceRows = await getTrimmedTextContent(STACKTRACE_ROW_SELECTOR);
// Make sure the length is equivalent to the expected value below
if (stackTraceRows.length === 2) {
return stackTraceRows;
}
return undefined;
});
const expectedCollapsed = [
'second\xA0@\xA0js-oopif.html:13',
'Show 2 more frames',
];
assert.deepEqual(stackTraceRowsTextContent, expectedCollapsed);
// Expand all frames
await click(STACKTRACE_ROW_LINK_SELECTOR);
stackTraceRowsTextContent = await waitForFunction(async () => {
const stackTraceRows = await getTrimmedTextContent(STACKTRACE_ROW_SELECTOR);
// Make sure the length is equivalent to the expected value below
if (stackTraceRows.length === 4) {
return stackTraceRows;
}
return undefined;
});
const expectedFull = [
'second\xA0@\xA0js-oopif.html:13',
'first\xA0@\xA0js-oopif.js:3',
'(anonymous)\xA0@\xA0js-oopif.js:6',
'Show less',
];
assert.deepEqual(stackTraceRowsTextContent, expectedFull);
await click(STACKTRACE_ROW_LINK_SELECTOR);
stackTraceRowsTextContent = await waitForFunction(async () => {
const stackTraceRows = await getTrimmedTextContent(STACKTRACE_ROW_SELECTOR);
// Make sure the length is equivalent to the expected value below
if (stackTraceRows.length === 2) {
return stackTraceRows;
}
return undefined;
});
assert.deepEqual(stackTraceRowsTextContent, expectedCollapsed);
});
describe('', () => {
after(async () => {
const {target} = getBrowserAndPages();
await target.evaluate(() => {
window.iFrameWindow?.close();
});
});
it('shows details for opened windows in the frame tree', async () => {
const {target, frontend} = getBrowserAndPages();
await navigateToApplicationTab('frame-tree');
await click('#tab-resources');
await navigateToFrame('top');
await target.evaluate(() => {
window.iFrameWindow = window.open('iframe.html');
});
// window.open above would put DevTools in the background stopping updates
// to the application panel.
await frontend.bringToFront();
await navigateToOpenedWindows();
await waitFor(`${OPENED_WINDOWS_SELECTOR} + ol li:first-child`);
void pressKey('ArrowDown');
const fieldValuesTextContent = await waitForFunction(async () => {
const fieldValues = await getTrimmedTextContent('.report-field-value');
// Make sure the length is equivalent to the expected value below
if (fieldValues.length === 3 && !fieldValues.includes('')) {
return fieldValues;
}
return undefined;
});
const expected = [
`https://localhost:${getTestServerPort()}/test/e2e/resources/application/iframe.html`,
'<#document>',
'Yes',
];
assert.deepEqual(fieldValuesTextContent, expected);
});
});
it('shows dedicated workers in the frame tree', async () => {
expectError('Request CacheStorage.requestCacheNames failed. {"code":-32602,"message":"Invalid security origin"}');
const {target} = getBrowserAndPages();
await navigateToApplicationTab('frame-tree');
await navigateToFrame('top');
// DevTools is not ready yet when the worker is being initially attached.
// We therefore need to reload the page to see the worker in DevTools.
await target.reload();
await navigateToWebWorkers();
void pressKey('ArrowDown');
const fieldValuesTextContent = await waitForFunction(async () => {
const fieldValues = await getTrimmedTextContent('.report-field-value');
// Make sure the length is equivalent to the expected value below
if (fieldValues.length === 3 && fieldValues.every(field => field.trim() !== '')) {
return fieldValues;
}
return undefined;
});
const expected = [
`https://localhost:${getTestServerPort()}/test/e2e/resources/application/dedicated-worker.js`,
'Web Worker',
'None',
];
assert.deepEqual(fieldValuesTextContent, expected);
});
it('shows service workers in the frame tree', async () => {
expectError('Request CacheStorage.requestCacheNames failed. {"code":-32602,"message":"Invalid security origin"}');
await navigateToApplicationTab('service-worker-network');
await navigateToFrameServiceWorkers('top');
void pressKey('ArrowDown');
const fieldValuesTextContent = await waitForFunction(async () => {
const fieldValues = await getTrimmedTextContent('.report-field-value');
// Make sure the length is equivalent to the expected value below
if (fieldValues.length === 3 && fieldValues.every(field => field.trim() !== '')) {
return fieldValues;
}
return undefined;
});
const expected = [
`https://localhost:${getTestServerPort()}/test/e2e/resources/application/service-worker.js`,
'Service Worker',
'None',
];
assert.deepEqual(fieldValuesTextContent, expected);
// Unregister service worker to prevent leftovers from causing test errors.
void pressKey('ArrowUp');
void pressKey('ArrowLeft');
await unregisterServiceWorker();
});
// Update and reactivate when the whole FrameDetailsView is a custom component
it.skip('[crbug.com/1519420]: can handle when JS writes to frame', async () => {
expectError('Request CacheStorage.requestCacheNames failed. {"code":-32602,"message":"Invalid security origin"}');
const {target} = getBrowserAndPages();
await goToResource('application/main-frame.html');
await click('#tab-resources');
await navigateToFrame('top');
await navigateToFrame('frameId (iframe.html)');
// check iframe's URL after pageload
const fieldValuesTextContent = await waitForFunction(getFieldValuesTextContent);
const expected = [
`https://localhost:${getTestServerPort()}/test/e2e/resources/application/iframe.html`,
`https://localhost:${getTestServerPort()}`,
'<iframe>',
'Yes\xA0Localhost is always a secure context',
'No',
'None',
'UnsafeNone',
'None',
'unavailable\xA0requires cross-origin isolated context',
'unavailable\xA0Learn more',
'accelerometer',
];
assert.deepEqual(fieldValuesTextContent, expected);
assert.deepEqual(await getFrameTreeTitles(), ['top', 'frameId (iframe.html)', 'iframe.html', 'main-frame.html']);
// write to the iframe using 'document.write()'
await target.evaluate(() => {
const frame = document.getElementById('frameId') as HTMLIFrameElement;
const doc = frame.contentDocument;
if (doc) {
doc.open();
doc.write('<h1>Hello world !</h1>');
doc.close();
}
});
// check that iframe's URL has changed
await navigateToFrame('frameId (main-frame.html)');
const fieldValuesTextContent2 = await waitForFunction(getFieldValuesTextContent);
const expected2 = [
`https://localhost:${getTestServerPort()}/test/e2e/resources/application/main-frame.html`,
`https://localhost:${getTestServerPort()}`,
'<iframe>',
'Yes\xA0Localhost is always a secure context',
'No',
'None',
'UnsafeNone',
'None',
'unavailable\xA0requires cross-origin isolated context',
'unavailable\xA0Learn more',
'accelerometer',
];
assert.deepEqual(fieldValuesTextContent2, expected2);
assert.deepEqual(
await getFrameTreeTitles(), ['top', 'frameId (main-frame.html)', 'Document not available', 'main-frame.html']);
});
});