Skip to content

Commit e487cd7

Browse files
rrewllclaude
andauthored
test(webview): finish CSS transitions after the visual theme changes (#2327)
* test(webview): finish CSS transitions after the visual theme changes applyVisualTheme changes the theme classes after the story mounts. The Button transition-colors class then changes the border color over about 150 ms. expectContrast can read a color between the two themes, so the WelcomeViewProvider contrast check fails at random. Finish each running CSSTransition after the theme classes change. Other animation types stay as they are, because finishing an infinite CSS animation throws an error. Fixes #2326 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: add e2e test for finishing transitions * fix: setup the long transition * test(webview): keep infinite CSS animations running across theme switches applyVisualTheme finishes only CSS transitions, because finish() throws on an infinite animation. Add an infinite animation to the visual theme test and check that it is still running after the theme switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent a0d03a0 commit e487cd7

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

‎webview-ui/playwright/themes.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,11 @@ export async function applyVisualTheme(page: Page, theme: VisualTheme) {
2424
document.body.className = bodyClass
2525
document.body.removeAttribute("style")
2626
document.body.dataset.vscodeThemeId = themeId
27+
28+
for (const animation of document.getAnimations()) {
29+
if (animation instanceof CSSTransition) {
30+
animation.finish()
31+
}
32+
}
2733
}, theme)
2834
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { expect, test } from "../../../../playwright/coverage-fixture"
2+
import { mountedStory } from "../../../../playwright/mounted-story"
3+
import { applyVisualTheme, visualThemes } from "../../../../playwright/themes"
4+
5+
test("finishes theme color transitions before contrast checks read them", async ({ mount, page }) => {
6+
const component = mountedStory(await mount("welcome"))
7+
const action = component.getByRole("button", { name: /provider/i }).first()
8+
await expect(action).toBeVisible()
9+
10+
// Stretch the about 150 ms transition so it cannot end on its own before the assertions read it.
11+
// Reading the computed style flushes it, so the theme switch below starts the long transition.
12+
const transitionDuration = await action.evaluate((element) => {
13+
element.style.transitionDuration = "10s"
14+
return getComputedStyle(element).transitionDuration
15+
})
16+
expect(transitionDuration).toBe("10s")
17+
18+
// finish() throws on an infinite animation, so the theme switch must leave CSS animations running.
19+
await page.evaluate(() => {
20+
const style = document.createElement("style")
21+
style.id = "visual-theme-infinite-animation"
22+
style.textContent =
23+
"@keyframes visual-theme-pulse { to { opacity: 0.5; } }" +
24+
"#visual-theme-infinite-animation-probe { animation: visual-theme-pulse 1s infinite; }"
25+
const probe = document.createElement("div")
26+
probe.id = "visual-theme-infinite-animation-probe"
27+
document.head.append(style)
28+
document.body.append(probe)
29+
})
30+
31+
// The gallery starts in the dark theme, so this switch starts the Button border-color transition.
32+
await applyVisualTheme(
33+
page,
34+
visualThemes.find((theme) => theme.name === "high-contrast-light")!,
35+
)
36+
37+
const { runningTransitions, runningAnimations } = await page.evaluate(() => {
38+
const running = document.getAnimations().filter((animation) => animation.playState === "running")
39+
return {
40+
runningTransitions: running.filter((animation) => animation instanceof CSSTransition).length,
41+
runningAnimations: running.filter(
42+
(animation) => animation instanceof CSSAnimation && animation.animationName === "visual-theme-pulse",
43+
).length,
44+
}
45+
})
46+
expect(runningTransitions).toBe(0)
47+
expect(runningAnimations).toBe(1)
48+
// Read once instead of toHaveCSS, which retries until the transition ends on its own.
49+
expect(await action.evaluate((element) => getComputedStyle(element).borderTopColor)).toBe("rgb(15, 74, 133)")
50+
51+
await page.evaluate(() => {
52+
document.getElementById("visual-theme-infinite-animation")?.remove()
53+
document.getElementById("visual-theme-infinite-animation-probe")?.remove()
54+
})
55+
})

0 commit comments

Comments
 (0)