onAccepted?.(item)}
+ className={clsx(
+ 'tree-view-entry',
+ selectedItem === item && 'selected',
+ highlightedItem === item && 'highlighted',
+ isError?.(item) && 'error')}
+ onClick={() => onSelected?.(item)}
+ onMouseEnter={() => setHighlightedItem(item)}
+ onMouseLeave={() => setHighlightedItem(undefined)}
+ >
+ {indentation ? new Array(indentation).fill(0).map((_, i) =>
) : undefined}
+
{
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ onClick={e => {
+ e.stopPropagation();
+ e.preventDefault();
+ toggleExpanded(item);
+ }}
+ />
+ {icon &&
}
+ {typeof rendered === 'string' ?
{rendered}
: rendered}
+
;
}
type TreeItemData = {
@@ -160,7 +285,12 @@ type TreeItemData = {
parent: TreeItem | null,
};
-function flattenTree
(rootItem: T, selectedItem: T | undefined, expandedItems: Map, autoExpandDepth: number): Map {
+function flattenTree(
+ rootItem: T,
+ selectedItem: T | undefined,
+ expandedItems: Map,
+ autoExpandDepth: number): Map {
+
const result = new Map();
const temporaryExpanded = new Set();
for (let item: TreeItem | undefined = selectedItem?.parent; item; item = item.parent)
diff --git a/packages/web/src/uiUtils.ts b/packages/web/src/uiUtils.ts
index 2697177c6f..ea71486014 100644
--- a/packages/web/src/uiUtils.ts
+++ b/packages/web/src/uiUtils.ts
@@ -208,5 +208,14 @@ export async function sha1(str: string): Promise {
return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-1', buffer))).map(b => b.toString(16).padStart(2, '0')).join('');
}
+export function scrollIntoViewIfNeeded(element: Element | undefined) {
+ if (!element)
+ return;
+ if ((element as any)?.scrollIntoViewIfNeeded)
+ (element as any).scrollIntoViewIfNeeded(false);
+ else
+ element?.scrollIntoView();
+}
+
const kControlCodesRe = '\\u0000-\\u0020\\u007f-\\u009f';
export const kWebLinkRe = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|www\\.)[^\\s' + kControlCodesRe + '"]{2,}[^\\s' + kControlCodesRe + '"\')}\\],:;.!?]', 'ug');
diff --git a/tests/config/traceViewerFixtures.ts b/tests/config/traceViewerFixtures.ts
index 0fe4a9a5c9..3eb3b11a15 100644
--- a/tests/config/traceViewerFixtures.ts
+++ b/tests/config/traceViewerFixtures.ts
@@ -62,13 +62,13 @@ class TraceViewerPage {
}
async actionIconsText(action: string) {
- const entry = await this.page.waitForSelector(`.list-view-entry:has-text("${action}")`);
+ const entry = await this.page.waitForSelector(`.tree-view-entry:has-text("${action}")`);
await entry.waitForSelector('.action-icon-value:visible');
return await entry.$$eval('.action-icon-value:visible', ee => ee.map(e => e.textContent));
}
async actionIcons(action: string) {
- return await this.page.waitForSelector(`.list-view-entry:has-text("${action}") .action-icons`);
+ return await this.page.waitForSelector(`.tree-view-entry:has-text("${action}") .action-icons`);
}
@step
diff --git a/tests/playwright-test/ui-mode-fixtures.ts b/tests/playwright-test/ui-mode-fixtures.ts
index 2952761d60..1e3b11a03a 100644
--- a/tests/playwright-test/ui-mode-fixtures.ts
+++ b/tests/playwright-test/ui-mode-fixtures.ts
@@ -66,16 +66,16 @@ export function dumpTestTree(page: Page, options: { time?: boolean } = {}): () =
}
const result: string[] = [];
- const listItems = treeElement.querySelectorAll('[role=listitem]');
- for (const listItem of listItems) {
- const iconElements = listItem.querySelectorAll('.codicon');
+ const treeItems = treeElement.querySelectorAll('[role=treeitem]');
+ for (const treeItem of treeItems) {
+ const iconElements = treeItem.querySelectorAll('.codicon');
const treeIcon = iconName(iconElements[0]);
const statusIcon = iconName(iconElements[1]);
- 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').childNodes[0].textContent;
- const timeElement = options.time ? listItem.querySelector('.ui-mode-list-item-time') : undefined;
+ const indent = treeItem.querySelectorAll('.tree-view-indent').length;
+ const watch = treeItem.querySelector('.toolbar-button.eye.toggled') ? ' 👁' : '';
+ const selected = treeItem.getAttribute('aria-selected') === 'true' ? ' <=' : '';
+ const title = treeItem.querySelector('.ui-mode-tree-item-title').childNodes[0].textContent;
+ const timeElement = options.time ? treeItem.querySelector('.ui-mode-tree-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-annotations.spec.ts b/tests/playwright-test/ui-mode-test-annotations.spec.ts
index 7a0dea8af1..f32d43aecf 100644
--- a/tests/playwright-test/ui-mode-test-annotations.spec.ts
+++ b/tests/playwright-test/ui-mode-test-annotations.spec.ts
@@ -33,7 +33,7 @@ test('should display annotations', async ({ runUITest }) => {
});
await page.getByTitle('Run all').click();
await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
- await page.getByRole('listitem').filter({ hasText: 'suite' }).locator('.codicon-chevron-right').click();
+ await page.getByRole('treeitem').filter({ hasText: 'suite' }).locator('.codicon-chevron-right').click();
await page.getByText('annotation test').click();
await page.getByText('Annotations', { exact: true }).click();
diff --git a/tests/playwright-test/ui-mode-test-filters.spec.ts b/tests/playwright-test/ui-mode-test-filters.spec.ts
index 5d70048473..dd59c334b2 100644
--- a/tests/playwright-test/ui-mode-test-filters.spec.ts
+++ b/tests/playwright-test/ui-mode-test-filters.spec.ts
@@ -64,7 +64,7 @@ test('should display native tags and filter by them on click', async ({ runUITes
test('pwt', { tag: '@smoke' }, () => {});
`,
});
- await page.locator('.ui-mode-list-item-title').getByText('smoke').click();
+ await page.locator('.ui-mode-tree-item-title').getByText('smoke').click();
await expect(page.getByPlaceholder('Filter')).toHaveValue('@smoke');
await expect.poll(dumpTestTree(page)).toBe(`
▼ ◯ a.test.ts
diff --git a/tests/playwright-test/ui-mode-test-progress.spec.ts b/tests/playwright-test/ui-mode-test-progress.spec.ts
index f87eaa8fbc..f2f01a79ce 100644
--- a/tests/playwright-test/ui-mode-test-progress.spec.ts
+++ b/tests/playwright-test/ui-mode-test-progress.spec.ts
@@ -47,7 +47,7 @@ test('should update trace live', async ({ runUITest, server }) => {
await page.getByText('live test').dblclick();
// It should halt on loading one.html.
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -57,11 +57,11 @@ test('should update trace live', async ({ runUITest, server }) => {
]);
await expect(
- listItem.locator(':scope.selected'),
+ listItem.locator(':scope[aria-selected="true"]'),
'last action to be selected'
).toHaveText(/page.goto/);
await expect(
- listItem.locator(':scope.selected .codicon.codicon-loading'),
+ listItem.locator(':scope[aria-selected="true"] .codicon.codicon-loading'),
'spinner'
).toBeVisible();
@@ -83,11 +83,11 @@ test('should update trace live', async ({ runUITest, server }) => {
/page.gotohttp:\/\/localhost:\d+\/two.html/
]);
await expect(
- listItem.locator(':scope.selected'),
+ listItem.locator(':scope[aria-selected="true"]'),
'last action to be selected'
).toHaveText(/page.goto/);
await expect(
- listItem.locator(':scope.selected .codicon.codicon-loading'),
+ listItem.locator(':scope[aria-selected="true"] .codicon.codicon-loading'),
'spinner'
).toBeVisible();
@@ -132,7 +132,7 @@ test('should preserve action list selection upon live trace update', async ({ ru
await page.getByText('live test').dblclick();
// It should wait on the latch.
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -157,7 +157,7 @@ test('should preserve action list selection upon live trace update', async ({ ru
/page.setContent[\d.]+m?s/,
]);
await expect(
- listItem.locator(':scope.selected'),
+ listItem.locator(':scope[aria-selected="true"]'),
'selected action stays the same'
).toHaveText(/page.goto/);
});
@@ -193,7 +193,7 @@ test('should update tracing network live', async ({ runUITest, server }) => {
await page.getByText('live test').dblclick();
// It should wait on the latch.
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -233,7 +233,7 @@ test('should show trace w/ multiple contexts', async ({ runUITest, server, creat
await page.getByText('live test').dblclick();
// It should wait on the latch.
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -278,7 +278,7 @@ test('should show live trace for serial', async ({ runUITest, server, createLatc
await page.getByText('two', { exact: true }).click();
await page.getByTitle('Run all').click();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -318,7 +318,7 @@ test('should show live trace from hooks', async ({ runUITest, createLatch }) =>
`);
await page.getByText('test one').dblclick();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
diff --git a/tests/playwright-test/ui-mode-test-run.spec.ts b/tests/playwright-test/ui-mode-test-run.spec.ts
index 5ead1889f0..24731bcbb2 100644
--- a/tests/playwright-test/ui-mode-test-run.spec.ts
+++ b/tests/playwright-test/ui-mode-test-run.spec.ts
@@ -93,7 +93,7 @@ test('should run on hover', async ({ runUITest }) => {
});
await page.getByText('passes').hover();
- await page.getByRole('listitem').filter({ hasText: 'passes' }).getByTitle('Run').click();
+ await page.getByRole('treeitem').filter({ hasText: 'passes' }).getByTitle('Run').click();
await expect.poll(dumpTestTree(page)).toBe(`
▼ ◯ a.test.ts
@@ -275,7 +275,7 @@ test('should run folder', async ({ runUITest }) => {
});
await page.getByText('folder-b').hover();
- await page.getByRole('listitem').filter({ hasText: 'folder-b' }).getByTitle('Run').click();
+ await page.getByRole('treeitem').filter({ hasText: 'folder-b' }).getByTitle('Run').click();
await expect.poll(dumpTestTree(page)).toContain(`
▼ ✅ folder-b <=
diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts
index cd5503427d..f8de9e262a 100644
--- a/tests/playwright-test/ui-mode-test-setup.spec.ts
+++ b/tests/playwright-test/ui-mode-test-setup.spec.ts
@@ -211,7 +211,7 @@ test('should run part of the setup only', async ({ runUITest }) => {
await page.getByLabel('test').setChecked(true);
await page.getByText('setup.ts').hover();
- await page.getByRole('listitem').filter({ hasText: 'setup.ts' }).getByTitle('Run').click();
+ await page.getByRole('treeitem').filter({ hasText: 'setup.ts' }).getByTitle('Run').click();
await expect.poll(dumpTestTree(page)).toBe(`
▼ ✅ setup.ts <=
diff --git a/tests/playwright-test/ui-mode-test-update.spec.ts b/tests/playwright-test/ui-mode-test-update.spec.ts
index 61e2c89dc7..ae5752c3f0 100644
--- a/tests/playwright-test/ui-mode-test-update.spec.ts
+++ b/tests/playwright-test/ui-mode-test-update.spec.ts
@@ -149,7 +149,7 @@ test('should not loose run information after execution if test wrote into testDi
await page.getByTitle('Run all').click();
await page.waitForTimeout(5_000);
await expect(page.getByText('Did not run')).toBeHidden();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -215,7 +215,7 @@ test('should update test locations', async ({ runUITest, writeFiles }) => {
const messages: any[] = [];
await page.exposeBinding('__logForTest', (source, arg) => messages.push(arg));
- const passesItemLocator = page.getByRole('listitem').filter({ hasText: 'passes' });
+ const passesItemLocator = page.getByRole('treeitem').filter({ hasText: 'passes' });
await passesItemLocator.hover();
await passesItemLocator.getByTitle('Show source').click();
await page.getByTitle('Open in VS Code').click();
diff --git a/tests/playwright-test/ui-mode-test-watch.spec.ts b/tests/playwright-test/ui-mode-test-watch.spec.ts
index bd04750a1f..893a0ef7ac 100644
--- a/tests/playwright-test/ui-mode-test-watch.spec.ts
+++ b/tests/playwright-test/ui-mode-test-watch.spec.ts
@@ -28,14 +28,14 @@ test('should watch files', async ({ runUITest, writeFiles }) => {
});
await page.getByText('fails').click();
- await page.getByRole('listitem').filter({ hasText: 'fails' }).getByTitle('Watch').click();
+ await page.getByRole('treeitem').filter({ hasText: 'fails' }).getByTitle('Watch').click();
await expect.poll(dumpTestTree(page)).toBe(`
▼ ◯ a.test.ts
◯ passes
◯ fails 👁 <=
`);
- await page.getByRole('listitem').filter({ hasText: 'fails' }).getByTitle('Run').click();
+ await page.getByRole('treeitem').filter({ hasText: 'fails' }).getByTitle('Run').click();
await expect.poll(dumpTestTree(page)).toBe(`
▼ ❌ a.test.ts
@@ -75,7 +75,7 @@ test('should watch e2e deps', async ({ runUITest, writeFiles }) => {
});
await page.getByText('answer').click();
- await page.getByRole('listitem').filter({ hasText: 'answer' }).getByTitle('Watch').click();
+ await page.getByRole('treeitem').filter({ hasText: 'answer' }).getByTitle('Watch').click();
await expect.poll(dumpTestTree(page)).toBe(`
▼ ◯ a.test.ts
◯ answer 👁 <=
@@ -102,13 +102,13 @@ test('should batch watch updates', async ({ runUITest, writeFiles }) => {
});
await page.getByText('a.test.ts').click();
- await page.getByRole('listitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
+ await page.getByRole('treeitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
await page.getByText('b.test.ts').click();
- await page.getByRole('listitem').filter({ hasText: 'b.test.ts' }).getByTitle('Watch').click();
+ await page.getByRole('treeitem').filter({ hasText: 'b.test.ts' }).getByTitle('Watch').click();
await page.getByText('c.test.ts').click();
- await page.getByRole('listitem').filter({ hasText: 'c.test.ts' }).getByTitle('Watch').click();
+ await page.getByRole('treeitem').filter({ hasText: 'c.test.ts' }).getByTitle('Watch').click();
await page.getByText('d.test.ts').click();
- await page.getByRole('listitem').filter({ hasText: 'd.test.ts' }).getByTitle('Watch').click();
+ await page.getByRole('treeitem').filter({ hasText: 'd.test.ts' }).getByTitle('Watch').click();
await expect.poll(dumpTestTree(page)).toBe(`
▼ ◯ a.test.ts 👁
@@ -229,7 +229,7 @@ test('should run added test in watched file', async ({ runUITest, writeFiles })
});
await page.getByText('a.test.ts').click();
- await page.getByRole('listitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
+ await page.getByRole('treeitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
await expect.poll(dumpTestTree(page)).toBe(`
▼ ◯ a.test.ts 👁 <=
diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts
index 9f0749893e..def44e9aeb 100644
--- a/tests/playwright-test/ui-mode-trace.spec.ts
+++ b/tests/playwright-test/ui-mode-trace.spec.ts
@@ -34,7 +34,7 @@ test('should merge trace events', async ({ runUITest }) => {
await page.getByText('trace test').dblclick();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -61,7 +61,7 @@ test('should merge web assertion events', async ({ runUITest }, testInfo) => {
await page.getByText('trace test').dblclick();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -86,7 +86,7 @@ test('should merge screenshot assertions', async ({ runUITest }, testInfo) => {
await page.getByText('trace test').dblclick();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -134,7 +134,7 @@ test('should show snapshots for sync assertions', async ({ runUITest }) => {
await page.getByText('trace test').dblclick();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
'action list'
@@ -214,7 +214,7 @@ test('should not fail on internal page logs', async ({ runUITest, server }) => {
});
await page.getByText('pass').dblclick();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
@@ -241,7 +241,7 @@ test('should not show caught errors in the errors tab', async ({ runUITest }, te
});
await page.getByText('pass').dblclick();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,
@@ -272,7 +272,7 @@ test('should reveal errors in the sourcetab', async ({ runUITest }) => {
});
await page.getByText('pass').dblclick();
- const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+ const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
await expect(
listItem,