Merge pull request 'test(e2e): make the resend allowance failure say why (#257)' (#319) from fix/257-resend-verification-flake into main
Reviewed-on: #319
This commit was merged in pull request #319.
This commit is contained in:
@@ -22,24 +22,70 @@ test.describe('Resending your verification email', () => {
|
|||||||
// The message the customer gets on the fourth attempt is the point of the
|
// The message the customer gets on the fourth attempt is the point of the
|
||||||
// limiter's copy: it says the mail probably did send and where to look,
|
// limiter's copy: it says the mail probably did send and where to look,
|
||||||
// rather than only that a limit exists.
|
// rather than only that a limit exists.
|
||||||
|
//
|
||||||
|
// ## Why this asserts on responses rather than on what is on screen (#257)
|
||||||
|
//
|
||||||
|
// This test failed roughly one full-suite run in three, never in isolation,
|
||||||
|
// with a strict-mode violation: `getByText(/already sent several/)` resolved
|
||||||
|
// to three refusal toasts where one was expected. Two investigations could
|
||||||
|
// not establish why, and the second corrected the arithmetic the first
|
||||||
|
// relied on — antd toasts auto-dismiss, so **the number visible at the moment
|
||||||
|
// of an assertion is a lower bound on how many refusals happened, not a
|
||||||
|
// count**. Three visible refusals is equally consistent with four where the
|
||||||
|
// first had already faded.
|
||||||
|
//
|
||||||
|
// That makes toast-counting the wrong instrument twice over: it is
|
||||||
|
// timing-dependent, and `toBeVisible()` on a multi-match locator fails in
|
||||||
|
// strict mode even when the behaviour was correct. The responses are the
|
||||||
|
// behaviour; the toasts are a lossy rendering of it.
|
||||||
|
//
|
||||||
|
// So the sequence of statuses is recorded and asserted directly. If this
|
||||||
|
// fails again it now says which failure it is, which is exactly what #257
|
||||||
|
// could not determine: `[429, 429, 429, 429]` means the customer's bucket
|
||||||
|
// already held hits before the test clicked anything, while more than four
|
||||||
|
// entries means the UI sent more requests than there were clicks. Either
|
||||||
|
// answer identifies the mechanism from a single failing run, rather than
|
||||||
|
// needing a temporary probe re-added and the suite run until it fails again.
|
||||||
test('says something useful once the allowance runs out', async ({
|
test('says something useful once the allowance runs out', async ({
|
||||||
page,
|
page,
|
||||||
customer,
|
customer,
|
||||||
accountModal
|
accountModal
|
||||||
}) => {
|
}) => {
|
||||||
|
const isResend = (url: string) =>
|
||||||
|
new URL(url).pathname === '/api/customers/resend-verification';
|
||||||
|
|
||||||
|
// Recorded from the first navigation onward, deliberately: a request the
|
||||||
|
// test did not make is one of the explanations still open, and a recorder
|
||||||
|
// installed after the clicks could not see it.
|
||||||
|
const statuses: number[] = [];
|
||||||
|
page.on('response', (res) => {
|
||||||
|
if (isResend(res.url())) statuses.push(res.status());
|
||||||
|
});
|
||||||
|
|
||||||
await accountModal.open();
|
await accountModal.open();
|
||||||
|
|
||||||
const resend = accountModal.resendVerificationButton;
|
const resend = accountModal.resendVerificationButton;
|
||||||
for (let i = 0; i < 3; i++) {
|
// Each click waits for its own response before the next. The previous
|
||||||
await resend.click();
|
// `await expect(resend).toBeEnabled()` looked like pacing but was a no-op —
|
||||||
await expect(resend).toBeEnabled();
|
// the button is never disabled, so it returned immediately and left four
|
||||||
|
// requests racing. Waiting on the response removes concurrency as a
|
||||||
|
// variable, so a failure here cannot be explained by ordering.
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForResponse((res) => isResend(res.url())),
|
||||||
|
resend.click()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await resend.click();
|
// The allowance is three an hour, so three sends and one refusal. The
|
||||||
|
// message names the customer because a bucket carrying hits from somewhere
|
||||||
|
// else is the open question, and the address is what identifies whose.
|
||||||
|
expect(statuses, `resend responses for ${customer.email}`).toEqual([204, 204, 204, 429]);
|
||||||
|
|
||||||
// Matched on the phrase unique to the refusal. "spam folder" appears in the
|
// The copy itself still matters — it is what this test is named for. Scoped
|
||||||
// success message too, and the three stacked success toasts from the loop
|
// with `.first()` because strict mode treats several identical toasts as
|
||||||
// above are still on screen, so the looser match finds them instead.
|
// ambiguous, and how many are still on screen is a timing detail this
|
||||||
await expect(page.getByText(/already sent several/)).toBeVisible();
|
// assertion should not depend on.
|
||||||
|
await expect(page.getByText(/already sent several/).first()).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user