blob: 876d04956f2d47e4a73070f99173f9c6638f8c46 [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 {
$$,
click,
clickElement,
enableExperiment,
getBrowserAndPages,
getVisibleTextContents,
goToResource,
pasteText,
pressKey,
step,
typeText,
waitFor,
waitForElementWithTextContent,
waitForFunction,
withControlOrMetaKey,
} from '../../shared/helper.js';
import {CONSOLE_TAB_SELECTOR, focusConsolePrompt, getCurrentConsoleMessages} from '../helpers/console-helpers.js';
import {openSoftContextMenuAndClickOnItem} from '../helpers/context-menu-helpers.js';
import {reloadDevTools} from '../helpers/cross-tool-helper.js';
import {
clickNthChildOfSelectedElementNode,
focusElementsTree,
waitForContentOfSelectedElementsNode,
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';
async function waitForTextContent(selector: string) {
const element = await 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() {
// Some of these tests that use instrumentation breakpoints
// can be slower on mac and windows. Increase the timeout for them.
if (this.timeout() !== 0) {
this.timeout(10000);
}
it('steps over a source line mapping to a range with several statements', async () => {
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-stepping-source.js', 'sourcemap-stepping.html');
let scriptEvaluation: Promise<unknown>;
// 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$/;
await step('Run to breakpoint', async () => {
await addBreakpointForLine(frontend, 12);
scriptEvaluation = target.evaluate('singleline();');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp);
assert.match(scriptLocation, breakLocationRegExp);
});
await step('Step over the mapped line', async () => {
await click(STEP_OVER_BUTTON);
const stepLocation = await waitForStackTopMatch(stepLocationRegExp);
assert.match(stepLocation, stepLocationRegExp);
});
await step('Resume', async () => {
await click(RESUME_BUTTON);
await scriptEvaluation;
});
});
it('steps over a source line with mappings to several adjacent target lines', async () => {
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-stepping-source.js', 'sourcemap-stepping.html');
let scriptEvaluation: Promise<unknown>;
// 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$/;
await step('Run to breakpoint', async () => {
await addBreakpointForLine(frontend, 4);
scriptEvaluation = target.evaluate('multiline();');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp);
assert.match(scriptLocation, breakLocationRegExp);
});
await step('Step over the mapped line', async () => {
await click(STEP_OVER_BUTTON);
const stepLocation = await waitForStackTopMatch(stepLocationRegExp);
assert.match(stepLocation, stepLocationRegExp);
});
await step('Resume', async () => {
await click(RESUME_BUTTON);
await scriptEvaluation;
});
});
it('steps out from a function, with source maps available (crbug/1283188)', async () => {
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-stepping-source.js', 'sourcemap-stepping.html');
let scriptEvaluation: Promise<unknown>;
// 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$/;
await step('Run to breakpoint', async () => {
await addBreakpointForLine(frontend, 4);
scriptEvaluation = target.evaluate('outer();');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp);
assert.match(scriptLocation, breakLocationRegExp);
});
await step('Step out from breakpoint', async () => {
await click(STEP_OUT_BUTTON);
const stepLocation = await waitForStackTopMatch(stepLocationRegExp);
assert.match(stepLocation, stepLocationRegExp);
});
await step('Resume', async () => {
await click(RESUME_BUTTON);
await scriptEvaluation;
});
});
it('stepping works at the end of a sourcemapped script (crbug/1305956)', async () => {
const {target} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-stepping-at-end.js', 'sourcemap-stepping-at-end.html');
// 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 [description, button] of [
['into', STEP_INTO_BUTTON],
['out', STEP_OUT_BUTTON],
['over', STEP_OVER_BUTTON],
]) {
let scriptEvaluation: Promise<unknown>;
await step('Run to debugger statement', async () => {
scriptEvaluation = target.evaluate('outer();');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp);
assert.match(scriptLocation, breakLocationRegExp);
});
await step(`Step ${description} from debugger statement`, async () => {
await click(button);
const stepLocation = await waitForStackTopMatch(stepLocationRegExp);
assert.match(stepLocation, stepLocationRegExp);
});
await step('Resume', async () => {
await click(RESUME_BUTTON);
await scriptEvaluation;
});
}
});
it('shows unminified identifiers in scopes and console', async () => {
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-minified.js', 'sourcemap-minified.html');
let scriptEvaluation: Promise<unknown>;
const breakLocationRegExp = /sourcemap-minified\.js:1$/;
await step('Run to debugger statement', async () => {
scriptEvaluation = target.evaluate('sayHello(" world");');
const scriptLocation = await waitForStackTopMatch(breakLocationRegExp);
assert.match(scriptLocation, breakLocationRegExp);
});
await step('Check local variable is eventually un-minified', async () => {
const unminifiedVariable = 'element: div';
await openSoftContextMenuAndClickOnItem('.cm-line', 'Add source map…');
// Enter the source map URL into the appropriate input box.
await click('.add-source-map');
await typeText('sourcemap-minified.map');
await frontend.keyboard.press('Enter');
const scopeValues = await waitForFunction(async () => {
const values = await getValuesForScope('Local', 0, 0);
return (values && values.includes(unminifiedVariable)) ? values : undefined;
});
assert.include(scopeValues, unminifiedVariable);
});
await step('Check that expression evaluation understands unminified name', async () => {
await click(CONSOLE_TAB_SELECTOR);
await focusConsolePrompt();
await pasteText('`Hello${text}!`');
await frontend.keyboard.press('Enter');
// Wait for the console to be usable again.
await frontend.waitForFunction(() => {
return document.querySelectorAll('.console-user-command-result').length === 1;
});
const messages = await getCurrentConsoleMessages();
assert.deepEqual(messages, ['\'Hello world!\'']);
await openSourcesPanel();
});
await step('Resume', async () => {
await click(RESUME_BUTTON);
await scriptEvaluation;
});
});
it('shows unminified identifiers in scopes with minified names clash and nested scopes', async () => {
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-scopes-minified.js', 'sourcemap-scopes-minified.html');
let scriptEvaluation: Promise<unknown>;
const breakLocationOuterRegExp = /sourcemap-scopes-minified\.js:2$/;
const breakLocationInnerRegExp = /sourcemap-scopes-minified\.js:5$/;
const outerUnminifiedVariable = 'arg0: 10';
const innerUnminifiedVariable = 'loop_var: 0';
await step('Run to outer scope breakpoint', async () => {
await addBreakpointForLine(frontend, 2);
scriptEvaluation = target.evaluate('foo(10);');
const scriptLocation = await waitForStackTopMatch(breakLocationOuterRegExp);
assert.match(scriptLocation, breakLocationOuterRegExp);
});
await step('Check local scope variable is eventually un-minified', async () => {
const scopeValues = await waitForFunction(async () => {
const values = await getValuesForScope('Local', 0, 0);
return (values && values.includes(outerUnminifiedVariable)) ? values : undefined;
});
assert.include(scopeValues, outerUnminifiedVariable);
});
await step('Resume from outer breakpoint', async () => {
await addBreakpointForLine(frontend, 5);
await click(RESUME_BUTTON);
const scriptLocation = await waitForStackTopMatch(breakLocationInnerRegExp);
assert.match(scriptLocation, breakLocationInnerRegExp);
});
await step('Check local and block scope variables are eventually un-minified', async () => {
const blockScopeValues = await waitForFunction(async () => {
const values = await getValuesForScope('Block', 0, 0);
return (values && values.includes(innerUnminifiedVariable)) ? values : undefined;
});
assert.include(blockScopeValues, innerUnminifiedVariable);
const scopeValues = await waitForFunction(async () => {
const values = await getValuesForScope('Local', 0, 0);
return (values && values.includes(outerUnminifiedVariable)) ? values : undefined;
});
assert.include(scopeValues, outerUnminifiedVariable);
});
await step('Resume from inner breakpoint', async () => {
await removeBreakpointForLine(frontend, 2);
await removeBreakpointForLine(frontend, 5);
await click(RESUME_BUTTON);
await scriptEvaluation;
});
});
it('shows unminified function name in stack trace', async () => {
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile(
'sourcemap-minified-function-name-compiled.js', 'sourcemap-minified-function-name.html');
let scriptEvaluation: Promise<unknown>;
const breakLocationOuterRegExp = /sourcemap-.*-compiled\.js:1$/;
await step('Run to breakpoint', async () => {
scriptEvaluation = target.evaluate('o(1, 2)');
const scriptLocation = await waitForStackTopMatch(breakLocationOuterRegExp);
assert.match(scriptLocation, breakLocationOuterRegExp);
});
await step('Add source map', async () => {
await openSoftContextMenuAndClickOnItem('.cm-line', 'Add source map…');
// Enter the source map URL into the appropriate input box.
await click('.add-source-map');
await typeText('sourcemap-minified-function-name-compiled.map');
await frontend.keyboard.press('Enter');
});
await step('Check function name is eventually un-minified', async () => {
const functionName = await waitForFunction(async () => {
const functionName = await waitForTextContent('.call-frame-title-text');
return functionName && functionName === 'unminified' ? functionName : undefined;
});
assert.strictEqual(functionName, 'unminified');
});
await step('Resume execution', async () => {
await click(RESUME_BUTTON);
await scriptEvaluation;
});
});
// Flaky test
it.skipOnPlatforms(
['win32'], '[crbug.com/327580855] automatically ignore-lists third party code from source maps', async () => {
const {target} = getBrowserAndPages();
await openSourceCodeEditorForFile('webpack-main.js', 'webpack-index.html');
let scriptEvaluation: Promise<unknown>;
const breakLocationOuterRegExp = /index\.js:12$/;
await step('Run to breakpoint', async () => {
scriptEvaluation = target.evaluate('window.foo()');
const scriptLocation = await waitForStackTopMatch(breakLocationOuterRegExp);
assert.match(scriptLocation, breakLocationOuterRegExp);
assert.deepEqual(await getCallFrameNames(), ['baz', 'bar', 'foo', '(anonymous)']);
});
await step('Toggle to show ignore-listed frames', async () => {
await click('.ignore-listed-message-label');
await waitFor('.ignore-listed-call-frame:not(.hidden)');
assert.deepEqual(await getCallFrameNames(), ['baz', 'vendor', 'bar', 'foo', '(anonymous)']);
});
await step('Toggle back off', async () => {
await click('.ignore-listed-message-label');
await waitFor('.ignore-listed-call-frame.hidden');
assert.deepEqual(await getCallFrameNames(), ['baz', 'bar', 'foo', '(anonymous)']);
});
await step('Resume execution', async () => {
await click(RESUME_BUTTON);
await scriptEvaluation;
});
});
it('updates decorators for removed breakpoints in case of code-splitting (crbug.com/1251675)', async () => {
const {frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-disjoint.js', 'sourcemap-disjoint.html');
assert.deepEqual(await getBreakpointDecorators(), []);
await addBreakpointForLine(frontend, 2);
assert.deepEqual(await getBreakpointDecorators(), [2]);
await removeBreakpointForLine(frontend, 2);
assert.deepEqual(await getBreakpointDecorators(), []);
});
it('reliably hits breakpoints on worker with source map', async () => {
await enableExperiment('instrumentation-breakpoints');
const {frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-stepping-source.js', 'sourcemap-breakpoint.html');
await step('Add a breakpoint at first line of function multiline', async () => {
await addBreakpointForLine(frontend, 4);
});
await step('Navigate to a different site to refresh devtools and remove back-end state', async () => {
await reloadDevTools({removeBackendState: true, selectedPanel: {name: 'sources'}});
});
await step('Navigate back to test page', () => {
void goToResource('sources/sourcemap-breakpoint.html');
});
await step('wait for pause and check if we stopped at line 4', async () => {
await waitFor(PAUSE_INDICATOR_SELECTOR);
await waitForFunction(async () => {
const topCallFrame = await retrieveTopCallFrameWithoutResuming();
return topCallFrame === 'sourcemap-stepping-source.js:4';
});
});
await step('Resume', async () => {
await click(RESUME_BUTTON);
});
});
it('links to the correct origins for source-mapped resources', async () => {
await goToResource('sources/sourcemap-origin.html');
await openSourcesPanel();
await step('Check origin of source-mapped JavaScript', async () => {
await openFileInEditor('sourcemap-origin.js');
const linkText = await waitForTextContent(DEVTOOLS_LINK);
assert.strictEqual(linkText, 'sourcemap-origin.min.js');
});
await step('Check origin of source-mapped SASS', async () => {
await openFileInEditor('sourcemap-origin.scss');
const linkText = await waitForTextContent(DEVTOOLS_LINK);
assert.strictEqual(linkText, 'sourcemap-origin.css');
});
await step('Check origin of source-mapped JavaScript with URL clash', async () => {
await openFileInEditor('sourcemap-origin.clash.js');
const linkText = await waitForTextContent(DEVTOOLS_LINK);
assert.strictEqual(linkText, 'sourcemap-origin.clash.js');
});
});
it('shows Source map loaded infobar', async () => {
await goToResource('sources/sourcemap-origin.html');
await openSourcesPanel();
await step('Get infobar text', async () => {
await openFileInEditor('sourcemap-origin.min.js');
const infobarText = await waitForTextContent(INFOBAR_TEXT);
assert.strictEqual(infobarText, 'Source map loaded.');
});
});
it('shows Source map loaded infobar after attaching', async () => {
const {frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-minified.js', 'sourcemap-minified.html');
await step('Attach source map', async () => {
await openSoftContextMenuAndClickOnItem('.cm-line', 'Add source map…');
// Enter the source map URL into the appropriate input box.
await click('.add-source-map');
await typeText('sourcemap-minified.map');
await frontend.keyboard.press('Enter');
});
await step('Get infobar text', async () => {
const infobarText = await waitForTextContent(INFOBAR_TEXT);
assert.strictEqual(infobarText, 'Source map loaded.');
});
});
it('shows Source map skipped infobar', async () => {
await setIgnoreListPattern('.min.js');
await openSourceCodeEditorForFile('sourcemap-origin.min.js', 'sourcemap-origin.html');
await step('Get infobar texts', async () => {
await openFileInEditor('sourcemap-origin.min.js');
await waitFor('.infobar-warning');
await waitFor('.infobar-info');
const infobarTexts = await 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 () => {
const {frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-minified.js', 'sourcemap-minified.html');
await step('Attach source map', async () => {
await openSoftContextMenuAndClickOnItem('.cm-line', 'Add source map…');
// Enter the source map URL into the appropriate input box.
await click('.add-source-map');
await typeText('sourcemap-invalid.map');
await frontend.keyboard.press('Enter');
});
await step('Get infobar text', async () => {
const infobarText = await waitForTextContent(INFOBAR_TEXT);
assert.strictEqual(infobarText, 'Source map failed to load.');
});
});
describe('can deal with code-splitting', () => {
it('sets multiple breakpoints in case of code-splitting', async () => {
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('sourcemap-codesplit.ts', 'sourcemap-codesplit.html');
await addBreakpointForLine(frontend, 3);
for (let i = 0; i < 2; ++i) {
const scriptLocation = await retrieveTopCallFrameScriptLocation(`functions[${i}]();`, target);
assert.deepEqual(scriptLocation, 'sourcemap-codesplit.ts:3');
}
});
it('restores breakpoints correctly in case of code-splitting (crbug.com/1412033)', async () => {
const {target, frontend} = getBrowserAndPages();
// Load the initial setup with only one script pointing to `codesplitting-bar.ts`...
await openSourceCodeEditorForFile('codesplitting-bar.ts', 'codesplitting.html');
// ...and set a breakpoint inside `bar()`.
await addBreakpointForLine(frontend, 2);
// Now load the second script pointing to `codesplitting-bar.ts`...
await target.evaluate('addSecond();');
// ...wait for the new origin to be listed...
const linkTexts = await waitForFunction(async () => {
const links = await $$(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 waitForFunction(async () => await isBreakpointSet(2));
// Eventually we should stop on the breakpoint in the `codesplitting-second.js`.
await waitForFunction(() => {
return Promise.race([
target.evaluate('second()').then(() => false),
waitFor(PAUSE_INDICATOR_SELECTOR).then(() => true),
]);
});
await click(RESUME_BUTTON);
});
it('hits breakpoints reliably after reload in case of code-splitting (crbug.com/1490369)', async () => {
const {target, frontend} = getBrowserAndPages();
// Set the breakpoint inside `shared()` in `shared.js`.
await openSourceCodeEditorForFile('shared.js', 'codesplitting-race.html');
await addBreakpointForLine(frontend, 2);
await waitForFunction(async () => await isBreakpointSet(2));
// Reload the page.
const reloadPromise = target.reload();
// Now the debugger should pause twice reliably.
await waitFor(PAUSE_INDICATOR_SELECTOR);
await click(RESUME_BUTTON);
await waitFor(PAUSE_INDICATOR_SELECTOR);
await 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 () => {
const {target} = getBrowserAndPages();
// Load the "initial bundle"...
await openSourceCodeEditorForFile('index.js', 'sourcemap-hmr.html');
// ...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();
assert.deepEqual(initialContent, [
'globalThis.hello = () => {',
' console.log("Hello world!");',
'}',
'',
]);
// Simulate the hot module replacement for index.js...
await target.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 waitForFunction(async () => {
const content = await retrieveCodeMirrorEditorContent();
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 () => {
const {target, frontend} = getBrowserAndPages();
// Load the "initial bundle" and set a breakpoint on the second line.
await openSourceCodeEditorForFile('index.js', 'sourcemap-hmr.html');
await addBreakpointForLine(frontend, 2);
// Simulate the hot module replacement for index.js
await target.evaluate('update();');
// Wait for the "hot module replacement" to take effect for index.js.
await waitForFunction(async () => {
const content = await retrieveCodeMirrorEditorContent();
return content[1].includes('UPDATED');
});
// Wait for the breakpoint to appear on line 2 of the updated index.js.
await waitForFunction(async () => await isBreakpointSet(2));
});
it('correctly maintains breakpoints from replacement to initial bundle (across reloads)', async () => {
const {target, frontend} = getBrowserAndPages();
// Load the "initial bundle".
await openSourceCodeEditorForFile('index.js', 'sourcemap-hmr.html');
// Simulate the hot module replacement for index.js
await target.evaluate('update();');
// Wait for the "hot module replacement" to take effect for index.js.
await waitForFunction(async () => {
const content = await retrieveCodeMirrorEditorContent();
return content[1].includes('UPDATED');
});
// Set a breakpoint on the second line.
await addBreakpointForLine(frontend, 2);
// Reload the page and re-open (the initial) index.js.
await reloadPageAndWaitForSourceFile(target, 'index.js');
// Check that the breakpoint still exists on line 2.
assert.isTrue(await isBreakpointSet(2));
});
});
it('can attach sourcemaps to CSS files from a context menu', async () => {
await openSourceCodeEditorForFile('sourcemap-css.css', 'sourcemap-css-noinline.html');
await click('aria/Code editor', {clickOptions: {button: 'right'}});
await click('aria/Add source map…');
await waitFor('.add-source-map');
await typeText('sourcemap-css-absolute.map');
await pressKey('Enter');
await waitFor('[aria-label="app.scss, file"]');
});
});
describe('The Elements Tab', () => {
async function clickStyleValueWithModifiers(selector: string, name: string, value: string, location: string) {
const element = await waitForCSSPropertyValue(selector, name, value, location);
// Click with offset to skip swatches.
await withControlOrMetaKey(() => clickElement(element, {clickOptions: {offset: {x: 20, y: 5}}}));
}
it('links to the right SASS source for inline CSS with relative sourcemap (crbug.com/787792)', async () => {
await goToResource('sources/sourcemap-css-inline-relative.html');
await step('Prepare elements tab', async () => {
await waitForElementsStyleSection();
await waitForContentOfSelectedElementsNode('<body>\u200B');
await focusElementsTree();
await clickNthChildOfSelectedElementNode(1);
});
await clickStyleValueWithModifiers('body .text', 'color', 'green', 'app.scss:6');
await waitForElementWithTextContent('Line 12, Column 9');
});
it('links to the right SASS source for inline CSS with absolute sourcemap (crbug.com/787792)', async () => {
await goToResource('sources/sourcemap-css-dynamic-link.html');
await step('Prepare elements tab', async () => {
await waitForElementsStyleSection();
await waitForContentOfSelectedElementsNode('<body>\u200B');
await focusElementsTree();
await clickNthChildOfSelectedElementNode(1);
});
await clickStyleValueWithModifiers('body .text', 'color', 'green', 'app.scss:6');
await waitForElementWithTextContent('Line 12, Column 9');
});
it('links to the right SASS source for dynamically added CSS style tags (crbug.com/787792)', async () => {
await goToResource('sources/sourcemap-css-dynamic.html');
await step('Prepare elements tab', async () => {
await waitForElementsStyleSection();
await waitForContentOfSelectedElementsNode('<body>\u200B');
await focusElementsTree();
await clickNthChildOfSelectedElementNode(1);
});
await clickStyleValueWithModifiers('body .text', 'color', 'green', 'app.scss:6');
await waitForElementWithTextContent('Line 12, Column 9');
});
it('links to the right SASS source for dynamically added CSS link tags (crbug.com/787792)', async () => {
await goToResource('sources/sourcemap-css-dynamic-link.html');
await step('Prepare elements tab', async () => {
await waitForElementsStyleSection();
await waitForContentOfSelectedElementsNode('<body>\u200B');
await focusElementsTree();
await clickNthChildOfSelectedElementNode(1);
});
await clickStyleValueWithModifiers('body .text', 'color', 'green', 'app.scss:6');
await waitForElementWithTextContent('Line 12, Column 9');
});
});