feat(ui-mode): show native tags in test tree
This commit is contained in:
parent
4b3c596874
commit
791c68df42
92
packages/trace-viewer/src/ui/tag.css
Normal file
92
packages/trace-viewer/src/ui/tag.css
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -14,10 +14,24 @@
|
||||||
* limitations under the License.
|
* 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 <span
|
||||||
|
className={`label label-color-${color}`}
|
||||||
|
onClick={onClick}
|
||||||
|
style={{ margin: '6px 0 0 6px', ...style }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>;
|
||||||
|
};
|
||||||
|
|
||||||
// hash string to integer in range [0, 6] for color index, to get same color for same tag
|
// 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;
|
let hash = 0;
|
||||||
for (let i = 0; i < str.length; i++)
|
for (let i = 0; i < str.length; i++)
|
||||||
hash = str.charCodeAt(i) + ((hash << 8) - hash);
|
hash = str.charCodeAt(i) + ((hash << 8) - hash);
|
||||||
return Math.abs(hash % 6);
|
return Math.abs(hash % 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Tag;
|
||||||
|
|
@ -30,6 +30,7 @@ import { testStatusIcon } from './testUtils';
|
||||||
import type { TestModel } from './uiModeModel';
|
import type { TestModel } from './uiModeModel';
|
||||||
import './uiModeTestListView.css';
|
import './uiModeTestListView.css';
|
||||||
import type { TestServerConnection } from '@testIsomorphic/testServerConnection';
|
import type { TestServerConnection } from '@testIsomorphic/testServerConnection';
|
||||||
|
import Tag, { tagNametoColor } from './tag';
|
||||||
|
|
||||||
const TestTreeView = TreeView<TreeItem>;
|
const TestTreeView = TreeView<TreeItem>;
|
||||||
|
|
||||||
|
|
@ -46,7 +47,8 @@ export const TestListView: React.FC<{
|
||||||
isLoading?: boolean,
|
isLoading?: boolean,
|
||||||
onItemSelected: (item: { treeItem?: TreeItem, testCase?: reporterTypes.TestCase, testFile?: SourceLocation }) => void,
|
onItemSelected: (item: { treeItem?: TreeItem, testCase?: reporterTypes.TestCase, testFile?: SourceLocation }) => void,
|
||||||
requestedCollapseAllCount: number,
|
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<TreeState>({ expandedItems: new Map() });
|
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
||||||
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
||||||
const [collapseAllCount, setCollapseAllCount] = React.useState(requestedCollapseAllCount);
|
const [collapseAllCount, setCollapseAllCount] = React.useState(requestedCollapseAllCount);
|
||||||
|
|
@ -140,7 +142,10 @@ export const TestListView: React.FC<{
|
||||||
dataTestId='test-tree'
|
dataTestId='test-tree'
|
||||||
render={treeItem => {
|
render={treeItem => {
|
||||||
return <div className='hbox ui-mode-list-item'>
|
return <div className='hbox ui-mode-list-item'>
|
||||||
<div className='ui-mode-list-item-title' title={treeItem.title}>{treeItem.title}</div>
|
<div className='ui-mode-list-item-title' title={treeItem.title}>
|
||||||
|
{treeItem.title}
|
||||||
|
{treeItem.kind === 'case' ? treeItem.tags.map(tag => <Tag key={tag} color={tagNametoColor(tag)} onClick={e => {e.preventDefault(); setFilterText(tag);}}>{tag.slice(1)}</Tag>) : null}
|
||||||
|
</div>
|
||||||
{!!treeItem.duration && treeItem.status !== 'skipped' && <div className='ui-mode-list-item-time'>{msToString(treeItem.duration)}</div>}
|
{!!treeItem.duration && treeItem.status !== 'skipped' && <div className='ui-mode-list-item-time'>{msToString(treeItem.duration)}</div>}
|
||||||
<Toolbar noMinHeight={true} noShadow={true}>
|
<Toolbar noMinHeight={true} noShadow={true}>
|
||||||
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={!!runningState}></ToolbarButton>
|
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={!!runningState}></ToolbarButton>
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ const queryParams = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMac = navigator.platform === 'MacIntel';
|
const isMac = navigator.platform === 'MacIntel';
|
||||||
|
const isUnderTest = searchParams.get('isUnderTest') === 'true';
|
||||||
|
|
||||||
export const UIModeView: React.FC<{}> = ({
|
export const UIModeView: React.FC<{}> = ({
|
||||||
}) => {
|
}) => {
|
||||||
|
|
@ -376,7 +377,7 @@ export const UIModeView: React.FC<{}> = ({
|
||||||
<div className='title'>UI Mode disconnected</div>
|
<div className='title'>UI Mode disconnected</div>
|
||||||
<div><a href='#' onClick={() => window.location.href = '/'}>Reload the page</a> to reconnect</div>
|
<div><a href='#' onClick={() => window.location.href = '/'}>Reload the page</a> to reconnect</div>
|
||||||
</div>}
|
</div>}
|
||||||
<SplitView sidebarSize={250} minSidebarSize={150} orientation='horizontal' sidebarIsFirst={true} settingName='testListSidebar'>
|
<SplitView sidebarSize={isUnderTest ? 350 : 250} minSidebarSize={150} orientation='horizontal' sidebarIsFirst={true} settingName='testListSidebar'>
|
||||||
<div className='vbox'>
|
<div className='vbox'>
|
||||||
<div className={'vbox' + (isShowingOutput ? '' : ' hidden')}>
|
<div className={'vbox' + (isShowingOutput ? '' : ' hidden')}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
|
|
@ -439,7 +440,9 @@ export const UIModeView: React.FC<{}> = ({
|
||||||
watchedTreeIds={watchedTreeIds}
|
watchedTreeIds={watchedTreeIds}
|
||||||
setWatchedTreeIds={setWatchedTreeIds}
|
setWatchedTreeIds={setWatchedTreeIds}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
requestedCollapseAllCount={collapseAllCount} />
|
requestedCollapseAllCount={collapseAllCount}
|
||||||
|
setFilterText={setFilterText}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SplitView>
|
</SplitView>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export function dumpTestTree(page: Page, options: { time?: boolean } = {}): () =
|
||||||
const indent = listItem.querySelectorAll('.list-view-indent').length;
|
const indent = listItem.querySelectorAll('.list-view-indent').length;
|
||||||
const watch = listItem.querySelector('.toolbar-button.eye.toggled') ? ' 👁' : '';
|
const watch = listItem.querySelector('.toolbar-button.eye.toggled') ? ' 👁' : '';
|
||||||
const selected = listItem.classList.contains('selected') ? ' <=' : '';
|
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 timeElement = options.time ? listItem.querySelector('.ui-mode-list-item-time') : undefined;
|
||||||
const time = timeElement ? ' ' + timeElement.textContent.replace(/[.\d]+m?s/, 'XXms') : '';
|
const time = timeElement ? ' ' + timeElement.textContent.replace(/[.\d]+m?s/, 'XXms') : '';
|
||||||
result.push(' ' + ' '.repeat(indent) + treeIcon + ' ' + statusIcon + ' ' + title + time + watch + selected);
|
result.push(' ' + ' '.repeat(indent) + treeIcon + ' ' + statusIcon + ' ' + title + time + watch + selected);
|
||||||
|
|
|
||||||
|
|
@ -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 }) => {
|
test('should filter by status', async ({ runUITest }) => {
|
||||||
const { page } = await runUITest(basicTestTree);
|
const { page } = await runUITest(basicTestTree);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue