diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 8d5e7992..e3abd3e3 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -108,7 +108,7 @@ jobs: # - { name: onchain_boost_receive_widgets, grep: "@onchain|@boost|@receive|@widgets" } # - { name: settings, grep: "@settings" } # - { name: security, grep: "@security" } - - { name: e2e, grep: '^(?!.*@settings_10)(@backup|@onboarding|@onchain_1|@onchain_2|@numberpad|@widgets|@boost|@receive|@settings|@security)' } + - { name: e2e, grep: '@backup|@onboarding|@onchain_1|@onchain_2|@numberpad|@widgets|@boost|@receive|@settings|@security' } name: e2e-tests - ${{ matrix.shard.name }} @@ -175,7 +175,7 @@ jobs: working-directory: bitkit-e2e-tests run: | echo "Waiting for Electrum server..." - while ! nc -z '127.0.0.1' 60001; do + while ! nc -z '127.0.0.1' 60001; do echo "Electrum server not ready, waiting..." sleep 5 done @@ -183,7 +183,7 @@ jobs: - name: Clear previous E2E artifacts working-directory: bitkit-e2e-tests - run: | + run: | rm -rf artifacts/ rm -rf /tmp/lock/ @@ -249,4 +249,4 @@ jobs: echo "❌ Some E2E shards failed." exit 1 fi - echo "✅ All E2E shards passed!" \ No newline at end of file + echo "✅ All E2E shards passed!" diff --git a/Bitkit/Components/TabBar/ScanButton.swift b/Bitkit/Components/TabBar/ScanButton.swift index c4674086..cfee6891 100644 --- a/Bitkit/Components/TabBar/ScanButton.swift +++ b/Bitkit/Components/TabBar/ScanButton.swift @@ -20,6 +20,7 @@ struct ScanButton: View { .background(Circle().fill(background)) .foregroundColor(.gray1) } + .accessibilityIdentifier("Scan") .shadow(color: Color.gray2, radius: 0, x: 0, y: -1) .shadow(color: Color.black.opacity(0.25), radius: 25, x: 0, y: 20) .overlay( diff --git a/Bitkit/Managers/ScannerManager.swift b/Bitkit/Managers/ScannerManager.swift index 137949f2..177aa315 100644 --- a/Bitkit/Managers/ScannerManager.swift +++ b/Bitkit/Managers/ScannerManager.swift @@ -233,4 +233,18 @@ class ScannerManager: ObservableObject { ) } } + + func handleManualEntry( + _ value: String, + context: ScannerContext, + onSuccess: @MainActor () -> Void + ) async { + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return } + + await handleScan(trimmed, context: context) + await MainActor.run { + onSuccess() + } + } } diff --git a/Bitkit/Views/Scanner/ManualScanPrompt.swift b/Bitkit/Views/Scanner/ManualScanPrompt.swift new file mode 100644 index 00000000..d7d06885 --- /dev/null +++ b/Bitkit/Views/Scanner/ManualScanPrompt.swift @@ -0,0 +1,41 @@ +import SwiftUI + +struct ScannerManualEntryPrompt: View { + @Binding var text: String + var onSubmit: () -> Void + var onCancel: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + HStack { + Spacer() + Button(action: onCancel) { + Image("x-mark") + .resizable() + .frame(width: 16, height: 16) + } + .accessibilityIdentifier("DialogCancel") + } + + BodyMSBText("Enter QR Code String") + + SwiftUI.TextField("", text: $text) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .padding() + .background(Color.white10) + .cornerRadius(10) + .accessibilityIdentifier("QRInput") + + CustomButton(title: t("common__yes_proceed"), shouldExpand: true) { + onSubmit() + } + .accessibilityIdentifier("DialogConfirm") + + Spacer() + } + .padding(16) + .accessibilityElement(children: .contain) + .accessibilityIdentifier("QRDialog") + } +} diff --git a/Bitkit/Views/Scanner/ScannerScreen.swift b/Bitkit/Views/Scanner/ScannerScreen.swift index 453268ab..1a660905 100644 --- a/Bitkit/Views/Scanner/ScannerScreen.swift +++ b/Bitkit/Views/Scanner/ScannerScreen.swift @@ -7,6 +7,8 @@ struct ScannerScreen: View { @EnvironmentObject private var scanner: ScannerManager @EnvironmentObject private var settings: SettingsViewModel @EnvironmentObject private var sheets: SheetViewModel + @State private var isManualEntryPresented = false + @State private var manualEntry = "" private var scannerContext: ScannerContext { if navigation.path.contains(.electrumSettings) { @@ -39,6 +41,19 @@ struct ScannerScreen: View { ) { await scanner.handlePaste(context: scannerContext) } + .padding(.bottom, Env.isE2E ? 12 : 0) + + if Env.isE2E { + CustomButton( + title: "Enter QR Code String", + variant: .secondary, + shouldExpand: true + ) { + manualEntry = "" + isManualEntryPresented = true + } + .accessibilityIdentifier("ScanPrompt") + } } .navigationBarHidden(true) .padding(.horizontal, 16) @@ -52,5 +67,27 @@ struct ScannerScreen: View { sheets: sheets ) } + .sheet(isPresented: $isManualEntryPresented) { + ScannerManualEntryPrompt( + text: $manualEntry, + onSubmit: { + Task { + await handleManualEntrySubmit() + } + }, + onCancel: { + isManualEntryPresented = false + } + ) + .presentationDetents([.fraction(0.35)]) + .presentationDragIndicator(.visible) + } + } + + private func handleManualEntrySubmit() async { + await scanner.handleManualEntry(manualEntry, context: scannerContext) { + isManualEntryPresented = false + manualEntry = "" + } } } diff --git a/Bitkit/Views/Scanner/ScannerSheet.swift b/Bitkit/Views/Scanner/ScannerSheet.swift index 1bac389d..511287b8 100644 --- a/Bitkit/Views/Scanner/ScannerSheet.swift +++ b/Bitkit/Views/Scanner/ScannerSheet.swift @@ -12,6 +12,8 @@ struct ScannerSheet: View { @EnvironmentObject private var scanner: ScannerManager @EnvironmentObject private var settings: SettingsViewModel @EnvironmentObject private var sheets: SheetViewModel + @State private var isManualEntryPresented = false + @State private var manualEntry = "" let config: ScannerSheetItem @@ -40,6 +42,19 @@ struct ScannerSheet: View { ) { await scanner.handlePaste(context: .main) } + + if Env.isE2E { + CustomButton( + title: "Enter QRCode String", + variant: .secondary, + shouldExpand: true + ) { + manualEntry = "" + isManualEntryPresented = true + } + .padding(.top, 12) + .accessibilityIdentifier("ScanPrompt") + } } } .navigationBarHidden(false) @@ -54,6 +69,28 @@ struct ScannerSheet: View { sheets: sheets ) } + .sheet(isPresented: $isManualEntryPresented) { + ScannerManualEntryPrompt( + text: $manualEntry, + onSubmit: { + Task { + await handleManualEntrySubmit() + } + }, + onCancel: { + isManualEntryPresented = false + } + ) + .presentationDetents([.fraction(0.35)]) + .presentationDragIndicator(.visible) + } + } + } + + private func handleManualEntrySubmit() async { + await scanner.handleManualEntry(manualEntry, context: .main) { + isManualEntryPresented = false + manualEntry = "" } } }