blob: c6647ab639b58b73e5d1fe0ed71a03807704b3a6 [file] [log] [blame]
// Copyright 2022 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 * as puppeteer from 'puppeteer-core';
import {
$,
activeElementTextContent,
assertNotNullOrUndefined,
clickElement,
getBrowserAndPages,
waitFor,
waitForFunction,
waitForMany,
} from '../../shared/helper.js';
import {
addBreakpointForLine,
getLineNumberElement,
isBreakpointSet,
isEqualOrAbbreviation,
openSourceCodeEditorForFile,
retrieveCodeMirrorEditorContent,
} from '../helpers/sources-helpers.js';
const BREAKPOINT_VIEW_COMPONENT = 'devtools-breakpoint-view';
const FIRST_BREAKPOINT_ITEM_SELECTOR = '[data-first-breakpoint]';
const BREAKPOINT_ITEM_SELECTOR = '.breakpoint-item';
const LOCATION_SELECTOR = '.location';
const GROUP_HEADER_TITLE_SELECTOR = '.group-header-title';
const CODE_SNIPPET_SELECTOR = '.code-snippet';
async function extractTextContentIfConnected(element: puppeteer.ElementHandle): Promise<string|null> {
return element.evaluate(element => element.isConnected ? element.textContent : null);
}
describe('The Breakpoints Sidebar', () => {
describe('for source mapped files', () => {
it('correctly shows the breakpoint location on reload', async () => {
const testBreakpointContent = async (expectedFileName: string, expectedLineNumber: number) => {
await checkFileGroupName(expectedFileName);
await checkLineNumber(BREAKPOINT_ITEM_SELECTOR, expectedLineNumber);
};
const {target} = getBrowserAndPages();
const setBreakpointLine = 14;
const expectedResolvedLineNumber = 17;
const originalSource = 'reload-breakpoints-with-source-maps-source1.js';
await openSourceCodeEditorForFile(originalSource, 'reload-breakpoints-with-source-maps.html');
// Set a breakpoint on the original source.
const breakpointLineHandle = await getLineNumberElement(setBreakpointLine);
assertNotNullOrUndefined(breakpointLineHandle);
await clickElement(breakpointLineHandle);
await waitForFunction(async () => await isBreakpointSet(expectedResolvedLineNumber));
// Check if the breakpoint sidebar correctly shows the original source breakpoint.
await testBreakpointContent(originalSource, expectedResolvedLineNumber);
// Check if the breakpoint is correctly restored after reloading.
await target.reload();
await testBreakpointContent(originalSource, expectedResolvedLineNumber);
});
});
describe('for JS files', () => {
const expectedLocations = [3, 4, 9];
const fileName = 'click-breakpoint.js';
beforeEach(async () => {
const {frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile(fileName, 'click-breakpoint.html');
for (const location of expectedLocations) {
await addBreakpointForLine(frontend, location);
}
await waitForMany(BREAKPOINT_ITEM_SELECTOR, 3);
});
it('shows the correct location', async () => {
for (let i = 0; i < expectedLocations.length; ++i) {
const selector = `${BREAKPOINT_ITEM_SELECTOR}:nth-of-type(${i + 1})`;
await checkLineNumber(selector, expectedLocations[i]);
}
});
it('shows the correct file name', async () => {
await checkFileGroupName(fileName);
});
it('shows the correct code snippets', async () => {
const breakpointItems = await waitForMany(BREAKPOINT_ITEM_SELECTOR, 3);
const actualCodeSnippets = await Promise.all(breakpointItems.map(async breakpoint => {
const codeSnippetHandle = await waitFor(CODE_SNIPPET_SELECTOR, breakpoint);
const content = await extractTextContentIfConnected(codeSnippetHandle);
assertNotNullOrUndefined(content);
return content;
}));
const sourceContent = await retrieveCodeMirrorEditorContent();
const expectedCodeSnippets = expectedLocations.map(line => sourceContent[line - 1]);
assert.deepEqual(actualCodeSnippets, expectedCodeSnippets);
});
});
describe('for wasm files', () => {
it('shows the correct code snippets', async () => {
const {frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('memory.wasm', 'wasm/memory.html');
await addBreakpointForLine(frontend, '0x037');
const codeSnippetHandle = await waitFor(`${BREAKPOINT_ITEM_SELECTOR} ${CODE_SNIPPET_SELECTOR}`);
const actualCodeSnippet = await extractTextContentIfConnected(codeSnippetHandle);
const sourceContent = await retrieveCodeMirrorEditorContent();
const expectedCodeSnippet = sourceContent[3];
assert.strictEqual(actualCodeSnippet, expectedCodeSnippet);
});
});
it('will keep the focus on breakpoint items whose location has changed after disabling', async () => {
await openSourceCodeEditorForFile('breakpoint-on-comment.js', 'breakpoint-on-comment.html');
// Set a breakpoint on a comment and expect it to slide.
const originalBreakpointLine = 3;
const slidBreakpointLine = 5;
const breakpointLine = await getLineNumberElement(originalBreakpointLine);
assertNotNullOrUndefined(breakpointLine);
await clickElement(breakpointLine);
await waitForFunction(async () => await isBreakpointSet(slidBreakpointLine));
const breakpointView = await $(BREAKPOINT_VIEW_COMPONENT);
assertNotNullOrUndefined(breakpointView);
// Click on the first breakpoint item to 1. disable and 2. focus.
const breakpointItem = await waitFor(FIRST_BREAKPOINT_ITEM_SELECTOR, breakpointView);
assertNotNullOrUndefined(breakpointItem);
const checkbox = await breakpointItem.$('input');
assertNotNullOrUndefined(checkbox);
await clickElement(checkbox);
// Wait until the click has propagated: the line is updated with the new location.
await waitForFunction(async () => await isBreakpointSet(originalBreakpointLine));
let breakpointItemTextContent: string|null = null;
await waitForFunction(async () => {
const updatedBreakpointItem = await waitFor(FIRST_BREAKPOINT_ITEM_SELECTOR, breakpointView);
breakpointItemTextContent = await extractTextContentIfConnected(updatedBreakpointItem);
const location = await waitFor(LOCATION_SELECTOR, updatedBreakpointItem);
const locationString = await extractTextContentIfConnected(location);
return locationString === `${originalBreakpointLine}`;
});
// Check that the breakpoint item still has focus although the ui location has changed.
const focusedTextContent = await activeElementTextContent();
assert.strictEqual(focusedTextContent, breakpointItemTextContent);
});
});
async function checkFileGroupName(expectedFileName: string) {
await waitForFunction(async () => {
const titleHandle = await waitFor(GROUP_HEADER_TITLE_SELECTOR);
const actualFileName = await extractTextContentIfConnected(titleHandle);
return actualFileName && isEqualOrAbbreviation(actualFileName, expectedFileName);
});
}
async function checkLineNumber(breakpointItemSelector: string, expectedLineNumber: number) {
await waitForFunction(async () => {
const breakpointItem = await waitFor(breakpointItemSelector);
const locationHandle = await waitFor(LOCATION_SELECTOR, breakpointItem);
const content = await extractTextContentIfConnected(locationHandle);
return content && expectedLineNumber === parseInt(content, 10);
});
}