From 1cac3b507bdf938ca53a10d5dfc586173e705ee0 Mon Sep 17 00:00:00 2001 From: arzafran Date: Thu, 1 Oct 2026 15:10:52 -0300 Subject: [PATCH 1/3] fix: skip escrow orphan recovery under automated UI tests --- Sources/AppDelegate.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 265e2ff3..e89ca807 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1946,6 +1946,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser /// closed immediately once the revived panel has taken its place in the /// same pane, so no tab is ever left showing two panels or an empty one. private func reconcileOrphanedEscrowedSessions() { + // Automated runs relaunch the app many times in a row, and each run leaves its shells + // escrowed. Snapshot restore already skips these runs; recovering their orphans would + // add the previous run's terminals as extra workspaces to every fresh launch. + guard !SessionRestorePolicy.isRunningUnderAutomatedTests() else { + dilog("escrow.reconcile", "skipped=automated_tests") + return + } var known = startupEndedHiddenSessionIds for context in mainWindowContexts.values { for workspace in context.tabManager.tabs { From 35535cbdf228774418026b94bdcc219154d38bf0 Mon Sep 17 00:00:00 2001 From: arzafran Date: Thu, 1 Oct 2026 15:33:29 -0300 Subject: [PATCH 2/3] fix: keep the 2x2 bottom-row UI test harness within the four-pane limit --- Sources/TabManager+UITestHarness.swift | 15 ++-- .../CloseWindowConfirmDialogUITests.swift | 74 ------------------- 2 files changed, 6 insertions(+), 83 deletions(-) delete mode 100644 programaUITests/CloseWindowConfirmDialogUITests.swift diff --git a/Sources/TabManager+UITestHarness.swift b/Sources/TabManager+UITestHarness.swift index ea04df10..37bff17d 100644 --- a/Sources/TabManager+UITestHarness.swift +++ b/Sources/TabManager+UITestHarness.swift @@ -946,19 +946,16 @@ extension TabManager { } } else if layout == "tdlr_close_bottom_then_exit_top_left" { // Alternate repro flow: - // 1) split top/down - // 2) split left/right for each row (2x2) - // 3) close both bottom panes - // 4) trigger Ctrl+D in top-left + // 1) split each column top/down (2x2; the right column already exists, and a + // fifth pane would exceed SplitPolicy.maxPanesPerWorkspace) + // 2) close both bottom panes + // 3) trigger Ctrl+D in top-left + let topRight = rightPanel guard let bottomLeft = tab.newTerminalSplit(from: leftPanelId, orientation: .vertical) else { write(["setupError": "Failed to create bottom-left split", "done": "1"]) return } - guard let topRight = tab.newTerminalSplit(from: leftPanelId, orientation: .horizontal) else { - write(["setupError": "Failed to create top-right split", "done": "1"]) - return - } - guard let bottomRight = tab.newTerminalSplit(from: bottomLeft.id, orientation: .horizontal) else { + guard let bottomRight = tab.newTerminalSplit(from: topRight.id, orientation: .vertical) else { write(["setupError": "Failed to create bottom-right split", "done": "1"]) return } diff --git a/programaUITests/CloseWindowConfirmDialogUITests.swift b/programaUITests/CloseWindowConfirmDialogUITests.swift deleted file mode 100644 index 3881b7e3..00000000 --- a/programaUITests/CloseWindowConfirmDialogUITests.swift +++ /dev/null @@ -1,74 +0,0 @@ -import XCTest - -final class CloseWindowConfirmDialogUITests: XCTestCase { - override func setUp() { - super.setUp() - continueAfterFailure = false - } - - func testCloseShortcutPreservesRunningShellOnReopen() throws { - try assertRunningShellSurvivesClose(useNativeButton: false) - } - - func testNativeCloseButtonPreservesRunningShellOnReopen() throws { - try assertRunningShellSurvivesClose(useNativeButton: true) - } - - private func assertRunningShellSurvivesClose(useNativeButton: Bool) throws { - let token = UUID().uuidString - let before = URL(fileURLWithPath: "/tmp/programa-close-before-\(token)") - let after = URL(fileURLWithPath: "/tmp/programa-close-after-\(token)") - defer { - try? FileManager.default.removeItem(at: before) - try? FileManager.default.removeItem(at: after) - } - let app = XCUIApplication() - app.launchEnvironment["PROGRAMA_TAG"] = "ui-tests-close-\(token.lowercased())" - app.launch() - if !app.wait(for: .runningForeground, timeout: 12) { app.activate() } - XCTAssertTrue(app.wait(for: .runningForeground, timeout: 6)) - XCTAssertTrue(app.windows.firstMatch.waitForExistence(timeout: 5)) - - // A fresh shell displaying old output cannot preserve both its process - // identity and this shell-local variable. - app.typeText("PROGRAMA_REOPEN_TOKEN=\(token); printf '%s\\n%s\\n' \"$$\" \"$PROGRAMA_REOPEN_TOKEN\" > '\(before.path)'") - app.typeKey(.return, modifierFlags: []) - XCTAssertTrue(waitUntil { self.shellRecord(at: before)?.contains(token) == true }) - let expected = try XCTUnwrap(shellRecord(at: before)) - - if useNativeButton { - app.windows.firstMatch.buttons[XCUIIdentifierCloseWindow].click() - } else { - app.typeKey("w", modifierFlags: [.command, .control]) - } - XCTAssertTrue(waitUntil { app.windows.count == 0 }, "Closing should hide the window without a confirmation dialog") - XCTAssertFalse(app.staticTexts["Close window?"].exists) - XCTAssertNotEqual(app.state, .notRunning, "Closing a window must keep its sessions running") - - app.typeKey("n", modifierFlags: [.command, .shift]) - XCTAssertTrue(app.windows.firstMatch.waitForExistence(timeout: 5)) - XCTAssertEqual(app.windows.count, 1, "New Window should reopen the preserved window") - app.typeText("printf '%s\\n%s\\n' \"$$\" \"$PROGRAMA_REOPEN_TOKEN\" > '\(after.path)'") - app.typeKey(.return, modifierFlags: []) - XCTAssertTrue(waitUntil { self.shellRecord(at: after) != nil }) - XCTAssertEqual(try XCTUnwrap(shellRecord(at: after)), expected, "Reopen must preserve the running shell, not just its text") - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Reopened preserved terminal" - attachment.lifetime = .keepAlways - add(attachment) - } - - private func shellRecord(at url: URL) -> String? { - guard let value = try? String(contentsOf: url, encoding: .utf8), - value.split(separator: "\n", omittingEmptySubsequences: false).count >= 3 else { return nil } - return value - } - - private func waitUntil(_ condition: @escaping () -> Bool) -> Bool { - let expectation = XCTNSPredicateExpectation( - predicate: NSPredicate { _, _ in condition() }, object: NSObject() - ) - return XCTWaiter().wait(for: [expectation], timeout: 10) == .completed - } -} From fdf25d7e3dfc9a50a9fea4f743b6d207ab1fc3a4 Mon Sep 17 00:00:00 2001 From: arzafran Date: Thu, 1 Oct 2026 15:34:04 -0300 Subject: [PATCH 3/3] test: drop the hide-on-close window test superseded by confirm-and-free close --- .github/workflows/ci.yml | 1 - GhosttyTabs.xcodeproj/project.pbxproj | 4 ---- 2 files changed, 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f96a7fe1..dd5e8589 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1415,7 +1415,6 @@ jobs: -derivedDataPath "$PROGRAMA_DERIVED_DATA_DIR" \ -destination "platform=macOS" \ -maximum-test-execution-time-allowance 180 \ - -only-testing:programaUITests/CloseWindowConfirmDialogUITests \ -only-testing:programaUITests/CloseWorkspaceConfirmDialogUITests \ -only-testing:programaUITests/CloseWorkspacesConfirmDialogUITests \ -only-testing:programaUITests/CloseWorkspaceCmdDUITests \ diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index ee8b8873..032331bc 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -293,7 +293,6 @@ B9000015A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000016A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift */; }; B900001AA1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000019A1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift */; }; B9000023A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000022A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift */; }; - B9000025A1B2C3D4E5F60719 /* CloseWindowConfirmDialogUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000026A1B2C3D4E5F60719 /* CloseWindowConfirmDialogUITests.swift */; }; BEEF50100000000000000003 /* WindowCloseConfirmCancelUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEEF50100000000000000004 /* WindowCloseConfirmCancelUITests.swift */; }; D0E0F0B0A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E0F0B1A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift */; }; D0E0F0B2A1B2C3D4E5F60718 /* BrowserOmnibarSuggestionsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E0F0B3A1B2C3D4E5F60718 /* BrowserOmnibarSuggestionsUITests.swift */; }; @@ -716,7 +715,6 @@ B9000016A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiWindowNotificationsUITests.swift; sourceTree = ""; }; B9000019A1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseWorkspaceConfirmDialogUITests.swift; sourceTree = ""; }; B9000022A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseWorkspaceCmdDUITests.swift; sourceTree = ""; }; - B9000026A1B2C3D4E5F60719 /* CloseWindowConfirmDialogUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseWindowConfirmDialogUITests.swift; sourceTree = ""; }; BEEF50100000000000000004 /* WindowCloseConfirmCancelUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowCloseConfirmCancelUITests.swift; sourceTree = ""; }; D0E0F0B1A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserPaneNavigationKeybindUITests.swift; sourceTree = ""; }; D0E0F0B3A1B2C3D4E5F60718 /* BrowserOmnibarSuggestionsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserOmnibarSuggestionsUITests.swift; sourceTree = ""; }; @@ -1241,7 +1239,6 @@ B9000013A1B2C3D4E5F60719 /* JumpToUnreadUITests.swift */, B9000022A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift */, B9000019A1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift */, - B9000026A1B2C3D4E5F60719 /* CloseWindowConfirmDialogUITests.swift */, BEEF50100000000000000004 /* WindowCloseConfirmCancelUITests.swift */, B9000016A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift */, 818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */, @@ -1717,7 +1714,6 @@ B9000014A1B2C3D4E5F60719 /* JumpToUnreadUITests.swift in Sources */, B900001AA1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift in Sources */, B9000023A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift in Sources */, - B9000025A1B2C3D4E5F60719 /* CloseWindowConfirmDialogUITests.swift in Sources */, BEEF50100000000000000003 /* WindowCloseConfirmCancelUITests.swift in Sources */, B9000015A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift in Sources */, B8F266236A1A3D9A45BD840F /* SidebarResizeUITests.swift in Sources */,