diff --git a/Library/Package.swift b/Library/Package.swift index 88dd3cda..3b0d0f42 100644 --- a/Library/Package.swift +++ b/Library/Package.swift @@ -55,12 +55,12 @@ let package = Package( ] ), .library( - name: "UILibrary", - targets: ["UILibrary"] + name: "UIAccessibility", + targets: ["UIAccessibility"] ), .library( - name: "UITesting", - targets: ["UITesting"] + name: "UILibrary", + targets: ["UILibrary"] ) ], dependencies: [ @@ -186,20 +186,20 @@ let package = Package( .product(name: "PassepartoutWireGuardGo", package: "passepartoutkit-source-wireguard-go") ] ), + .target( + name: "UIAccessibility" + ), .target( name: "UILibrary", dependencies: [ "CommonAPI", "CommonLibrary", - "UITesting" + "UIAccessibility" ], resources: [ .process("Resources") ] ), - .target( - name: "UITesting" - ), .testTarget( name: "AppUIMainTests", dependencies: ["AppUIMain"] diff --git a/Library/Sources/AppUIMain/Views/App/InstalledProfileView.swift b/Library/Sources/AppUIMain/Views/App/InstalledProfileView.swift index 1ef9f62c..cebe529b 100644 --- a/Library/Sources/AppUIMain/Views/App/InstalledProfileView.swift +++ b/Library/Sources/AppUIMain/Views/App/InstalledProfileView.swift @@ -27,7 +27,7 @@ import CommonLibrary import CommonUtils import PassepartoutKit import SwiftUI -import UITesting +import UIAccessibility struct InstalledProfileView: View, Routable { diff --git a/Library/Sources/AppUIMain/Views/App/ProfileContextMenu.swift b/Library/Sources/AppUIMain/Views/App/ProfileContextMenu.swift index 83bf9a74..e035e77d 100644 --- a/Library/Sources/AppUIMain/Views/App/ProfileContextMenu.swift +++ b/Library/Sources/AppUIMain/Views/App/ProfileContextMenu.swift @@ -27,7 +27,7 @@ import CommonLibrary import CommonUtils import PassepartoutKit import SwiftUI -import UITesting +import UIAccessibility struct ProfileContextMenu: View, Routable { enum Style { diff --git a/Library/Sources/AppUIMain/Views/Profile/ModuleViewModifier.swift b/Library/Sources/AppUIMain/Views/Profile/ModuleViewModifier.swift index 9e76b56c..02010bda 100644 --- a/Library/Sources/AppUIMain/Views/Profile/ModuleViewModifier.swift +++ b/Library/Sources/AppUIMain/Views/Profile/ModuleViewModifier.swift @@ -25,9 +25,13 @@ import PassepartoutKit import SwiftUI +import UIAccessibility struct ModuleViewModifier: ViewModifier where T: ModuleBuilder & Equatable { + @Environment(\.isUITesting) + private var isUITesting + @ObservedObject var editor: ProfileEditor @@ -37,7 +41,9 @@ struct ModuleViewModifier: ViewModifier where T: ModuleBuilder & Equatable { Form { content #if DEBUG - UUIDSection(uuid: draft.id) + if !isUITesting { + UUIDSection(uuid: draft.id) + } #endif } .themeForm() diff --git a/Library/Sources/AppUIMain/Views/Profile/ProfileNameSection.swift b/Library/Sources/AppUIMain/Views/Profile/ProfileNameSection.swift index 9f86999f..4d13afd5 100644 --- a/Library/Sources/AppUIMain/Views/Profile/ProfileNameSection.swift +++ b/Library/Sources/AppUIMain/Views/Profile/ProfileNameSection.swift @@ -37,5 +37,6 @@ struct ProfileNameSection: View { placeholder: Strings.Placeholders.Profile.name, footer: Strings.Views.Profile.Sections.Name.footer ) + .uiAccessibility(.Profile.name) } } diff --git a/Library/Sources/AppUIMain/Views/Profile/iOS/ProfileEditView+iOS.swift b/Library/Sources/AppUIMain/Views/Profile/iOS/ProfileEditView+iOS.swift index 8643ad45..380fb64e 100644 --- a/Library/Sources/AppUIMain/Views/Profile/iOS/ProfileEditView+iOS.swift +++ b/Library/Sources/AppUIMain/Views/Profile/iOS/ProfileEditView+iOS.swift @@ -131,6 +131,7 @@ private extension ProfileEditView { .contentShape(.rect) } .buttonStyle(.plain) + .uiAccessibility(.Profile.moduleLink) } } diff --git a/Library/Sources/AppUIMain/Views/Profile/macOS/ModuleListView+macOS.swift b/Library/Sources/AppUIMain/Views/Profile/macOS/ModuleListView+macOS.swift index 214ffd0d..d46ca796 100644 --- a/Library/Sources/AppUIMain/Views/Profile/macOS/ModuleListView+macOS.swift +++ b/Library/Sources/AppUIMain/Views/Profile/macOS/ModuleListView+macOS.swift @@ -29,10 +29,14 @@ import CommonLibrary import CommonUtils import PassepartoutKit import SwiftUI +import UIAccessibility struct ModuleListView: View, Routable { static let generalModuleId = UUID() + @Environment(\.isUITesting) + private var isUITesting + @ObservedObject var profileEditor: ProfileEditor @@ -58,6 +62,7 @@ struct ModuleListView: View, Routable { NavigationLink(value: ProfileSplitView.Detail.module(id: module.id)) { moduleRow(for: module) } + .uiAccessibility(.Profile.moduleLink) } .onMove(perform: moveModules) } @@ -82,8 +87,10 @@ private extension ModuleListView { ) } Spacer() - EditorModuleToggle(profileEditor: profileEditor, module: module) { - EmptyView() + if !isUITesting { + EditorModuleToggle(profileEditor: profileEditor, module: module) { + EmptyView() + } } } } diff --git a/Library/Sources/AppUIMain/Views/VPN/iOS/VPNProviderServer+Content+iOS.swift b/Library/Sources/AppUIMain/Views/VPN/iOS/VPNProviderServer+Content+iOS.swift index 3d4bfc57..aa9dfe99 100644 --- a/Library/Sources/AppUIMain/Views/VPN/iOS/VPNProviderServer+Content+iOS.swift +++ b/Library/Sources/AppUIMain/Views/VPN/iOS/VPNProviderServer+Content+iOS.swift @@ -29,6 +29,7 @@ import CommonAPI import CommonLibrary import PassepartoutKit import SwiftUI +import UIAccessibility extension VPNProviderServerView { struct ContentView: View { @@ -129,6 +130,7 @@ private extension VPNProviderServerView.ContentView { } label: { ThemeCountryText(code) } + .uiAccessibility(.VPNServers.countryGroup) } } diff --git a/Library/Sources/AppUITV/Views/Profile/ActiveProfileView.swift b/Library/Sources/AppUITV/Views/Profile/ActiveProfileView.swift index f77e9b3a..7ca9c9f5 100644 --- a/Library/Sources/AppUITV/Views/Profile/ActiveProfileView.swift +++ b/Library/Sources/AppUITV/Views/Profile/ActiveProfileView.swift @@ -27,6 +27,7 @@ import CommonLibrary import CommonUtils import PassepartoutKit import SwiftUI +import UIAccessibility struct ActiveProfileView: View { @@ -73,7 +74,7 @@ struct ActiveProfileView: View { .clipShape(RoundedRectangle(cornerRadius: 50)) } .padding(.horizontal, 100) - .padding(.top, 50) +// .padding(.top, 50) Spacer() } @@ -86,6 +87,7 @@ private extension ActiveProfileView { .font(.title) .fontWeight(theme.relevantWeight) .frame(maxWidth: .infinity, alignment: .leading) + .uiAccessibility(.App.installedProfile) } var statusView: some View { diff --git a/Library/Sources/AppUITV/Views/Profile/ProfileListView.swift b/Library/Sources/AppUITV/Views/Profile/ProfileListView.swift index d1dfc21a..747260be 100644 --- a/Library/Sources/AppUITV/Views/Profile/ProfileListView.swift +++ b/Library/Sources/AppUITV/Views/Profile/ProfileListView.swift @@ -27,6 +27,7 @@ import CommonLibrary import CommonUtils import PassepartoutKit import SwiftUI +import UIAccessibility struct ProfileListView: View { @@ -84,6 +85,7 @@ private extension ProfileListView { } ) .focused($focusedField, equals: .profile(preview.id)) + .uiAccessibility(.App.ProfileList.profile) } func toggleView(for preview: ProfilePreview) -> some View { diff --git a/Library/Sources/UITesting/Domain/AccessibilityInfo.swift b/Library/Sources/UIAccessibility/Domain/AccessibilityInfo.swift similarity index 98% rename from Library/Sources/UITesting/Domain/AccessibilityInfo.swift rename to Library/Sources/UIAccessibility/Domain/AccessibilityInfo.swift index 0e25312f..e6718702 100644 --- a/Library/Sources/UITesting/Domain/AccessibilityInfo.swift +++ b/Library/Sources/UIAccessibility/Domain/AccessibilityInfo.swift @@ -29,6 +29,8 @@ public struct AccessibilityInfo: Equatable, Sendable { public enum ElementType: Sendable { case button + case link + case menu case menuItem diff --git a/Library/Sources/UITesting/Domain/View+Accessibility.swift b/Library/Sources/UIAccessibility/Domain/View+Accessibility.swift similarity index 100% rename from Library/Sources/UITesting/Domain/View+Accessibility.swift rename to Library/Sources/UIAccessibility/Domain/View+Accessibility.swift diff --git a/Library/Sources/UITesting/IPC/AppCommandLine.swift b/Library/Sources/UIAccessibility/IPC/AppCommandLine.swift similarity index 100% rename from Library/Sources/UITesting/IPC/AppCommandLine.swift rename to Library/Sources/UIAccessibility/IPC/AppCommandLine.swift diff --git a/Library/Sources/UITesting/Screens/App.swift b/Library/Sources/UIAccessibility/Screens/App.swift similarity index 91% rename from Library/Sources/UITesting/Screens/App.swift rename to Library/Sources/UIAccessibility/Screens/App.swift index 0172f094..c84ffb88 100644 --- a/Library/Sources/UITesting/Screens/App.swift +++ b/Library/Sources/UIAccessibility/Screens/App.swift @@ -33,14 +33,14 @@ extension AccessibilityInfo { public static let connectTo = AccessibilityInfo("app.profileMenu.connectTo", .menuItem) } + public enum ProfileList { + public static let profile = AccessibilityInfo("app.profileList.profile", .button) + } + public static let installedProfile = AccessibilityInfo("app.installedProfile", .text) public static let profileToggle = AccessibilityInfo("app.profileToggle", .button) public static let profileMenu = AccessibilityInfo("app.profileMenu", .menu) } - - public enum Profile { - public static let cancel = AccessibilityInfo("profile.cancel", .button) - } } diff --git a/Library/Sources/UIAccessibility/Screens/Profile.swift b/Library/Sources/UIAccessibility/Screens/Profile.swift new file mode 100644 index 00000000..1dfa72fb --- /dev/null +++ b/Library/Sources/UIAccessibility/Screens/Profile.swift @@ -0,0 +1,36 @@ +// +// Profile.swift +// Passepartout +// +// Created by Davide De Rosa on 12/2/24. +// Copyright (c) 2024 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension AccessibilityInfo { + public enum Profile { + public static let name = AccessibilityInfo("profile.name", .text) + + public static let moduleLink = AccessibilityInfo("profile.moduleLink", .link) + + public static let cancel = AccessibilityInfo("profile.cancel", .button) + } +} diff --git a/Library/Sources/UIAccessibility/Screens/VPNServers.swift b/Library/Sources/UIAccessibility/Screens/VPNServers.swift new file mode 100644 index 00000000..242be602 --- /dev/null +++ b/Library/Sources/UIAccessibility/Screens/VPNServers.swift @@ -0,0 +1,32 @@ +// +// VPNServers.swift +// Passepartout +// +// Created by Davide De Rosa on 12/9/24. +// Copyright (c) 2024 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension AccessibilityInfo { + public enum VPNServers { + public static let countryGroup = AccessibilityInfo("vpnServers.countryGroup", .button) + } +} diff --git a/Library/Sources/UILibrary/Business/AppContext.swift b/Library/Sources/UILibrary/Business/AppContext.swift index bde070f5..4617a04f 100644 --- a/Library/Sources/UILibrary/Business/AppContext.swift +++ b/Library/Sources/UILibrary/Business/AppContext.swift @@ -28,7 +28,7 @@ import CommonLibrary import CommonUtils import Foundation import PassepartoutKit -import UITesting +import UIAccessibility @MainActor public final class AppContext: ObservableObject, Sendable { diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index 20783fd5..0e066869 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -8,11 +8,11 @@ /* Begin PBXBuildFile section */ 0E08447C2CF86F2A00ECED7C /* XCTestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E08447B2CF86F2A00ECED7C /* XCTestCase+Extensions.swift */; }; - 0E2D68DC2CF7CF7500DC95BC /* UITesting in Frameworks */ = {isa = PBXBuildFile; productRef = 0E2D68DB2CF7CF7500DC95BC /* UITesting */; }; 0E3E22962CE53510005135DF /* AppUIMain in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, macos, ); productRef = 0E3E22952CE53510005135DF /* AppUIMain */; }; 0E3E22982CE53510005135DF /* AppUITV in Frameworks */ = {isa = PBXBuildFile; platformFilters = (tvos, ); productRef = 0E3E22972CE53510005135DF /* AppUITV */; }; 0E3FF4BA2CE3AFBC00BFF640 /* Profiles.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = 0E3FF4B72CE3AFBC00BFF640 /* Profiles.sqlite */; }; 0E3FF4BB2CE3AFBC00BFF640 /* MigrationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3FF4B92CE3AFBC00BFF640 /* MigrationManagerTests.swift */; }; + 0E418AB52D074F0E00D33D47 /* VPNServersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E418AB42D074F0E00D33D47 /* VPNServersScreen.swift */; platformFilters = (ios, macos, ); }; 0E483E812CE64D6B00584B32 /* TunnelContext+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E483E7F2CE64D6B00584B32 /* TunnelContext+Shared.swift */; }; 0E60512C2CE5393C00F763D4 /* PassepartoutImplementations in Frameworks */ = {isa = PBXBuildFile; productRef = 0E60512B2CE5393C00F763D4 /* PassepartoutImplementations */; }; 0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */; }; @@ -23,9 +23,9 @@ 0E7C3CCD2C9AF44600B72E69 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */; }; 0E7E3D692B9345FD002BBDB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */; }; 0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */; }; - 0E7F460E2CF7F01600B1C53A /* FlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F460B2CF7F01600B1C53A /* FlowTests.swift */; }; + 0E7F460E2CF7F01600B1C53A /* FlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F460B2CF7F01600B1C53A /* FlowTests.swift */; platformFilters = (ios, macos, ); }; 0E7F460F2CF7F01600B1C53A /* XCUIApplication+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F460C2CF7F01600B1C53A /* XCUIApplication+Extensions.swift */; }; - 0E7F46122CF7F44C00B1C53A /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F46102CF7F44C00B1C53A /* AppScreen.swift */; }; + 0E7F46122CF7F44C00B1C53A /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F46102CF7F44C00B1C53A /* AppScreen.swift */; platformFilters = (ios, macos, ); }; 0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8195592CFDA75200CC8FFD /* Dependencies.swift */; }; 0E81955B2CFDA7BF00CC8FFD /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8195592CFDA75200CC8FFD /* Dependencies.swift */; }; 0E8DFD482D05FA7000531CDE /* TunnelContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD472D05FA7000531CDE /* TunnelContext.swift */; }; @@ -36,10 +36,14 @@ 0E8DFD532D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */; }; 0E8DFD542D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */; }; 0E8DFD562D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */; }; - 0E916B782CF80FD60072921A /* ProfileEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */; }; + 0E916B782CF80FD60072921A /* ProfileEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */; platformFilters = (ios, macos, ); }; 0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */; }; 0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; }; - 0EAD6A1B2CF7F79A00CC1F02 /* ScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */; }; + 0EA6340C2D088C8200180D7C /* UIAccessibility in Frameworks */ = {isa = PBXBuildFile; productRef = 0EA6340B2D088C8200180D7C /* UIAccessibility */; }; + 0EA6340E2D08995800180D7C /* ScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA6340D2D08995800180D7C /* ScreenshotTests.swift */; platformFilters = (tvos, ); }; + 0EA634122D08997300180D7C /* FlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA634112D08997300180D7C /* FlowTests.swift */; platformFilters = (tvos, ); }; + 0EA634142D08998700180D7C /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA634132D08998700180D7C /* AppScreen.swift */; platformFilters = (tvos, ); }; + 0EAD6A1B2CF7F79A00CC1F02 /* ScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */; platformFilters = (ios, macos, ); }; 0EAEC8A92D05DB8D001AA50C /* DefaultAppProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */; }; 0EAEC8AA2D05DB8D001AA50C /* DefaultTunnelProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */; }; 0EB08B982CA46F4900A02591 /* AppPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0EB08B962CA46F4900A02591 /* AppPlist.strings */; }; @@ -47,7 +51,7 @@ 0EC066D12C7DC47600D88A94 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */; platformFilter = ios; }; 0EC332CA2B8A1808000B9C2F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EC332C92B8A1808000B9C2F /* NetworkExtension.framework */; }; 0EC332D22B8A1808000B9C2F /* PassepartoutTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0EC332C82B8A1808000B9C2F /* PassepartoutTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 0EC418D22CF86B7400AC6F2F /* ProfileMenuScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC418D12CF86B7400AC6F2F /* ProfileMenuScreen.swift */; }; + 0EC418D22CF86B7400AC6F2F /* ProfileMenuScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC418D12CF86B7400AC6F2F /* ProfileMenuScreen.swift */; platformFilters = (ios, macos, ); }; 0EC797422B9378E000C093B7 /* AppContext+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797402B9378E000C093B7 /* AppContext+Shared.swift */; }; 0ED61CF82CD0418C008FE259 /* App+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF72CD0418C008FE259 /* App+macOS.swift */; }; 0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF92CD04192008FE259 /* App+iOS.swift */; }; @@ -142,6 +146,7 @@ 0E3FF4AE2CE3AF6F00BFF640 /* PassepartoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PassepartoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 0E3FF4B72CE3AFBC00BFF640 /* Profiles.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = Profiles.sqlite; sourceTree = ""; }; 0E3FF4B92CE3AFBC00BFF640 /* MigrationManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = ""; }; + 0E418AB42D074F0E00D33D47 /* VPNServersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNServersScreen.swift; sourceTree = ""; }; 0E483E7F2CE64D6B00584B32 /* TunnelContext+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelContext+Shared.swift"; sourceTree = ""; }; 0E5DFDDC2CDB8F9100F2DE70 /* Passepartout.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.storekit; sourceTree = ""; }; 0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+Testing.swift"; sourceTree = ""; }; @@ -171,6 +176,9 @@ 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditorScreen.swift; sourceTree = ""; }; 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = ""; }; 0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = ""; }; + 0EA6340D2D08995800180D7C /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = ""; }; + 0EA634112D08997300180D7C /* FlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowTests.swift; sourceTree = ""; }; + 0EA634132D08998700180D7C /* AppScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = ""; }; 0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = ""; }; 0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAppProcessor.swift; sourceTree = ""; }; 0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTunnelProcessor.swift; sourceTree = ""; }; @@ -213,7 +221,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0E2D68DC2CF7CF7500DC95BC /* UITesting in Frameworks */, + 0EA6340C2D088C8200180D7C /* UIAccessibility in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -297,6 +305,16 @@ path = Resources; sourceTree = ""; }; + 0E418AB72D0752D100D33D47 /* TV */ = { + isa = PBXGroup; + children = ( + 0EA634102D08996500180D7C /* Screens */, + 0EA634112D08997300180D7C /* FlowTests.swift */, + 0EA6340D2D08995800180D7C /* ScreenshotTests.swift */, + ); + path = TV; + sourceTree = ""; + }; 0E757F112CD0CFFC006E13E1 /* LoginItem */ = { isa = PBXGroup; children = ( @@ -366,9 +384,8 @@ isa = PBXGroup; children = ( 0E916B7A2CF811DE0072921A /* Extensions */, - 0E916B7E2CF81A110072921A /* Screens */, - 0E7F460B2CF7F01600B1C53A /* FlowTests.swift */, - 0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */, + 0EC418C92CF81C6A00AC6F2F /* Main */, + 0E418AB72D0752D100D33D47 /* TV */, ); path = UITests; sourceTree = ""; @@ -408,7 +425,18 @@ 0E916B7E2CF81A110072921A /* Screens */ = { isa = PBXGroup; children = ( - 0EC418C92CF81C6A00AC6F2F /* Main */, + 0E7F46102CF7F44C00B1C53A /* AppScreen.swift */, + 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */, + 0EC418D12CF86B7400AC6F2F /* ProfileMenuScreen.swift */, + 0E418AB42D074F0E00D33D47 /* VPNServersScreen.swift */, + ); + path = Screens; + sourceTree = ""; + }; + 0EA634102D08996500180D7C /* Screens */ = { + isa = PBXGroup; + children = ( + 0EA634132D08998700180D7C /* AppScreen.swift */, ); path = Screens; sourceTree = ""; @@ -416,9 +444,9 @@ 0EC418C92CF81C6A00AC6F2F /* Main */ = { isa = PBXGroup; children = ( - 0E7F46102CF7F44C00B1C53A /* AppScreen.swift */, - 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */, - 0EC418D12CF86B7400AC6F2F /* ProfileMenuScreen.swift */, + 0E916B7E2CF81A110072921A /* Screens */, + 0E7F460B2CF7F01600B1C53A /* FlowTests.swift */, + 0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */, ); path = Main; sourceTree = ""; @@ -527,7 +555,7 @@ ); name = PassepartoutUITests; packageProductDependencies = ( - 0E2D68DB2CF7CF7500DC95BC /* UITesting */, + 0EA6340B2D088C8200180D7C /* UIAccessibility */, ); productName = PassepartoutUITests; productReference = 0E78FE4C2CF799F400B0C5BF /* PassepartoutUITests.xctest */; @@ -742,11 +770,15 @@ buildActionMask = 2147483647; files = ( 0E08447C2CF86F2A00ECED7C /* XCTestCase+Extensions.swift in Sources */, + 0EA6340E2D08995800180D7C /* ScreenshotTests.swift in Sources */, + 0EA634122D08997300180D7C /* FlowTests.swift in Sources */, 0E7F460E2CF7F01600B1C53A /* FlowTests.swift in Sources */, 0E916B782CF80FD60072921A /* ProfileEditorScreen.swift in Sources */, + 0EA634142D08998700180D7C /* AppScreen.swift in Sources */, 0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */, 0EAD6A1B2CF7F79A00CC1F02 /* ScreenshotTests.swift in Sources */, 0E7F460F2CF7F01600B1C53A /* XCUIApplication+Extensions.swift in Sources */, + 0E418AB52D074F0E00D33D47 /* VPNServersScreen.swift in Sources */, 0EC418D22CF86B7400AC6F2F /* ProfileMenuScreen.swift in Sources */, 0E7F46122CF7F44C00B1C53A /* AppScreen.swift in Sources */, ); @@ -1349,10 +1381,6 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - 0E2D68DB2CF7CF7500DC95BC /* UITesting */ = { - isa = XCSwiftPackageProductDependency; - productName = UITesting; - }; 0E3E22952CE53510005135DF /* AppUIMain */ = { isa = XCSwiftPackageProductDependency; productName = AppUIMain; @@ -1373,6 +1401,10 @@ isa = XCSwiftPackageProductDependency; productName = TunnelLibrary; }; + 0EA6340B2D088C8200180D7C /* UIAccessibility */ = { + isa = XCSwiftPackageProductDependency; + productName = UIAccessibility; + }; 0EBE80DB2BF55C0E00E36A20 /* TunnelLibrary */ = { isa = XCSwiftPackageProductDependency; productName = TunnelLibrary; diff --git a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme index 946e30cb..8e21e845 100644 --- a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme +++ b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme @@ -85,7 +85,7 @@ + isEnabled = "YES"> { + do { + var cfgBuilder = OpenVPN.Configuration.Builder() + cfgBuilder.ca = .init(pem: "...") + let cfg = try cfgBuilder.tryBuild(isClient: false) + let cfgData = try JSONEncoder().encode(cfg) + + let preset = AnyVPNPreset( + providerId: .hideme, + presetId: "default", + description: "Default", + endpoints: [.init(.udp, 1194)], + configurationIdentifier: "OpenVPN", + configuration: cfgData + ) + + return VPNEntity( + server: .init( + provider: .init( + id: .hideme, + serverId: "be-v4", + supportedConfigurationIdentifiers: ["OpenVPN"], + supportedPresetIds: nil, + categoryName: "", + countryCode: "BE", + otherCountryCodes: nil, + area: nil + ), + hostname: "be-v4.hideservers.net", + ipAddresses: nil + ), + preset: try preset.ofType(OpenVPN.Configuration.self) + ) + } catch { + fatalError("Unable to build Hide.me entity: \(error)") + } + } } diff --git a/Passepartout/App/Platforms/App+iOS.swift b/Passepartout/App/Platforms/App+iOS.swift index bd2cbbb4..813c4da7 100644 --- a/Passepartout/App/Platforms/App+iOS.swift +++ b/Passepartout/App/Platforms/App+iOS.swift @@ -27,7 +27,7 @@ import AppUIMain import SwiftUI -import UITesting +import UIAccessibility extension AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { diff --git a/Passepartout/App/Platforms/App+macOS.swift b/Passepartout/App/Platforms/App+macOS.swift index 3efb7257..e4437e0f 100644 --- a/Passepartout/App/Platforms/App+macOS.swift +++ b/Passepartout/App/Platforms/App+macOS.swift @@ -31,7 +31,7 @@ import CommonLibrary import CommonUtils import PassepartoutKit import SwiftUI -import UITesting +import UIAccessibility extension AppDelegate: NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { diff --git a/Passepartout/App/Platforms/App+tvOS.swift b/Passepartout/App/Platforms/App+tvOS.swift index ddf36e68..1c587c04 100644 --- a/Passepartout/App/Platforms/App+tvOS.swift +++ b/Passepartout/App/Platforms/App+tvOS.swift @@ -27,7 +27,7 @@ import AppUITV import SwiftUI -import UITesting +import UIAccessibility extension AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { diff --git a/Passepartout/UITests/Extensions/XCUIApplication+Extensions.swift b/Passepartout/UITests/Extensions/XCUIApplication+Extensions.swift index 0c401ecf..8cf005c9 100644 --- a/Passepartout/UITests/Extensions/XCUIApplication+Extensions.swift +++ b/Passepartout/UITests/Extensions/XCUIApplication+Extensions.swift @@ -24,7 +24,7 @@ // import Foundation -import UITesting +import UIAccessibility import XCTest protocol XCUIApplicationProviding { diff --git a/Passepartout/UITests/Extensions/XCUIElement+Extensions.swift b/Passepartout/UITests/Extensions/XCUIElement+Extensions.swift index ca5ed32d..19881b03 100644 --- a/Passepartout/UITests/Extensions/XCUIElement+Extensions.swift +++ b/Passepartout/UITests/Extensions/XCUIElement+Extensions.swift @@ -24,7 +24,7 @@ // import Foundation -import UITesting +import UIAccessibility import XCTest extension XCUIElement { @@ -44,14 +44,14 @@ private extension XCUIElement { func query(for elementType: AccessibilityInfo.ElementType) -> XCUIElementQuery { #if os(iOS) switch elementType { - case .button, .menu, .menuItem: + case .button, .link, .menu, .menuItem: return buttons case .text: return staticTexts } -#else +#elseif os(macOS) switch elementType { - case .button: + case .button, .link: return buttons case .menu: return menuButtons @@ -60,6 +60,13 @@ private extension XCUIElement { case .text: return staticTexts } +#else + switch elementType { + case .button, .link, .menu, .menuItem: + return buttons + case .text: + return staticTexts + } #endif } } diff --git a/Passepartout/UITests/FlowTests.swift b/Passepartout/UITests/Main/FlowTests.swift similarity index 73% rename from Passepartout/UITests/FlowTests.swift rename to Passepartout/UITests/Main/FlowTests.swift index ec1e3819..2b613fbd 100644 --- a/Passepartout/UITests/FlowTests.swift +++ b/Passepartout/UITests/Main/FlowTests.swift @@ -23,7 +23,8 @@ // along with Passepartout. If not, see . // -import UITesting +import Foundation +import UIAccessibility import XCTest @MainActor @@ -41,23 +42,42 @@ final class FlowTests: XCTestCase { try await Task.sleep(for: .seconds(2)) } - func testMainConnected() async throws { + func testConnect() { AppScreen(app: app) .waitForProfiles() .enableProfile(at: 2) } - func testEditProfile() async throws { + func testEditProfile() { AppScreen(app: app) .waitForProfiles() .openProfileMenu(at: 2) .editProfile() } - func testConnectToProviderServer() async throws { + func testEditProfileModule() { + AppScreen(app: app) + .waitForProfiles() + .openProfileMenu(at: 2) + .editProfile() + .enterModule(at: 1) + .leaveModule() + } + + func testConnectToProviderServer() { AppScreen(app: app) .waitForProfiles() .openProfileMenu(at: 2) .connectToProfile() } + +#if os(iOS) + func testDiscloseProviderCountry() { + AppScreen(app: app) + .waitForProfiles() + .openProfileMenu(at: 2) + .connectToProfile() + .discloseCountry(at: 2) + } +#endif } diff --git a/Passepartout/UITests/Screens/Main/AppScreen.swift b/Passepartout/UITests/Main/Screens/AppScreen.swift similarity index 98% rename from Passepartout/UITests/Screens/Main/AppScreen.swift rename to Passepartout/UITests/Main/Screens/AppScreen.swift index 9ea65bdf..33c4e30b 100644 --- a/Passepartout/UITests/Screens/Main/AppScreen.swift +++ b/Passepartout/UITests/Main/Screens/AppScreen.swift @@ -24,7 +24,7 @@ // import Foundation -import UITesting +import UIAccessibility import XCTest @MainActor diff --git a/Passepartout/UITests/Main/Screens/ProfileEditorScreen.swift b/Passepartout/UITests/Main/Screens/ProfileEditorScreen.swift new file mode 100644 index 00000000..1469581b --- /dev/null +++ b/Passepartout/UITests/Main/Screens/ProfileEditorScreen.swift @@ -0,0 +1,57 @@ +// +// ProfileEditorScreen.swift +// Passepartout +// +// Created by Davide De Rosa on 11/28/24. +// Copyright (c) 2024 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import UIAccessibility +import XCTest + +@MainActor +struct ProfileEditorScreen { + let app: XCUIApplication + + @discardableResult + func enterModule(at index: Int) -> Self { + let moduleLink = app.get(.Profile.moduleLink, at: index) + moduleLink.tap() + return self + } + + @discardableResult + func leaveModule() -> Self { +#if os(iOS) + let backButton = app.navigationBars.element(boundBy: 1).buttons.element(boundBy: 0) + XCTAssertTrue(backButton.waitForExistence(timeout: 1.0)) + backButton.tap() +#endif + return self + } + + @discardableResult + func closeProfile() -> AppScreen { + let cancelButton = app.get(.Profile.cancel) + cancelButton.tap() + return AppScreen(app: app) + } +} diff --git a/Passepartout/UITests/Screens/Main/ProfileMenuScreen.swift b/Passepartout/UITests/Main/Screens/ProfileMenuScreen.swift similarity index 89% rename from Passepartout/UITests/Screens/Main/ProfileMenuScreen.swift rename to Passepartout/UITests/Main/Screens/ProfileMenuScreen.swift index 556c0c98..12c7ed94 100644 --- a/Passepartout/UITests/Screens/Main/ProfileMenuScreen.swift +++ b/Passepartout/UITests/Main/Screens/ProfileMenuScreen.swift @@ -24,7 +24,7 @@ // import Foundation -import UITesting +import UIAccessibility import XCTest @MainActor @@ -32,16 +32,17 @@ struct ProfileMenuScreen { let app: XCUIApplication @discardableResult - func editProfile() -> ProfileEditorScreen { - let editButton = app.get(.App.ProfileMenu.edit) - editButton.tap() - return ProfileEditorScreen(app: app) + func connectToProfile() -> VPNServersScreen { + let connectToButton = app.get(.App.ProfileMenu.connectTo) + connectToButton.tap() + return VPNServersScreen(app: app) } @discardableResult - func connectToProfile() -> AppScreen { - let connectToButton = app.get(.App.ProfileMenu.connectTo) - connectToButton.tap() - return AppScreen(app: app) + func editProfile() -> ProfileEditorScreen { + let editButton = app.get(.App.ProfileMenu.edit) + editButton.tap() + _ = app.get(.Profile.name) + return ProfileEditorScreen(app: app) } } diff --git a/Passepartout/UITests/Screens/Main/ProfileEditorScreen.swift b/Passepartout/UITests/Main/Screens/VPNServersScreen.swift similarity index 77% rename from Passepartout/UITests/Screens/Main/ProfileEditorScreen.swift rename to Passepartout/UITests/Main/Screens/VPNServersScreen.swift index 821aeab3..b991d23f 100644 --- a/Passepartout/UITests/Screens/Main/ProfileEditorScreen.swift +++ b/Passepartout/UITests/Main/Screens/VPNServersScreen.swift @@ -1,8 +1,8 @@ // -// ProfileEditorScreen.swift +// VPNServersScreen.swift // Passepartout // -// Created by Davide De Rosa on 11/28/24. +// Created by Davide De Rosa on 12/9/24. // Copyright (c) 2024 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,17 +24,17 @@ // import Foundation -import UITesting +import UIAccessibility import XCTest @MainActor -struct ProfileEditorScreen { +struct VPNServersScreen { let app: XCUIApplication @discardableResult - func closeProfile() -> AppScreen { - let cancelButton = app.get(.Profile.cancel) - cancelButton.tap() - return AppScreen(app: app) + func discloseCountry(at index: Int) -> Self { + let group = app.get(.VPNServers.countryGroup, at: index) + group.tap() + return self } } diff --git a/Passepartout/UITests/ScreenshotTests.swift b/Passepartout/UITests/Main/ScreenshotTests.swift similarity index 78% rename from Passepartout/UITests/ScreenshotTests.swift rename to Passepartout/UITests/Main/ScreenshotTests.swift index 49187724..7c6a1df2 100644 --- a/Passepartout/UITests/ScreenshotTests.swift +++ b/Passepartout/UITests/Main/ScreenshotTests.swift @@ -23,7 +23,8 @@ // along with Passepartout. If not, see . // -import UITesting +import Foundation +import UIAccessibility import XCTest @MainActor @@ -50,7 +51,7 @@ final class ScreenshotTests: XCTestCase, XCUIApplicationProviding { .enableProfile(at: 0) try await Task.sleep(for: .seconds(2)) - try snapshot("1_MainConnected") + try snapshot("1_Connected") let profile = root .openProfileMenu(at: 2) @@ -60,12 +61,29 @@ final class ScreenshotTests: XCTestCase, XCUIApplicationProviding { try snapshot("2_ProfileEditor", target: .sheet) profile + .enterModule(at: 1) + + try await Task.sleep(for: .seconds(2)) + try snapshot("3_OnDemand", target: .sheet) + + profile + .leaveModule() + .enterModule(at: 2) + + try await Task.sleep(for: .seconds(2)) + try snapshot("4_DNS", target: .sheet) + + profile + .leaveModule() .closeProfile() .openProfileMenu(at: 2) .connectToProfile() +#if os(iOS) + .discloseCountry(at: 2) +#endif try await Task.sleep(for: .seconds(2)) - try snapshot("3_ProviderServers", target: .sheet) + try snapshot("5_ProviderServers", target: .sheet) print("Saved to: \(ScreenshotDestination.temporary.url)") } diff --git a/Passepartout/UITests/TV/FlowTests.swift b/Passepartout/UITests/TV/FlowTests.swift new file mode 100644 index 00000000..6972eb82 --- /dev/null +++ b/Passepartout/UITests/TV/FlowTests.swift @@ -0,0 +1,71 @@ +// +// FlowTests.swift +// Passepartout +// +// Created by Davide De Rosa on 12/10/24. +// Copyright (c) 2024 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import UIAccessibility +import XCTest + +@MainActor +final class FlowTests: XCTestCase { + private var app: XCUIApplication! + + override func setUp() async throws { + continueAfterFailure = false + app = XCUIApplication() + app.appArguments = [.uiTesting] + app.launch() + } + + override func tearDown() async throws { + try await Task.sleep(for: .seconds(2)) + } + + func testShow() { + AppScreen(app: app) + .waitForProfiles() + } + + func testPresentProfiles() { + AppScreen(app: app) + .waitForProfiles() + .presentInitialProfiles() + } + + func testConnect() { + AppScreen(app: app) + .waitForProfiles() + .presentInitialProfiles() + .enableProfile(up: 1) + } + + func testReconnectToOtherProfile() { + AppScreen(app: app) + .waitForProfiles() + .presentInitialProfiles() + .enableProfile(up: 1) + .presentProfilesWhileConnected() + .enableProfile(up: 0) + } +} diff --git a/Passepartout/UITests/TV/Screens/AppScreen.swift b/Passepartout/UITests/TV/Screens/AppScreen.swift new file mode 100644 index 00000000..edd7a7a1 --- /dev/null +++ b/Passepartout/UITests/TV/Screens/AppScreen.swift @@ -0,0 +1,66 @@ +// +// AppScreen.swift +// Passepartout +// +// Created by Davide De Rosa on 12/10/24. +// Copyright (c) 2024 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import UIAccessibility +import XCTest + +@MainActor +struct AppScreen { + let app: XCUIApplication + + private let remote: XCUIRemote = .shared + + @discardableResult + func waitForProfiles() -> Self { + app.get(.App.installedProfile) + return self + } + + @discardableResult + func presentInitialProfiles() -> Self { + remote.press(.down) + return self + } + + @discardableResult + func enableProfile(up: Int) -> Self { + let profileButton = app.get(.App.ProfileList.profile) + remote.press(.right) + for _ in 0.. Self { + remote.press(.down) + _ = app.get(.App.ProfileList.profile) + return self + } +} diff --git a/Passepartout/UITests/TV/ScreenshotTests.swift b/Passepartout/UITests/TV/ScreenshotTests.swift new file mode 100644 index 00000000..a6bd9e8b --- /dev/null +++ b/Passepartout/UITests/TV/ScreenshotTests.swift @@ -0,0 +1,66 @@ +// +// ScreenshotTests.swift +// Passepartout +// +// Created by Davide De Rosa on 12/10/24. +// Copyright (c) 2024 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import UIAccessibility +import XCTest + +@MainActor +final class ScreenshotTests: XCTestCase, XCUIApplicationProviding { + let app: XCUIApplication = { + let app = XCUIApplication() + app.appArguments = [.uiTesting] + return app + }() + + override func setUp() async throws { + continueAfterFailure = false + app.launch() + } + + func testTakeScreenshots() async throws { + let root = AppScreen(app: app) + .waitForProfiles() + .presentInitialProfiles() + .enableProfile(up: 1) + + try await Task.sleep(for: .seconds(2)) + try snapshot("1_Connected") + + root + .presentProfilesWhileConnected() + + try await Task.sleep(for: .seconds(2)) + try snapshot("2_ConnectedWithProfileList") + + root + .enableProfile(up: 0) + + try await Task.sleep(for: .seconds(2)) + try snapshot("3_OnDemand") + + print("Saved to: \(ScreenshotDestination.temporary.url)") + } +}