diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index 5c6127ddd5..603d39034a 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -23,7 +23,7 @@ import { Connection } from './connection'; import { Events } from './events'; import type { ChildProcess } from 'child_process'; import { envObjectToArray } from './clientHelper'; -import { assert, headersObjectToArray, monotonicTime } from '../utils'; +import { jsonStringifyForceASCII, assert, headersObjectToArray, monotonicTime } from '../utils'; import type * as api from '../../types/types'; import { kBrowserClosedError } from '../common/errors'; import { raceAgainstTimeout } from '../utils/timeoutRunner'; @@ -93,7 +93,8 @@ export class BrowserType extends ChannelOwner imple return this._connect({ wsEndpoint: connectOptions.wsEndpoint, headers: { - 'x-playwright-launch-options': JSON.stringify({ ...this._defaultLaunchOptions, ...launchOptions }), + // HTTP headers are ASCII only (not UTF-8). + 'x-playwright-launch-options': jsonStringifyForceASCII({ ...this._defaultLaunchOptions, ...launchOptions }), ...connectOptions.headers, }, _exposeNetwork: connectOptions._exposeNetwork, diff --git a/packages/playwright-core/src/utils/ascii.ts b/packages/playwright-core/src/utils/ascii.ts index 91291ec18a..97bc1944b1 100644 --- a/packages/playwright-core/src/utils/ascii.ts +++ b/packages/playwright-core/src/utils/ascii.ts @@ -23,3 +23,10 @@ export function wrapInASCIIBox(text: string, padding = 0): string { '╚' + '═'.repeat(maxLength + padding * 2) + '╝', ].join('\n'); } + +export function jsonStringifyForceASCII(object: any): string { + return JSON.stringify(object).replace( + /[\u007f-\uffff]/g, + c => '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4) + ); +} diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index ad64aaf04e..7111a3cec4 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -18,7 +18,7 @@ import * as fs from 'fs'; import * as path from 'path'; import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core'; import * as playwrightLibrary from 'playwright-core'; -import { createGuid, debugMode, addInternalStackPrefix, mergeTraceFiles, saveTraceFile, removeFolders, isString, asLocator } from 'playwright-core/lib/utils'; +import { createGuid, debugMode, addInternalStackPrefix, mergeTraceFiles, saveTraceFile, removeFolders, isString, asLocator, jsonStringifyForceASCII } from 'playwright-core/lib/utils'; import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, TraceMode, VideoMode } from '../types/test'; import type { TestInfoImpl } from './worker/testInfo'; import { rootTestType } from './common/testType'; @@ -125,7 +125,8 @@ const playwrightFixtures: Fixtures = ({ ...connectOptions, headers: { 'x-playwright-reuse-context': '1', - 'x-playwright-launch-options': JSON.stringify(_browserOptions), + // HTTP headers are ASCII only (not UTF-8). + 'x-playwright-launch-options': jsonStringifyForceASCII(_browserOptions), ...connectOptions.headers, }, }); diff --git a/tests/playwright-test/playwright.connect.spec.ts b/tests/playwright-test/playwright.connect.spec.ts index d73344ab9c..a69c638a0e 100644 --- a/tests/playwright-test/playwright.connect.spec.ts +++ b/tests/playwright-test/playwright.connect.spec.ts @@ -21,10 +21,19 @@ test('should work with connectOptions', async ({ runInlineTest }) => { 'playwright.config.js': ` module.exports = { globalSetup: './global-setup', + // outputDir is relative to the config file. Customers can have special characters in the path: + // See: https://github.com/microsoft/playwright/issues/24157 + outputDir: 'Привет', use: { connectOptions: { wsEndpoint: process.env.CONNECT_WS_ENDPOINT, }, + launchOptions: { + env: { + // Customers can have special characters: https://github.com/microsoft/playwright/issues/24157 + RANDOM_TEST_SPECIAL: 'Привет', + } + } }, }; `, diff --git a/tests/playwright-test/playwright.reuse.browser.spec.ts b/tests/playwright-test/playwright.reuse.browser.spec.ts index b75bc88df8..ecada652a4 100644 --- a/tests/playwright-test/playwright.reuse.browser.spec.ts +++ b/tests/playwright-test/playwright.reuse.browser.spec.ts @@ -61,3 +61,40 @@ test('should reuse browser', async ({ runInlineTest, runServer }) => { expect(guid2).toBe(guid1); expect(workerIndex2).not.toBe(workerIndex1); }); + +test('should reuse browser with special characters in the launch options', async ({ runInlineTest, runServer }) => { + const server = await runServer(); + const result = await runInlineTest({ + 'playwright.config.js': ` + module.exports = { + use: { + launchOptions: { + env: { + RANDOM_TEST_SPECIAL: 'Привет', + } + } + } + } + `, + 'src/a.test.ts': ` + import { test, expect } from '@playwright/test'; + test('a', async ({ browser }) => { + console.log('%%' + process.env.TEST_WORKER_INDEX + ':' + browser._guid); + }); + `, + 'src/b.test.ts': ` + import { test, expect } from '@playwright/test'; + test('b', async ({ browser }) => { + console.log('%%' + process.env.TEST_WORKER_INDEX + ':' + browser._guid); + }); + `, + }, { workers: 2 }, { PW_TEST_REUSE_CONTEXT: '1', PW_TEST_CONNECT_WS_ENDPOINT: server.wsEndpoint() }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(2); + expect(result.outputLines).toHaveLength(2); + const [workerIndex1, guid1] = result.outputLines[0].split(':'); + const [workerIndex2, guid2] = result.outputLines[1].split(':'); + expect(guid2).toBe(guid1); + expect(workerIndex2).not.toBe(workerIndex1); +});