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
|
what [#154](https://git.eeqj.de/sneak/AutistMask/issues/154) was, and it was
|
||||||
previously correct by reading only.
|
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
|
It also covers the **dApp approval round trips** — the one place where the
|
||||||
content script, the inpage provider, the background worker and the approval
|
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
|
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
|
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
|
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
|
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`)
|
### Element id guard (part of `make check`)
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# 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: Each control that leads to a signature or to the private key has a
|
- 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
|
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,
|
([#254](https://git.eeqj.de/sneak/AutistMask/issues/254)): Send on the main,
|
||||||
|
|||||||
@@ -448,6 +448,16 @@ function rpcReply(req, opts, report) {
|
|||||||
return Object.assign(envelope, { result: ethCallResult(req, opts) });
|
return Object.assign(envelope, { result: ethCallResult(req, opts) });
|
||||||
}
|
}
|
||||||
if (req.method === "eth_getTransactionReceipt") {
|
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;
|
const hash = Array.isArray(req.params) ? req.params[0] : null;
|
||||||
return Object.assign(envelope, {
|
return Object.assign(envelope, {
|
||||||
result: opts.seedReceipt && hash ? transactionReceipt(hash) : null,
|
result: opts.seedReceipt && hash ? transactionReceipt(hash) : null,
|
||||||
@@ -607,6 +617,8 @@ function traceEnabled(raw) {
|
|||||||
* symbol is markup; read at request time.
|
* symbol is markup; read at request time.
|
||||||
* @param {boolean} [opts.seedReceipt] answer eth_getTransactionReceipt with a
|
* @param {boolean} [opts.seedReceipt] answer eth_getTransactionReceipt with a
|
||||||
* confirmed receipt instead of null, so a wait screen resolves.
|
* 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) =>
|
* @returns {Promise<{waitForServiceWorkerTraffic: (ms: number) =>
|
||||||
* Promise<string|null>}>}
|
* 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)
|
// ------------------------------------------- hostile token symbol (#307)
|
||||||
//
|
//
|
||||||
// The reproduction from the issue, in the real browser against the real
|
// 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
|
// Whether eth_getTransactionReceipt confirms a transaction rather than
|
||||||
// answering "not mined yet".
|
// answering "not mined yet".
|
||||||
seedReceipt: false,
|
seedReceipt: false,
|
||||||
|
// Whether eth_getTransactionReceipt fails instead of answering (#315).
|
||||||
|
failReceiptLookup: false,
|
||||||
// Every raw signed transaction handed to eth_sendRawTransaction, in
|
// Every raw signed transaction handed to eth_sendRawTransaction, in
|
||||||
// order. The dApp transaction round trip asserts against these bytes
|
// order. The dApp transaction round trip asserts against these bytes
|
||||||
// rather than against anything the extension reported about them.
|
// rather than against anything the extension reported about them.
|
||||||
|
|||||||
Reference in New Issue
Block a user