test(e2e): stabilize virtualized pagination and compaction checks (#7244)

* test(e2e): stabilize virtualized pagination and compaction checks

* test: register the scoped E2E guidance file
This commit is contained in:
Willem Jiang 2026-10-21 18:58:53 +08:00 committed by GitHub
parent 033fb1aa1f
commit 4b7f2454fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 46 additions and 22 deletions

View File

@ -54,6 +54,7 @@ EXPECTED_GUIDANCE_PATHS = {
"backend/packages/harness/deerflow/utils/AGENTS.md",
"frontend/src/AGENTS.md",
"frontend/src/core/memory/AGENTS.md",
"frontend/tests/e2e/AGENTS.md",
"scripts/AGENTS.md",
"examples/deerflow-extension-agent-teams/AGENTS.md",
"examples/deerflow-extension-workflows/AGENTS.md",

View File

@ -0,0 +1,10 @@
# Browser E2E tests
Virtualized lists do not keep every fetched row mounted. Assert the pagination
request, then filter or scroll the loaded row into the rendered window before
checking its content. Preserve the real scroll trigger and search-mode guard.
For message ordering across compaction/refetch, poll an atomic DOM comparison
until both nodes settle. Missing or reversed nodes must still fail; keep history,
refresh and hidden-summary assertions. Avoid fixed sleeps and whole-test retries
as substitutes for synchronization.

View File

@ -64,6 +64,7 @@ test.describe("Thread list infinite scroll (issue #3482)", () => {
test("chats list page loads more threads when scrolling to the bottom", async ({
page,
}) => {
const offsets = observeSearchOffsets(page);
mockLangGraphAPI(page, { threads: THREADS });
await page.goto("/workspace/chats");
@ -80,7 +81,12 @@ test.describe("Thread list infinite scroll (issue #3482)", () => {
// Scrolling the sentinel into view triggers the next page.
const sentinel = page.getByTestId("chats-page-sentinel");
await sentinel.scrollIntoViewIfNeeded();
await expect.poll(() => offsets).toContain(PAGE_SIZE);
// Crossing the virtualization threshold can unmount Conversation 051
// while the scroll anchor stays near the bottom of the expanded list.
// Filter the fetched rows to verify the next page reached the UI.
await page.getByPlaceholder("Search chats").fill(SECOND_PAGE_FIRST);
await expect(main.getByText(SECOND_PAGE_FIRST)).toBeVisible({
timeout: 15_000,
});

View File

@ -395,28 +395,35 @@ test.describe("Thread message ordering", () => {
});
// DOM relative order, not viewport coordinates: stick-to-bottom smooth
// scrolling makes two separate boundingBox reads race each other.
const questionBeforeAnswer = await page.evaluate(() => {
const list = document.querySelector('[data-testid="main-message-list"]');
if (!list) {
return null;
}
const leaf = (text: string) =>
[...list.querySelectorAll("div, p")].find(
(element) =>
element.children.length === 0 && element.textContent === text,
);
const question = leaf("final-turn question");
const answer = leaf("final-turn answer");
if (!question || !answer) {
return null;
}
return (
(question.compareDocumentPosition(answer) &
Node.DOCUMENT_POSITION_FOLLOWING) !==
0
);
});
expect(questionBeforeAnswer).toBe(true);
// Compaction and the finishing history refetch can briefly replace
// these nodes. Re-read both in one DOM snapshot until they settle.
await expect
.poll(() =>
page.evaluate(() => {
const list = document.querySelector(
'[data-testid="main-message-list"]',
);
if (!list) {
return null;
}
const leaf = (text: string) =>
[...list.querySelectorAll("div, p")].find(
(element) =>
element.children.length === 0 && element.textContent === text,
);
const question = leaf("final-turn question");
const answer = leaf("final-turn answer");
if (!question || !answer) {
return null;
}
return (
(question.compareDocumentPosition(answer) &
Node.DOCUMENT_POSITION_FOLLOWING) !==
0
);
}),
)
.toBe(true);
// The finishing refetch observed the extended feed; the established
// order — including the compacted head — is unchanged.