summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj26
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift19
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift3
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift3
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift7
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/ListCustomListViewController.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/LocationCoordinator.swift7
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift1
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationSectionHeaderView.swift5
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift1
-rw-r--r--ios/MullvadVPNUITests/CustomListsTests.swift173
-rw-r--r--ios/MullvadVPNUITests/Pages/CustomListPage.swift59
-rw-r--r--ios/MullvadVPNUITests/Pages/EditCustomListLocationsPage.swift53
-rw-r--r--ios/MullvadVPNUITests/Pages/ListCustomListsPage.swift33
-rw-r--r--ios/MullvadVPNUITests/Pages/Page.swift5
-rw-r--r--ios/MullvadVPNUITests/Pages/SelectLocationPage.swift44
-rw-r--r--ios/MullvadVPNUITests/XCUIElementQuery+Extensions.swift4
17 files changed, 435 insertions, 12 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index f178e1cdcd..fc6766d83f 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -703,6 +703,8 @@
A988A3E22AFE54AC0008D2C7 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; };
A988DF272ADE86ED00D807EF /* WireGuardObfuscationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */; };
A988DF2A2ADE880300D807EF /* TunnelSettingsV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */; };
+ A998DA812BD147AD001D61A2 /* ListCustomListsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */; };
+ A998DA832BD2B055001D61A2 /* EditCustomListLocationsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A998DA822BD2B055001D61A2 /* EditCustomListLocationsPage.swift */; };
A99E5EE02B7628150033F241 /* ProblemReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99E5EDF2B7628150033F241 /* ProblemReportViewModel.swift */; };
A99E5EE22B762ED30033F241 /* ProblemReportViewController+ViewManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99E5EE12B762ED30033F241 /* ProblemReportViewController+ViewManagement.swift */; };
A9A1DE792AD5708E0073F689 /* TransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A1DE782AD5708E0073F689 /* TransportStrategy.swift */; };
@@ -808,6 +810,8 @@
A9B6AC1B2ADEA3AD00F7802A /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB9C2A98F69E00F578F2 /* MemoryCache.swift */; };
A9BA08312BA32FA9005A7A2D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A92962582B1F4FDB00DFB93B /* PrivacyInfo.xcprivacy */; };
A9BA08322BA32FB6005A7A2D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A92962582B1F4FDB00DFB93B /* PrivacyInfo.xcprivacy */; };
+ A9BFAFFF2BD004ED00F2BCA1 /* CustomListsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */; };
+ A9BFB0012BD00B7F00F2BCA1 /* CustomListPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */; };
A9C342C12ACC37E30045F00E /* TunnelStatusBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */; };
A9C342C32ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */; };
A9C342C52ACC42130045F00E /* ServerRelaysResponse+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C342C42ACC42130045F00E /* ServerRelaysResponse+Stubs.swift */; };
@@ -1966,6 +1970,8 @@
A98502022B627B120061901E /* LocalNetworkProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkProbe.swift; sourceTree = "<group>"; };
A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardObfuscationSettings.swift; sourceTree = "<group>"; };
A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV3.swift; sourceTree = "<group>"; };
+ A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCustomListsPage.swift; sourceTree = "<group>"; };
+ A998DA822BD2B055001D61A2 /* EditCustomListLocationsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomListLocationsPage.swift; sourceTree = "<group>"; };
A99E5EDF2B7628150033F241 /* ProblemReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportViewModel.swift; sourceTree = "<group>"; };
A99E5EE12B762ED30033F241 /* ProblemReportViewController+ViewManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProblemReportViewController+ViewManagement.swift"; sourceTree = "<group>"; };
A9A1DE782AD5708E0073F689 /* TransportStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportStrategy.swift; sourceTree = "<group>"; };
@@ -1973,6 +1979,8 @@
A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = "<group>"; };
A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = "<group>"; };
A9B6AC192ADE8FBB00F7802A /* InMemorySettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemorySettingsStore.swift; sourceTree = "<group>"; };
+ A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsTests.swift; sourceTree = "<group>"; };
+ A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListPage.swift; sourceTree = "<group>"; };
A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelayCacheTracker+Stubs.swift"; sourceTree = "<group>"; };
A9C342C42ACC42130045F00E /* ServerRelaysResponse+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerRelaysResponse+Stubs.swift"; sourceTree = "<group>"; };
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = "<group>"; };
@@ -3623,19 +3631,20 @@
852969262B4D9C1F007EAD4C /* MullvadVPNUITests */ = {
isa = PBXGroup;
children = (
- 85B267602B849ADB0098E3CD /* mullvad-api.h */,
852969272B4D9C1F007EAD4C /* AccountTests.swift */,
8556EB532B9A1D7100D26DD4 /* BridgingHeader.h */,
85557B112B594FC900795FE1 /* ConnectivityTests.swift */,
+ A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */,
852969372B4ED20E007EAD4C /* Info.plist */,
+ 85B267602B849ADB0098E3CD /* mullvad-api.h */,
85557B0C2B591B0F00795FE1 /* Networking */,
852969312B4E9220007EAD4C /* Pages */,
850201DA2B503D7700EF8C96 /* RelayTests.swift */,
+ 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */,
+ 85C7A2E82B89024B00035D5A /* SettingsTests.swift */,
8518F6392B601910009EB113 /* Test base classes */,
- 85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */,
85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.swift */,
- 85C7A2E82B89024B00035D5A /* SettingsTests.swift */,
- 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */,
+ 85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */,
);
path = MullvadVPNUITests;
sourceTree = "<group>";
@@ -3647,9 +3656,12 @@
85557B1F2B5FBBD700795FE1 /* AccountPage.swift */,
8529693B2B4F0257007EAD4C /* Alert.swift */,
8587A05C2B84D43100152938 /* ChangeLogAlert.swift */,
- 852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */,
+ A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */,
85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */,
+ 852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */,
+ A998DA822BD2B055001D61A2 /* EditCustomListLocationsPage.swift */,
85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */,
+ A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */,
852969342B4E9270007EAD4C /* LoginPage.swift */,
85139B2C2B84B4A700734217 /* OutOfTimePage.swift */,
852969322B4E9232007EAD4C /* Page.swift */,
@@ -5639,6 +5651,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ A9BFB0012BD00B7F00F2BCA1 /* CustomListPage.swift in Sources */,
8556EB522B9A1C6900D26DD4 /* MullvadApi.swift in Sources */,
85EC620C2B838D10005AFFB5 /* MullvadAPIWrapper.swift in Sources */,
A9DF789D2B7D1E8B0094E4AD /* LoggedInWithTimeUITestCase.swift in Sources */,
@@ -5655,7 +5668,9 @@
8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */,
85557B202B5FBBD700795FE1 /* AccountPage.swift in Sources */,
852969352B4E9270007EAD4C /* LoginPage.swift in Sources */,
+ A998DA832BD2B055001D61A2 /* EditCustomListLocationsPage.swift in Sources */,
8556EB562B9B0AC500D26DD4 /* RevokedDevicePage.swift in Sources */,
+ A9BFAFFF2BD004ED00F2BCA1 /* CustomListsTests.swift in Sources */,
85557B102B59215F00795FE1 /* FirewallRule.swift in Sources */,
85557B0E2B591B2600795FE1 /* FirewallAPIClient.swift in Sources */,
852969282B4D9C1F007EAD4C /* AccountTests.swift in Sources */,
@@ -5668,6 +5683,7 @@
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */,
852A26462BA9C9CB006EB9C8 /* DNSSettingsPage.swift in Sources */,
+ A998DA812BD147AD001D61A2 /* ListCustomListsPage.swift in Sources */,
850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */,
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */,
85557B1E2B5FB8C700795FE1 /* HeaderBar.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index 174408f0af..9be9248b7d 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -37,12 +37,21 @@ public enum AccessibilityIdentifier: String {
case restorePurchasesButton
case secureConnectionButton
case selectLocationButton
+ case closeSelectLocationButton
case settingsButton
case startUsingTheAppButton
case problemReportAppLogsButton
case problemReportSendButton
case relayStatusCollapseButton
case settingsDoneButton
+ case openCustomListsMenuButton
+ case addNewCustomListButton
+ case editCustomListButton
+ case saveCreateCustomListButton
+ case confirmDeleteCustomListButton
+ case cancelDeleteCustomListButton
+ case customListLocationCheckmarkButton
+ case listCustomListDoneButton
// Cells
case deviceCell
@@ -60,6 +69,9 @@ public enum AccessibilityIdentifier: String {
case wireGuardObfuscationCell
case udpOverTCPPortCell
case quantumResistantTunnelCell
+ case customListEditNameFieldCell
+ case customListEditAddOrEditLocationCell
+ case customListEditDeleteListCell
// Labels
case accountPagePaidUntilLabel
@@ -71,7 +83,6 @@ public enum AccessibilityIdentifier: String {
// Views
case accountView
- case addLocationsView
case alertContainerView
case alertTitle
case changeLogAlert
@@ -91,6 +102,12 @@ public enum AccessibilityIdentifier: String {
case welcomeView
case deleteAccountView
case settingsContainerView
+ case newCustomListView
+ case customListEditTableView
+ case listCustomListsView
+ case listCustomListsTableView
+ case editCustomListEditLocationsView
+ case editCustomListEditLocationsTableView
// Other UI elements
case connectionPanelInAddressRow
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift
index 1dbd7ac7ae..06245b5c90 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsViewController.swift
@@ -27,7 +27,7 @@ class AddLocationsViewController: UIViewController {
tableView.separatorInset = .zero
tableView.rowHeight = 56
tableView.indicatorStyle = .white
- tableView.accessibilityIdentifier = .addLocationsView
+ tableView.accessibilityIdentifier = .editCustomListEditLocationsTableView
return tableView
}()
@@ -46,6 +46,7 @@ class AddLocationsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
+ view.accessibilityIdentifier = .editCustomListEditLocationsView
tableView.backgroundColor = view.backgroundColor
view.backgroundColor = .secondaryColor
addConstraints()
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift b/ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift
index 4731a74cc8..10f2c3ef7c 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/CustomListCellConfiguration.swift
@@ -78,6 +78,7 @@ struct CustomListCellConfiguration {
contentConfiguration.maxLength = 30
contentConfiguration.editingEvents.onChange = subject.bindTextAction(to: \.name)
+ cell.accessibilityIdentifier = AccessibilityIdentifier.customListEditNameFieldCell
cell.contentConfiguration = contentConfiguration
}
@@ -86,6 +87,7 @@ struct CustomListCellConfiguration {
contentConfiguration.text = itemIdentifier.text
cell.contentConfiguration = contentConfiguration
+ cell.accessibilityIdentifier = AccessibilityIdentifier.customListEditAddOrEditLocationCell
if let cell = cell as? CustomCellDisclosureHandling {
cell.disclosureType = .chevron
@@ -101,6 +103,7 @@ struct CustomListCellConfiguration {
onDelete?()
}
+ cell.accessibilityIdentifier = AccessibilityIdentifier.customListEditDeleteListCell
cell.contentConfiguration = contentConfiguration
}
}
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift b/ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift
index a3a7518af8..baf4e8949a 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift
@@ -58,6 +58,7 @@ class CustomListViewController: UIViewController {
}
)
barButtonItem.style = .done
+ barButtonItem.accessibilityIdentifier = AccessibilityIdentifier.saveCreateCustomListButton
return barButtonItem
}()
@@ -86,6 +87,7 @@ class CustomListViewController: UIViewController {
navigationItem.rightBarButtonItem = saveBarButton
view.directionalLayoutMargins = UIMetrics.contentLayoutMargins
view.backgroundColor = .secondaryColor
+ view.accessibilityIdentifier = .newCustomListView
isModalInPresentation = true
addSubviews()
@@ -102,6 +104,7 @@ class CustomListViewController: UIViewController {
tableView.delegate = dataSourceConfiguration
tableView.backgroundColor = .secondaryColor
tableView.registerReusableViews(from: CustomListItemIdentifier.CellIdentifier.self)
+ tableView.accessibilityIdentifier = AccessibilityIdentifier.customListEditTableView
}
private func configureDataSource() {
@@ -180,6 +183,7 @@ class CustomListViewController: UIViewController {
comment: ""
),
style: .destructive,
+ accessibilityId: .confirmDeleteCustomListButton,
handler: {
self.interactor.delete(id: self.subject.value.id)
self.delegate?.customListDidDelete(self.subject.value.customList)
@@ -192,7 +196,8 @@ class CustomListViewController: UIViewController {
value: "Cancel",
comment: ""
),
- style: .default
+ style: .default,
+ accessibilityId: .cancelDeleteCustomListButton
),
]
)
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/ListCustomListViewController.swift b/ios/MullvadVPN/Coordinators/CustomLists/ListCustomListViewController.swift
index 74b185169c..3295124592 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/ListCustomListViewController.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/ListCustomListViewController.swift
@@ -50,6 +50,7 @@ class ListCustomListViewController: UIViewController {
super.viewDidLoad()
view.backgroundColor = .secondaryColor
+ view.accessibilityIdentifier = .listCustomListsView
addSubviews()
configureNavigationItem()
@@ -88,6 +89,7 @@ class ListCustomListViewController: UIViewController {
tableView.separatorStyle = .singleLine
tableView.rowHeight = UIMetrics.SettingsCell.customListsCellHeight
tableView.registerReusableViews(from: CellReuseIdentifier.self)
+ tableView.accessibilityIdentifier = .listCustomListsTableView
}
private func configureNavigationItem() {
@@ -104,6 +106,8 @@ class ListCustomListViewController: UIViewController {
self?.didFinish?()
})
)
+
+ navigationItem.rightBarButtonItem?.accessibilityIdentifier = .listCustomListDoneButton
}
private func configureDataSource() {
diff --git a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
index 30fe23d8e3..3ccfbd1c70 100644
--- a/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/LocationCoordinator.swift
@@ -200,7 +200,7 @@ extension LocationCoordinator: LocationViewControllerDelegate {
actionSheet.overrideUserInterfaceStyle = .dark
actionSheet.view.tintColor = UIColor(red: 0.0, green: 0.59, blue: 1.0, alpha: 1)
- actionSheet.addAction(UIAlertAction(
+ let addCustomListAction = UIAlertAction(
title: NSLocalizedString(
"CUSTOM_LIST_ACTION_SHEET_ADD_LIST_BUTTON",
tableName: "CustomLists",
@@ -211,7 +211,9 @@ extension LocationCoordinator: LocationViewControllerDelegate {
handler: { [weak self] _ in
self?.showAddCustomList(nodes: nodes)
}
- ))
+ )
+ addCustomListAction.accessibilityIdentifier = AccessibilityIdentifier.addNewCustomListButton
+ actionSheet.addAction(addCustomListAction)
let editAction = UIAlertAction(
title: NSLocalizedString(
"CUSTOM_LIST_ACTION_SHEET_EDIT_LISTS_BUTTON",
@@ -225,6 +227,7 @@ extension LocationCoordinator: LocationViewControllerDelegate {
}
)
editAction.isEnabled = !customListRepository.fetchAll().isEmpty
+ editAction.accessibilityIdentifier = AccessibilityIdentifier.editCustomListButton
actionSheet.addAction(editAction)
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift
index 63ff5bcaee..ba4de11c17 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift
@@ -314,6 +314,7 @@ extension LocationCell {
locationLabel.text = item.node.name
showsCollapseControl = !item.node.children.isEmpty
isExpanded = item.node.showsChildren
+ checkboxButton.accessibilityIdentifier = .customListLocationCheckmarkButton
checkboxButton.isSelected = item.isSelected
checkboxButton.tintColor = item.isSelected ? .successColor : .white
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationSectionHeaderView.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationSectionHeaderView.swift
index 220df6323d..c84fea9097 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationSectionHeaderView.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationSectionHeaderView.swift
@@ -16,9 +16,8 @@ class LocationSectionHeaderView: UIView, UIContentView {
} set {
guard let newConfiguration = newValue as? Configuration,
actualConfiguration != newConfiguration else { return }
- let previousConfiguration = actualConfiguration
actualConfiguration = newConfiguration
- apply(configuration: previousConfiguration)
+ apply(configuration: newConfiguration)
}
}
@@ -66,7 +65,9 @@ class LocationSectionHeaderView: UIView, UIContentView {
let isActionHidden = configuration.primaryAction == nil
nameLabel.text = configuration.name
actionButton.isHidden = isActionHidden
+ actionButton.accessibilityIdentifier = nil
actualConfiguration.primaryAction.flatMap { [weak self] action in
+ self?.actionButton.accessibilityIdentifier = .openCustomListsMenuButton
self?.actionButton.addAction(action, for: .touchUpInside)
}
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
index 99f7f51488..6d82f5e1b9 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
@@ -83,6 +83,7 @@ final class LocationViewController: UIViewController {
self?.didFinish?()
})
)
+ navigationItem.rightBarButtonItem?.accessibilityIdentifier = .closeSelectLocationButton
setUpDataSources()
setUpTableView()
diff --git a/ios/MullvadVPNUITests/CustomListsTests.swift b/ios/MullvadVPNUITests/CustomListsTests.swift
new file mode 100644
index 0000000000..eb579b1b55
--- /dev/null
+++ b/ios/MullvadVPNUITests/CustomListsTests.swift
@@ -0,0 +1,173 @@
+//
+// CustomListsTests.swift
+// MullvadVPNUITests
+//
+// Created by Marco Nikic on 2024-04-17.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import XCTest
+
+class CustomListsTests: LoggedInWithTimeUITestCase {
+ func testCreateCustomListPersistAfterAppRestarts() throws {
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+
+ let customListName = createCustomListName()
+ createCustomList(named: customListName)
+ // Custom lists are persisted across app sessions, guarantee that the next test starts in a clean state
+ addTeardownBlock {
+ self.deleteCustomList(named: customListName)
+ }
+
+ app.terminate()
+ app.launch()
+
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+
+ SelectLocationPage(app)
+ .tapWhereStatusBarShouldBeToScrollToTopMostPosition()
+
+ XCTAssertTrue(app.staticTexts[customListName].exists)
+ }
+
+ func testDeleteCustomList() throws {
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+
+ let customListName = createCustomListName()
+ createCustomList(named: customListName)
+ workaroundOpenCustomListMenuBug()
+ deleteCustomList(named: customListName)
+
+ SelectLocationPage(app)
+ .tapWhereStatusBarShouldBeToScrollToTopMostPosition()
+
+ XCTAssertFalse(app.staticTexts[customListName].exists)
+ }
+
+ func testEditCustomListLocations() throws {
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+
+ let customListName = createCustomListName()
+ createCustomList(named: customListName)
+
+ addTeardownBlock {
+ self.workaroundOpenCustomListMenuBug()
+ self.deleteCustomList(named: customListName)
+ }
+
+ workaroundOpenCustomListMenuBug()
+ startEditingCustomList(named: customListName)
+
+ EditCustomListLocationsPage(app)
+ .scrollToLocationWith(identifier: "se")
+ .toggleLocationCheckmarkWith(identifier: "se")
+ .pressBackButton()
+
+ CustomListPage(app)
+ .tapSaveListButton()
+
+ ListCustomListsPage(app)
+ .tapDoneButton()
+
+ XCTAssertTrue(app.staticTexts[customListName].exists)
+ }
+
+ func testAddSingleLocationToCustomList() throws {
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+
+ let customListName = createCustomListName()
+ createCustomList(named: customListName)
+
+ addTeardownBlock {
+ self.workaroundOpenCustomListMenuBug()
+ self.deleteCustomList(named: customListName)
+ }
+
+ workaroundOpenCustomListMenuBug()
+ startEditingCustomList(named: customListName)
+
+ EditCustomListLocationsPage(app)
+ .scrollToLocationWith(identifier: "se")
+ .unfoldLocationwith(identifier: "se")
+ .unfoldLocationwith(identifier: "se-got")
+ .toggleLocationCheckmarkWith(identifier: "se-got-wg-001")
+ .pressBackButton()
+
+ CustomListPage(app)
+ .tapSaveListButton()
+
+ ListCustomListsPage(app)
+ .tapDoneButton()
+
+ SelectLocationPage(app)
+ .tapLocationCellExpandButton(withName: customListName)
+ let customListLocationName = "\(customListName)-se-got-wg-001"
+ let customListLocationCell = SelectLocationPage(app).cellWithIdentifier(identifier: customListLocationName)
+ XCTAssertTrue(customListLocationCell.exists)
+ }
+
+ func createCustomList(named name: String) {
+ SelectLocationPage(app)
+ .tapWhereStatusBarShouldBeToScrollToTopMostPosition()
+ .tapCustomListEllipsisButton()
+ .tapAddNewCustomList()
+
+ // When creating a new custom list, the "create" button should be disabled until the list has a name at minimum
+ CustomListPage(app)
+ .verifyCreateButtonIs(enabled: false)
+ .renameCustomList(name: name)
+ .verifyCreateButtonIs(enabled: true)
+ .tapCreateListButton()
+ }
+
+ func workaroundOpenCustomListMenuBug() {
+ // In order to avoid a bug where the open custom list button cannot be found, the location view is closed and then reopened
+ SelectLocationPage(app)
+ .closeSelectLocationPage()
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+ }
+
+ func startEditingCustomList(named customListName: String) {
+ SelectLocationPage(app)
+ .tapWhereStatusBarShouldBeToScrollToTopMostPosition()
+ .tapCustomListEllipsisButton()
+ .editExistingCustomLists()
+
+ ListCustomListsPage(app)
+ .selectCustomListToEdit(named: customListName)
+
+ CustomListPage(app)
+ .addOrEditLocations()
+
+ EditCustomListLocationsPage(app)
+ }
+
+ func deleteCustomList(named customListName: String) {
+ SelectLocationPage(app)
+ .tapWhereStatusBarShouldBeToScrollToTopMostPosition()
+ .tapCustomListEllipsisButton()
+ .editExistingCustomLists()
+
+ ListCustomListsPage(app)
+ .selectCustomListToEdit(named: customListName)
+
+ CustomListPage(app)
+ .deleteCustomList(named: customListName)
+ }
+
+ /// Creates a unique name for a custom list
+ ///
+ /// The name will be used as an accessibility identifier
+ /// Those are lower case and case sensitive.
+ func createCustomListName() -> String {
+ let customListOriginalName = UUID().uuidString
+ let index = customListOriginalName.index(customListOriginalName.startIndex, offsetBy: 30)
+ return String(customListOriginalName.prefix(upTo: index)).lowercased()
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/CustomListPage.swift b/ios/MullvadVPNUITests/Pages/CustomListPage.swift
new file mode 100644
index 0000000000..17ebf363c3
--- /dev/null
+++ b/ios/MullvadVPNUITests/Pages/CustomListPage.swift
@@ -0,0 +1,59 @@
+//
+// CustomListPage.swift
+// MullvadVPNUITests
+//
+// Created by Marco Nikic on 2024-04-17.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import XCTest
+
+class CustomListPage: Page {
+ @discardableResult override init(_ app: XCUIApplication) {
+ super.init(app)
+
+ self.pageAccessibilityIdentifier = .newCustomListView
+ waitForPageToBeShown()
+ }
+
+ @discardableResult func verifyCreateButtonIs(enabled: Bool) -> Self {
+ let saveOrCreateButton = app.buttons[.saveCreateCustomListButton]
+ XCTAssertTrue(saveOrCreateButton.isEnabled == enabled)
+ return self
+ }
+
+ @discardableResult func tapCreateListButton() -> Self {
+ let saveOrCreateButton = app.buttons[.saveCreateCustomListButton]
+ saveOrCreateButton.tap()
+ return self
+ }
+
+ // It's the same button, the difference is just for semantics
+ @discardableResult func tapSaveListButton() -> Self {
+ tapCreateListButton()
+ }
+
+ @discardableResult func renameCustomList(name: String) -> Self {
+ let editCustomListNameCell = app.cells[.customListEditNameFieldCell]
+ // Activate the text field
+ editCustomListNameCell.tap()
+ // Select the entire text with a triple tap
+ editCustomListNameCell.tap(withNumberOfTaps: 3, numberOfTouches: 1)
+ // Tap the "delete" key on the on-screen keyboard, the case is sensitive
+ app.keys["delete"].tap()
+ editCustomListNameCell.typeText(name)
+ return self
+ }
+
+ @discardableResult func deleteCustomList(named customListName: String) -> Self {
+ let deleteCustomListCell = app.cells[.customListEditDeleteListCell]
+ deleteCustomListCell.tap()
+ app.buttons[.confirmDeleteCustomListButton].tap()
+ return self
+ }
+
+ @discardableResult func addOrEditLocations() -> Self {
+ app.cells[.customListEditAddOrEditLocationCell].tap()
+ return self
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/EditCustomListLocationsPage.swift b/ios/MullvadVPNUITests/Pages/EditCustomListLocationsPage.swift
new file mode 100644
index 0000000000..08d3d6fa9f
--- /dev/null
+++ b/ios/MullvadVPNUITests/Pages/EditCustomListLocationsPage.swift
@@ -0,0 +1,53 @@
+//
+// EditCustomListLocationsPage.swift
+// MullvadVPNUITests
+//
+// Created by Marco Nikic on 2024-04-19.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import XCTest
+
+class EditCustomListLocationsPage: Page {
+ @discardableResult override init(_ app: XCUIApplication) {
+ super.init(app)
+
+ self.pageAccessibilityIdentifier = .editCustomListEditLocationsView
+ waitForPageToBeShown()
+ }
+
+ @discardableResult func scrollToLocationWith(identifier: String) -> Self {
+ let tableView = app.tables[.editCustomListEditLocationsTableView]
+ tableView.cells[identifier].tap()
+ return self
+ }
+
+ @discardableResult func toggleLocationCheckmarkWith(identifier: String) -> Self {
+ let locationCell = app.tables[.editCustomListEditLocationsTableView].cells[identifier]
+ locationCell.buttons[.customListLocationCheckmarkButton].tap()
+ return self
+ }
+
+ @discardableResult func unfoldLocationwith(identifier: String) -> Self {
+ let locationCell = app.tables[.editCustomListEditLocationsTableView].cells[identifier]
+ let expandCellButton = locationCell.buttons["expandButton"]
+ if expandCellButton.exists {
+ expandCellButton.tap()
+ }
+ return self
+ }
+
+ @discardableResult func collapseLocationwith(identifier: String) -> Self {
+ let locationCell = app.tables[.editCustomListEditLocationsTableView].cells[identifier]
+ let collapseCellButton = locationCell.buttons["collapseButton"]
+ if collapseCellButton.exists {
+ collapseCellButton.tap()
+ }
+ return self
+ }
+
+ @discardableResult func pressBackButton() -> Self {
+ app.navigationBars["Edit locations"].buttons.firstMatch.tap()
+ return self
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/ListCustomListsPage.swift b/ios/MullvadVPNUITests/Pages/ListCustomListsPage.swift
new file mode 100644
index 0000000000..2af421be7a
--- /dev/null
+++ b/ios/MullvadVPNUITests/Pages/ListCustomListsPage.swift
@@ -0,0 +1,33 @@
+//
+// ListCustomListsPage.swift
+// MullvadVPNUITests
+//
+// Created by Marco Nikic on 2024-04-18.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import XCTest
+
+class ListCustomListsPage: Page {
+ @discardableResult override init(_ app: XCUIApplication) {
+ super.init(app)
+
+ self.pageAccessibilityIdentifier = .listCustomListsView
+ waitForPageToBeShown()
+ }
+
+ /// This function taps on a given custom list in the Edit Custom List page.
+ ///
+ /// This functions assumes that all the custom lists are visible on a single page
+ /// No scrolling will be attempted to scroll to find a custom list
+ /// - Parameter customListName: The custom list to edit
+ @discardableResult func selectCustomListToEdit(named customListName: String) -> Self {
+ app.tables[.listCustomListsTableView].staticTexts[customListName].tap()
+ return self
+ }
+
+ @discardableResult func tapDoneButton() -> Self {
+ app.buttons[.listCustomListDoneButton].tap()
+ return self
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/Page.swift b/ios/MullvadVPNUITests/Pages/Page.swift
index 77fbebadae..9ac69f8ce0 100644
--- a/ios/MullvadVPNUITests/Pages/Page.swift
+++ b/ios/MullvadVPNUITests/Pages/Page.swift
@@ -46,4 +46,9 @@ class Page {
app.toolbars.buttons["Done"].tap()
return self
}
+
+ @discardableResult func tapWhereStatusBarShouldBeToScrollToTopMostPosition() -> Self {
+ app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0)).tap()
+ return self
+ }
}
diff --git a/ios/MullvadVPNUITests/Pages/SelectLocationPage.swift b/ios/MullvadVPNUITests/Pages/SelectLocationPage.swift
index ad0ee06c4b..a1f46b7ea8 100644
--- a/ios/MullvadVPNUITests/Pages/SelectLocationPage.swift
+++ b/ios/MullvadVPNUITests/Pages/SelectLocationPage.swift
@@ -33,8 +33,52 @@ class SelectLocationPage: Page {
return self
}
+ @discardableResult func tapLocationCellCollapseButton(withName name: String) -> Self {
+ let table = app.tables[AccessibilityIdentifier.selectLocationTableView]
+ let matchingCells = table.cells.containing(.any, identifier: name)
+ let buttons = matchingCells.buttons
+ let collapseButton = buttons[AccessibilityIdentifier.collapseButton]
+
+ collapseButton.tap()
+
+ return self
+ }
+
+ @discardableResult func closeSelectLocationPage() -> Self {
+ let doneButton = app.buttons[.closeSelectLocationButton]
+ doneButton.tap()
+ return self
+ }
+
+ @discardableResult func tapCustomListEllipsisButton() -> Self {
+ let customListEllipsisButton = app.buttons[AccessibilityIdentifier.openCustomListsMenuButton]
+ customListEllipsisButton.tap()
+ return self
+ }
+
+ @discardableResult func tapAddNewCustomList() -> Self {
+ let addNewCustomListButton = app.buttons[AccessibilityIdentifier.addNewCustomListButton]
+ addNewCustomListButton.tap()
+ return self
+ }
+
+ @discardableResult func editExistingCustomLists() -> Self {
+ let editCustomListsButton = app.buttons[AccessibilityIdentifier.editCustomListButton]
+ editCustomListsButton.tap()
+ return self
+ }
+
+ @discardableResult func cellWithIdentifier(identifier: String) -> XCUIElement {
+ app.tables[AccessibilityIdentifier.selectLocationTableView].cells[identifier]
+ }
+
func locationCellIsExpanded(_ name: String) -> Bool {
let matchingCells = app.cells.containing(.any, identifier: name)
return matchingCells.buttons[AccessibilityIdentifier.expandButton].exists ? false : true
}
+
+ func verifyEditCustomListsButtonIs(enabled: Bool) {
+ let editCustomListsButton = app.buttons[AccessibilityIdentifier.editCustomListButton]
+ XCTAssertTrue(editCustomListsButton.isEnabled == enabled)
+ }
}
diff --git a/ios/MullvadVPNUITests/XCUIElementQuery+Extensions.swift b/ios/MullvadVPNUITests/XCUIElementQuery+Extensions.swift
index 8500d7c08c..509ec7d0da 100644
--- a/ios/MullvadVPNUITests/XCUIElementQuery+Extensions.swift
+++ b/ios/MullvadVPNUITests/XCUIElementQuery+Extensions.swift
@@ -13,4 +13,8 @@ extension XCUIElementQuery {
subscript(key: any RawRepresentable<String>) -> XCUIElement {
self[key.rawValue]
}
+
+ subscript(key: AccessibilityIdentifier) -> XCUIElement {
+ self[key.rawValue]
+ }
}