diff --git a/media/common.css b/media/common.css index bae0a3ee1..3f70a36a9 100644 --- a/media/common.css +++ b/media/common.css @@ -63,8 +63,9 @@ input { letter-spacing: 0px; } -.combobox { - margin: 0 8px; +.combobox select { + margin: 0 6px; + flex: auto; } input:focus, textarea:focus { @@ -87,12 +88,13 @@ textarea { display: flex; justify-content: space-between; - align-items: baseline; + align-items: center; } .section-toolbar > a { border-radius: 5px; font-size: 16px; + cursor: pointer; } .section-toolbar > a:hover { @@ -124,7 +126,6 @@ select { height: 22px; padding: 2px 0; cursor: pointer; - background: var(--vscode-sideBar-background); } label { diff --git a/src/settingsView.script.ts b/src/settingsView.script.ts index 642469142..cc4304537 100644 --- a/src/settingsView.script.ts +++ b/src/settingsView.script.ts @@ -20,6 +20,7 @@ let selectConfig: Config; const selectAllButton = document.getElementById('selectAll') as HTMLAnchorElement; const unselectAllButton = document.getElementById('unselectAll') as HTMLAnchorElement; +const toggleModels = document.getElementById('toggleModels') as HTMLAnchorElement; function updateProjects(projects: ProjectEntry[]) { const projectsElement = document.getElementById('projects') as HTMLElement; @@ -51,6 +52,7 @@ function setAllProjectsEnabled(enabled: boolean) { } selectAllButton.addEventListener('click', () => setAllProjectsEnabled(true)); unselectAllButton.addEventListener('click', () => setAllProjectsEnabled(false)); +toggleModels.addEventListener('click', () => (vscode.postMessage({ method: 'execute', params: { command: 'pw.extension.command.toggleModels' } }))); for (const input of Array.from(document.querySelectorAll('input[type=checkbox]'))) { input.addEventListener('change', event => { @@ -65,7 +67,6 @@ for (const select of Array.from(document.querySelectorAll('se window.addEventListener('message', event => { const actionsElement = document.getElementById('actions')!; - const configToolbarElement = document.getElementById('configToolbar')!; const rareActionsElement = document.getElementById('rareActions')!; const modelSelector = document.getElementById('model-selector')!; @@ -85,15 +86,12 @@ window.addEventListener('message', event => { } } else if (method === 'actions') { actionsElement.textContent = ''; - configToolbarElement.textContent = ''; rareActionsElement.textContent = ''; for (const action of params.actions) { const actionElement = createAction(action); if (!actionElement) continue; - if (action.location === 'configToolbar') - configToolbarElement.appendChild(actionElement); - else if (action.location === 'rareActions') + if (action.location === 'rareActions') rareActionsElement.appendChild(actionElement); else actionsElement.appendChild(actionElement); @@ -119,6 +117,6 @@ window.addEventListener('message', event => { vscode.postMessage({ method: 'selectModel', params: { configFile: select.value } }); updateProjects(configsMap.get(select.value).projects); }); - modelSelector.style.display = showModelSelector ? 'block' : 'none'; + modelSelector.style.display = showModelSelector ? 'flex' : 'none'; } }); diff --git a/src/settingsView.ts b/src/settingsView.ts index c8cf31a5a..65f208b3c 100644 --- a/src/settingsView.ts +++ b/src/settingsView.ts @@ -133,10 +133,6 @@ export class SettingsView extends DisposableBase implements vscodeTypes.WebviewV ...clearCacheAction(this._vscode, this._models), location: 'rareActions', }, - { - ...toggleModelsAction(this._vscode), - location: 'configToolbar', - }, ]; if (this._view) this._view.webview.postMessage({ method: 'actions', params: { actions } }); @@ -214,12 +210,17 @@ function htmlForWebview(vscode: vscodeTypes.VSCode, extensionUri: vscodeTypes.Ur
${vscode.l10n.t('TOOLS')}
-
-
- - +
@@ -261,9 +262,9 @@ function htmlForWebview(vscode: vscodeTypes.VSCode, extensionUri: vscodeTypes.Ur
-
+
- @@ -276,9 +277,9 @@ function htmlForWebview(vscode: vscodeTypes.VSCode, extensionUri: vscodeTypes.Ur
-
+
- @@ -360,11 +361,3 @@ export const clearCacheAction = (vscode: vscodeTypes.VSCode, models: TestModelCo disabled: !models.selectedModel(), }; }; - -export const toggleModelsAction = (vscode: vscodeTypes.VSCode) => { - return { - command: 'pw.extension.command.toggleModels', - svg: ``, - title: vscode.l10n.t('Toggle Playwright Configs'), - }; -}; diff --git a/src/testTree.ts b/src/testTree.ts index 8a55dc065..537efc69b 100644 --- a/src/testTree.ts +++ b/src/testTree.ts @@ -16,12 +16,13 @@ import path from 'path'; import { TestModelCollection } from './testModel'; -import { createGuid, uriToPath } from './utils'; +import { createGuid, normalizePath, uriToPath } from './utils'; import * as vscodeTypes from './vscodeTypes'; import * as reporterTypes from './upstream/reporter'; import * as upstream from './upstream/testTree'; import { TeleSuite } from './upstream/teleReceiver'; import { DisposableBase } from './disposableBase'; +import type { TestConfig } from './playwrightTestTypes'; /** * This class maps a collection of TestModels into the UI terms, it merges @@ -57,6 +58,7 @@ export class TestTree extends DisposableBase { startedLoading() { this._testGeneration = createGuid() + ':'; this._testController.items.replace([]); + this._rootItems.clear(); this._testItemByTestId.clear(); this._testItemByFile.clear(); @@ -90,23 +92,32 @@ export class TestTree extends DisposableBase { private _update() { for (const workspaceFolder of this._vscode.workspace.workspaceFolders ?? []) { + const disabledProjects = new Map(); const workspaceFSPath = uriToPath(workspaceFolder.uri); const rootSuite = new TeleSuite('', 'root'); for (const model of this._models.enabledModels().filter(m => m.config.workspaceFolder === workspaceFSPath)) { - for (const project of model.enabledProjects()) - rootSuite.suites.push(project.suite as TeleSuite); + for (const project of model.projects()) { + if (project.isEnabled) + rootSuite.suites.push(project.suite as TeleSuite); + else + disabledProjects.set(project.name, model.config); + } } + const upstreamTree = new upstream.TestTree(workspaceFSPath, rootSuite, [], undefined, path.sep); upstreamTree.sortAndPropagateStatus(); upstreamTree.flattenForSingleProject(); - // Create root item if there are test files. - if (upstreamTree.rootItem.children.length === 0) { + + if (upstreamTree.rootItem.children.length === 0 && !disabledProjects.size) { this._deleteRootItem(workspaceFSPath); continue; } + const workspaceRootItem = this._createRootItemIfNeeded(workspaceFolder.uri); this._syncSuite(upstreamTree.rootItem, workspaceRootItem); + this._syncDisabledProjects(workspaceRootItem, disabledProjects); } + // Remove stale root items. for (const itemFsPath of this._rootItems.keys()) { if (!this._vscode.workspace.workspaceFolders!.find(f => uriToPath(f.uri) === itemFsPath)) @@ -163,6 +174,26 @@ export class TestTree extends DisposableBase { } } + private _syncDisabledProjects(workspaceRootItem: vscodeTypes.TestItem, disabledProjects: Map) { + const topLevelItems: string[] = []; + workspaceRootItem.children.forEach(item => topLevelItems.push(item.id)); + const oldDisabledProjectIds = new Set(topLevelItems.filter(item => item.startsWith('[disabled] '))); + for (const [projectName, config] of disabledProjects) { + const projectId = '[disabled] ' + projectName + ' - ' + config.configFile; + if (oldDisabledProjectIds.has(projectId)) { + oldDisabledProjectIds.delete(projectId); + continue; + } + + const item = this._testController.createTestItem(projectId, ''); + item.description = `${path.relative(config.workspaceFolder, normalizePath(config.configFile))} [${projectName}] — disabled`; + item.sortText = 'z' + projectName; + workspaceRootItem.children.add(item); + } + for (const projectId of oldDisabledProjectIds) + workspaceRootItem.children.delete(projectId); + } + private _indexTree() { this._testItemByTestId.clear(); this._testItemByFile.clear(); @@ -206,10 +237,13 @@ export class TestTree extends DisposableBase { } private _deleteRootItem(fsPath: string): void { - if (this._vscode.workspace.workspaceFolders!.length === 1) - this._testController.items.replace([]); - else + if (this._vscode.workspace.workspaceFolders!.length === 1) { + const items: vscodeTypes.TestItem[] = []; + this._testController.items.forEach(item => items.push(item)); + this._testController.items.replace(items.filter(i => i.id === 'loading' || i.id === 'disabled')); + } else { this._testController.items.delete(this._idWithGeneration(fsPath)); + } this._rootItems.delete(fsPath); } diff --git a/tests/list-files.spec.ts b/tests/list-files.spec.ts index 25e20c139..de55e58ca 100644 --- a/tests/list-files.spec.ts +++ b/tests/list-files.spec.ts @@ -275,6 +275,7 @@ test('should support multiple projects', async ({ activate }) => { - tests - test1.spec.ts - test2.spec.ts + - [playwright.config.js [project 2] — disabled] `); await expect(vscode).toHaveExecLog(` @@ -310,6 +311,7 @@ test('should switch between multiple projects with filter', async ({ activate }) await expect(testController).toHaveTestTree(` - tests - test1.spec.ts + - [playwright.config.js [project 2] — disabled] `); await expect(vscode).toHaveExecLog(` @@ -330,6 +332,7 @@ test('should switch between multiple projects with filter', async ({ activate }) await expect(testController).toHaveTestTree(` - tests - test2.spec.ts + - [playwright.config.js [project 1] — disabled] `); await expect(vscode).toHaveProjectTree(` diff --git a/tests/list-tests.spec.ts b/tests/list-tests.spec.ts index 5d4fa039b..5829eb755 100644 --- a/tests/list-tests.spec.ts +++ b/tests/list-tests.spec.ts @@ -838,12 +838,16 @@ test('should show project-specific tests', async ({ activate }, testInfo) => { await expect(testController).toHaveTestTree(` - test.spec.ts + - [playwright.config.ts [firefox] — disabled] + - [playwright.config.ts [webkit] — disabled] `); await testController.expandTestItems(/test.spec.ts/); await expect(testController).toHaveTestTree(` - test.spec.ts - test [2:0] + - [playwright.config.ts [firefox] — disabled] + - [playwright.config.ts [webkit] — disabled] `); await enableProjects(vscode, ['chromium', 'firefox', 'webkit']); @@ -859,6 +863,8 @@ test('should show project-specific tests', async ({ activate }, testInfo) => { await expect(testController).toHaveTestTree(` - test.spec.ts - test [2:0] + - [playwright.config.ts [chromium] — disabled] + - [playwright.config.ts [firefox] — disabled] `); }); diff --git a/tests/mock/vscode.ts b/tests/mock/vscode.ts index 5436cf18d..f9cdcc6cd 100644 --- a/tests/mock/vscode.ts +++ b/tests/mock/vscode.ts @@ -202,6 +202,8 @@ export class TestItem { tags: readonly TestTag[] = []; canResolveChildren = false; status: 'none' | 'enqueued' | 'started' | 'skipped' | 'failed' | 'passed' = 'none'; + description: string | undefined; + sortText: string | undefined; constructor( readonly testController: TestController, @@ -288,7 +290,10 @@ export class TestItem { let location = ''; if (this.range) location = ` [${this.range.start.toString()}]`; - return `${this.label}${location}`; + let description = ''; + if (this.description) + description = ` [${this.description}]`; + return `${this.label}${description}${location}`; } flatTitle(): string { @@ -309,7 +314,7 @@ function itemOrder(item: TestItem) { let result = ''; if (item.range) result += item.range.start.line.toString().padStart(5, '0'); - result += item.label; + result += item.sortText || item.label; return result; } diff --git a/tests/project-setup.spec.ts b/tests/project-setup.spec.ts index 19e2aeee4..10ed14746 100644 --- a/tests/project-setup.spec.ts +++ b/tests/project-setup.spec.ts @@ -87,6 +87,7 @@ test.describe(() => { - ✅ teardown [2:0] - test.ts - ✅ test [2:0] + - [playwright.config.ts [setup] — disabled] `); const output = testRun.renderLog({ output: true }); @@ -103,6 +104,8 @@ test.describe(() => { await expect(testController).toHaveTestTree(` - test.ts - ✅ test [2:0] + - [playwright.config.ts [setup] — disabled] + - [playwright.config.ts [teardown] — disabled] `); const output = testRun.renderLog({ output: true }); diff --git a/tests/project-tree.spec.ts b/tests/project-tree.spec.ts index 5cd5e8182..103778424 100644 --- a/tests/project-tree.spec.ts +++ b/tests/project-tree.spec.ts @@ -33,6 +33,7 @@ test('should switch between configs', async ({ activate }) => { await expect(testController).toHaveTestTree(` - tests1 - test.spec.ts + - [tests1/playwright.config.js [projectTwo] — disabled] `); await expect(vscode).toHaveProjectTree(` config: tests1/playwright.config.js @@ -58,6 +59,7 @@ test('should switch between configs', async ({ activate }) => { await expect(testController).toHaveTestTree(` - tests2 - test.spec.ts + - [tests2/playwright.config.js [projectFour] — disabled] `); await expect(vscode).toHaveExecLog(` tests1> playwright list-files -c playwright.config.js @@ -90,6 +92,7 @@ test('should switch between projects', async ({ activate }) => { await expect(testController).toHaveTestTree(` - tests1 - test.spec.ts + - [playwright.config.js [projectTwo] — disabled] `); await expect(vscode).toHaveProjectTree(` @@ -143,6 +146,7 @@ test('should hide unchecked projects', async ({ activate }) => { await expect(testController).toHaveTestTree(` - tests1 - test.spec.ts + - [playwright.config.js [projectTwo] — disabled] `); await expect(vscode).toHaveProjectTree(` @@ -160,5 +164,7 @@ test('should hide unchecked projects', async ({ activate }) => { `); await expect(testController).toHaveTestTree(` + - [playwright.config.js [projectOne] — disabled] + - [playwright.config.js [projectTwo] — disabled] `); });