diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 598749dac..de2e16011 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -204,6 +204,30 @@ jobs: with: name: UTM-${{ matrix.configuration.scheme }}-${{ matrix.configuration.platform }}-${{ matrix.configuration.arch }} path: UTM.xcarchive.tgz + test-utm: + name: Test UTM (macOS) + runs-on: ${{ fromJSON(needs.configuration.outputs.runner) }} + needs: [configuration, build-sysroot] + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Cache Sysroot + uses: actions/cache/restore@v4 + with: + path: ./sysroot-macos-arm64 + key: macos-arm64-${{ hashFiles('scripts/build_dependencies.sh') }}-${{ hashFiles('patches/**') }} + fail-on-cache-miss: true + - name: Setup Xcode + shell: bash + run: | + [[ "$(xcode-select -p)" == "${{ env.BUILD_XCODE_PATH }}"* ]] || sudo xcode-select -s "${{ env.BUILD_XCODE_PATH }}" + - name: Run Tests + run: | + xcodebuild test \ + -scheme macOS \ + -destination 'platform=macOS' build-universal: name: Build UTM (Universal Mac) runs-on: ${{ fromJSON(needs.configuration.outputs.runner) }} diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index 1a37efbb0..09033df8e 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 4B224B9E279D4D8100B63CFF /* InListButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B224B9C279D4D8100B63CFF /* InListButtonStyle.swift */; }; 4B224B9F279D4D8100B63CFF /* InListButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B224B9C279D4D8100B63CFF /* InListButtonStyle.swift */; }; 53A0BDD726D79FE40010EDC5 /* SavePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A0BDD426D79FE40010EDC5 /* SavePanel.swift */; }; + 5FAD12C52F4F7BCA00733342 /* VMWizardViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FAD12C12F4F7BCA00733342 /* VMWizardViewTest.swift */; }; 83034C0726AB630F006B4BAF /* UTMPendingVMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83034C0626AB630F006B4BAF /* UTMPendingVMView.swift */; }; 83034C0826AB630F006B4BAF /* UTMPendingVMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83034C0626AB630F006B4BAF /* UTMPendingVMView.swift */; }; 83034C0926AB630F006B4BAF /* UTMPendingVMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83034C0626AB630F006B4BAF /* UTMPendingVMView.swift */; }; @@ -1320,6 +1321,13 @@ remoteGlobalIDString = CEBDA1D924D8BDDA0010B5EC; remoteInfo = QEMUHelper; }; + 5FAD12B72F4F7B0E00733342 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CE550BC1225947990063E575 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CE2D951B24AD48BE0059923A; + remoteInfo = macOS; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -1687,6 +1695,8 @@ 5A1746A228BA9C4300278241 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; 5A1746A528BA9C4300278241 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; 5A1746A628BA9C4300278241 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = ""; }; + 5FAD12B12F4F7B0E00733342 /* UTMUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UTMUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5FAD12C12F4F7BCA00733342 /* VMWizardViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMWizardViewTest.swift; sourceTree = ""; }; 61EBDE9F2AACA83100B959A2 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/VMDisplayWindow.strings; sourceTree = ""; }; 61EBDEA02AACA83100B959A2 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 61EBDEA12AACA83100B959A2 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -2206,6 +2216,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 5FAD12AE2F4F7B0E00733342 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8401FD5F269BE9C500265F0D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2555,6 +2572,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5FAD12C22F4F7BCA00733342 /* UTMUITests */ = { + isa = PBXGroup; + children = ( + 5FAD12C12F4F7BCA00733342 /* VMWizardViewTest.swift */, + ); + path = UTMUITests; + sourceTree = ""; + }; 841619A42843150E000034B2 /* Legacy */ = { isa = PBXGroup; children = ( @@ -2900,6 +2925,7 @@ CEFE98DD2948518D007CB7A8 /* Scripting */, 84E3A8F1293DB37E0024A740 /* utmctl */, CE88A0A12E23201100EAA28E /* Intents */, + 5FAD12C22F4F7BCA00733342 /* UTMUITests */, CE550BCA225947990063E575 /* Products */, CE2D63D622653C7300FC7E63 /* Frameworks */, ); @@ -2917,6 +2943,7 @@ 8401FD62269BE9C500265F0D /* QEMULauncher.app */, 84E3A8F0293DB37E0024A740 /* utmctl */, CEF7F6D32AEEDCC400E34952 /* UTM Remote.app */, + 5FAD12B12F4F7B0E00733342 /* UTMUITests.xctest */, ); name = Products; sourceTree = ""; @@ -3376,6 +3403,26 @@ productReference = CEF7F6D32AEEDCC400E34952 /* UTM Remote.app */; productType = "com.apple.product-type.application"; }; + 5FAD12B02F4F7B0E00733342 /* UTMUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5FAD12B92F4F7B0E00733342 /* Build configuration list for PBXNativeTarget "UTMUITests" */; + buildPhases = ( + 5FAD12AD2F4F7B0E00733342 /* Sources */, + 5FAD12AE2F4F7B0E00733342 /* Frameworks */, + 5FAD12AF2F4F7B0E00733342 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5FAD12B82F4F7B0E00733342 /* PBXTargetDependency */, + ); + name = UTMUITests; + packageProductDependencies = ( + ); + productName = UTMUITests; + productReference = 5FAD12B12F4F7B0E00733342 /* UTMUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -3387,6 +3434,10 @@ LastUpgradeCheck = 1500; ORGANIZATIONNAME = "Turing Software, LLC"; TargetAttributes = { + 5FAD12B02F4F7B0E00733342 = { + CreatedOnToolsVersion = 26.2; + TestTargetID = CE2D951B24AD48BE0059923A; + }; 8401FD61269BE9C500265F0D = { CreatedOnToolsVersion = 12.4; }; @@ -3459,6 +3510,7 @@ 8401FD61269BE9C500265F0D /* QEMULauncher */, CE9A352C26533A51005077CF /* JailbreakInterposer */, 84E3A8EF293DB37E0024A740 /* utmctl */, + 5FAD12B02F4F7B0E00733342 /* UTMUITests */, ); }; /* End PBXProject section */ @@ -3559,6 +3611,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5FAD12AF2F4F7B0E00733342 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -4399,6 +4458,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5FAD12AD2F4F7B0E00733342 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5FAD12C52F4F7BCA00733342 /* VMWizardViewTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -4431,6 +4498,11 @@ target = CEBDA1D924D8BDDA0010B5EC /* QEMUHelper */; targetProxy = CEBDA1E324D8BDDB0010B5EC /* PBXContainerItemProxy */; }; + 5FAD12B82F4F7B0E00733342 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = CE2D951B24AD48BE0059923A /* macOS */; + targetProxy = 5FAD12B72F4F7B0E00733342 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -5270,6 +5342,38 @@ }; name = Release; }; + 5FAD12BA2F4F7B0E00733342 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.3; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SUPPORTED_PLATFORMS = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = macOS; + }; + name = Debug; + }; + 5FAD12BB2F4F7B0E00733342 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.3; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SUPPORTED_PLATFORMS = macosx; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = macOS; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -5354,6 +5458,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 5FAD12B92F4F7B0E00733342 /* Build configuration list for PBXNativeTarget "UTMUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5FAD12BA2F4F7B0E00733342 /* Debug */, + 5FAD12BB2F4F7B0E00733342 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/UTM.xcodeproj/xcshareddata/xcschemes/macOS.xcscheme b/UTM.xcodeproj/xcshareddata/xcschemes/macOS.xcscheme index ebc2e60a7..d1318da45 100644 --- a/UTM.xcodeproj/xcshareddata/xcschemes/macOS.xcscheme +++ b/UTM.xcodeproj/xcshareddata/xcschemes/macOS.xcscheme @@ -24,10 +24,20 @@ + + + + ` +- Example: `testClickExitButton`, `testNavigateToHardwarePage` + + +## Additional Resources + +- [Apple's XCTest Documentation](https://developer.apple.com/documentation/xctest) +- [UI Testing in Xcode](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/) +- [XCUI Element Query Reference](https://developer.apple.com/documentation/xctest/xcuielementquery) + +## Contributing + +When adding new tests: + +1. Follow the existing code style and patterns +2. Add appropriate documentation comments +3. Ensure tests pass consistently +4. Update this README if adding new testing patterns or utilities +5. Consider adding helper methods for commonly repeated operations + +--- + +**Last Updated**: February 2026 + diff --git a/UTMUITests/VMWizardViewTest.swift b/UTMUITests/VMWizardViewTest.swift new file mode 100644 index 000000000..39db328ba --- /dev/null +++ b/UTMUITests/VMWizardViewTest.swift @@ -0,0 +1,38 @@ +import XCTest + +final class VMWizardViewTest: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + @MainActor + func testClickExitButton() throws { + // Launch the application + let app = XCUIApplication() + app.launch() + + // Wait for the window to appear + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 5), "Window should appear") + + // Find and click the close button (red button in top-left corner) + let closeButton = window.buttons[XCUIIdentifierCloseWindow] + XCTAssertTrue(closeButton.exists, "Close button should exist") + + // Click the close button to exit + closeButton.click() + + // Verify the app or window is closed + XCTAssertFalse(window.exists, "Window should be closed after clicking close button") + } +}