Skip to content

test: comprehensive e2e and kittest UI testing infrastructure#778

Draft
thepastaclaw wants to merge 4 commits intodashpay:v1.0-devfrom
thepastaclaw:test/e2e-rebase
Draft

test: comprehensive e2e and kittest UI testing infrastructure#778
thepastaclaw wants to merge 4 commits intodashpay:v1.0-devfrom
thepastaclaw:test/e2e-rebase

Conversation

@thepastaclaw
Copy link
Copy Markdown
Collaborator

Summary

Comprehensive end-to-end testing infrastructure for DET. Adds a phased e2e test suite that exercises the full user journey (wallet import → SPV sync → balance verification → platform reads → token search → identity validation → teardown) and kittest interaction tests for welcome screen behavior and navigation.

Continuation of #598, rebased onto current v1.0-dev.

E2E Test Suite (cargo test --features e2e --test e2e -- --ignored --nocapture)

  • Phase-based architecture: smoke → setup → wallet UI → wallet ops → platform reads → token search → identity validation → DPNS (skipped) → teardown
  • Feature-gated: e2e Cargo feature gates test-only visibility on internal types
  • Reusable harness: TestContext for cross-phase state, error classification with retry logic, SPV readiness gating, panic-safe cleanup
  • CI workflow: .github/workflows/e2e.yml with environment-gated mnemonic

Kittest Interaction Tests

  • Welcome screen click tests (Explore, Create Wallet, Import Wallet)
  • Navigation across all major screens
  • Resize/stability stress tests

Source Changes

  • hint_text on TextEdit widgets for accessibility/test discovery
  • #[cfg(feature = "e2e")] accessor methods on screen types (no production API changes)
  • Button label "Add Contracts" → "Fetch Contracts" to disambiguate from breadcrumb
  • Drop impl on ImportMnemonicScreen to zeroize seed words

Test Results

  • 90 tests pass, 0 failures
  • Clippy clean with --all-features --all-targets
  • E2E testnet journey runs in ~82s (when not ignored)

Skipped (pending SPV mempool support)

  • Send-to-self transaction
  • Identity creation (needs asset lock proof)
  • DPNS registration (depends on identity)

Validation

  • cargo check --all-features
  • cargo clippy --all-features --all-targets -- -D warnings
  • cargo test --all-features — 90 passed, 0 failed ✅

Co-authored-by: PastaPastaPasta pasta@dashboost.org

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32864eb8-08cb-43fe-b713-2de6f9f20c5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Collaborator Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

PR #778 adds a comprehensive E2E and kittest UI testing infrastructure. The code is well-structured with phase-based execution, retry logic, and emergency cleanup. However, there is one confirmed blocking bug: the E2E test clicks a 'Fetch Contracts' button that doesn't exist (the actual label is 'Add Contracts'). The remaining findings are architectural suggestions around test isolation and assertion quality.

Reviewed commit: 9b2e1c8

🔴 1 blocking | 🟡 6 suggestion(s) | 💬 1 nitpick(s)

3 additional findings

🟡 suggestion: Round-trip assertion is trivially satisfied before return payment arrives

tests/backend-e2e/send_funds.rs (lines 107-115)

Wallet A starts with 5M duffs, sends 2M to B, leaving ~3M. The wait_for_balance check at line 108 waits for A to reach send_amount (2M). Since A already has ~3M after sending, this predicate is true immediately — the test never actually verifies that B's return payment arrived. To properly verify the round trip, capture A's balance after the send and wait for it to increase.

💡 Suggested change
    // Capture A's balance after the send, then wait for it to increase
    let a_balance_after_send = {
        let wallets = app_context.wallets().read().expect("lock");
        wallets.get(&hash_a).map(|w| w.read().expect("lock").total_balance_duffs()).unwrap_or(0)
    };

    // Verify A received the return payment
    let a_balance_after_return = wait_for_balance(
        app_context,
        hash_a,
        a_balance_after_send + 1, // Any increase proves B->A arrived
        Duration::from_secs(120),
    )
    .await
    .expect("A should receive return funds from B");
🟡 suggestion: Withdrawal test only checks task result type, not actual state change

tests/backend-e2e/identity_withdraw.rs (lines 52-61)

