Skip to content

Commit ee4571a

Browse files
Liviu RauDevtools-frontend LUCI CQ
authored andcommitted
Migrate webaudio tests to non-hosted e2e tests
Drive-by: with suites ported there is no reason to keep the dummy suite around; removed. Bug: 400683715 Change-Id: I7d60ac3121ae13ce1361a5416d9893f907f80f0b Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6308503 Reviewed-by: Simon Zünd <[email protected]> Reviewed-by: Alex Rudenko <[email protected]> Commit-Queue: Liviu Rau <[email protected]>
1 parent 0a927a7 commit ee4571a

File tree

16 files changed

+209
-203
lines changed

16 files changed

+209
-203
lines changed

test/e2e/BUILD.gn

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ node_ts_library("tests") {
4848
"sources",
4949
"targets",
5050
"throttling",
51-
"webaudio",
5251
]
5352
sources = [ "mocharc.ts" ]
5453
}

test/e2e/helpers/BUILD.gn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ node_ts_library("helpers") {
3535
"sources-helpers.ts",
3636
"style-property-editor-helpers.ts",
3737
"visual-logging-helpers.ts",
38-
"webaudio-helpers.ts",
3938
]
4039

4140
deps = [
4241
"../../../extension-api",
42+
"../../e2e_non_hosted/shared",
4343
"../../shared",
4444
]
4545
}

test/e2e/helpers/settings-helpers.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,42 @@
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 type {DevToolsFronendPage} from '../../e2e_non_hosted/shared/frontend-helper.js';
56
import {
6-
$,
77
click,
88
clickElement,
9-
getBrowserAndPages,
10-
hover,
119
scrollElementIntoView,
1210
waitFor,
13-
waitForAria,
1411
waitForFunction,
1512
} from '../../shared/helper.js';
13+
import {getBrowserAndPagesWrappers} from '../../shared/non_hosted_wrappers.js';
1614

