Skip to content

Commit

Permalink
Fix UI test cases authentication (#8)
Browse files Browse the repository at this point in the history
# Description

Fix UI test case authentication on GitHub Actions by:
1. Adding a Test-credentials.xcconfig to the UI Test target
2. Reading these credentials into the test target and using them to complete the authentication flow in the UI tests
3. Updating behavior around NavigableWebView to ensure that the parent view is the only owner of the `@State var url: URL` 
    - and that no other navigation manipulations (see: `.navigationDestination(item: $link)`) are parent to a NavigableWebView.

## Output

- Infrastructure for continuous integration is configured and tests are 🟢 passing for future PRs
- Navigation and web view behavior is corrected for iOS 18+

## Screenshots

Captured from local build testing against developer accounts in production.

<img width="1242" alt="Screenshot 2024-12-08 at 22 45 01" src="https://github.com/user-attachments/assets/9b0af814-0521-4d51-82ae-5a99452a450c">
<img width="667" alt="Screenshot 2024-12-08 at 22 43 55" src="https://github.com/user-attachments/assets/92a0d214-296a-4dc3-86da-2f2ad4d92311">
<img width="1227" alt="Screenshot 2024-12-08 at 22 43 39" src="https://github.com/user-attachments/assets/2c75a3b4-1952-4999-a09d-3760c2d119d0">
  • Loading branch information
aokj4ck authored Dec 10, 2024
1 parent ab3493c commit 0d58425
Show file tree
Hide file tree
Showing 22 changed files with 297 additions and 105 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ jobs:
BIKE_INDEX_DEVELOPMENT_XCCONFIG: ${{ secrets.BIKE_INDEX_DEVELOPMENT_XCCONFIG }}
run: |
echo "$BIKE_INDEX_DEVELOPMENT_XCCONFIG" >> BikeIndex-development.xcconfig
- name: Set up xcconfig UI test credentials (see README.md#UI-Test-configuration)
env:
BIKE_INDEX_TEST_CREDENTIALS: ${{ secrets.BIKE_INDEX_TEST_CREDENTIALS }}
run: |
echo "$BIKE_INDEX_TEST_CREDENTIALS" >> SharedTests/Test-credentials.xcconfig
- name: List available simulators
run: xcodebuild -scheme Debug\ \(development\) -project BikeIndex.xcodeproj -showdestinations
- name: Run tests
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# This project assumes you already have a system-wide ignore file
# such as one provided by https://gitignore.io configured for 'xcode'

# App secrets!
BikeIndex-development.xcconfig
BikeIndex-production.xcconfig
# Test app secrets!
SharedTests/Test-credentials.xcconfig

# Fastlane recommended ignore:
# https://docs.fastlane.tools/best-practices/source-control/
Expand Down
22 changes: 20 additions & 2 deletions BikeIndex.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
040FA32E2BCCB7F5000C4B18 /* ManufacturerKeyboardUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 040FA32D2BCCB7F5000C4B18 /* ManufacturerKeyboardUITestCase.swift */; };
041CCD8C2BC2E0D300A3A9A8 /* BikeRegistration+Propulsion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 041CCD8B2BC2E0D300A3A9A8 /* BikeRegistration+Propulsion.swift */; };
041CCD8E2BC2E36300A3A9A8 /* RegistrationPropulsionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 041CCD8D2BC2E36300A3A9A8 /* RegistrationPropulsionTests.swift */; };
042724CA2B85714D008BEDBC /* SingleBikeResponse_mock.json in Resources */ = {isa = PBXBuildFile; fileRef = 04C7BC652B53514C0091493C /* SingleBikeResponse_mock.json */; };
043033282B3E285F00FD126F /* StolenRecordEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043033272B3E285F00FD126F /* StolenRecordEntryView.swift */; };
0430332A2B3E5EEB00FD126F /* Countries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043033292B3E5EEB00FD126F /* Countries.swift */; };
0430332C2B3E642200FD126F /* Binding+Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0430332B2B3E642200FD126F /* Binding+Optional.swift */; };
Expand Down Expand Up @@ -48,6 +47,7 @@
04C7BC702B535E330091493C /* BikeStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C7BC6F2B535E330091493C /* BikeStatus.swift */; };
04C7BC722B5361220091493C /* RegisterMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C7BC712B5361220091493C /* RegisterMode.swift */; };
04C7BC772B536C150091493C /* StolenRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C7BC762B536C150091493C /* StolenRecord.swift */; };
04CA5AE32D014CC800BD572A /* AuthenticatedUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04CA5AE22D014CC800BD572A /* AuthenticatedUITestCase.swift */; };
04E1343E2B4B718200CE88DD /* WebViewKit in Frameworks */ = {isa = PBXBuildFile; productRef = 04E1343D2B4B718200CE88DD /* WebViewKit */; };
04EA110E2B2502660024CDDD /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04EA110C2B2502660024CDDD /* API.swift */; };
04EA110F2B2502660024CDDD /* APIEndpoints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04EA110D2B2502660024CDDD /* APIEndpoints.swift */; };
Expand Down Expand Up @@ -85,6 +85,7 @@
04F4B95E2B0990CE009442B5 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 04F4B95D2B0990CE009442B5 /* KeychainSwift */; };
04F4B9612B0996EC009442B5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F4B9602B0996EC009442B5 /* Logger.swift */; };
04F4B9632B0998F6009442B5 /* ClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F4B9622B0998F6009442B5 /* ClientConfiguration.swift */; };
04FA3D632D02922700BF54E6 /* AcknowledgementRepositoryWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04FA3D622D02922700BF54E6 /* AcknowledgementRepositoryWebView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -147,6 +148,10 @@
04C7BC6F2B535E330091493C /* BikeStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BikeStatus.swift; sourceTree = "<group>"; };
04C7BC712B5361220091493C /* RegisterMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterMode.swift; sourceTree = "<group>"; };
04C7BC762B536C150091493C /* StolenRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StolenRecord.swift; sourceTree = "<group>"; };
04CA5AD92D013A5400BD572A /* Test-credentials.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Test-credentials.xcconfig"; sourceTree = "<group>"; };
04CA5ADC2D013A7200BD572A /* Test-credentials-template.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Test-credentials-template.xcconfig"; sourceTree = "<group>"; };
04CA5AE02D013BAA00BD572A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
04CA5AE22D014CC800BD572A /* AuthenticatedUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedUITestCase.swift; sourceTree = "<group>"; };
04EA110C2B2502660024CDDD /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = "<group>"; };
04EA110D2B2502660024CDDD /* APIEndpoints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIEndpoints.swift; sourceTree = "<group>"; };
04EA11122B2502AF0024CDDD /* AuthenticatedUserResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticatedUserResponse.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -192,6 +197,7 @@
04F4B95A2B098C3C009442B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
04F4B9602B0996EC009442B5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
04F4B9622B0998F6009442B5 /* ClientConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConfiguration.swift; sourceTree = "<group>"; };
04FA3D622D02922700BF54E6 /* AcknowledgementRepositoryWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementRepositoryWebView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -250,6 +256,7 @@
0452C6B22B4A630500AD572C /* DebugMenu.swift */,
0452C6AF2B4A608E00AD572C /* AcknowledgementsListView.swift */,
0452C6B92B4A72FD00AD572C /* AcknowledgementPackageDetailView.swift */,
04FA3D622D02922700BF54E6 /* AcknowledgementRepositoryWebView.swift */,
);
path = Settings;
sourceTree = "<group>";
Expand Down Expand Up @@ -283,6 +290,8 @@
isa = PBXGroup;
children = (
046B0A3C2B251E91006519A7 /* Logger+Tests.swift */,
04CA5AD92D013A5400BD572A /* Test-credentials.xcconfig */,
04CA5ADC2D013A7200BD572A /* Test-credentials-template.xcconfig */,
);
path = SharedTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -432,7 +441,9 @@
04F4B9172B098ADE009442B5 /* BikeIndexUITests */ = {
isa = PBXGroup;
children = (
04CA5AE02D013BAA00BD572A /* Info.plist */,
04F4B9182B098ADE009442B5 /* BikeIndexUITests.swift */,
04CA5AE22D014CC800BD572A /* AuthenticatedUITestCase.swift */,
040FA32D2BCCB7F5000C4B18 /* ManufacturerKeyboardUITestCase.swift */,
04F4B91A2B098ADE009442B5 /* BikeIndexUITestsLaunchTests.swift */,
);
Expand Down Expand Up @@ -614,7 +625,6 @@
buildActionMask = 2147483647;
files = (
04F4B9012B098ADE009442B5 /* Assets.xcassets in Resources */,
042724CA2B85714D008BEDBC /* SingleBikeResponse_mock.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -659,6 +669,7 @@
04F2C63A2B4B00CD009E0A37 /* BikeDetailView.swift in Sources */,
0490A5F12B269F7800C4EC1E /* AddBikeOutput.swift in Sources */,
04F4B9392B098B9C009442B5 /* Client.swift in Sources */,
04FA3D632D02922700BF54E6 /* AcknowledgementRepositoryWebView.swift in Sources */,
04C7BC772B536C150091493C /* StolenRecord.swift in Sources */,
043033362B40C34800FD126F /* ContentButtonView.swift in Sources */,
046B0A372B251E58006519A7 /* AppIconPicker.swift in Sources */,
Expand Down Expand Up @@ -724,6 +735,7 @@
files = (
04F4B91B2B098ADE009442B5 /* BikeIndexUITestsLaunchTests.swift in Sources */,
0485E9C72B9E2FA70071C48B /* Logger+Tests.swift in Sources */,
04CA5AE32D014CC800BD572A /* AuthenticatedUITestCase.swift in Sources */,
04F4B9192B098ADE009442B5 /* BikeIndexUITests.swift in Sources */,
040FA32E2BCCB7F5000C4B18 /* ManufacturerKeyboardUITestCase.swift in Sources */,
);
Expand Down Expand Up @@ -873,11 +885,13 @@
};
04EA11262B25135D0024CDDD /* Debug BikeIndex.org */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 04CA5AD92D013A5400BD572A /* Test-credentials.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BikeIndexUITests/Info.plist;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.bikeindex.BikeIndexUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -1137,11 +1151,13 @@
};
04F4B9252B098ADE009442B5 /* Debug (development) */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 04CA5AD92D013A5400BD572A /* Test-credentials.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BikeIndexUITests/Info.plist;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.bikeindex.BikeIndexUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -1157,11 +1173,13 @@
};
04F4B9262B098ADE009442B5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 04CA5AD92D013A5400BD572A /* Test-credentials.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BikeIndexUITests/Info.plist;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.bikeindex.BikeIndexUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
12 changes: 6 additions & 6 deletions BikeIndex/Model/Settings/AcknowledgementPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct AcknowledgementPackage: Identifiable, Hashable {
let title: String
let license: License
let copyright: String
let repository: URL?
let repository: URL

func fullLicense() -> String {
license.with(copyright: copyright)
Expand All @@ -26,25 +26,25 @@ extension AcknowledgementPackage {
AcknowledgementPackage(title: "Bike Index",
license: .gnuAfferoGPLv3,
copyright: "2023 © Bike Index, a 501(c)(3) nonprofit - EIN 81-4296194",
repository: URL(string: "https://github.com/bikeindex/bike_index")),
repository: URL(string: "https://github.com/bikeindex/bike_index")!),
AcknowledgementPackage(title: "Bike Index iOS",
license: .gnuAfferoGPLv3,
copyright: "2023 © Bike Index, a 501(c)(3) nonprofit - EIN 81-4296194",
repository: URL(string: "https://github.com/bikeindex/bike_index_ios")),
repository: URL(string: "https://github.com/bikeindex/bike_index_ios")!),

// MARK: MIT
AcknowledgementPackage(title: "KeychainSwift",
license: .mit,
copyright: "Copyright © 2015 - 2021 Evgenii Neumerzhitckii",
repository: URL(string: "https://github.com/evgenyneu/keychain-swift")),
repository: URL(string: "https://github.com/evgenyneu/keychain-swift")!),
AcknowledgementPackage(title: "URLEncodedForm",
license: .mit,
copyright: "Copyright © 2023 Scott Moon",
repository: URL(string: "https://github.com/forXifLess/URLEncodedForm")),
repository: URL(string: "https://github.com/forXifLess/URLEncodedForm")!),
AcknowledgementPackage(title: "WebViewKit",
license: .mit,
copyright: "Copyright © 2022 Daniel Saidi",
repository: URL(string: "https://github.com/danielsaidi/WebViewKit")),
repository: URL(string: "https://github.com/danielsaidi/WebViewKit")!),
]

static var gnuAfferoGPLv3Packages: [AcknowledgementPackage] {
Expand Down
2 changes: 1 addition & 1 deletion BikeIndex/View/MainContent/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct ContentView: View {
case .lostBike:
RegisterBikeView(mode: .myStolenBike)
case .searchBikes:
NavigableWebView(url: URL(string: "https://bikeindex.org/bikes?stolenness=all"))
NavigableWebView(url: .constant(URL(string: "https://bikeindex.org/bikes?stolenness=all")!))
.environment(client)
}
}
Expand Down
2 changes: 1 addition & 1 deletion BikeIndex/View/Onboarding/AuthView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ struct AuthView: View {
}
.sheet(isPresented: $displaySignIn, content: {
NavigationStack {
NavigableWebView(url: oAuthUrl,
NavigableWebView(url: .constant(oAuthUrl!),
navigator: HistoryNavigator(child: authNavigationDelegate))
.environment(client)
.navigationTitle("Sign in")
Expand Down
20 changes: 13 additions & 7 deletions BikeIndex/View/Registering/RegisterBikeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct RegisterBikeView: View {
// MARK: UI State

/// Used for serial\_page link
@State var link: URL?
@State var showSerialsPage = false

// MARK: Authoritative State

Expand Down Expand Up @@ -80,8 +80,11 @@ struct RegisterBikeView: View {
if mode == .myStolenBike {
Section {
NavigationLink {
NavigableWebView(url: BikeIndexLink.stolenBikeFAQ.link(base: client.configuration.host))
.environment(client)
NavigableWebView(
constantLink: .stolenBikeFAQ,
host: client.configuration.host
)
.environment(client)
} label: {
Text("⚠️ How to get your stolen bike back")
}
Expand Down Expand Up @@ -126,7 +129,7 @@ struct RegisterBikeView: View {
} footer: {
TextLink(base: client.configuration.host, link: .serials)
.environment(\.openURL, OpenURLAction { URL in
link = URL
showSerialsPage = true
return .handled
})
}
Expand Down Expand Up @@ -279,9 +282,12 @@ struct RegisterBikeView: View {
Logger.views.info("Failed to find authenticated users with email, skiping association of ownerEmail. Authenticated users has count \(authenticatedUsers.count)")
}
}
.navigationDestination(item: $link) { url in
NavigableWebView(url: url)
.environment(client)
.navigationDestination(isPresented: $showSerialsPage) {
NavigableWebView(
constantLink: .serials,
host: client.configuration.host
)
.environment(client)
}
}

Expand Down
12 changes: 6 additions & 6 deletions BikeIndex/View/Settings/AcknowledgementPackageDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import SwiftUI

struct AcknowledgementPackageDetailView: View {
let package: AcknowledgementPackage
@Binding var url: URL?
@Binding var showRepositoryUrl: Bool

var body: some View {
Text(package.fullLicense())
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button(action: {
url = package.repository
showRepositoryUrl = true
}, label: {
Label("Open Repository", systemImage: "link")
})
Expand All @@ -30,13 +30,13 @@ struct AcknowledgementPackageDetailView: View {
let package = AcknowledgementPackage(title: "BikeIndex iOS",
license: .gnuAfferoGPLv3,
copyright: "2023 © Bike Index, a 501(c)(3) nonprofit - EIN 81-4296194",
repository: URL(string: "https://github.com/bikeindex/bike_index_ios"))
let binding: Binding<URL?> = Binding {
package.repository
repository: URL(string: "https://github.com/bikeindex/bike_index_ios")!)
let showUrl: Binding<Bool> = Binding {
false
} set: { _ in }

return NavigationStack {
AcknowledgementPackageDetailView(package: package, url: binding)
AcknowledgementPackageDetailView(package: package, showRepositoryUrl: showUrl)
.navigationTitle("BikeIndex iOS")
.navigationBarTitleDisplayMode(.inline)
}
Expand Down
27 changes: 27 additions & 0 deletions BikeIndex/View/Settings/AcknowledgementRepositoryWebView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// AcknowledgementRepositoryWebView.swift
// BikeIndex
//
// Created by Jack on 12/5/24.
//

import SwiftUI

// TODO: Evaluate generalizing this with ``BikeDetailView`` (although BikeDetailView is intended to grow in complexity.
struct AcknowledgementRepositoryWebView: View {
@Environment(Client.self) var client

var package: AcknowledgementPackage
@State private var url: URL

init(package: AcknowledgementPackage) {
self.package = package
self._url = State(initialValue: package.repository)
}

var body: some View {
NavigableWebView(url: $url)
.environment(client)
.navigationTitle(package.title)
}
}
14 changes: 8 additions & 6 deletions BikeIndex/View/Settings/AcknowledgementsListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@ import WebViewKit
struct AcknowledgementListItemView: View {
@Environment(Client.self) private var client
var package: AcknowledgementPackage
@State private var repositoryUrl: URL?
@State private var showRepositoryUrl = false

var body: some View {
NavigationLink {
ScrollView {
AcknowledgementPackageDetailView(package: package, url: $repositoryUrl)
.navigationBarTitleDisplayMode(.inline)
AcknowledgementPackageDetailView(
package: package,
showRepositoryUrl: $showRepositoryUrl
)
.navigationBarTitleDisplayMode(.inline)
}
.navigationDestination(item: $repositoryUrl) { url in
NavigableWebView(url: url)
.environment(client)
.navigationDestination(isPresented: $showRepositoryUrl) {
AcknowledgementRepositoryWebView(package: package)
}
} label: {
VStack(alignment: .leading) {
Expand Down
Loading

0 comments on commit 0d58425

Please sign in to comment.