chore: support reverse in ansi2html, drop ansi-to-html (#33389)
This commit is contained in:
parent
26c2049d5a
commit
c95feccce4
|
@ -2412,28 +2412,6 @@
|
|||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ansi-to-html": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz",
|
||||
"integrity": "sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==",
|
||||
"dependencies": {
|
||||
"entities": "^2.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"ansi-to-html": "bin/ansi-to-html"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-to-html/node_modules/entities": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
|
@ -7895,10 +7873,7 @@
|
|||
}
|
||||
},
|
||||
"packages/html-reporter": {
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.7.2"
|
||||
}
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"packages/playwright": {
|
||||
"version": "1.49.0-next",
|
||||
|
|
|
@ -7,8 +7,5 @@
|
|||
"dev": "vite",
|
||||
"build": "vite build && tsc",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.7.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
@import '@web/third_party/vscode/colors.css';
|
||||
|
||||
.test-error-view {
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import ansi2html from 'ansi-to-html';
|
||||
import { ansi2html } from '@web/ansi2html';
|
||||
import * as React from 'react';
|
||||
import './testErrorView.css';
|
||||
import type { ImageDiff } from '@web/shared/imageDiffView';
|
||||
|
@ -43,33 +43,9 @@ export const TestScreenshotErrorView: React.FC<{
|
|||
};
|
||||
|
||||
function ansiErrorToHtml(text?: string): string {
|
||||
const config: any = {
|
||||
const defaultColors = {
|
||||
bg: 'var(--color-canvas-subtle)',
|
||||
fg: 'var(--color-fg-default)',
|
||||
};
|
||||
config.colors = ansiColors;
|
||||
return new ansi2html(config).toHtml(escapeHTML(text || ''));
|
||||
}
|
||||
|
||||
const ansiColors = {
|
||||
0: '#000',
|
||||
1: '#C00',
|
||||
2: '#0C0',
|
||||
3: '#C50',
|
||||
4: '#00C',
|
||||
5: '#C0C',
|
||||
6: '#0CC',
|
||||
7: '#CCC',
|
||||
8: '#555',
|
||||
9: '#F55',
|
||||
10: '#5F5',
|
||||
11: '#FF5',
|
||||
12: '#55F',
|
||||
13: '#F5F',
|
||||
14: '#5FF',
|
||||
15: '#FFF'
|
||||
};
|
||||
|
||||
function escapeHTML(text: string): string {
|
||||
return text.replace(/[&"<>]/g, c => ({ '&': '&', '"': '"', '<': '<', '>': '>' }[c]!));
|
||||
return ansi2html(text || '', defaultColors);
|
||||
}
|
||||
|
|
|
@ -14,11 +14,16 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
export function ansi2html(text: string): string {
|
||||
export function ansi2html(text: string, defaultColors?: { bg: string, fg: string }): string {
|
||||
const regex = /(\x1b\[(\d+(;\d+)*)m)|([^\x1b]+)/g;
|
||||
const tokens: string[] = [];
|
||||
let match;
|
||||
let style: any = {};
|
||||
|
||||
let reverse = false;
|
||||
let fg: string | undefined = defaultColors?.fg;
|
||||
let bg: string | undefined = defaultColors?.bg;
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const [, , codeStr, , text] = match;
|
||||
if (codeStr) {
|
||||
|
@ -29,11 +34,28 @@ export function ansi2html(text: string): string {
|
|||
case 2: style['opacity'] = '0.8'; break;
|
||||
case 3: style['font-style'] = 'italic'; break;
|
||||
case 4: style['text-decoration'] = 'underline'; break;
|
||||
case 7:
|
||||
reverse = true;
|
||||
break;
|
||||
case 8: style.display = 'none'; break;
|
||||
case 9: style['text-decoration'] = 'line-through'; break;
|
||||
case 22: style = { ...style, 'font-weight': undefined, 'font-style': undefined, 'opacity': undefined, 'text-decoration': undefined }; break;
|
||||
case 23: style = { ...style, 'font-weight': undefined, 'font-style': undefined, 'opacity': undefined }; break;
|
||||
case 24: style = { ...style, 'text-decoration': undefined }; break;
|
||||
case 22:
|
||||
delete style['font-weight'];
|
||||
delete style['font-style'];
|
||||
delete style['opacity'];
|
||||
delete style['text-decoration'];
|
||||
break;
|
||||
case 23:
|
||||
delete style['font-weight'];
|
||||
delete style['font-style'];
|
||||
delete style['opacity'];
|
||||
break;
|
||||
case 24:
|
||||
delete style['text-decoration'];
|
||||
break;
|
||||
case 27:
|
||||
reverse = false;
|
||||
break;
|
||||
case 30:
|
||||
case 31:
|
||||
case 32:
|
||||
|
@ -41,8 +63,12 @@ export function ansi2html(text: string): string {
|
|||
case 34:
|
||||
case 35:
|
||||
case 36:
|
||||
case 37: style.color = ansiColors[code - 30]; break;
|
||||
case 39: style = { ...style, color: undefined }; break;
|
||||
case 37:
|
||||
fg = ansiColors[code - 30];
|
||||
break;
|
||||
case 39:
|
||||
fg = defaultColors?.fg;
|
||||
break;
|
||||
case 40:
|
||||
case 41:
|
||||
case 42:
|
||||
|
@ -50,8 +76,12 @@ export function ansi2html(text: string): string {
|
|||
case 44:
|
||||
case 45:
|
||||
case 46:
|
||||
case 47: style['background-color'] = ansiColors[code - 40]; break;
|
||||
case 49: style = { ...style, 'background-color': undefined }; break;
|
||||
case 47:
|
||||
bg = ansiColors[code - 40];
|
||||
break;
|
||||
case 49:
|
||||
bg = defaultColors?.bg;
|
||||
break;
|
||||
case 53: style['text-decoration'] = 'overline'; break;
|
||||
case 90:
|
||||
case 91:
|
||||
|
@ -60,7 +90,9 @@ export function ansi2html(text: string): string {
|
|||
case 94:
|
||||
case 95:
|
||||
case 96:
|
||||
case 97: style.color = brightAnsiColors[code - 90]; break;
|
||||
case 97:
|
||||
fg = brightAnsiColors[code - 90];
|
||||
break;
|
||||
case 100:
|
||||
case 101:
|
||||
case 102:
|
||||
|
@ -68,10 +100,19 @@ export function ansi2html(text: string): string {
|
|||
case 104:
|
||||
case 105:
|
||||
case 106:
|
||||
case 107: style['background-color'] = brightAnsiColors[code - 100]; break;
|
||||
case 107:
|
||||
bg = brightAnsiColors[code - 100];
|
||||
break;
|
||||
}
|
||||
} else if (text) {
|
||||
tokens.push(`<span style="${styleBody(style)}">${escapeHTML(text)}</span>`);
|
||||
const styleCopy = { ...style };
|
||||
const color = reverse ? bg : fg;
|
||||
if (color !== undefined)
|
||||
styleCopy['color'] = color;
|
||||
const backgroundColor = reverse ? fg : bg;
|
||||
if (backgroundColor !== undefined)
|
||||
styleCopy['background-color'] = backgroundColor;
|
||||
tokens.push(`<span style="${styleBody(styleCopy)}">${escapeHTML(text)}</span>`);
|
||||
}
|
||||
}
|
||||
return tokens.join('');
|
||||
|
|
|
@ -472,7 +472,7 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||
|
||||
await showReport();
|
||||
await page.click('text=fails');
|
||||
await expect(page.locator('.test-error-view span:has-text("received")').nth(1)).toHaveCSS('color', 'rgb(204, 0, 0)');
|
||||
await expect(page.locator('.test-error-view span:has-text("true")').first()).toHaveCSS('color', 'rgb(205, 49, 49)');
|
||||
});
|
||||
|
||||
test('should show trace source', async ({ runInlineTest, page, showReport }) => {
|
||||
|
@ -939,8 +939,9 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||
expect(result.exitCode).toBe(1);
|
||||
await showReport();
|
||||
await page.click('text="is a test"');
|
||||
const stricken = await page.locator('css=strike').innerText();
|
||||
expect(stricken).toBe('old');
|
||||
|
||||
await expect(page.locator('.test-error-view').getByText('old')).toHaveCSS('text-decoration', 'line-through solid rgb(205, 49, 49)');
|
||||
await expect(page.locator('.test-error-view').getByText('new', { exact: true })).toHaveCSS('text-decoration', 'none solid rgb(0, 188, 0)');
|
||||
});
|
||||
|
||||
test('should strikethrough textual diff with commonalities', async ({ runInlineTest, showReport, page }) => {
|
||||
|
@ -966,8 +967,32 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||
expect(result.exitCode).toBe(1);
|
||||
await showReport();
|
||||
await page.click('text="is a test"');
|
||||
const stricken = await page.locator('css=strike').innerText();
|
||||
expect(stricken).toBe('old');
|
||||
await expect(page.locator('.test-error-view').getByText('old')).toHaveCSS('text-decoration', 'line-through solid rgb(205, 49, 49)');
|
||||
await expect(page.locator('.test-error-view').getByText('new', { exact: true })).toHaveCSS('text-decoration', 'none solid rgb(0, 188, 0)');
|
||||
await expect(page.locator('.test-error-view').getByText('common Expected:')).toHaveCSS('text-decoration', 'none solid rgb(36, 41, 47)');
|
||||
});
|
||||
|
||||
test('should highlight inline textual diff in toHaveText', async ({ runInlineTest, showReport, page }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('is a test', async ({ page }) => {
|
||||
await page.setContent('<div>begin inner end</div>');
|
||||
await expect(page.locator('div')).toHaveText('inner', { timeout: 500 });
|
||||
});
|
||||
`
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
await showReport();
|
||||
await page.click('text="is a test"');
|
||||
await expect(page.locator('.test-error-view').getByText('begin ', { exact: true })).toHaveCSS('color', 'rgb(246, 248, 250)');
|
||||
await expect(page.locator('.test-error-view').getByText('begin ', { exact: true })).toHaveCSS('background-color', 'rgb(205, 49, 49)');
|
||||
|
||||
await expect(page.locator('.test-error-view').getByText('inner', { exact: true })).toHaveCSS('color', 'rgb(205, 49, 49)');
|
||||
await expect(page.locator('.test-error-view').getByText('inner', { exact: true })).toHaveCSS('background-color', 'rgb(246, 248, 250)');
|
||||
|
||||
await expect(page.locator('.test-error-view').getByText('end ', { exact: true })).toHaveCSS('color', 'rgb(246, 248, 250)');
|
||||
await expect(page.locator('.test-error-view').getByText('end ', { exact: true })).toHaveCSS('background-color', 'rgb(205, 49, 49)');
|
||||
});
|
||||
|
||||
test('should differentiate repeat-each test cases', async ({ runInlineTest, showReport, page }) => {
|
||||
|
@ -984,13 +1009,13 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||
expect(result.exitCode).toBe(1);
|
||||
await showReport();
|
||||
|
||||
await page.locator('text=sample').first().click();
|
||||
await expect(page.locator('text=ouch')).toHaveCount(1);
|
||||
await page.locator('text=All').first().click();
|
||||
await page.getByText('sample').first().click();
|
||||
await expect(page.getByText('ouch')).toHaveCount(2);
|
||||
await page.getByText('All').first().click();
|
||||
|
||||
await page.locator('text=sample').nth(1).click();
|
||||
await expect(page.locator('text=Before Hooks')).toBeVisible();
|
||||
await expect(page.locator('text=ouch')).toBeHidden();
|
||||
await page.getByText('sample').nth(1).click();
|
||||
await expect(page.getByText('Before Hooks')).toBeVisible();
|
||||
await expect(page.getByText('ouch')).toBeHidden();
|
||||
});
|
||||
|
||||
test('should group similar / loop steps', async ({ runInlineTest, showReport, page }) => {
|
||||
|
|
Loading…
Reference in New Issue