mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00: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) {
|
async start(port?: number) {
|
||||||
await this._server.start(port);
|
await this._server.start({ port });
|
||||||
}
|
}
|
||||||
|
|
||||||
gridURL(): string {
|
gridURL(): string {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { serverSideCallMetadata } from '../../instrumentation';
|
|||||||
import { createPlaywright } from '../../playwright';
|
import { createPlaywright } from '../../playwright';
|
||||||
import { ProgressController } from '../../progress';
|
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) {
|
for (const traceUrl of traceUrls) {
|
||||||
if (!traceUrl.startsWith('http://') && !traceUrl.startsWith('https://') && !fs.existsSync(traceUrl)) {
|
if (!traceUrl.startsWith('http://') && !traceUrl.startsWith('https://') && !fs.existsSync(traceUrl)) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -49,7 +49,7 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
|||||||
return server.serveFile(request, response, absolutePath);
|
return server.serveFile(request, response, absolutePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
const urlPrefix = await server.start(port);
|
const urlPrefix = await server.start({ preferredPort });
|
||||||
|
|
||||||
const traceViewerPlaywright = createPlaywright('javascript', true);
|
const traceViewerPlaywright = createPlaywright('javascript', true);
|
||||||
const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName;
|
const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import path from 'path';
|
|||||||
import { mime, wsServer } from '../utilsBundle';
|
import { mime, wsServer } from '../utilsBundle';
|
||||||
import type { WebSocketServer } from '../utilsBundle';
|
import type { WebSocketServer } from '../utilsBundle';
|
||||||
import { assert } from './';
|
import { assert } from './';
|
||||||
|
import { ManualPromise } from './manualPromise';
|
||||||
|
|
||||||
export type ServerRouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => boolean;
|
export type ServerRouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => boolean;
|
||||||
|
|
||||||
@ -51,15 +52,43 @@ export class HttpServer {
|
|||||||
return this._port;
|
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');
|
assert(!this._started, 'server already started');
|
||||||
this._started = true;
|
this._started = true;
|
||||||
this._server.on('connection', socket => {
|
this._server.on('connection', socket => {
|
||||||
this._activeSockets.add(socket);
|
this._activeSockets.add(socket);
|
||||||
socket.once('close', () => this._activeSockets.delete(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();
|
const address = this._server.address();
|
||||||
assert(address, 'Could not bind server socket');
|
assert(address, 'Could not bind server socket');
|
||||||
if (!this._urlPrefix) {
|
if (!this._urlPrefix) {
|
||||||
|
|||||||
@ -149,7 +149,7 @@ function standaloneDefaultFolder(): string {
|
|||||||
return reportFolderFromEnv() ?? defaultReportFolder(process.cwd());
|
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();
|
const folder = reportFolder ?? standaloneDefaultFolder();
|
||||||
try {
|
try {
|
||||||
assert(fs.statSync(folder).isDirectory());
|
assert(fs.statSync(folder).isDirectory());
|
||||||
@ -159,7 +159,7 @@ export async function showHTMLReport(reportFolder: string | undefined, host: str
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const server = startHtmlReportServer(folder);
|
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('');
|
||||||
console.log(colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
|
console.log(colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
|
||||||
if (testId)
|
if (testId)
|
||||||
|
|||||||
@ -29,7 +29,7 @@ type BaseWorkerFixtures = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TraceViewerFixtures = {
|
export type TraceViewerFixtures = {
|
||||||
showTraceViewer: (trace: string[]) => Promise<TraceViewerPage>;
|
showTraceViewer: (trace: string[], preferredPort?: number) => Promise<TraceViewerPage>;
|
||||||
runAndTrace: (body: () => Promise<void>) => Promise<TraceViewerPage>;
|
runAndTrace: (body: () => Promise<void>) => Promise<TraceViewerPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,15 +110,19 @@ class TraceViewerPage {
|
|||||||
|
|
||||||
export const traceViewerFixtures: Fixtures<TraceViewerFixtures, {}, BaseTestFixtures, BaseWorkerFixtures> = {
|
export const traceViewerFixtures: Fixtures<TraceViewerFixtures, {}, BaseTestFixtures, BaseWorkerFixtures> = {
|
||||||
showTraceViewer: async ({ playwright, browserName, headless }, use) => {
|
showTraceViewer: async ({ playwright, browserName, headless }, use) => {
|
||||||
let browser: Browser;
|
const browsers: Browser[] = [];
|
||||||
let contextImpl: any;
|
const contextImpls: any[] = [];
|
||||||
await use(async (traces: string[]) => {
|
await use(async (traces: string[], preferredPort?: number) => {
|
||||||
contextImpl = await showTraceViewer(traces, browserName, headless);
|
const contextImpl = await showTraceViewer(traces, browserName, headless, preferredPort);
|
||||||
browser = await playwright.chromium.connectOverCDP(contextImpl._browser.options.wsEndpoint);
|
const browser = await playwright.chromium.connectOverCDP(contextImpl._browser.options.wsEndpoint);
|
||||||
|
browsers.push(browser);
|
||||||
|
contextImpls.push(contextImpl);
|
||||||
return new TraceViewerPage(browser.contexts()[0].pages()[0]);
|
return new TraceViewerPage(browser.contexts()[0].pages()[0]);
|
||||||
});
|
});
|
||||||
await browser?.close();
|
for (const browser of browsers)
|
||||||
await contextImpl?._browser.close();
|
await browser.close();
|
||||||
|
for (const contextImpl of contextImpls)
|
||||||
|
await contextImpl._browser.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
runAndTrace: async ({ context, showTraceViewer }, use, testInfo) => {
|
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');
|
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 }) => {
|
test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
||||||
const traceViewer = await showTraceViewer([traceFile]);
|
const traceViewer = await showTraceViewer([traceFile]);
|
||||||
await expect(traceViewer.actionTitles).toHaveText([
|
await expect(traceViewer.actionTitles).toHaveText([
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user