blob: 69e335fa5aff8b2cabaebbc896aaf26d55b47580 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// 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 {getBrowserAndPagesWrappers} from '../../shared/non_hosted_wrappers.js';
import {getMenuItemAtPosition, getMenuItemTitleAtPosition, openFileQuickOpen} from '../helpers/quick_open-helpers.js';
import {
addBreakpointForLine,
addLogpointForLine,
CODE_LINE_COLUMN_SELECTOR,
getBreakpointHitLocation,
isBreakpointSet,
isEqualOrAbbreviation,
openSourceCodeEditorForFile,
PAUSE_INDICATOR_SELECTOR,
RESUME_BUTTON,
retrieveTopCallFrameWithoutResuming,
} from '../helpers/sources-helpers.js';
import type {DevToolsPage} from '../shared/frontend-helper.js';
import type {InspectedPage} from '../shared/target-helper.js';
async function assertScriptLocation(
expectedLocation: string, devToolsPage = getBrowserAndPagesWrappers().devToolsPage) {
const scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.isOk(scriptLocation, 'Unable to retrieve script location for call frame');
assert.isTrue(isEqualOrAbbreviation(scriptLocation, expectedLocation));
}
async function waitForTopCallFrameChanged(
previousCallFrame: string, updatedCallFrame: string, devToolsPage: DevToolsPage) {
await devToolsPage.waitForFunction(async () => {
const scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
if (scriptLocation === previousCallFrame) {
return false;
}
assert.strictEqual(scriptLocation, updatedCallFrame);
return true;
});
}
async function testScriptPauseAndResume(script: string, inspectedPage: InspectedPage, devToolsPage: DevToolsPage) {
const scriptEvaluation = inspectedPage.evaluate('f2();');
const scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, `${script}:4`);
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
}
describe('The Sources Tab', function() {
const CLICK_BREAKPOINT_SCRIPT = 'click-breakpoint.js';
const CLICK_BREAKPOINT_HTML = 'click-breakpoint.html';
it('sets and hits breakpoints in JavaScript', async ({inspectedPage, devToolsPage}) => {
await openSourceCodeEditorForFile(CLICK_BREAKPOINT_SCRIPT, CLICK_BREAKPOINT_HTML, devToolsPage, inspectedPage);
await addBreakpointForLine(4, devToolsPage);
const scriptEvaluation = inspectedPage.evaluate('f2();');
const scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, `${CLICK_BREAKPOINT_SCRIPT}:4`);
const breakpointLocation = await getBreakpointHitLocation(devToolsPage);
assert.deepEqual(breakpointLocation, scriptLocation);
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
});
it('can disable and re-enable breakpoints in JavaScript', async ({inspectedPage, devToolsPage}) => {
await openSourceCodeEditorForFile(CLICK_BREAKPOINT_SCRIPT, CLICK_BREAKPOINT_HTML, devToolsPage, inspectedPage);
// After adding a breakpoint, we expect the script to pause. Resume afterwards.
await addBreakpointForLine(4, devToolsPage);
await testScriptPauseAndResume(CLICK_BREAKPOINT_SCRIPT, inspectedPage, devToolsPage);
// Disable breakpoint. This time, we should not pause but be able to
// run the script until the end.
await devToolsPage.click(`[aria-label="${CLICK_BREAKPOINT_SCRIPT}"] [aria-label="checked"] input`);
await inspectedPage.evaluate('f2();');
// Re-enable breakpoint. Again, we should expect a pause and resume to finish the script.
await devToolsPage.click(`[aria-label="${CLICK_BREAKPOINT_SCRIPT}"] [aria-label="unchecked"] input`);
await testScriptPauseAndResume(CLICK_BREAKPOINT_SCRIPT, inspectedPage, devToolsPage);
});
it('can set and remove breakpoints in JavaScript', async ({inspectedPage, devToolsPage}) => {
await openSourceCodeEditorForFile(CLICK_BREAKPOINT_SCRIPT, CLICK_BREAKPOINT_HTML, devToolsPage, inspectedPage);
await addBreakpointForLine(4, devToolsPage);
// Hover over breakpoint.
await devToolsPage.hover(`[aria-label="${CLICK_BREAKPOINT_SCRIPT}"] [aria-label="checked"]`);
// Remove breakpoint.
await devToolsPage.click(`[aria-label="${CLICK_BREAKPOINT_SCRIPT}"] [aria-label="Remove breakpoint"]`);
// Running the function should not pause anywhere.
await inspectedPage.evaluate('f2();');
});
it('doesn\'t synchronize breakpoints between scripts and source-mapped scripts',
async ({devToolsPage, inspectedPage}) => {
// Navigate to page with sourceURL annotation and set breakpoint in line 2.
await openSourceCodeEditorForFile(
'breakpoint-conflict.js', 'breakpoint-conflict-source-url.html', devToolsPage, inspectedPage);
await addBreakpointForLine(2, devToolsPage);
// Navigate to page with sourceMappingURL annotation and check that breakpoint did not sync.
await openSourceCodeEditorForFile(
'breakpoint-conflict.js', 'breakpoint-conflict-source-map.html', devToolsPage, inspectedPage);
assert.isFalse(await isBreakpointSet(2, devToolsPage), 'Breakpoint found on line 2 which shouldn\'t be there');
});
it('stops at each breakpoint on resume (using F8) on target', async ({inspectedPage, devToolsPage}) => {
await openSourceCodeEditorForFile(CLICK_BREAKPOINT_SCRIPT, CLICK_BREAKPOINT_HTML, devToolsPage, inspectedPage);
await addBreakpointForLine(3, devToolsPage);
await addBreakpointForLine(4, devToolsPage);
await addBreakpointForLine(9, devToolsPage);
const scriptEvaluation = inspectedPage.evaluate('f2();');
await devToolsPage.waitFor(PAUSE_INDICATOR_SELECTOR);
const scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, `${CLICK_BREAKPOINT_SCRIPT}:3`);
await inspectedPage.page.keyboard.press('F8');
await waitForTopCallFrameChanged(`${CLICK_BREAKPOINT_SCRIPT}:3`, `${CLICK_BREAKPOINT_SCRIPT}:4`, devToolsPage);
await inspectedPage.page.keyboard.press('F8');
await waitForTopCallFrameChanged(`${CLICK_BREAKPOINT_SCRIPT}:4`, `${CLICK_BREAKPOINT_SCRIPT}:9`, devToolsPage);
await devToolsPage.pressKey('F8');
await scriptEvaluation;
});
it('shows a tooltip for logpoints', async ({inspectedPage, devToolsPage}) => {
await openSourceCodeEditorForFile(CLICK_BREAKPOINT_SCRIPT, CLICK_BREAKPOINT_HTML, devToolsPage, inspectedPage);
await addLogpointForLine(4, '14', devToolsPage);
const tooltip = await devToolsPage.waitFor('.cm-breakpoint-logpoint devtools-tooltip');
assert.strictEqual(await tooltip.evaluate(e => e.textContent), '14');
});
describe('with instrumenation breackpoints', () => {
setup({enabledDevToolsExperiments: ['instrumentation-breakpoints']});
it('can hit a breakpoint on the main thread on a fresh DevTools', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'breakpoint-hit-on-first-load.js', 'breakpoint-hit-on-first-load.html', devToolsPage, inspectedPage);
await addBreakpointForLine(1, devToolsPage);
await inspectedPage.goTo('about:blank');
await devToolsPage.reloadWithParams({panel: 'sources'});
void inspectedPage.goToResource('sources/breakpoint-hit-on-first-load.html');
await devToolsPage.waitFor(PAUSE_INDICATOR_SELECTOR);
await assertScriptLocation('breakpoint-hit-on-first-load.js:1', devToolsPage);
await devToolsPage.click(RESUME_BUTTON);
});
it('can hit a breakpoint in an inline script on the main thread on a fresh DevTools',
async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'breakpoint-hit-on-first-load.html', 'breakpoint-hit-on-first-load.html', devToolsPage, inspectedPage);
await addBreakpointForLine(9, devToolsPage);
await inspectedPage.goTo('about:blank');
await devToolsPage.reloadWithParams({panel: 'sources'});
void inspectedPage.goToResource('sources/breakpoint-hit-on-first-load.html');
await devToolsPage.waitFor(PAUSE_INDICATOR_SELECTOR);
await assertScriptLocation('breakpoint-hit-on-first-load.html:9', devToolsPage);
await devToolsPage.click(RESUME_BUTTON);
});
it('can hit a breakpoint in an inline script with sourceURL comment on the main thread on a fresh DevTools',
async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'breakpoint-hit-on-first-load.html', 'breakpoint-hit-on-first-load.html', devToolsPage, inspectedPage);
await openFileQuickOpen(devToolsPage);
await devToolsPage.page.keyboard.type('hello.js');
// TODO: it should actually wait for rendering to finish.
await devToolsPage.drainTaskQueue();
const firstItemTitle = await getMenuItemTitleAtPosition(0, devToolsPage);
const firstItem = await getMenuItemAtPosition(0, devToolsPage);
assert.strictEqual(firstItemTitle, 'hello.js');
await devToolsPage.clickElement(firstItem);
await addBreakpointForLine(2, devToolsPage);
await inspectedPage.goTo('about:blank');
await devToolsPage.reloadWithParams({panel: 'sources'});
void inspectedPage.goToResource('sources/breakpoint-hit-on-first-load.html');
await devToolsPage.waitFor(PAUSE_INDICATOR_SELECTOR);
await assertScriptLocation('hello.js:2', devToolsPage);
await devToolsPage.click(RESUME_BUTTON);
});
});
describe('The breakpoint edit dialog', () => {
it('shows up on Ctrl/Meta + click if no breakpoint was set', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(CLICK_BREAKPOINT_SCRIPT, CLICK_BREAKPOINT_HTML, devToolsPage, inspectedPage);
const lineNumberColumn = await devToolsPage.waitFor(CODE_LINE_COLUMN_SELECTOR);
await devToolsPage.click('text/4', {
root: lineNumberColumn,
modifiers: {
control: true,
}
});
await devToolsPage.waitFor('.sources-edit-breakpoint-dialog');
});
it('shows up on Ctrl/Meta + click if breakpoint was already set', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(CLICK_BREAKPOINT_SCRIPT, CLICK_BREAKPOINT_HTML, devToolsPage, inspectedPage);
await addBreakpointForLine(4, devToolsPage);
const lineNumberColumn = await devToolsPage.waitFor(CODE_LINE_COLUMN_SELECTOR);
await devToolsPage.click('text/4', {
root: lineNumberColumn,
modifiers: {
control: true,
}
});
await devToolsPage.waitFor('.sources-edit-breakpoint-dialog');
});
});
});