test(recipes): fiabilise le scénario 3 pages (scroll réel + log de curseurs)
Le scénario échouait en CI sur deux points de fragilité, pas sur le fond :
1. `IntersectionObserver` ne se redéclenche qu'à une *transition*
d'intersection — avec des pages courtes la sentinelle restait visible en
continu et seule la 2e page se chargeait. Les pages font maintenant 15
éléments (la liste déborde son conteneur) et chaque page suivante est
tirée par un `scrollTo("bottom")` explicite, répété 5 fois.
2. Une fois la liste débordante, `should("be.visible")` sur un élément
sorti du conteneur `overflow:auto` échoue (même classe de bug que #1) —
l'assertion "3e page chargée" fait maintenant `.scrollIntoView()` avant.
L'assertion anti-boucle porte sur le journal des curseurs demandés
(tolérance 2× pour le double-effet React StrictMode du serveur e2e), pas
sur un `Set.size` strict.
Vérifié localement : 7/7 avec le correctif, "Page 3 Item 1" jamais chargée
sans lui.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
c842fbb348
commit
1cd886cb8a
2 changed files with 33 additions and 15 deletions
|
|
@ -64,11 +64,9 @@ Feature: Browsing external recipe sources
|
||||||
When I visit "/recettes"
|
When I visit "/recettes"
|
||||||
And I click the button "TheMealDB"
|
And I click the button "TheMealDB"
|
||||||
Then I should see the source item "Page 1 Item 1"
|
Then I should see the source item "Page 1 Item 1"
|
||||||
When I scroll the source list to the bottom
|
When I scroll the source list to the bottom 5 times
|
||||||
Then I should see the source item "Page 2 Item 1"
|
Then the third page's items should have loaded
|
||||||
When I scroll the source list to the bottom
|
And no browse cursor should have been requested more than twice
|
||||||
Then I should see the source item "Page 3 Item 1"
|
|
||||||
And no browse cursor should have been requested more than once
|
|
||||||
|
|
||||||
Scenario: Offers a retry when loading the next page fails
|
Scenario: Offers a retry when loading the next page fails
|
||||||
Given the recipe catalog contains nothing
|
Given the recipe catalog contains nothing
|
||||||
|
|
|
||||||
|
|
@ -174,18 +174,38 @@ Given("browsing TheMealDB returns three pages, each addressed by its own cursor"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
When("I scroll the source list to the bottom", () => {
|
When("I scroll the source list to the bottom {int} times", (times: number) => {
|
||||||
|
for (let attempt = 0; attempt < times; attempt += 1) {
|
||||||
cy.get(".source-items-column .recipe-table-wrap").scrollTo("bottom", {
|
cy.get(".source-items-column .recipe-table-wrap").scrollTo("bottom", {
|
||||||
ensureScrollable: false,
|
ensureScrollable: false,
|
||||||
});
|
});
|
||||||
// IntersectionObserver callbacks are delivered on their own task, and the
|
// IntersectionObserver callbacks land on their own task, and the next
|
||||||
// next page's fetch + merge then needs a render — give both a beat.
|
// page's fetch + merge then needs a render — give both a beat before
|
||||||
cy.wait(400);
|
// the next scroll (which needs fresh content below it to have anything
|
||||||
|
// new to reveal).
|
||||||
|
cy.wait(350);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("no browse cursor should have been requested more than once", () => {
|
Then("the third page's items should have loaded", () => {
|
||||||
const seen = [...threePageCursorsRequested];
|
// Page 3's marker sits ~30 rows down inside the list's own overflow
|
||||||
expect(seen.length, `cursors requested: ${JSON.stringify(seen)}`).to.equal(new Set(seen).size);
|
// container — scroll it in before asserting, like `common.steps.ts`'s
|
||||||
|
// ingredient-row check does. With the stale-closure loop it never loads
|
||||||
|
// at all: `handleLoadMore` keeps re-requesting cursor-2.
|
||||||
|
cy.contains(".recipe-table__name", "Page 3 Item 1").scrollIntoView().should("be.visible");
|
||||||
|
});
|
||||||
|
|
||||||
|
Then("no browse cursor should have been requested more than twice", () => {
|
||||||
|
const counts = new Map<string, number>();
|
||||||
|
for (const cursor of threePageCursorsRequested) {
|
||||||
|
counts.set(cursor, (counts.get(cursor) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
// React StrictMode double-invokes effects in the e2e dev server (see
|
||||||
|
// recipes.cy.ts), so the panel's initial fetch + one-page-ahead prefetch
|
||||||
|
// can each fire twice — hence "twice", not "once". The issue #38 loop
|
||||||
|
// blows well past that: every scroll re-requests the same stale cursor.
|
||||||
|
const offenders = [...counts.entries()].filter(([, n]) => n > 2);
|
||||||
|
expect(offenders, `cursor request counts: ${JSON.stringify([...counts])}`).to.have.length(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// The panel prefetches the next page as soon as page 1 is on screen (before
|
// The panel prefetches the next page as soon as page 1 is on screen (before
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue