Skip to content

Commit a270fa3

Browse files
authored
Merge pull request #233 from mcode-webui/fix/browser-provider-picker-animation-flake
test(webui): read the provider picker's exit animation where it changes
2 parents 2bd9a2a + ffaee4a commit a270fa3

1 file changed

Lines changed: 46 additions & 3 deletions

File tree

‎packages/webui/test/browser/provider-form.spec.mjs‎

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,43 @@ async function fillNewProvider(page, dialog) {
102102
await dialog.getByLabel("模型名称").fill("draft-model");
103103
}
104104

105+
/**
106+
* Records what the provider picker's popup carried, in the page.
107+
*
108+
* The exit animation is a 120ms window: the popup starts leaving, the
109+
* stylesheet fades it out, and the skeleton unmounts it when the fade is
110+
* over. A reading taken across the wire can arrive after that unmount — and a
111+
* popup that is no longer in the document has no computed style to read, so
112+
* the reading comes back empty and the test fails on a state it never saw.
113+
* It can also arrive before the popup has started leaving, which reads as
114+
* the entrance animation.
115+
*
116+
* So the reading is taken where the state changes: the page watches the
117+
* popup's own class and records the popup's computed animation-name at the
118+
* moment that class changes. The record outlives the popup, so the test can
119+
* read it whenever it gets there, and it cannot miss the window.
120+
*/
121+
async function recordProviderPickerMotion(page) {
122+
await page.evaluate(() => {
123+
const selector = '[role="listbox"][aria-label="提供商列表"]';
124+
window.__webuiProviderPickerMotion = [];
125+
new MutationObserver((records) => {
126+
for (const record of records) {
127+
const node = record.target;
128+
if (!(node instanceof Element) || !node.matches(selector)) continue;
129+
window.__webuiProviderPickerMotion.push({
130+
leaving: node.classList.contains("is-leaving"),
131+
animationName: getComputedStyle(node).animationName,
132+
});
133+
}
134+
}).observe(document.documentElement, {
135+
attributes: true,
136+
subtree: true,
137+
attributeFilter: ["class"],
138+
});
139+
});
140+
}
141+
105142
test("the custom tab lists the saved provider and its model row", async ({ page }) => {
106143
await configureFixture(page, seedProviderSurface);
107144
await openCustomModels(page);
@@ -180,12 +217,18 @@ test.describe("with motion allowed", () => {
180217
// opens and closes the same way.
181218
expect(await listbox.evaluate((node) => getComputedStyle(node).animationName)).toBe("webui-dropdown-in");
182219

220+
// The recorder is in place before the press, so what it records is this
221+
// press's leaving.
222+
await recordProviderPickerMotion(page);
183223
await page.mouse.click(6, 6);
224+
184225
// While it is fading out it is still mounted, and it carries the exit
185-
// animation rather than the entrance one.
226+
// animation rather than the entrance one. The reading is the one the page
227+
// took at the moment the popup started leaving — not a sample of a popup
228+
// that has already been unmounted.
186229
await expect
187-
.poll(() => listbox.evaluate((node) => getComputedStyle(node).animationName).catch(() => "gone"))
188-
.toBe("webui-dropdown-out");
230+
.poll(() => page.evaluate(() => (window.__webuiProviderPickerMotion ?? []).find((entry) => entry.leaving) ?? null))
231+
.toEqual({ leaving: true, animationName: "webui-dropdown-out" });
189232
await expect(listbox).toHaveCount(0);
190233
});
191234
});

0 commit comments

Comments
 (0)