blob: 2760399c0b703a0b6552cf53a232fa940cb4b490 [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 {
CONSOLE_TAB_SELECTOR,
focusConsolePrompt,
getCurrentConsoleMessages,
Level
} from '../helpers/console-helpers.js';
import {openSoftContextMenuAndClickOnItem} from '../helpers/context-menu-helpers.js';
import {
clickNthChildOfSelectedElementNode,
focusElementsTree,
waitForCSSPropertyValue,
waitForElementsStyleSection
} from '../helpers/elements-helpers.js';
import {setIgnoreListPattern} from '../helpers/settings-helpers.js';
import {
addBreakpointForLine,
getBreakpointDecorators,
getCallFrameNames,
getValuesForScope,
isBreakpointSet,
openFileInEditor,
openSourceCodeEditorForFile,
openSourcesPanel,
PAUSE_INDICATOR_SELECTOR,
reloadPageAndWaitForSourceFile,
removeBreakpointForLine,
RESUME_BUTTON,
retrieveCodeMirrorEditorContent,
retrieveTopCallFrameScriptLocation,
retrieveTopCallFrameWithoutResuming,
STEP_INTO_BUTTON,
STEP_OUT_BUTTON,
STEP_OVER_BUTTON,
waitForStackTopMatch
} from '../helpers/sources-helpers.js';
import type {DevToolsPage} from '../shared/frontend-helper.js';
async function waitForTextContent(selector: string, devToolsPage: DevToolsPage) {
const element = await devToolsPage.waitFor(selector);
return await element.evaluate(({textContent}) => textContent);
}
const DEVTOOLS_LINK = 'devtools-toolbar .devtools-link';
const INFOBAR_TEXT = '.infobar-info-text';
describe('The Sources Tab', function() {
setup({enabledDevToolsExperiments: ['instrumentation-breakpoints']});
it('reliably hits breakpoints on worker with source map', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-stepping-source.js', 'sourcemap-breakpoint.html', devToolsPage, inspectedPage);
// Add a breakpoint at first line of function multiline
await addBreakpointForLine(4, devToolsPage);
// Navigate to a different site to refresh devtools and remove back-end state
await inspectedPage.goTo('about:blank');
// Navigate back to test page
void inspectedPage.goToResource('sources/sourcemap-breakpoint.html');
// wait for pause and check if we stopped at line 4
await devToolsPage.waitFor(PAUSE_INDICATOR_SELECTOR);
await devToolsPage.waitForFunction(async () => {
const topCallFrame = await retrieveTopCallFrameWithoutResuming(devToolsPage);
return topCallFrame === 'sourcemap-stepping-source.js:4';
});
// Resume
await devToolsPage.click(RESUME_BUTTON);
});
});
describe('The Sources Tab', function() {
setup({});
it('steps over a source line mapping to a range with several statements', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-stepping-source.js', 'sourcemap-stepping.html', devToolsPage, inspectedPage);
// DevTools is contracting long filenames with ellipses.
// Let us match the location with regexp to match even contracted locations.
const breakLocationRegExp = /.*source\.js:12$/;
const stepLocationRegExp = /.*source\.js:13$/;
// Run to breakpoint
await addBreakpointForLine(12, devToolsPage);
const scriptEvaluation = inspectedPage.evaluate('singleline();');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationRegExp);
// Step over the mapped line
await devToolsPage.click(STEP_OVER_BUTTON);
const stepLocation = await waitForStackTopMatch(stepLocationRegExp, devToolsPage);
assert.match(stepLocation, stepLocationRegExp);
// Resume
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
});
it('steps over a source line with mappings to several adjacent target lines',
async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-stepping-source.js', 'sourcemap-stepping.html', devToolsPage, inspectedPage);
// DevTools is contracting long filenames with ellipses.
// Let us match the location with regexp to match even contracted locations.
const breakLocationRegExp = /.*source\.js:4$/;
const stepLocationRegExp = /.*source\.js:5$/;
// Run to breakpoint
await addBreakpointForLine(4, devToolsPage);
const scriptEvaluation = inspectedPage.evaluate('multiline();');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationRegExp);
// Step over the mapped line
await devToolsPage.click(STEP_OVER_BUTTON);
const stepLocation = await waitForStackTopMatch(stepLocationRegExp, devToolsPage);
assert.match(stepLocation, stepLocationRegExp);
// Resume
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
});
it('steps out from a function, with source maps available (crbug/1283188)', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-stepping-source.js', 'sourcemap-stepping.html', devToolsPage, inspectedPage);
// DevTools is contracting long filenames with ellipses.
// Let us match the location with regexp to match even contracted locations.
const breakLocationRegExp = /.*source\.js:4$/;
const stepLocationRegExp = /sourcemap-stepping.html:6$/;
// Run to breakpoint
await addBreakpointForLine(4, devToolsPage);
const scriptEvaluation = inspectedPage.evaluate('outer();');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationRegExp);
// Step out from breakpoint
await devToolsPage.click(STEP_OUT_BUTTON);
const stepLocation = await waitForStackTopMatch(stepLocationRegExp, devToolsPage);
assert.match(stepLocation, stepLocationRegExp);
// Resume
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
});
it('stepping works at the end of a sourcemapped script (crbug/1305956)', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-stepping-at-end.js', 'sourcemap-stepping-at-end.html', devToolsPage, inspectedPage);
// DevTools is contracting long filenames with ellipses.
// Let us match the location with regexp to match even contracted locations.
const breakLocationRegExp = /.*at-end\.js:2$/;
const stepLocationRegExp = /.*at-end.html:6$/;
for (const button of [STEP_INTO_BUTTON, STEP_OUT_BUTTON, STEP_OVER_BUTTON]) {
// Run to debugger statement
const scriptEvaluation = inspectedPage.evaluate('outer();');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationRegExp);
// Step ${description} from debugger statement
await devToolsPage.click(button);
const stepLocation = await waitForStackTopMatch(stepLocationRegExp, devToolsPage);
assert.match(stepLocation, stepLocationRegExp);
// Resume
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
}
});
it('shows unminified identifiers in scopes and console', async ({devToolsPage, inspectedPage}) => {
await devToolsPage.evaluate(() => {
// @ts-expect-error different context
DevToolsAPI.setUseSoftMenu(true);
});
await openSourceCodeEditorForFile('sourcemap-minified.js', 'sourcemap-minified.html', devToolsPage, inspectedPage);
const breakLocationRegExp = /sourcemap-minified\.js:1$/;
// Run to debugger statement
const scriptEvaluation = inspectedPage.evaluate('sayHello(" world");');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationRegExp);
// Check local variable is eventually un-minified
const unminifiedVariable = 'element: div';
await openSoftContextMenuAndClickOnItem('.cm-executionLine', 'Add source map…', devToolsPage);
// Enter the source map URL into the appropriate input box.
await devToolsPage.click('.add-source-map');
await devToolsPage.typeText('sourcemap-minified.map');
await devToolsPage.pressKey('Enter');
const scopeValues = await devToolsPage.waitForFunction(async () => {
const values = await getValuesForScope('Local', 0, 0, devToolsPage);
return (values?.includes(unminifiedVariable)) ? values : undefined;
});
assert.include(scopeValues, unminifiedVariable);
// Check that expression evaluation understands unminified name
await devToolsPage.click(CONSOLE_TAB_SELECTOR);
await focusConsolePrompt(devToolsPage);
await devToolsPage.pasteText('`Hello${text}!`');
await devToolsPage.pressKey('Enter');
// Wait for the console to be usable again.
await devToolsPage.waitForFunction(
() => devToolsPage.evaluate(() => document.querySelectorAll('.console-user-command-result').length === 1));
const messages = await getCurrentConsoleMessages(false, Level.All, undefined, devToolsPage);
assert.deepEqual(messages, ['\'Hello world!\'']);
await openSourcesPanel(devToolsPage);
// Resume
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
});
it('shows unminified identifiers in scopes with minified names clash and nested scopes',
async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-scopes-minified.js', 'sourcemap-scopes-minified.html', devToolsPage, inspectedPage);
const breakLocationOuterRegExp = /sourcemap-scopes-minified\.js:2$/;
const breakLocationInnerRegExp = /sourcemap-scopes-minified\.js:5$/;
const outerUnminifiedVariable = 'arg0: 10';
const innerUnminifiedVariable = 'loop_var: 0';
// Run to outer scope breakpoint
await addBreakpointForLine(2, devToolsPage);
const scriptEvaluation = inspectedPage.evaluate('foo(10);');
const scriptLocation = await waitForStackTopMatch(breakLocationOuterRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationOuterRegExp);
// Check local scope variable is eventually un-minified
{
const scopeValues = await devToolsPage.waitForFunction(async () => {
const values = await getValuesForScope('Local', 0, 0, devToolsPage);
return (values?.includes(outerUnminifiedVariable)) ? values : undefined;
});
assert.include(scopeValues, outerUnminifiedVariable);
}
// Resume from outer breakpoint
{
await addBreakpointForLine(5, devToolsPage);
await devToolsPage.click(RESUME_BUTTON);
const scriptLocation = await waitForStackTopMatch(breakLocationInnerRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationInnerRegExp);
}
// Check local and block scope variables are eventually un-minified
const blockScopeValues = await devToolsPage.waitForFunction(async () => {
const values = await getValuesForScope('Block', 0, 0, devToolsPage);
return (values?.includes(innerUnminifiedVariable)) ? values : undefined;
});
assert.include(blockScopeValues, innerUnminifiedVariable);
const scopeValues = await devToolsPage.waitForFunction(async () => {
const values = await getValuesForScope('Local', 0, 0, devToolsPage);
return (values?.includes(outerUnminifiedVariable)) ? values : undefined;
});
assert.include(scopeValues, outerUnminifiedVariable);
// Resume from inner breakpoint
await removeBreakpointForLine(2, devToolsPage);
await removeBreakpointForLine(5, devToolsPage);
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
});
it('shows unminified function name in stack trace', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-minified-function-name-compiled.js', 'sourcemap-minified-function-name.html', devToolsPage,
inspectedPage);
const breakLocationOuterRegExp = /sourcemap-.*-compiled\.js:1$/;
// Run to breakpoint
const scriptEvaluation = inspectedPage.evaluate('o(1, 2)');
const scriptLocation = await waitForStackTopMatch(breakLocationOuterRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationOuterRegExp);
// Add source map
await openSoftContextMenuAndClickOnItem('.cm-line', 'Add source map…', devToolsPage);
// Enter the source map URL into the appropriate input box.
await devToolsPage.click('.add-source-map');
await devToolsPage.typeText('sourcemap-minified-function-name-compiled.map');
await devToolsPage.pressKey('Enter');
// Check function name is eventually un-minified
const functionName = await devToolsPage.waitForFunction(async () => {
const functionName = await waitForTextContent('.call-frame-title-text', devToolsPage);
return functionName && functionName === 'unminified' ? functionName : undefined;
});
assert.strictEqual(functionName, 'unminified');
// Resume execution
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
});
it('automatically ignore-lists third party code from source maps', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile('webpack-main.js', 'webpack-index.html', devToolsPage, inspectedPage);
const breakLocationOuterRegExp = /index\.js:12$/;
// Run to breakpoint
const scriptEvaluation = inspectedPage.evaluate('window.foo()');
const scriptLocation = await waitForStackTopMatch(breakLocationOuterRegExp, devToolsPage);
assert.match(scriptLocation, breakLocationOuterRegExp);
assert.deepEqual(await getCallFrameNames(devToolsPage), ['baz', 'bar', 'foo', '(anonymous)']);
// Toggle to show ignore-listed frames
await devToolsPage.click('.ignore-listed-message-label');
await devToolsPage.waitFor('.ignore-listed-call-frame:not(.hidden)');
assert.deepEqual(await getCallFrameNames(devToolsPage), ['baz', 'vendor', 'bar', 'foo', '(anonymous)']);
// Toggle back off
await devToolsPage.click('.ignore-listed-message-label');
await devToolsPage.waitFor('.ignore-listed-call-frame.hidden');
assert.deepEqual(await getCallFrameNames(devToolsPage), ['baz', 'bar', 'foo', '(anonymous)']);
// Resume execution
await devToolsPage.click(RESUME_BUTTON);
await scriptEvaluation;
});
it('updates decorators for removed breakpoints in case of code-splitting (crbug.com/1251675)',
async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-disjoint.js', 'sourcemap-disjoint.html', devToolsPage, inspectedPage);
assert.deepEqual(await getBreakpointDecorators(false, 0, devToolsPage), []);
await addBreakpointForLine(2, devToolsPage);
assert.deepEqual(await getBreakpointDecorators(false, 1, devToolsPage), [2]);
await removeBreakpointForLine(2, devToolsPage);
assert.deepEqual(await getBreakpointDecorators(false, 0, devToolsPage), []);
});
it('links to the correct origins for source-mapped resources', async ({devToolsPage, inspectedPage}) => {
await inspectedPage.goToResource('sources/sourcemap-origin.html');
await openSourcesPanel(devToolsPage);
// Check origin of source-mapped JavaScript
{
await openFileInEditor('sourcemap-origin.js', devToolsPage);
const linkText = await waitForTextContent(DEVTOOLS_LINK, devToolsPage);
assert.strictEqual(linkText, 'sourcemap-origin.min.js');
}
// Check origin of source-mapped SASS
{
await openFileInEditor('sourcemap-origin.scss', devToolsPage);
const linkText = await waitForTextContent(DEVTOOLS_LINK, devToolsPage);
assert.strictEqual(linkText, 'sourcemap-origin.css');
}
// Check origin of source-mapped JavaScript with URL clash
{
await openFileInEditor('sourcemap-origin.clash.js', devToolsPage);
const linkText = await waitForTextContent(DEVTOOLS_LINK, devToolsPage);
assert.strictEqual(linkText, 'sourcemap-origin.clash.js');
}
});
it('shows Source map loaded infobar', async ({devToolsPage, inspectedPage}) => {
await inspectedPage.goToResource('sources/sourcemap-origin.html');
await openSourcesPanel(devToolsPage);
// Get infobar text
await openFileInEditor('sourcemap-origin.min.js', devToolsPage);
const infobarText = await waitForTextContent(INFOBAR_TEXT, devToolsPage);
assert.strictEqual(infobarText, 'Source map loaded');
});
it('shows Source map loaded infobar after attaching', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile('sourcemap-minified.js', 'sourcemap-minified.html', devToolsPage, inspectedPage);
// Attach source map
await openSoftContextMenuAndClickOnItem('.cm-line', 'Add source map…', devToolsPage);
// Enter the source map URL into the appropriate input box.
await devToolsPage.click('.add-source-map');
await devToolsPage.typeText('sourcemap-minified.map');
await devToolsPage.pressKey('Enter');
// Get infobar text
const infobarText = await waitForTextContent(INFOBAR_TEXT, devToolsPage);
assert.strictEqual(infobarText, 'Source map loaded');
});
it('shows Source map skipped infobar', async ({devToolsPage, inspectedPage}) => {
await setIgnoreListPattern('.min.js', devToolsPage);
await openSourceCodeEditorForFile('sourcemap-origin.min.js', 'sourcemap-origin.html', devToolsPage, inspectedPage);
// Get infobar texts
await openFileInEditor('sourcemap-origin.min.js', devToolsPage);
await devToolsPage.waitFor('.infobar-warning');
await devToolsPage.waitFor('.infobar-info');
const infobarTexts = await devToolsPage.getVisibleTextContents(INFOBAR_TEXT);
assert.deepEqual(
infobarTexts, ['This script is on the debugger\'s ignore list', 'Source map skipped for this file']);
});
it('shows Source map error infobar after failing to attach', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile('sourcemap-minified.js', 'sourcemap-minified.html', devToolsPage, inspectedPage);
// Attach source map
await openSoftContextMenuAndClickOnItem('.cm-line', 'Add source map…', devToolsPage);
// Enter the source map URL into the appropriate input box.
await devToolsPage.click('.add-source-map');
await devToolsPage.typeText('sourcemap-invalid.map');
await devToolsPage.pressKey('Enter');
// Get infobar text
const infobarText = await waitForTextContent(INFOBAR_TEXT, devToolsPage);
assert.strictEqual(infobarText, 'Source map failed to load');
});
describe('can deal with code-splitting', () => {
it('sets multiple breakpoints in case of code-splitting', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile(
'sourcemap-codesplit.ts', 'sourcemap-codesplit.html', devToolsPage, inspectedPage);
await addBreakpointForLine(3, devToolsPage);
for (let i = 0; i < 2; ++i) {
const scriptLocation =
await retrieveTopCallFrameScriptLocation(`functions[${i}]();`, inspectedPage, devToolsPage);
assert.deepEqual(scriptLocation, 'sourcemap-codesplit.ts:3');
}
});
it('restores breakpoints correctly in case of code-splitting (crbug.com/1412033)',
async ({devToolsPage, inspectedPage}) => {
// Load the initial setup with only one script pointing to `codesplitting-bar.ts`...
await openSourceCodeEditorForFile('codesplitting-bar.ts', 'codesplitting.html', devToolsPage, inspectedPage);
// ...and set a breakpoint inside `bar()`.
await addBreakpointForLine(2, devToolsPage);
// Now load the second script pointing to `codesplitting-bar.ts`...
await inspectedPage.evaluate('addSecond();');
// ...wait for the new origin to be listed...
const linkTexts = await devToolsPage.waitForFunction(async () => {
const links = await devToolsPage.page.$$(DEVTOOLS_LINK);
const linkTexts = await Promise.all(links.map(node => node.evaluate(({textContent}) => textContent)));
if (linkTexts.length === 1 && linkTexts[0] === 'codesplitting-first.js') {
return undefined;
}
return linkTexts;
});
assert.sameMembers(linkTexts, ['codesplitting-first.js', 'codesplitting-second.js']);
// ...and eventually wait for the breakpoint to be restored in line 2.
await devToolsPage.waitForFunction(async () => await isBreakpointSet(2, devToolsPage));
// Eventually we should stop on the breakpoint in the `codesplitting-second.js`.
await devToolsPage.waitForFunction(() => {
return Promise.race([
inspectedPage.evaluate('second()').then(() => false),
devToolsPage.waitFor(PAUSE_INDICATOR_SELECTOR).then(() => true),
]);
});
await devToolsPage.click(RESUME_BUTTON);
});
it('hits breakpoints reliably after reload in case of code-splitting (crbug.com/1490369)',
async ({devToolsPage, inspectedPage}) => {
// Set the breakpoint inside `shared()` in `shared.js`.
await openSourceCodeEditorForFile('shared.js', 'codesplitting-race.html', devToolsPage, inspectedPage);
await addBreakpointForLine(2, devToolsPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet(2, devToolsPage));
// Reload the page.
const reloadPromise = inspectedPage.reload();
// Now the debugger should pause twice reliably.
await devToolsPage.waitFor(PAUSE_INDICATOR_SELECTOR);
await devToolsPage.click(RESUME_BUTTON);
await devToolsPage.waitFor(PAUSE_INDICATOR_SELECTOR);
await devToolsPage.click(RESUME_BUTTON);
await reloadPromise;
});
});
describe('can deal with hot module replacement', () => {
// The tests in here simulate Hot Module Replacement (HMR) workflows related
// to how DevTools deals with source maps in these situations.
it('correctly handles URL clashes between compiled and source-mapped scripts',
async ({devToolsPage, inspectedPage}) => {
// Load the "initial bundle"...
await openSourceCodeEditorForFile('index.js', 'sourcemap-hmr.html', devToolsPage, inspectedPage);
// ...and check that index.js content is as expected.
// In particular, this asserts that the front-end does not get creative in
// appending suffixes like '? [sm]' to the index.js here.
const initialContent = await retrieveCodeMirrorEditorContent(devToolsPage);
assert.deepEqual(initialContent, [
'globalThis.hello = () => {',
' console.log("Hello world!");',
'}',
'',
]);
// Simulate the hot module replacement for index.js...
await inspectedPage.evaluate('update();');
// ...and wait for its content to load (should just replace
// the existing tab contents for index.js). We perform this
// check by waiting until the editor contents differ from
// the initial contents, and then asserting that it looks
// as expected afterwards.
const updatedContent = await devToolsPage.waitForFunction(async () => {
const content = await retrieveCodeMirrorEditorContent(devToolsPage);
if (content.length !== initialContent.length) {
return content;
}
for (let i = 0; i < content.length; ++i) {
if (content[i] !== initialContent[i]) {
return content;
}
}
return undefined;
});
assert.deepEqual(updatedContent, [
'globalThis.hello = () => {',
' console.log("Hello UPDATED world!");',
'}',
'',
]);
});
it('correctly maintains breakpoints from initial bundle to replacement', async ({devToolsPage, inspectedPage}) => {
// Load the "initial bundle" and set a breakpoint on the second line.
await openSourceCodeEditorForFile('index.js', 'sourcemap-hmr.html', devToolsPage, inspectedPage);
await addBreakpointForLine(2, devToolsPage);
// Simulate the hot module replacement for index.js
await inspectedPage.evaluate('update();');
// Wait for the "hot module replacement" to take effect for index.js.
await devToolsPage.waitForFunction(async () => {
const content = await retrieveCodeMirrorEditorContent(devToolsPage);
return content[1].includes('UPDATED');
});
// Wait for the breakpoint to appear on line 2 of the updated index.js.
await devToolsPage.waitForFunction(async () => await isBreakpointSet(2, devToolsPage));
});
it('correctly maintains breakpoints from replacement to initial bundle (across reloads)',
async ({devToolsPage, inspectedPage}) => {
// Load the "initial bundle".
await openSourceCodeEditorForFile('index.js', 'sourcemap-hmr.html', devToolsPage, inspectedPage);
// Simulate the hot module replacement for index.js
await inspectedPage.evaluate('update();');
// Wait for the "hot module replacement" to take effect for index.js.
await devToolsPage.waitForFunction(async () => {
const content = await retrieveCodeMirrorEditorContent(devToolsPage);
return content[1].includes('UPDATED');
});
// Set a breakpoint on the second line.
await addBreakpointForLine(2, devToolsPage);
// Reload the page and re-open (the initial) index.js.
await reloadPageAndWaitForSourceFile('index.js', devToolsPage, inspectedPage);
// Check that the breakpoint still exists on line 2.
assert.isTrue(await isBreakpointSet(2, devToolsPage));
});
});
it('can attach sourcemaps to CSS files from a context menu', async ({devToolsPage, inspectedPage}) => {
await openSourceCodeEditorForFile('sourcemap-css.css', 'sourcemap-css-noinline.html', devToolsPage, inspectedPage);
await devToolsPage.click('aria/Code editor', {clickOptions: {button: 'right'}});
await devToolsPage.click('aria/Add source map…');
await devToolsPage.waitFor('.add-source-map');
await devToolsPage.typeText('sourcemap-css-absolute.map');
await devToolsPage.pressKey('Enter');
await devToolsPage.waitFor('[aria-label="app.scss, file"]');
});
});
describe('The Elements Tab', () => {
async function clickStyleValueWithModifiers(
selector: string, name: string, value: string, location: string, devToolsPage: DevToolsPage) {
const element = await waitForCSSPropertyValue(selector, name, value, location, devToolsPage);
// Click with offset to skip swatches.
await devToolsPage.clickElement(
element,
{
clickOptions: {offset: {x: 20, y: 5}},
modifiers: {control: true},
},
);
}
it('links to the right SASS source for inline CSS with relative sourcemap (crbug.com/787792)',
async ({devToolsPage, inspectedPage}) => {
await inspectedPage.goToResource('sources/sourcemap-css-inline-relative.html');
// Prepare elements tab
await waitForElementsStyleSection('<body', devToolsPage);
await focusElementsTree(devToolsPage);
await clickNthChildOfSelectedElementNode(1, devToolsPage);
await clickStyleValueWithModifiers('body .text', 'color', 'green', 'app.scss:6', devToolsPage);
await devToolsPage.waitForElementWithTextContent('Line 12, Column 9');
});
it('links to the right SASS source for inline CSS with absolute sourcemap (crbug.com/787792)',
async ({devToolsPage, inspectedPage}) => {
await inspectedPage.goToResource('sources/sourcemap-css-dynamic-link.html');
// Prepare elements tab
await waitForElementsStyleSection('<body', devToolsPage);
await focusElementsTree(devToolsPage);
await clickNthChildOfSelectedElementNode(1, devToolsPage);
await clickStyleValueWithModifiers('body .text', 'color', 'green', 'app.scss:6', devToolsPage);
await devToolsPage.waitForElementWithTextContent('Line 12, Column 9');
});
it('links to the right SASS source for dynamically added CSS style tags (crbug.com/787792)',
async ({devToolsPage, inspectedPage}) => {
await inspectedPage.goToResource('sources/sourcemap-css-dynamic.html');
// Prepare elements tab
await waitForElementsStyleSection('<body', devToolsPage);
await focusElementsTree(devToolsPage);
await clickNthChildOfSelectedElementNode(1, devToolsPage);
await clickStyleValueWithModifiers('body .text', 'color', 'green', 'app.scss:6', devToolsPage);
await devToolsPage.waitForElementWithTextContent('Line 12, Column 9');
});
it('links to the right SASS source for dynamically added CSS link tags (crbug.com/787792)',
async ({devToolsPage, inspectedPage}) => {
await inspectedPage.goToResource('sources/sourcemap-css-dynamic-link.html');
// Prepare elements tab
await waitForElementsStyleSection('<body', devToolsPage);
await focusElementsTree(devToolsPage);
await clickNthChildOfSelectedElementNode(1, devToolsPage);
await clickStyleValueWithModifiers('body .text', 'color', 'green', 'app.scss:6', devToolsPage);
await devToolsPage.waitForElementWithTextContent('Line 12, Column 9');
});
});