mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(html): add NOT filtering to HTML reporter (#35390)
This commit is contained in:
parent
62a8d4e01f
commit
3d603d1e5c
@ -15,12 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TestCaseSummary } from './types';
|
import type { TestCaseSummary } from './types';
|
||||||
|
|
||||||
|
type FilterToken = {
|
||||||
|
name: string;
|
||||||
|
not: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export class Filter {
|
export class Filter {
|
||||||
project: string[] = [];
|
project: FilterToken[] = [];
|
||||||
status: string[] = [];
|
status: FilterToken[] = [];
|
||||||
text: string[] = [];
|
text: FilterToken[] = [];
|
||||||
labels: string[] = [];
|
labels: FilterToken[] = [];
|
||||||
annotations: string[] = [];
|
annotations: FilterToken[] = [];
|
||||||
|
|
||||||
empty(): boolean {
|
empty(): boolean {
|
||||||
return this.project.length + this.status.length + this.text.length === 0;
|
return this.project.length + this.status.length + this.text.length === 0;
|
||||||
@ -28,29 +34,33 @@ export class Filter {
|
|||||||
|
|
||||||
static parse(expression: string): Filter {
|
static parse(expression: string): Filter {
|
||||||
const tokens = Filter.tokenize(expression);
|
const tokens = Filter.tokenize(expression);
|
||||||
const project = new Set<string>();
|
const project = new Set<FilterToken>();
|
||||||
const status = new Set<string>();
|
const status = new Set<FilterToken>();
|
||||||
const text: string[] = [];
|
const text: FilterToken[] = [];
|
||||||
const labels = new Set<string>();
|
const labels = new Set<FilterToken>();
|
||||||
const annotations = new Set<string>();
|
const annotations = new Set<FilterToken>();
|
||||||
for (const token of tokens) {
|
for (let token of tokens) {
|
||||||
|
const not = token.startsWith('!');
|
||||||
|
if (not)
|
||||||
|
token = token.slice(1);
|
||||||
|
|
||||||
if (token.startsWith('p:')) {
|
if (token.startsWith('p:')) {
|
||||||
project.add(token.slice(2));
|
project.add({ name: token.slice(2), not });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (token.startsWith('s:')) {
|
if (token.startsWith('s:')) {
|
||||||
status.add(token.slice(2));
|
status.add({ name: token.slice(2), not });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (token.startsWith('@')) {
|
if (token.startsWith('@')) {
|
||||||
labels.add(token);
|
labels.add({ name: token, not });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (token.startsWith('annot:')) {
|
if (token.startsWith('annot:')) {
|
||||||
annotations.add(token.slice('annot:'.length));
|
annotations.add({ name: token.slice('annot:'.length), not });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
text.push(token.toLowerCase());
|
text.push({ name: token.toLowerCase(), not });
|
||||||
}
|
}
|
||||||
|
|
||||||
const filter = new Filter();
|
const filter = new Filter();
|
||||||
@ -106,12 +116,18 @@ export class Filter {
|
|||||||
matches(test: TestCaseSummary): boolean {
|
matches(test: TestCaseSummary): boolean {
|
||||||
const searchValues = cacheSearchValues(test);
|
const searchValues = cacheSearchValues(test);
|
||||||
if (this.project.length) {
|
if (this.project.length) {
|
||||||
const matches = !!this.project.find(p => searchValues.project.includes(p));
|
const matches = !!this.project.find(p => {
|
||||||
|
const match = searchValues.project.includes(p.name);
|
||||||
|
return p.not ? !match : match;
|
||||||
|
});
|
||||||
if (!matches)
|
if (!matches)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.status.length) {
|
if (this.status.length) {
|
||||||
const matches = !!this.status.find(s => searchValues.status.includes(s));
|
const matches = !!this.status.find(s => {
|
||||||
|
const match = searchValues.status.includes(s.name);
|
||||||
|
return s.not ? !match : match;
|
||||||
|
});
|
||||||
if (!matches)
|
if (!matches)
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@ -119,23 +135,32 @@ export class Filter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.text.length) {
|
if (this.text.length) {
|
||||||
for (const text of this.text) {
|
const matches = this.text.every(text => {
|
||||||
if (searchValues.text.includes(text))
|
if (searchValues.text.includes(text.name))
|
||||||
continue;
|
return text.not ? false : true;
|
||||||
const [fileName, line, column] = text.split(':');
|
|
||||||
|
const [fileName, line, column] = text.name.split(':');
|
||||||
if (searchValues.file.includes(fileName) && searchValues.line === line && (column === undefined || searchValues.column === column))
|
if (searchValues.file.includes(fileName) && searchValues.line === line && (column === undefined || searchValues.column === column))
|
||||||
continue;
|
return text.not ? false : true;
|
||||||
|
|
||||||
|
return text.not ? true : false;
|
||||||
|
});
|
||||||
|
if (!matches)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (this.labels.length) {
|
if (this.labels.length) {
|
||||||
const matches = this.labels.every(l => searchValues.labels.includes(l));
|
const matches = this.labels.every(l => {
|
||||||
|
const match = searchValues.labels.includes(l.name);
|
||||||
|
return l.not ? !match : match;
|
||||||
|
});
|
||||||
if (!matches)
|
if (!matches)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.annotations.length) {
|
if (this.annotations.length) {
|
||||||
const matches = this.annotations.every(annotation =>
|
const matches = this.annotations.every(annotation => {
|
||||||
searchValues.annotations.some(a => a.includes(annotation)));
|
const match = searchValues.annotations.some(a => a.includes(annotation.name));
|
||||||
|
return annotation.not ? !match : match;
|
||||||
|
});
|
||||||
if (!matches)
|
if (!matches)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2010,7 +2010,7 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||||||
await expect(page.locator('.test-file-test')).toHaveCount(2);
|
await expect(page.locator('.test-file-test')).toHaveCount(2);
|
||||||
await expect(page.locator('.chip', { hasText: 'a.test.js' })).toHaveCount(1);
|
await expect(page.locator('.chip', { hasText: 'a.test.js' })).toHaveCount(1);
|
||||||
await expect(page.locator('.chip', { hasText: 'b.test.js' })).toHaveCount(1);
|
await expect(page.locator('.chip', { hasText: 'b.test.js' })).toHaveCount(1);
|
||||||
await expect(page.locator('.test-file-test .test-file-title')).toHaveCount(2);
|
await expect(page.locator('.test-file-test .test-file-title')).toHaveText(['@smoke fails', '@smoke passes']);
|
||||||
await expect(searchInput).toHaveValue('@smoke ');
|
await expect(searchInput).toHaveValue('@smoke ');
|
||||||
await expect(page).toHaveURL(/%40smoke/);
|
await expect(page).toHaveURL(/%40smoke/);
|
||||||
|
|
||||||
@ -2019,9 +2019,18 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||||||
await expect(page.locator('.test-file-test')).toHaveCount(2);
|
await expect(page.locator('.test-file-test')).toHaveCount(2);
|
||||||
await expect(page.locator('.chip', { hasText: 'a.test.js' })).toHaveCount(1);
|
await expect(page.locator('.chip', { hasText: 'a.test.js' })).toHaveCount(1);
|
||||||
await expect(page.locator('.chip', { hasText: 'b.test.js' })).toHaveCount(1);
|
await expect(page.locator('.chip', { hasText: 'b.test.js' })).toHaveCount(1);
|
||||||
await expect(page.locator('.test-file-test .test-file-title')).toHaveCount(2);
|
await expect(page.locator('.test-file-test .test-file-title')).toHaveText(['@regression fails', '@regression passes']);
|
||||||
await expect(searchInput).toHaveValue('@regression ');
|
await expect(searchInput).toHaveValue('@regression ');
|
||||||
await expect(page).toHaveURL(/%40regression/);
|
await expect(page).toHaveURL(/%40regression/);
|
||||||
|
|
||||||
|
await searchInput.fill('!@regression');
|
||||||
|
await searchInput.press('Enter');
|
||||||
|
await expect(page.locator('.test-file-test')).toHaveCount(2);
|
||||||
|
await expect(page.locator('.chip', { hasText: 'a.test.js' })).toHaveCount(1);
|
||||||
|
await expect(page.locator('.chip', { hasText: 'b.test.js' })).toHaveCount(1);
|
||||||
|
await expect(page.locator('.test-file-test .test-file-title')).toHaveText(['@smoke fails', '@smoke passes']);
|
||||||
|
await expect(searchInput).toHaveValue('!@regression ');
|
||||||
|
await expect(page).toHaveURL(/%21%40regression/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('if label contains similar words only one label should be selected', async ({ runInlineTest, showReport, page }) => {
|
test('if label contains similar words only one label should be selected', async ({ runInlineTest, showReport, page }) => {
|
||||||
@ -2421,13 +2430,33 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||||||
await expect(page.getByText('file-a.test.js', { exact: true })).toBeVisible();
|
await expect(page.getByText('file-a.test.js', { exact: true })).toBeVisible();
|
||||||
await expect(page.getByText('a test 1')).toBeVisible();
|
await expect(page.getByText('a test 1')).toBeVisible();
|
||||||
await expect(page.getByText('a test 2')).toBeVisible();
|
await expect(page.getByText('a test 2')).toBeVisible();
|
||||||
await expect(page.getByText('file-b.test.js', { exact: true })).not.toBeVisible();
|
await expect(page.getByText('file-b.test.js', { exact: true })).toBeHidden();
|
||||||
await expect(page.getByText('b test 1')).not.toBeVisible();
|
await expect(page.getByText('b test 1')).toBeHidden();
|
||||||
await expect(page.getByText('b test 2')).not.toBeVisible();
|
await expect(page.getByText('b test 2')).toBeHidden();
|
||||||
|
|
||||||
|
await searchInput.fill('!file-a');
|
||||||
|
await expect(page.getByText('file-a.test.js', { exact: true })).toBeHidden();
|
||||||
|
await expect(page.getByText('a test 1')).toBeHidden();
|
||||||
|
await expect(page.getByText('a test 2')).toBeHidden();
|
||||||
|
await expect(page.getByText('file-b.test.js', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('b test 1')).toBeVisible();
|
||||||
|
await expect(page.getByText('b test 2')).toBeVisible();
|
||||||
|
|
||||||
await searchInput.fill('file-a:3');
|
await searchInput.fill('file-a:3');
|
||||||
|
await expect(page.getByText('file-a.test.js', { exact: true })).toBeVisible();
|
||||||
await expect(page.getByText('a test 1')).toBeVisible();
|
await expect(page.getByText('a test 1')).toBeVisible();
|
||||||
await expect(page.getByText('a test 2')).not.toBeVisible();
|
await expect(page.getByText('a test 2')).toBeHidden();
|
||||||
|
await expect(page.getByText('file-b.test.js', { exact: true })).toBeHidden();
|
||||||
|
await expect(page.getByText('b test 1')).toBeHidden();
|
||||||
|
await expect(page.getByText('b test 2')).toBeHidden();
|
||||||
|
|
||||||
|
await searchInput.fill('!file-a:3');
|
||||||
|
await expect(page.getByText('file-a.test.js', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('a test 1')).toBeHidden();
|
||||||
|
await expect(page.getByText('a test 2')).toBeVisible();
|
||||||
|
await expect(page.getByText('file-b.test.js', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('b test 1')).toBeVisible();
|
||||||
|
await expect(page.getByText('b test 2')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tests should filter by status', async ({ runInlineTest, showReport, page }) => {
|
test('tests should filter by status', async ({ runInlineTest, showReport, page }) => {
|
||||||
@ -2449,17 +2478,22 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||||||
|
|
||||||
await searchInput.fill('s:failed');
|
await searchInput.fill('s:failed');
|
||||||
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
||||||
await expect(page.getByText('failed title')).not.toBeVisible();
|
await expect(page.getByText('failed title')).toBeHidden();
|
||||||
await expect(page.getByText('passes title')).toBeVisible();
|
await expect(page.getByText('passes title')).toBeVisible();
|
||||||
|
|
||||||
|
await searchInput.fill('!s:failed');
|
||||||
|
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('failed title')).toBeVisible();
|
||||||
|
await expect(page.getByText('passes title')).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tests should filter by annotation texts', async ({ runInlineTest, showReport, page }) => {
|
test('tests should filter by annotation texts', async ({ runInlineTest, showReport, page }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.js': `
|
'a.test.js': `
|
||||||
const { test, expect } = require('@playwright/test');
|
const { test, expect } = require('@playwright/test');
|
||||||
test('annotated test',{ annotation :[{type:'key',description:'value'}]}, async ({}) => {expect(1).toBe(1);});
|
test('with annotation',{ annotation :[{type:'key',description:'value'}]}, async ({}) => {expect(1).toBe(1);});
|
||||||
test('slow test', () => { test.slow(); });
|
test('slow test', () => { test.slow(); });
|
||||||
test('non-annotated test', async ({}) => {expect(1).toBe(2);});
|
test('without annotation', async ({}) => {expect(1).toBe(2);});
|
||||||
`,
|
`,
|
||||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||||
|
|
||||||
@ -2474,15 +2508,29 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||||||
await test.step('filter by type and value', async () => {
|
await test.step('filter by type and value', async () => {
|
||||||
await searchInput.fill('annot:key=value');
|
await searchInput.fill('annot:key=value');
|
||||||
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
||||||
await expect(page.getByText('non-annotated test')).not.toBeVisible();
|
await expect(page.getByText('without annotation')).toBeHidden();
|
||||||
await expect(page.getByText('annotated test')).toBeVisible();
|
await expect(page.getByText('with annotation')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('NOT filter by type and value', async () => {
|
||||||
|
await searchInput.fill('!annot:key=value');
|
||||||
|
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('without annotation')).toBeVisible();
|
||||||
|
await expect(page.getByText('with annotation')).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('filter by type', async () => {
|
await test.step('filter by type', async () => {
|
||||||
await searchInput.fill('annot:key');
|
await searchInput.fill('annot:key');
|
||||||
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
||||||
await expect(page.getByText('non-annotated test')).not.toBeVisible();
|
await expect(page.getByText('without annotation')).toBeHidden();
|
||||||
await expect(page.getByText('annotated test')).toBeVisible();
|
await expect(page.getByText('with annotation')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('NOT filter by type', async () => {
|
||||||
|
await searchInput.fill('!annot:key');
|
||||||
|
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('without annotation')).toBeVisible();
|
||||||
|
await expect(page.getByText('with annotation')).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('filter by result annotation', async () => {
|
await test.step('filter by result annotation', async () => {
|
||||||
@ -2516,9 +2564,17 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||||||
await expect(page.getByText('a.test.js:3', { exact: true })).toBeVisible();
|
await expect(page.getByText('a.test.js:3', { exact: true })).toBeVisible();
|
||||||
await expect(page.getByText('a.test.js:4', { exact: true })).toBeHidden();
|
await expect(page.getByText('a.test.js:4', { exact: true })).toBeHidden();
|
||||||
|
|
||||||
|
await searchInput.fill('!a.test.js:3');
|
||||||
|
await expect(page.getByText('a.test.js:3', { exact: true })).toBeHidden();
|
||||||
|
await expect(page.getByText('a.test.js:4', { exact: true })).toBeVisible();
|
||||||
|
|
||||||
await searchInput.fill('a.test.js:4:15');
|
await searchInput.fill('a.test.js:4:15');
|
||||||
await expect(page.getByText('a.test.js:3', { exact: true })).toBeHidden();
|
await expect(page.getByText('a.test.js:3', { exact: true })).toBeHidden();
|
||||||
await expect(page.getByText('a.test.js:4', { exact: true })).toBeVisible();
|
await expect(page.getByText('a.test.js:4', { exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
await searchInput.fill('!a.test.js:4:15');
|
||||||
|
await expect(page.getByText('a.test.js:3', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('a.test.js:4', { exact: true })).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should properly display beforeEach with and without title', async ({ runInlineTest, showReport, page }) => {
|
test('should properly display beforeEach with and without title', async ({ runInlineTest, showReport, page }) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user