110 lines
4.4 KiB
TypeScript
110 lines
4.4 KiB
TypeScript
/**
|
|
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*
|
|
* WebGL device-pixel-ratio (HiDPI) and font-size ("resize") atlas tests.
|
|
*
|
|
* A devicePixelRatio > 1 and font-size changes both change the device cell
|
|
* dimensions, which key the texture atlas cache and force the atlas to be
|
|
* rebuilt. Historically these paths have produced blank/oversized/garbled glyphs
|
|
* (e.g. xtermjs#4728, #5915/#5929). These tests assert glyphs render and stay
|
|
* correct across those transitions.
|
|
*
|
|
* Chromium + SwiftShader only (additive scope; Firefox/WebKit WebGL broken in CI
|
|
* per xtermjs#5854).
|
|
*/
|
|
|
|
import test, { Browser, expect } from '@playwright/test';
|
|
import { ITestContext, TerminalProxy, openTerminal } from '../../../test/playwright/TestUtils';
|
|
import {
|
|
assertTextureAtlasPresent,
|
|
captureRowSignatures,
|
|
expectSignatureMatches,
|
|
loadWebglStrict,
|
|
waitForRender,
|
|
writeAndWaitForRender
|
|
} from '../../../test/playwright/RendererTestUtils';
|
|
|
|
const REFERENCE_COLS = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
|
|
/**
|
|
* Creates a test context whose page uses a specific devicePixelRatio. The shared
|
|
* `createTestContext` helper always uses DPR 1 (`browser.newPage()`), so HiDPI
|
|
* tests build their own context here.
|
|
*/
|
|
async function createDprContext(browser: Browser, deviceScaleFactor: number): Promise<ITestContext> {
|
|
const context = await browser.newContext({ deviceScaleFactor });
|
|
const page = await context.newPage();
|
|
page.on('pageerror', e => console.error(`[dpr ${deviceScaleFactor}]`, e));
|
|
await page.goto('/test');
|
|
const proxy = new TerminalProxy(page);
|
|
proxy.initPage();
|
|
return {
|
|
browser,
|
|
page,
|
|
termHandle: await page.evaluateHandle('window.term'),
|
|
proxy
|
|
};
|
|
}
|
|
|
|
test.describe('WebGL HiDPI (devicePixelRatio) rendering', () => {
|
|
test.skip(({ browserName }) => browserName !== 'chromium', 'WebGL atlas tests run on Chromium/SwiftShader only');
|
|
|
|
test('initializes the WebGL renderer and scales to device pixels at devicePixelRatio 2', async ({ browser }) => {
|
|
const ctx = await createDprContext(browser, 2);
|
|
try {
|
|
await openTerminal(ctx, { cols: 80, rows: 24 });
|
|
// GPU must be genuinely active at HiDPI (asserted inside loadWebglStrict).
|
|
await loadWebglStrict(ctx);
|
|
await writeAndWaitForRender(ctx, 'A W .');
|
|
await assertTextureAtlasPresent(ctx);
|
|
|
|
// The WebGL renderer must render at the device resolution: device cell
|
|
// dimensions should be ~2x the CSS cell dimensions. This catches the
|
|
// DPR-scaling class of bugs (e.g. xtermjs#4728) without relying on
|
|
// screenshotting a WebGL canvas at a non-1 device scale factor.
|
|
const dims = await ctx.proxy.core.renderDimensions;
|
|
expect(dims.device.cell.width).toBeGreaterThan(dims.css.cell.width * 1.8);
|
|
expect(dims.device.cell.width).toBeLessThan(dims.css.cell.width * 2.2);
|
|
expect(dims.device.cell.height).toBeGreaterThan(dims.css.cell.height * 1.8);
|
|
expect(dims.device.cell.height).toBeLessThan(dims.css.cell.height * 2.2);
|
|
} finally {
|
|
await ctx.page.context().close();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('WebGL font-size change atlas integrity', () => {
|
|
let ctx: ITestContext;
|
|
test.skip(({ browserName }) => browserName !== 'chromium', 'WebGL atlas tests run on Chromium/SwiftShader only');
|
|
|
|
test.beforeAll(async ({ browser }) => {
|
|
ctx = await createDprContext(browser, 1);
|
|
});
|
|
test.afterAll(async () => await ctx.page.close());
|
|
|
|
test.beforeEach(async () => {
|
|
await openTerminal(ctx, { cols: 80, rows: 24, fontSize: 15 });
|
|
await loadWebglStrict(ctx);
|
|
});
|
|
|
|
test('glyphs stay correct after cycling font size (atlas rebuilds)', async () => {
|
|
await writeAndWaitForRender(ctx, 'FontRef0123456789');
|
|
const reference = await captureRowSignatures(ctx, 1, REFERENCE_COLS);
|
|
|
|
// Changing font size changes device cell dimensions -> the atlas is rebuilt.
|
|
for (const fontSize of [12, 18, 21, 15]) {
|
|
await ctx.proxy.setOption('fontSize', fontSize);
|
|
await writeAndWaitForRender(ctx, '\r\nchurn');
|
|
}
|
|
|
|
// Back at the original font size the reference glyphs must be unchanged.
|
|
await ctx.proxy.scrollToTop();
|
|
await waitForRender(ctx);
|
|
const after = await captureRowSignatures(ctx, 1, REFERENCE_COLS);
|
|
for (let i = 0; i < REFERENCE_COLS.length; i++) {
|
|
expectSignatureMatches(after[i], reference[i], 16, `reference glyph at col ${REFERENCE_COLS[i]} corrupted after font-size cycling`);
|
|
}
|
|
});
|
|
});
|