mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-05 17:07:10 +03:00
feat: allow opening multiple html reporters and trace viewers (#17636)
This makes `HttpServer` accept `preferredPort` option that will first try to listen on that port, and if that port is already in use, listen on some available port instead. Fixes #17201.
This commit is contained in:
parent
bfd38bf7df
commit
3409a37f77
@ -393,7 +393,7 @@ export class GridServer {
|
||||
}
|
||||
|
||||
async start(port?: number) {
|
||||
await this._server.start(port);
|
||||
await this._server.start({ port });
|
||||
}
|
||||
|
||||
gridURL(): string {
|
||||
|
@ -26,7 +26,7 @@ import { serverSideCallMetadata } from '../../instrumentation';
|
||||
import { createPlaywright } from '../../playwright';
|
||||
import { ProgressController } from '../../progress';
|
||||
|
||||
export async function showTraceViewer(traceUrls: string[], browserName: string, headless = false, port?: number): Promise<BrowserContext | undefined> {
|
||||
export async function showTraceViewer(traceUrls: string[], browserName: string, headless = false, preferredPort?: number): Promise<BrowserContext | undefined> {
|
||||
for (const traceUrl of traceUrls) {
|
||||
if (!traceUrl.startsWith('http://') && !traceUrl.startsWith('https://') && !fs.existsSync(traceUrl)) {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -49,7 +49,7 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
||||
return server.serveFile(request, response, absolutePath);
|
||||
});
|
||||
|
||||
const urlPrefix = await server.start(port);
|
||||
const urlPrefix = await server.start({ preferredPort });
|
||||
|
||||
const traceViewerPlaywright = createPlaywright('javascript', true);
|
||||
const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName;
|
||||
|
@ -20,6 +20,7 @@ import path from 'path';
|
||||
import { mime, wsServer } from '../utilsBundle';
|
||||
import type { WebSocketServer } from '../utilsBundle';
|
||||
import { assert } from './';
|
||||
import { ManualPromise } from './manualPromise';
|
||||
|
||||
export type ServerRouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => boolean;
|
||||
|
||||
@ -51,15 +52,43 @@ export class HttpServer {
|
||||
return this._port;
|
||||
}
|
||||
|
||||
async start(port?: number, host = 'localhost'): Promise<string> {
|
||||
private async _tryStart(port: number | undefined, host: string) {
|
||||
const errorPromise = new ManualPromise();
|
||||
const errorListener = (error: Error) => errorPromise.reject(error);
|
||||
this._server.on('error', errorListener);
|
||||
|
||||
try {
|
||||
this._server.listen(port, host);
|
||||
await Promise.race([
|
||||
new Promise(cb => this._server!.once('listening', cb)),
|
||||
errorPromise,
|
||||
]);
|
||||
} finally {
|
||||
this._server.removeListener('error', errorListener);
|
||||
}
|
||||
}
|
||||
|
||||
async start(options: { port?: number, preferredPort?: number, host?: string } = {}): Promise<string> {
|
||||
assert(!this._started, 'server already started');
|
||||
this._started = true;
|
||||
this._server.on('connection', socket => {
|
||||
this._activeSockets.add(socket);
|
||||
socket.once('close', () => this._activeSockets.delete(socket));
|
||||
});
|
||||
this._server.listen(port, host);
|
||||
await new Promise(cb => this._server!.once('listening', cb));
|
||||
|
||||
const host = options.host || 'localhost';
|
||||
if (options.preferredPort) {
|
||||
try {
|
||||
await this._tryStart(options.preferredPort, host);
|
||||
} catch (e) {
|
||||
if (!e || !e.message || !e.message.includes('EADDRINUSE'))
|
||||
throw e;
|
||||
await this._tryStart(undefined, host);
|
||||
}
|
||||
} else {
|
||||
await this._tryStart(options.port, host);
|
||||
}
|
||||
|
||||
const address = this._server.address();
|
||||
assert(address, 'Could not bind server socket');
|
||||
if (!this._urlPrefix) {
|
||||
|
@ -149,7 +149,7 @@ function standaloneDefaultFolder(): string {
|
||||
return reportFolderFromEnv() ?? defaultReportFolder(process.cwd());
|
||||
}
|
||||
|
||||
export async function showHTMLReport(reportFolder: string | undefined, host: string = 'localhost', port: number = 9223, testId?: string) {
|
||||
export async function showHTMLReport(reportFolder: string | undefined, host: string = 'localhost', port?: number, testId?: string) {
|
||||
const folder = reportFolder ?? standaloneDefaultFolder();
|
||||
try {
|
||||
assert(fs.statSync(folder).isDirectory());
|
||||
@ -159,7 +159,7 @@ export async function showHTMLReport(reportFolder: string | undefined, host: str
|
||||
return;
|
||||
}
|
||||
const server = startHtmlReportServer(folder);
|
||||
let url = await server.start(port, host);
|
||||
let url = await server.start({ port, host, preferredPort: port ? undefined : 9223 });
|
||||
console.log('');
|
||||
console.log(colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
|
||||
if (testId)
|
||||
|
@ -29,7 +29,7 @@ type BaseWorkerFixtures = {
|
||||
};
|
||||
|
||||
export type TraceViewerFixtures = {
|
||||
showTraceViewer: (trace: string[]) => Promise<TraceViewerPage>;
|
||||
showTraceViewer: (trace: string[], preferredPort?: number) => Promise<TraceViewerPage>;
|
||||
runAndTrace: (body: () => Promise<void>) => Promise<TraceViewerPage>;
|
||||
};
|
||||
|
||||
@ -110,15 +110,19 @@ class TraceViewerPage {
|
||||
|
||||
export const traceViewerFixtures: Fixtures<TraceViewerFixtures, {}, BaseTestFixtures, BaseWorkerFixtures> = {
|
||||
showTraceViewer: async ({ playwright, browserName, headless }, use) => {
|
||||
let browser: Browser;
|
||||
let contextImpl: any;
|
||||
await use(async (traces: string[]) => {
|
||||
contextImpl = await showTraceViewer(traces, browserName, headless);
|
||||
browser = await playwright.chromium.connectOverCDP(contextImpl._browser.options.wsEndpoint);
|
||||
const browsers: Browser[] = [];
|
||||
const contextImpls: any[] = [];
|
||||
await use(async (traces: string[], preferredPort?: number) => {
|
||||
const contextImpl = await showTraceViewer(traces, browserName, headless, preferredPort);
|
||||
const browser = await playwright.chromium.connectOverCDP(contextImpl._browser.options.wsEndpoint);
|
||||
browsers.push(browser);
|
||||
contextImpls.push(contextImpl);
|
||||
return new TraceViewerPage(browser.contexts()[0].pages()[0]);
|
||||
});
|
||||
await browser?.close();
|
||||
await contextImpl?._browser.close();
|
||||
for (const browser of browsers)
|
||||
await browser.close();
|
||||
for (const contextImpl of contextImpls)
|
||||
await contextImpl._browser.close();
|
||||
},
|
||||
|
||||
runAndTrace: async ({ context, showTraceViewer }, use, testInfo) => {
|
||||
|
@ -78,6 +78,14 @@ test('should show empty trace viewer', async ({ showTraceViewer }, testInfo) =>
|
||||
await expect(traceViewer.page).toHaveTitle('Playwright Trace Viewer');
|
||||
});
|
||||
|
||||
test('should open two trace viewers', async ({ showTraceViewer }, testInfo) => {
|
||||
const preferredPort = testInfo.workerIndex + 48321;
|
||||
const traceViewer1 = await showTraceViewer([testInfo.outputPath()], preferredPort);
|
||||
await expect(traceViewer1.page).toHaveTitle('Playwright Trace Viewer');
|
||||
const traceViewer2 = await showTraceViewer([testInfo.outputPath()], preferredPort);
|
||||
await expect(traceViewer2.page).toHaveTitle('Playwright Trace Viewer');
|
||||
});
|
||||
|
||||
test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
||||
const traceViewer = await showTraceViewer([traceFile]);
|
||||
await expect(traceViewer.actionTitles).toHaveText([
|
||||
|
Loading…
Reference in New Issue
Block a user