17-
export const openPanelViaMoreTools = async (panelTitle: string) => {
18-
const {frontend} = getBrowserAndPages();
19-
15+
export async function openPanelViaMoreTools(panelTitle: string, frontend?: DevToolsFronendPage) {
16+
frontend = frontend || getBrowserAndPagesWrappers().devToolsPage;
2017
await frontend.bringToFront();
2118

2219
// Head to the triple dot menu.
23-
await click('aria/Customize and control DevTools');
20+
await frontend.click('aria/Customize and control DevTools');
2421

25-
await waitForFunction(async () => {
22+
await frontend.waitForFunction(async () => {
2623
// Open the “More Tools” option.
27-
await hover('aria/More tools[role="menuitem"]');
28-
return await $(`${panelTitle}[role="menuitem"]`, undefined, 'aria');
24+
await frontend.hover('aria/More tools[role="menuitem"]');
25+
return await frontend.$(`${panelTitle}[role="menuitem"]`, undefined, 'aria');
2926
});
3027

3128
// Click the desired menu item
32-
await click(`aria/${panelTitle}[role="menuitem"]`);
29+
await frontend.click(`aria/${panelTitle}[role="menuitem"]`);
3330

3431
// Wait for the triple dot menu to be collapsed.
35-
const button = await waitForAria('Customize and control DevTools');
36-
await waitForFunction(async () => {
32+
const button = await frontend.waitForAria('Customize and control DevTools');
33+
await frontend.waitForFunction(async () => {
3734
const expanded = await button.evaluate(el => el.getAttribute('aria-expanded'));
3835
return expanded === null;
3936
});
4037

4138
// Wait for the corresponding panel to appear.
42-
await waitForAria(`${panelTitle} panel[role="tabpanel"]`);
43-
};
39+
await frontend.waitForAria(`${panelTitle} panel[role="tabpanel"]`);
40+
}
4441

4542
export const openSettingsTab = async (tabTitle: string) => {
4643
const gearIconSelector = 'devtools-button[aria-label="Settings"]';

test/e2e/helpers/webaudio-helpers.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

test/e2e/webaudio/webaudio_test.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

test/e2e_non_hosted/BUILD.gn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ node_ts_library("tests") {
1616
deps = [
1717
"../conductor:implementation",
1818
"assertion",
19-
"dummy",
19+
"webaudio",
2020
]
2121
sources = [ "mocharc.ts" ]
2222
}

test/e2e_non_hosted/dummy/BUILD.gn

Lines changed: 0 additions & 10 deletions
This file was deleted.

test/e2e_non_hosted/dummy/dummy_test.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

test/e2e_non_hosted/shared/BUILD.gn

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
import("../../../scripts/build/typescript/typescript.gni")
66

77
ts_e2e_library("shared") {
8-
deps = [
9-
"../../conductor:implementation",
10-
"../../shared",
11-
]
8+
deps = [ "../../conductor:implementation" ]
129
sources = [
1310
"browser-helper.ts",
1411
"frontend-helper.ts",

test/e2e_non_hosted/shared/frontend-helper.ts

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,27 @@
44

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

7+
import {AsyncScope} from '../../conductor/async-scope.js';
78
import {installPageErrorHandlers} from '../../conductor/events.js';
89

910
import {PageWrapper} from './page-wrapper.js';
1011

12+
export type Action = (element: puppeteer.ElementHandle) => Promise<void>;
13+
14+
export interface ClickOptions {
15+
root?: puppeteer.ElementHandle;
16+
clickOptions?: puppeteer.ClickOptions;
17+
maxPixelsFromLeft?: number;
18+
}
19+
1120
const envThrottleRate = process.env['STRESS'] ? 3 : 1;
1221
const envLatePromises = process.env['LATE_PROMISES'] !== undefined ?
1322
['true', ''].includes(process.env['LATE_PROMISES'].toLowerCase()) ? 10 : Number(process.env['LATE_PROMISES']) :
1423
0;
1524

25+
type DeducedElementType<ElementType extends Element|null, Selector extends string> =
26+
ElementType extends null ? puppeteer.NodeFor<Selector>: ElementType;
27+
1628
export class DevToolsFronendPage extends PageWrapper {
1729
async setExperimentEnabled(experiment: string, enabled: boolean) {
1830
await this.evaluate(`(async () => {
@@ -78,7 +90,7 @@ export class DevToolsFronendPage extends PageWrapper {
7890
}
7991

8092
async ensureReadyForTesting() {
81-
await this.waitForFunction(`
93+
await this.page.waitForFunction(`
8294
(async function() {
8395
const Main = await import('./entrypoints/main/main.js');
8496
return Main.MainImpl.MainImpl.instanceForTest !== null;
@@ -91,6 +103,117 @@ export class DevToolsFronendPage extends PageWrapper {
91103
})();
92104
`);
93105
}
106+
107+
// Get a single element handle. Uses `pierce` handler per default for piercing Shadow DOM.
108+
async $<ElementType extends Element|null = null, Selector extends string = string>(
109+
selector: Selector, root?: puppeteer.ElementHandle, handler = 'pierce') {
110+
const rootElement = root ? root : this.page;
111+
const element = await rootElement.$(`${handler}/${selector}`) as
112+
puppeteer.ElementHandle<DeducedElementType<ElementType, Selector>>;
113+
return element;
114+
}
115+
116+
async performActionOnSelector(selector: string, options: {root?: puppeteer.ElementHandle}, action: Action):
117+
Promise<puppeteer.ElementHandle> {
118+
// TODO(crbug.com/1410168): we should refactor waitFor to be compatible with
119+
// Puppeteer's syntax for selectors.
120+
const queryHandlers = new Set([
121+
'pierceShadowText',
122+
'pierce',
123+
'aria',
124+
'xpath',
125+
'text',
126+
]);
127+
let queryHandler = 'pierce';
128+
for (const handler of queryHandlers) {
129+
const prefix = handler + '/';
130+
if (selector.startsWith(prefix)) {
131+
queryHandler = handler;
132+
selector = selector.substring(prefix.length);
133+
break;
134+
}
135+
}
136+
return await this.waitForFunction(async () => {
137+
const element = await this.waitFor(selector, options?.root, undefined, queryHandler);
138+
try {
139+
await action(element);
140+
await this.drainFrontendTaskQueue();
141+
return element;
142+
} catch {
143+
return undefined;
144+
}
145+
});
146+
}
147+
148+
async waitFor<ElementType extends Element = Element>(
149+
selector: string, root?: puppeteer.ElementHandle, asyncScope = new AsyncScope(), handler?: string) {
150+
return await asyncScope.exec(() => this.waitForFunction(async () => {
151+
const element = await this.$<ElementType, typeof selector>(selector, root, handler);
152+
return (element || undefined);
153+
}, asyncScope), `Waiting for element matching selector '${selector}'`);
154+
}
155+
156+
/**
157+
* Schedules a task in the frontend page that ensures that previously
158+
* handled tasks have been handled.
159+
*/
160+
async drainFrontendTaskQueue(): Promise<void> {
161+
await this.evaluate(async () => {
162+
await new Promise(resolve => setTimeout(resolve, 0));
163+
});
164+
}
165+
166+
async waitForFunction<T>(fn: () => Promise<T|undefined>, asyncScope = new AsyncScope(), description?: string) {
167+
const innerFunction = async () => {
168+
while (true) {
169+
AsyncScope.abortSignal?.throwIfAborted();
170+
const result = await fn();
171+
AsyncScope.abortSignal?.throwIfAborted();
172+
if (result) {
173+
return result;
174+
}
175+
await this.timeout(100);
176+
}
177+
};
178+
return await asyncScope.exec(innerFunction, description);
179+
}
180+
181+
timeout(duration: number) {
182+
return new Promise<void>(resolve => setTimeout(resolve, duration));
183+
}
184+
185+
async click(selector: string, options?: ClickOptions) {
186+
return await this.performActionOnSelector(
187+
selector, {root: options?.root}, element => element.click(options?.clickOptions));
188+
}
189+
190+
async hover(selector: string, options?: {root?: puppeteer.ElementHandle}) {
191+
return await this.performActionOnSelector(selector, {root: options?.root}, element => element.hover());
192+
}
193+
194+
waitForAria<ElementType extends Element = Element>(
195+
selector: string, root?: puppeteer.ElementHandle, asyncScope = new AsyncScope()) {
196+
return this.waitFor<ElementType>(selector, root, asyncScope, 'aria');
197+
}
198+
199+
async waitForNone(selector: string, root?: puppeteer.ElementHandle, asyncScope = new AsyncScope(), handler?: string) {
200+
return await asyncScope.exec(() => this.waitForFunction(async () => {
201+
const elements = await this.$$(selector, root, handler);
202+
if (elements.length === 0) {
203+
return true;
204+
}
205+
return false;
206+
}, asyncScope), `Waiting for no elements to match selector '${selector}'`);
207+
}
208+
209+
// Get multiple element handles. Uses `pierce` handler per default for piercing Shadow DOM.
210+
async $$<ElementType extends Element|null = null, Selector extends string = string>(
211+
selector: Selector, root?: puppeteer.JSHandle, handler = 'pierce') {
212+
const rootElement = root ? root.asElement() || this.page : this.page;
213+
const elements = await rootElement.$$(`${handler}/${selector}`) as
214+
Array<puppeteer.ElementHandle<DeducedElementType<ElementType, Selector>>>;
215+
return elements;
216+
}
94217
}
95218

96219
export interface DevtoolsSettings {

0 commit comments

Comments
 (0)