Skip to content

Commit a252a43

Browse files
danilsomsikovDevtools-frontend LUCI CQ
authored andcommitted
Port devtools/service-workers/resources/user-agent-override-worker.js to a non-hosted e2e test
Bug: 40733722 Change-Id: I419f7e63eecf90851da975fddeb0c012759d695d Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6589778 Commit-Queue: Alex Rudenko <alexrudenko@chromium.org> Auto-Submit: Danil Somsikov <dsv@chromium.org> Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
1 parent d87f11a commit a252a43

File tree

6 files changed

+138
-50
lines changed

6 files changed

+138
-50
lines changed

test/e2e/helpers/network-helpers.ts

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,32 @@
44

55
import type * as puppeteer from 'puppeteer-core';
66

7+
import type {DevToolsPage} from '../../e2e_non_hosted/shared/frontend-helper.js';
78
import {
89
$,
9-
$$,
1010
click,
11-
getBrowserAndPages,
1211
goToResource,
1312
setCheckBox,
1413
typeText,
1514
waitFor,
16-
waitForAria,
1715
waitForFunction,
1816
} from '../../shared/helper.js';
17+
import {getBrowserAndPagesWrappers} from '../../shared/non_hosted_wrappers.js';
1918

2019
import {veImpression} from './visual-logging-helpers.js';
2120

2221
const REQUEST_LIST_SELECTOR = '.network-log-grid tbody';
2322

