Skip to content

Commit 2e3c9a1

Browse files
committed
test: stabilize Trezor emulator UI suite
1 parent 7aebd35 commit 2e3c9a1

22 files changed

Lines changed: 352 additions & 92 deletions

.github/workflows/ai-device-tests.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ jobs:
7878
- name: Run Trezor emulator UI tests
7979
env:
8080
TEST_TREZOR_EMU: "1"
81+
TEST_TREZOR_RESET_STATE: "1"
8182
TREZOR_BRIDGE: "true"
8283
TREZOR_BRIDGE_URL: "http://127.0.0.1:21325"
8384
TREZOR_ELECTRUM_URL: "tcp://127.0.0.1:60001"
@@ -97,7 +98,7 @@ jobs:
9798
-destination "platform=iOS Simulator,name=$SIMULATOR_NAME,OS=$SIMULATOR_OS" \
9899
-derivedDataPath DerivedData \
99100
-resultBundlePath TestResults/TrezorBridgeDashboardUITests.xcresult \
100-
SWIFT_ACTIVE_COMPILATION_CONDITIONS='DEBUG E2E_BUILD' \
101+
SWIFT_ACTIVE_COMPILATION_CONDITIONS='DEBUG E2E_BUILD TEST_TREZOR_EMU' \
101102
-only-testing:BitkitUITests/TrezorBridgeDashboardUITests \
102103
-parallel-testing-enabled NO \
103104
| xcbeautify

Bitkit.xcodeproj/project.pbxproj

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,23 +434,24 @@
434434
/* Begin PBXShellScriptBuildPhase section */
435435
96EMBED0012026012000FRAME /* Remove Static Framework Stubs */ = {
436436
isa = PBXShellScriptBuildPhase;
437+
alwaysOutOfDate = 1;
437438
buildActionMask = 2147483647;
438439
files = (
439440
);
440441
inputFileListPaths = (
441442
);
442443
inputPaths = (
443-
"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Frameworks/LDKNodeFFI.framework",
444+
"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/Frameworks/LDKNodeFFI.framework/_CodeSignature/CodeResources",
444445
);
445446
name = "Remove Static Framework Stubs";
446447
outputFileListPaths = (
447448
);
448449
outputPaths = (
449-
"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/.ldk-stubs-removed",
450+
"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/.ldk-stubs-removed",
450451
);
451452
runOnlyForDeploymentPostprocessing = 0;
452453
shellPath = /bin/sh;
453-
shellScript = "# Remove static framework stubs from app bundle\\n# LDKNodeFFI is a static library - its code is linked into the main executable.\\n# The empty framework structure causes iOS install errors.\\nFRAMEWORK_PATH=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Frameworks/LDKNodeFFI.framework\"\\nOUTPUT_SENTINEL=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/.ldk-stubs-removed\"\\n\\nif [ -d \"$FRAMEWORK_PATH\" ]; then\\n echo \"Removing LDKNodeFFI static framework stub...\"\\n rm -rf \"$FRAMEWORK_PATH\"\\n echo \"Done.\"\\nfi\\ntouch \"$OUTPUT_SENTINEL\"\\n";
454+
shellScript = "# Remove static framework stubs from app bundle\n# LDKNodeFFI is a static library - its code is linked into the main executable.\n# The empty framework structure causes iOS install errors.\nFRAMEWORK_PATH=\"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/Frameworks/LDKNodeFFI.framework\"\nOUTPUT_SENTINEL=\"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/.ldk-stubs-removed\"\n\nif [ -d \"$FRAMEWORK_PATH\" ]; then\n echo \"Removing LDKNodeFFI static framework stub...\"\n rm -rf \"$FRAMEWORK_PATH\"\n if [ -d \"$FRAMEWORK_PATH\" ]; then\n echo \"error: Failed to remove LDKNodeFFI static framework stub\" >&2\n exit 1\n fi\n echo \"Done.\"\nfi\ntouch \"$OUTPUT_SENTINEL\"\n";
454455
};
455456
/* End PBXShellScriptBuildPhase section */
456457

@@ -696,6 +697,7 @@
696697
DEVELOPMENT_TEAM = KYH47R284B;
697698
ENABLE_HARDENED_RUNTIME = YES;
698699
ENABLE_PREVIEWS = YES;
700+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
699701
GENERATE_INFOPLIST_FILE = YES;
700702
INFOPLIST_FILE = Bitkit/Info.plist;
701703
INFOPLIST_KEY_CFBundleDisplayName = Bitkit;
@@ -744,6 +746,7 @@
744746
DEVELOPMENT_TEAM = KYH47R284B;
745747
ENABLE_HARDENED_RUNTIME = YES;
746748
ENABLE_PREVIEWS = YES;
749+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
747750
GENERATE_INFOPLIST_FILE = YES;
748751
INFOPLIST_FILE = Bitkit/Info.plist;
749752
INFOPLIST_KEY_CFBundleDisplayName = Bitkit;

