-
Notifications
You must be signed in to change notification settings - Fork 53
test(swift-sdk): add testnet identity-discovery UI test #3560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v3.1-dev
Are you sure you want to change the base?
Changes from all commits
03af8e2
b3c85a2
7c0ae94
c08f9fa
52312a2
e8eb123
4e4cf1a
296d966
3823ee4
33b6f3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,20 @@ name: Swift Example App UI Smoke | |
|
|
||
| 'on': | ||
| workflow_dispatch: | ||
| schedule: | ||
| # 23:00 UTC daily. Aligns with the main `Tests` workflow's nightly | ||
| # cron (also 23:00 UTC) but runs on a different runner pool | ||
| # (self-hosted macOS), so they don't compete for compute. The Swift | ||
| # discovery test hits testnet DAPI; if that becomes a contention | ||
| # signal, shift to 02:00 UTC. | ||
| - cron: "0 23 * * *" | ||
|
|
||
| concurrency: | ||
| # Prevents an in-flight manual dispatch from being clobbered by the | ||
| # cron firing (or vice versa) on the single self-hosted Mac. Cancels | ||
| # the older run in favor of the newer one. | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
@@ -161,25 +175,41 @@ jobs: | |
| env: | ||
| SIM_UDID: ${{ steps.simulator.outputs.udid }} | ||
| RESULT_BUNDLE_PATH: ${{ runner.temp }}/SwiftExampleAppUITests.xcresult | ||
| # Forwarded into the XCUITest runner as `UI_TEST_MNEMONIC` — | ||
| # `xcodebuild test` strips the `TEST_RUNNER_` prefix before | ||
| # passing env vars through to the test process. Empty on PRs | ||
| # from forks (GitHub withholds secrets there), and the test | ||
| # self-skips when the value is empty. | ||
| TEST_RUNNER_UI_TEST_MNEMONIC: ${{ secrets.UI_TEST_MNEMONIC }} | ||
| run: | | ||
| set -euo pipefail | ||
| xcodebuild test \ | ||
| -project SwiftExampleApp/SwiftExampleApp.xcodeproj \ | ||
| -scheme SwiftExampleApp \ | ||
| -destination "platform=iOS Simulator,id=$SIM_UDID" \ | ||
| -only-testing:SwiftExampleAppUITests/SwiftExampleAppUITests/testCreateGeneratedWalletFlow \ | ||
| -only-testing:SwiftExampleAppUITests/WalletPersistenceTests/testWalletPersistsAcrossRelaunch \ | ||
| -only-testing:SwiftExampleAppUITests/WalletPersistenceTests/testWalletDeletionCleanupSurvivesRelaunch \ | ||
| -only-testing:SwiftExampleAppUITests/CreditTransferTest/testImportWalletAndDiscoverIdentity \ | ||
| -parallel-testing-enabled NO \ | ||
| -maximum-concurrent-test-simulator-destinations 1 \ | ||
| -resultBundlePath "$RESULT_BUNDLE_PATH" | ||
|
|
||
| - name: Upload XCUITest result bundle | ||
| if: always() | ||
| # `failure()` only — xcresult bundles include the XCUITest activity | ||
| # log (which records `typeText` arguments) and may include failure | ||
| # screenshots. `importWallet` types the testnet mnemonic into a | ||
| # plain TextField, so a successful run's artifact would archive | ||
| # the mnemonic in the activity log. Restricting to failures | ||
| # narrows the leak surface (you only get the artifact when there's | ||
| # something to debug). 7-day retention also reduces exposure. | ||
| if: failure() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: SwiftExampleAppUITests-xcresult | ||
| path: ${{ runner.temp }}/SwiftExampleAppUITests.xcresult | ||
| if-no-files-found: ignore | ||
| retention-days: 14 | ||
| retention-days: 7 | ||
|
Comment on lines
198
to
+212
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: Failure-only xcresult upload still archives the testnet mnemonic on the most likely failure paths The source: ['claude-general', 'codex-general'] 🤖 Fix this with AI agents |
||
|
|
||
| - name: Delete disposable simulator | ||
| if: always() && steps.simulator.outputs.udid != '' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,12 +9,27 @@ class AppState: ObservableObject { | |
| @Published var showError = false | ||
| @Published var errorMessage = "" | ||
|
|
||
| /// `true` from the moment a network change is requested until the | ||
| /// new SDK is bound. Spans the full async cycle (didSet → Task → | ||
| /// `switchNetwork` → `sdk = newSDK`), so consumers can wait on it | ||
| /// as a real readiness signal. UI bindings should treat | ||
| /// `appState.sdk != nil && !isSwitchingNetwork` as "connected on | ||
| /// the current network" — `appState.sdk != nil` alone is true even | ||
| /// while `switchNetwork` is still tearing down the previous SDK. | ||
| @Published var isSwitchingNetwork: Bool = false | ||
|
|
||
| /// Monotonic request id for in-flight switches. If two switches | ||
| /// overlap (user taps mainnet → testnet before the first lands), the | ||
| /// earlier task's completion would otherwise clear `isSwitchingNetwork` | ||
| /// while the later switch is still running. Each new request bumps | ||
| /// this counter and the spawned task only clears the flag when its | ||
| /// captured id still matches. | ||
| private var networkSwitchRequestID: UInt64 = 0 | ||
|
|
||
| @Published var currentNetwork: Network { | ||
| didSet { | ||
| UserDefaults.standard.set(Int(currentNetwork.rawValue), forKey: "currentNetwork") | ||
| Task { | ||
| await switchNetwork(to: currentNetwork) | ||
| } | ||
| UserDefaults.standard.set(currentNetwork.rawValue, forKey: "currentNetwork") | ||
| beginNetworkSwitch() | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -27,7 +42,23 @@ class AppState: ObservableObject { | |
| UserDefaults.standard.set(useDockerSetup, forKey: "useLocalhostPlatform") | ||
| UserDefaults.standard.set(useDockerSetup, forKey: "useLocalhostCore") | ||
| UserDefaults.standard.set(useDockerSetup, forKey: "useLocalhost") | ||
| Task { await switchNetwork(to: currentNetwork) } | ||
| beginNetworkSwitch() | ||
| } | ||
| } | ||
|
|
||
| /// Bumps `networkSwitchRequestID`, raises `isSwitchingNetwork`, and | ||
| /// spawns the SDK-rebuild task. Only the task that owns the latest | ||
| /// request id may lower `isSwitchingNetwork` again — overlapping | ||
| /// switches' earlier tasks no-op on completion. | ||
| private func beginNetworkSwitch() { | ||
| networkSwitchRequestID &+= 1 | ||
| let requestID = networkSwitchRequestID | ||
| isSwitchingNetwork = true | ||
| Task { | ||
| await switchNetwork(to: currentNetwork) | ||
| if requestID == networkSwitchRequestID { | ||
| isSwitchingNetwork = false | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+53
to
63
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard the SDK rebuild itself, not just The request ID only prevents an old task from clearing the spinner early. A stale 🔧 Suggested shape of the fix Task {
- await switchNetwork(to: currentNetwork)
+ await switchNetwork(to: currentNetwork, requestID: requestID)
if requestID == networkSwitchRequestID {
isSwitchingNetwork = false
}
}
- func switchNetwork(to network: Network) async {
+ func switchNetwork(to network: Network, requestID: UInt64) async {
guard let modelContext = modelContext else { return }
+ guard requestID == networkSwitchRequestID else { return }
dataManager?.currentNetwork = network
do {
isLoading = true
let newSDK = try SDK(network: network)
+ guard requestID == networkSwitchRequestID else { return }
sdk = newSDK
await loadKnownContractsIntoSDK(sdk: newSDK, modelContext: modelContext)
+ guard requestID == networkSwitchRequestID else { return }
isLoading = false
} catch {
+ guard requestID == networkSwitchRequestID else { return }
sdk = nil
...
}
}🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.