24-
export async function waitForNetworkTab(): Promise<void> {
23+
export async function waitForNetworkTab(devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage):
24+
Promise<void> {
2525
// Make sure the network tab is shown on the screen
26-
await waitFor('.network-log-grid');
26+
await devToolsPage.waitFor('.network-log-grid');
2727
}
2828

29-
export async function openNetworkTab(): Promise<void> {
30-
await click('#tab-network');
31-
await waitForNetworkTab();
29+
export async function openNetworkTab(devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage):
30+
Promise<void> {
31+
await devToolsPage.click('#tab-network');
32+
await waitForNetworkTab(devToolsPage);
3233
}
3334

3435
/**
@@ -44,16 +45,17 @@ export async function navigateToNetworkTab(testName: string) {
4445
* @param numberOfRequests The expected number of requests to wait for.
4546
* @param selector Optional. The selector to use to get the list of requests.
4647
*/
47-
export async function waitForSomeRequestsToAppear(numberOfRequests: number) {
48-
await waitForFunction(async () => {
49-
const requests = await getAllRequestNames();
48+
export async function waitForSomeRequestsToAppear(
49+
numberOfRequests: number, devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage) {
50+
await devToolsPage.waitForFunction(async () => {
51+
const requests = await getAllRequestNames(devToolsPage);
5052
return requests.length >= numberOfRequests && Boolean(requests.map(name => name ? name.trim() : '').join(''));
5153
});
5254
}
5355

54-
export async function getAllRequestNames() {
55-
const requests = await $$(REQUEST_LIST_SELECTOR + ' .name-column');
56-
return await Promise.all(requests.map(request => request.evaluate(r => r.childNodes[2].textContent)));
56+
export async function getAllRequestNames(devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage) {
57+
const requests = await devToolsPage.$$(REQUEST_LIST_SELECTOR + ' .name-column');
58+
return await Promise.all(requests.map(request => request.evaluate(r => r.deepInnerText().split('\n')[0])));
5759
}
5860

5961
export async function getNumberOfRequests() {
@@ -70,31 +72,17 @@ export async function getSelectedRequestName() {
7072
});
7173
}
7274

73-
export async function selectRequestByName(name: string, clickOptions?: puppeteer.ClickOptions) {
74-
const selector = REQUEST_LIST_SELECTOR + ' .name-column';
75-
const {frontend} = getBrowserAndPages();
76-
77-
// Finding he click position is done in a single frontend.evaluate call
78-
// to make sure the element still exists after finding the element.
79-
// If this were done outside of evaluate code, it would be possible for an
80-
// element to be removed from the dom between the $$(.selector) call and the
81-
// click(element) call.
82-
const rect = await frontend.evaluate((name, selector) => {
83-
const elements = document.querySelectorAll(selector);
84-
for (const element of elements) {
85-
if (element.childNodes[2].textContent === name) {
86-
const {left, top, width, height} = element.getBoundingClientRect();
87-
return {left, top, width, height};
88-
}
89-
}
90-
return null;
91-
}, name, selector);
92-
93-
if (rect) {
94-
const x = rect.left + rect.width * 0.5;
95-
const y = rect.top + rect.height * 0.5;
96-
await frontend.mouse.click(x, y, clickOptions);
75+
export async function selectRequestByName(
76+
name: string, clickOptions?: puppeteer.ClickOptions,
77+
devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage) {
78+
const requests = await devToolsPage.$$(REQUEST_LIST_SELECTOR + ' .name-column');
79+
const names = await Promise.all(requests.map(request => request.evaluate(r => r.deepInnerText().split('\n')[0])));
80+
const index = names.findIndex(n => n === name);
81+
if (index === -1) {
82+
assert.fail(`Request "${name}" not found.`);
9783
}
84+
const request = requests[index];
85+
await request.click(clickOptions);
9886
}
9987

10088
export async function waitForSelectedRequestChange(initialRequestName: string|null) {
@@ -108,8 +96,9 @@ export async function setPersistLog(persist: boolean) {
10896
await setCheckBox('[title="Do not clear log on page reload / navigation"]', persist);
10997
}
11098

111-
export async function setCacheDisabled(disabled: boolean): Promise<void> {
112-
await setCheckBox('[title^="Disable cache"]', disabled);
99+
export async function setCacheDisabled(
100+
disabled: boolean, devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage): Promise<void> {
101+
await devToolsPage.setCheckBox('[title^="Disable cache"]', disabled);
113102
}
114103

115104
export async function setInvert(invert: boolean) {
@@ -126,11 +115,12 @@ export async function clearTimeWindow(): Promise<void> {
126115
await overviewGridCursorArea.click({count: 2});
127116
}
128117

129-
export async function setTextFilter(text: string): Promise<void> {
130-
const toolbarHandle = await waitFor('.text-filter');
131-
const input = await waitForAria('Filter', toolbarHandle);
118+
export async function setTextFilter(
119+
text: string, devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage): Promise<void> {
120+
const toolbarHandle = await devToolsPage.waitFor('.text-filter');
121+
const input = await devToolsPage.waitForAria('Filter', toolbarHandle);
132122
await input.focus();
133-
await typeText(text);
123+
await typeText(text, devToolsPage);
134124
}
135125

136126
export async function getTextFilterContent(): Promise<string> {

test/e2e_non_hosted/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ node_ts_library("tests") {
1717
"../conductor:implementation",
1818
"ai_assistance",
1919
"animations",
20+
"application",
2021
"assertion",
2122
"console",
2223
"coverage",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2025 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("../../../scripts/build/typescript/typescript.gni")
6+
7+
ts_e2e_library("application") {
8+
sources = [ "user-agent-override_test.ts" ]
9+
10+
deps = [
11+
"../../../front_end/core/host:bundle",
12+
"../../../front_end/core/root:bundle",
13+
"../../conductor:implementation",
14+
"../../e2e/helpers",
15+
"../../shared",
16+
]
17+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2025 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+
import {assert} from 'chai';
5+
import type {ElementHandle} from 'puppeteer-core';
6+
7+
import {
8+
openNetworkTab,
9+
selectRequestByName,
10+
setCacheDisabled,
11+
setTextFilter,
12+
waitForSomeRequestsToAppear
13+
} from '../../e2e/helpers/network-helpers.js';
14+
import type {DevToolsPage} from '../../e2e_non_hosted/shared/frontend-helper.js';
15+
16+
const NETWORK_VIEW_SELECTOR = '.network-item-view';
17+
const HEADERS_TAB_SELECTOR = '[aria-label=Headers][role="tab"]';
18+
const ACTIVE_HEADERS_TAB_SELECTOR = '[aria-label=Headers][role=tab][aria-selected=true]';
19+
const RESPONSE_HEADERS_SELECTOR = '[aria-label="Request Headers"]';
20+
const HEADER_ROW_SELECTOR = '.row';
21+
22+
async function assertChecked(checkbox: ElementHandle<HTMLInputElement>, expected: boolean) {
23+
const checked = await checkbox.evaluate(el => el.checked);
24+
assert.strictEqual(checked, expected);
25+
}
26+
27+
async function openHeadersTab(devToolsPage: DevToolsPage) {
28+
const networkView = await devToolsPage.waitFor(NETWORK_VIEW_SELECTOR);
29+
await devToolsPage.click(HEADERS_TAB_SELECTOR, {
30+
root: networkView,
31+
});
32+
await devToolsPage.waitFor(ACTIVE_HEADERS_TAB_SELECTOR, networkView);
33+
}
34+
35+
describe('Network emulation', () => {
36+
it('can override user agent for service worker requests', async ({devToolsPage, inspectedPage}) => {
37+
const fixedVersionUAValue =
38+
'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30';
39+
await openNetworkTab(devToolsPage);
40+
const networkConditionsButton = await devToolsPage.waitForAria('More network conditions…');
41+
await networkConditionsButton.click();
42+
const section = await devToolsPage.waitFor('.network-config-ua');
43+
44+
const autoCheckbox = await (await devToolsPage.waitForAria('Use browser default', section)).toElement('input');
45+
const uaDropdown = await devToolsPage.waitForAria('User agent', section);
46+
await assertChecked(autoCheckbox, true);
47+
await autoCheckbox.click();
48+
await assertChecked(autoCheckbox, false);
49+
50+
await uaDropdown.click();
51+
await uaDropdown.select(fixedVersionUAValue);
52+
await uaDropdown.click();
53+
await devToolsPage.click('[aria-label="Close drawer"]');
54+
55+
await setCacheDisabled(true, devToolsPage);
56+
await inspectedPage.goToResource('application/service-worker-network.html');
57+
await setTextFilter('is:service-worker-initiated', devToolsPage);
58+
59+
await waitForSomeRequestsToAppear(2, devToolsPage);
60+
await selectRequestByName('⚙ main.css', undefined, devToolsPage);
61+
await openHeadersTab(devToolsPage);
62+
63+
const responseHeaderSection = await devToolsPage.waitFor(RESPONSE_HEADERS_SELECTOR);
64+
const headersRows = await devToolsPage.getAllTextContents(HEADER_ROW_SELECTOR, responseHeaderSection);
65+
assert.include(headersRows, 'user-agent' + fixedVersionUAValue);
66+
67+
await inspectedPage.evaluate(async () => {
68+
if (!navigator.serviceWorker) {
69+
return;
70+
}
71+
const registrations = await navigator.serviceWorker.getRegistrations();
72+
await Promise.all(registrations.map(r => r.unregister()));
73+
});
74+
});
75+
});

test/e2e_non_hosted/conductor/state-provider.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ export class StateProvider {
4343
} catch (e) {
4444
throw await screenshotError(state, e);
4545
} finally {
46-
await browsingContext.close();
47-
dumpCollectedErrors();
46+
try {
47+
await browsingContext.close();
48+
} catch {
49+
} finally {
50+
dumpCollectedErrors();
51+
}
4852
}
4953
}
5054

test/shared/helper.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ export const doubleClick =
8686
return await devToolsPage.doubleClick(selector, options);
8787
};
8888

89-
export const typeText = async (text: string) => {
90-
const {devToolsPage} = getBrowserAndPagesWrappers();
89+
export const typeText =
90+
async (text: string, devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage) => {
9191
await devToolsPage.typeText(text);
9292
};
9393

@@ -550,8 +550,9 @@ export async function renderCoordinatorQueueEmpty(): Promise<void> {
550550
await devToolsPage.renderCoordinatorQueueEmpty();
551551
}
552552

553-
export async function setCheckBox(selector: string, wantChecked: boolean): Promise<void> {
554-
const {devToolsPage} = getBrowserAndPagesWrappers();
553+
export async function setCheckBox(
554+
selector: string, wantChecked: boolean,
555+
devToolsPage: DevToolsPage = getBrowserAndPagesWrappers().devToolsPage): Promise<void> {
555556
await devToolsPage.setCheckBox(selector, wantChecked);
556557
}
557558

0 commit comments

Comments
 (0)