chore: support reverse in ansi2html, drop ansi-to-html (#33389)
This commit is contained in:
parent
26c2049d5a
commit
c95feccce4
27
package-lock.json
generated
27
package-lock.json
generated
|
|
@ -2412,28 +2412,6 @@
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
|
|
@ -7895,10 +7873,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/html-reporter": {
|
"packages/html-reporter": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0"
|
||||||
"dependencies": {
|
|
||||||
"ansi-to-html": "^0.7.2"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"packages/playwright": {
|
"packages/playwright": {
|
||||||
"version": "1.49.0-next",
|
"version": "1.49.0-next",
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,5 @@
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build && tsc",
|
"build": "vite build && tsc",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-to-html": "^0.7.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import '@web/third_party/vscode/colors.css';
|
||||||
|
|
||||||
.test-error-view {
|
.test-error-view {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ansi2html from 'ansi-to-html';
|
import { ansi2html } from '@web/ansi2html';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './testErrorView.css';
|
import './testErrorView.css';
|
||||||
import type { ImageDiff } from '@web/shared/imageDiffView';
|
import type { ImageDiff } from '@web/shared/imageDiffView';
|
||||||
|
|
@ -43,33 +43,9 @@ export const TestScreenshotErrorView: React.FC<{
|
||||||
};
|
};
|
||||||
|
|
||||||
function ansiErrorToHtml(text?: string): string {
|
function ansiErrorToHtml(text?: string): string {
|
||||||
const config: any = {
|
const defaultColors = {
|
||||||
bg: 'var(--color-canvas-subtle)',
|
bg: 'var(--color-canvas-subtle)',
|
||||||
fg: 'var(--color-fg-default)',
|
fg: 'var(--color-fg-default)',
|
||||||
};
|
};
|
||||||
config.colors = ansiColors;
|
return ansi2html(text || '', defaultColors);
|
||||||
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]!));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,16 @@
|
||||||
limitations under the License.
|
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 regex = /(\x1b\[(\d+(;\d+)*)m)|([^\x1b]+)/g;
|
||||||
const tokens: string[] = [];
|
const tokens: string[] = [];
|
||||||
let match;
|
let match;
|
||||||
let style: any = {};
|
let style: any = {};
|
||||||
|
|
||||||
|
let reverse = false;
|
||||||
|
let fg: string | undefined = defaultColors?.fg;
|
||||||
|
let bg: string | undefined = defaultColors?.bg;
|
||||||
|
|
||||||
while ((match = regex.exec(text)) !== null) {
|
while ((match = regex.exec(text)) !== null) {
|
||||||
const [, , codeStr, , text] = match;
|
const [, , codeStr, , text] = match;
|
||||||
if (codeStr) {
|
if (codeStr) {
|
||||||
|
|
@ -29,11 +34,28 @@ export function ansi2html(text: string): string {
|
||||||
case 2: style['opacity'] = '0.8'; break;
|
case 2: style['opacity'] = '0.8'; break;
|
||||||
case 3: style['font-style'] = 'italic'; break;
|
case 3: style['font-style'] = 'italic'; break;
|
||||||
case 4: style['text-decoration'] = 'underline'; break;
|
case 4: style['text-decoration'] = 'underline'; break;
|
||||||
|
case 7:
|
||||||
|
reverse = true;
|
||||||
|
break;
|
||||||
case 8: style.display = 'none'; break;
|
case 8: style.display = 'none'; break;
|
||||||
case 9: style['text-decoration'] = 'line-through'; 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 22:
|
||||||
case 23: style = { ...style, 'font-weight': undefined, 'font-style': undefined, 'opacity': undefined }; break;
|
delete style['font-weight'];
|
||||||
case 24: style = { ...style, 'text-decoration': undefined }; break;
|
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 30:
|
||||||
case 31:
|
case 31:
|
||||||
case 32:
|
case 32:
|
||||||
|
|
@ -41,8 +63,12 @@ export function ansi2html(text: string): string {
|
||||||
case 34:
|
case 34:
|
||||||
case 35:
|
case 35:
|
||||||
case 36:
|
case 36:
|
||||||
case 37: style.color = ansiColors[code - 30]; break;
|
case 37:
|
||||||
case 39: style = { ...style, color: undefined }; break;
|
fg = ansiColors[code - 30];
|
||||||
|
break;
|
||||||
|
case 39:
|
||||||
|
fg = defaultColors?.fg;
|
||||||
|
break;
|
||||||
case 40:
|
case 40:
|
||||||
case 41:
|
case 41:
|
||||||
case 42:
|
case 42:
|
||||||
|
|
@ -50,8 +76,12 @@ export function ansi2html(text: string): string {
|
||||||
case 44:
|
case 44:
|
||||||
case 45:
|
case 45:
|
||||||
case 46:
|
case 46:
|
||||||
case 47: style['background-color'] = ansiColors[code - 40]; break;
|
case 47:
|
||||||
case 49: style = { ...style, 'background-color': undefined }; break;
|
bg = ansiColors[code - 40];
|
||||||
|
break;
|
||||||
|
case 49:
|
||||||
|
bg = defaultColors?.bg;
|
||||||
|
break;
|
||||||
case 53: style['text-decoration'] = 'overline'; break;
|
case 53: style['text-decoration'] = 'overline'; break;
|
||||||
case 90:
|
case 90:
|
||||||
case 91:
|
case 91:
|
||||||
|
|
@ -60,7 +90,9 @@ export function ansi2html(text: string): string {
|
||||||
case 94:
|
case 94:
|
||||||
case 95:
|
case 95:
|
||||||
case 96:
|
case 96:
|
||||||
case 97: style.color = brightAnsiColors[code - 90]; break;
|
case 97:
|
||||||
|
fg = brightAnsiColors[code - 90];
|
||||||
|
break;
|
||||||
case 100:
|
case 100:
|
||||||
case 101:
|
case 101:
|
||||||
case 102:
|
case 102:
|
||||||
|
|
@ -68,10 +100,19 @@ export function ansi2html(text: string): string {
|
||||||
case 104:
|
case 104:
|
||||||
case 105:
|
case 105:
|
||||||
case 106:
|
case 106:
|
||||||
case 107: style['background-color'] = brightAnsiColors[code - 100]; break;
|
case 107:
|
||||||
|
bg = brightAnsiColors[code - 100];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (text) {
|
} 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('');
|
return tokens.join('');
|
||||||
|
|
|
||||||
|
|
@ -472,7 +472,7 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
|
|
||||||
await showReport();
|
await showReport();
|
||||||
await page.click('text=fails');
|
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 }) => {
|
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);
|
expect(result.exitCode).toBe(1);
|
||||||
await showReport();
|
await showReport();
|
||||||
await page.click('text="is a test"');
|
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 }) => {
|
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);
|
expect(result.exitCode).toBe(1);
|
||||||
await showReport();
|
await showReport();
|
||||||
await page.click('text="is a test"');
|
await page.click('text="is a test"');
|
||||||
const stricken = await page.locator('css=strike').innerText();
|
await expect(page.locator('.test-error-view').getByText('old')).toHaveCSS('text-decoration', 'line-through solid rgb(205, 49, 49)');
|
||||||
expect(stricken).toBe('old');
|
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 }) => {
|
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);
|
expect(result.exitCode).toBe(1);
|
||||||
await showReport();
|
await showReport();
|
||||||
|
|
||||||
await page.locator('text=sample').first().click();
|
await page.getByText('sample').first().click();
|
||||||
await expect(page.locator('text=ouch')).toHaveCount(1);
|
await expect(page.getByText('ouch')).toHaveCount(2);
|
||||||
await page.locator('text=All').first().click();
|
await page.getByText('All').first().click();
|
||||||
|
|
||||||
await page.locator('text=sample').nth(1).click();
|
await page.getByText('sample').nth(1).click();
|
||||||
await expect(page.locator('text=Before Hooks')).toBeVisible();
|
await expect(page.getByText('Before Hooks')).toBeVisible();
|
||||||
await expect(page.locator('text=ouch')).toBeHidden();
|
await expect(page.getByText('ouch')).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should group similar / loop steps', async ({ runInlineTest, showReport, page }) => {
|
test('should group similar / loop steps', async ({ runInlineTest, showReport, page }) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue