Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iOS test verifying API can be reached in blocked state #6154

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@
852BC6732BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 852BC6722BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan */; };
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */; };
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */; };
8542F7532BCFBD050035C042 /* SelectLocationFilterPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */; };
85557B0E2B591B2600795FE1 /* FirewallAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */; };
85557B102B59215F00795FE1 /* FirewallRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0F2B59215F00795FE1 /* FirewallRule.swift */; };
85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B112B594FC900795FE1 /* ConnectivityTests.swift */; };
Expand Down Expand Up @@ -1897,6 +1898,7 @@
852BC6722BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsChangeDNSSettings.xctestplan; sourceTree = "<group>"; };
8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportSubmittedPage.swift; sourceTree = "<group>"; };
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsPage.swift; sourceTree = "<group>"; };
8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationFilterPage.swift; sourceTree = "<group>"; };
85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallAPIClient.swift; sourceTree = "<group>"; };
85557B0F2B59215F00795FE1 /* FirewallRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallRule.swift; sourceTree = "<group>"; };
85557B112B594FC900795FE1 /* ConnectivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3812,6 +3814,7 @@
850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */,
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */,
85FB5A0B2B6903990015DCED /* WelcomePage.swift */,
8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */,
);
path = Pages;
sourceTree = "<group>";
Expand Down Expand Up @@ -5797,6 +5800,7 @@
A9DF789D2B7D1E8B0094E4AD /* LoggedInWithTimeUITestCase.swift in Sources */,
85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */,
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */,
8542F7532BCFBD050035C042 /* SelectLocationFilterPage.swift in Sources */,
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */,
85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */,
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */,
Expand Down
12 changes: 11 additions & 1 deletion ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public enum AccessibilityIdentifier: String {
case cancelDeleteCustomListButton
case customListLocationCheckmarkButton
case listCustomListDoneButton
case selectLocationFilterButton

// Cells
case deviceCell
Expand All @@ -63,7 +64,6 @@ public enum AccessibilityIdentifier: String {
case problemReportCell
case faqCell
case apiAccessCell
case relayFilterOwnershipCell
case relayFilterProviderCell
case wireGuardPortsCell
case wireGuardObfuscationCell
Expand All @@ -72,9 +72,19 @@ public enum AccessibilityIdentifier: String {
case customListEditNameFieldCell
case customListEditAddOrEditLocationCell
case customListEditDeleteListCell
case locationFilterOwnershipHeaderCell
case locationFilterProvidersHeaderCell
case ownershipMullvadOwnedCell
case ownershipRentedCell
case ownershipAnyCell
case countryLocationCell
case cityLocationCell
case relayLocationCell
case customListLocationCell

// Labels
case accountPagePaidUntilLabel
case accountPageDeviceNameLabel
case headerDeviceNameLabel
case connectionStatusConnectedLabel
case connectionStatusNotConnectedLabel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class AccountDeviceRow: UIView {
didSet {
deviceLabel.text = deviceName?.capitalized ?? ""
accessibilityValue = deviceName
accessibilityIdentifier = .accountPageDeviceNameLabel
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ struct RelayFilterCellFactory: CellFactoryProtocol {
switch item {
case .ownershipAny:
title = "Any"
cell.accessibilityIdentifier = .ownershipAnyCell
case .ownershipOwned:
title = "Mullvad owned only"
cell.accessibilityIdentifier = .ownershipMullvadOwnedCell
case .ownershipRented:
title = "Rented only"
cell.accessibilityIdentifier = .ownershipRentedCell
default:
assertionFailure("Item mismatch. Got: \(item)")
}
Expand All @@ -50,7 +53,6 @@ struct RelayFilterCellFactory: CellFactoryProtocol {
)

cell.applySubCellStyling()
cell.accessibilityIdentifier = .relayFilterOwnershipCell
}

private func configureProviderCell(_ cell: UITableViewCell, item: RelayFilterDataSource.Item) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,18 @@ extension RelayFilterDataSource: UITableViewDelegate {

let sectionId = snapshot().sectionIdentifiers[section]
let title: String
let accessibilityIdentifier: AccessibilityIdentifier

switch sectionId {
case .ownership:
accessibilityIdentifier = .locationFilterOwnershipHeaderCell
title = "Ownership"
case .providers:
accessibilityIdentifier = .locationFilterProvidersHeaderCell
title = "Providers"
}

view.accessibilityIdentifier = accessibilityIdentifier
view.titleLabel.text = NSLocalizedString(
"RELAY_FILTER_HEADER_LABEL",
tableName: "Relay filter header",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,6 @@ extension LocationCell {
}

func configure(item: LocationCellViewModel, behavior: LocationCellBehavior) {
accessibilityIdentifier = item.node.code
isDisabled = !item.node.isActive
locationLabel.text = item.node.name
showsCollapseControl = !item.node.children.isEmpty
Expand All @@ -318,6 +317,19 @@ extension LocationCell {
checkboxButton.isSelected = item.isSelected
checkboxButton.tintColor = item.isSelected ? .successColor : .white

if item.node is CountryLocationNode {
accessibilityIdentifier = .countryLocationCell
accessibilityValue = item.node.code
} else if item.node is CityLocationNode {
accessibilityIdentifier = .cityLocationCell
accessibilityValue = item.node.code
} else if item.node is HostLocationNode {
accessibilityIdentifier = .relayLocationCell
accessibilityValue = item.node.code
} else if item.node is CustomListLocationNode {
accessibilityIdentifier = .customListLocationCell
}

setBehavior(behavior)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ final class LocationViewController: UIViewController {
self?.navigateToFilter?()
})
)
navigationItem.leftBarButtonItem?.accessibilityIdentifier = .selectLocationFilterButton

navigationItem.rightBarButtonItem = UIBarButtonItem(
systemItem: .done,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class SettingsHeaderView: UITableViewHeaderFooterView {

let collapseButton: UIButton = {
let button = UIButton(type: .custom)
button.accessibilityIdentifier = .collapseButton
button.accessibilityIdentifier = .expandButton
button.tintColor = .white
return button
}()
Expand Down Expand Up @@ -123,6 +123,7 @@ class SettingsHeaderView: UITableViewHeaderFooterView {
let image = isExpanded ? chevronUp : chevronDown

collapseButton.setImage(image, for: .normal)
collapseButton.accessibilityIdentifier = isExpanded ? .collapseButton : .expandButton
}

private func updateAccessibilityCustomActions() {
Expand Down
6 changes: 4 additions & 2 deletions ios/MullvadVPNScreenshots/MullvadVPNScreenshots.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ class MullvadVPNScreenshots: XCTestCase {

// Tap the "Filter" button and expand each relay filter
app.navigationBars.buttons["Filter"].tap()
app.otherElements["Ownership"].buttons[AccessibilityIdentifier.collapseButton.rawValue].tap()
app.otherElements["Providers"].buttons[AccessibilityIdentifier.collapseButton.rawValue].tap()
app.otherElements[AccessibilityIdentifier.locationFilterOwnershipHeaderCell.rawValue]
.buttons[AccessibilityIdentifier.expandButton.rawValue].tap()
app.otherElements[AccessibilityIdentifier.locationFilterProvidersHeaderCell.rawValue]
.buttons[AccessibilityIdentifier.expandButton.rawValue].tap()
snapshot("RelayFilter")

app.navigationBars.buttons["Cancel"].tap()
Expand Down
70 changes: 67 additions & 3 deletions ios/MullvadVPNUITests/ConnectivityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ class ConnectivityTests: LoggedOutUITestCase {

override func tearDownWithError() throws {
super.tearDown()
firewallAPIClient.removeRules()
}

/// Verifies that the app still functions when API has been blocked
func testAPIConnectionViaBridges() throws {
let app = XCUIApplication()
app.launch()
addTeardownBlock {
self.firewallAPIClient.removeRules()
}

try Networking.verifyCanAccessAPI() // Just to make sure there's no old firewall rule still active
firewallAPIClient.createRule(try FirewallRule.makeBlockAPIAccessFirewallRule())
Expand All @@ -45,4 +45,68 @@ class ConnectivityTests: LoggedOutUITestCase {
.verifyDeviceLabelShown()
}
}

/// Get the app into a blocked state by connecting to a relay then applying a filter which don't find this relay, then verify that app can still communicate by logging out and verifying that the device was successfully removed
func testAPIReachableWhenBlocked() throws {
// Setup. Enter blocked state by connecting to relay and applying filter which relay isn't part of.
login(accountNumber: hasTimeAccountNumber)

TunnelControlPage(app)
.tapSelectLocationButton()

SelectLocationPage(app)
.tapFilterButton()

SelectLocationFilterPage(app)
.tapOwnershipCellExpandButton()
.tapMullvadOwnershipCell()
.tapApplyButton()

// Select the first country, its first city and its first relay
SelectLocationPage(app)
.tapCountryLocationCellExpandButton(withIndex: 0)
.tapCityLocationCellExpandButton(withIndex: 0)
.tapRelayLocationCell(withIndex: 0)

allowAddVPNConfigurationsIfAsked()

TunnelControlPage(app)
.tapSelectLocationButton()

SelectLocationPage(app)
.tapFilterButton()

SelectLocationFilterPage(app)
.tapOwnershipCellExpandButton()
.tapRentedOwnershipCell()
.tapApplyButton()

SelectLocationPage(app)
.tapDoneButton()

// Get device name, log out and make sure device was removed as a a way of verifying that the API can be reached
HeaderBar(app)
.tapAccountButton()

let deviceName = try AccountPage(app).getDeviceName()

AccountPage(app)
.tapLogOutButton()

LoginPage(app)

verifyDeviceHasBeenRemoved(deviceName: deviceName, accountNumber: hasTimeAccountNumber)
}
}

private func verifyDeviceHasBeenRemoved(deviceName: String, accountNumber: String) {
do {
let devices = try MullvadAPIWrapper().getDevices(accountNumber)

for device in devices where device.name == deviceName {
XCTFail("Device has not been removed which tells us that the logout was not successful")
}
} catch {
XCTFail("Failed to get devices from app API")
}
}
2 changes: 1 addition & 1 deletion ios/MullvadVPNUITests/CustomListsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class CustomListsTests: LoggedInWithTimeUITestCase {
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()
.tapDoneButton()
TunnelControlPage(app)
.tapSelectLocationButton()
}
Expand Down
5 changes: 5 additions & 0 deletions ios/MullvadVPNUITests/Pages/AccountPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ class AccountPage: Page {
return self
}

func getDeviceName() throws -> String {
let deviceNameLabel = app.otherElements[AccessibilityIdentifier.accountPageDeviceNameLabel]
return try XCTUnwrap(deviceNameLabel.value as? String, "Failed to read device name from label")
}

@discardableResult func verifyPaidUntil(_ date: Date) -> Self {
// Strip seconds from date, since the app don't display seconds
let calendar = Calendar.current
Expand Down
48 changes: 48 additions & 0 deletions ios/MullvadVPNUITests/Pages/SelectLocationFilterPage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// SelectLocationFilterPage.swift
// MullvadVPNUITests
//
// Created by Niklas Berglund on 2024-04-17.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import XCTest

class SelectLocationFilterPage: Page {
override init(_ app: XCUIApplication) {
super.init(app)
}

@discardableResult func tapOwnershipCellExpandButton() -> Self {
app.otherElements[AccessibilityIdentifier.locationFilterOwnershipHeaderCell]
.buttons[AccessibilityIdentifier.expandButton].tap()
return self
}

@discardableResult func tapProvidersCellExpandButton() -> Self {
app.otherElements[AccessibilityIdentifier.locationFilterProvidersHeaderCell]
.buttons[AccessibilityIdentifier.expandButton].tap()
return self
}

@discardableResult func tapAnyOwnershipCell() -> Self {
app.cells[AccessibilityIdentifier.ownershipAnyCell].tap()
return self
}

@discardableResult func tapMullvadOwnershipCell() -> Self {
app.cells[AccessibilityIdentifier.ownershipMullvadOwnedCell].tap()
return self
}

@discardableResult func tapRentedOwnershipCell() -> Self {
app.cells[AccessibilityIdentifier.ownershipRentedCell].tap()
return self
}

@discardableResult func tapApplyButton() -> Self {
app.buttons[AccessibilityIdentifier.applyButton].tap()
return self
}
}
39 changes: 33 additions & 6 deletions ios/MullvadVPNUITests/Pages/SelectLocationPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ class SelectLocationPage: Page {
return self
}

@discardableResult func tapCountryLocationCellExpandButton(withIndex: Int) -> Self {
let cell = app.cells.containing(.any, identifier: AccessibilityIdentifier.countryLocationCell.rawValue)
.element(boundBy: withIndex)
let expandButton = cell.buttons[AccessibilityIdentifier.expandButton]
expandButton.tap()
return self
}

@discardableResult func tapCityLocationCellExpandButton(withIndex: Int) -> Self {
let cell = app.cells.containing(.any, identifier: AccessibilityIdentifier.cityLocationCell.rawValue)
.element(boundBy: withIndex)
let expandButton = cell.buttons[AccessibilityIdentifier.expandButton]
expandButton.tap()
return self
}

@discardableResult func tapRelayLocationCell(withIndex: Int) -> Self {
let cell = app.cells.containing(.any, identifier: AccessibilityIdentifier.relayLocationCell.rawValue)
.element(boundBy: withIndex)
cell.tap()
return self
}

@discardableResult func tapLocationCellExpandButton(withName name: String) -> Self {
let table = app.tables[AccessibilityIdentifier.selectLocationTableView]
let matchingCells = table.cells.containing(.any, identifier: name)
Expand All @@ -44,12 +67,6 @@ class SelectLocationPage: Page {
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()
Expand All @@ -72,6 +89,16 @@ class SelectLocationPage: Page {
app.tables[AccessibilityIdentifier.selectLocationTableView].cells[identifier]
}

@discardableResult func tapFilterButton() -> Self {
app.buttons[AccessibilityIdentifier.selectLocationFilterButton].tap()
return self
}

@discardableResult func tapDoneButton() -> Self {
app.buttons[AccessibilityIdentifier.closeSelectLocationButton].tap()
return self
}

func locationCellIsExpanded(_ name: String) -> Bool {
let matchingCells = app.cells.containing(.any, identifier: name)
return matchingCells.buttons[AccessibilityIdentifier.expandButton].exists ? false : true
Expand Down
Loading