diff --git a/packages/html-reporter/src/labels.css b/packages/html-reporter/src/labels.css
index ad9bdc3a36369..c9775add0ea69 100644
--- a/packages/html-reporter/src/labels.css
+++ b/packages/html-reporter/src/labels.css
@@ -30,6 +30,16 @@
cursor: pointer;
}
+.label-button {
+ font-family: inherit;
+ cursor: pointer;
+}
+
+.label-button:focus-visible {
+ outline: 1px solid var(--color-accent-fg);
+ outline-offset: -1px;
+}
+
.label-anchor {
text-decoration: none;
color: var(--color-fg-default);
diff --git a/packages/html-reporter/src/labels.tsx b/packages/html-reporter/src/labels.tsx
index 044e9ea074d9d..d5102f0a64703 100644
--- a/packages/html-reporter/src/labels.tsx
+++ b/packages/html-reporter/src/labels.tsx
@@ -28,9 +28,14 @@ export const Label: React.FC<{
onClick?: (e: React.MouseEvent, label: string) => void,
colorIndex?: number,
}> = ({ label, href, onClick, colorIndex, trimAtSymbolPrefix }) => {
- const baseLabel = onClick(e, label) : undefined}>
- {trimAtSymbolPrefix && label.startsWith('@') ? label.slice(1) : label}
- ;
+ const className = clsx('label', 'label-color-' + (colorIndex !== undefined ? colorIndex : hashStringToInt(label)));
+ const text = trimAtSymbolPrefix && label.startsWith('@') ? label.slice(1) : label;
+
+ // When there is an href the anchor below is already keyboard accessible, so only a
+ // standalone click handler needs a button of its own.
+ const baseLabel = onClick && !href
+ ?
+ : onClick(e, label) : undefined}>{text};
return href
? {baseLabel}
diff --git a/packages/trace-viewer/src/ui/tag.css b/packages/trace-viewer/src/ui/tag.css
index 41317c441aafe..0726ab6f44717 100644
--- a/packages/trace-viewer/src/ui/tag.css
+++ b/packages/trace-viewer/src/ui/tag.css
@@ -29,6 +29,16 @@
font-weight: 600;
}
+.tag-button {
+ font-family: inherit;
+ cursor: pointer;
+}
+
+.tag-button:focus-visible {
+ outline: 1px solid var(--vscode-focusBorder);
+ outline-offset: -1px;
+}
+
.tag-color-0 {
background-color: #ddf4ff;
color: #0550ae;
diff --git a/packages/trace-viewer/src/ui/tag.tsx b/packages/trace-viewer/src/ui/tag.tsx
index 057d70ac66d03..cd3dde43e3d4f 100644
--- a/packages/trace-viewer/src/ui/tag.tsx
+++ b/packages/trace-viewer/src/ui/tag.tsx
@@ -18,14 +18,21 @@ import { clsx } from '@web/uiUtils';
import './tag.css';
export const TagView = ({ tag, style, onClick }: { tag: string, style?: React.CSSProperties, onClick?: (e: React.MouseEvent) => void }) => {
- return {tag};
+
+ return ;
};
// hash string to integer in range [0, 6] for color index, to get same color for same tag
diff --git a/tests/playwright-test/ui-mode-test-filters.spec.ts b/tests/playwright-test/ui-mode-test-filters.spec.ts
index f549d150e4b09..7e2edd89831b5 100644
--- a/tests/playwright-test/ui-mode-test-filters.spec.ts
+++ b/tests/playwright-test/ui-mode-test-filters.spec.ts
@@ -72,6 +72,23 @@ test('should display native tags and filter by them on click', async ({ runUITes
`);
});
+test('should filter by tag from the keyboard', async ({ runUITest }) => {
+ const { page } = await runUITest({
+ 'a.test.ts': `
+ import { test, expect } from '@playwright/test';
+ test('p', () => {});
+ test('pwt', { tag: '@smoke' }, () => {});
+ `,
+ });
+
+ const tag = page.locator('.ui-mode-tree-item-title').getByRole('button', { name: 'smoke' });
+ await tag.focus();
+ await expect(tag).toBeFocused();
+
+ await tag.press('Enter');
+ await expect(page.getByPlaceholder('Filter (e.g. text, @tag)')).toHaveValue('@smoke');
+});
+
test('should toggle filters from the keyboard', async ({ runUITest }) => {
const { page } = await runUITest(basicTestTree);
const summary = page.locator('.filter-summary');