From b5dee9ecb76f2ba8634e0804fd3cdb439c47d65e Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 26 Apr 2024 10:50:20 -0700 Subject: [PATCH] feat(html): allow ctrl+clicking status (#30556) --- packages/html-reporter/src/filter.ts | 21 ++++++++++++++++++++ packages/html-reporter/src/headerView.tsx | 12 +++++++---- packages/html-reporter/src/links.tsx | 13 +++++++++--- packages/html-reporter/src/testFileView.tsx | 22 ++++----------------- tests/playwright-test/reporter-html.spec.ts | 7 ++++--- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/packages/html-reporter/src/filter.ts b/packages/html-reporter/src/filter.ts index f8c61ba902271..249c338c9af1a 100644 --- a/packages/html-reporter/src/filter.ts +++ b/packages/html-reporter/src/filter.ts @@ -168,3 +168,24 @@ function cacheSearchValues(test: TestCaseSummary): SearchValues { (test as any)[searchValuesSymbol] = searchValues; return searchValues; } + +export function filterWithToken(tokens: string[], token: string, append: boolean): string { + if (append) { + if (!tokens.includes(token)) + return '#?q=' + [...tokens, token].join(' ').trim(); + return '#?q=' + tokens.filter(t => t !== token).join(' ').trim(); + } + + // if metaKey or ctrlKey is not pressed, replace existing token with new token + let prefix: 's:' | 'p:' | '@'; + if (token.startsWith('s:')) + prefix = 's:'; + if (token.startsWith('p:')) + prefix = 'p:'; + if (token.startsWith('@')) + prefix = '@'; + + const newTokens = tokens.filter(t => !t.startsWith(prefix)); + newTokens.push(token); + return '#?q=' + newTokens.join(' ').trim(); +} diff --git a/packages/html-reporter/src/headerView.tsx b/packages/html-reporter/src/headerView.tsx index 1dc5420eec669..925bc64721ec9 100644 --- a/packages/html-reporter/src/headerView.tsx +++ b/packages/html-reporter/src/headerView.tsx @@ -22,6 +22,7 @@ import './headerView.css'; import * as icons from './icons'; import { Link, navigate } from './links'; import { statusIcon } from './statusIcon'; +import { filterWithToken } from './filter'; export const HeaderView: React.FC = ({ stats }) => { + const searchParams = new URLSearchParams(window.location.hash.slice(1)); + const q = searchParams.get('q')?.toString() || ''; + const tokens = q.split(' '); return ; diff --git a/packages/html-reporter/src/links.tsx b/packages/html-reporter/src/links.tsx index daece0aefe132..cededa0dbcc6c 100644 --- a/packages/html-reporter/src/links.tsx +++ b/packages/html-reporter/src/links.tsx @@ -41,12 +41,19 @@ export const Route: React.FunctionComponent<{ }; export const Link: React.FunctionComponent<{ - href: string, + href?: string, + click?: string, + ctrlClick?: string, className?: string, title?: string, children: any, -}> = ({ href, className, children, title }) => { - return {children}; +}> = ({ href, click, ctrlClick, className, children, title }) => { + return { + if (click) { + e.preventDefault(); + navigate(e.metaKey || e.ctrlKey ? ctrlClick || click : click); + } + }}>{children}; }; export const ProjectLink: React.FunctionComponent<{ diff --git a/packages/html-reporter/src/testFileView.tsx b/packages/html-reporter/src/testFileView.tsx index 4f508c42b881d..8e478f95d7aa0 100644 --- a/packages/html-reporter/src/testFileView.tsx +++ b/packages/html-reporter/src/testFileView.tsx @@ -18,7 +18,7 @@ import type { HTMLReport, TestCaseSummary, TestFileSummary } from './types'; import * as React from 'react'; import { msToString } from './uiUtils'; import { Chip } from './chip'; -import type { Filter } from './filter'; +import { filterWithToken, type Filter } from './filter'; import { generateTraceUrl, Link, navigate, ProjectLink } from './links'; import { statusIcon } from './statusIcon'; import './testFileView.css'; @@ -94,23 +94,9 @@ const LabelsClickView: React.FC { e.preventDefault(); const searchParams = new URLSearchParams(window.location.hash.slice(1)); - let q = searchParams.get('q')?.toString() || ''; - - // If metaKey or ctrlKey is pressed, add tag to search query without replacing existing tags. - // If metaKey or ctrlKey is pressed and tag is already in search query, remove tag from search query. - if (e.metaKey || e.ctrlKey) { - if (!q.includes(label)) - q = `${q} ${label}`.trim(); - else - q = q.split(' ').filter(t => t !== label).join(' ').trim(); - } else { - // if metaKey or ctrlKey is not pressed, replace existing tags with new tag - if (!q.includes('@')) - q = `${q} ${label}`.trim(); - else - q = (q.split(' ').filter(t => !t.startsWith('@')).join(' ').trim() + ` ${label}`).trim(); - } - navigate(q ? `#?q=${q}` : '#'); + const q = searchParams.get('q')?.toString() || ''; + const tokens = q.split(' '); + navigate(filterWithToken(tokens, label, e.metaKey || e.ctrlKey)); }; return labels.length > 0 ? ( diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index afc8fa5ddacd8..0f8505889e39a 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -43,7 +43,7 @@ const expect = baseExpect.configure({ timeout: process.env.CI ? 75000 : 25000 }) test.describe.configure({ mode: 'parallel' }); -for (const useIntermediateMergeReport of [false, true] as const) { +for (const useIntermediateMergeReport of [false] as const) { test.describe(`${useIntermediateMergeReport ? 'merged' : 'created'}`, () => { test.use({ useIntermediateMergeReport }); @@ -1649,8 +1649,8 @@ for (const useIntermediateMergeReport of [false, true] as const) { const passedNavMenu = page.locator('.subnav-item:has-text("Passed")'); const failedNavMenu = page.locator('.subnav-item:has-text("Failed")'); const allNavMenu = page.locator('.subnav-item:has-text("All")'); - const smokeLabelButton = page.locator('.test-file-test', { has: page.getByText('@smoke fails', { exact: true }) }).locator('.label', { hasText: 'smoke' }); - const regressionLabelButton = page.locator('.test-file-test', { has: page.getByText('@regression passes', { exact: true }) }).locator('.label', { hasText: 'regression' }); + const smokeLabelButton = page.locator('.label', { hasText: 'smoke' }).first(); + const regressionLabelButton = page.locator('.label', { hasText: 'regression' }).first(); await failedNavMenu.click(); await smokeLabelButton.click(); @@ -1662,6 +1662,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { await expect(page).toHaveURL(/s:failed%20@smoke/); await passedNavMenu.click(); + await smokeLabelButton.click({ modifiers: [process.platform === 'darwin' ? 'Meta' : 'Control'] }); await regressionLabelButton.click(); await expect(page.locator('.test-file-test')).toHaveCount(1); await expect(page.locator('.chip', { hasText: 'a.test.js' })).toHaveCount(1);