test: cover Add Token's confirm, TransactionDetail from the token screen, and the token explorer link #466

Merged
clawbot merged 1 commits from issue-295-e2e-nav-coverage into next 2026-10-05 13:59:19 +02:00
3 changed files with 129 additions and 6 deletions
+11
View File
@@ -45,6 +45,17 @@ but the review is broader than any of them.
# Completed Steps
- 2026-10-05: The Chrome end-to-end suite covers the last of the
[#150](https://git.eeqj.de/sneak/AutistMask/issues/150) and
[#151](https://git.eeqj.de/sneak/AutistMask/issues/151) items
([#295](https://git.eeqj.de/sneak/AutistMask/issues/295)): a token added on
Add Token by its contract address is listed on the address screen;
TransactionDetail opened from the token screen leaves that screen on the
persisted navigation stack, and Back returns to it; and the token contract row
links to the explorer's token page, read off the link rather than followed.
The network stub answers `symbol()` and `name()` for the stub token, which
adding it reads.
- 2026-10-05: Each control that leads to a signature or to the private key has a
test that it refuses a defective wallet before asking for a password
([#254](https://git.eeqj.de/sneak/AutistMask/issues/254)): Send on the main,
+18 -6
View File
@@ -22,7 +22,7 @@
"use strict";
const { Transaction } = require("ethers");
const { AbiCoder, Transaction } = require("ethers");
// Fictional ERC-20 used to seed the transaction-detail test. The symbol
// must not collide with any entry in src/shared/tokenList.js, or
@@ -244,12 +244,16 @@ function latestBlock() {
};
}
// keccak("decimals()")[0:4].
// keccak("decimals()")[0:4], and the same for symbol() and name().
const SELECTOR_DECIMALS = "0x313ce567";
const SELECTOR_SYMBOL = "0x95d89b41";
const SELECTOR_NAME = "0x06fdde03";
// Every eth_call still answers with a zero word except decimals() on the
// stub token, which the wallet reads back at signing time to compare with
// the scale the confirmation screen rendered (issue #305).
// Every eth_call still answers with a zero word except decimals(), symbol()
// and name() on the stub token. The wallet reads decimals() back at signing
// time to compare with the scale the confirmation screen rendered (issue
// #305). Adding the token by its contract address reads all three (issue
// #295); symbol() and name() answer what the explorer reports for it.
//
// opts.tokenDecimalsOverride is the lying contract: set it and decimals()
// answers something other than the value this same fixture reports through
@@ -263,9 +267,17 @@ function ethCallResult(req, opts) {
if (!call || typeof call !== "object") return ZERO_WORD;
const data = String(call.data || call.input || "").toLowerCase();
const to = String(call.to || "").toLowerCase();
if (data.startsWith(SELECTOR_DECIMALS) && to === STUB_TOKEN.address) {
if (to !== STUB_TOKEN.address) return ZERO_WORD;
if (data.startsWith(SELECTOR_DECIMALS)) {
return word(opts.tokenDecimalsOverride ?? STUB_TOKEN.decimals);
}
const abi = AbiCoder.defaultAbiCoder();
if (data.startsWith(SELECTOR_SYMBOL)) {
return abi.encode(["string"], [tokenObject(opts).symbol]);
}
if (data.startsWith(SELECTOR_NAME)) {
return abi.encode(["string"], [tokenObject(opts).name]);
}
return ZERO_WORD;
}
+100
View File
@@ -596,6 +596,106 @@ test("tap-to-copy on the transaction detail screen copies the address (#151)", a
);
});
// ------------------------- the last of the #150 and #151 items (#295)
//
// Add Token's confirm button, TransactionDetail opened from the token screen
// and Back from it, and the explorer link on the token contract row.
// The stub token stays tracked for the rest of the run: the next test reaches
// its token screen through the balance row this one adds.
test("a token added by its contract address is listed on the address screen (#150)", async (env) => {
await leaveTransactionDetail(env.page);
await env.page.click("#btn-add-token");
await visible(env.page, "#view-add-token");
await env.page.fill("#add-token-address", STUB_TOKEN.address);
await env.page.click("#btn-add-token-confirm");
await visible(env.page, "#view-address");
// No wait: the confirm renders the balance list before it shows the
// screen, and nothing renders the list again while the screen is up.
const row = env.page.locator(
'#address-balances [data-token="' + STUB_TOKEN.address + '"]',
{ hasText: STUB_TOKEN.symbol },
);
const balances = await env.page.locator("#address-balances").innerText();
assert(
(await row.count()) === 1,
"the balance list has no " +
STUB_TOKEN.symbol +
" row for the token just added: " +
JSON.stringify(balances),
);
});
// TransactionDetail looks the same from either entry point. Only the
// persisted stack says which one opened it, so that is what is asserted: from
// the token screen it ends in "address-token", and Back has to land there
// rather than on the address screen beneath it.
test("transaction detail opened from the token screen goes Back to it (#151)", async (env) => {
await goHome(env.page);
const base = await persistedViewStack(env.page);
await env.page.locator("#wallet-list .btn-addr-info").first().click();
await visible(env.page, "#view-address");
await env.page
.locator('#address-balances [data-token="' + STUB_TOKEN.address + '"]')
.click();
await visible(env.page, "#view-address-token");
const row = env.page.locator("#address-token-tx-list .tx-row").first();
await row.waitFor({ state: "visible", timeout: 30000 });
await row.click();
await visible(env.page, "#view-transaction");
await waitForPersisted(
env.page,
"viewStack",
base.concat("main", "address", "address-token"),
"on transaction detail opened from the token screen",
);
// The stack is checked before the screen, so a Back that lands on the
// wrong screen fails by saying what the stack holds.
await env.page.click("#btn-tx-back");
await waitForPersisted(
env.page,
"viewStack",
base.concat("main", "address"),
"after Back from transaction detail",
);
await visible(env.page, "#view-address-token");
// Onto the address screen, which the next test starts from.
await env.page.click("#btn-address-token-back");
await visible(env.page, "#view-address");
});
// Read off the anchor rather than followed: where it points is all the popup
// decides, and following it would only load the explorer's page. The suite is
// on mainnet until the Settings section.
test("the token contract row links to the explorer's token page (#151)", async (env) => {
await leaveTransactionDetail(env.page);
const row = env.page
.locator("#tx-list .tx-row")
.filter({ hasText: STUB_TOKEN.symbol });
await row.waitFor({ state: "visible", timeout: 30000 });
await row.click();
await visible(env.page, "#view-transaction");
await visible(env.page, "#tx-detail-token-contract-section");
const href = await env.page
.locator("#tx-detail-token-contract a")
.getAttribute("href");
const expected = "https://etherscan.io/token/" + STUB_TOKEN.address;
assert(
href === expected,
"the token contract row links to " +
JSON.stringify(href) +
", expected " +
expected,
);
});
// -------------------------------------------- recovery phrase (#161)
// The gear toggles, so pressing it while Settings is already up leaves it.