diff --git a/packages/playwright-core/src/server/launchApp.ts b/packages/playwright-core/src/server/launchApp.ts index ee65f3ca27258..0431400d2198c 100644 --- a/packages/playwright-core/src/server/launchApp.ts +++ b/packages/playwright-core/src/server/launchApp.ts @@ -27,6 +27,48 @@ import type { CRPage } from './chromium/crPage'; import type { Page } from './page'; import type * as types from './types'; +/** + * Get Chromium rendering arguments for WSL UI mode workarounds. + * Addresses transparent window issues in WSL with Intel iGPU + discrete GPU. + * + * Environment variables (in precedence order): + * - PW_UI_DISABLE_GPU=1: Disable GPU entirely + * - PW_UI_USE_SWIFTSHADER=1: Force SwiftShader software rendering + * - PW_UI_USE_DISCRETE_GPU=1: Force discrete GPU selection + * - Auto-detect WSL and use SwiftShader as fallback + */ +function getWSLRenderingArgs(): string[] { + // Check explicit environment variable overrides first + if (process.env.PW_UI_DISABLE_GPU === '1') + return ['--disable-gpu', '--disable-software-rasterizer']; + + if (process.env.PW_UI_USE_SWIFTSHADER === '1') + return ['--use-gl=swiftshader']; + + if (process.env.PW_UI_USE_DISCRETE_GPU === '1') + return ['--use-gl=angle', '--use-angle=d3d11']; + + // Auto-detect WSL and apply SwiftShader fallback + if (isWSL()) + return ['--use-gl=swiftshader']; + + return []; +} + +/** + * Detect if running under WSL (Windows Subsystem for Linux) + */ +function isWSL(): boolean { + if (process.platform !== 'linux') + return false; + + return ( + fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop') || + (fs.existsSync('/proc/version') && + fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft')) || + !!process.env.WSL_DISTRO_NAME + ); +} export async function launchApp(browserType: BrowserType, options: { sdkLanguage: string, @@ -44,6 +86,12 @@ export async function launchApp(browserType: BrowserType, options: { ...(options.windowPosition ? [`--window-position=${options.windowPosition.x},${options.windowPosition.y}`] : []), '--test-type=', ); + // WSL UI rendering workarounds for transparent window issues + // See: https://github.com/microsoft/playwright/issues/37287 + const gpuArgs = getWSLRenderingArgs(); + if (gpuArgs.length > 0) + args.push(...gpuArgs); + if (!channel && !options.persistentContextOptions?.executablePath) channel = findChromiumChannelBestEffort(options.sdkLanguage); } diff --git a/tests/playwright-test/ui-mode-wsl-rendering.spec.ts b/tests/playwright-test/ui-mode-wsl-rendering.spec.ts new file mode 100644 index 0000000000000..9aab55c2904ff --- /dev/null +++ b/tests/playwright-test/ui-mode-wsl-rendering.spec.ts @@ -0,0 +1,208 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test as base, expect } from './ui-mode-fixtures'; +import fs from 'fs'; + +const test = base.extend<{}>({}); + +// Helper function to create a simple test file +const createTestFile = () => ({ + 'test.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('sample test', async ({ page }) => { + await page.goto('data:text/html,

Hello

'); + await expect(page.locator('h1')).toHaveText('Hello'); + }); + ` +}); + +// Helper function to verify UI mode loads correctly +const verifyUILoads = async (page: any) => { + await expect(page.locator('[data-testid="test-tree"]')).toBeVisible({ timeout: 10000 }); +}; + +test.describe('UI mode WSL rendering workarounds', () => { + test('should use SwiftShader by default on WSL', async ({ runUITest }) => { + const originalPlatform = process.platform; + const originalEnv = { ...process.env }; + + // Mock WSL environment + Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }); + process.env.WSL_DISTRO_NAME = 'Ubuntu'; + + try { + const { page } = await runUITest(createTestFile()); + await verifyUILoads(page); + } finally { + // Restore original environment + Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); + Object.assign(process.env, originalEnv); + } + }); + + test('should respect PW_UI_DISABLE_GPU environment variable', async ({ runUITest }) => { + const originalEnv = { ...process.env }; + process.env.PW_UI_DISABLE_GPU = '1'; + + try { + const { page } = await runUITest(createTestFile()); + await verifyUILoads(page); + } finally { + Object.assign(process.env, originalEnv); + } + }); + + test('should respect PW_UI_USE_SWIFTSHADER environment variable', async ({ runUITest }) => { + const originalEnv = { ...process.env }; + process.env.PW_UI_USE_SWIFTSHADER = '1'; + + try { + const { page } = await runUITest(createTestFile()); + await verifyUILoads(page); + } finally { + Object.assign(process.env, originalEnv); + } + }); + + test('should respect PW_UI_USE_DISCRETE_GPU environment variable', async ({ runUITest }) => { + const originalEnv = { ...process.env }; + process.env.PW_UI_USE_DISCRETE_GPU = '1'; + + try { + const { page } = await runUITest(createTestFile()); + await verifyUILoads(page); + } finally { + Object.assign(process.env, originalEnv); + } + }); + + test('should prioritize PW_UI_DISABLE_GPU over other options', async ({ runUITest }) => { + const originalEnv = { ...process.env }; + process.env.PW_UI_DISABLE_GPU = '1'; + process.env.PW_UI_USE_SWIFTSHADER = '1'; + process.env.PW_UI_USE_DISCRETE_GPU = '1'; + + try { + const { page } = await runUITest(createTestFile()); + await verifyUILoads(page); + } finally { + Object.assign(process.env, originalEnv); + } + }); + + test('should prioritize PW_UI_USE_SWIFTSHADER over PW_UI_USE_DISCRETE_GPU', async ({ runUITest }) => { + const originalEnv = { ...process.env }; + process.env.PW_UI_USE_SWIFTSHADER = '1'; + process.env.PW_UI_USE_DISCRETE_GPU = '1'; + + try { + const { page } = await runUITest(createTestFile()); + await verifyUILoads(page); + } finally { + Object.assign(process.env, originalEnv); + } + }); + + test('should detect WSL via WSLInterop file', async ({ runUITest }) => { + const originalPlatform = process.platform; + const originalEnv = { ...process.env }; + const originalExistsSync = fs.existsSync; + + // Mock WSL environment + Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }); + delete process.env.WSL_DISTRO_NAME; // Ensure other WSL detection is off + fs.existsSync = (path: string) => { + if (path === '/proc/sys/fs/binfmt_misc/WSLInterop') + return true; + return originalExistsSync(path); + }; + + try { + const { page } = await runUITest(createTestFile()); + await verifyUILoads(page); + } finally { + // Restore original environment + Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); + Object.assign(process.env, originalEnv); + fs.existsSync = originalExistsSync; + } + }); + + test('should detect WSL via /proc/version', async ({ runUITest }) => { + const originalPlatform = process.platform; + const originalEnv = { ...process.env }; + + // Mock WSL environment with Microsoft in /proc/version + Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }); + + // Mock fs.existsSync and fs.readFileSync for /proc/version + const originalExistsSync = fs.existsSync; + const originalReadFileSync = fs.readFileSync; + + fs.existsSync = (path: string) => { + if (path === '/proc/version') + return true; + + return originalExistsSync(path); + }; + + fs.readFileSync = (path: string, encoding?: any) => { + if (path === '/proc/version') + return 'Linux version 5.10.102.1-microsoft-standard-WSL2' as any; + + return originalReadFileSync(path, encoding); + }; + + try { + const { page } = await runUITest(createTestFile()); + await verifyUILoads(page); + } finally { + // Restore original environment + Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); + Object.assign(process.env, originalEnv); + fs.existsSync = originalExistsSync; + fs.readFileSync = originalReadFileSync; + } + }); + + test('should not apply WSL workarounds on non-Linux platforms', async ({ runUITest }) => { + const originalPlatform = process.platform; + const originalEnv = { ...process.env }; + + // Mock non-Linux platform + Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true }); + + try { + const { page } = await runUITest({ + 'test.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('sample test', async ({ page }) => { + await page.goto('data:text/html,

Hello

'); + await expect(page.locator('h1')).toHaveText('Hello'); + }); + ` + }); + + // Verify the UI mode loads correctly without WSL workarounds + await expect(page.locator('[data-testid="test-tree"]')).toBeVisible({ timeout: 10000 }); + } finally { + // Restore original environment + Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); + Object.assign(process.env, originalEnv); + } + }); +});