// // TabBarNavigator.swift // suixinkanUITests // // Created by Codex on 2026/6/26. // import XCTest /// 主 Tab 导航封装。 enum TabBarNavigator { enum Tab: String, CaseIterable { case home = "首页" case orders = "订单" case statistics = "数据" case profile = "我的" var identifier: String { switch self { case .home: return "main.tab.home" case .orders: return "main.tab.orders" case .statistics: return "main.tab.statistics" case .profile: return "main.tab.profile" } } } /// 切换到指定 Tab 并等待页面稳定。 static func select(_ tab: Tab, app: SuixinkanApp, file: StaticString = #filePath, line: UInt = #line) { let button = bottomTabButton(named: tab.rawValue, in: app) XCTAssertTrue(button.waitForExistence(timeout: 8), "未找到 Tab:\(tab.rawValue)", file: file, line: line) button.tap() app.waitForGlobalLoadingToDisappear() } /// 获取主界面的扫码入口按钮。 static func scannerButton(in app: SuixinkanApp) -> XCUIElement { let identifiedButton = app.application.buttons["main.scan"] if identifiedButton.exists { return identifiedButton } let systemTabButton = app.application.tabBars.buttons["扫码核销"] if systemTabButton.exists { return systemTabButton } return bottomTabButton(named: "扫码核销", in: app) } /// 判断主 Tab 是否已经出现在界面上。 static func isVisible(in app: SuixinkanApp) -> Bool { Tab.allCases.contains { tab in app.application.tabBars.buttons[tab.rawValue].exists || app.application.buttons[tab.identifier].exists || app.application.buttons[tab.rawValue].exists } } /// 在底部系统 TabBar 或旧自定义 TabBar 中定位唯一可点击的 Tab 按钮。 private static func bottomTabButton(named title: String, in app: SuixinkanApp) -> XCUIElement { if let tab = Tab(rawValue: title) { let identifiedButton = app.application.buttons[tab.identifier] if identifiedButton.exists { return identifiedButton } } let systemTabButton = app.application.tabBars.buttons[title] if systemTabButton.exists { return systemTabButton } let candidates = app.application.buttons.matching(NSPredicate(format: "label == %@", title)) let screenHeight = app.application.windows.element(boundBy: 0).frame.height var bestMatch: XCUIElement? var bestY: CGFloat = -1 for index in 0.. screenHeight * 0.72 else { continue } if midY > bestY { bestMatch = candidate bestY = midY } } return bestMatch ?? candidates.firstMatch } }