Skip to content

Commit 7201867

Browse files
danilsomsikovDevtools-frontend LUCI CQ
authored and
Devtools-frontend LUCI CQ
committed
[ve] Check impressions after reloadDevTools in e2e tests
Bug: 348173254 Change-Id: I02fd731fafafd3cd536c83d8af1a7a9fafaa4854 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5643076 Auto-Submit: Danil Somsikov <[email protected]> Reviewed-by: Simon Zünd <[email protected]> Commit-Queue: Danil Somsikov <[email protected]>
1 parent 06d3037 commit 7201867

13 files changed

+237
-13
lines changed

test/conductor/frontend_tab.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,15 @@ export class DevToolsFrontendTab {
6363
// frontend instances.
6464
const id = DevToolsFrontendTab.tabCounter++;
6565
const frontendUrl = `https://i${id}.devtools-frontend.test:${testServerPort}/${devToolsAppURL}?ws=localhost:${
66-
getDebugPort(browser)}/devtools/page/${targetId}&targetType=tab`;
66+
getDebugPort(browser)}/devtools/page/${targetId}&targetType=tab&veLogging=true`;
6767

6868
const frontend = await browser.newPage();
6969
installPageErrorHandlers(frontend);
7070
await frontend.goto(frontendUrl, {waitUntil: DEVTOOLS_WAITUNTIL_EVENTS});
71+
await frontend.evaluate(() => {
72+
// @ts-ignore
73+
globalThis.startTestLogging();
74+
});
7175

7276
const tab = new DevToolsFrontendTab(frontend, frontendUrl);
7377
return tab;
@@ -78,6 +82,11 @@ export class DevToolsFrontendTab {
7882
// Clear any local storage settings.
7983
await this.page.evaluate(() => localStorage.clear());
8084

85+
// Test logging needs debug event logging, which is controlled via localStorage, hence we need to restart test logging here
86+
await this.page.evaluate(() => {
87+
// @ts-ignore
88+
globalThis.startTestLogging();
89+
});
8190
await this.reload();
8291
}
8392

test/e2e/cross_tool_integration/workflow_test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import {click, closeAllCloseableTabs, goToResource, reloadDevTools, timeout, waitFor} from '../../shared/helper.js';
5+
import {click, closeAllCloseableTabs, goToResource, timeout, waitFor} from '../../shared/helper.js';
66
import {describe, it} from '../../shared/mocha-extensions.js';
77
import {navigateToConsoleTab, waitForConsoleInfoMessageAndClickOnLink} from '../helpers/console-helpers.js';
88
import {
99
clickOnContextMenuItemFromTab,
1010
MOVE_TO_DRAWER_SELECTOR,
1111
MOVE_TO_MAIN_PANEL_SELECTOR,
12+
reloadDevTools,
1213
tabExistsInDrawer,
1314
tabExistsInMainPanel,
1415
} from '../helpers/cross-tool-helper.js';

test/e2e/helpers/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ node_ts_library("helpers") {
3434
"settings-shortcuts-helpers.ts",
3535
"sources-helpers.ts",
3636
"style-property-editor-helpers.ts",
37+
"visual-logging-helpers.ts",
3738
"webaudio-helpers.ts",
3839
]
3940

test/e2e/helpers/cross-tool-helper.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import {click, waitFor} from '../../shared/helper.js';
5+
import {type DevToolsFrontendReloadOptions} from '../../conductor/frontend_tab.js';
6+
import {click, reloadDevTools as baseReloadDevTools, waitFor} from '../../shared/helper.js';
7+
8+
import {
9+
expectVeImpressions,
10+
veImpression,
11+
veImpressionForElementsPanel,
12+
veImpressionForMainToolbar,
13+
} from './visual-logging-helpers.js';
614

715
export async function clickOnContextMenuItemFromTab(tabId: string, menuItemSelector: string) {
816
// Find the selected node, right click.
@@ -34,3 +42,28 @@ export const checkIfTabExistsInDrawer = async (tabId: string) => {
3442
const tab = await waitFor(tabId, header);
3543
return Boolean(tab);
3644
};
45+
46+
/**
47+
* Reloads DevTools and checks for VE impressions
48+
*/
49+
export async function reloadDevTools(options?: DevToolsFrontendReloadOptions&{expectClosedPanels?: string[]}) {
50+
await baseReloadDevTools(options);
51+
const selectedPanel = options?.selectedPanel?.name || options?.queryParams?.panel || 'elements';
52+
await waitFor(`.panel.${selectedPanel}`);
53+
const expectClosedPanels = options?.expectClosedPanels;
54+
const dockable = options?.canDock;
55+
const panelImpression =
56+
selectedPanel === 'elements' ? veImpressionForElementsPanel({dockable}) : veImpression('Panel', selectedPanel);
57+
const expectedVeEvents = [veImpressionForMainToolbar({selectedPanel, expectClosedPanels, dockable}), panelImpression];
58+
if (options?.drawerShown) {
59+
expectedVeEvents.push(veImpression('Drawer', undefined, [
60+
veImpression(
61+
'Toolbar', 'drawer',
62+
[
63+
veImpression('PanelTabHeader', 'console'),
64+
veImpression('Close'),
65+
]),
66+
]));
67+
}
68+
await expectVeImpressions(expectedVeEvents.flat());
69+
}

test/e2e/helpers/emulation-helpers.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ import type * as puppeteer from 'puppeteer-core';
66
import {
77
$,
88
click,
9+
clickElement,
910
getBrowserAndPages,
1011
goToResource,
11-
reloadDevTools,
1212
waitFor,
13-
clickElement,
1413
} from '../../shared/helper.js';
1514

15+
import {
16+
reloadDevTools,
17+
} from './cross-tool-helper.js';
18+
1619
const DEVICE_TOOLBAR_TOGGLER_SELECTOR = '[aria-label="Toggle device toolbar"]';
1720
const DEVICE_TOOLBAR_SELECTOR = '.device-mode-toolbar';
1821
const DEVICE_TOOLBAR_OPTIONS_SELECTOR = '.device-mode-toolbar .device-mode-toolbar-options';

test/e2e/helpers/sources-helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
pasteText,
2323
platform,
2424
pressKey,
25-
reloadDevTools,
2625
setCheckBox,
2726
step,
2827
timeout,
@@ -35,6 +34,7 @@ import {
3534
} from '../../shared/helper.js';
3635

3736
import {openSoftContextMenuAndClickOnItem} from './context-menu-helpers.js';
37+
import {reloadDevTools} from './cross-tool-helper.js';
3838

3939
export const ACTIVE_LINE = '.CodeMirror-activeline > pre > span';
4040
export const PAUSE_BUTTON = '[aria-label="Pause script execution"]';
+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import {assert} from 'chai';
6+
7+
import {getBrowserAndPages} from '../../conductor/puppeteer-state.js';
8+
9+
// Corresponds to the type in front_end/ui/visual_logging/Debugging.ts
10+
type TestLogEntry = {
11+
impressions: string[],
12+
}|{
13+
interaction: string,
14+
};
15+
16+
function formatImpressions(impressions: string[]) {
17+
const result: string[] = [];
18+
let lastImpression = '';
19+
for (const impression of impressions.sort()) {
20+
while (!impression.startsWith(lastImpression)) {
21+
lastImpression = lastImpression.substr(0, lastImpression.lastIndexOf(' > '));
22+
}
23+
result.push(' '.repeat(lastImpression.length) + impression.substr(lastImpression.length));
24+
lastImpression = impression;
25+
}
26+
return result.join('\n');
27+
}
28+
29+
function compareVeImpressions(actual: string[], expected: string[]): {match: boolean, description?: string} {
30+
const actualSet = new Set(actual);
31+
const expectedSet = new Set(expected);
32+
const missing = [...expectedSet].filter(k => !actualSet.has(k));
33+
34+
if (missing.length) {
35+
return {
36+
match: false,
37+
description:
38+
'Missing VE events:\n' + formatImpressions(missing) + '\nActual impressions:\n' + formatImpressions(actual),
39+
};
40+
}
41+
return {match: true};
42+
}
43+
44+
export function veImpression(ve: string, context?: string, children?: string[][]) {
45+
let key = ve;
46+
if (context) {
47+
key += ': ' + context;
48+
}
49+
const result = [key];
50+
for (const child of children || []) {
51+
for (const impression of child) {
52+
result.push(key + ' > ' + impression);
53+
}
54+
}
55+
return result;
56+
}
57+
58+
function veImpressionForTabHeader(panel: string, options?: {closable: boolean}) {
59+
if (options?.closable) {
60+
return veImpression('PanelTabHeader', panel, [veImpression('Close')]);
61+
}
62+
return veImpression('PanelTabHeader', panel);
63+
}
64+
65+
export function veImpressionForMainToolbar(options?: {
66+
selectedPanel?: string,
67+
expectClosedPanels?: string[],
68+
dockable?: boolean,
69+
}) {
70+
const regularPanels = ['elements', 'console', 'sources', 'network'];
71+
if (!options?.dockable) {
72+
regularPanels.push('timeline', 'heap-profiler', 'resources', 'lighthouse');
73+
}
74+
75+
const closablePanels =
76+
options?.dockable ? [] : ['security', 'chrome-recorder'].filter(p => !options?.expectClosedPanels?.includes(p));
77+
if (options?.selectedPanel && !regularPanels.includes(options?.selectedPanel)) {
78+
closablePanels.push(options.selectedPanel);
79+
}
80+
81+
const dockableItems = options?.dockable ?
82+
[
83+
veImpression('DropDown', 'more-tabs'),
84+
veImpression('Toggle', 'emulation.toggle-device-mode'),
85+
veImpression('Close'),
86+
] :
87+
[];
88+
89+
return veImpression('Toolbar', 'main', [
90+
...regularPanels.map(panel => veImpressionForTabHeader(panel)),
91+
...closablePanels.map(panel => veImpressionForTabHeader(panel, {closable: true})),
92+
veImpression('Toggle', 'elements.toggle-element-search'),
93+
veImpression('Action', 'settings.show'),
94+
veImpression('DropDown', 'main-menu'),
95+
...dockableItems,
96+
]);
97+
}
98+
99+
export function veImpressionForElementsPanel(options?: {dockable?: boolean}) {
100+
return veImpression('Panel', 'elements', [
101+
veImpression('Toolbar', 'sidebar', [
102+
veImpressionForTabHeader('styles'),
103+
veImpressionForTabHeader('computed'),
104+
veImpressionForTabHeader('elements.layout'),
105+
...(options?.dockable ? ['elements.event-listeners', 'elements.dom-breakpoints', 'elements.dom-properties'] : []).map(panel => veImpressionForTabHeader(panel)),
106+
]),
107+
veImpression('ElementsBreadcrumbs', undefined, [veImpression('Item'), veImpression('Item')]),
108+
veImpression('Tree', 'elements', [
109+
veImpression('TreeItem'),
110+
veImpression('TreeItem', undefined, [veImpression('Value', 'tag-name'), veImpression('Expand')]),
111+
veImpression('TreeItem', undefined, [veImpression('Value', 'tag-name')]),
112+
veImpression('TreeItem', undefined, [veImpression('Value', 'tag-name')]),
113+
veImpression('TreeItem'),
114+
]),
115+
veImpression('Pane', 'styles', [
116+
veImpression('Section', 'style-properties', [veImpression('CSSRuleHeader', 'selector')]),
117+
veImpression('Section', 'style-properties', [
118+
veImpression('Action', 'elements.new-style-rule'),
119+
veImpression('CSSRuleHeader', 'selector'),
120+
veImpression('Tree', undefined, [
121+
veImpression('TreeItem', 'display', [veImpression('Toggle'), veImpression('Key'), veImpression('Value')]),
122+
veImpression('TreeItem', 'margin', [
123+
veImpression('Toggle'),
124+
veImpression('Key'),
125+
veImpression('Expand'),
126+
veImpression('Value'),
127+
veImpression('Expand'),
128+
]),
129+
]),
130+
]),
131+
veImpression('ToggleSubpane', 'element-states'),
132+
veImpression('ToggleSubpane', 'elements-classes'),
133+
veImpression('Action', 'elements.new-style-rule'),
134+
veImpression('DropDown', 'rendering-emulations'),
135+
veImpression('ToggleSubpane', 'computed-styles'),
136+
veImpression('TextField'),
137+
]),
138+
]);
139+
}
140+
141+
export function veImpressionForDrawerToolbar(options?: {
142+
selectedPanel?: string,
143+
}) {
144+
const closeablePanels = options?.selectedPanel ? [options?.selectedPanel] : [];
145+
return veImpression('Toolbar', 'drawer', [
146+
veImpressionForTabHeader('console'),
147+
...closeablePanels.map(panel => veImpressionForTabHeader(panel, {closable: true})),
148+
veImpression('DropDown', 'more-tabs'),
149+
veImpression('Close'),
150+
]);
151+
}
152+
153+
export async function expectVeImpressions(expectedImpressions: string[]) {
154+
const startTime = performance.now();
155+
const {frontend} = getBrowserAndPages();
156+
const actualEvents =
157+
// @ts-ignore
158+
await frontend.evaluate(async () => (await globalThis.getVeDebugEventsLog()) as unknown as TestLogEntry[]);
159+
const actualImpressionEvent = actualEvents.findLast(event => 'impressions' in event);
160+
const actualImpressions =
161+
actualImpressionEvent && 'impressions' in actualImpressionEvent ? actualImpressionEvent.impressions : null;
162+
if (!actualImpressions) {
163+
assert.fail('Missing VE impressions:\n' + formatImpressions(expectedImpressions));
164+
}
165+
166+
const {match, description} = compareVeImpressions(actualImpressions, expectedImpressions);
167+
console.error('expectVeImpressions took', performance.now() - startTime);
168+
assert.isTrue(
169+
match,
170+
description + '\nAll VE Events:\n' +
171+
actualEvents.map(e => 'impressions' in e ? formatImpressions(e.impressions) : e.interaction).join('\n\n'));
172+
}

test/e2e/host/user-metrics_test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import {
1414
goToResource,
1515
platform,
1616
pressKey,
17-
reloadDevTools,
1817
scrollElementIntoView,
1918
typeText,
2019
waitFor,
2120
waitForFunction,
2221
} from '../../shared/helper.js';
2322
import {describe, it} from '../../shared/mocha-extensions.js';
2423
import {CONSOLE_MESSAGES_SELECTOR, navigateToConsoleTab} from '../helpers/console-helpers.js';
24+
import {reloadDevTools} from '../helpers/cross-tool-helper.js';
2525
import {navigateToCssOverviewTab, startCaptureCSSOverview} from '../helpers/css-overview-helpers.js';
2626
import {
2727
editCSSProperty,

test/e2e/network/network-filter_test.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
disableExperiment,
1212
enableExperiment,
1313
getTestServerPort,
14-
reloadDevTools,
1514
step,
1615
typeText,
1716
waitFor,
@@ -20,6 +19,9 @@ import {
2019
waitForNone,
2120
} from '../../shared/helper.js';
2221
import {describe, it} from '../../shared/mocha-extensions.js';
22+
import {
23+
reloadDevTools,
24+
} from '../helpers/cross-tool-helper.js';
2325
import {
2426
clearTextFilter,
2527
getAllRequestNames,

test/e2e/network/network-throttle-persist_test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import {assert} from 'chai';
66
import {type ElementHandle} from 'puppeteer-core';
77

8-
import {reloadDevTools, waitFor, waitForAria} from '../../shared/helper.js';
8+
import {waitFor, waitForAria} from '../../shared/helper.js';
99
import {describe, it} from '../../shared/mocha-extensions.js';
10+
import {reloadDevTools} from '../helpers/cross-tool-helper.js';
1011
import {navigateToNetworkTab} from '../helpers/network-helpers.js';
1112

1213
describe('The Network Tab', function() {

test/e2e/performance/selector-stats-tracing_test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
import {assert} from 'chai';
66

7-
import {click, getBrowserAndPages, reloadDevTools, step, waitForFunction} from '../../shared/helper.js';
7+
import {click, getBrowserAndPages, step, waitForFunction} from '../../shared/helper.js';
88
import {describe, it} from '../../shared/mocha-extensions.js';
9+
import {reloadDevTools} from '../helpers/cross-tool-helper.js';
910
import {getDataGridRows} from '../helpers/datagrid-helpers.js';
1011
import {
1112
disableCSSSelectorStats,

test/e2e/performance/settings-throttle-persist_test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import {assert} from 'chai';
66
import {type ElementHandle} from 'puppeteer-core';
77

8-
import {reloadDevTools, waitFor, waitForAria} from '../../shared/helper.js';
8+
import {waitFor, waitForAria} from '../../shared/helper.js';
99
import {describe, it} from '../../shared/mocha-extensions.js';
10+
import {reloadDevTools} from '../helpers/cross-tool-helper.js';
1011
import {navigateToPerformanceTab, openCaptureSettings} from '../helpers/performance-helpers.js';
1112

1213
describe('The Performance panel', function() {

test/e2e/security/security_test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import {reloadDevTools} from '../../shared/helper.js';
65
import {describe, it} from '../../shared/mocha-extensions.js';
6+
import {reloadDevTools} from '../helpers/cross-tool-helper.js';
77
import {
88
closeSecurityTab,
99
navigateToSecurityTab,
@@ -21,7 +21,7 @@ describe('The Security Panel', function() {
2121

2222
it('closes without crashing and stays closed after reloading tools', async () => {
2323
await closeSecurityTab();
24-
await reloadDevTools();
24+
await reloadDevTools({expectClosedPanels: ['security']});
2525
await securityTabDoesNotExist();
2626
});
2727

0 commit comments

Comments
 (0)