Autogenerate screenshots for all platforms (#998)

Unframed for now.

- Split Main/TV targets
- Extend ProfileManager for screenshot scenarios
- Rename UITesting to UIAccessibility

Active profile looks very big on TV simulator, temporarily commented
`.padding(.top, 50)` in ActiveProfileView.

Closes #974
This commit is contained in:
Davide 2024-12-10 18:06:23 +01:00 committed by GitHub
parent a626722224
commit f441a2eed0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 588 additions and 81 deletions

View File

@ -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"]

View File

@ -27,7 +27,7 @@ import CommonLibrary
import CommonUtils
import PassepartoutKit
import SwiftUI
import UITesting
import UIAccessibility
struct InstalledProfileView: View, Routable {

View File

@ -27,7 +27,7 @@ import CommonLibrary
import CommonUtils
import PassepartoutKit
import SwiftUI
import UITesting
import UIAccessibility
struct ProfileContextMenu: View, Routable {
enum Style {

View File

@ -25,9 +25,13 @@
import PassepartoutKit
import SwiftUI
import UIAccessibility
struct ModuleViewModifier<T>: ViewModifier where T: ModuleBuilder & Equatable {
@Environment(\.isUITesting)
private var isUITesting
@ObservedObject
var editor: ProfileEditor
@ -37,7 +41,9 @@ struct ModuleViewModifier<T>: ViewModifier where T: ModuleBuilder & Equatable {
Form {
content
#if DEBUG
if !isUITesting {
UUIDSection(uuid: draft.id)
}
#endif
}
.themeForm()

View File

@ -37,5 +37,6 @@ struct ProfileNameSection: View {
placeholder: Strings.Placeholders.Profile.name,
footer: Strings.Views.Profile.Sections.Name.footer
)
.uiAccessibility(.Profile.name)
}
}

View File

@ -131,6 +131,7 @@ private extension ProfileEditView {
.contentShape(.rect)
}
.buttonStyle(.plain)
.uiAccessibility(.Profile.moduleLink)
}
}

View File

@ -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,11 +87,13 @@ private extension ModuleListView {
)
}
Spacer()
if !isUITesting {
EditorModuleToggle(profileEditor: profileEditor, module: module) {
EmptyView()
}
}
}
}
@ViewBuilder
func toolbarContent() -> some View {

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -29,6 +29,8 @@ public struct AccessibilityInfo: Equatable, Sendable {
public enum ElementType: Sendable {
case button
case link
case menu
case menuItem

View File

@ -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)
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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)
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//
import Foundation
extension AccessibilityInfo {
public enum VPNServers {
public static let countryGroup = AccessibilityInfo("vpnServers.countryGroup", .button)
}
}

View File

@ -28,7 +28,7 @@ import CommonLibrary
import CommonUtils
import Foundation
import PassepartoutKit
import UITesting
import UIAccessibility
@MainActor
public final class AppContext: ObservableObject, Sendable {

View File

@ -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 = "<group>"; };
0E3FF4B92CE3AFBC00BFF640 /* MigrationManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = "<group>"; };
0E418AB42D074F0E00D33D47 /* VPNServersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNServersScreen.swift; sourceTree = "<group>"; };
0E483E7F2CE64D6B00584B32 /* TunnelContext+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelContext+Shared.swift"; sourceTree = "<group>"; };
0E5DFDDC2CDB8F9100F2DE70 /* Passepartout.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.storekit; sourceTree = "<group>"; };
0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+Testing.swift"; sourceTree = "<group>"; };
@ -171,6 +176,9 @@
0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditorScreen.swift; sourceTree = "<group>"; };
0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; };
0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = "<group>"; };
0EA6340D2D08995800180D7C /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = "<group>"; };
0EA634112D08997300180D7C /* FlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowTests.swift; sourceTree = "<group>"; };
0EA634132D08998700180D7C /* AppScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = "<group>"; };
0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = "<group>"; };
0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAppProcessor.swift; sourceTree = "<group>"; };
0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTunnelProcessor.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
};
0E418AB72D0752D100D33D47 /* TV */ = {
isa = PBXGroup;
children = (
0EA634102D08996500180D7C /* Screens */,
0EA634112D08997300180D7C /* FlowTests.swift */,
0EA6340D2D08995800180D7C /* ScreenshotTests.swift */,
);
path = TV;
sourceTree = "<group>";
};
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 = "<group>";
@ -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 = "<group>";
};
0EA634102D08996500180D7C /* Screens */ = {
isa = PBXGroup;
children = (
0EA634132D08998700180D7C /* AppScreen.swift */,
);
path = Screens;
sourceTree = "<group>";
@ -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 = "<group>";
@ -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;

View File

@ -85,7 +85,7 @@
</CommandLineArgument>
<CommandLineArgument
argument = "-pp_ui_testing"
isEnabled = "NO">
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-pp_fake_migration"

View File

@ -26,8 +26,8 @@
import CommonLibrary
import PassepartoutKit
import SwiftUI
import UIAccessibility
import UILibrary
import UITesting
@MainActor
final class AppDelegate: NSObject {

View File

@ -32,8 +32,8 @@ import CommonUtils
import Foundation
import LegacyV2
import PassepartoutKit
import UIAccessibility
import UILibrary
import UITesting
extension AppContext {
static let shared: AppContext = {

View File

@ -48,7 +48,10 @@ extension AppContext {
)
let tunnelEnvironment = InMemoryEnvironment()
let tunnel = ExtendedTunnel(
tunnel: Tunnel(strategy: FakeTunnelStrategy(environment: tunnelEnvironment)),
tunnel: Tunnel(strategy: FakeTunnelStrategy(
environment: tunnelEnvironment,
dataCountDelta: DataCount(2 * 1200000, 1200000)
)),
environment: tunnelEnvironment,
processor: processor,
interval: Constants.shared.tunnel.refreshInterval

View File

@ -52,10 +52,47 @@ extension ProfileManager {
assert((moduleBuilder as? any ProviderSelecting)?.providerId == parameters.providerId)
}
if parameters.name == "Hide.me",
var ovpnBuilder = moduleBuilder as? OpenVPNModule.Builder {
if parameters.name == "Hide.me" {
if var ovpnBuilder = moduleBuilder as? OpenVPNModule.Builder {
#if !os(tvOS)
ovpnBuilder.isInteractive = true
#endif
ovpnBuilder.providerEntity = mockHideMeEntity
moduleBuilder = ovpnBuilder
} else if var onDemandBuilder = moduleBuilder as? OnDemandModule.Builder {
#if !os(tvOS)
onDemandBuilder.isEnabled = true
#endif
onDemandBuilder.policy = .excluding
onDemandBuilder.withSSIDs = [
"Friend's House": false,
"My Home Network": true,
"Safe Wi-Fi": true
]
moduleBuilder = onDemandBuilder
} else if var dnsBuilder = moduleBuilder as? DNSModule.Builder {
dnsBuilder.protocolType = .https
dnsBuilder.dohURL = "https://cloudflare-dns.com/dns-query"
dnsBuilder.servers = ["1.1.1.1", "1.0.0.1"]
dnsBuilder.domainName = "my-domain.com"
dnsBuilder.searchDomains = ["search-one.com", "search-two.org"]
moduleBuilder = dnsBuilder
}
}
if parameters.name == "My VPS" {
if var ovpnBuilder = moduleBuilder as? OpenVPNModule.Builder {
var cfgBuilder = OpenVPN.Configuration.Builder()
cfgBuilder.ca = .init(pem: "...")
cfgBuilder.remotes = [
ExtendedEndpoint(rawValue: "1.2.3.4:UDP:1234")!
]
ovpnBuilder.configurationBuilder = cfgBuilder
moduleBuilder = ovpnBuilder
} else if var onDemandBuilder = moduleBuilder as? OnDemandModule.Builder {
onDemandBuilder.isEnabled = true
moduleBuilder = onDemandBuilder
}
}
let module = try moduleBuilder.tryBuild()
@ -99,9 +136,47 @@ private extension ProfileManager {
static let mockParameters: [Parameters] = [
Parameters("CloudFlare DoT", false, false, [.dns]),
Parameters("Coffee VPN", true, false, [.wireGuard, .onDemand]),
Parameters("Hide.me", true, true, [.openVPN, .onDemand, .ip], .hideme),
Parameters("My VPS", true, true, [.openVPN]),
Parameters("Hide.me", true, true, [.openVPN, .onDemand, .dns, .ip], .hideme),
Parameters("My VPS", true, true, [.openVPN, .onDemand]),
Parameters("Office", true, false, [.onDemand, .httpProxy]),
Parameters("Personal DoH", false, false, [.dns, .onDemand])
]
static var mockHideMeEntity: VPNEntity<OpenVPN.Configuration> {
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)")
}
}
}

View File

@ -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 {

View File

@ -31,7 +31,7 @@ import CommonLibrary
import CommonUtils
import PassepartoutKit
import SwiftUI
import UITesting
import UIAccessibility
extension AppDelegate: NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {

View File

@ -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 {

View File

@ -24,7 +24,7 @@
//
import Foundation
import UITesting
import UIAccessibility
import XCTest
protocol XCUIApplicationProviding {

View File

@ -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
}
}

View File

@ -23,7 +23,8 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
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
}

View File

@ -24,7 +24,7 @@
//
import Foundation
import UITesting
import UIAccessibility
import XCTest
@MainActor

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -23,7 +23,8 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
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)")
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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)
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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..<up {
remote.press(.up)
}
remote.press(.select)
profileButton.waitForNonExistence(timeout: 1.0)
return self
}
@discardableResult
func presentProfilesWhileConnected() -> Self {
remote.press(.down)
_ = app.get(.App.ProfileList.profile)
return self
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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)")
}
}