test: drive WaitTx's timeout and failed-lookup exits end to end (closes #315)
Two Chrome end-to-end cases send ETH and leave the wait for its receipt running. In one, lookups answer "no receipt" until the 60-second deadline ends the wait with the timeout message. In the other, a new fixture switch makes every receipt lookup fail, and the sixth failure in a row ends the wait with the message naming the unreachable network. Both check the exact message and that Done returns to the address screen. Both wait in real time: Playwright's clock would apply to every later test, and backdating the stored broadcast time races the popup's own save. Model: opus-5-5
This commit is contained in:
@@ -376,6 +376,12 @@ reserve while sitting on the same side of the estimate, so swapping the two in
|
||||
what [#154](https://git.eeqj.de/sneak/AutistMask/issues/154) was, and it was
|
||||
previously correct by reading only.
|
||||
|
||||
It also covers both ways the wait for a sent transaction's receipt ends on the
|
||||
error screen: lookups that still find no receipt 60 seconds after the broadcast,
|
||||
and six lookups in a row that fail. Each must show its own message, and Done
|
||||
must lead back to the address screen. Both wait in real time, about a minute
|
||||
each.
|
||||
|
||||
It also covers the **dApp approval round trips** — the one place where the
|
||||
content script, the inpage provider, the background worker and the approval
|
||||
popup all have to work together. A local test page is served by the route
|
||||
@@ -640,7 +646,9 @@ suite outright if its network interception is not in effect.
|
||||
Measured on this repo's runner: `e2e-chrome` about 1m55s cold, almost all of it
|
||||
the one-time pull of the pinned ~800MB Playwright layer, and well under a minute
|
||||
once that layer is cached. `e2e-firefox` about 1m05s cold, and it caches its
|
||||
Firefox and geckodriver downloads the same way.
|
||||
Firefox and geckodriver downloads the same way. The `e2e-chrome` figures predate
|
||||
the two cases that wait for a receipt to end in error, which add about two
|
||||
minutes of real waiting.
|
||||
|
||||
### Element id guard (part of `make check`)
|
||||
|
||||
|
||||
@@ -45,6 +45,16 @@ but the review is broader than any of them.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-05: The Chrome end-to-end suite drives both ways the wait for a
|
||||
transaction's receipt ends on the error screen
|
||||
([#315](https://git.eeqj.de/sneak/AutistMask/issues/315)): lookups that still
|
||||
find no receipt 60 seconds after the broadcast end it with the timeout
|
||||
message, and six lookups that fail in a row end it with the message naming the
|
||||
unreachable network. Done then returns to the address screen. Both cases wait
|
||||
in real time, about a minute each. Playwright's clock would apply to every
|
||||
later test in the run and cannot be removed, and moving the stored broadcast
|
||||
time back can be undone by the save the popup makes every ten seconds.
|
||||
|
||||
- 2026-10-05: The Chrome end-to-end suite drives the private key export screen
|
||||
as it drives the recovery phrase screen
|
||||
([#253](https://git.eeqj.de/sneak/AutistMask/issues/253)): the correct
|
||||
|
||||
@@ -448,6 +448,16 @@ function rpcReply(req, opts, report) {
|
||||
return Object.assign(envelope, { result: ethCallResult(req, opts) });
|
||||
}
|
||||
if (req.method === "eth_getTransactionReceipt") {
|
||||
// A lookup that fails, which the wait screen counts differently from
|
||||
// one that answers "not mined yet" (README.md, WaitTx).
|
||||
if (opts.failReceiptLookup) {
|
||||
return Object.assign(envelope, {
|
||||
error: {
|
||||
code: -32000,
|
||||
message: "e2e fixture: receipt lookup failed",
|
||||
},
|
||||
});
|
||||
}
|
||||
const hash = Array.isArray(req.params) ? req.params[0] : null;
|
||||
return Object.assign(envelope, {
|
||||
result: opts.seedReceipt && hash ? transactionReceipt(hash) : null,
|
||||
@@ -607,6 +617,8 @@ function traceEnabled(raw) {
|
||||
* symbol is markup; read at request time.
|
||||
* @param {boolean} [opts.seedReceipt] answer eth_getTransactionReceipt with a
|
||||
* confirmed receipt instead of null, so a wait screen resolves.
|
||||
* @param {boolean} [opts.failReceiptLookup] answer eth_getTransactionReceipt
|
||||
* with an error, so every receipt lookup fails; read at request time.
|
||||
* @returns {Promise<{waitForServiceWorkerTraffic: (ms: number) =>
|
||||
* Promise<string|null>}>}
|
||||
*/
|
||||
|
||||
@@ -2699,6 +2699,84 @@ test("a token that lies about decimals() at signing time broadcasts nothing (#30
|
||||
}
|
||||
});
|
||||
|
||||
// ------------------------------ the wait for a receipt ending in error (#315)
|
||||
//
|
||||
// README.md (WaitTx) documents two ways the wait ends on the error screen: a
|
||||
// lookup that answers "no receipt" 60 seconds or more after the broadcast, and
|
||||
// six lookups in a row that fail. They are different facts with different
|
||||
// messages, so each is driven to its own.
|
||||
//
|
||||
// Both wait in real time, about a minute each. The wait reads the popup's own
|
||||
// clock and its own ten-second timer. Playwright's clock would move both, but
|
||||
// it is installed on the whole browser context and cannot be removed, so every
|
||||
// later test would run on it. Moving the stored broadcast time back instead can
|
||||
// be undone by the save the popup makes every ten seconds.
|
||||
|
||||
// Send ETH from the address screen and stop on the wait for its receipt.
|
||||
async function sendEthToWait(env) {
|
||||
await goToConfirm(env.page, {
|
||||
token: "ETH",
|
||||
balance: FUNDED_ETH_TEXT + " ETH",
|
||||
amount: COMFORTABLE_AMOUNT,
|
||||
});
|
||||
await waitForEstimate(env.page);
|
||||
await fillPasswordAndSend(env.page);
|
||||
await visible(env.page, "#view-wait-tx", 60000);
|
||||
}
|
||||
|
||||
test("a wait still without a receipt after 60 seconds ends on the timeout message (#315)", async (env) => {
|
||||
try {
|
||||
await sendEthToWait(env);
|
||||
// Lookups run every ten seconds and answer "no receipt", so the one
|
||||
// that ends the wait comes about 60 seconds after the broadcast.
|
||||
await visible(env.page, "#view-error-tx", 90000);
|
||||
const message = (
|
||||
await env.page.locator("#error-tx-message").innerText()
|
||||
).trim();
|
||||
assert(
|
||||
message ===
|
||||
"Transaction was not confirmed within 60 seconds. It may still confirm later — check Etherscan.",
|
||||
"the wait did not end on the timeout message: " +
|
||||
JSON.stringify(message),
|
||||
);
|
||||
await env.page.click("#btn-error-tx-done");
|
||||
await visible(env.page, "#view-address");
|
||||
} finally {
|
||||
await backToAddressAfterSend(env);
|
||||
}
|
||||
});
|
||||
|
||||
test("six failed receipt lookups in a row end on the unreachable-network message (#315)", async (env) => {
|
||||
// Each failed lookup is logged through log.errorf, i.e. console.error.
|
||||
// Exactly six are declared: a wait that ended sooner leaves one unmatched,
|
||||
// and one that went on logs a seventh, and either fails this test.
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
env.errors.expect(
|
||||
"failed receipt lookup " + i + " of 6",
|
||||
/poll receipt failed/,
|
||||
);
|
||||
}
|
||||
env.routeOpts.failReceiptLookup = true;
|
||||
try {
|
||||
await sendEthToWait(env);
|
||||
await visible(env.page, "#view-error-tx", 90000);
|
||||
const message = (
|
||||
await env.page.locator("#error-tx-message").innerText()
|
||||
).trim();
|
||||
assert(
|
||||
message ===
|
||||
"The network could not be reached to check this transaction — 6 lookups failed in a row. Check the RPC URL in Settings. The transaction may still have confirmed — check Etherscan.",
|
||||
"the wait did not end on the unreachable-network message: " +
|
||||
JSON.stringify(message),
|
||||
);
|
||||
await env.page.click("#btn-error-tx-done");
|
||||
await visible(env.page, "#view-address");
|
||||
} finally {
|
||||
env.routeOpts.failReceiptLookup = false;
|
||||
await backToAddressAfterSend(env);
|
||||
}
|
||||
});
|
||||
|
||||
// ------------------------------------------- hostile token symbol (#307)
|
||||
//
|
||||
// The reproduction from the issue, in the real browser against the real
|
||||
@@ -4302,6 +4380,8 @@ async function main() {
|
||||
// Whether eth_getTransactionReceipt confirms a transaction rather than
|
||||
// answering "not mined yet".
|
||||
seedReceipt: false,
|
||||
// Whether eth_getTransactionReceipt fails instead of answering (#315).
|
||||
failReceiptLookup: false,
|
||||
// Every raw signed transaction handed to eth_sendRawTransaction, in
|
||||
// order. The dApp transaction round trip asserts against these bytes
|
||||
// rather than against anything the extension reported about them.
|
||||
|
||||
Reference in New Issue
Block a user