Skip to content

Commit 42c23d4

Browse files
authored
Merge pull request #564 from synonymdev/ovi/trezor-ai-device-tests
test: add Trezor emulator tests
2 parents eaa1ef5 + 514bbe9 commit 42c23d4

27 files changed

Lines changed: 1200 additions & 26 deletions
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
name: ai-device-tests
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
suite:
7+
description: "AI device test suite to run"
8+
required: true
9+
default: "trezor-emu"
10+
type: choice
11+
options:
12+
- trezor-emu
13+
simulator_name:
14+
description: "iOS Simulator name"
15+
required: false
16+
default: "iPhone 17"
17+
simulator_os:
18+
description: "iOS Simulator OS version"
19+
required: false
20+
default: "26.2"
21+
22+
env:
23+
TERM: xterm-256color
24+
FORCE_COLOR: 1
25+
26+
concurrency:
27+
group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.suite }}
28+
cancel-in-progress: true
29+
30+
jobs:
31+
trezor-emu:
32+
if: inputs.suite == 'trezor-emu'
33+
name: Trezor emulator dashboard
34+
runs-on: [self-hosted, macOS]
35+
timeout-minutes: 90
36+
37+
steps:
38+
- name: Checkout Bitkit iOS
39+
uses: actions/checkout@v6
40+
41+
- name: Checkout bitkit-docker
42+
uses: actions/checkout@v6
43+
with:
44+
repository: synonymdev/bitkit-docker
45+
ref: main
46+
path: bitkit-docker
47+
48+
- name: Install xcbeautify
49+
run: |
50+
if ! command -v xcbeautify >/dev/null 2>&1; then
51+
brew install xcbeautify
52+
fi
53+
54+
- name: System information
55+
run: |
56+
sw_vers
57+
xcodebuild -version
58+
docker version
59+
docker compose version
60+
61+
- name: Resolve Swift packages
62+
run: |
63+
xcodebuild -resolvePackageDependencies -onlyUsePackageVersionsFromResolvedFile | xcbeautify
64+
65+
- name: Start regtest and Trezor emulator
66+
working-directory: bitkit-docker
67+
run: |
68+
docker compose up -d
69+
./scripts/trezor-emulator start
70+
./scripts/trezor-emulator status
71+
72+
- name: Boot simulator
73+
env:
74+
SIMULATOR_NAME: ${{ inputs.simulator_name }}
75+
run: |
76+
xcrun simctl boot "$SIMULATOR_NAME" || true
77+
78+
- name: Run Trezor emulator UI tests
79+
env:
80+
TEST_TREZOR_EMU: "1"
81+
TEST_TREZOR_RESET_STATE: "1"
82+
TREZOR_BRIDGE: "true"
83+
TREZOR_BRIDGE_URL: "http://127.0.0.1:21325"
84+
TREZOR_ELECTRUM_URL: "tcp://127.0.0.1:60001"
85+
E2E: "true"
86+
E2E_BACKEND: "local"
87+
E2E_NETWORK: "regtest"
88+
GEO: "false"
89+
SIMULATOR_NAME: ${{ inputs.simulator_name }}
90+
SIMULATOR_OS: ${{ inputs.simulator_os }}
91+
run: |
92+
mkdir -p TestResults
93+
set -o pipefail
94+
xcodebuild test \
95+
-workspace Bitkit.xcodeproj/project.xcworkspace \
96+
-scheme BitkitAITests \
97+
-configuration Debug \
98+
-destination "platform=iOS Simulator,name=$SIMULATOR_NAME,OS=$SIMULATOR_OS" \
99+
-derivedDataPath DerivedData \
100+
-resultBundlePath TestResults/TrezorBridgeDashboardUITests.xcresult \
101+
SWIFT_ACTIVE_COMPILATION_CONDITIONS='DEBUG E2E_BUILD TEST_TREZOR_EMU' \
102+
-only-testing:BitkitUITests/TrezorBridgeDashboardUITests \
103+
-parallel-testing-enabled NO \
104+
| xcbeautify
105+
106+
- name: Collect diagnostics
107+
if: always()
108+
run: |
109+
mkdir -p ai-device-artifacts/trezor
110+
xcrun simctl io booted screenshot ai-device-artifacts/trezor/simulator.png || true
111+
if [ -d TestResults ]; then
112+
cp -R TestResults ai-device-artifacts/trezor/ || true
113+
fi
114+
(
115+
cd bitkit-docker
116+
./scripts/trezor-emulator status > ../ai-device-artifacts/trezor/trezor-status.json 2>&1 || true
117+
docker compose logs --no-color --tail=500 trezor-user-env-mac > ../ai-device-artifacts/trezor/trezor-user-env.log 2>&1 || true
118+
docker compose logs --no-color > ../ai-device-artifacts/trezor/docker-compose.log 2>&1 || true
119+
curl --silent --show-error -X POST http://127.0.0.1:21325/enumerate > ../ai-device-artifacts/trezor/bridge-enumerate.json 2>&1 || true
120+
)
121+
122+
- name: Upload diagnostics
123+
if: always()
124+
uses: actions/upload-artifact@v6
125+
with:
126+
name: ai-device-tests-trezor-${{ github.run_number }}
127+
path: ai-device-artifacts/trezor
128+
if-no-files-found: warn
129+
130+
- name: Stop emulator services
131+
if: always()
132+
working-directory: bitkit-docker
133+
run: |
134+
./scripts/trezor-emulator stop || true
135+
docker compose down || true