Bitkit/BitkitApp.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,16 @@ struct BitkitApp: App {
9696

9797
init() {
9898
UIWindow.appearance().overrideUserInterfaceStyle = .dark
99+
if Env.shouldResetTrezorEmulatorState {
100+
TrezorKnownDeviceStorage.removeAll()
101+
TrezorCredentialStorage.deleteAll()
102+
}
99103
_ = ToastWindowManager.shared
100104
}
101105

102106
var body: some Scene {
103107
WindowGroup {
104-
if Env.isUnitTest {
108+
if Env.isUnitTest, !Env.isTrezorEmulatorTesting {
105109
Text("Running tests...")
106110
} else {
107111
ContentView()

Bitkit/Components/Trezor/TrezorDeviceRow.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ struct KnownDeviceRow: View {
138138
}
139139
.buttonStyle(.plain)
140140
.disabled(isConnecting)
141+
.accessibilityIdentifier("TrezorKnownDeviceConnect-\(accessibilitySuffix)")
141142

142143
// Forget button
143144
Button(action: onForget) {
@@ -147,11 +148,16 @@ struct KnownDeviceRow: View {
147148
.padding(10)
148149
}
149150
.buttonStyle(.plain)
151+
.accessibilityIdentifier("TrezorForgetDevice-\(accessibilitySuffix)")
150152
}
151153
.padding(16)
152154
.background(Color.white.opacity(0.05))
153155
.clipShape(RoundedRectangle(cornerRadius: 16))
154-
.accessibilityIdentifier("TrezorKnownDevice-\(device.id)")
156+
.trezorAccessibilityAnchor("TrezorKnownDevice-\(accessibilitySuffix)")
157+
}
158+
159+
private var accessibilitySuffix: String {
160+
device.path.hasPrefix("bridge:") ? "bridge" : device.id
155161
}
156162
}
157163

Bitkit/Components/Trezor/TrezorStatusBadge.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ struct TrezorConfirmOnDeviceOverlay: View {
6969
.padding(32)
7070
.frame(maxWidth: .infinity, maxHeight: .infinity)
7171
.background(Color.black.opacity(0.9))
72-
.accessibilityIdentifier("TrezorConfirmOnDeviceOverlay")
72+
.trezorAccessibilityAnchor("TrezorConfirmOnDeviceOverlay")
7373
}
7474
}
7575

@@ -120,7 +120,7 @@ struct TrezorErrorBanner: View {
120120
.padding(16)
121121
.background(Color.red.opacity(0.1))
122122
.clipShape(RoundedRectangle(cornerRadius: 12))
123-
.accessibilityIdentifier("TrezorErrorBanner")
123+
.trezorAccessibilityAnchor("TrezorErrorBanner")
124124
}
125125
}
126126

Bitkit/Constants/Env.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,33 @@ enum Env {
3535
if envValue?.isEmpty == false {
3636
return envValue
3737
}
38+
if let argumentValue = launchArgumentValue(key) {
39+
return argumentValue
40+
}
3841
return infoPlistValue(key)
3942
}
4043

44+
private static func launchArgumentValue(_ key: String) -> String? {
45+
let arguments = ProcessInfo.processInfo.arguments
46+
for argument in arguments {
47+
for prefix in ["-\(key)=", "--\(key)=", "\(key)="] where argument.hasPrefix(prefix) {
48+
let value = String(argument.dropFirst(prefix.count)).trimmingCharacters(in: .whitespacesAndNewlines)
49+
return value.isEmpty ? nil : value
50+
}
51+
}
52+
53+
for (index, argument) in arguments.enumerated() where argument == "-\(key)" || argument == "--\(key)" || argument == key {
54+
let nextIndex = index + 1
55+
guard nextIndex < arguments.count else { return "true" }
56+
let value = arguments[nextIndex]
57+
guard !value.hasPrefix("-") else { return "true" }
58+
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
59+
return trimmed.isEmpty ? nil : trimmed
60+
}
61+
62+
return nil
63+
}
64+
4165
private static func boolConfigValue(_ key: String) -> Bool {
4266
guard let value = configValue(key)?.lowercased() else { return false }
4367
return ["1", "true", "yes", "y"].contains(value)
@@ -167,7 +191,15 @@ enum Env {
167191
}
168192

169193
static var isTrezorEmulatorTesting: Bool {
170-
(isDebug || isE2E) && boolConfigValue("TEST_TREZOR_EMU")
194+
#if TEST_TREZOR_EMU
195+
return isDebug || isE2E
196+
#else
197+
return (isDebug || isE2E) && boolConfigValue("TEST_TREZOR_EMU")
198+
#endif
199+
}
200+
201+
static var shouldResetTrezorEmulatorState: Bool {
202+
isTrezorEmulatorTesting && boolConfigValue("TEST_TREZOR_RESET_STATE")
171203
}
172204

173205
static var appStorageUrl: URL {

Bitkit/Services/Trezor/TrezorCredentialStorage.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ enum TrezorCredentialStorage {
9898
}
9999
}
100100

101+
/// Delete all stored THP credentials.
102+
static func deleteAll() {
103+
let query: [String: Any] = [
104+
kSecClass as String: kSecClassGenericPassword,
105+
kSecAttrService as String: serviceName,
106+
]
107+
108+
let status = SecItemDelete(query as CFDictionary)
109+
if status != errSecSuccess, status != errSecItemNotFound {
110+
Logger.warn("Failed to delete all THP credentials: \(status)", context: "TrezorCredentialStorage")
111+
}
112+
}
113+
101114
/// List all device IDs with stored credentials
102115
/// - Returns: Array of device IDs (sanitized form)
103116
static func listAllDeviceIds() -> [String] {

Bitkit/Services/Trezor/TrezorKnownDeviceStorage.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ enum TrezorKnownDeviceStorage {
4242
}
4343
}
4444

45+
/// Remove all remembered Trezor devices.
46+
static func removeAll() {
47+
UserDefaults.standard.removeObject(forKey: key)
48+
}
49+
4550
/// Check if a device is known
4651
static func isKnown(id: String) -> Bool {
4752
loadAll().contains { $0.id == id }

Bitkit/ViewModels/Trezor/TrezorViewModel.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ class TrezorViewModel {
118118
/// Status text during auto-reconnect
119119
var autoReconnectStatus: String?
120120

121+
/// Prevents a user-initiated disconnect from immediately reconnecting
122+
/// when the disconnected device list appears.
123+
private var suppressNextAutoReconnect = false
124+
121125
// MARK: - Address Index
122126

123127
/// Current address index (last path component)
@@ -386,6 +390,7 @@ class TrezorViewModel {
386390
/// Connect to a device
387391
func connect(device: TrezorDeviceInfo) async {
388392
error = nil
393+
suppressNextAutoReconnect = false
389394

390395
trezorLog("=== Connecting to device: \(device.path) ===")
391396

@@ -408,6 +413,7 @@ class TrezorViewModel {
408413
/// Disconnect from current device
409414
func disconnect() async {
410415
guard connectedDevice != nil else { return }
416+
suppressNextAutoReconnect = true
411417

412418
do {
413419
try await trezorService.disconnect()
@@ -640,6 +646,11 @@ class TrezorViewModel {
640646
func autoReconnect() async {
641647
guard !knownDevices.isEmpty else { return }
642648
guard !isAutoReconnecting else { return }
649+
if suppressNextAutoReconnect {
650+
suppressNextAutoReconnect = false
651+
trezorLog("Auto-reconnect: skipped after manual disconnect")
652+
return
653+
}
643654

644655
isAutoReconnecting = true
645656
autoReconnectStatus = "Scanning for known devices..."

Bitkit/Views/Trezor/TrezorAddressView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ private struct DerivationPathSection: View {
154154
.padding(12)
155155
.background(Color.white.opacity(0.05))
156156
.clipShape(RoundedRectangle(cornerRadius: 8))
157-
.accessibilityIdentifier("TrezorAddressIndex")
157+
.trezorAccessibilityAnchor("TrezorAddressIndex")
158158

159159
Button(action: {
160160
trezor.derivationPath = selectedScriptType.defaultPath(coinType: trezor.coinTypeComponent)
@@ -291,7 +291,7 @@ private struct GeneratedAddressCard: View {
291291
.frame(maxWidth: .infinity)
292292
.background(Color.white.opacity(0.05))
293293
.clipShape(RoundedRectangle(cornerRadius: 16))
294-
.accessibilityIdentifier("TrezorGeneratedAddressCard")
294+
.trezorAccessibilityAnchor("TrezorGeneratedAddressCard")
295295
}
296296
}
297297

0 commit comments

Comments
 (0)