Jelajahi Sumber

Improve Playwright Screenshot flakes (#1629)

* feat(playwright): add environment variables to make playwright screenshots less flakey

* feat(playwright): improve test stability by seeding user settings and closing tabs before screenshotting

This commit introduces several changes to improve the stability and reliability of Playwright E2E tests:

- **Seed User Settings**: A new `seedUserSettings` function is added to `playwright-base-test.ts` to pre-configure VS Code user settings. This hides elements like the "Get Started" page, release notes, and recommendations, which can cause flakiness in tests.
- **Close All Tabs Helper**: A `closeAllTabs` helper function is introduced in `webview-helpers.ts` to ensure all open editor tabs are closed before taking screenshots. This prevents visual inconsistencies and flakiness in screenshot comparisons.
- **Removed Flaky Command Palette Test**: The command palette test in `sanity.test.ts` was removed as it was contributing to test flakiness and is not critical for basic sanity checks.

These changes aim to create a more consistent and predictable testing environment, reducing intermittent failures.

* fix(playwright): remove unnecessary VSCode launch arguments

Removes arguments that are either redundant or cause issues in Playwright E2E tests, such as crash reporting, backgrounding, and translate UI features. This helps streamline the test environment.

* test(playwright): Cleanup

Refactor `closeAllTabs` helper to use more descriptive variable names for Playwright locators and clarify the keyboard command used.

* fix(playwright): click mouse after moving to avoid tooltip in settings tests

* fix(playwright): wait for extension activation before screenshot

The `takeScreenshot` fixture now waits for the "Activating Extensions" status message to disappear before taking a screenshot. This prevents flakiness in tests where the screenshot is taken before the VS Code environment is fully ready, leading to incomplete or incorrect screenshots.

* test(playwright): add timeouts to stabilize chat tests

* test(playwright): rename settings test and add index to screenshot name

---------

Co-authored-by: Chris Hasson <[email protected]>
Chris Hasson 6 bulan lalu
induk
melakukan
1b19caf0db

+ 15 - 0
apps/playwright-e2e/helpers/webview-helpers.ts

@@ -3,6 +3,8 @@ import { type Page, type FrameLocator, expect } from "@playwright/test"
 import type { WebviewMessage } from "../../../src/shared/WebviewMessage"
 import { ProviderSettings } from "@roo-code/types"
 
+const modifier = process.platform === "darwin" ? "Meta" : "Control"
+
 const defaultPlaywrightApiConfig = {
 	apiProvider: "openrouter" as const,
 	openRouterApiKey: process.env.OPENROUTER_API_KEY,
@@ -83,3 +85,16 @@ export async function configureApiKeyThroughUI(page: Page): Promise<void> {
 	await submitButton.click()
 	console.log("✅ Provider configured!")
 }
+
+export async function closeAllTabs(page: Page): Promise<void> {
+	const tabs = page.locator(".tab a.label-name")
+	const count = await tabs.count()
+	if (count > 0) {
+		// Close all editor tabs using the default keyboard command [Cmd+K Cmd+W]
+		await page.keyboard.press(`${modifier}+K`)
+		await page.keyboard.press(`${modifier}+W`)
+
+		const dismissedTabs = page.locator(".tab a.label-name")
+		await expect(dismissedTabs).not.toBeVisible()
+	}
+}

+ 3 - 1
apps/playwright-e2e/tests/chat-with-response.test.ts

@@ -6,12 +6,14 @@ test.describe("E2E Chat Test", () => {
 	test("should configure credentials and send a message", async ({ workbox: page, takeScreenshot }: TestFixtures) => {
 		await verifyExtensionInstalled(page)
 		await waitForWebviewText(page, "Welcome to Kilo Code!")
+
+		await page.waitForTimeout(1000) // Let the page settle to avoid flakes
 		await takeScreenshot("welcome")
 
 		await configureApiKeyThroughUI(page)
 		await waitForWebviewText(page, "Generate, refactor, and debug code with AI assistance")
 
-		await page.waitForTimeout(1000) // Let the page settle to avoid flakey screenshots
+		await page.waitForTimeout(1000) // Let the page settle to avoid flakes
 		await takeScreenshot("ready-to-chat")
 
 		// Don't take any more screenshots after the reponse starts-

+ 44 - 3
apps/playwright-e2e/tests/playwright-base-test.ts

@@ -7,6 +7,7 @@ import * as fs from "fs"
 import { fileURLToPath } from "url"
 import { camelCase } from "change-case"
 import { setupConsoleLogging, cleanLogMessage } from "../helpers/console-logging"
+import { closeAllTabs } from "../helpers"
 
 // ES module equivalent of __dirname
 const __filename = fileURLToPath(import.meta.url)
@@ -33,6 +34,8 @@ export const test = base.extend<TestFixtures>({
 		}
 
 		const defaultCachePath = await createTempDir()
+		const userDataDir = path.join(defaultCachePath, "user-data")
+		seedUserSettings(userDataDir)
 
 		// Use the pre-downloaded VS Code from global setup
 		const vscodePath = process.env.VSCODE_EXECUTABLE_PATH
@@ -42,6 +45,12 @@ export const test = base.extend<TestFixtures>({
 
 		const electronApp = await _electron.launch({
 			executablePath: vscodePath,
+			env: {
+				...process.env,
+				VSCODE_SKIP_GETTING_STARTED: "1",
+				VSCODE_DISABLE_WORKSPACE_TRUST: "1",
+				ELECTRON_DISABLE_SECURITY_WARNINGS: "1",
+			},
 			args: [
 				"--no-sandbox",
 				"--disable-gpu-sandbox",
@@ -60,9 +69,16 @@ export const test = base.extend<TestFixtures>({
 				"--disable-crash-reporter",
 				"--enable-logging",
 				"--log-level=0",
+				"--disable-extensions-except=kilocode.kilo-code",
+				"--disable-extension-recommendations",
+				"--disable-extension-update-check",
+				"--disable-default-apps",
+				"--disable-background-timer-throttling",
+				"--disable-renderer-backgrounding",
+				"--disable-component-extensions-with-background-pages",
 				`--extensionDevelopmentPath=${path.resolve(__dirname, "..", "..", "..", "src")}`,
 				`--extensions-dir=${path.join(defaultCachePath, "extensions")}`,
-				`--user-data-dir=${path.join(defaultCachePath, "user-data")}`,
+				`--user-data-dir=${userDataDir}`,
 				"--enable-proposed-api=kilocode.kilo-code",
 				await createProject(),
 			],
@@ -175,8 +191,17 @@ export const test = base.extend<TestFixtures>({
 		}
 	},
 
-	takeScreenshot: async ({ workbox }, use) => {
+	takeScreenshot: async ({ workbox: page }, use) => {
 		await use(async (name?: string) => {
+			await closeAllTabs(page)
+
+			const activatingStatus = page.locator("text=Activating Extensions")
+			const activatingStatusCount = await activatingStatus.count()
+			if (activatingStatusCount > 0) {
+				console.log("⌛️ Waiting for `Activating Extensions` to go away...")
+				await activatingStatus.waitFor({ state: "hidden", timeout: 10000 })
+			}
+
 			const testInfo = test.info()
 			// Extract test suite from the test file name or use a default
 			const fileName = testInfo.file.split("/").pop()?.replace(".test.ts", "") || "unknown"
@@ -191,8 +216,24 @@ export const test = base.extend<TestFixtures>({
 				.replace(/^-|-$/g, "") // Remove leading/trailing dashes
 
 			const screenshotPath = test.info().outputPath(`${hierarchicalName}.png`)
-			await workbox.screenshot({ path: screenshotPath, fullPage: true })
+			await page.screenshot({ path: screenshotPath, fullPage: true })
 			console.log(`📸 Screenshot captured: ${hierarchicalName}`)
 		})
 	},
 })
+
+function seedUserSettings(userDataDir: string) {
+	const userDir = path.join(userDataDir, "User")
+	const settingsPath = path.join(userDir, "settings.json")
+	fs.mkdirSync(userDir, { recursive: true })
+
+	const settings = {
+		"workbench.startupEditor": "none", // hides 'Get Started'
+		"workbench.tips.enabled": false,
+		"update.showReleaseNotes": false,
+		"extensions.ignoreRecommendations": true,
+		"telemetry.telemetryLevel": "off",
+	}
+
+	fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
+}

+ 0 - 10
apps/playwright-e2e/tests/sanity.test.ts

@@ -10,16 +10,6 @@ test.describe("Sanity Tests", () => {
 		await expect(page.locator(".activitybar")).toBeVisible()
 		console.log("✅ Activity bar visible")
 
-		const modifier = process.platform === "darwin" ? "Meta" : "Control"
-		const shortcut = `${modifier}+Shift+P`
-		await page.keyboard.press(shortcut)
-		const commandPalette = page.locator(".quick-input-widget")
-		await expect(commandPalette).toBeVisible()
-
-		await page.keyboard.press("Escape")
-		await expect(commandPalette).not.toBeVisible()
-		console.log("✅ Command palette working")
-
 		await setupTestEnvironment(page)
 	})
 })

+ 6 - 6
apps/playwright-e2e/tests/settings-screenshots.test.ts

@@ -1,15 +1,15 @@
 import { test, expect, type TestFixtures } from "./playwright-base-test"
 import { verifyExtensionInstalled, findWebview, upsertApiConfiguration } from "../helpers/webview-helpers"
 
-test.describe("Settings Screenshots", () => {
-	test("should take screenshots of all settings tabs", async ({ workbox: page, takeScreenshot }: TestFixtures) => {
+test.describe("Settings", () => {
+	test("settings tab screenshot", async ({ workbox: page, takeScreenshot }: TestFixtures) => {
 		await verifyExtensionInstalled(page)
-
 		await upsertApiConfiguration(page)
 
-		// Open the settings
+		// Open the settings then move the mouse to avoid triggering the tooltip
 		page.locator('[aria-label*="Settings"], [title*="Settings"]').first().click()
-		await page.mouse.move(0, 0) // Move the mouse to (0, 0) to avoid triggering the tooltip!
+		await page.mouse.move(0, 0)
+		await page.mouse.click(0, 0)
 
 		const webviewFrame = await findWebview(page)
 		await expect(webviewFrame.locator('[role="tablist"]')).toBeVisible({ timeout: 10000 })
@@ -36,7 +36,7 @@ test.describe("Settings Screenshots", () => {
 			const sectionId = testId?.replace("tab-", "") || `section-${i}`
 
 			console.log(`📸 Taking screenshot of tab: ${tabName} (${sectionId})`)
-			await takeScreenshot(`settings-${sectionId}-${tabName.toLowerCase().replace(/\s+/g, "-")}`)
+			await takeScreenshot(`${i}-settings-${sectionId}-${tabName.toLowerCase().replace(/\s+/g, "-")}`)
 		}
 
 		console.log("✅ All settings tabs screenshots completed")