Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/playwright/src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function addTestServerCommand(program: Command) {
const command = program.command('test-server', { hidden: true });
command.description('start test server');
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.option('--host <host>', 'Host to start the server on', 'localhost');
command.option('--host <host>', 'Host to start the server on', '127.0.0.1');
command.option('--port <port>', 'Port to start the server on', '0');
command.action(async opts => {
await runTestServerAction(opts);
Expand Down
5 changes: 4 additions & 1 deletion packages/utils/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,10 @@ export function createHttp2Server(...args: any[]): http2.Http2SecureServer {
}

export async function startHttpServer(server: http.Server, options: { host?: string, port?: number }) {
const { host = 'localhost', port = 0 } = options;
// Prefer IPv4 loopback over 'localhost' to avoid ambiguous DNS resolution
// on dual-stack systems (some environments resolve 'localhost' to '::1'
// first and then fail to connect over IPv6 loopback).
const { host = '127.0.0.1', port = 0 } = options;
const errorPromise = new ManualPromise();
const errorListener = (error: Error) => errorPromise.reject(error);
server.on('error', errorListener);
Expand Down
14 changes: 14 additions & 0 deletions tests/playwright-test/test-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,17 @@ test('runGlobalSetup returns env', async ({ startTestServer, writeFiles }) => {
expect(result2.env).toContainEqual(['MAGIC_BEFORE', null]);
expect(result2.env).toContainEqual(['MAGIC_AFTER', '43']);
});

test('listens on 127.0.0.1 by default', async ({ startCLICommand, writeFiles }) => {
// 'localhost' resolution is ambiguous on dual-stack systems — some resolve
// to '::1' first, and IPv6 loopback has proven unreliable in certain
// environments (see https://github.com/microsoft/playwright/issues/40226).
// Default to IPv4 loopback so clients can always connect.
await writeFiles({ 'playwright.config.ts': `export default {};` });
const testServerProcess = await startCLICommand({}, 'test-server', []);
await testServerProcess.waitForOutput('Listening on');
const line = testServerProcess.output.split('\n').find(l => l.includes('Listening on'))!;
const wsEndpoint = line.split(' ')[2];
expect(wsEndpoint).toMatch(/^ws:\/\/127\.0\.0\.1:\d+\//);
await testServerProcess.kill();
});
Loading