Skip to content

Commit 0523914

Browse files
authored
fix(cli): trim overlong session names to fit unix socket path limit (#40898)
1 parent 9afece0 commit 0523914

11 files changed

Lines changed: 45 additions & 28 deletions

File tree

packages/playwright-core/src/serverRegistry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class ServerRegistry extends EventEmitter {
171171
}
172172

173173
private _browsersDir() {
174-
return process.env.PLAYWRIGHT_SERVER_REGISTRY || registryDirectory;
174+
return process.env.PWTEST_SERVER_REGISTRY || registryDirectory;
175175
}
176176

177177
private _startWatcher() {

packages/playwright-core/src/tools/cli-client/registry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ export class Registry {
143143
}
144144

145145
export const baseDaemonDir = (() => {
146-
if (process.env.PLAYWRIGHT_DAEMON_SESSION_DIR)
147-
return process.env.PLAYWRIGHT_DAEMON_SESSION_DIR;
146+
if (process.env.PWTEST_DAEMON_SESSION_DIR)
147+
return process.env.PWTEST_DAEMON_SESSION_DIR;
148148

149149
let localCacheDir: string | undefined;
150150
if (process.platform === 'linux')

packages/playwright/src/util.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -186,16 +186,6 @@ export function expectTypes(receiver: any, types: ('APIResponse' | 'Page' | 'Loc
186186

187187
export const windowsFilesystemFriendlyLength = 60;
188188

189-
export function trimLongString(s: string, length = 100) {
190-
if (s.length <= length)
191-
return s;
192-
const hash = calculateSha1(s);
193-
const middle = `-${hash.substring(0, 5)}-`;
194-
const start = Math.floor((length - middle.length) / 2);
195-
const end = length - middle.length - start;
196-
return s.substring(0, start) + middle + s.slice(-end);
197-
}
198-
199189
export function addSuffixToFilePath(filePath: string, suffix: string): string {
200190
const ext = path.extname(filePath);
201191
const base = filePath.substring(0, filePath.length - ext.length);

packages/playwright/src/worker/testInfo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ import { captureRawStack, stringifyStackFrames } from '@isomorphic/stackTrace';
2222
import { escapeWithQuotes } from '@isomorphic/stringUtils';
2323
import { monotonicTime } from '@isomorphic/time';
2424
import { createGuid } from '@utils/crypto';
25-
import { sanitizeForFilePath } from '@utils/fileUtils';
25+
import { sanitizeForFilePath, trimLongString } from '@utils/fileUtils';
2626
import { currentZone } from '@utils/zones';
2727

2828
import { TimeoutManager, TimeoutManagerError } from './timeoutManager';
29-
import { addSuffixToFilePath, filteredStackTrace, getContainedPath, normalizeAndSaveAttachment, sanitizeFilePathBeforeExtension, trimLongString, windowsFilesystemFriendlyLength } from '../util';
29+
import { addSuffixToFilePath, filteredStackTrace, getContainedPath, normalizeAndSaveAttachment, sanitizeFilePathBeforeExtension, windowsFilesystemFriendlyLength } from '../util';
3030
import { TestTracing } from './testTracing';
3131
import { testInfoError } from './util';
3232
import { ipc, transform } from '../common';

packages/utils/fileUtils.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ export function sanitizeForFilePath(s: string) {
7878
return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
7979
}
8080

81+
export function trimLongString(s: string, length = 100) {
82+
if (s.length <= length)
83+
return s;
84+
const hash = calculateSha1(s);
85+
const middle = `-${hash.substring(0, 5)}-`;
86+
const start = Math.floor((length - middle.length) / 2);
87+
const end = length - middle.length - start;
88+
return s.substring(0, start) + middle + s.slice(-end);
89+
}
90+
8191
export function isPathInside(root: string, candidate: string): boolean {
8292
const resolvedRoot = path.resolve(root);
8393
const resolvedCandidate = path.resolve(candidate);
@@ -104,16 +114,24 @@ export function toPosixPath(aPath: string): string {
104114
return aPath.split(path.sep).join(path.posix.sep);
105115
}
106116

117+
// macOS sun_path is 104 bytes (Linux is 108) including the NUL terminator. Use the lower bound.
118+
const UNIX_SOCKET_PATH_MAX = 103;
119+
107120
export function makeSocketPath(domain: string, name: string): string {
108121
const userNameHash = calculateSha1(process.env.USERNAME || process.env.USER || 'default').slice(0, 8);
109122
if (process.platform === 'win32') {
110-
const socketsDir = process.env.PLAYWRIGHT_SOCKETS_DIR;
123+
const socketsDir = process.env.PWTEST_SOCKETS_DIR;
111124
const suffix = socketsDir ? `-${calculateSha1(socketsDir).slice(0, 8)}` : '';
112125
return `\\\\.\\pipe\\pw-${userNameHash}-${domain}-${name}${suffix}`;
113126
}
114-
const baseDir = process.env.PLAYWRIGHT_SOCKETS_DIR || path.join(os.tmpdir(), `pw-${userNameHash}`);
127+
const baseDir = process.env.PWTEST_SOCKETS_DIR || path.join(os.tmpdir(), `pw-${userNameHash}`);
115128
const dir = path.join(baseDir, domain);
116-
const result = path.join(dir, `${name}.sock`);
129+
const suffix = '.sock';
130+
const maxNameLength = UNIX_SOCKET_PATH_MAX - dir.length - path.sep.length - suffix.length;
131+
if (maxNameLength < 1)
132+
throw new Error(`Socket directory path is too long (${dir.length} chars); set PWTEST_SOCKETS_DIR to a shorter location.`);
133+
const fsFriendlyName = trimLongString(sanitizeForFilePath(name), maxNameLength);
134+
const result = path.join(dir, `${fsFriendlyName}${suffix}`);
117135
fs.mkdirSync(dir, { recursive: true });
118136
return result;
119137
}

tests/extension/extension-fixtures.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,11 @@ export const testWithOldExtensionVersion = test.extend({
146146

147147
function cliEnv() {
148148
return {
149-
PLAYWRIGHT_SERVER_REGISTRY: test.info().outputPath('registry'),
150-
PLAYWRIGHT_DAEMON_SESSION_DIR: test.info().outputPath('daemon'),
149+
PWTEST_SERVER_REGISTRY: test.info().outputPath('registry'),
150+
PWTEST_DAEMON_SESSION_DIR: test.info().outputPath('daemon'),
151151
// Short path because macOS caps unix socket paths at 104 chars; the
152152
// long `project.outputDir` path overflows and causes EADDRINUSE.
153-
PLAYWRIGHT_SOCKETS_DIR: path.join(os.tmpdir(), 'pwmcp-sock', String(test.info().parallelIndex)),
153+
PWTEST_SOCKETS_DIR: path.join(os.tmpdir(), 'pwmcp-sock', String(test.info().parallelIndex)),
154154
};
155155
}
156156

tests/library/browser-server.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ import { browserTest as it, expect } from '../config/browserTest';
2222
it.skip(({ mode }) => mode !== 'default');
2323

2424
it.beforeEach(({}, testInfo) => {
25-
process.env.PLAYWRIGHT_SERVER_REGISTRY = testInfo.outputPath('registry');
25+
process.env.PWTEST_SERVER_REGISTRY = testInfo.outputPath('registry');
2626
});
2727

2828
it('should start and stop pipe server', async ({ browserType, browser }) => {
2929
const serverInfo = await browser.bind('default', {});
3030
expect(serverInfo).toEqual(expect.objectContaining({
31-
endpoint: expect.stringMatching(/browser@/),
31+
endpoint: expect.stringMatching(/browser-/),
3232
}));
3333

3434
const browser2 = await browserType.connect(serverInfo.endpoint);

tests/mcp/cli-fixtures.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,10 @@ export const test = baseTest.extend<{
119119

120120
function cliEnv() {
121121
return {
122-
PLAYWRIGHT_SERVER_REGISTRY: test.info().outputPath('registry'),
122+
PWTEST_SERVER_REGISTRY: test.info().outputPath('registry'),
123123
PWTEST_DASHBOARD_SETTINGS_FILE: test.info().outputPath('dashboard.settings.json'),
124-
PLAYWRIGHT_DAEMON_SESSION_DIR: test.info().outputPath('daemon'),
125-
PLAYWRIGHT_SOCKETS_DIR: path.join(os.tmpdir(), 'ds-' + crypto.createHash('sha1').update(test.info().outputDir).digest('hex').slice(0, 16)),
124+
PWTEST_DAEMON_SESSION_DIR: test.info().outputPath('daemon'),
125+
PWTEST_SOCKETS_DIR: path.join(os.tmpdir(), 'ds-' + crypto.createHash('sha1').update(test.info().outputDir).digest('hex').slice(0, 16)),
126126
PWTEST_CLI_CHANNEL_SCAN_DISABLED_FOR_TEST: '1',
127127
};
128128
}

tests/mcp/cli-misc.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,12 @@ test('install handles browser detection', async ({ cli }) => {
6767
if (foundMatch?.[1] !== 'chrome')
6868
expect(output).toContain(`Created default config for ${foundMatch?.[1] ?? 'chromium'}.`);
6969
});
70+
71+
test('open with very long session name (issue 40878)', async ({ cli, server }) => {
72+
// Long session names push the unix socket path past sun_path's 104-byte limit on macOS.
73+
const longSessionName = 'awesome-coding-agent-orchestrators-with-an-overlong-suffix-for-testing';
74+
const result = await cli(`-s=${longSessionName}`, 'open', server.PREFIX);
75+
expect(result.error).toBe('');
76+
expect(result.exitCode).toBe(0);
77+
expect(result.output).toContain('Page URL');
78+
});

tests/mcp/cli-session.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ test('older client with newer daemon - list shows incompatible warning', async (
268268
test.describe('browser server', () => {
269269
test.beforeEach(async ({ mcpBrowser }, testInfo) => {
270270
test.skip(!['chrome', 'chromium', 'webkit', 'firefox'].includes(mcpBrowser));
271-
process.env.PLAYWRIGHT_SERVER_REGISTRY = testInfo.outputPath('registry');
271+
process.env.PWTEST_SERVER_REGISTRY = testInfo.outputPath('registry');
272272
});
273273

274274
test('list browser servers', async ({ cli, mcpBrowser }) => {

0 commit comments

Comments
 (0)