Earn E2E Skill
You are both a test runner and a maintenance guide for the Earn E2E suites. When invoked as a command (/e2e-earn), default behaviour is to run the relevant Earn tests locally and triage any failures — but always confirm scope with the user first (see "Running the Earn E2E tests").
Use this skill when
- Running the Earn E2E tests locally (
earn.v2, delegate, or both) and triaging failures.
- Updating Earn specs/page objects in
e2e/desktop or e2e/mobile.
- Refactoring Earn page objects or moving reusable flow logic out of specs.
- Adapting the Earn v2 dashboard flows (ice/cold/hot start, deposit, withdrawal, provider selection, native vs redirect staking).
- Fixing PR review comments about dead code, selector ownership, brittle amounts, webview/window context, or feature-flag drift.
Running the Earn E2E tests
This is the primary command behaviour. Follow these steps in order.
Step 1 — Confirm scope with the user
Do not assume. Ask which subset to run before doing anything heavy:
- Desktop:
earn.v2 only — pnpm e2e:desktop test:playwright earn.v2
delegate only — pnpm e2e:desktop test:playwright delegate
- both — run
earn.v2 then delegate
- optionally narrow with a
--grep (e.g. a single currency/provider): pnpm e2e:desktop test:playwright earn.v2 --grep "Ethereum"
- Mobile (heavier — requires a built app + simulator/emulator): ask whether to include it, and if so iOS (debug) or Android (release). Defer build/setup to
/e2e-mobile-onboard.
The earn.v2 / delegate argument is a path substring filter Playwright matches against spec file paths — earn.v2 matches tests/specs/earn.v2.spec.ts, delegate matches tests/specs/delegate.spec.ts.
Step 2 — Verify environment (Speculos mode, MOCK=0)
These must be set in the shell that runs the tests. If any are missing, point the user to /e2e-desktop-onboard (or /e2e-mobile-onboard) rather than guessing.
| Variable |
Required value / note |
MOCK |
0 (Speculos mode) |
SEED |
Non-empty. NEVER print or echo it. Read at runtime (e.g. from 1Password). |
COINAPPS |
Path to a cloned, up-to-date coin-apps repo |
SPECULOS_IMAGE_TAG |
See "Speculos image" in Troubleshooting — pin to avoid :latest drift |
SPECULOS_DEVICE |
e.g. nanoSP (options: nanoS, nanoSP, nanoX, stax, flex, nanoGen5) |
Docker Desktop must be running, and the Speculos image must be pulled.
Step 3 — Ask whether to rebuild
The Playwright suite runs the built desktop app from apps/ledger-live-desktop/.webpack/main.bundle.js. A stale or partial build is the #1 cause of domcontentloaded timeouts (see Troubleshooting).
Step 4 — Run
From the repo root. Example combined run (only after confirming scope + build):
# Desktop, both suites, fresh build
pnpm desktop build:testing && pnpm e2e:desktop test:playwright earn.v2 && pnpm e2e:desktop test:playwright delegate
# Desktop, earn only, no rebuild
pnpm e2e:desktop test:playwright earn.v2
# Desktop, delegate only
pnpm e2e:desktop test:playwright delegate
# Debugging (Playwright inspector + Ledger Wallet devtools)
PWDEBUG=1 DEV_TOOLS=1 pnpm e2e:desktop test:playwright earn.v2
# Allure report after a run
pnpm e2e:desktop allure
Mobile (from e2e/mobile/, after /e2e-mobile-onboard build): pnpm test:android <earnSpecFile> / pnpm test:ios:debug <earnSpecFile>.
Step 5 — Triage failures
On any failure, classify using the Troubleshooting taxonomy below and tell the user the category (environment vs build vs test-flow mismatch) and the concrete fix. Read the run's error-context.md and per-test logs.log under e2e/desktop/tests/artifacts/test-results/<test>/ for the real cause.
Current source of truth
- Desktop specs:
e2e/desktop/tests/specs/earn.v2.spec.ts, e2e/desktop/tests/specs/delegate.spec.ts (delegate is owned by the Earn team). The legacy Earn v1 suite (earn.spec.ts) has been removed.
- Desktop page objects:
e2e/desktop/tests/page/earn.base.page.ts — abstract EarnBasePage extends WebViewAppPage (webviewIdentifier = "earn", goAndWaitForEarnToBeReady, verifyProviderURL).
e2e/desktop/tests/page/earn.v2.dashboard.page.ts — EarnV2Page (dashboard states + deposit/withdrawal + provider selection).
e2e/desktop/tests/page/modal/delegate.modal.ts — native delegation modal.
- Desktop utils / flags:
e2e/desktop/tests/utils/featureFlagUtils.ts (EARN_V2_DESKTOP_FLAGS, FF_STAKE_PROGRAMS_MODAL, useLocalEarnManifest), e2e/desktop/tests/utils/earnLocalManifest.json.
- Mobile specs/builders:
e2e/mobile/specs/earn/** (e.g. earnV2_iceColdStart_ETH_3.spec.ts, earnV2_position_withdrawal_USDT.spec.ts, earnInlineAddAccount.spec.ts), builder e2e/mobile/specs/earn/earnV2.ts (includes runInlineAddAccountTest). The legacy Earn v1 builder (earn.ts) and its consumer specs (correctEarnPage_*, startStakingFromEarnDashboard_*) have been removed.
- Mobile page objects:
e2e/mobile/page/trade/earnV2Dashboard.page.ts.
Platform model
- Desktop:
- Electron exposes multiple windows (main app + one or more webviews). The Earn app is a webview.
- Resolve the Earn webview via
EarnBasePage.getWebView() using webviewIdentifier = "earn" (title matching) — never fixed electronApp.windows()[N] indexes.
- The Earn app signals readiness via a console log:
goAndWaitForEarnToBeReady() waits for the "Earn Live App Loaded" info message, then for the loading-skeleton test id to be hidden. Prefer this over ad-hoc waits.
- Native staking (the
delegate modal) acts on the main window; the Earn dashboard/deposit flow acts on the webview.
- Mobile:
- Webview operations use Detox web helpers; native operations use regular Detox matchers.
- Mirror desktop test ids where the Earn web app shares them (e.g.
asset-earn-cta-<ticker>, eth-provider-card-<provider>).
Earn flow & feature-flag model (critical)
Earn behaviour is feature-flag driven. Get this wrong and the app routes to the legacy modal instead of the Earn dashboard (or vice versa).
EARN_V2_DESKTOP_FLAGS enables the Earn v2 dashboard. When useLocalEarnManifest (USE_LOCAL_EARN_MANIFEST=1) is set, it also points ptxEarnLiveApp at the local earn-local-manifest (earnLocalManifest.json) and the spec applies localManifestOverride.
FF_STAKE_PROGRAMS_MODAL (stakePrograms) controls native staking vs redirect-to-Earn per currency:
params.list — currencies that use native staking UI.
params.redirects — currencies that redirect into the Earn app (platform earn, with queryParams). For native ETH the redirect carries cryptoAssetId: "ethereum", intent: "deposit", and an ethDepositCohort (e.g. "basic_sorting"). Omitting ethDepositCohort yields a missing_cohort_value sentinel and the wrong screen.
- Per-test
featureFlags (passed via test.use) override env/remote flags. This is how specs force a deterministic flow.
Desktop patterns (current API)
- Keep assertion method prefixes consistent within a page object (
verify*).
- In
EarnV2Page, resolve the webview with const webview = await this.getWebView(); act on webview.getByTestId(...). Do not pass electronApp unless a method needs non-webview windows/events.
- Keep test ids as class properties / id-builder functions in the page object (e.g.
asset-item-ticker-<ticker>, asset-earn-cta-<ticker>, eth-provider-card-<provider>), never inline literals repeated across specs.
- Current dashboard-state helpers:
verifyIceColdStartPage(), verifyColdStartPage(), verifyHotStartPage(), verifyAssetReadyToEarn(ticker), verifyRewardsSummaryBoxes().
- Deposit/withdrawal/provider helpers:
clickAssetEarnCta(ticker), verifyDepositFlowVisible() (URL /deposit), verifyWithdrawalFlowVisible() (URL /redeem|intent=withdraw), selectEthProvider(providerId) (clicks eth-provider-card-<provider>), verifyProviderVisible(), modular-selector helpers (expectModularSelectorToBeVisible, selectAssetInModularSelector, addExistingAccountViaModularSelector).
- Provider redirect URL assertions live in
EarnBasePage.verifyProviderURL(provider, account) — extend that switch rather than asserting URLs inline.
- Native ETH "select validator" now flows through the Earn deposit screen (
delegate.spec.ts uses verifyDepositFlowVisible() + selectEthProvider(...) for ETH), not the old stake-provider-container-lido modal. Don't reintroduce removed Lido-modal helpers.
Mobile patterns (current API)
- Use the Page Object Model; native vs webview matchers as in "Platform model".
- Keep flows in builders (
earnV2.ts / earn.ts) and page objects, not inline in specs.
- Custom flags can be injected globally via
E2E_FEATURE_FLAGS_JSON (merged with defaults; per-test flags still win). Use this to force the Earn v2 / redirect behaviour on mobile.
- Use the
.skip.spec.ts suffix to temporarily exclude a spec from CI (don't comment-out).
Troubleshooting / failure taxonomy
Classify every failure as environment, build, or test-flow mismatch, then apply the fix.
page.waitForLoadState: Timeout ... domcontentloaded (build) — the app launched but the renderer never loaded. Almost always a stale/partial .webpack build (e.g. built under a restricted sandbox, or never rebuilt after app changes). Fix in the user's own terminal:rm -rf apps/ledger-live-desktop/.webpack && pnpm desktop build:testing
Validate with one simple spec (tests/specs/add.account.spec.ts --grep "Bitcoin"). If it still hangs, launch the built app standalone to see it: cd apps/ledger-live-desktop && npx electron .webpack/main.bundle.js --user-data-dir=/tmp/lld-debug (blank/crash ⇒ bad build or native-module ABI mismatch ⇒ pnpm build:lld:deps && pnpm desktop build:testing).
Invariant Violation: SEED is not set (environment) — process.env.SEED is empty in the test process. Ensure the test runs in a shell where SEED is exported (e.g. an e2e_env function sourcing it from 1Password). Never print the value.
speculos.py: error: unrecognized arguments: -p (environment) — image/code mismatch. The harness sets PLAYWRIGHT_RUN, so speculos-transport passes -p to speculos.py; an old cached :latest image doesn't support it. Fix by pinning to the tag the code expects and pulling it:export SPECULOS_IMAGE_TAG=ghcr.io/ledgerhq/speculos:sha-e262a0c # matches libs/speculos-transport default
docker pull ghcr.io/ledgerhq/speculos:sha-e262a0c
:latest is a moving tag and is NOT pinning; if you stay on it you must docker pull to refresh, and setting the env var alone never changes the cached image. (See "Speculos image" note below.)
WARNING: ... platform (linux/amd64) does not match host (arm64) (benign) — the Speculos image is amd64-only and runs under emulation on Apple Silicon. Not a failure; ignore.
speculos already in use / address already in use / port conflicts (environment) — stale Speculos containers from interrupted runs. Clean up: docker rm -f $(docker ps -a --filter name=speculos -q).
TypeError: Invalid Version: DS_Store (environment) — .DS_Store files in coin-apps break semver lookup: find "$COINAPPS" -name ".DS_Store" -type f -delete.
- App + Speculos connect, but a specific Earn element isn't found (test-flow mismatch, not setup) — e.g. expecting the legacy stake modal when the flow now redirects to the Earn dashboard, or a provider card id changed. Check feature flags (
FF_STAKE_PROGRAMS_MODAL list vs redirects, ethDepositCohort) and the current test ids in the Earn web app. Report it as a test/scenario issue, not an environment one.
Speculos image note: pin (sha-... / v...) for reproducible, comparable runs (this is what the bot/smoke CI jobs do); :latest tracks the current device/firmware matrix (what the broad e2e matrix uses) but drifts and must be re-pulled. A SHA hardcoded in your shell unblocks a run now but becomes a liability long-term as the code's expected image advances — prefer pinning per-run, or unset SPECULOS_IMAGE_TAG to follow the code's own default.
Validation before finishing (for code changes)
Run validation for the impacted E2E package(s), not the whole monorepo:
- Desktop:
pnpm --filter ledger-live-desktop-e2e-tests typecheck; run targeted earn.v2 / delegate Playwright tests when the environment allows.
- Mobile:
pnpm --filter ledger-live-mobile-e2e-tests typecheck; run targeted Detox earn specs when the environment allows.
- Pre-commit runs
oxfmt (formatting) + gitleaks. If oxfmt flags a file, apply exactly its suggested change.
Reuse an existing watch terminal when available before starting a new watcher. If unrelated pre-existing failures appear, report them as pre-existing and keep scope focused on Earn changes.
PR review comment workflow
For each comment:
- Verify current code and usages (
rg for symbol usage).
- Classify: real change needed / duplicate / stale (already addressed).
- Implement only real changes.
- Prepare explicit PR replies for stale/duplicate/already-fixed comments.
Quick checklist
1---2name: e2e-earn3description: Check, update, and RUN Earn E2E tests for Ledger Wallet Desktop (Playwright) and Mobile (Detox). Covers the Earn v2 dashboard, the native + redirect staking flows, and the delegate specs (owned by the Earn team). Use to run earn.v2 / delegate locally, adapt earn page objects/flows, and triage failures.4---56# Earn E2E Skill78You are both a **test runner** and a **maintenance guide** for the Earn E2E suites. When invoked as a command (`/e2e-earn`), default behaviour is to **run the relevant Earn tests locally and triage any failures** — but always confirm scope with the user first (see "Running the Earn E2E tests").910## Use this skill when1112- Running the Earn E2E tests locally (`earn.v2`, `delegate`, or both) and triaging failures.13- Updating Earn specs/page objects in `e2e/desktop` or `e2e/mobile`.14- Refactoring Earn page objects or moving reusable flow logic out of specs.15- Adapting the Earn v2 dashboard flows (ice/cold/hot start, deposit, withdrawal, provider selection, native vs redirect staking).16- Fixing PR review comments about dead code, selector ownership, brittle amounts, webview/window context, or feature-flag drift.1718## Running the Earn E2E tests1920This is the primary command behaviour. Follow these steps in order.2122### Step 1 — Confirm scope with the user2324Do **not** assume. Ask which subset to run before doing anything heavy:2526- **Desktop**:27 - `earn.v2` only — `pnpm e2e:desktop test:playwright earn.v2`28 - `delegate` only — `pnpm e2e:desktop test:playwright delegate`29 - both — run `earn.v2` then `delegate`30 - optionally narrow with a `--grep` (e.g. a single currency/provider): `pnpm e2e:desktop test:playwright earn.v2 --grep "Ethereum"`31- **Mobile** (heavier — requires a built app + simulator/emulator): ask whether to include it, and if so iOS (debug) or Android (release). Defer build/setup to `/e2e-mobile-onboard`.3233The `earn.v2` / `delegate` argument is a **path substring filter** Playwright matches against spec file paths — `earn.v2` matches `tests/specs/earn.v2.spec.ts`, `delegate` matches `tests/specs/delegate.spec.ts`.3435### Step 2 — Verify environment (Speculos mode, MOCK=0)3637These must be set in the shell that runs the tests. If any are missing, point the user to `/e2e-desktop-onboard` (or `/e2e-mobile-onboard`) rather than guessing.3839| Variable | Required value / note |40| -------------------- | ------------------------------------------------------------------------- |41| `MOCK` | `0` (Speculos mode) |42| `SEED` | Non-empty. NEVER print or echo it. Read at runtime (e.g. from 1Password). |43| `COINAPPS` | Path to a cloned, up-to-date `coin-apps` repo |44| `SPECULOS_IMAGE_TAG` | See "Speculos image" in Troubleshooting — pin to avoid `:latest` drift |45| `SPECULOS_DEVICE` | e.g. `nanoSP` (options: nanoS, nanoSP, nanoX, stax, flex, nanoGen5) |4647Docker Desktop must be running, and the Speculos image must be pulled.4849### Step 3 — Ask whether to rebuild5051The Playwright suite runs the **built** desktop app from `apps/ledger-live-desktop/.webpack/main.bundle.js`. A stale or partial build is the #1 cause of `domcontentloaded` timeouts (see Troubleshooting).5253- Check the build's freshness: `ls -la apps/ledger-live-desktop/.webpack/main.bundle.js`.54- If it's stale (older than recent app changes), missing, or the user is unsure, **ask** before rebuilding — a rebuild is slow:55 ```bash56 pnpm desktop build:testing57 ```58- Only spec/page-object/util changes under `e2e/` do **not** require a rebuild. Any change to app code under `apps/ledger-live-desktop/src` does.5960### Step 4 — Run6162From the **repo root**. Example combined run (only after confirming scope + build):6364```bash65# Desktop, both suites, fresh build66pnpm desktop build:testing && pnpm e2e:desktop test:playwright earn.v2 && pnpm e2e:desktop test:playwright delegate6768# Desktop, earn only, no rebuild69pnpm e2e:desktop test:playwright earn.v27071# Desktop, delegate only72pnpm e2e:desktop test:playwright delegate7374# Debugging (Playwright inspector + Ledger Wallet devtools)75PWDEBUG=1 DEV_TOOLS=1 pnpm e2e:desktop test:playwright earn.v27677# Allure report after a run78pnpm e2e:desktop allure79```8081Mobile (from `e2e/mobile/`, after `/e2e-mobile-onboard` build): `pnpm test:android <earnSpecFile>` / `pnpm test:ios:debug <earnSpecFile>`.8283### Step 5 — Triage failures8485On any failure, classify using the Troubleshooting taxonomy below and tell the user the category (environment vs build vs test-flow mismatch) and the concrete fix. Read the run's `error-context.md` and per-test `logs.log` under `e2e/desktop/tests/artifacts/test-results/<test>/` for the real cause.8687## Current source of truth8889- **Desktop specs**: `e2e/desktop/tests/specs/earn.v2.spec.ts`, `e2e/desktop/tests/specs/delegate.spec.ts` (delegate is owned by the Earn team). The legacy Earn v1 suite (`earn.spec.ts`) has been removed.90- **Desktop page objects**:91 - `e2e/desktop/tests/page/earn.base.page.ts` — abstract `EarnBasePage extends WebViewAppPage` (`webviewIdentifier = "earn"`, `goAndWaitForEarnToBeReady`, `verifyProviderURL`).92 - `e2e/desktop/tests/page/earn.v2.dashboard.page.ts` — `EarnV2Page` (dashboard states + deposit/withdrawal + provider selection).93 - `e2e/desktop/tests/page/modal/delegate.modal.ts` — native delegation modal.94- **Desktop utils / flags**: `e2e/desktop/tests/utils/featureFlagUtils.ts` (`EARN_V2_DESKTOP_FLAGS`, `FF_STAKE_PROGRAMS_MODAL`, `useLocalEarnManifest`), `e2e/desktop/tests/utils/earnLocalManifest.json`.95- **Mobile specs/builders**: `e2e/mobile/specs/earn/**` (e.g. `earnV2_iceColdStart_ETH_3.spec.ts`, `earnV2_position_withdrawal_USDT.spec.ts`, `earnInlineAddAccount.spec.ts`), builder `e2e/mobile/specs/earn/earnV2.ts` (includes `runInlineAddAccountTest`). The legacy Earn v1 builder (`earn.ts`) and its consumer specs (`correctEarnPage_*`, `startStakingFromEarnDashboard_*`) have been removed.96- **Mobile page objects**: `e2e/mobile/page/trade/earnV2Dashboard.page.ts`.9798## Platform model99100- **Desktop**:101 - Electron exposes multiple windows (main app + one or more webviews). The Earn app is a **webview**.102 - Resolve the Earn webview via `EarnBasePage.getWebView()` using `webviewIdentifier = "earn"` (title matching) — never fixed `electronApp.windows()[N]` indexes.103 - The Earn app signals readiness via a console log: `goAndWaitForEarnToBeReady()` waits for the `"Earn Live App Loaded"` info message, then for the `loading-skeleton` test id to be hidden. Prefer this over ad-hoc waits.104 - Native staking (the `delegate` modal) acts on the **main window**; the Earn dashboard/deposit flow acts on the **webview**.105- **Mobile**:106 - Webview operations use Detox web helpers; native operations use regular Detox matchers.107 - Mirror desktop test ids where the Earn web app shares them (e.g. `asset-earn-cta-<ticker>`, `eth-provider-card-<provider>`).108109## Earn flow & feature-flag model (critical)110111Earn behaviour is feature-flag driven. Get this wrong and the app routes to the legacy modal instead of the Earn dashboard (or vice versa).112113- `EARN_V2_DESKTOP_FLAGS` enables the Earn v2 dashboard. When `useLocalEarnManifest` (`USE_LOCAL_EARN_MANIFEST=1`) is set, it also points `ptxEarnLiveApp` at the local `earn-local-manifest` (`earnLocalManifest.json`) and the spec applies `localManifestOverride`.114- `FF_STAKE_PROGRAMS_MODAL` (`stakePrograms`) controls native staking vs redirect-to-Earn per currency:115 - `params.list` — currencies that use **native** staking UI.116 - `params.redirects` — currencies that **redirect** into the Earn app (platform `earn`, with `queryParams`). For native ETH the redirect carries `cryptoAssetId: "ethereum"`, `intent: "deposit"`, and an `ethDepositCohort` (e.g. `"basic_sorting"`). Omitting `ethDepositCohort` yields a `missing_cohort_value` sentinel and the wrong screen.117- Per-test `featureFlags` (passed via `test.use`) override env/remote flags. This is how specs force a deterministic flow.118119## Desktop patterns (current API)120121- Keep assertion method prefixes consistent within a page object (`verify*`).122- In `EarnV2Page`, resolve the webview with `const webview = await this.getWebView()`; act on `webview.getByTestId(...)`. Do not pass `electronApp` unless a method needs non-webview windows/events.123- Keep test ids as class properties / id-builder functions in the page object (e.g. `asset-item-ticker-<ticker>`, `asset-earn-cta-<ticker>`, `eth-provider-card-<provider>`), never inline literals repeated across specs.124- Current dashboard-state helpers: `verifyIceColdStartPage()`, `verifyColdStartPage()`, `verifyHotStartPage()`, `verifyAssetReadyToEarn(ticker)`, `verifyRewardsSummaryBoxes()`.125- Deposit/withdrawal/provider helpers: `clickAssetEarnCta(ticker)`, `verifyDepositFlowVisible()` (URL `/deposit`), `verifyWithdrawalFlowVisible()` (URL `/redeem|intent=withdraw`), `selectEthProvider(providerId)` (clicks `eth-provider-card-<provider>`), `verifyProviderVisible()`, modular-selector helpers (`expectModularSelectorToBeVisible`, `selectAssetInModularSelector`, `addExistingAccountViaModularSelector`).126- Provider redirect URL assertions live in `EarnBasePage.verifyProviderURL(provider, account)` — extend that switch rather than asserting URLs inline.127- Native ETH "select validator" now flows through the Earn deposit screen (`delegate.spec.ts` uses `verifyDepositFlowVisible()` + `selectEthProvider(...)` for ETH), not the old `stake-provider-container-lido` modal. Don't reintroduce removed Lido-modal helpers.128129## Mobile patterns (current API)130131- Use the Page Object Model; native vs webview matchers as in "Platform model".132- Keep flows in builders (`earnV2.ts` / `earn.ts`) and page objects, not inline in specs.133- Custom flags can be injected globally via `E2E_FEATURE_FLAGS_JSON` (merged with defaults; per-test flags still win). Use this to force the Earn v2 / redirect behaviour on mobile.134- Use the `.skip.spec.ts` suffix to temporarily exclude a spec from CI (don't comment-out).135136## Troubleshooting / failure taxonomy137138Classify every failure as **environment**, **build**, or **test-flow mismatch**, then apply the fix.1391401. **`page.waitForLoadState: Timeout ... domcontentloaded` (build)** — the app launched but the renderer never loaded. Almost always a **stale/partial `.webpack` build** (e.g. built under a restricted sandbox, or never rebuilt after app changes). Fix in the user's own terminal:141 ```bash142 rm -rf apps/ledger-live-desktop/.webpack && pnpm desktop build:testing143 ```144 Validate with one simple spec (`tests/specs/add.account.spec.ts --grep "Bitcoin"`). If it still hangs, launch the built app standalone to see it: `cd apps/ledger-live-desktop && npx electron .webpack/main.bundle.js --user-data-dir=/tmp/lld-debug` (blank/crash ⇒ bad build or native-module ABI mismatch ⇒ `pnpm build:lld:deps && pnpm desktop build:testing`).1452. **`Invariant Violation: SEED is not set` (environment)** — `process.env.SEED` is empty in the test process. Ensure the test runs in a shell where `SEED` is exported (e.g. an `e2e_env` function sourcing it from 1Password). Never print the value.1463. **`speculos.py: error: unrecognized arguments: -p` (environment)** — image/code mismatch. The harness sets `PLAYWRIGHT_RUN`, so `speculos-transport` passes `-p` to `speculos.py`; an **old cached `:latest` image** doesn't support it. Fix by pinning to the tag the code expects and pulling it:147 ```bash148 export SPECULOS_IMAGE_TAG=ghcr.io/ledgerhq/speculos:sha-e262a0c # matches libs/speculos-transport default149 docker pull ghcr.io/ledgerhq/speculos:sha-e262a0c150 ```151 `:latest` is a _moving_ tag and is NOT pinning; if you stay on it you must `docker pull` to refresh, and setting the env var alone never changes the cached image. (See "Speculos image" note below.)1524. **`WARNING: ... platform (linux/amd64) does not match host (arm64)` (benign)** — the Speculos image is amd64-only and runs under emulation on Apple Silicon. Not a failure; ignore.1535. **`speculos already in use` / `address already in use` / port conflicts (environment)** — stale Speculos containers from interrupted runs. Clean up: `docker rm -f $(docker ps -a --filter name=speculos -q)`.1546. **`TypeError: Invalid Version: DS_Store` (environment)** — `.DS_Store` files in `coin-apps` break semver lookup: `find "$COINAPPS" -name ".DS_Store" -type f -delete`.1557. **App + Speculos connect, but a specific Earn element isn't found (test-flow mismatch, not setup)** — e.g. expecting the legacy stake modal when the flow now redirects to the Earn dashboard, or a provider card id changed. Check feature flags (`FF_STAKE_PROGRAMS_MODAL` list vs redirects, `ethDepositCohort`) and the current test ids in the Earn web app. Report it as a test/scenario issue, not an environment one.156157**Speculos image note:** pin (`sha-...` / `v...`) for reproducible, comparable runs (this is what the bot/smoke CI jobs do); `:latest` tracks the current device/firmware matrix (what the broad e2e matrix uses) but drifts and must be re-pulled. A SHA hardcoded in your shell unblocks a run now but becomes a liability long-term as the code's expected image advances — prefer pinning per-run, or unset `SPECULOS_IMAGE_TAG` to follow the code's own default.158159## Validation before finishing (for code changes)160161Run validation for the impacted E2E package(s), not the whole monorepo:162163- Desktop: `pnpm --filter ledger-live-desktop-e2e-tests typecheck`; run targeted `earn.v2` / `delegate` Playwright tests when the environment allows.164- Mobile: `pnpm --filter ledger-live-mobile-e2e-tests typecheck`; run targeted Detox earn specs when the environment allows.165- Pre-commit runs `oxfmt` (formatting) + `gitleaks`. If `oxfmt` flags a file, apply exactly its suggested change.166167Reuse an existing watch terminal when available before starting a new watcher. If unrelated pre-existing failures appear, report them as pre-existing and keep scope focused on Earn changes.168169## PR review comment workflow170171For each comment:1721731. Verify current code and usages (`rg` for symbol usage).1742. Classify: real change needed / duplicate / stale (already addressed).1753. Implement only real changes.1764. Prepare explicit PR replies for stale/duplicate/already-fixed comments.177178## Quick checklist179180- [ ] Scope confirmed with the user (earn.v2 / delegate / both; desktop and/or mobile).181- [ ] Env verified (`MOCK=0`, `SEED` set but never printed, `COINAPPS`, pinned `SPECULOS_IMAGE_TAG`, `SPECULOS_DEVICE`); Docker running + image pulled.182- [ ] Build freshness checked; rebuilt only when needed (and confirmed with the user).183- [ ] Webview resolved via `webviewIdentifier = "earn"`; readiness via `goAndWaitForEarnToBeReady`.184- [ ] No inline repeated test ids in specs when a page object exists; no dead selectors/helpers added.185- [ ] Feature flags correct for the intended flow (`list` vs `redirects`, `ethDepositCohort`).186- [ ] Failures classified (environment vs build vs test-flow) with a concrete fix.187- [ ] Typecheck + relevant test commands attempted/reported.