From 791c68df42dbb4b731c1b7e587c8b43d5c7f9303 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 25 Mar 2024 22:14:17 +0100 Subject: [PATCH] feat(ui-mode): show native tags in test tree --- packages/trace-viewer/src/ui/tag.css | 92 +++++++++++++++++++ .../src/ui/tag.tsx} | 16 +++- .../src/ui/uiModeTestListView.tsx | 9 +- packages/trace-viewer/src/ui/uiModeView.tsx | 7 +- tests/playwright-test/ui-mode-fixtures.ts | 2 +- .../ui-mode-test-filters.spec.ts | 16 ++++ 6 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 packages/trace-viewer/src/ui/tag.css rename packages/{html-reporter/src/labelUtils.tsx => trace-viewer/src/ui/tag.tsx} (66%) diff --git a/packages/trace-viewer/src/ui/tag.css b/packages/trace-viewer/src/ui/tag.css new file mode 100644 index 0000000000..7cbc57b91d --- /dev/null +++ b/packages/trace-viewer/src/ui/tag.css @@ -0,0 +1,92 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.label { + display: inline-block; + padding: 0 8px; + font-size: 12px; + font-weight: 500; + line-height: 18px; + border: 1px solid transparent; + border-radius: 2em; + background-color: #8c959f; + color: white; + margin: 0 10px; + flex: none; + font-weight: 600; +} + +.light-mode .label-color-0 { + background-color: #ddf4ff; + color: #0550ae; + border: 1px solid #218bff; +} +.light-mode .label-color-1 { + background-color: #fff8c5; + color: #7d4e00; + border: 1px solid #bf8700; +} +.light-mode .label-color-2 { + background-color: #fbefff; + color: #6e40c9; + border: 1px solid #a475f9; +} +.light-mode .label-color-3 { + background-color: #ffeff7; + color: #99286e; + border: 1px solid #e85aad; +} +.light-mode .label-color-4 { + background-color: #FFF0EB; + color: #9E2F1C; + border: 1px solid #EA6045; +} +.light-mode .label-color-5 { + background-color: #fff1e5; + color: #9b4215; + border: 1px solid #e16f24; +} + +.dark-mode .label-color-0 { + background-color: #051d4d; + color: #80ccff; + border: 1px solid #218bff; +} +.dark-mode .label-color-1 { + background-color: #3b2300; + color: #eac54f; + border: 1px solid #bf8700; +} +.dark-mode .label-color-2 { + background-color: #271052; + color: #d2a8ff; + border: 1px solid #a475f9; +} +.dark-mode .label-color-3 { + background-color: #42062a; + color: #ff9bce; + border: 1px solid #e85aad; +} +.dark-mode .label-color-4 { + background-color: #460701; + color: #FFA28B; + border: 1px solid #EC6547; +} +.dark-mode .label-color-5 { + background-color: #471700; + color: #ffa657; + border: 1px solid #e16f24; +} \ No newline at end of file diff --git a/packages/html-reporter/src/labelUtils.tsx b/packages/trace-viewer/src/ui/tag.tsx similarity index 66% rename from packages/html-reporter/src/labelUtils.tsx rename to packages/trace-viewer/src/ui/tag.tsx index 014ec77d59..ede316e76c 100644 --- a/packages/html-reporter/src/labelUtils.tsx +++ b/packages/trace-viewer/src/ui/tag.tsx @@ -14,10 +14,24 @@ * limitations under the License. */ +import './tag.css'; + +const Tag: React.FC<{ color: number, children: string, style?: React.CSSProperties, onClick?: (e: React.MouseEvent) => void }> = ({ color, children, style, onClick }) => { + return + {children} + ; +}; + // hash string to integer in range [0, 6] for color index, to get same color for same tag -export function hashStringToInt(str: string) { +export function tagNametoColor(str: string) { let hash = 0; for (let i = 0; i < str.length; i++) hash = str.charCodeAt(i) + ((hash << 8) - hash); return Math.abs(hash % 6); } + +export default Tag; \ No newline at end of file diff --git a/packages/trace-viewer/src/ui/uiModeTestListView.tsx b/packages/trace-viewer/src/ui/uiModeTestListView.tsx index 7ca7f95bdc..7d7df8bfed 100644 --- a/packages/trace-viewer/src/ui/uiModeTestListView.tsx +++ b/packages/trace-viewer/src/ui/uiModeTestListView.tsx @@ -30,6 +30,7 @@ import { testStatusIcon } from './testUtils'; import type { TestModel } from './uiModeModel'; import './uiModeTestListView.css'; import type { TestServerConnection } from '@testIsomorphic/testServerConnection'; +import Tag, { tagNametoColor } from './tag'; const TestTreeView = TreeView; @@ -46,7 +47,8 @@ export const TestListView: React.FC<{ isLoading?: boolean, onItemSelected: (item: { treeItem?: TreeItem, testCase?: reporterTypes.TestCase, testFile?: SourceLocation }) => void, requestedCollapseAllCount: number, -}> = ({ filterText, testModel, testServerConnection, testTree, runTests, runningState, watchAll, watchedTreeIds, setWatchedTreeIds, isLoading, onItemSelected, requestedCollapseAllCount }) => { + setFilterText: (text: string) => void; +}> = ({ filterText, testModel, testServerConnection, testTree, runTests, runningState, watchAll, watchedTreeIds, setWatchedTreeIds, isLoading, onItemSelected, requestedCollapseAllCount, setFilterText }) => { const [treeState, setTreeState] = React.useState({ expandedItems: new Map() }); const [selectedTreeItemId, setSelectedTreeItemId] = React.useState(); const [collapseAllCount, setCollapseAllCount] = React.useState(requestedCollapseAllCount); @@ -140,7 +142,10 @@ export const TestListView: React.FC<{ dataTestId='test-tree' render={treeItem => { return
-
{treeItem.title}
+
+ {treeItem.title} + {treeItem.kind === 'case' ? treeItem.tags.map(tag => {e.preventDefault(); setFilterText(tag);}}>{tag.slice(1)}) : null} +
{!!treeItem.duration && treeItem.status !== 'skipped' &&
{msToString(treeItem.duration)}
} runTreeItem(treeItem)} disabled={!!runningState}> diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 2cc76ff8fd..c9bb76f578 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -64,6 +64,7 @@ const queryParams = { }; const isMac = navigator.platform === 'MacIntel'; +const isUnderTest = searchParams.get('isUnderTest') === 'true'; export const UIModeView: React.FC<{}> = ({ }) => { @@ -376,7 +377,7 @@ export const UIModeView: React.FC<{}> = ({
UI Mode disconnected
} - +
@@ -439,7 +440,9 @@ export const UIModeView: React.FC<{}> = ({ watchedTreeIds={watchedTreeIds} setWatchedTreeIds={setWatchedTreeIds} isLoading={isLoading} - requestedCollapseAllCount={collapseAllCount} /> + requestedCollapseAllCount={collapseAllCount} + setFilterText={setFilterText} + />
; diff --git a/tests/playwright-test/ui-mode-fixtures.ts b/tests/playwright-test/ui-mode-fixtures.ts index a8c2ede79b..fa1ee5c023 100644 --- a/tests/playwright-test/ui-mode-fixtures.ts +++ b/tests/playwright-test/ui-mode-fixtures.ts @@ -75,7 +75,7 @@ export function dumpTestTree(page: Page, options: { time?: boolean } = {}): () = const indent = listItem.querySelectorAll('.list-view-indent').length; const watch = listItem.querySelector('.toolbar-button.eye.toggled') ? ' 👁' : ''; const selected = listItem.classList.contains('selected') ? ' <=' : ''; - const title = listItem.querySelector('.ui-mode-list-item-title').textContent; + const title = listItem.querySelector('.ui-mode-list-item-title').childNodes[0].textContent; const timeElement = options.time ? listItem.querySelector('.ui-mode-list-item-time') : undefined; const time = timeElement ? ' ' + timeElement.textContent.replace(/[.\d]+m?s/, 'XXms') : ''; result.push(' ' + ' '.repeat(indent) + treeIcon + ' ' + statusIcon + ' ' + title + time + watch + selected); diff --git a/tests/playwright-test/ui-mode-test-filters.spec.ts b/tests/playwright-test/ui-mode-test-filters.spec.ts index f35d4dec4a..8469db3990 100644 --- a/tests/playwright-test/ui-mode-test-filters.spec.ts +++ b/tests/playwright-test/ui-mode-test-filters.spec.ts @@ -56,6 +56,22 @@ test('should filter by explicit tags', async ({ runUITest }) => { `); }); +test('should display native tags and filter by them on click', async ({ runUITest }) => { + const { page } = await runUITest({ + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + test('passes', () => {}); + test('passes with tags', { tag: '@smoke' }, () => {}); + `, + }); + await page.locator('.ui-mode-list-item-title').getByText('smoke').click(); + await expect(page.getByPlaceholder('Filter')).toHaveValue('@smoke'); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ a.test.ts + ◯ passes with tags <= + `); +}); + test('should filter by status', async ({ runUITest }) => { const { page } = await runUITest(basicTestTree);