mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(html): filter test cases by annotation text in HTML report (#30751)
This commit is contained in:
		
							parent
							
								
									af46dbef53
								
							
						
					
					
						commit
						0e0b426e47
					
				| @ -15,12 +15,12 @@ | |||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| import type { TestCaseSummary } from './types'; | import type { TestCaseSummary } from './types'; | ||||||
| 
 |  | ||||||
| export class Filter { | export class Filter { | ||||||
|   project: string[] = []; |   project: string[] = []; | ||||||
|   status: string[] = []; |   status: string[] = []; | ||||||
|   text: string[] = []; |   text: string[] = []; | ||||||
|   labels: string[] = []; |   labels: string[] = []; | ||||||
|  |   annotations: string[] = []; | ||||||
| 
 | 
 | ||||||
|   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; | ||||||
| @ -32,6 +32,7 @@ export class Filter { | |||||||
|     const status = new Set<string>(); |     const status = new Set<string>(); | ||||||
|     const text: string[] = []; |     const text: string[] = []; | ||||||
|     const labels = new Set<string>(); |     const labels = new Set<string>(); | ||||||
|  |     const annotations = new Set<string>(); | ||||||
|     for (const token of tokens) { |     for (const token of tokens) { | ||||||
|       if (token.startsWith('p:')) { |       if (token.startsWith('p:')) { | ||||||
|         project.add(token.slice(2)); |         project.add(token.slice(2)); | ||||||
| @ -45,6 +46,10 @@ export class Filter { | |||||||
|         labels.add(token); |         labels.add(token); | ||||||
|         continue; |         continue; | ||||||
|       } |       } | ||||||
|  |       if (token.startsWith('annot:')) { | ||||||
|  |         annotations.add(token.slice('annot:'.length)); | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|       text.push(token.toLowerCase()); |       text.push(token.toLowerCase()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -53,6 +58,7 @@ export class Filter { | |||||||
|     filter.project = [...project]; |     filter.project = [...project]; | ||||||
|     filter.status = [...status]; |     filter.status = [...status]; | ||||||
|     filter.labels = [...labels]; |     filter.labels = [...labels]; | ||||||
|  |     filter.annotations = [...annotations]; | ||||||
|     return filter; |     return filter; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -127,7 +133,12 @@ export class Filter { | |||||||
|       if (!matches) |       if (!matches) | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 |     if (this.annotations.length) { | ||||||
|  |       const matches = this.annotations.every(annotation => | ||||||
|  |         searchValues.annotations.some(a => a.includes(annotation))); | ||||||
|  |       if (!matches) | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -140,6 +151,7 @@ type SearchValues = { | |||||||
|   line: string; |   line: string; | ||||||
|   column: string; |   column: string; | ||||||
|   labels: string[]; |   labels: string[]; | ||||||
|  |   annotations: string[]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const searchValuesSymbol = Symbol('searchValues'); | const searchValuesSymbol = Symbol('searchValues'); | ||||||
| @ -164,6 +176,7 @@ function cacheSearchValues(test: TestCaseSummary): SearchValues { | |||||||
|     line: String(test.location.line), |     line: String(test.location.line), | ||||||
|     column: String(test.location.column), |     column: String(test.location.column), | ||||||
|     labels: test.tags.map(tag => tag.toLowerCase()), |     labels: test.tags.map(tag => tag.toLowerCase()), | ||||||
|  |     annotations: test.annotations.map(a => a.type.toLowerCase() + '=' + a.description?.toLocaleLowerCase()) | ||||||
|   }; |   }; | ||||||
|   (test as any)[searchValuesSymbol] = searchValues; |   (test as any)[searchValuesSymbol] = searchValues; | ||||||
|   return searchValues; |   return searchValues; | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ test('should render counters', async ({ mount }) => { | |||||||
|     flaky: 17, |     flaky: 17, | ||||||
|     skipped: 10, |     skipped: 10, | ||||||
|     ok: false, |     ok: false, | ||||||
|   }} filterText='' setFilterText={() => {}}></HeaderView>); |   }} filterText='' setFilterText={() => { }}></HeaderView>); | ||||||
|   await expect(component.locator('a', { hasText: 'All' }).locator('.counter')).toHaveText('90'); |   await expect(component.locator('a', { hasText: 'All' }).locator('.counter')).toHaveText('90'); | ||||||
|   await expect(component.locator('a', { hasText: 'Passed' }).locator('.counter')).toHaveText('42'); |   await expect(component.locator('a', { hasText: 'Passed' }).locator('.counter')).toHaveText('42'); | ||||||
|   await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31'); |   await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31'); | ||||||
| @ -59,5 +59,6 @@ test('should toggle filters', async ({ page, mount }) => { | |||||||
|   await expect(page).toHaveURL(/#\?q=s:flaky/); |   await expect(page).toHaveURL(/#\?q=s:flaky/); | ||||||
|   await component.locator('a', { hasText: 'Skipped' }).click(); |   await component.locator('a', { hasText: 'Skipped' }).click(); | ||||||
|   await expect(page).toHaveURL(/#\?q=s:skipped/); |   await expect(page).toHaveURL(/#\?q=s:skipped/); | ||||||
|   expect(filters).toEqual(['', 's:passed', 's:failed', 's:flaky', 's:skipped']); |   await component.getByRole('searchbox').fill('annot:annotation type=annotation description'); | ||||||
|  |   expect(filters).toEqual(['', 's:passed', 's:failed', 's:flaky', 's:skipped', 'annot:annotation type=annotation description']); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -1455,7 +1455,7 @@ for (const useIntermediateMergeReport of [false] as const) { | |||||||
|         await showReport(); |         await showReport(); | ||||||
| 
 | 
 | ||||||
|         const searchInput = page.locator('.subnav-search-input'); |         const searchInput = page.locator('.subnav-search-input'); | ||||||
|         const smokeLabelButton =  page.locator('.test-file-test', { has: page.getByText('Error Pages › @smoke fails', { exact: true }) }).locator('.label', { hasText: 'smoke' }); |         const smokeLabelButton = page.locator('.test-file-test', { has: page.getByText('Error Pages › @smoke fails', { exact: true }) }).locator('.label', { hasText: 'smoke' }); | ||||||
| 
 | 
 | ||||||
|         await expect(smokeLabelButton).toBeVisible(); |         await expect(smokeLabelButton).toBeVisible(); | ||||||
|         await smokeLabelButton.click(); |         await smokeLabelButton.click(); | ||||||
| @ -1465,7 +1465,7 @@ for (const useIntermediateMergeReport of [false] as const) { | |||||||
|         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')).toHaveText('Error Pages › @smoke fails'); |         await expect(page.locator('.test-file-test .test-file-title')).toHaveText('Error Pages › @smoke fails'); | ||||||
| 
 | 
 | ||||||
|         const regressionLabelButton =  page.locator('.test-file-test', { has: page.getByText('Error Pages › @regression passes', { exact: true }) }).locator('.label', { hasText: 'regression' }); |         const regressionLabelButton = page.locator('.test-file-test', { has: page.getByText('Error Pages › @regression passes', { exact: true }) }).locator('.label', { hasText: 'regression' }); | ||||||
| 
 | 
 | ||||||
|         await expect(regressionLabelButton).not.toBeVisible(); |         await expect(regressionLabelButton).not.toBeVisible(); | ||||||
| 
 | 
 | ||||||
| @ -1577,7 +1577,7 @@ for (const useIntermediateMergeReport of [false] as const) { | |||||||
| 
 | 
 | ||||||
|         const searchInput = page.locator('.subnav-search-input'); |         const searchInput = page.locator('.subnav-search-input'); | ||||||
| 
 | 
 | ||||||
|         const smokeLabelButton =  page.locator('.test-file-test', { has: page.getByText('@smoke fails', { exact: true }) }).locator('.label', { hasText: 'smoke' }); |         const smokeLabelButton = page.locator('.test-file-test', { has: page.getByText('@smoke fails', { exact: true }) }).locator('.label', { hasText: 'smoke' }); | ||||||
|         await smokeLabelButton.click(); |         await smokeLabelButton.click(); | ||||||
|         await expect(page).toHaveURL(/@smoke/); |         await expect(page).toHaveURL(/@smoke/); | ||||||
|         await searchInput.clear(); |         await searchInput.clear(); | ||||||
| @ -1585,7 +1585,7 @@ for (const useIntermediateMergeReport of [false] as const) { | |||||||
|         await expect(searchInput).toHaveValue(''); |         await expect(searchInput).toHaveValue(''); | ||||||
|         await expect(page).not.toHaveURL(/@smoke/); |         await expect(page).not.toHaveURL(/@smoke/); | ||||||
| 
 | 
 | ||||||
|         const regressionLabelButton =  page.locator('.test-file-test', { has: page.getByText('@regression passes', { exact: true }) }).locator('.label', { hasText: 'regression' }); |         const regressionLabelButton = page.locator('.test-file-test', { has: page.getByText('@regression passes', { exact: true }) }).locator('.label', { hasText: 'regression' }); | ||||||
|         await regressionLabelButton.click(); |         await regressionLabelButton.click(); | ||||||
|         await expect(page).toHaveURL(/@regression/); |         await expect(page).toHaveURL(/@regression/); | ||||||
|         await searchInput.clear(); |         await searchInput.clear(); | ||||||
| @ -1863,7 +1863,7 @@ for (const useIntermediateMergeReport of [false] as const) { | |||||||
|         await expect(page.locator('.test-file-test .test-file-title', { hasText: '@company_information_widget fails' })).toHaveCount(1); |         await expect(page.locator('.test-file-test .test-file-title', { hasText: '@company_information_widget fails' })).toHaveCount(1); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       test('handling of meta or ctrl key', async ({ runInlineTest, showReport, page,  }) => { |       test('handling of meta or ctrl key', async ({ runInlineTest, showReport, page, }) => { | ||||||
|         const result = await runInlineTest({ |         const result = await runInlineTest({ | ||||||
|           'a.test.js': ` |           'a.test.js': ` | ||||||
|             const { expect, test } = require('@playwright/test'); |             const { expect, test } = require('@playwright/test'); | ||||||
| @ -2214,6 +2214,29 @@ for (const useIntermediateMergeReport of [false] as const) { | |||||||
|       await expect(page.getByText('passes title')).toBeVisible(); |       await expect(page.getByText('passes title')).toBeVisible(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     test('tests should filter by annotation texts', async ({ runInlineTest, showReport, page }) => { | ||||||
|  |       const result = await runInlineTest({ | ||||||
|  |         'a.test.js': ` | ||||||
|  |           const { test, expect } = require('@playwright/test'); | ||||||
|  |           test('annotated test',{ annotation :[{type:'key',description:'value'}]}, async ({}) => {expect(1).toBe(1);}); | ||||||
|  |           test('non-annotated test', async ({}) => {expect(1).toBe(2);}); | ||||||
|  |         `,
 | ||||||
|  |       }, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' }); | ||||||
|  | 
 | ||||||
|  |       expect(result.exitCode).toBe(1); | ||||||
|  |       expect(result.passed).toBe(1); | ||||||
|  |       expect(result.failed).toBe(1); | ||||||
|  | 
 | ||||||
|  |       await showReport(); | ||||||
|  | 
 | ||||||
|  |       const searchInput = page.locator('.subnav-search-input'); | ||||||
|  | 
 | ||||||
|  |       await searchInput.fill('annot:key=value'); | ||||||
|  |       await expect(page.getByText('a.test.js', { exact: true })).toBeVisible(); | ||||||
|  |       await expect(page.getByText('non-annotated test')).not.toBeVisible(); | ||||||
|  |       await expect(page.getByText('annotated test')).toBeVisible(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     test('tests should filter by fileName:line/column', async ({ runInlineTest, showReport, page }) => { |     test('tests should filter by fileName:line/column', async ({ runInlineTest, showReport, page }) => { | ||||||
|       const result = await runInlineTest({ |       const result = await runInlineTest({ | ||||||
|         'a.test.js': ` |         'a.test.js': ` | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 amankagithub
						amankagithub