[e2e] Add DevTools frontend tab + target tab pair pool

This CL adds two small helper classes. `Pool` is a promise based queue.
`FrontendTargetPool` uses `Pool` to keep a ready stock of
target and DevTools frontend tab pairs.

The pool implementation is not used yet to keep CL size reasonable.

R=jacktfranklin@chromium.org, pfaffe@chromium.org

Bug: 1297458
Change-Id: I17d2f882279f7ef94c052e67975efdf7e6e3a449
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3468514
Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
This commit is contained in:
Simon Zünd
2022-02-18 09:36:53 +00:00
committed by Devtools-frontend LUCI CQ
parent d8f4a40ce2
commit db0dd94286
3 changed files with 116 additions and 1 deletions
+1
View File
@@ -19,6 +19,7 @@ node_ts_library("implementation") {
"frontend_tab.ts",
"hooks.ts",
"mocha_hooks.ts",
"pool.ts",
"puppeteer-state.ts",
"target_tab.ts",
"test_runner_config.ts",
+6 -1
View File
@@ -40,6 +40,8 @@ export class DevToolsFrontendTab {
name: 'elements',
selector: '.elements',
};
// We use the counter to give each tab a unique origin.
private static tabCounter = 0;
private constructor(readonly page: puppeteer.Page, frontendUrl: string) {
this.#frontendUrl = frontendUrl;
@@ -56,7 +58,10 @@ export class DevToolsFrontendTab {
// We load the DevTools frontend on a unique origin. Otherwise we would share 'localhost' with
// target pages. This could cause difficult to debug problems as things like window.localStorage
// would be shared and requests would be "same-origin".
const frontendUrl = `https://i1.devtools-frontend.test:${testServerPort}/${devToolsAppURL}?ws=localhost:${
// We also use a unique ID per DevTools frontend instance, to avoid the same issue with other
// frontend instances.
const id = DevToolsFrontendTab.tabCounter++;
const frontendUrl = `https://i${id}.devtools-frontend.test:${testServerPort}/${devToolsAppURL}?ws=localhost:${
getDebugPort(browser)}/devtools/page/${targetId}`;
const frontend = await browser.newPage();
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2022 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.
// use require here due to
// https://github.com/evanw/esbuild/issues/587#issuecomment-901397213
import puppeteer = require('puppeteer');
import {DevToolsFrontendTab} from './frontend_tab.js';
import {TargetTab} from './target_tab.js';
interface FrontendTargetTabs {
frontend: DevToolsFrontendTab;
target: TargetTab;
}
export interface FrontedTargetPoolOptions {
browser: puppeteer.Browser;
testServerPort: number;
/**
* Defaults to FrontendTargetPool.POOL_SIZE.
* Setting `poolSize` to 0 will create target/frontend pairs on-demand.
*/
poolSize?: number;
}
/**
* A pool of DevTools frontend tab plus target tab pairs. Every time a pair
* is taken from the pool, the pool will automatically prepare another pair.
*
* Tab pairs are not intended to be reused and its the consumer's responsibility
* to clean them up properly.
*/
export class FrontendTargetPool {
private static readonly POOL_SIZE = 5;
#pool = new Pool<FrontendTargetTabs>();
#browser: puppeteer.Browser;
#testServerPort: number;
private constructor(browser: puppeteer.Browser, testServerPort: number) {
this.#browser = browser;
this.#testServerPort = testServerPort;
}
/** Returns a pool with `options.poolSize` tab pairs ready to go. */
static create(options: FrontedTargetPoolOptions): FrontendTargetPool {
const {browser, testServerPort, poolSize = FrontendTargetPool.POOL_SIZE} = options;
const tabPool = new FrontendTargetPool(browser, testServerPort);
for (let i = 0; i < poolSize; ++i) {
void tabPool.addTabPairToPool();
}
return tabPool;
}
private async addTabPairToPool(): Promise<void> {
const target = await TargetTab.create(this.#browser);
const frontend = await DevToolsFrontendTab.create(
{browser: this.#browser, testServerPort: this.#testServerPort, targetId: target.targetId()});
this.#pool.put({target, frontend});
}
async takeTabPair(): Promise<FrontendTargetTabs> {
const pair = this.#pool.take();
// We took a pair, so lets queue up the creation of a fresh pair.
// It's important that we do not block here, the fresh pair is prepared
// in the background.
// Also note that this approach allows a pool size of 0. For every
// `takeTabPair`, we call `addTabPairToPool`. The resulting pair is then
// used to resolve the `pair` promise of the `takeTabPair` call.
void this.addTabPairToPool();
return pair;
}
}
/**
* A simple Promise-based pool. Esentially a queue.
*
* When the pool is empty, consumers get a promise that resolves as soon as
* the pool is refilled.
*/
export class Pool<T> {
#pool: T[] = [];
#queue: Array<(value: T) => void> = [];
put(value: T) {
if (this.#queue.length > 0) {
const waitee = this.#queue.shift() as (value: T) => void;
waitee(value);
return;
}
this.#pool.push(value);
}
take(): Promise<T> {
if (this.#pool.length > 0) {
const value = this.#pool.shift() as T;
return Promise.resolve(value);
}
return new Promise(resolve => {
this.#queue.push(resolve);
});
}
}