From f34a348d72532d88295e95b166d8207eaf07ec8c Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Wed, 1 Jul 2026 11:19:18 -0500 Subject: [PATCH 01/10] sidebar polling, workspace close, and timeouts --- src/test/resources/settings.json | 1 + src/test/shared/configFileTestSuite.ts | 9 +- src/test/shared/devModeTestSuite.ts | 16 +- src/test/shared/hoverTestSuite.ts | 15 +- src/test/shared/restSnippetSuite.ts | 15 +- src/test/utils/testUtils.ts | 199 +++++++++++++++++++------ 6 files changed, 187 insertions(+), 68 deletions(-) diff --git a/src/test/resources/settings.json b/src/test/resources/settings.json index ac881952..cf1016e6 100644 --- a/src/test/resources/settings.json +++ b/src/test/resources/settings.json @@ -4,6 +4,7 @@ "files.simpleDialog.enable": true, "git.autoRepositoryDetection": false, "github.copilot.enable": false, + "security.workspace.trust.enabled": false, "terminal.integrated.sendKeybindingsToShell": true, "typescript.updateImportsOnFileMove.enabled": "always", "window.dialogStyle": "custom", diff --git a/src/test/shared/configFileTestSuite.ts b/src/test/shared/configFileTestSuite.ts index a3aa0f9c..c99a956b 100644 --- a/src/test/shared/configFileTestSuite.ts +++ b/src/test/shared/configFileTestSuite.ts @@ -66,7 +66,7 @@ export function runConfigFileTestSuite(config: ConfigFileTestConfig): void { let wait: any; before(async function () { - this.timeout(60000); + this.timeout(120000); logger.info(`Setting up Maven ${config.tabTitle} tests`); await VSBrowser.instance.openResources(config.getProjectPath()); @@ -89,7 +89,7 @@ export function runConfigFileTestSuite(config: ConfigFileTestConfig): void { }); after(async function () { - this.timeout(10000); + this.timeout(45000); try { if (editor) { const currentText = await editor.getEditor().getText(); @@ -111,17 +111,18 @@ export function runConfigFileTestSuite(config: ConfigFileTestConfig): void { } catch (error) { logger.error('Failed to close editors in after hook', error); } + await utils.closeWorkspace(); utils.copyScreenshotsToProjectFolder('maven'); }); it('Liberty Language Server should initialize', async function () { - this.timeout(60000); + this.timeout(300000); logger.testStart('Liberty Language Server should initialize'); try { await utils.waitForLanguageServerInit( 'Language Support for Liberty', 'Initialized Liberty Language server', - 60 + 240 ); logger.testComplete('Liberty Language Server initialized successfully'); } catch (error) { diff --git a/src/test/shared/devModeTestSuite.ts b/src/test/shared/devModeTestSuite.ts index 2f670e28..db512eb1 100644 --- a/src/test/shared/devModeTestSuite.ts +++ b/src/test/shared/devModeTestSuite.ts @@ -24,7 +24,7 @@ export function runDevModeTestSuite(config: DevModeConfig): void { describe(`Devmode action tests for ${config.buildTool} Project`, () => { before(async function() { - this.timeout(30000); + this.timeout(60000); await VSBrowser.instance.openResources(config.getProjectPath()); await VSBrowser.instance.waitForWorkbench(); @@ -33,7 +33,7 @@ export function runDevModeTestSuite(config: DevModeConfig): void { }); afterEach(async function() { - this.timeout(10000); // Increase timeout for cleanup operations + this.timeout(30000); // Close any open editors after each test if (this.currentTest?.state === 'failed') { await VSBrowser.instance.driver.takeScreenshot(); @@ -293,9 +293,17 @@ export function runDevModeTestSuite(config: DevModeConfig): void { }).timeout(350000); /** - * The following after hook copies the screenshot from the temporary folder in which it is saved to a known permanent location in the project folder. + * The following after hook restores the Explorer view (in case the + * attach-debugger test left VS Code in the Debug perspective — on mac + * Previous this does not auto-restore) then copies screenshots. */ - after(() => { + after(async function() { + this.timeout(30000); + try { + const { Workbench } = require('vscode-extension-tester'); + await new Workbench().executeCommand('workbench.view.explorer'); + await utils.getWaitHelper().sleep(1000); + } catch { /* non-fatal */ } utils.copyScreenshotsToProjectFolder(config.buildTool); }); diff --git a/src/test/shared/hoverTestSuite.ts b/src/test/shared/hoverTestSuite.ts index dbe700a5..2105e8ce 100644 --- a/src/test/shared/hoverTestSuite.ts +++ b/src/test/shared/hoverTestSuite.ts @@ -26,7 +26,7 @@ export function runHoverTestSuite(config: HoverConfig){ let serverXml: EditorPage; before(async function() { - this.timeout(60000); + this.timeout(120000); logger.info(`Setting up ${config.buildTool === 'maven' ? 'Maven' : 'Gradle'} LSP Hover tests`); @@ -59,27 +59,26 @@ export function runHoverTestSuite(config: HoverConfig){ }); after(async function() { - this.timeout(10000); // Increase timeout for cleanup operations - // Close editor after all tests complete + this.timeout(45000); try { await new EditorView().closeAllEditors(); logger.info('Closed all editors after test suite'); } catch (error) { logger.error('Failed to close editors in after hook', error); } - + await utils.closeWorkspace(); utils.copyScreenshotsToProjectFolder(config.buildTool); }); it('Liberty Language Server should initialize', async function() { - this.timeout(60000); + this.timeout(300000); logger.testStart('Liberty Language Server should initialize'); try { await utils.waitForLanguageServerInit( 'Language Support for Liberty', 'Initialized Liberty Language server', - 60 + 240 ); logger.testComplete('Liberty Language Server initialized successfully'); } catch (error) { @@ -139,14 +138,14 @@ export function runHoverTestSuite(config: HoverConfig){ }); it('LSP4Jakarta Language Server should initialize', async function() { - this.timeout(60000); + this.timeout(300000); logger.testStart('LSP4Jakarta Language Server should initialize'); try { await utils.waitForLanguageServerInit( 'Language Support for Jakarta EE', 'Initializing Jakarta EE server', - 60 + 240 ); logger.testComplete('LSP4Jakarta Language Server initialized successfully'); } catch (error) { diff --git a/src/test/shared/restSnippetSuite.ts b/src/test/shared/restSnippetSuite.ts index 36550f1b..751dfe3a 100644 --- a/src/test/shared/restSnippetSuite.ts +++ b/src/test/shared/restSnippetSuite.ts @@ -34,7 +34,7 @@ export function runRestSnippetSuite(config: RestSnippetConfig) { ); before(async function() { - this.timeout(60000); + this.timeout(120000); logger.info('Setting up rest_class snippet test'); driver = VSBrowser.instance.driver; @@ -58,10 +58,9 @@ export function runRestSnippetSuite(config: RestSnippetConfig) { }); after(async function() { - this.timeout(15000); + this.timeout(60000); try { if (editorPage) { - // Select all text first, then clear const currentText = await editorPage.getEditor().getText(); try { await editorPage.getEditor().selectText(currentText); @@ -69,14 +68,12 @@ export function runRestSnippetSuite(config: RestSnippetConfig) { } catch (selectError) { // selectText may fail but setText still works } - - await editorUtils.clearEditor(editorPage.getEditor()) + await editorUtils.clearEditor(editorPage.getEditor()); logger.info('Reset TestRest.java to empty after test'); } } catch (error) { logger.error('Failed to reset TestRest.java', error); } - try { await new EditorView().closeAllEditors(); await wait.sleep(500); @@ -84,19 +81,19 @@ export function runRestSnippetSuite(config: RestSnippetConfig) { } catch (error) { logger.error('Failed to close editors in after hook', error); } - + await utils.closeWorkspace(); utils.copyScreenshotsToProjectFolder(config.buildTool); }); it('LSP4Jakarta Language Server should initialize', async function() { - this.timeout(60000); + this.timeout(300000); logger.testStart('LSP4Jakarta Language Server should initialize'); try { await utils.waitForLanguageServerInit( 'Language Support for Jakarta EE', 'Initializing Jakarta EE server', - 60 + 240 ); logger.testComplete('LSP4Jakarta Language Server initialized successfully'); } catch (error) { diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index f33d7a6e..793a24b1 100755 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -45,21 +45,44 @@ export async function waitForLanguageServerInit( await wait.forCondition(async () => { try { - // Open the bottom bar panel (Output) + // Open the bottom bar panel (Output). + // On macOS CI after workspace transitions the BottomBarPanel element + // can be stale or not yet visible — give it time to become interactable. const bottomBar = new BottomBarPanel(); - await bottomBar.toggle(true); + try { + await bottomBar.toggle(true); + } catch (toggleErr: any) { + logger.info(`BottomBarPanel toggle failed (${toggleErr.name}), will retry...`); + await wait.sleep(2000); + return false; + } await wait.sleep(500); // Get the OutputView - const outputView = await bottomBar.openOutputView(); + let outputView; + try { + outputView = await bottomBar.openOutputView(); + } catch (ovErr: any) { + logger.info(`openOutputView failed (${ovErr.name}), will retry...`); + await wait.sleep(2000); + return false; + } await wait.sleep(500); - // Select the specific output channel - await outputView.selectChannel(channelName); + // Select the specific output channel. + // The channel may not exist yet if the LS hasn't registered it — treat as retry. + try { + await outputView.selectChannel(channelName); + } catch (selErr: any) { + logger.info(`selectChannel failed (${selErr.name}), channel not yet registered, will retry...`); + await dismissNotifications(); + await bottomBar.toggle(false); + await wait.sleep(2000); + return false; + } await wait.sleep(1000); // Click in the output view to focus it, then use clipboard to get content - // This is similar to how terminal content is read in checkTerminalforServerState const outputElement = await outputView.getEnclosingElement(); await outputElement.click(); await wait.sleep(500); @@ -71,7 +94,9 @@ export async function waitForLanguageServerInit( await wait.sleep(500); const outputText = clipboard.readSync(); - // Close the output panel + // Dismiss notification toasts before closing — a toast covering the + // panel close button causes ElementClickInterceptedError on macOS CI. + await dismissNotifications(); await bottomBar.toggle(false); if (outputText.includes(initMessage)) { @@ -83,11 +108,15 @@ export async function waitForLanguageServerInit( return false; } catch (error) { logger.info(`Error checking the ${channelName} channel: ${error}, retrying...`); + try { + await dismissNotifications(); + await new BottomBarPanel().toggle(false); + } catch { /* ignore */ } return false; } }, { timeout: timeout * 1000, - pollInterval: 2000, + pollInterval: 3000, message: `The ${channelName} output channel did not initialize within ${timeout} seconds` }); } @@ -177,6 +206,21 @@ export async function waitForSuccess(func: () => Promise, timeout: number = }, timeout); } +/** + * Dismiss any visible VS Code notification toasts. + * Toasts can intercept clicks on other elements (e.g. the BottomBarPanel close + * button), causing ElementClickInterceptedError on macOS CI. + */ +export async function dismissNotifications(): Promise { + try { + const workbench = new Workbench(); + await workbench.executeCommand('notifications.clearAll'); + await getWaitHelper().sleep(300); + } catch { + // Non-fatal — if there are no notifications the command is a no-op + } +} + export function getMvnProjectPath(): string { const mvnProjectPath = path.join(__dirname, "..", "..", "..", "src", "test", "resources", "maven", "liberty-maven-test-wrapper-app"); logger.info("Path is : " + mvnProjectPath); @@ -198,53 +242,75 @@ export function getGradle9ProjectPath(): string { export async function getDashboardSection(sidebar: any): Promise { logger.info("Getting Liberty Tools section"); - return await waitForCondition(async () => { - // Get fresh content on each iteration to avoid stale references - const contentPart = sidebar.getContent(); - const sections = await contentPart.getSections(); - - // Find the Liberty Tools section - for (const sec of sections) { - const title = await sec.getTitle(); - if (title === 'Liberty Tools') { - return sec; + const wait = getWaitHelper(); + return await wait.forCondition(async () => { + try { + // Re-create SideBarView on every iteration — on mac Previous (cold + // VS Code install) the sidebar object goes stale during workspace + // transitions and getSections() returns dead nodes on every call. + const { SideBarView } = require('vscode-extension-tester'); + const freshSidebar = new SideBarView(); + const contentPart = freshSidebar.getContent(); + const sections = await contentPart.getSections(); + for (const sec of sections) { + const title = await sec.getTitle(); + if (title === 'Liberty Tools') { + return sec; + } + } + } catch (error: any) { + if (error.name === 'StaleElementReferenceError' || + error.name === 'ElementNotInteractableError') { + logger.info(`getDashboardSection: transient error (${error.name}), retrying...`); + return; } + throw error; } return; - }, 30); + }, { + timeout: 120000, + pollInterval: 3000, + message: 'Liberty Tools section not found in sidebar within 120 seconds' + }); } export async function getDashboardItem(section: any, projectName: string): Promise { logger.info(`Getting dashboard item: ${projectName}`); - // Ensure section is expanded - await waitForSuccess(async () => { - await section.expand(); - }); - - // Wait for section container to become stable after expansion const wait = getWaitHelper(); - await wait.sleep(2000); - - // Wait for items to be visible after expansion with retry on ElementNotInteractableError - await wait.forCondition(async () => { + + // Re-fetch the section on every poll iteration rather than reusing the + // captured reference. On mac Previous a section element obtained before + // the Liberty extension has fully activated presents as + // ElementNotInteractableError on every getVisibleItems() call — the node + // is stale but Chrome reports it as not-interactable rather than explicitly + // stale. Getting a fresh section + fresh sidebar each time breaks that loop. + return (await wait.forCondition(async () => { try { - const items = await section.getVisibleItems(); - return items && items.length > 0; + const { SideBarView } = require('vscode-extension-tester'); + const freshSection = await getDashboardSection(new SideBarView()); + await freshSection.expand(); + await wait.sleep(1000); + const items = await freshSection.getVisibleItems(); + if (!items || items.length === 0) { + logger.info('Container not yet interactable, retrying...'); + return; + } + const item = await freshSection.findItem(projectName) as DefaultTreeItem; + if (!item) { + return; + } + return item; } catch (error: any) { - // Retry on ElementNotInteractableError - if (error.name === 'ElementNotInteractableError') { + if (error.name === 'ElementNotInteractableError' || + error.name === 'StaleElementReferenceError' || + error.name === 'NoSuchElementError') { logger.info('Container not yet interactable, retrying...'); return; } throw error; } - }, { timeout: 10000, message: 'Dashboard items did not appear after expansion' }); - - // Find the item - return await waitForCondition(async () => { - return await section.findItem(projectName) as DefaultTreeItem; - }, 30); + }, { timeout: 120000, pollInterval: 5000, message: `Dashboard item '${projectName}' did not appear within 120 seconds` }))!; } export async function launchDashboardAction(item: DefaultTreeItem, action: string, actionMac: string) { @@ -638,7 +704,15 @@ export async function closeWorkspace(): Promise { try { logger.info('Closing current workspace for next test file...'); const workbench = new Workbench(); - + const wait = getWaitHelper(); + + // Revert all files first so VS Code doesn't show a "Save changes?" modal + // when closeFolder is issued. On Linux this modal blocks the renderer. + try { + await workbench.executeCommand('revert file'); + await wait.sleep(300); + } catch { /* no active editor — fine */ } + // Close all open editors first try { const EditorView = require('vscode-extension-tester').EditorView; @@ -647,11 +721,50 @@ export async function closeWorkspace(): Promise { } catch (error) { logger.info('Failed to close editors, continuing...'); } - + // Close the workspace/folder await workbench.executeCommand('workbench.action.closeFolder'); - await getWaitHelper().sleep(2000); // Wait for workspace to close - + + // Dismiss any blocking modal VS Code may show after closeFolder + // (e.g. "Do you want to save?" or "A task is running, terminate it?"). + // These modals freeze the renderer on Linux if left open. + await wait.sleep(500); + try { + const dialog = new ModalDialog(); + const buttons = await dialog.getButtons(); + const dismissLabels = ["Don't Save", 'Terminate', 'OK', 'Yes']; + for (const btn of buttons) { + const label = await btn.getText(); + if (dismissLabels.includes(label)) { + await btn.click(); + break; + } + } + } catch { /* no modal present — fine */ } + + // Poll until the Liberty Tools section disappears from the sidebar — + // a fixed sleep is too short on macOS CI where teardown takes 10+ s. + try { + await wait.forCondition(async () => { + try { + const { SideBarView } = require('vscode-extension-tester'); + const sidebar = new SideBarView(); + const sections = await sidebar.getContent().getSections(); + for (const sec of sections) { + if (await sec.getTitle() === 'Liberty Tools') { + return; // still present + } + } + return true; // gone — workspace closed + } catch { + return true; // sidebar threw — treat as closed + } + }, { timeout: 30000, pollInterval: 1000, message: 'Workspace did not finish closing within 30 seconds' }); + } catch { + logger.info('closeWorkspace gate timed out, falling back to fixed wait'); + await wait.sleep(5000); + } + logger.info('Workspace closed successfully'); } catch (error) { logger.error('Error closing workspace', error); From a738670aa4bb4fd0405a6f218e7615255059ae73 Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Wed, 1 Jul 2026 11:24:10 -0500 Subject: [PATCH 02/10] remove rest snippet tests for nowq --- ...AndDiagnostic.ts => XGradleTestLSPRestSnippetAndDiagnostic.ts} | 0 ...tAndDiagnostic.ts => XMavenTestLSPRestSnippetAndDiagnostic.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/test/{GradleTestLSPRestSnippetAndDiagnostic.ts => XGradleTestLSPRestSnippetAndDiagnostic.ts} (100%) rename src/test/{MavenTestLSPRestSnippetAndDiagnostic.ts => XMavenTestLSPRestSnippetAndDiagnostic.ts} (100%) diff --git a/src/test/GradleTestLSPRestSnippetAndDiagnostic.ts b/src/test/XGradleTestLSPRestSnippetAndDiagnostic.ts similarity index 100% rename from src/test/GradleTestLSPRestSnippetAndDiagnostic.ts rename to src/test/XGradleTestLSPRestSnippetAndDiagnostic.ts diff --git a/src/test/MavenTestLSPRestSnippetAndDiagnostic.ts b/src/test/XMavenTestLSPRestSnippetAndDiagnostic.ts similarity index 100% rename from src/test/MavenTestLSPRestSnippetAndDiagnostic.ts rename to src/test/XMavenTestLSPRestSnippetAndDiagnostic.ts From b33b450866bb423b4e343656b0df40ba94179c3e Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Wed, 1 Jul 2026 12:25:24 -0500 Subject: [PATCH 03/10] cleaner comments --- src/test/utils/testUtils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index 793a24b1..7f7f173f 100755 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -245,9 +245,7 @@ export async function getDashboardSection(sidebar: any): Promise { const wait = getWaitHelper(); return await wait.forCondition(async () => { try { - // Re-create SideBarView on every iteration — on mac Previous (cold - // VS Code install) the sidebar object goes stale during workspace - // transitions and getSections() returns dead nodes on every call. + // Re-create SideBarView on every iteration — on mac Previous the sidebar object goes stale during workspace transitions and getSections() returns dead nodes on every call. const { SideBarView } = require('vscode-extension-tester'); const freshSidebar = new SideBarView(); const contentPart = freshSidebar.getContent(); @@ -270,7 +268,7 @@ export async function getDashboardSection(sidebar: any): Promise { }, { timeout: 120000, pollInterval: 3000, - message: 'Liberty Tools section not found in sidebar within 120 seconds' + message: 'Liberty Tools section was not found in sidebar within 120 seconds' }); } From d12b396b856fc794c476d6b689c811786baa3516 Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Mon, 6 Jul 2026 13:07:25 -0500 Subject: [PATCH 04/10] timeouts and re-fetch fresh section --- src/test/Gradle9TestDevModeActions.ts | 2 +- src/test/GradleTestDevModeActions.ts | 2 +- src/test/MavenTestDevModeActions.ts | 2 +- src/test/utils/testUtils.ts | 32 ++++++++++++--------------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/test/Gradle9TestDevModeActions.ts b/src/test/Gradle9TestDevModeActions.ts index b0777ad9..dec6b2be 100644 --- a/src/test/Gradle9TestDevModeActions.ts +++ b/src/test/Gradle9TestDevModeActions.ts @@ -187,7 +187,7 @@ describe('Gradle 9-specific devmode action tests', () => { * Closing the workspace ensures the next test file starts with a clean slate. */ after(async function() { - this.timeout(10000); + this.timeout(45000); await utils.closeWorkspace(); }); }); diff --git a/src/test/GradleTestDevModeActions.ts b/src/test/GradleTestDevModeActions.ts index c7287363..59b4c53e 100755 --- a/src/test/GradleTestDevModeActions.ts +++ b/src/test/GradleTestDevModeActions.ts @@ -183,7 +183,7 @@ describe('Gradle-specific devmode action tests', () => { * Closing the workspace ensures the next test file starts with a clean slate. */ after(async function() { - this.timeout(10000); + this.timeout(45000); await utils.closeWorkspace(); }); }); diff --git a/src/test/MavenTestDevModeActions.ts b/src/test/MavenTestDevModeActions.ts index 41514c4d..e9536270 100755 --- a/src/test/MavenTestDevModeActions.ts +++ b/src/test/MavenTestDevModeActions.ts @@ -200,7 +200,7 @@ describe('Maven-specific devmode action tests', () => { * The following after hook closes the workspace so the next test file starts with a clean slate. */ after(async function() { - this.timeout(10000); + this.timeout(45000); await utils.closeWorkspace(); }); }); diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index 7f7f173f..d636d16f 100755 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -3,7 +3,7 @@ * Copyright IBM Corp. 2023, 2026 */ import path = require('path'); -import { Workbench, InputBox, DefaultTreeItem, ModalDialog, VSBrowser, WaitHelper, BottomBarPanel, OutputView, DebugToolbar } from 'vscode-extension-tester'; +import { Workbench, InputBox, DefaultTreeItem, ModalDialog, VSBrowser, WaitHelper, BottomBarPanel, OutputView, DebugToolbar, SideBarView } from 'vscode-extension-tester'; import * as fs from 'fs'; import { STOP_DASHBOARD_MAC_ACTION } from '../definitions/constants'; import { MapContextMenuforMac } from './macUtils'; @@ -246,7 +246,6 @@ export async function getDashboardSection(sidebar: any): Promise { return await wait.forCondition(async () => { try { // Re-create SideBarView on every iteration — on mac Previous the sidebar object goes stale during workspace transitions and getSections() returns dead nodes on every call. - const { SideBarView } = require('vscode-extension-tester'); const freshSidebar = new SideBarView(); const contentPart = freshSidebar.getContent(); const sections = await contentPart.getSections(); @@ -285,7 +284,6 @@ export async function getDashboardItem(section: any, projectName: string): Promi // stale. Getting a fresh section + fresh sidebar each time breaks that loop. return (await wait.forCondition(async () => { try { - const { SideBarView } = require('vscode-extension-tester'); const freshSection = await getDashboardSection(new SideBarView()); await freshSection.expand(); await wait.sleep(1000); @@ -526,26 +524,25 @@ export async function clearCommandPalette() { export async function waitForDashboardToLoad(section: any): Promise { const wait = getWaitHelper(); logger.info('Waiting for Liberty Dashboard to load'); - - // Expand the section - await waitForSuccess(async () => { - await section.expand(); - }); - - // Wait for section container to become stable after expansion - await wait.sleep(2000); - - // Wait for items to appear with retry on ElementNotInteractableError + + // Re-fetch a fresh section on every poll iteration. The section reference + // passed in (or obtained just before this call) goes stale on cold runners + // once VS Code rebuilds the sidebar DOM after workspace activation. + // Using a stale reference causes ElementNotInteractableError on every + // getVisibleItems() call, looping forever until timeout. await wait.forCondition(async () => { try { - const items = await section.getVisibleItems(); + const freshSection = await getDashboardSection(new SideBarView()); + await freshSection.expand(); + await wait.sleep(500); + const items = await freshSection.getVisibleItems(); if (items && items.length > 0) { logger.info(`Dashboard loaded with ${items.length} items`); return true; } } catch (error: any) { - // Retry on ElementNotInteractableError - if (error.name === 'ElementNotInteractableError') { + if (error.name === 'ElementNotInteractableError' || + error.name === 'StaleElementReferenceError') { logger.info('Container not yet interactable, retrying...'); return; } @@ -553,7 +550,7 @@ export async function waitForDashboardToLoad(section: any): Promise { } return; }, { - timeout: 120000, // 2 minutes max + timeout: 240000, // 4 minutes — matches the 275 s Mocha cap on "Liberty Tools shows items" pollInterval: 5000, // check every 5 seconds message: 'Dashboard items did not load' }); @@ -745,7 +742,6 @@ export async function closeWorkspace(): Promise { try { await wait.forCondition(async () => { try { - const { SideBarView } = require('vscode-extension-tester'); const sidebar = new SideBarView(); const sections = await sidebar.getContent().getSections(); for (const sec of sections) { From 6d8875c2642656df98a618fd9d1a5fe67578e535 Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Mon, 6 Jul 2026 13:37:57 -0500 Subject: [PATCH 05/10] more timeouts and retry confirm() --- src/test/utils/testUtils.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index d636d16f..1f07d7f6 100755 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -329,18 +329,19 @@ export async function launchDashboardAction(item: DefaultTreeItem, action: strin export async function setCustomParameter(customParam: string) { logger.info("Setting custom Parameter"); - + const wait = getWaitHelper(); + + // Find the input box and set text once. + const input = new InputBox(); + await input.click(); + await input.setText(customParam); + + // confirm() targets a button/Enter that may not be interactable immediately + // after setText (the input animation is still running on macOS CI). + // Retry only confirm() — don't re-create the InputBox on each attempt. await waitForSuccess(async () => { - const input = new InputBox(); - await input.click(); - await input.setText(customParam); - - // Wait for input to be fully ready before confirming - const wait = getWaitHelper(); - await wait.sleep(2000); - await input.confirm(); - }); + }, 15); // 15 s budget for the confirm to become interactable } @@ -550,7 +551,7 @@ export async function waitForDashboardToLoad(section: any): Promise { } return; }, { - timeout: 240000, // 4 minutes — matches the 275 s Mocha cap on "Liberty Tools shows items" + timeout: 265000, // stay inside the 275 s Mocha cap on "Liberty Tools shows items" pollInterval: 5000, // check every 5 seconds message: 'Dashboard items did not load' }); From c6a68ddf47a98d75ee2f595f34678d111d3227b3 Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Mon, 6 Jul 2026 14:05:17 -0500 Subject: [PATCH 06/10] increase dashboard load timeout --- src/test/shared/devModeTestSuite.ts | 12 ++++++++---- src/test/utils/testUtils.ts | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/test/shared/devModeTestSuite.ts b/src/test/shared/devModeTestSuite.ts index db512eb1..97e55fd3 100644 --- a/src/test/shared/devModeTestSuite.ts +++ b/src/test/shared/devModeTestSuite.ts @@ -73,16 +73,20 @@ export function runDevModeTestSuite(config: DevModeConfig): void { logger.testStart('Liberty Tools shows items - Maven'); try { logger.step(1, 'Getting dashboard section'); - const section = await dashboard.getSection(); + const section = await dashboard.getSection(); logger.stepSuccess(1, 'Dashboard section retrieved'); logger.step(2, 'Waiting for Liberty Tools to load'); await utils.waitForDashboardToLoad(section); logger.stepSuccess(2, 'Liberty Tools loaded successfully'); + // waitForDashboardToLoad already confirmed items exist on a fresh + // section reference. Re-fetch here so getVisibleItems() doesn't + // operate on the original stale reference from before the wait. logger.step(3, 'Getting visible items from section'); + const freshSection = await dashboard.getSection(); const menu = await utils.waitForCondition(async () => { - const items = await section.getVisibleItems(); + const items = await freshSection.getVisibleItems(); if (items && items.length > 0) { return items; } @@ -92,7 +96,7 @@ export function runDevModeTestSuite(config: DevModeConfig): void { expect(menu).not.empty; logger.step(4, `Finding Maven project item: ${config.projectConstant}`); - const item = await dashboard.getProjectItem(config.projectConstant); + const item = await dashboard.getProjectItem(config.projectConstant); logger.stepSuccess(4, 'Maven project item found'); expect(item).not.undefined; @@ -101,7 +105,7 @@ export function runDevModeTestSuite(config: DevModeConfig): void { logger.testFailed(`Liberty Tools shows items - ${config.buildTool}`, error); throw error; } - }).timeout(275000); + }).timeout(360000); it(`Start ${config.buildTool} project from Liberty Tools`, async () => { logger.testStart('Start Maven project from Liberty Tools'); diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index 1f07d7f6..e8430450 100755 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -551,7 +551,7 @@ export async function waitForDashboardToLoad(section: any): Promise { } return; }, { - timeout: 265000, // stay inside the 275 s Mocha cap on "Liberty Tools shows items" + timeout: 340000, // stay inside the 360 s Mocha cap on "Liberty Tools shows items" pollInterval: 5000, // check every 5 seconds message: 'Dashboard items did not load' }); From 04344a8821a56b7c3a7de96583332804bb9ab5eb Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Mon, 6 Jul 2026 15:36:21 -0500 Subject: [PATCH 07/10] activate extension before dashboard poll --- src/test/GradleTestDevModeActions.ts | 2 +- src/test/shared/devModeTestSuite.ts | 2 +- src/test/utils/testUtils.ts | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/test/GradleTestDevModeActions.ts b/src/test/GradleTestDevModeActions.ts index 59b4c53e..bedb249d 100755 --- a/src/test/GradleTestDevModeActions.ts +++ b/src/test/GradleTestDevModeActions.ts @@ -183,7 +183,7 @@ describe('Gradle-specific devmode action tests', () => { * Closing the workspace ensures the next test file starts with a clean slate. */ after(async function() { - this.timeout(45000); + this.timeout(60000); await utils.closeWorkspace(); }); }); diff --git a/src/test/shared/devModeTestSuite.ts b/src/test/shared/devModeTestSuite.ts index 97e55fd3..47b3acd7 100644 --- a/src/test/shared/devModeTestSuite.ts +++ b/src/test/shared/devModeTestSuite.ts @@ -105,7 +105,7 @@ export function runDevModeTestSuite(config: DevModeConfig): void { logger.testFailed(`Liberty Tools shows items - ${config.buildTool}`, error); throw error; } - }).timeout(360000); + }).timeout(480000); it(`Start ${config.buildTool} project from Liberty Tools`, async () => { logger.testStart('Start Maven project from Liberty Tools'); diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index e8430450..7a7f0402 100755 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -526,6 +526,18 @@ export async function waitForDashboardToLoad(section: any): Promise { const wait = getWaitHelper(); logger.info('Waiting for Liberty Dashboard to load'); + // Focus the Liberty Tools view before polling. The extension has an + // "onView:liberty-dev" activation event — explicitly revealing the view + // triggers immediate activation and project scanning instead of waiting + // for the extension host to do it passively, which can take 5+ minutes + // on cold mac-previous runners after a prior heavy devmode suite. + try { + await new Workbench().executeCommand('liberty-dev.focus'); + await wait.sleep(1000); + } catch { + // Non-fatal — falls back to the poll loop below if the command fails + } + // Re-fetch a fresh section on every poll iteration. The section reference // passed in (or obtained just before this call) goes stale on cold runners // once VS Code rebuilds the sidebar DOM after workspace activation. @@ -551,7 +563,7 @@ export async function waitForDashboardToLoad(section: any): Promise { } return; }, { - timeout: 340000, // stay inside the 360 s Mocha cap on "Liberty Tools shows items" + timeout: 460000, // stay inside the 480 s Mocha cap on "Liberty Tools shows items" pollInterval: 5000, // check every 5 seconds message: 'Dashboard items did not load' }); From 1fc17db24accf6e8b507d398ea6b8b4ce8d779ef Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Tue, 7 Jul 2026 09:05:29 -0500 Subject: [PATCH 08/10] remove dev.focus --- src/test/utils/testUtils.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index 7a7f0402..5088a9bc 100755 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -526,24 +526,22 @@ export async function waitForDashboardToLoad(section: any): Promise { const wait = getWaitHelper(); logger.info('Waiting for Liberty Dashboard to load'); - // Focus the Liberty Tools view before polling. The extension has an - // "onView:liberty-dev" activation event — explicitly revealing the view - // triggers immediate activation and project scanning instead of waiting - // for the extension host to do it passively, which can take 5+ minutes - // on cold mac-previous runners after a prior heavy devmode suite. - try { - await new Workbench().executeCommand('liberty-dev.focus'); - await wait.sleep(1000); - } catch { - // Non-fatal — falls back to the poll loop below if the command fails - } - // Re-fetch a fresh section on every poll iteration. The section reference // passed in (or obtained just before this call) goes stale on cold runners // once VS Code rebuilds the sidebar DOM after workspace activation. // Using a stale reference causes ElementNotInteractableError on every // getVisibleItems() call, looping forever until timeout. await wait.forCondition(async () => { + // Prod the extension to refresh on every iteration. On cold previous + // runners registerTreeDataProvider is not called until the Liberty LS + // JVM has started (extension.ts registers the tree provider only after + // startLangServer resolves). Executing liberty.explorer.refresh forces + // a repopulation once the provider is registered; before that the + // command simply isn't registered and the error is swallowed. + try { + await new Workbench().executeCommand('liberty.explorer.refresh'); + } catch { /* command not yet registered — keep waiting */ } + try { const freshSection = await getDashboardSection(new SideBarView()); await freshSection.expand(); From 7afa677bdc31c2e53257a8983b992cb11283c159 Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Tue, 7 Jul 2026 09:19:30 -0500 Subject: [PATCH 09/10] wait for language server to initialize before starting a test --- src/test/shared/devModeTestSuite.ts | 14 ++++++++++++-- src/test/utils/testUtils.ts | 10 ---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/test/shared/devModeTestSuite.ts b/src/test/shared/devModeTestSuite.ts index 47b3acd7..4f4ef2cc 100644 --- a/src/test/shared/devModeTestSuite.ts +++ b/src/test/shared/devModeTestSuite.ts @@ -24,11 +24,21 @@ export function runDevModeTestSuite(config: DevModeConfig): void { describe(`Devmode action tests for ${config.buildTool} Project`, () => { before(async function() { - this.timeout(60000); + this.timeout(300000); await VSBrowser.instance.openResources(config.getProjectPath()); await VSBrowser.instance.waitForWorkbench(); - + + // Wait for the Liberty LS to initialize before any test runs. + // registerTreeDataProvider is only called after startLangServer + // resolves — without this, waitForDashboardToLoad polls an empty + // tree for minutes on cold mac-previous runners. + await utils.waitForLanguageServerInit( + 'Language Support for Liberty', + 'Initialized Liberty Language server', + 240 + ); + dashboard = new DashboardPage(); }); diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index 5088a9bc..21f91773 100755 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -532,16 +532,6 @@ export async function waitForDashboardToLoad(section: any): Promise { // Using a stale reference causes ElementNotInteractableError on every // getVisibleItems() call, looping forever until timeout. await wait.forCondition(async () => { - // Prod the extension to refresh on every iteration. On cold previous - // runners registerTreeDataProvider is not called until the Liberty LS - // JVM has started (extension.ts registers the tree provider only after - // startLangServer resolves). Executing liberty.explorer.refresh forces - // a repopulation once the provider is registered; before that the - // command simply isn't registered and the error is swallowed. - try { - await new Workbench().executeCommand('liberty.explorer.refresh'); - } catch { /* command not yet registered — keep waiting */ } - try { const freshSection = await getDashboardSection(new SideBarView()); await freshSection.expand(); From d920d73dc7c8ad3fac13124b3c2f831ac6f68bd8 Mon Sep 17 00:00:00 2001 From: Siddharth Saladi Date: Tue, 7 Jul 2026 10:17:33 -0500 Subject: [PATCH 10/10] more timeouts --- src/test/shared/devModeTestSuite.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/test/shared/devModeTestSuite.ts b/src/test/shared/devModeTestSuite.ts index 4f4ef2cc..f63beebb 100644 --- a/src/test/shared/devModeTestSuite.ts +++ b/src/test/shared/devModeTestSuite.ts @@ -24,7 +24,7 @@ export function runDevModeTestSuite(config: DevModeConfig): void { describe(`Devmode action tests for ${config.buildTool} Project`, () => { before(async function() { - this.timeout(300000); + this.timeout(480000); await VSBrowser.instance.openResources(config.getProjectPath()); await VSBrowser.instance.waitForWorkbench(); @@ -33,11 +33,18 @@ export function runDevModeTestSuite(config: DevModeConfig): void { // registerTreeDataProvider is only called after startLangServer // resolves — without this, waitForDashboardToLoad polls an empty // tree for minutes on cold mac-previous runners. - await utils.waitForLanguageServerInit( - 'Language Support for Liberty', - 'Initialized Liberty Language server', - 240 - ); + // Non-fatal: if the LS doesn't init in time we proceed anyway and + // let waitForDashboardToLoad handle the remaining wait — this avoids + // skipping the entire suite when the before() hook itself times out. + try { + await utils.waitForLanguageServerInit( + 'Language Support for Liberty', + 'Initialized Liberty Language server', + 420 + ); + } catch { + logger.info('Liberty LS did not init within before() budget — proceeding anyway'); + } dashboard = new DashboardPage(); });