Skip to content
Closed
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
48 changes: 48 additions & 0 deletions packages/playwright-core/src/server/launchApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
}
Expand Down
208 changes: 208 additions & 0 deletions tests/playwright-test/ui-mode-wsl-rendering.spec.ts
Original file line number Diff line number Diff line change
@@ -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,<h1>Hello</h1>');
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,<h1>Hello</h1>');
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);
}
});
});
Loading