5656import org .eclipse .swtbot .swt .finder .widgets .SWTBotToolbarPushButton ;
5757import org .eclipse .swtbot .swt .finder .widgets .SWTBotTree ;
5858import org .eclipse .swtbot .swt .finder .widgets .SWTBotTreeItem ;
59+ import org .eclipse .ui .IViewPart ;
5960import org .eclipse .ui .IWorkbench ;
6061import org .eclipse .ui .IWorkbenchWindow ;
6162import org .eclipse .ui .PlatformUI ;
@@ -151,6 +152,25 @@ public static SWTBotMenu getDebuggerConnectMenuForDebugObject(Object debugObject
151152 return obj .contextMenu ("Connect Liberty Debugger" );
152153 }
153154
155+ /**
156+ * Polls the input debugger context menu object ("Connect Liberty Debugger") until its
157+ * enabled state matches the expected enabled input indicator.
158+ *
159+ * @param debugObject The debug view tree item to check.
160+ * @param expectedEnabled {@code true} to wait until the menu is enabled;
161+ * {@code false} to wait until it is disabled.
162+ * @return {@code true} if the expected state was reached within 5 seconds.
163+ */
164+ public static boolean waitForDebuggerConnectMenuState (Object debugObject , boolean expectedEnabled ) {
165+ return SWTBotTestCondition .waitFor (() -> {
166+ try {
167+ return getDebuggerConnectMenuForDebugObject (debugObject ).isEnabled () == expectedEnabled ;
168+ } catch (Exception e ) {
169+ return false ;
170+ }
171+ }, SWTBotTestCondition .MIN_WAIT_MS );
172+ }
173+
154174 /**
155175 * Disconnects the given debug target (debugger) in the Debug View
156176 *
@@ -793,6 +813,27 @@ public static TreeItem getDefaultSourceLookupTreeItemNoBot(Shell shell) {
793813 return ti ;
794814 }
795815
816+ /**
817+ * Returns a comma-separated string of the direct child item texts of the given tree item.
818+ * Uses {@code getItems()} (safe: returns an empty array, never throws) so it can be called
819+ * while the shell is still open without risking an {@code IndexOutOfBoundsException}.
820+ *
821+ * @param treeItem The parent tree item whose children to list.
822+ * @return A string of the form {@code [child1, child2, ...]} or {@code []} if there are none.
823+ */
824+ public static String getTreeItemChildrenAsString (SWTBotTreeItem treeItem ) {
825+ SWTBotTreeItem [] items = treeItem .getItems ();
826+ StringBuilder sb = new StringBuilder ("[" );
827+ for (int i = 0 ; i < items .length ; i ++) {
828+ if (i > 0 ) {
829+ sb .append (", " );
830+ }
831+ sb .append (items [i ].getText ());
832+ }
833+ sb .append ("]" );
834+ return sb .toString ();
835+ }
836+
796837 public static SWTBotTreeItem getLibertyToolsConfigMenuItem (Shell shell ) {
797838 return new SWTBotTreeItem ((TreeItem ) find (LAUNCH_CONFIG_LIBERTY_MENU_NAME , shell ));
798839 }
@@ -968,24 +1009,54 @@ public static void checkRunCleanProjectCheckBox(Shell shell, String runDebugConf
9681009
9691010 public static Object getAppInPackageExplorerTree (String appName ) {
9701011 openJavaPerspectiveViaMenu ();
971- // Open Package Explorer view using Eclipse API instead of menu navigation
972- // This is more reliable in headless CI environments
1012+ // Open Package Explorer view using Eclipse API instead of menu navigation.
1013+ // This is more reliable in headless CI environments.
9731014 showPackageExplorerView ();
974- Object peView = MagicWidgetFinder .findGlobal ("Package Explorer" );
1015+
1016+ // Obtain the Package Explorer IViewPart directly from the Eclipse API rather
1017+ // than using findGlobal(), which scans all visible shells and could match a
1018+ // widget in an unrelated view. MagicWidgetFinder already knows how to traverse
1019+ // a CommonNavigator (the PE's base class) to reach its TreeItems, so passing
1020+ // the IViewPart as the neighbour is both correct and race-condition-free.
1021+ IViewPart peView = getPackageExplorerView ();
9751022
9761023 Object project = MagicWidgetFinder .find (appName , peView , Option .factory ().useContains (true ).widgetClass (TreeItem .class ).build ());
977- go (project );
9781024
979- // Wait until the tree item is enabled before returning.
1025+ // Select and focus the item so the Package Explorer's selection provider is
1026+ // active before the caller opens a context menu. Calling go() here is omitted
1027+ // intentionally: the intermediate click it triggers can fire selection-listener
1028+ // events that steal focus away before the caller reaches context().
9801029 SWTBotTreeItem botItem = new SWTBotTreeItem ((TreeItem ) project );
9811030 SWTBotTestCondition .waitFor (botItem ::isEnabled , SWTBotTestCondition .SHORT_WAIT_MS );
9821031 botItem .select ();
9831032 botItem .setFocus ();
984- System .out .println ("Explorer item selected: " + botItem .contextMenu ().menuItems ());
9851033
1034+ System .out .println ("Project in context explorer context menu: " + botItem .contextMenu ().menuItems ());
9861035 return project ;
9871036 }
9881037
1038+ /**
1039+ * Returns the Package Explorer IViewPart obtained directly from the Eclipse API.
1040+ * Must be called after showPackageExplorerView() has confirmed the view is present.
1041+ *
1042+ * @return The Package Explorer IViewPart, or {@code null} if unavailable.
1043+ */
1044+ private static IViewPart getPackageExplorerView () {
1045+ final IViewPart [] result = { null };
1046+ Display .getDefault ().syncExec (() -> {
1047+ try {
1048+ IWorkbench wb = PlatformUI .getWorkbench ();
1049+ IWorkbenchWindow window = wb .getActiveWorkbenchWindow ();
1050+ if (window != null && window .getActivePage () != null ) {
1051+ result [0 ] = window .getActivePage ().findView ("org.eclipse.jdt.ui.PackageExplorer" );
1052+ }
1053+ } catch (Exception e ) {
1054+ System .err .println ("Failed to retrieve Package Explorer view: " + e .getMessage ());
1055+ }
1056+ });
1057+ return result [0 ];
1058+ }
1059+
9891060 /**
9901061 * Launches the start action using the debug as configuration shortcut.
9911062 *
@@ -1324,6 +1395,11 @@ public static void openJRETab(SWTWorkbenchBot bot) {
13241395 * Switches the Liberty run configuration main tab to the Source Tab. A Liberty configuration must be opened prior to calling this
13251396 * method.
13261397 *
1398+ * <p>On Linux the source path computer runs asynchronously after a new configuration is created
1399+ * and populates the source lookup tree incrementally. This method waits until the "Default" tree
1400+ * item count has been stable (unchanged) across 3 consecutive polls before returning, ensuring
1401+ * the tree is fully populated before callers inspect its contents.
1402+ *
13271403 * @param shell The Debug Configurations shell already obtained by the caller.
13281404 */
13291405 public static void openSourceTab (Shell shell ) {
@@ -1332,6 +1408,40 @@ public static void openSourceTab(Shell shell) {
13321408 SWTBot shellBot = botShell .bot ();
13331409 SWTBotCTabItem tabItem = shellBot .cTabItem ("Source" );
13341410 tabItem .activate ().setFocus ();
1411+
1412+ // Wait until the Source tab's "Default" item has a stable non-zero child count
1413+ // across 3 consecutive polls. The source path computer on Linux populates the tree
1414+ // incrementally, so we must confirm the count has stopped changing before returning.
1415+ // We use shellBot.tree(1) to target the Source tab's tree directly rather than
1416+ // find("Default", shell), which can match the wrong tree on the left-hand side of
1417+ // the Debug Configurations dialog.
1418+ final int STABLE_ITERATIONS_REQUIRED = 3 ;
1419+ final int [] lastCount = { -1 };
1420+ final int [] stableIterations = { 0 };
1421+ SWTBotTestCondition .waitFor (() -> {
1422+ try {
1423+ SWTBotTreeItem defaultItem = shellBot .tree (1 ).getTreeItem ("Default" );
1424+ if (defaultItem == null || !defaultItem .isEnabled ()) {
1425+ lastCount [0 ] = -1 ;
1426+ stableIterations [0 ] = 0 ;
1427+ return false ;
1428+ }
1429+ defaultItem .expand ();
1430+ int count = defaultItem .getItems ().length ;
1431+ if (count > 0 && count == lastCount [0 ]) {
1432+ stableIterations [0 ]++;
1433+ } else {
1434+ // Count changed (or is still zero) — reset the stability counter.
1435+ stableIterations [0 ] = 0 ;
1436+ lastCount [0 ] = count ;
1437+ }
1438+ return stableIterations [0 ] >= STABLE_ITERATIONS_REQUIRED ;
1439+ } catch (Exception e ) {
1440+ lastCount [0 ] = -1 ;
1441+ stableIterations [0 ] = 0 ;
1442+ return false ;
1443+ }
1444+ }, SWTBotTestCondition .MID_WAIT_MS );
13351445 }
13361446
13371447 /**
0 commit comments