import XCTest /// Comprehensive UI tests for MixBoard iOS. /// Tests cover: app launch, playlist CRUD, navigation, settings, and Now Playing. final class MixBoardUITests: XCTestCase { var app: XCUIApplication! override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launchArguments += ["-UITesting"] app.launch() } override func tearDownWithError() throws { app = nil } // MARK: - App Launch func testAppLaunchShowsMixBoardTitle() { // The main screen should show "MixBoard" as the navigation title let title = app.navigationBars["MixBoard"] XCTAssertTrue(title.waitForExistence(timeout: 5), "MixBoard navigation title should exist") } func testAppLaunchShowsToolbarButtons() { // Library button should exist let libraryButton = app.buttons["libraryButton"] XCTAssertTrue(libraryButton.waitForExistence(timeout: 5), "Library button should exist") // Settings button should exist let settingsButton = app.buttons["settingsButton"] XCTAssertTrue(settingsButton.exists, "Settings button should exist") // New playlist button should exist let newPlaylistButton = app.buttons["newPlaylistButton"] XCTAssertTrue(newPlaylistButton.exists, "New playlist button should exist") } // MARK: - Empty State func testEmptyStateVisible() { // On a fresh install with no playlists, the empty state should be visible let emptyState = app.staticTexts["No playlists yet"] if emptyState.waitForExistence(timeout: 3) { XCTAssertTrue(emptyState.exists) let subtitle = app.staticTexts["Create a playlist to start building your mix"] XCTAssertTrue(subtitle.exists) } // If playlists already exist from a previous test, that's OK — skip } // MARK: - Create Playlist func testCreatePlaylist() { let newPlaylistButton = app.buttons["newPlaylistButton"] XCTAssertTrue(newPlaylistButton.waitForExistence(timeout: 5)) newPlaylistButton.tap() // Alert should appear with "New Playlist" title let alert = app.alerts["New Playlist"] XCTAssertTrue(alert.waitForExistence(timeout: 3), "New Playlist alert should appear") // Type a name let textField = alert.textFields["Playlist name"] XCTAssertTrue(textField.exists, "Playlist name text field should exist") textField.tap() textField.typeText("UI Test Playlist") // Tap Create alert.buttons["Create"].tap() // Verify the playlist appears in the list let playlistCell = app.staticTexts["UI Test Playlist"] XCTAssertTrue(playlistCell.waitForExistence(timeout: 3), "Created playlist should appear in the list") } func testCreatePlaylistCancel() { let newPlaylistButton = app.buttons["newPlaylistButton"] XCTAssertTrue(newPlaylistButton.waitForExistence(timeout: 5)) newPlaylistButton.tap() let alert = app.alerts["New Playlist"] XCTAssertTrue(alert.waitForExistence(timeout: 3)) // Cancel alert.buttons["Cancel"].tap() // Alert should dismiss XCTAssertFalse(alert.exists) } // MARK: - Playlist Navigation func testNavigateToPlaylistDetail() { // Create a playlist first createPlaylistViaUI(name: "Detail Test") // Tap it let cell = app.staticTexts["Detail Test"] XCTAssertTrue(cell.waitForExistence(timeout: 3)) cell.tap() // Should navigate to detail view let detailView = app.otherElements["playlistDetailView"] XCTAssertTrue(detailView.waitForExistence(timeout: 3), "Playlist detail view should appear") } func testPlaylistDetailShowsHeader() { createPlaylistViaUI(name: "Header Test") let cell = app.staticTexts["Header Test"] XCTAssertTrue(cell.waitForExistence(timeout: 3)) cell.tap() // Should show track count let trackCount = app.staticTexts["0 tracks"] XCTAssertTrue(trackCount.waitForExistence(timeout: 3), "Track count should show 0 tracks") } // MARK: - Delete Playlist func testDeletePlaylistViaSwipe() { createPlaylistViaUI(name: "Delete Me") let cell = app.staticTexts["Delete Me"] XCTAssertTrue(cell.waitForExistence(timeout: 3)) // Swipe left to reveal delete cell.swipeLeft() // Tap delete let deleteButton = app.buttons["Delete"] if deleteButton.waitForExistence(timeout: 2) { deleteButton.tap() // Verify it's gone XCTAssertFalse(cell.waitForExistence(timeout: 2)) } } // MARK: - Library Navigation func testOpenLibrarySheet() { let libraryButton = app.buttons["libraryButton"] XCTAssertTrue(libraryButton.waitForExistence(timeout: 5)) libraryButton.tap() // Library view should appear let libraryView = app.otherElements["libraryView"] let libraryTitle = app.navigationBars["Library"] let appeared = libraryView.waitForExistence(timeout: 3) || libraryTitle.waitForExistence(timeout: 3) XCTAssertTrue(appeared, "Library should open as a sheet") } func testLibraryBrowseModes() { let libraryButton = app.buttons["libraryButton"] XCTAssertTrue(libraryButton.waitForExistence(timeout: 5)) libraryButton.tap() // Check browse mode buttons exist let modes = ["Folders", "Songs", "Artists", "Albums", "Genres"] for mode in modes { let button = app.buttons[mode] if button.waitForExistence(timeout: 2) { XCTAssertTrue(button.exists, "\(mode) browse mode button should exist") } } } // MARK: - Settings Navigation func testOpenSettingsSheet() { let settingsButton = app.buttons["settingsButton"] XCTAssertTrue(settingsButton.waitForExistence(timeout: 5)) settingsButton.tap() // Settings view should appear let settingsTitle = app.navigationBars["Settings"] XCTAssertTrue(settingsTitle.waitForExistence(timeout: 3), "Settings should open as a sheet") } func testSettingsShowsSkinSection() { let settingsButton = app.buttons["settingsButton"] XCTAssertTrue(settingsButton.waitForExistence(timeout: 5)) settingsButton.tap() // Check that skin options are visible let skinSection = app.staticTexts["Skin"] XCTAssertTrue(skinSection.waitForExistence(timeout: 3), "Skin section should be visible") } func testSettingsShowsMixTargetsSection() { let settingsButton = app.buttons["settingsButton"] XCTAssertTrue(settingsButton.waitForExistence(timeout: 5)) settingsButton.tap() let mixTargetsSection = app.staticTexts["Mix Targets"] XCTAssertTrue(mixTargetsSection.waitForExistence(timeout: 3), "Mix Targets section should be visible") } func testSwitchSkin() { let settingsButton = app.buttons["settingsButton"] XCTAssertTrue(settingsButton.waitForExistence(timeout: 5)) settingsButton.tap() // Tap on a skin option let vinylButton = app.buttons.matching(NSPredicate(format: "label CONTAINS 'Vinyl'")).firstMatch if vinylButton.waitForExistence(timeout: 3) { vinylButton.tap() // Skin should now be selected (checkmark should appear) // We just verify no crash — theme switching is tested in unit tests } } func testSettingsShowsLibraryStats() { let settingsButton = app.buttons["settingsButton"] XCTAssertTrue(settingsButton.waitForExistence(timeout: 5)) settingsButton.tap() // Scroll down to Library section let librarySection = app.staticTexts["Library"] if librarySection.waitForExistence(timeout: 2) { XCTAssertTrue(app.staticTexts["Tracks"].exists || app.staticTexts.matching(NSPredicate(format: "label CONTAINS 'Tracks'")).count > 0) } } // MARK: - Mini Player func testMiniPlayerNotVisibleWithoutTrack() { // When no track is playing, mini player should not be visible let miniPlayer = app.otherElements["miniPlayer"] XCTAssertFalse(miniPlayer.exists, "Mini player should not be visible without a playing track") } // MARK: - Now Playing func testNowPlayingNotShownInitially() { // Now Playing should not be shown on launch let nowPlayingTitle = app.staticTexts["nowPlayingTitle"] XCTAssertFalse(nowPlayingTitle.exists, "Now Playing should not be shown initially") } // MARK: - Multiple Playlist Operations func testCreateMultiplePlaylists() { createPlaylistViaUI(name: "Playlist Alpha") createPlaylistViaUI(name: "Playlist Beta") createPlaylistViaUI(name: "Playlist Gamma") XCTAssertTrue(app.staticTexts["Playlist Alpha"].waitForExistence(timeout: 3)) XCTAssertTrue(app.staticTexts["Playlist Beta"].exists) XCTAssertTrue(app.staticTexts["Playlist Gamma"].exists) } // MARK: - Orientation (iPad) func testLandscapeDoesNotCrash() { XCUIDevice.shared.orientation = .landscapeLeft sleep(1) let title = app.navigationBars["MixBoard"] XCTAssertTrue(title.waitForExistence(timeout: 3)) // Rotate back XCUIDevice.shared.orientation = .portrait sleep(1) XCTAssertTrue(title.waitForExistence(timeout: 3)) } // MARK: - Helpers /// Create a playlist using the UI flow. private func createPlaylistViaUI(name: String) { let newPlaylistButton = app.buttons["newPlaylistButton"] guard newPlaylistButton.waitForExistence(timeout: 5) else { XCTFail("New playlist button not found") return } newPlaylistButton.tap() let alert = app.alerts["New Playlist"] guard alert.waitForExistence(timeout: 3) else { XCTFail("New Playlist alert not found") return } let textField = alert.textFields["Playlist name"] textField.tap() textField.typeText(name) alert.buttons["Create"].tap() // Wait for the playlist to appear _ = app.staticTexts[name].waitForExistence(timeout: 3) } } // MARK: - Launch Performance Test final class MixBoardLaunchPerformanceTests: XCTestCase { func testLaunchPerformance() throws { if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { measure(metrics: [XCTApplicationLaunchMetric()]) { XCUIApplication().launch() } } } }