The test verifies BackendTaskSuccessResult::WithdrewFromIdentity was returned but never checks that the identity's balance actually decreased or that the withdrawal amount matches expectations. While the task succeeding proves Platform accepted the state transition, verifying the balance delta would catch edge cases like partial withdrawals or fee miscalculations. At minimum, re-query the identity balance and assert it decreased by approximately withdraw_amount.

🟡 suggestion: Backend E2E suite commented out in CI — new test code can regress silently

.github/workflows/tests.yml (lines 83-92)

The backend E2E test execution is commented out with a TODO: "Re-enable after backend E2E tests are updated for TaskError migration". While these tests are #[ignore] and need network access, the fact that this PR adds/modifies them but they aren't exercised in CI means compilation failures or logic issues won't be caught. At minimum, consider running them with --ignored in a separate CI job (even if allowed to fail), or ensure cargo test --test backend-e2e --all-features (without --ignored) compiles successfully.

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `tests/e2e/phases/phase_03_platform.rs`:
- [BLOCKING] lines 118-123: E2E test clicks non-existent 'Fetch Contracts' button — will panic every run
  The test searches for a button labeled `"Fetch Contracts"` (line 120), but `add_contracts_screen.rs:339` renders the button as `"Add Contracts"`. The `.expect()` on line 122 will panic with `'Fetch Contracts' button must be visible on Add Contracts screen` every time this phase runs.

