From 00851b98e5f5a1199b328d87ca7088af14e5af40 Mon Sep 17 00:00:00 2001 From: Mathijs Bernson Date: Fri, 24 Jul 2026 16:08:35 +0200 Subject: [PATCH 1/3] Update test workflow to use a build matrix --- .github/workflows/test.yml | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5483a1..cdb3467 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,32 +11,39 @@ env: XCODE_PROJECT: "HackerTube.xcodeproj" SCHEME: "HackerTube" TEST_PLAN: UnitTests - TEST_RESULT_BUNDLE: "TestResults.xcresult" - - # iOS Simulator configuration - TEST_PLATFORM: 'iOS Simulator' - TEST_DEVICE_TYPE: 'iPhone 16' jobs: build: - name: Build and Test on iOS Simulator + name: Test on ${{ matrix.name }} runs-on: macos-latest + strategy: + fail-fast: false + matrix: + include: + - name: iOS Simulator + platform: 'iOS Simulator' + device_type: 'iPhone 17' + - name: tvOS Simulator + platform: 'tvOS Simulator' + device_type: 'Apple TV' + env: TEST_DEVICE_NAME: 'TestDevice' + TEST_RESULT_BUNDLE: "TestResults.xcresult" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: ${{ env.XCODE_VERSION }} - - name: Create iOS Simulator + - name: Create ${{ matrix.platform }} run: | - xcrun simctl create "${{ env.TEST_DEVICE_NAME }}" "${{ env.TEST_DEVICE_TYPE }}" + xcrun simctl create "${{ env.TEST_DEVICE_NAME }}" "${{ matrix.device_type }}" - name: Test run: | @@ -45,11 +52,11 @@ jobs: -scheme ${{ env.SCHEME }} \ -testPlan "${{ env.TEST_PLAN }}" \ -resultBundlePath "${{ env.TEST_RESULT_BUNDLE }}" \ - -destination "platform=${{ env.TEST_PLATFORM }},name=${{ env.TEST_DEVICE_NAME }}" + -destination "platform=${{ matrix.platform }},name=${{ env.TEST_DEVICE_NAME }}" - name: Upload test result bundle - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: ${{ failure() }} with: - name: ${{ env.SCHEME }}-${{ env.TEST_PLAN }}-${{ github.sha }}.xcresult + name: ${{ env.SCHEME }}-${{ env.TEST_PLAN }}-${{ matrix.platform }}-${{ github.sha }}.xcresult path: ${{ env.TEST_RESULT_BUNDLE }} From a8704560cf0ef1cddd443898eab5a52049d80055 Mon Sep 17 00:00:00 2001 From: Mathijs Bernson Date: Fri, 18 Sep 2026 13:49:29 +0200 Subject: [PATCH 2/3] Remove screenshot UI tests --- HackerTube.xcodeproj/project.pbxproj | 112 +----------------- HackerTube/Info.plist | 2 - .../HackerTubeUIScreenshotTests.swift | 63 ---------- Screenshots.xctestplan | 50 -------- screenshots.sh | 45 ------- 5 files changed, 2 insertions(+), 270 deletions(-) delete mode 100644 HackerTubeUITests/HackerTubeUIScreenshotTests.swift delete mode 100644 Screenshots.xctestplan delete mode 100755 screenshots.sh diff --git a/HackerTube.xcodeproj/project.pbxproj b/HackerTube.xcodeproj/project.pbxproj index c0e3312..d0bfe56 100644 --- a/HackerTube.xcodeproj/project.pbxproj +++ b/HackerTube.xcodeproj/project.pbxproj @@ -30,13 +30,6 @@ remoteGlobalIDString = D5A156D3289462B3006989FF; remoteInfo = HackerTube; }; - D5EFB1A22B407683008CFE82 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D5A156CC289462B3006989FF /* Project object */; - proxyType = 1; - remoteGlobalIDString = D5A156D3289462B3006989FF; - remoteInfo = HackerTube; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -59,9 +52,7 @@ D59139402895851300369402 /* TVServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TVServices.framework; path = System/Library/Frameworks/TVServices.framework; sourceTree = SDKROOT; }; D591395928958B2600369402 /* HackerTubeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HackerTubeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5A156D4289462B3006989FF /* HackerTube.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HackerTube.app; sourceTree = BUILT_PRODUCTS_DIR; }; - D5D2CB292D43EBB300E08E44 /* Screenshots.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Screenshots.xctestplan; sourceTree = ""; }; D5D2CB2A2D43EBB300E08E44 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; - D5EFB19C2B407683008CFE82 /* HackerTubeUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HackerTubeUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5F872242DFC3F35002D2F28 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; /* End PBXFileReference section */ @@ -104,11 +95,6 @@ path = HackerTubeTests; sourceTree = ""; }; - D5AFD7052C9BFD1100BF89F5 /* HackerTubeUITests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = HackerTubeUITests; - sourceTree = ""; - }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -131,11 +117,6 @@ D5FCC6782FFC42DA00569E27 /* NanoSVG in Frameworks */, ); }; - D5EFB1992B407683008CFE82 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - files = ( - ); - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -151,7 +132,6 @@ isa = PBXGroup; children = ( D557E8BA2896F70400342EE2 /* README.md */, - D5D2CB292D43EBB300E08E44 /* Screenshots.xctestplan */, D5F872242DFC3F35002D2F28 /* AppIcon.icon */, D5D2CB2A2D43EBB300E08E44 /* UnitTests.xctestplan */, D5AFD6D92C9BFCF700BF89F5 /* HackerTube */, @@ -159,7 +139,6 @@ D574ECF72FFC42FA0050AF63 /* NanoSVG */, D5AFD6FB2C9BFCFB00BF89F5 /* TopShelf */, D5AFD7022C9BFD0F00BF89F5 /* HackerTubeTests */, - D5AFD7052C9BFD1100BF89F5 /* HackerTubeUITests */, D5A156D5289462B3006989FF /* Products */, D50C6F7428956DFC0003D1D0 /* Frameworks */, ); @@ -173,7 +152,6 @@ D5A156D4289462B3006989FF /* HackerTube.app */, D591393F2895851300369402 /* TopShelf.appex */, D591395928958B2600369402 /* HackerTubeTests.xctest */, - D5EFB19C2B407683008CFE82 /* HackerTubeUITests.xctest */, ); name = Products; sourceTree = ""; @@ -249,27 +227,6 @@ productReference = D5A156D4289462B3006989FF /* HackerTube.app */; productType = "com.apple.product-type.application"; }; - D5EFB19B2B407683008CFE82 /* HackerTubeUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = D5EFB1A62B407683008CFE82 /* Build configuration list for PBXNativeTarget "HackerTubeUITests" */; - buildPhases = ( - D5EFB1982B407683008CFE82 /* Sources */, - D5EFB1992B407683008CFE82 /* Frameworks */, - D5EFB19A2B407683008CFE82 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - D5EFB1A32B407683008CFE82 /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - D5AFD7052C9BFD1100BF89F5 /* HackerTubeUITests */, - ); - name = HackerTubeUITests; - productName = HackerTubeUITests; - productReference = D5EFB19C2B407683008CFE82 /* HackerTubeUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -290,10 +247,6 @@ D5A156D3289462B3006989FF = { CreatedOnToolsVersion = 13.4.1; }; - D5EFB19B2B407683008CFE82 = { - CreatedOnToolsVersion = 15.1; - TestTargetID = D5A156D3289462B3006989FF; - }; }; }; buildConfigurationList = D5A156CF289462B3006989FF /* Build configuration list for PBXProject "HackerTube" */; @@ -317,7 +270,6 @@ D5A156D3289462B3006989FF /* HackerTube */, D591393E2895851300369402 /* TopShelf */, D591395828958B2600369402 /* HackerTubeTests */, - D5EFB19B2B407683008CFE82 /* HackerTubeUITests */, ); }; /* End PBXProject section */ @@ -339,11 +291,6 @@ D5F872252DFC3F3F002D2F28 /* AppIcon.icon in Resources */, ); }; - D5EFB19A2B407683008CFE82 /* Resources */ = { - isa = PBXResourcesBuildPhase; - files = ( - ); - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -362,11 +309,6 @@ files = ( ); }; - D5EFB1982B407683008CFE82 /* Sources */ = { - isa = PBXSourcesBuildPhase; - files = ( - ); - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -383,11 +325,6 @@ target = D5A156D3289462B3006989FF /* HackerTube */; targetProxy = D591395D28958B2600369402 /* PBXContainerItemProxy */; }; - D5EFB1A32B407683008CFE82 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D5A156D3289462B3006989FF /* HackerTube */; - targetProxy = D5EFB1A22B407683008CFE82 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -628,6 +565,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = HackerTube/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = HackerTube; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -669,6 +607,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = HackerTube/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = HackerTube; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -691,45 +630,6 @@ }; name = Release; }; - D5EFB1A42B407683008CFE82 /* Debug configuration for PBXNativeTarget "HackerTubeUITests" */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_STYLE = Automatic; - DEAD_CODE_STRIPPING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = nl.bernson.CCCTubeUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_EMIT_LOC_STRINGS = NO; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_TARGET_NAME = HackerTube; - }; - name = Debug; - }; - D5EFB1A52B407683008CFE82 /* Release configuration for PBXNativeTarget "HackerTubeUITests" */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_STYLE = Automatic; - DEAD_CODE_STRIPPING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = nl.bernson.CCCTubeUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SWIFT_EMIT_LOC_STRINGS = NO; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_TARGET_NAME = HackerTube; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -765,14 +665,6 @@ ); defaultConfigurationName = Release; }; - D5EFB1A62B407683008CFE82 /* Build configuration list for PBXNativeTarget "HackerTubeUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D5EFB1A42B407683008CFE82 /* Debug configuration for PBXNativeTarget "HackerTubeUITests" */, - D5EFB1A52B407683008CFE82 /* Release configuration for PBXNativeTarget "HackerTubeUITests" */, - ); - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ diff --git a/HackerTube/Info.plist b/HackerTube/Info.plist index 1fbb4d2..b3fe316 100644 --- a/HackerTube/Info.plist +++ b/HackerTube/Info.plist @@ -13,8 +13,6 @@ - ITSAppUsesNonExemptEncryption - UIBackgroundModes audio diff --git a/HackerTubeUITests/HackerTubeUIScreenshotTests.swift b/HackerTubeUITests/HackerTubeUIScreenshotTests.swift deleted file mode 100644 index 22688c7..0000000 --- a/HackerTubeUITests/HackerTubeUIScreenshotTests.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// HackerTubeUIScreenshotTests.swift -// HackerTubeUITests -// -// Created by Mathijs Bernson on 30/12/2023. -// - -import XCTest - -class HackerTubeUIScreenshotTests: XCTestCase { - var app: XCUIApplication! - - override func setUpWithError() throws { - continueAfterFailure = false - } - - func testRecentTalks() { - app = XCUIApplication() - app.launch() - XCTAssertTrue( - app.otherElements["TalksGrid"].buttons.firstMatch.waitForExistence(timeout: 3.0)) - wait(forTimeInterval: 2.0) // Give the app some time to load - takeScreenshot("RecentTalks") - } - - func testConferences() { - app = XCUIApplication() - app.launch() - app.buttons["Conferences"].firstMatch.tap() - XCTAssertTrue( - app.otherElements["ConferencesGrid"].buttons.firstMatch.waitForExistence(timeout: 3.0)) - wait(forTimeInterval: 2.0) // Give the app some time to load - takeScreenshot("Conferences") - } - - func testVideo() { - app = XCUIApplication() - app.launch() - let firstTalk = app.otherElements["TalksGrid"].buttons.firstMatch - XCTAssertTrue(firstTalk.waitForExistence(timeout: 5.0)) - firstTalk.tap() - XCTAssertTrue(app.otherElements["Video"].waitForExistence(timeout: 5)) - app.otherElements["Video"].firstMatch.tap() - app.buttons["Play/Pause"].firstMatch.tap() - wait(forTimeInterval: 2.0) - takeScreenshot("Video") - } - - private func takeScreenshot(_ name: String) { - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = name - attachment.lifetime = .keepAlways - add(attachment) - } - - private func wait(forTimeInterval timeout: TimeInterval) { - let exp = expectation(description: "Wait for \(timeout) seconds") - let result = XCTWaiter.wait(for: [exp], timeout: timeout) - if result != .timedOut { - XCTFail("Delay interrupted") - } - } -} diff --git a/Screenshots.xctestplan b/Screenshots.xctestplan deleted file mode 100644 index 46c92b9..0000000 --- a/Screenshots.xctestplan +++ /dev/null @@ -1,50 +0,0 @@ -{ - "configurations" : [ - { - "id" : "5F64D8EB-84F8-4F41-935B-C660E7907F5F", - "name" : "English", - "options" : { - "language" : "en", - "region" : "US" - } - }, - { - "enabled" : false, - "id" : "7A838377-F05E-4BDC-9257-D723B91FEE33", - "name" : "Dutch", - "options" : { - "language" : "nl", - "region" : "NL" - } - }, - { - "enabled" : false, - "id" : "F9F3D1F9-6C1B-480F-A7B9-9B77D342889F", - "name" : "German", - "options" : { - "language" : "de", - "region" : "DE" - } - } - ], - "defaultOptions" : { - "codeCoverage" : false, - "mainThreadCheckerEnabled" : false, - "maximumTestRepetitions" : 3, - "preferredScreenCaptureFormat" : "screenshot", - "testRepetitionMode" : "retryOnFailure", - "testTimeoutsEnabled" : true, - "uiTestingScreenshotsLifetime" : "keepNever", - "userAttachmentLifetime" : "keepAlways" - }, - "testTargets" : [ - { - "target" : { - "containerPath" : "container:HackerTube.xcodeproj", - "identifier" : "D5EFB19B2B407683008CFE82", - "name" : "HackerTubeUITests" - } - } - ], - "version" : 1 -} diff --git a/screenshots.sh b/screenshots.sh deleted file mode 100755 index f7f241a..0000000 --- a/screenshots.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env zsh - -# This script automates the process of taking screenshots for the App Store. -# It uses UI tests target, which are configured in the Screenshots test plan. -# The final screenshots are saved in a folder named 'screenshots'. - -set -e - -if ! command -v xcparse &> /dev/null -then - echo "xcparse could not be found. Please install it: brew install chargepoint/xcparse/xcparse" - exit 1 -fi - -if ! command -v convert &> /dev/null -then - echo "ImageMagick could not be found. Please install it: brew install imagemagick" - exit 1 -fi - -declare -a destinations=("platform=iOS Simulator,name=iPhone 16 Plus" "platform=iOS Simulator,name=iPad Pro 13-inch (M4)") - -rm -rf ./screenshots -mkdir ./screenshots - -for destination in "${destinations[@]}" -do - -rm -rf ./Screenshots.xcresult - -echo "Taking screenshots for $destination" -xcodebuild -quiet \ --project "HackerTube.xcodeproj" \ --scheme "HackerTube" \ --destination "$destination" \ --resultBundlePath "./Screenshots.xcresult" \ --testPlan Screenshots \ -clean test - -xcparse screenshots --os --model --test-plan-config "./Screenshots.xcresult" "./screenshots/" - -done - -# Remove alpha channel from the screenshots, otherwise the App Store will reject them -find ./screenshots -name "*.png" -exec convert "{}" -alpha off "{}" \; From f080fd6719feb790cb71567cf1667e90abc989f9 Mon Sep 17 00:00:00 2001 From: Mathijs Bernson Date: Fri, 18 Sep 2026 13:51:14 +0200 Subject: [PATCH 3/3] Update unit test to use Swift Testing --- HackerTubeTests/URLParserTests.swift | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/HackerTubeTests/URLParserTests.swift b/HackerTubeTests/URLParserTests.swift index 056bed3..1d516da 100644 --- a/HackerTubeTests/URLParserTests.swift +++ b/HackerTubeTests/URLParserTests.swift @@ -5,27 +5,32 @@ // Created by Mathijs Bernson on 30/07/2022. // -import XCTest +import Foundation +import Testing @testable import HackerTube -class URLParserTests: XCTestCase { +struct URLParserTests { let parser = URLParser() - func testParseOpenURL() { + @Test func `Parse open URL`() { let url = URL(string: "ccctube://talk/44ab627f-ed5d-522b-b84b-15a3ed761895")! - XCTAssertEqual(parser.parseURL(url), .openTalk(id: "44ab627f-ed5d-522b-b84b-15a3ed761895")) + #expect(parser.parseURL(url) == .openTalk(id: "44ab627f-ed5d-522b-b84b-15a3ed761895")) } - func testParsePlayURL() { + @Test func `Parse play URL`() { let url = URL(string: "ccctube://talk/44ab627f-ed5d-522b-b84b-15a3ed761895/play")! - XCTAssertEqual(parser.parseURL(url), .playTalk(id: "44ab627f-ed5d-522b-b84b-15a3ed761895")) + #expect(parser.parseURL(url) == .playTalk(id: "44ab627f-ed5d-522b-b84b-15a3ed761895")) } - func testInvalidURLs() { - XCTAssertNil(parser.parseURL(URL(string: "ccctube://")!)) - XCTAssertNil(parser.parseURL(URL(string: "https://google.com/")!)) - XCTAssertNil(parser.parseURL(URL(string: "ccctube://foo")!)) - XCTAssertNil(parser.parseURL(URL(string: "mailto:info@example.com")!)) + @Test(arguments: [ + "ccctube://", + "https://google.com/", + "ccctube://foo", + "mailto:info@example.com", + ]) + func `Invalid URLs`(urlString: String) throws { + let url = try #require(URL(string: urlString)) + #expect(parser.parseURL(url) == nil) } }