Skip to content

Commit

Permalink
Add iOS test verifying API can be reached from blocked state
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasberglund committed May 6, 2024
1 parent c2f6334 commit 98f7888
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 16 deletions.
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

0 comments on commit 98f7888

Please sign in to comment.