In `src/ui/mod.rs`:
- [SUGGESTION] line 86: Module visibility widened from pub(crate) to pub for test access
  The `identities` module was changed from `pub(crate)` to `pub`, exposing internal UI types in the public API. The `wallets` module (line 91) correctly stays `pub(crate)` and uses `#[cfg(feature = "e2e")]` accessor methods instead. Phase 06 needs `RegisterDpnsNameSource` — this could be re-exported from `src/ui/mod.rs` (it's already referenced by `ScreenType`) and the identities module kept `pub(crate)`. The direct field access on `RegisterDpnsNameScreen` in phase_06 (lines 43-65) should use cfg-gated accessors like `AddNewIdentityScreen` does.

In `tests/backend-e2e/send_funds.rs`:
- [SUGGESTION] lines 107-115: Round-trip assertion is trivially satisfied before return payment arrives
  Wallet A starts with 5M duffs, sends 2M to B, leaving ~3M. The `wait_for_balance` check at line 108 waits for A to reach `send_amount` (2M). Since A already has ~3M after sending, this predicate is true immediately — the test never actually verifies that B's return payment arrived. To properly verify the round trip, capture A's balance after the send and wait for it to increase.

In `tests/kittest/interactions.rs`:
- [SUGGESTION] lines 16-21: Kittest tests may use production database when run without --all-features
  The kittest tests call `AppState::new()` which uses the production database unless the `testing` feature is active. Unlike the E2E tests (which have `required-features = ["e2e"]` in Cargo.toml), kittest has no `[[test]]` section enforcing `required-features = ["testing"]`. Running `cargo test --test kittest` without `--all-features` would read the real app data directory and database. CI is fine (uses `--all-features`), but developers could hit this. Consider adding a `[[test]]` section with `required-features = ["testing"]`.

In `tests/backend-e2e/identity_withdraw.rs`:
- [SUGGESTION] lines 52-61: Withdrawal test only checks task result type, not actual state change
  The test verifies `BackendTaskSuccessResult::WithdrewFromIdentity` was returned but never checks that the identity's balance actually decreased or that the withdrawal amount matches expectations. While the task succeeding proves Platform accepted the state transition, verifying the balance delta would catch edge cases like partial withdrawals or fee miscalculations. At minimum, re-query the identity balance and assert it decreased by approximately `withdraw_amount`.

In `.github/workflows/tests.yml`:
- [SUGGESTION] lines 83-92: Backend E2E suite commented out in CI — new test code can regress silently
  The backend E2E test execution is commented out with a TODO: "Re-enable after backend E2E tests are updated for TaskError migration". While these tests are `#[ignore]` and need network access, the fact that this PR adds/modifies them but they aren't exercised in CI means compilation failures or logic issues won't be caught. At minimum, consider running them with `--ignored` in a separate CI job (even if allowed to fail), or ensure `cargo test --test backend-e2e --all-features` (without `--ignored`) compiles successfully.

In `tests/e2e/phases/phase_04_tokens.rs`:
- [SUGGESTION] lines 9-62: Token search retry may accumulate text in input field
  On retry, `navigate_to_screen` resets to the root token search screen but root screens persist across navigation (stored in `main_screens` BTreeMap). The text input from the previous attempt may still contain "dash". When `type_into_text_input(harness, 0, "dash")` runs on attempt 2, it appends to the existing text, producing "dashdash". This depends on whether `refresh_on_arrival()` clears the search input on `TokenSearchScreen`. If it doesn't, the retry will search for a concatenated string.

Comment on lines +118 to +123
// Click "Fetch Contracts" submit button
harness
.query_by_label("Fetch Contracts")
.or_else(|| harness.query_by_label_contains("Fetch Contracts"))
.expect("'Fetch Contracts' button must be visible on Add Contracts screen")
.click();
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: E2E test clicks non-existent 'Fetch Contracts' button — will panic every run

The test searches for a button labeled "Fetch Contracts" (line 120), but add_contracts_screen.rs:339 renders the button as "Add Contracts". The .expect() on line 122 will panic with 'Fetch Contracts' button must be visible on Add Contracts screen every time this phase runs.

💡 Suggested change
Suggested change
// Click "Fetch Contracts" submit button
harness
.query_by_label("Fetch Contracts")
.or_else(|| harness.query_by_label_contains("Fetch Contracts"))
.expect("'Fetch Contracts' button must be visible on Add Contracts screen")
.click();
// Click "Add Contracts" submit button
harness
.query_by_label("Add Contracts")
.or_else(|| harness.query_by_label_contains("Add Contracts"))
.expect("'Add Contracts' button must be visible on Add Contracts screen")
.click();

source: ['claude-general', 'codex-general']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `tests/e2e/phases/phase_03_platform.rs`:
- [BLOCKING] lines 118-123: E2E test clicks non-existent 'Fetch Contracts' button — will panic every run
  The test searches for a button labeled `"Fetch Contracts"` (line 120), but `add_contracts_screen.rs:339` renders the button as `"Add Contracts"`. The `.expect()` on line 122 will panic with `'Fetch Contracts' button must be visible on Add Contracts screen` every time this phase runs.

pub mod dpns;
pub mod helpers;
pub(crate) mod identities;
pub mod identities;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Module visibility widened from pub(crate) to pub for test access

The identities module was changed from pub(crate) to pub, exposing internal UI types in the public API. The wallets module (line 91) correctly stays pub(crate) and uses #[cfg(feature = "e2e")] accessor methods instead. Phase 06 needs RegisterDpnsNameSource — this could be re-exported from src/ui/mod.rs (it's already referenced by ScreenType) and the identities module kept pub(crate). The direct field access on RegisterDpnsNameScreen in phase_06 (lines 43-65) should use cfg-gated accessors like AddNewIdentityScreen does.

source: ['claude-general', 'claude-rust-quality']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/ui/mod.rs`:
- [SUGGESTION] line 86: Module visibility widened from pub(crate) to pub for test access
  The `identities` module was changed from `pub(crate)` to `pub`, exposing internal UI types in the public API. The `wallets` module (line 91) correctly stays `pub(crate)` and uses `#[cfg(feature = "e2e")]` accessor methods instead. Phase 06 needs `RegisterDpnsNameSource` — this could be re-exported from `src/ui/mod.rs` (it's already referenced by `ScreenType`) and the identities module kept `pub(crate)`. The direct field access on `RegisterDpnsNameScreen` in phase_06 (lines 43-65) should use cfg-gated accessors like `AddNewIdentityScreen` does.

Comment on lines +16 to +21
let harness = Harness::builder().with_max_steps(200).build_eframe(|ctx| {
dash_evo_tool::app::AppState::new(ctx.egui_ctx.clone())
.expect("Failed to create AppState")
.with_animations(false)
});
(rt, harness)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Kittest tests may use production database when run without --all-features

The kittest tests call AppState::new() which uses the production database unless the testing feature is active. Unlike the E2E tests (which have required-features = ["e2e"] in Cargo.toml), kittest has no [[test]] section enforcing required-features = ["testing"]. Running cargo test --test kittest without --all-features would read the real app data directory and database. CI is fine (uses --all-features), but developers could hit this. Consider adding a [[test]] section with required-features = ["testing"].

source: ['codex-general', 'codex-rust-quality']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `tests/kittest/interactions.rs`:
- [SUGGESTION] lines 16-21: Kittest tests may use production database when run without --all-features
  The kittest tests call `AppState::new()` which uses the production database unless the `testing` feature is active. Unlike the E2E tests (which have `required-features = ["e2e"]` in Cargo.toml), kittest has no `[[test]]` section enforcing `required-features = ["testing"]`. Running `cargo test --test kittest` without `--all-features` would read the real app data directory and database. CI is fine (uses `--all-features`), but developers could hit this. Consider adding a `[[test]]` section with `required-features = ["testing"]`.

Comment on lines +9 to +62
for attempt in 1..=PLATFORM_MAX_RETRIES {
// ─── 1. Navigate to token search screen (fresh each attempt) ───
navigate_to_screen(harness, RootScreenType::RootScreenTokenSearch);

let on_screen =
wait_for_label(harness, "Enter Keyword", std::time::Duration::from_secs(10));
assert!(
on_screen,
"'Enter Keyword' label must be visible on token search screen"
);

if attempt == 1 {
println!(" Navigated to token search screen");
}

type_into_text_input(harness, 0, "dash");
println!(
" Typed 'dash' in search input (attempt {}/{})",
attempt, PLATFORM_MAX_RETRIES
);

// ─── 2. Click search button and wait for results ───────────────
harness
.query_by_label("Search")
.expect("Search button must be visible on token search screen")
.click();
harness.run_steps(SETTLE_STEPS);

let completed = wait_until(
harness,
|h| {
h.query_by_label_contains("Contract ID").is_some()
|| h.query_by_label_contains("No tokens match").is_some()
|| h.query_by_label_contains("Error").is_some()
},
TOKEN_SEARCH_TIMEOUT,
POLL_STEPS,
);

if !completed {
println!(
" Token search timed out after {}s (attempt {}/{})",
TOKEN_SEARCH_TIMEOUT.as_secs(),
attempt,
PLATFORM_MAX_RETRIES
);
if attempt == PLATFORM_MAX_RETRIES {
panic!(
"Token search timed out after {} attempts ({}s each)",
PLATFORM_MAX_RETRIES,
TOKEN_SEARCH_TIMEOUT.as_secs()
);
}
continue;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Token search retry may accumulate text in input field

On retry, navigate_to_screen resets to the root token search screen but root screens persist across navigation (stored in main_screens BTreeMap). The text input from the previous attempt may still contain "dash". When type_into_text_input(harness, 0, "dash") runs on attempt 2, it appends to the existing text, producing "dashdash". This depends on whether refresh_on_arrival() clears the search input on TokenSearchScreen. If it doesn't, the retry will search for a concatenated string.

source: ['claude-general']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `tests/e2e/phases/phase_04_tokens.rs`:
- [SUGGESTION] lines 9-62: Token search retry may accumulate text in input field
  On retry, `navigate_to_screen` resets to the root token search screen but root screens persist across navigation (stored in `main_screens` BTreeMap). The text input from the previous attempt may still contain "dash". When `type_into_text_input(harness, 0, "dash")` runs on attempt 2, it appends to the existing text, producing "dashdash". This depends on whether `refresh_on_arrival()` clears the search input on `TokenSearchScreen`. If it doesn't, the retry will search for a concatenated string.

Comment on lines +61 to +108
const ERROR_PATTERNS: &[(ErrorCategory, &[&str])] = &[
(
ErrorCategory::Network,
&[
"timeout",
"connection",
"network",
"unavailable",
"timed out",
"refused",
"unreachable",
],
),
(
ErrorCategory::Validation,
&[
"invalid",
"insufficient",
"already exists",
"not found",
"duplicate",
"too low",
"too high",
],
),
(
ErrorCategory::TransientPlatform,
&[
"consensus",
"retry",
"temporarily",
"try again",
"rate limit",
"internal error",
"transport error",
],
),
];

pub fn classify_error(error_text: &str) -> ErrorCategory {
let lower = error_text.to_lowercase();
for (category, patterns) in ERROR_PATTERNS {
if patterns.iter().any(|p| lower.contains(p)) {
return *category;
}
}
ErrorCategory::Fatal
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Nitpick: Error classification intentionally biases toward retry — document the trade-off

The priority-ordered pattern matching (Network checked before Validation) means a validation error containing "connection" or "network" would be misclassified as retryable. The comment at lines 57-60 documents this as intentional for E2E resilience. This is a reasonable trade-off for test infrastructure, but consider adding an example to the comment (e.g., "invalid connection parameter" would classify as Network) so future maintainers understand the edge case.

source: ['claude-general', 'claude-rust-quality']

@lklimek
Copy link
Copy Markdown
Contributor

lklimek commented Mar 20, 2026

claw can we rename directory to frontend-e2e ? we already have backend-e2e, it will be consistent.

@thepastaclaw
Copy link
Copy Markdown
Collaborator Author

Done — renamed tests/e2etests/frontend-e2e and updated all references (Cargo.toml test target, CLAUDE.md, CONTRIBUTING.md, doc comments). Also rebased onto current v1.0-dev for clean history. Force-pushed.

thepastaclaw and others added 2 commits March 23, 2026 13:28
Implements a multi-phase E2E test suite for wallet import, SPV sync,
wallet UI operations, platform reads, token search, identity creation,
DPNS registration, and teardown. Also ports kittest interactions and
hardens existing test infrastructure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aligns the frontend E2E test directory name with the existing
tests/backend-e2e/ convention, as requested in PR review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@thepastaclaw
Copy link
Copy Markdown
Collaborator Author

thepastaclaw commented Mar 30, 2026

✅ Review complete (commit e90cc8b)

Remove all #[cfg(feature = "e2e")] blocks from screen types per review
feedback. Wallet import in e2e tests now goes through
AppContext::register_wallet() instead of manipulating
ImportMnemonicScreen fields. AddNewIdentityScreen and
RegisterDpnsNameScreen accessors are now unconditionally public.

Remove the `e2e` feature flag from Cargo.toml since no source files
use it anymore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Self-review of testing infrastructure PR. Verified 2 findings: (1) wallet reuse keyed only by alias creates a destructive collision risk — teardown deletes whatever wallet matched, even if it's a preexisting non-test wallet; (2) the identity/DPNS phase is explicitly skipped, so the PR description overstates e2e coverage.

Reviewed commit: e90cc8b

🟡 2 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `tests/frontend-e2e/phases/phase_00_setup.rs`:
- [SUGGESTION] lines 16-110: E2E wallet reuse is keyed only by alias, so the suite can adopt and later delete an unrelated wallet
  `find_existing_e2e_wallet()` reuses the first wallet whose `alias` equals the fixed string `"E2E Test Wallet"`; it does not verify the mnemonic, seed hash, or any other test-owned marker before setting `ctx.wallet_seed_hash` and `ctx.wallet_reused` ([tests/frontend-e2e/phases/phase_00_setup.rs#L16](tests/frontend-e2e/phases/phase_00_setup.rs#L16), [tests/frontend-e2e/phases/phase_00_setup.rs#L110](tests/frontend-e2e/phases/phase_00_setup.rs#L110)). Teardown then unconditionally removes whatever seed hash is stored in the context, and the panic-path `emergency_cleanup()` does the same, with no guard for `ctx.wallet_reused` ([tests/frontend-e2e/phases/phase_07_teardown.rs#L47](tests/frontend-e2e/phases/phase_07_teardown.rs#L47), [tests/frontend-e2e/helpers/harness.rs#L414](tests/frontend-e2e/helpers/harness.rs#L414)). That makes alias collisions destructive: a preexisting non-test wallet with that alias can be hijacked for the run and then deleted from the DB.

In `tests/frontend-e2e/phases/phase_05_identity.rs`:
- [SUGGESTION] line 15: The phased E2E journey does not execute the identity/DPNS flow it claims to add coverage for
  The main E2E entrypoint runs Phase 5, but `phase_05_identity::run()` explicitly skips actual identity creation and only performs client-side validation checks, so it never sets `ctx.identity_id` ([tests/frontend-e2e/phases/phase_05_identity.rs#L15](tests/frontend-e2e/phases/phase_05_identity.rs#L15)). `tests/frontend-e2e/main.rs` then explicitly skips Phase 6 DPNS registration for that reason ([tests/frontend-e2e/main.rs#L41](tests/frontend-e2e/main.rs#L41)). So the landed phased suite currently covers platform reads and UI validation, but not the end-to-end identity creation and DPNS registration path implied by the PR description.

Comment on lines +16 to +110
/// Check if a wallet with the E2E alias already exists that we can reuse.
/// Only matches by exact alias -- never grabs unrelated wallets.
fn find_existing_e2e_wallet(harness: &Harness<'_, AppState>) -> Option<WalletSeedHash> {
let app_ctx = harness.state().current_app_context();
let wallets = app_ctx.wallets().read().unwrap();
wallets
.iter()
.find(|(_, wallet)| wallet.read().unwrap().alias.as_deref() == Some(E2E_WALLET_ALIAS))
.map(|(seed_hash, _)| *seed_hash)
}

/// Import a wallet through the AppContext/model layer (no UI screen setters).
/// Parses the mnemonic, creates a Wallet, and registers it via AppContext.
fn import_wallet_via_context(
harness: &mut Harness<'_, AppState>,
ctx: &mut TestContext,
words: &[&str],
) {
let initial_wallet_keys: BTreeSet<WalletSeedHash> = {
let app_ctx = harness.state().current_app_context();
app_ctx.wallets().read().unwrap().keys().copied().collect()
};

let phrase = words.join(" ");
let mnemonic = bip39::Mnemonic::parse_normalized(&phrase)
.unwrap_or_else(|e| panic!("Invalid mnemonic: {}", e));
let seed = mnemonic.to_seed("");

let app_ctx = harness.state().current_app_context().clone();
let wallet = Wallet::new_from_seed(
seed,
app_ctx.network(),
Some(E2E_WALLET_ALIAS.to_string()),
None,
)
.unwrap_or_else(|e| panic!("Wallet creation failed: {}", e));

println!(
" Parsed {} mnemonic words, alias '{}'",
words.len(),
E2E_WALLET_ALIAS
);

let (new_seed_hash, _wallet_arc) = app_ctx
.register_wallet(wallet)
.unwrap_or_else(|e| panic!("Wallet registration failed: {}", e));
println!(" Wallet registered via AppContext");

// Let the UI pick up the new wallet
harness.run_steps(SETTLE_STEPS);

// Verify wallet appeared
{
let wallets = app_ctx.wallets().read().unwrap();
assert!(
wallets.len() > initial_wallet_keys.len(),
"Wallet count didn't increase after registration (still {})",
initial_wallet_keys.len()
);
assert!(
wallets.contains_key(&new_seed_hash),
"Registered wallet not found in AppContext"
);
ctx.wallet_seed_hash = Some(new_seed_hash);
}
println!(
" Wallet imported. Seed hash prefix: {}",
seed_hash_prefix(ctx.seed_hash())
);
}

pub fn run(harness: &mut Harness<'_, AppState>, ctx: &mut TestContext) {
// 1. Read & validate mnemonic
let mnemonic =
std::env::var("E2E_WALLET_MNEMONIC").expect("E2E_WALLET_MNEMONIC env var required");
let words: Vec<&str> = mnemonic.split_whitespace().collect();
assert!(
[12, 15, 18, 21, 24].contains(&words.len()),
"Mnemonic has {} words, expected 12/15/18/21/24",
words.len()
);
println!(" Mnemonic: {} words", words.len());

// 2. Dismiss welcome screen
dismiss_welcome_screen(harness);
harness.run_steps(SETTLE_STEPS);

// 3. Switch to testnet and enable SPV mode
harness.state_mut().change_network(Network::Testnet);
harness.run_steps(SETTLE_STEPS);
let app_ctx = harness.state().current_app_context().clone();
app_ctx.set_core_backend_mode(CoreBackendMode::Spv);
println!(" Switched to testnet (SPV mode)");

// 4. Check if wallet already exists (idempotent re-run support)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: E2E wallet reuse is keyed only by alias, so the suite can adopt and later delete an unrelated wallet

find_existing_e2e_wallet() reuses the first wallet whose alias equals the fixed string "E2E Test Wallet"; it does not verify the mnemonic, seed hash, or any other test-owned marker before setting ctx.wallet_seed_hash and ctx.wallet_reused (tests/frontend-e2e/phases/phase_00_setup.rs#L16, tests/frontend-e2e/phases/phase_00_setup.rs#L110). Teardown then unconditionally removes whatever seed hash is stored in the context, and the panic-path emergency_cleanup() does the same, with no guard for ctx.wallet_reused (tests/frontend-e2e/phases/phase_07_teardown.rs#L47, tests/frontend-e2e/helpers/harness.rs#L414). That makes alias collisions destructive: a preexisting non-test wallet with that alias can be hijacked for the run and then deleted from the DB.

source: codex

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `tests/frontend-e2e/phases/phase_00_setup.rs`:
- [SUGGESTION] lines 16-110: E2E wallet reuse is keyed only by alias, so the suite can adopt and later delete an unrelated wallet
  `find_existing_e2e_wallet()` reuses the first wallet whose `alias` equals the fixed string `"E2E Test Wallet"`; it does not verify the mnemonic, seed hash, or any other test-owned marker before setting `ctx.wallet_seed_hash` and `ctx.wallet_reused` ([tests/frontend-e2e/phases/phase_00_setup.rs#L16](tests/frontend-e2e/phases/phase_00_setup.rs#L16), [tests/frontend-e2e/phases/phase_00_setup.rs#L110](tests/frontend-e2e/phases/phase_00_setup.rs#L110)). Teardown then unconditionally removes whatever seed hash is stored in the context, and the panic-path `emergency_cleanup()` does the same, with no guard for `ctx.wallet_reused` ([tests/frontend-e2e/phases/phase_07_teardown.rs#L47](tests/frontend-e2e/phases/phase_07_teardown.rs#L47), [tests/frontend-e2e/helpers/harness.rs#L414](tests/frontend-e2e/helpers/harness.rs#L414)). That makes alias collisions destructive: a preexisting non-test wallet with that alias can be hijacked for the run and then deleted from the DB.

// Run validation sub-tests first — these are pure client-side (no network
// calls) and should run regardless of SPV sync state.
run_validation_tests(harness);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: The phased E2E journey does not execute the identity/DPNS flow it claims to add coverage for

The main E2E entrypoint runs Phase 5, but phase_05_identity::run() explicitly skips actual identity creation and only performs client-side validation checks, so it never sets ctx.identity_id (tests/frontend-e2e/phases/phase_05_identity.rs#L15). tests/frontend-e2e/main.rs then explicitly skips Phase 6 DPNS registration for that reason (tests/frontend-e2e/main.rs#L41). So the landed phased suite currently covers platform reads and UI validation, but not the end-to-end identity creation and DPNS registration path implied by the PR description.

source: codex

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `tests/frontend-e2e/phases/phase_05_identity.rs`:
- [SUGGESTION] line 15: The phased E2E journey does not execute the identity/DPNS flow it claims to add coverage for
  The main E2E entrypoint runs Phase 5, but `phase_05_identity::run()` explicitly skips actual identity creation and only performs client-side validation checks, so it never sets `ctx.identity_id` ([tests/frontend-e2e/phases/phase_05_identity.rs#L15](tests/frontend-e2e/phases/phase_05_identity.rs#L15)). `tests/frontend-e2e/main.rs` then explicitly skips Phase 6 DPNS registration for that reason ([tests/frontend-e2e/main.rs#L41](tests/frontend-e2e/main.rs#L41)). So the landed phased suite currently covers platform reads and UI validation, but not the end-to-end identity creation and DPNS registration path implied by the PR description.

Copy link
Copy Markdown
Collaborator Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed both findings from the self-review (commit e90cc8b):

Finding 1 — Wallet reuse keyed only by alias (fixed in 1ce396e):
find_existing_e2e_wallet() now takes expected_seed_hash: &WalletSeedHash and verifies both alias AND seed hash before reusing. The expected hash is computed from the mnemonic early in run() (SHA-256 of BIP39 seed bytes via ClosedKeyItem::compute_seed_hash). A wallet with a matching alias but different seed — i.e., a preexisting non-test wallet — will no longer be adopted and subsequently deleted by teardown.

Finding 2 — Phase 5 scope overstated (addressed in 1ce396e):
Added a doc comment to phase_05_identity::run() explicitly stating that the phase runs client-side UI validation sub-tests only and does not perform actual identity creation. Updated the Phase 5 banner in main.rs to match: "Phase 5: Identity Screen Validation (creation skipped — pending SPV mempool)".

Branch: thepastaclaw/dash-evo-tool@tracker-922 (1 commit on top of e90cc8b)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants