| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- 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()
- }
- }
- }
- }
|