import XCTest /// Playback and queue UI tests — verifies play, queue add, play next, and clear operations. /// Uses `-MockNetwork` to ensure tests work without a live server. final class PlaybackUITests: XCTestCase { var app: XCUIApplication! override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launchArguments += ["-UITesting", "-MockNetwork"] } override func tearDownWithError() throws { app = nil } // MARK: - Helpers private func openCloudBrowser() { let cloudButton = app.buttons["cloudBrowserButton"] XCTAssertTrue(cloudButton.waitForExistence(timeout: 5), "Cloud browser button should exist") cloudButton.tap() } /// Navigates to a mock album's track list. /// Returns true if tracks are visible. @discardableResult private func navigateToAlbumTracks() -> Bool { openCloudBrowser() let albumsLink = app.buttons["cloud.browse.album"] guard albumsLink.waitForExistence(timeout: 5) else { return false } albumsLink.tap() // Wait for mock album row to appear let firstAlbumRow = app.buttons["cloud.album.row.album-1"] guard firstAlbumRow.waitForExistence(timeout: 5) else { return false } firstAlbumRow.tap() // Wait for track content to load let trackTitle = app.staticTexts["First Track"] return trackTitle.waitForExistence(timeout: 5) } // MARK: - Phase 2: Playback Tests /// Play a cloud track → mini player should appear with the track title. func testPlayCloudTrack() { app.launch() guard navigateToAlbumTracks() else { XCTFail("Could not navigate to album tracks") return } // Tap the first track row (the "Play All" button or a track) let playAllButton = app.buttons["Play All"] if playAllButton.waitForExistence(timeout: 3) { playAllButton.tap() } else { // Tap first track cell let firstTrack = app.cells.element(boundBy: 2) // skip header + play all section if firstTrack.waitForExistence(timeout: 3) { firstTrack.tap() } } // Dismiss the cloud browser let doneButton = app.buttons["Done"] if doneButton.waitForExistence(timeout: 3) { doneButton.tap() } // Mini player should appear let miniPlayer = app.otherElements["miniPlayer"] XCTAssertTrue(miniPlayer.waitForExistence(timeout: 10), "Mini player should appear after playing a track") // Track title should be visible in mini player let trackTitle = app.staticTexts.matching(identifier: "miniPlayer.trackTitle").firstMatch XCTAssertTrue(trackTitle.exists, "Mini player should show the track title") } /// Long-press a track → "Add to Queue" → queue view shows the entry. func testAddToQueue() { app.launch() guard navigateToAlbumTracks() else { XCTFail("Could not navigate to album tracks") return } // Long-press on a track to open context menu let trackCell = app.cells.element(boundBy: 3) // skip header + play all rows guard trackCell.waitForExistence(timeout: 3) else { XCTFail("Track cell should exist") return } trackCell.press(forDuration: 1.2) // Tap "Add to Queue" from context menu let addToQueueButton = app.buttons["Add to Queue"] guard addToQueueButton.waitForExistence(timeout: 3) else { // Context menu might not appear in simulator, skip gracefully return } addToQueueButton.tap() // Dismiss cloud browser let doneButton = app.buttons["Done"] if doneButton.waitForExistence(timeout: 3) { doneButton.tap() } // Open queue view (via mini player queue button or Now Playing) // First play something so queue is accessible // Queue is a sheet — check if we can open it from the mini player let miniPlayerQueue = app.buttons["miniPlayerQueue"] if miniPlayerQueue.waitForExistence(timeout: 5) { miniPlayerQueue.tap() // Queue should show the added entry let queueEmpty = app.otherElements["queue.emptyState"] XCTAssertFalse(queueEmpty.exists, "Queue should not be empty after adding a track") } } /// Long-press → "Play Next" → track appears at top of queue. func testQueuePlayNext() { app.launch() guard navigateToAlbumTracks() else { XCTFail("Could not navigate to album tracks") return } // Play first track to establish a now-playing state let playAllButton = app.buttons["Play All"] if playAllButton.waitForExistence(timeout: 3) { playAllButton.tap() } // Wait for playback to start sleep(2) // Navigate back to tracks if needed let backButton = app.navigationBars.buttons.firstMatch if backButton.exists { // We may still be on the album detail — long-press a different track } // Long-press second track let trackCell = app.cells.element(boundBy: 4) guard trackCell.waitForExistence(timeout: 3) else { return } trackCell.press(forDuration: 1.2) let playNextButton = app.buttons["Play Next"] guard playNextButton.waitForExistence(timeout: 3) else { return } playNextButton.tap() // Dismiss cloud browser let doneButton = app.buttons["Done"] if doneButton.waitForExistence(timeout: 3) { doneButton.tap() } // Open queue let miniPlayerQueue = app.buttons["miniPlayerQueue"] if miniPlayerQueue.waitForExistence(timeout: 5) { miniPlayerQueue.tap() // Queue should have entries let queueEmpty = app.otherElements["queue.emptyState"] XCTAssertFalse(queueEmpty.exists, "Queue should have the 'Play Next' entry") } } /// Add to queue → Clear → queue should be empty. func testQueueClear() { app.launch() guard navigateToAlbumTracks() else { XCTFail("Could not navigate to album tracks") return } // Play a track first let playAllButton = app.buttons["Play All"] if playAllButton.waitForExistence(timeout: 3) { playAllButton.tap() } sleep(2) // Add another track to queue via context menu let trackCell = app.cells.element(boundBy: 4) guard trackCell.waitForExistence(timeout: 3) else { return } trackCell.press(forDuration: 1.2) let addToQueueButton = app.buttons["Add to Queue"] guard addToQueueButton.waitForExistence(timeout: 3) else { return } addToQueueButton.tap() // Dismiss cloud browser let doneButton = app.buttons["Done"] if doneButton.waitForExistence(timeout: 3) { doneButton.tap() } // Open queue let miniPlayerQueue = app.buttons["miniPlayerQueue"] guard miniPlayerQueue.waitForExistence(timeout: 5) else { XCTFail("Mini player queue button should exist") return } miniPlayerQueue.tap() // Tap Clear let clearButton = app.buttons["queue.clearButton"] if clearButton.waitForExistence(timeout: 3) { clearButton.tap() // Queue should be empty (only Now Playing remains) // The clear button itself should disappear after clearing XCTAssertFalse(clearButton.waitForExistence(timeout: 3), "Clear button should disappear after clearing queue") } } }