diff --git a/.github/workflows/build_appstore.yml b/.github/workflows/build_appstore.yml index af342181f9..a1ea7a269c 100644 --- a/.github/workflows/build_appstore.yml +++ b/.github/workflows/build_appstore.yml @@ -111,9 +111,17 @@ jobs: APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} run: | + if [ "${{ env.destination }}" == "testflight_review" ]; then + app_bundle_name="DuckDuckGo App Store Review" + app_dsym_name="DuckDuckGo-AppStore-Review" + else + app_bundle_name="DuckDuckGo App Store" + app_dsym_name="DuckDuckGo-AppStore" + fi + bundle exec fastlane release_${{ env.destination }} - dsym_path="${{ github.workspace }}/DuckDuckGo-AppStore.app.dSYM.zip" - mv -f "${{ github.workspace }}/DuckDuckGo App Store.app.dSYM.zip" "${dsym_path}" + dsym_path="${{ github.workspace }}/${app_dsym_name}.app.dSYM.zip" + mv -f "${{ github.workspace }}/${app_bundle_name}.app.dSYM.zip" "${dsym_path}" version="$(cut -d ' ' -f 3 < Configuration/Version.xcconfig)" build_number="$(cut -d ' ' -f 3 < Configuration/BuildNumber.xcconfig)" echo "dsym-path=${dsym_path}" >> $GITHUB_ENV diff --git a/.github/workflows/build_notarized.yml b/.github/workflows/build_notarized.yml index 76d8ff71f4..12163beedc 100644 --- a/.github/workflows/build_notarized.yml +++ b/.github/workflows/build_notarized.yml @@ -76,7 +76,7 @@ jobs: name: Export Notarized App - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge outputs: app-version: ${{ steps.set-outputs.outputs.app-version }} @@ -235,7 +235,7 @@ jobs: needs: export-notarized-app if: ${{ github.event.inputs.create-dmg == true || inputs.create-dmg == true }} - runs-on: macos-14 + runs-on: macos-15 env: app-version: ${{ needs.export-notarized-app.outputs.app-version }} diff --git a/.github/workflows/bump_internal_release.yml b/.github/workflows/bump_internal_release.yml index 670cb3323b..e95e6f556c 100644 --- a/.github/workflows/bump_internal_release.yml +++ b/.github/workflows/bump_internal_release.yml @@ -26,7 +26,7 @@ jobs: # This doesn't need Xcode, so could technically run on Ubuntu, but find_asana_release_task.sh # uses BSD-specific `date` syntax, that doesn't work with GNU `date` (available on Linux). - runs-on: macos-14 + runs-on: macos-15 timeout-minutes: 10 outputs: @@ -151,7 +151,7 @@ jobs: name: Increment Build Number needs: [ validate_input_conditions, run_tests ] - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge timeout-minutes: 10 steps: diff --git a/.github/workflows/code_freeze.yml b/.github/workflows/code_freeze.yml index 451b3daa11..9cdf49502f 100644 --- a/.github/workflows/code_freeze.yml +++ b/.github/workflows/code_freeze.yml @@ -9,7 +9,7 @@ jobs: name: Create Release Branch - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge timeout-minutes: 10 outputs: @@ -111,7 +111,7 @@ jobs: name: Increment Build Number needs: [ create_release_branch, run_tests ] - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge timeout-minutes: 10 steps: diff --git a/.github/workflows/create_variant.yml b/.github/workflows/create_variant.yml index 24def9102a..671d7e57de 100644 --- a/.github/workflows/create_variant.yml +++ b/.github/workflows/create_variant.yml @@ -45,7 +45,7 @@ jobs: ATB_VARIANT_NAME: ${{ inputs.atb-variant || github.event.inputs.atb-variant }} ORIGIN_VARIANT_NAME: ${{ inputs.origin-variant || github.event.inputs.origin-variant }} - runs-on: macos-14 + runs-on: macos-15 timeout-minutes: 15 steps: diff --git a/.github/workflows/hotfix.yml b/.github/workflows/hotfix.yml index b17d183a68..0ccac03008 100644 --- a/.github/workflows/hotfix.yml +++ b/.github/workflows/hotfix.yml @@ -9,7 +9,7 @@ jobs: name: Create Release Branch - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge timeout-minutes: 10 outputs: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e7f7b35a89..a7892b608b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -68,7 +68,7 @@ jobs: name: Test Shell Scripts - runs-on: macos-14 + runs-on: macos-15 steps: - name: Check out the code @@ -119,7 +119,7 @@ jobs: - cache-key: sandbox- flavor: Sandbox - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge timeout-minutes: 30 outputs: @@ -317,7 +317,7 @@ jobs: # workflow_call is used by bump_internal_release and is followed by a proper release job if: github.actor != 'dependabot[bot]' && (github.event_name == 'push' || github.event_name == 'pull_request') - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge timeout-minutes: 30 steps: diff --git a/.github/workflows/publish_dmg_release.yml b/.github/workflows/publish_dmg_release.yml index 8c003b27f1..988a717b27 100644 --- a/.github/workflows/publish_dmg_release.yml +++ b/.github/workflows/publish_dmg_release.yml @@ -76,7 +76,7 @@ jobs: # or failed (for public releases or hotfixes), because tagging doesn't block publishing the release if: always() - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge timeout-minutes: 10 steps: diff --git a/.github/workflows/sync_end_to_end.yml b/.github/workflows/sync_end_to_end.yml index 2f98c28507..ac4887c423 100644 --- a/.github/workflows/sync_end_to_end.yml +++ b/.github/workflows/sync_end_to_end.yml @@ -35,16 +35,26 @@ jobs: strategy: fail-fast: false matrix: - runner: [macos-13-xlarge, macos-14-xlarge] + runner: [macos-13-xlarge, macos-14-xlarge, macos-15-xlarge] include: - xcode-version: "15.2" runner: macos-13-xlarge - xcode-version: "15.4" runner: macos-14-xlarge + - xcode-version: "16.1" + runner: macos-15-xlarge timeout-minutes: 60 steps: + - name: Install screenresolution + if: matrix.runner == 'macos-15-xlarge' + run: brew install screenresolution + + - name: Change screen resolution + if: matrix.runner == 'macos-15-xlarge' + run: screenresolution set 1920x1080x32@60 + - name: Register SSH key for certificates repository access uses: webfactory/ssh-agent@v0.7.0 with: diff --git a/.github/workflows/tag_release.yml b/.github/workflows/tag_release.yml index dea7514c90..d526a30f73 100644 --- a/.github/workflows/tag_release.yml +++ b/.github/workflows/tag_release.yml @@ -55,7 +55,7 @@ jobs: # This doesn't need Xcode, so could technically run on Ubuntu, but actions that add comments and tasks # in Asana use BSD-specific sed syntax, that doesn't work with GNU sed (available on Linux). - runs-on: macos-14 + runs-on: macos-15 env: asana-task-url: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} diff --git a/.github/workflows/ui_tests.yml b/.github/workflows/ui_tests.yml index 4b57035826..81763a434f 100644 --- a/.github/workflows/ui_tests.yml +++ b/.github/workflows/ui_tests.yml @@ -39,12 +39,14 @@ jobs: strategy: fail-fast: false matrix: - runner: [macos-13-xlarge, macos-14-xlarge] + runner: [macos-13-xlarge, macos-14-xlarge, macos-15-xlarge] include: - xcode-version: "15.2" runner: macos-13-xlarge - xcode-version: "15.4" runner: macos-14-xlarge + - xcode-version: "16.1" + runner: macos-15-xlarge concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.runner }} @@ -53,6 +55,14 @@ jobs: timeout-minutes: 120 steps: + - name: Install screenresolution + if: matrix.runner == 'macos-15-xlarge' + run: brew install screenresolution + + - name: Change screen resolution + if: matrix.runner == 'macos-15-xlarge' + run: screenresolution set 1920x1080x32@60 + - name: Register SSH key for certificates repository access uses: webfactory/ssh-agent@v0.7.0 with: @@ -172,4 +182,4 @@ jobs: asana-pat: ${{ secrets.ASANA_ACCESS_TOKEN }} asana-project: ${{ vars.MACOS_APP_DEVELOPMENT_ASANA_PROJECT_ID }} asana-task-name: GH Workflow Failure - UI Tests - asana-task-description: The UI Tests workflow has failed. See https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + asana-task-description: The UI Tests workflow has failed. See https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} \ No newline at end of file diff --git a/.xcode-version b/.xcode-version index 232a7fc1a6..c32b0ec5ab 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -15.4 +16.1 diff --git a/Configuration/App/DuckDuckGoAppStore.xcconfig b/Configuration/App/DuckDuckGoAppStore.xcconfig index 6845ef0afe..2f8b968f22 100644 --- a/Configuration/App/DuckDuckGoAppStore.xcconfig +++ b/Configuration/App/DuckDuckGoAppStore.xcconfig @@ -22,6 +22,7 @@ PRODUCT_BUNDLE_IDENTIFIER = $(MAIN_BUNDLE_IDENTIFIER) CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAppStore.entitlements CODE_SIGN_ENTITLEMENTS[config=Debug][arch=*][sdk=*] = DuckDuckGo/DuckDuckGoAppStoreDebug.entitlements CODE_SIGN_ENTITLEMENTS[config=CI][arch=*][sdk=*] = DuckDuckGo/DuckDuckGoAppStoreCI.entitlements +CODE_SIGN_ENTITLEMENTS[config=Review][arch=*][sdk=*] = DuckDuckGo/DuckDuckGoAppStoreReview.entitlements CODE_SIGN_IDENTITY[sdk=macosx*] = 3rd Party Mac Developer Application CODE_SIGN_IDENTITY[config=Debug][sdk=macosx*] = Apple Development diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 37c92cdb75..743c7d2377 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3013,7 +3013,6 @@ EE02D41A2BB4609900DBE6B3 /* UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE02D4192BB4609900DBE6B3 /* UITests.swift */; }; EE02D41C2BB460A600DBE6B3 /* BrowsingHistoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE02D41B2BB460A600DBE6B3 /* BrowsingHistoryTests.swift */; }; EE02D4202BB460C000DBE6B3 /* BrowserServicesKit in Frameworks */ = {isa = PBXBuildFile; productRef = EE02D41F2BB460C000DBE6B3 /* BrowserServicesKit */; }; - EE02D4212BB460FE00DBE6B3 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8EDF2624923EC70071C2E8 /* StringExtension.swift */; }; EE02D4222BB4611A00DBE6B3 /* TestsURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */; }; EE0429E02BA31D2F009EB20F /* FindInPageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0429DF2BA31D2F009EB20F /* FindInPageTests.swift */; }; EE098E772C8EDE2C009EBA7F /* AutofillCredentialsImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE098E762C8EDE2C009EBA7F /* AutofillCredentialsImportManager.swift */; }; @@ -3903,6 +3902,7 @@ 4BA1A6F5258C4F9600F6F690 /* EncryptionMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionMocks.swift; sourceTree = ""; }; 4BA1A6FD258C5C1300F6F690 /* EncryptedValueTransformerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedValueTransformerTests.swift; sourceTree = ""; }; 4BA71ED92B4B81E80002EBCE /* AppVersionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionExtension.swift; sourceTree = ""; }; + 4BA808FF2CDE78F600338EE4 /* DuckDuckGoAppStoreReview.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoAppStoreReview.entitlements; sourceTree = ""; }; 4BB6CE5E26B77ED000EC5860 /* Cryptography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cryptography.swift; sourceTree = ""; }; 4BB88B4425B7B55C006F6B06 /* DebugUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugUserScript.swift; sourceTree = ""; }; 4BB88B4925B7B690006F6B06 /* SequenceExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceExtensions.swift; sourceTree = ""; }; @@ -7798,6 +7798,7 @@ 37D9BBA329376EE8000B99F9 /* DuckDuckGoAppStore.entitlements */, 377E54382937B7C400780A0A /* DuckDuckGoAppStoreCI.entitlements */, 37E1116C2C578F1B00583C19 /* DuckDuckGoAppStoreDebug.entitlements */, + 4BA808FF2CDE78F600338EE4 /* DuckDuckGoAppStoreReview.entitlements */, 4B5F15032A1570F10060320F /* DuckDuckGoDebug.entitlements */, 4B65143C26392483005B46EB /* Email */, B68412192B6A16030092F66A /* ErrorPage */, @@ -12639,7 +12640,6 @@ EE02D41A2BB4609900DBE6B3 /* UITests.swift in Sources */, EE0429E02BA31D2F009EB20F /* FindInPageTests.swift in Sources */, BBCD467A2C8643EC004DB483 /* XCUIApplicationExtension.swift in Sources */, - EE02D4212BB460FE00DBE6B3 /* StringExtension.swift in Sources */, BBBB65402C77BB9400E69AC6 /* BookmarkSearchTests.swift in Sources */, 56A054532C2592CE007D8FAB /* OnboardingUITests.swift in Sources */, EE9D81C32BC57A3700338BE3 /* StateRestorationTests.swift in Sources */, @@ -15072,7 +15072,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 210.0.1; + version = 210.0.2; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 06a74c8d0d..f5feb11db7 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "183b9c111176fd7821cd17d01c01ddb38486c9ac", - "version" : "210.0.1" + "revision" : "07af4d672e003cc247e28a01b9fe3e77abaf49f9", + "version" : "210.0.2" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/GRDB.swift.git", "state" : { - "revision" : "4225b85c9a0c50544e413a1ea1e502c802b44b35", - "version" : "2.4.0" + "revision" : "5b2f6a81099d26ae0f9e38788f51490cd6a4b202", + "version" : "2.4.2" } }, { diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/Sync End-to-End UI Tests CI.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/Sync End-to-End UI Tests CI.xcscheme index 68dbe3d4f5..cb1ad3c06b 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/Sync End-to-End UI Tests CI.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/Sync End-to-End UI Tests CI.xcscheme @@ -4,7 +4,7 @@ version = "1.7"> + buildImplicitDependencies = "NO"> + buildImplicitDependencies = "NO"> + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + app-proxy-provider + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + $(IPC_APP_GROUP) + $(DBP_APP_GROUP) + $(NETP_APP_GROUP) + $(SUBSCRIPTION_APP_GROUP) + $(APP_CONFIGURATION_APP_GROUP) + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.files.downloads.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.personal-information.location + + com.apple.security.print + + com.apple.security.temporary-exception.files.home-relative-path.read-only + + /Library/Application Support/Google/Chrome/ + /Library/Application Support/BraveSoftware/Brave-Browser/ + /Library/Application Support/Firefox/ + /Library/Application Support/Microsoft Edge/ + /Library/Application Support/Vivaldi/ + /Library/Application Support/com.operasoftware.Opera/ + /Library/Application Support/com.operasoftware.OperaGX/ + /Library/Application Support/Yandex/YandexBrowser/ + /Library/Application Support/Chromium/ + /Library/Application Support/Coccoc/ + /Library/Application Support/TorBrowser-Data/Browser/ + /Library/Application Support/Arc/User Data/ + + com.apple.security.temporary-exception.mach-lookup.global-name + + $(DBP_BACKGROUND_AGENT_BUNDLE_ID) + $(AGENT_BUNDLE_ID) + + keychain-access-groups + + $(DBP_APP_GROUP) + $(AppIdentifierPrefix)com.duckduckgo.mobile.ios + $(NETP_APP_GROUP) + $(SUBSCRIPTION_APP_GROUP) + + + diff --git a/DuckDuckGo/Windows/View/WindowsManager.swift b/DuckDuckGo/Windows/View/WindowsManager.swift index 6dcc6ead90..f24e936c17 100644 --- a/DuckDuckGo/Windows/View/WindowsManager.swift +++ b/DuckDuckGo/Windows/View/WindowsManager.swift @@ -76,7 +76,6 @@ final class WindowsManager { if let droppingPoint { mainWindowController.window?.setFrameOrigin(droppingPoint: droppingPoint) - } else if let sourceWindow = self.findPositioningSourceWindow(for: tabCollectionViewModel?.tabs.first) { mainWindowController.window?.setFrameOrigin(cascadedFrom: sourceWindow) } diff --git a/IntegrationTests/Common/TestsURLExtension.swift b/IntegrationTests/Common/TestsURLExtension.swift index b1bd1f7a8f..14976e7112 100644 --- a/IntegrationTests/Common/TestsURLExtension.swift +++ b/IntegrationTests/Common/TestsURLExtension.swift @@ -17,7 +17,6 @@ // import Foundation -import Common // Integration Tests helpers extension URL { @@ -62,4 +61,84 @@ extension URL { return url } + private func appendingParameters(_ parameters: QueryParams, allowedReservedCharacters: CharacterSet? = nil) -> URL + where QueryParams.Element == (key: String, value: String) { + + return parameters.reduce(self) { partialResult, parameter in + partialResult.appendingParameter( + name: parameter.key, + value: parameter.value, + allowedReservedCharacters: allowedReservedCharacters + ) + } + } + + private func appendingParameter(name: String, value: String, allowedReservedCharacters: CharacterSet? = nil) -> URL { + let queryItem = URLQueryItem(percentEncodingName: name, + value: value, + withAllowedCharacters: allowedReservedCharacters) + return self.appending(percentEncodedQueryItem: queryItem) + } + + private func appending(percentEncodedQueryItem: URLQueryItem) -> URL { + appending(percentEncodedQueryItems: [percentEncodedQueryItem]) + } + + private func appending(percentEncodedQueryItems: [URLQueryItem]) -> URL { + guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { return self } + + var existingPercentEncodedQueryItems = components.percentEncodedQueryItems ?? [URLQueryItem]() + existingPercentEncodedQueryItems.append(contentsOf: percentEncodedQueryItems) + components.percentEncodedQueryItems = existingPercentEncodedQueryItems + + return components.url ?? self + } +} + +fileprivate extension URLQueryItem { + + init(percentEncodingName name: String, value: String, withAllowedCharacters allowedReservedCharacters: CharacterSet? = nil) { + let allowedCharacters: CharacterSet = { + if let allowedReservedCharacters = allowedReservedCharacters { + return .urlQueryParameterAllowed.union(allowedReservedCharacters) + } + return .urlQueryParameterAllowed + }() + + let percentEncodedName = name.percentEncoded(withAllowedCharacters: allowedCharacters) + let percentEncodedValue = value.percentEncoded(withAllowedCharacters: allowedCharacters) + + self.init(name: percentEncodedName, value: percentEncodedValue) + } + +} + +extension StringProtocol { + + fileprivate func percentEncoded(withAllowedCharacters allowedCharacters: CharacterSet) -> String { + if let percentEncoded = self.addingPercentEncoding(withAllowedCharacters: allowedCharacters) { + return percentEncoded + } + assertionFailure("Unexpected failure") + return components(separatedBy: allowedCharacters.inverted).joined() + } + + var utf8data: Data { + data(using: .utf8)! + } +} + +public extension CharacterSet { + + /** + * As per [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-2.2). + * + * This set contains all reserved characters that are otherwise + * included in `CharacterSet.urlQueryAllowed` but still need to be percent-escaped. + */ + static let urlQueryReserved = CharacterSet(charactersIn: ":/?#[]@!$&'()*+,;=") + + static let urlQueryParameterAllowed = CharacterSet.urlQueryAllowed.subtracting(Self.urlQueryReserved) + static let urlQueryStringAllowed = CharacterSet(charactersIn: "%+?").union(.urlQueryParameterAllowed) + } diff --git a/IntegrationTests/Tab/ErrorPageTests.swift b/IntegrationTests/Tab/ErrorPageTests.swift index 216a9960fc..4f9b5b6657 100644 --- a/IntegrationTests/Tab/ErrorPageTests.swift +++ b/IntegrationTests/Tab/ErrorPageTests.swift @@ -622,9 +622,12 @@ class ErrorPageTests: XCTestCase { eServerQueried.fulfill() return .failure(NSError.connectionLost) }] - let eNavigationFailed2 = tab.$error.compactMap { $0 }.timeout(5).first().promise() + /// Set content before subscribing to `tab.$error.compactMap { $0 }` to allow + /// for error to be nullified first by the call to `setContent`. tab.setContent(.url(.test, source: .userEntered(URL.test.absoluteString))) + let eNavigationFailed2 = tab.$error.compactMap { $0 }.timeout(5).first().promise() + _=try await eNavigationFailed2.value await fulfillment(of: [eServerQueried]) @@ -698,9 +701,12 @@ class ErrorPageTests: XCTestCase { eServerQueried.fulfill() return .failure(NSError.connectionLost) }] - let eNavigationFailed2 = tab.$error.compactMap { $0 }.timeout(5).first().promise() + /// Set content before subscribing to `tab.$error.compactMap { $0 }` to allow + /// for error to be nullified first by the call to `setContent`. tab.setContent(.url(.alternative, source: .userEntered(URL.alternative.absoluteString))) + let eNavigationFailed2 = tab.$error.compactMap { $0 }.timeout(5).first().promise() + _=try await eNavigationFailed2.value await fulfillment(of: [eServerQueried]) diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index ba3685c716..b4cbbb80d3 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "210.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "210.0.2"), .package(path: "../SwiftUIExtensions"), .package(path: "../AppKitExtensions"), .package(path: "../XPCHelper"), diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionQueueManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionQueueManager.swift index feedc98340..7c4818a885 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionQueueManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionQueueManager.swift @@ -24,10 +24,14 @@ protocol DataBrokerProtectionOperationQueue { var maxConcurrentOperationCount: Int { get set } func cancelAllOperations() func addOperation(_ op: Operation) - func addBarrierBlock(_ barrier: @escaping @Sendable () -> Void) + func addBarrierBlock1(_ barrier: @escaping @Sendable () -> Void) } -extension OperationQueue: DataBrokerProtectionOperationQueue {} +extension OperationQueue: DataBrokerProtectionOperationQueue { + func addBarrierBlock1(_ barrier: @escaping () -> Void) { + addBarrierBlock(barrier) + } +} enum DataBrokerProtectionQueueMode { case idle @@ -278,7 +282,7 @@ private extension DefaultDataBrokerProtectionQueueManager { return } - operationQueue.addBarrierBlock { [weak self] in + operationQueue.addBarrierBlock1 { [weak self] in let errorCollection = DataBrokerProtectionAgentErrorCollection(oneTimeError: nil, operationErrors: self?.operationErrorsForCurrentOperations()) errorHandler?(errorCollection) self?.resetMode(clearErrors: true) diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index 08aac16db2..9b51b42ee9 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -1440,7 +1440,7 @@ final class MockDataBrokerProtectionOperationQueue: DataBrokerProtectionOperatio self.operations.append(op) } - func addBarrierBlock(_ barrier: @escaping @Sendable () -> Void) { + func addBarrierBlock1(_ barrier: @escaping @Sendable () -> Void) { didCallAddBarrierBlockCount += 1 self.barrierBlock = barrier } diff --git a/LocalPackages/FeatureFlags/Package.swift b/LocalPackages/FeatureFlags/Package.swift index 673391b842..9dfada92aa 100644 --- a/LocalPackages/FeatureFlags/Package.swift +++ b/LocalPackages/FeatureFlags/Package.swift @@ -32,7 +32,7 @@ let package = Package( targets: ["FeatureFlags"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "210.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "210.0.2"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index e0e7b63e3a..4289525b39 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -32,7 +32,7 @@ let package = Package( .library(name: "VPNAppLauncher", targets: ["VPNAppLauncher"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "210.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "210.0.2"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.3"), .package(path: "../AppLauncher"), .package(path: "../UDSHelper"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index fadd3544e4..bfbec76e9e 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "210.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "210.0.2"), .package(path: "../SwiftUIExtensions") ], targets: [ diff --git a/UITests/AddressBarKeyboardShortcutsTests.swift b/UITests/AddressBarKeyboardShortcutsTests.swift index bf97489e5d..41f5c69826 100644 --- a/UITests/AddressBarKeyboardShortcutsTests.swift +++ b/UITests/AddressBarKeyboardShortcutsTests.swift @@ -18,13 +18,15 @@ import XCTest -class AddressBarKeyboardShortcutsTests: XCTestCase { +class AddressBarKeyboardShortcutsTests: UITestCase { private var app: XCUIApplication! private var urlStringForAddressBar: String! private var urlForAddressBar: URL! private var addressBarTextField: XCUIElement! + override class func setUp() { + super.setUp() UITests.firstRun() UITests.setAutocompleteToggleBeforeTestcaseRuns(false) // We don't want changes in the address bar that we don't create } diff --git a/UITests/AutocompleteTests.swift b/UITests/AutocompleteTests.swift index 8669db1933..906e95fac2 100644 --- a/UITests/AutocompleteTests.swift +++ b/UITests/AutocompleteTests.swift @@ -18,7 +18,7 @@ import XCTest -class AutocompleteTests: XCTestCase { +class AutocompleteTests: UITestCase { private var app: XCUIApplication! private var addBookmarkButton: XCUIElement! private var resetBookMarksMenuItem: XCUIElement! @@ -32,6 +32,7 @@ class AutocompleteTests: XCTestCase { private var siteTitleForHistorySite: String! override class func setUp() { + super.setUp() UITests.firstRun() UITests.setAutocompleteToggleBeforeTestcaseRuns(true) // These tests require autocomplete to be on } diff --git a/UITests/BookmarkSearchTests.swift b/UITests/BookmarkSearchTests.swift index ae4beb5b78..ef6021c8f2 100644 --- a/UITests/BookmarkSearchTests.swift +++ b/UITests/BookmarkSearchTests.swift @@ -18,7 +18,7 @@ import XCTest -class BookmarkSearchTests: XCTestCase { +class BookmarkSearchTests: UITestCase { private var app: XCUIApplication! private enum AccessibilityIdentifiers { @@ -39,6 +39,7 @@ class BookmarkSearchTests: XCTestCase { } override class func setUp() { + super.setUp() UITests.firstRun() } @@ -277,8 +278,14 @@ class BookmarkSearchTests: XCTestCase { private func dragAndDropBookmark(_ thirdBookmarkCell: XCUIElement, mode: BookmarkMode) { let startCoordinate = thirdBookmarkCell.coordinate(withNormalizedOffset: .zero) - let targetCoordinate = (mode == .panel ? app.popovers.firstMatch.outlines.firstMatch : app.tables.firstMatch).coordinate(withNormalizedOffset: .zero) - startCoordinate.press(forDuration: 0.1, thenDragTo: targetCoordinate) + + if mode == .panel { + let targetCoordinate = (app.popovers.firstMatch.outlines.firstMatch).coordinate(withNormalizedOffset: .zero) + startCoordinate.press(forDuration: 0.1, thenDragTo: targetCoordinate) + } else { + let secondBookmarkCell = app.tables.firstMatch.staticTexts["Bookmark #2"] + startCoordinate.press(forDuration: 0.1, thenDragTo: secondBookmarkCell.coordinate(withNormalizedOffset: .zero)) + } } private func clearSearchInBookmarksManager() { diff --git a/UITests/BookmarkSortTests.swift b/UITests/BookmarkSortTests.swift index a484f1e8ce..882bac4080 100644 --- a/UITests/BookmarkSortTests.swift +++ b/UITests/BookmarkSortTests.swift @@ -18,7 +18,7 @@ import XCTest -class BookmarkSortTests: XCTestCase { +class BookmarkSortTests: UITestCase { private var app: XCUIApplication! private enum AccessibilityIdentifiers { @@ -29,6 +29,7 @@ class BookmarkSortTests: XCTestCase { } override class func setUp() { + super.setUp() UITests.firstRun() } diff --git a/UITests/BookmarksAndFavoritesTests.swift b/UITests/BookmarksAndFavoritesTests.swift index 7c9bb93673..a7ad55d948 100644 --- a/UITests/BookmarksAndFavoritesTests.swift +++ b/UITests/BookmarksAndFavoritesTests.swift @@ -18,7 +18,7 @@ import XCTest -class BookmarksAndFavoritesTests: XCTestCase { +class BookmarksAndFavoritesTests: UITestCase { private var app: XCUIApplication! private var pageTitle: String! private var urlForBookmarksBar: URL! @@ -55,6 +55,7 @@ class BookmarksAndFavoritesTests: XCTestCase { private var showFavoritesPreferenceToggle: XCUIElement! override class func setUp() { + super.setUp() UITests.firstRun() } @@ -627,7 +628,13 @@ class BookmarksAndFavoritesTests: XCTestCase { "The bookmarks bar bookmark icon failed to become available in a reasonable timeframe." ) let bookmarkBarBookmarkIconCoordinate = bookmarkBarBookmarkIcon.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) - let deleteContextMenuItemCoordinate = bookmarkBarBookmarkIcon.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 9.0)) + var deleteContextMenuItemCoordinate: XCUICoordinate + if #available(macOS 15.0, *) { + deleteContextMenuItemCoordinate = bookmarkBarBookmarkIcon.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 8.0)) + } else { + deleteContextMenuItemCoordinate = bookmarkBarBookmarkIcon.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 9.0)) + } + bookmarkBarBookmarkIconCoordinate.rightClick() deleteContextMenuItemCoordinate.click() app.typeKey("w", modifierFlags: [.command, .option, .shift]) @@ -653,7 +660,13 @@ private extension BookmarksAndFavoritesTests { /// Make sure that we can reply on the bookmarks bar always appearing func toggleShowBookmarksBarAlwaysOn() { - app.typeKey(",", modifierFlags: [.command]) // Open settings + let settings = app.menuItems["MainMenu.preferencesMenuItem"] + XCTAssertTrue( + settings.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Reset bookmarks menu item didn't become available in a reasonable timeframe." + ) + + settings.click() XCTAssertTrue( settingsAppearanceButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), @@ -686,7 +699,8 @@ private extension BookmarksAndFavoritesTests { /// Make sure that appearance tab has been used to set "show favorites" to true func toggleBookmarksBarShowFavoritesOn() { - app.typeKey(",", modifierFlags: [.command]) // Open settings + app.openNewTab() + addressBarTextField.typeURL(URL(string: "duck://settings")!) XCTAssertTrue( settingsAppearanceButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), diff --git a/UITests/BookmarksBarTests.swift b/UITests/BookmarksBarTests.swift index 2d233cdfbd..062070b2d9 100644 --- a/UITests/BookmarksBarTests.swift +++ b/UITests/BookmarksBarTests.swift @@ -16,10 +16,9 @@ // limitations under the License. // -import Common import XCTest -class BookmarksBarTests: XCTestCase { +class BookmarksBarTests: UITestCase { private var app: XCUIApplication! private var pageTitle: String! private var urlForBookmarksBar: URL! @@ -36,6 +35,7 @@ class BookmarksBarTests: XCTestCase { private let titleStringLength = 12 override class func setUp() { + super.setUp() UITests.firstRun() } @@ -58,6 +58,7 @@ class BookmarksBarTests: XCTestCase { app.typeKey("n", modifierFlags: [.command]) // Guarantee a single window resetBookmarksAndAddOneBookmark() app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Close windows + app.typeKey("n", modifierFlags: [.command]) openSettingsAndSetShowBookmarksBarToUnchecked() openSecondWindowAndVisitSite() siteWindow = app.windows.containing(.webView, identifier: pageTitle).firstMatch @@ -164,7 +165,7 @@ class BookmarksBarTests: XCTestCase { private extension BookmarksBarTests { func openSettingsAndSetShowBookmarksBarToUnchecked() { - app.typeKey(",", modifierFlags: [.command]) + addressBarTextField.typeURL(URL(string: "duck://settings")!) let settingsAppearanceButton = app.buttons["PreferencesSidebar.appearanceButton"] XCTAssertTrue( diff --git a/UITests/BrowsingHistoryTests.swift b/UITests/BrowsingHistoryTests.swift index ac26e4ca7a..775a7e756c 100644 --- a/UITests/BrowsingHistoryTests.swift +++ b/UITests/BrowsingHistoryTests.swift @@ -18,7 +18,7 @@ import XCTest -class BrowsingHistoryTests: XCTestCase { +class BrowsingHistoryTests: UITestCase { private var app: XCUIApplication! private var historyMenuBarItem: XCUIElement! private var clearAllHistoryMenuItem: XCUIElement! @@ -28,6 +28,7 @@ class BrowsingHistoryTests: XCTestCase { private let lengthForRandomPageTitle = 8 override class func setUp() { + super.setUp() UITests.firstRun() } diff --git a/UITests/Common/UITests.swift b/UITests/Common/UITests.swift index ff590a386e..e421170677 100644 --- a/UITests/Common/UITests.swift +++ b/UITests/Common/UITests.swift @@ -59,7 +59,13 @@ enum UITests { app.launchEnvironment["UITEST_MODE"] = "1" app.launch() - app.typeKey(",", modifierFlags: [.command]) // Open settings + let settings = app.menuItems["MainMenu.preferencesMenuItem"] + XCTAssertTrue( + settings.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Reset bookmarks menu item didn't become available in a reasonable timeframe." + ) + + settings.click() let generalPreferencesButton = app.buttons["PreferencesSidebar.generalButton"] let autocompleteToggle = app.checkBoxes["PreferencesGeneralView.showAutocompleteSuggestions"] XCTAssertTrue( @@ -104,3 +110,35 @@ enum UITests { app.terminate() } } + +class TestFailureObserver: NSObject, XCTestObservation { + func testCase(_ testCase: XCTestCase, didRecord issue: XCTIssue) { + print("Failed test with name: \(testCase.name)") + let screenshotName = "\(testCase.name)-failure" + testCase.takeScreenshot(screenshotName) + } +} + +class UITestCase: XCTestCase { + private static let failureObserver = TestFailureObserver() + + override class func setUp() { + super.setUp() + XCTestObservationCenter.shared.addTestObserver(failureObserver) + } + + override class func tearDown() { + XCTestObservationCenter.shared.removeTestObserver(failureObserver) + super.tearDown() + } +} + +extension XCTestCase { + func takeScreenshot(_ name: String) { + let fullScreenshot = XCUIScreen.main.screenshot() + let screenshot = XCTAttachment(screenshot: fullScreenshot) + screenshot.name = name + screenshot.lifetime = .keepAlways + add(screenshot) + } +} diff --git a/UITests/FindInPageTests.swift b/UITests/FindInPageTests.swift index 0f8de44641..7c2fc87dbc 100644 --- a/UITests/FindInPageTests.swift +++ b/UITests/FindInPageTests.swift @@ -18,7 +18,7 @@ import XCTest -class FindInPageTests: XCTestCase { +class FindInPageTests: UITestCase { private var app: XCUIApplication! private var addressBarTextField: XCUIElement! private var loremIpsumWebView: XCUIElement! @@ -26,11 +26,13 @@ class FindInPageTests: XCTestCase { private let minimumExpectedMatchingPixelsInFindHighlight = 150 override class func setUp() { + super.setUp() UITests.firstRun() saveLocalHTML() } override class func tearDown() { + super.tearDown() removeLocalHTML() } diff --git a/UITests/OnboardingUITests.swift b/UITests/OnboardingUITests.swift index 5898096fb8..542c6d1898 100644 --- a/UITests/OnboardingUITests.swift +++ b/UITests/OnboardingUITests.swift @@ -18,7 +18,7 @@ import XCTest -final class OnboardingUITests: XCTestCase { +final class OnboardingUITests: UITestCase { override func tearDownWithError() throws { try resetApplicationData() diff --git a/UITests/PermissionsTests.swift b/UITests/PermissionsTests.swift index 86a82b06f5..9bbba9b031 100644 --- a/UITests/PermissionsTests.swift +++ b/UITests/PermissionsTests.swift @@ -29,7 +29,7 @@ import XCTest /// the best way, in the event that a future macOS version stops supporting this approach, but also solves the bug with `addUIInterruptionMonitor`, /// and you want to branch the implementations per macOS version: /// https://stackoverflow.com/questions/56559269/adduiinterruptionmonitor-is-not-getting-called-on-macos -class PermissionsTests: XCTestCase { +class PermissionsTests: UITestCase { private var app: XCUIApplication! private var notificationCenter: XCUIApplication! private var addressBarTextField: XCUIElement! @@ -38,7 +38,9 @@ class PermissionsTests: XCTestCase { private var clearAllHistoryMenuItem: XCUIElement! private var clearAllHistoryAlertClearButton: XCUIElement! private var fakeFireButton: XCUIElement! + override class func setUp() { + super.setUp() UITests.firstRun() } diff --git a/UITests/PinnedTabsTests.swift b/UITests/PinnedTabsTests.swift index 252df1b8cf..b541b169bc 100644 --- a/UITests/PinnedTabsTests.swift +++ b/UITests/PinnedTabsTests.swift @@ -18,7 +18,8 @@ import XCTest -class PinnedTabsTests: XCTestCase { +class PinnedTabsTests: UITestCase { + private static let failureObserver = TestFailureObserver() private var app: XCUIApplication! override func setUpWithError() throws { @@ -31,6 +32,7 @@ class PinnedTabsTests: XCTestCase { } override class func setUp() { + super.setUp() UITests.firstRun() } diff --git a/UITests/StateRestorationTests.swift b/UITests/StateRestorationTests.swift index 4d952c2d49..e25dd42620 100644 --- a/UITests/StateRestorationTests.swift +++ b/UITests/StateRestorationTests.swift @@ -18,7 +18,7 @@ import XCTest -class StateRestorationTests: XCTestCase { +class StateRestorationTests: UITestCase { private var app: XCUIApplication! private var firstPageTitle: String! private var secondPageTitle: String! @@ -31,6 +31,7 @@ class StateRestorationTests: XCTestCase { private var reopenAllWindowsFromLastSessionPreference: XCUIElement! override class func setUp() { + super.setUp() UITests.firstRun() } @@ -57,7 +58,7 @@ class StateRestorationTests: XCTestCase { } func test_tabStateAtRelaunch_shouldContainTwoSitesVisitedInPreviousSession_whenReopenAllWindowsFromLastSessionIsSet() { - app.typeKey(",", modifierFlags: [.command]) // Open settings + addressBarTextField.typeURL(URL(string: "duck://settings")!) // Open settings settingsGeneralButton.click(forDuration: 0.5, thenDragTo: settingsGeneralButton) reopenAllWindowsFromLastSessionPreference.clickAfterExistenceTestSucceeds() app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Close windows @@ -93,7 +94,7 @@ class StateRestorationTests: XCTestCase { } func test_tabStateAtRelaunch_shouldContainNoSitesVisitedInPreviousSession_whenReopenAllWindowsFromLastSessionIsUnset() { - app.typeKey(",", modifierFlags: [.command]) // Open settings + addressBarTextField.typeURL(URL(string: "duck://settings")!) // Open settings settingsGeneralButton.click(forDuration: 0.5, thenDragTo: settingsGeneralButton) openANewWindowPreference.clickAfterExistenceTestSucceeds() app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Close windows diff --git a/UITests/TabBarTests.swift b/UITests/TabBarTests.swift index 0c1cc4c3cc..32119356b3 100644 --- a/UITests/TabBarTests.swift +++ b/UITests/TabBarTests.swift @@ -18,7 +18,7 @@ import XCTest -class TabBarTests: XCTestCase { +class TabBarTests: UITestCase { private var app: XCUIApplication! override func setUpWithError() throws { @@ -29,6 +29,7 @@ class TabBarTests: XCTestCase { } override class func setUp() { + super.setUp() UITests.firstRun() } diff --git a/UnitTests/Onboarding/ContextualOnboarding/BrowserTabViewControllerOnboardingTests.swift b/UnitTests/Onboarding/ContextualOnboarding/BrowserTabViewControllerOnboardingTests.swift index e94c5879dd..836d48f7fa 100644 --- a/UnitTests/Onboarding/ContextualOnboarding/BrowserTabViewControllerOnboardingTests.swift +++ b/UnitTests/Onboarding/ContextualOnboarding/BrowserTabViewControllerOnboardingTests.swift @@ -16,12 +16,12 @@ // limitations under the License. // -import XCTest -import struct SwiftUI.AnyView -import Onboarding -import Combine import BrowserServicesKit +import Combine +import Onboarding import PrivacyDashboard +import struct SwiftUI.AnyView +import XCTest @testable import DuckDuckGo_Privacy_Browser final class BrowserTabViewControllerOnboardingTests: XCTestCase {