Bitkit.xcodeproj/project.pbxproj

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,23 +563,24 @@
563563
/* Begin PBXShellScriptBuildPhase section */
564564
96EMBED0012026012000FRAME /* Remove Static Framework Stubs */ = {
565565
isa = PBXShellScriptBuildPhase;
566+
alwaysOutOfDate = 1;
566567
buildActionMask = 2147483647;
567568
files = (
568569
);
569570
inputFileListPaths = (
570571
);
571572
inputPaths = (
572-
"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Frameworks/LDKNodeFFI.framework",
573+
"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/Frameworks/LDKNodeFFI.framework/_CodeSignature/CodeResources",
573574
);
574575
name = "Remove Static Framework Stubs";
575576
outputFileListPaths = (
576577
);
577578
outputPaths = (
578-
"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/.ldk-stubs-removed",
579+
"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/.ldk-stubs-removed",
579580
);
580581
runOnlyForDeploymentPostprocessing = 0;
581582
shellPath = /bin/sh;
582-
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";
583+
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";
583584
};
584585
/* End PBXShellScriptBuildPhase section */
585586

@@ -929,6 +930,7 @@
929930
DEVELOPMENT_TEAM = KYH47R284B;
930931
ENABLE_HARDENED_RUNTIME = YES;
931932
ENABLE_PREVIEWS = YES;
933+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
932934
GENERATE_INFOPLIST_FILE = YES;
933935
INFOPLIST_FILE = Bitkit/Info.plist;
934936
INFOPLIST_KEY_CFBundleDisplayName = Bitkit;
@@ -977,6 +979,7 @@
977979
DEVELOPMENT_TEAM = KYH47R284B;
978980
ENABLE_HARDENED_RUNTIME = YES;
979981
ENABLE_PREVIEWS = YES;
982+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
980983
GENERATE_INFOPLIST_FILE = YES;
981984
INFOPLIST_FILE = Bitkit/Info.plist;
982985
INFOPLIST_KEY_CFBundleDisplayName = Bitkit;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1540"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
9+
<BuildActionEntries>
10+
<BuildActionEntry
11+
buildForTesting = "YES"
12+
buildForRunning = "YES"
13+
buildForProfiling = "YES"
14+
buildForArchiving = "NO"
15+
buildForAnalyzing = "NO">
16+
<BuildableReference
17+
BuildableIdentifier = "primary"
18+
BlueprintIdentifier = "96FE1F602C2DE6AA006D0C8B"
19+
BuildableName = "Bitkit.app"
20+
BlueprintName = "Bitkit"
21+
ReferencedContainer = "container:Bitkit.xcodeproj">
22+
</BuildableReference>
23+
</BuildActionEntry>
24+
</BuildActionEntries>
25+
</BuildAction>
26+
<TestAction
27+
buildConfiguration = "Debug"
28+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
29+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30+
shouldUseLaunchSchemeArgsEnv = "YES"
31+
shouldAutocreateTestPlan = "YES">
32+
<Testables>
33+
<TestableReference
34+
skipped = "NO"
35+
parallelizable = "NO">
36+
<BuildableReference
37+
BuildableIdentifier = "primary"
38+
BlueprintIdentifier = "96FE1F7B2C2DE6AC006D0C8B"
39+
BuildableName = "BitkitUITests.xctest"
40+
BlueprintName = "BitkitUITests"
41+
ReferencedContainer = "container:Bitkit.xcodeproj">
42+
</BuildableReference>
43+
</TestableReference>
44+
</Testables>
45+
</TestAction>
46+
<LaunchAction
47+
buildConfiguration = "Debug"
48+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
49+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
50+
launchStyle = "0"
51+
useCustomWorkingDirectory = "NO"
52+
ignoresPersistentStateOnLaunch = "NO"
53+
debugDocumentVersioning = "YES"
54+
debugServiceExtension = "internal"
55+
allowLocationSimulation = "YES">
56+
<BuildableProductRunnable
57+
runnableDebuggingMode = "0">
58+
<BuildableReference
59+
BuildableIdentifier = "primary"
60+
BlueprintIdentifier = "96FE1F602C2DE6AA006D0C8B"
61+
BuildableName = "Bitkit.app"
62+
BlueprintName = "Bitkit"
63+
ReferencedContainer = "container:Bitkit.xcodeproj">
64+
</BuildableReference>
65+
</BuildableProductRunnable>
66+
</LaunchAction>
67+
<ProfileAction
68+
buildConfiguration = "Release"
69+
shouldUseLaunchSchemeArgsEnv = "YES"
70+
savedToolIdentifier = ""
71+
useCustomWorkingDirectory = "NO"
72+
debugDocumentVersioning = "YES">
73+
<BuildableProductRunnable
74+
runnableDebuggingMode = "0">
75+
<BuildableReference
76+
BuildableIdentifier = "primary"
77+
BlueprintIdentifier = "96FE1F602C2DE6AA006D0C8B"
78+
BuildableName = "Bitkit.app"
79+
BlueprintName = "Bitkit"
80+
ReferencedContainer = "container:Bitkit.xcodeproj">
81+
</BuildableReference>
82+
</BuildableProductRunnable>
83+
</ProfileAction>
84+
<AnalyzeAction
85+
buildConfiguration = "Debug">
86+
</AnalyzeAction>
87+
<ArchiveAction
88+
buildConfiguration = "Release"
89+
revealArchiveInOrganizer = "YES">
90+
</ArchiveAction>
91+
</Scheme>

Bitkit/AppScene.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,9 @@ struct AppScene: View {
206206

207207
private var mainContent: some View {
208208
ZStack {
209-
if migrations.isShowingMigrationLoading {
209+
if Env.isTrezorEmulatorTesting {
210+
trezorEmulatorTestContent
211+
} else if migrations.isShowingMigrationLoading {
210212
migrationLoadingContent
211213
} else if showRecoveryScreen {
212214
RecoveryRouter()
@@ -217,7 +219,7 @@ struct AppScene: View {
217219
walletContent
218220
}
219221

220-
if !removeSplash && !session.skipSplashOnce {
222+
if !Env.isTrezorEmulatorTesting, !removeSplash, !session.skipSplashOnce {
221223
SplashView()
222224
.opacity(hideSplash ? 0 : 1)
223225
}
@@ -279,6 +281,13 @@ struct AppScene: View {
279281
}
280282
}
281283

284+
private var trezorEmulatorTestContent: some View {
285+
NavigationStack {
286+
TrezorRootView()
287+
}
288+
.accentColor(.white)
289+
}
290+
282291
@ViewBuilder
283292
private var existingWalletContent: some View {
284293
if walletIsInitializing == true {

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct TrezorDeviceRow: View {
5555
}
5656
.buttonStyle(.plain)
5757
.disabled(isConnecting)
58+
.accessibilityIdentifier(device.path.hasPrefix("bridge:") ? "TrezorDevice-bridge" : "TrezorDevice-\(device.path)")
5859
}
5960

6061
private var displayName: String {
@@ -137,6 +138,7 @@ struct KnownDeviceRow: View {
137138
}
138139
.buttonStyle(.plain)
139140
.disabled(isConnecting)
141+
.accessibilityIdentifier("TrezorKnownDeviceConnect-\(accessibilitySuffix)")
140142

141143
// Forget button
142144
Button(action: onForget) {
@@ -146,10 +148,16 @@ struct KnownDeviceRow: View {
146148
.padding(10)
147149
}
148150
.buttonStyle(.plain)
151+
.accessibilityIdentifier("TrezorForgetDevice-\(accessibilitySuffix)")
149152
}
150153
.padding(16)
151154
.background(Color.white.opacity(0.05))
152155
.clipShape(RoundedRectangle(cornerRadius: 16))
156+
.trezorAccessibilityAnchor("TrezorKnownDevice-\(accessibilitySuffix)")
157+
}
158+
159+
private var accessibilitySuffix: String {
160+
device.path.hasPrefix("bridge:") ? "bridge" : device.id
153161
}
154162
}
155163

Bitkit/Components/Trezor/TrezorExpandableSection.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ struct TrezorExpandableSection<Content: View>: View {
66
let title: String
77
let icon: String
88
let description: String
9+
var accessibilityIdentifier: String?
910
@Binding var isExpanded: Bool
1011
@ViewBuilder let content: () -> Content
1112

@@ -44,6 +45,7 @@ struct TrezorExpandableSection<Content: View>: View {
4445
.animation(.easeInOut(duration: 0.25), value: isExpanded)
4546
}
4647
}
48+
.accessibilityIdentifier(accessibilityIdentifier ?? "TrezorSection-\(title.replacingOccurrences(of: " ", with: ""))")
4749

4850
// Expandable content
4951
if isExpanded {

Bitkit/Components/Trezor/TrezorPairingCodeInput.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct TrezorPairingCodeInput: View {
3030
.focused($isFocused)
3131
.frame(width: 1, height: 1)
3232
.opacity(0.01) // Nearly invisible but still functional
33+
.accessibilityIdentifier("TrezorPairingCodeInput")
3334
.onChange(of: code) { newValue in
3435
// Filter to only digits and limit length
3536
let filtered = newValue.filter(\.isNumber)
@@ -48,6 +49,7 @@ struct TrezorPairingCodeInput: View {
4849
try? await Task.sleep(nanoseconds: 200_000_000)
4950
isFocused = true
5051
}
52+
.accessibilityIdentifier("TrezorPairingCodeDisplay")
5153
}
5254

5355
/// Get digit at specific index, or nil if not entered yet

0 commit comments

Comments
 (0)