mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-06 14:33:11 +08:00
* Add demo flow E2E coverage * Add live Agent Server E2E * Stabilize live E2E CI * Stabilize live Agent Server E2E * Use default pull request workflow triggers * Organize Playwright E2E tests * Comment live E2E results on PR * Fix live E2E comment permissions * Stabilize live E2E PR reporting * Add collapsible live E2E evidence * Embed live E2E media in PR report * Use release assets for live E2E media * Stabilize live E2E media and auth * Use raw URLs for live E2E media * Update tests for SDK workspace API * Use PR artifacts for live E2E media * Move live E2E scripts under tests * chore: Update PR QA artifacts * Clarify live E2E test layout * Document and simplify live E2E local runs * Remove unrelated non-test diffs * Preserve HEAD git ref in workspace client * Strengthen live Agent Server E2E * chore: Update PR QA artifacts * Remove workspace session URL normalization * Remove obsolete mock E2E regressions * Disable live E2E trace capture * Harden live E2E workflow * Harden live E2E review fixes * Fix live E2E manual checkout * chore: Update PR QA artifacts * Address live E2E re-review feedback * Address live E2E security review feedback * Address live E2E approval suggestions * chore: address PR review feedback (#195) * chore: address live e2e review followups (#195) * chore: Remove PR-only artifacts * chore: address latest live e2e review * fix(ci): drop --ignore-scripts so typescript-client git dep builds After merging main (PR #278), source files import directly from @openhands/typescript-client subpath exports (e.g. /clients, /workspace/remote-workspace). These resolve to dist/ files that are generated by the package's prepare script. The --ignore-scripts flag on npm ci prevented that script from running, so CI's typecheck failed with TS2307 'Cannot find module' for every subpath import. Main's CI uses plain 'npm ci' (no --ignore-scripts) and passes. Align this branch to match. Co-authored-by: openhands <openhands@all-hands.dev> * fix: restore avatar-menu and css-isolation regression tests These were moved from tests/ to tests/e2e/regressions/ in 08b8e12 but then mistakenly deleted in 5c5a39b. The live E2E framework is additive — it should not remove existing browser regression coverage. The placeholder.spec.ts is not restored since it was a no-op stub. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com> Co-authored-by: openhands <openhands@all-hands.dev>
115 lines
4.0 KiB
TypeScript
115 lines
4.0 KiB
TypeScript
import test, { expect } from "@playwright/test";
|
|
|
|
/**
|
|
* Test for issue #11933: Avatar context menu closes when moving cursor diagonally
|
|
*
|
|
* This test verifies that the user can move their cursor diagonally from the
|
|
* avatar to the context menu without the menu closing unexpectedly.
|
|
*
|
|
* The component opens the menu on hover and keeps it open during short
|
|
* diagonal movement through the hover bridge.
|
|
*/
|
|
test("avatar context menu stays open when moving cursor diagonally to menu", async ({
|
|
page,
|
|
}) => {
|
|
await page.addInitScript(() => {
|
|
window.localStorage.setItem("analytics-consent", "true");
|
|
});
|
|
|
|
// Intercept GET /api/settings to return settings with a configured provider.
|
|
// In OSS mode, the user context menu only renders when providers are configured.
|
|
await page.route("**/api/settings", async (route) => {
|
|
if (route.request().method() === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
llm_model: "openhands/claude-opus-4-5-20251101",
|
|
llm_base_url: "",
|
|
agent: "CodeActAgent",
|
|
language: "en",
|
|
llm_api_key: null,
|
|
llm_api_key_set: false,
|
|
search_api_key_set: false,
|
|
confirmation_mode: false,
|
|
security_analyzer: "llm",
|
|
remote_runtime_resource_factor: 1,
|
|
provider_tokens_set: { github: "" },
|
|
enable_default_condenser: true,
|
|
condenser_max_size: 240,
|
|
enable_sound_notifications: false,
|
|
user_consents_to_analytics: false,
|
|
enable_proactive_conversation_starters: false,
|
|
enable_solvability_analysis: false,
|
|
max_budget_per_task: null,
|
|
}),
|
|
});
|
|
} else {
|
|
await route.continue();
|
|
}
|
|
});
|
|
|
|
await page.goto("/");
|
|
|
|
const consentDialog = page.getByRole("dialog", {
|
|
name: "Help improve OpenHands",
|
|
});
|
|
await page
|
|
.getByRole("button", { name: "Confirm preferences" })
|
|
.click({ timeout: 5000 })
|
|
.catch(() => undefined);
|
|
await expect(consentDialog).toHaveCount(0, { timeout: 5000 });
|
|
|
|
// Wait for the page to be fully loaded and check for AI config modal
|
|
// The modal may appear for new users in OSS mode without settings
|
|
const aiConfigModal = page.getByTestId("ai-config-modal");
|
|
|
|
// Give the modal a chance to appear (it may load asynchronously)
|
|
try {
|
|
await aiConfigModal.waitFor({ state: "visible", timeout: 3000 });
|
|
// Modal appeared - dismiss it by clicking save
|
|
await page.getByTestId("save-settings-button").click();
|
|
await expect(aiConfigModal).toBeHidden();
|
|
} catch {
|
|
// Modal didn't appear within timeout - that's fine, continue with test
|
|
}
|
|
|
|
const userAvatar = page.getByTestId("user-avatar");
|
|
await expect(userAvatar).toBeVisible();
|
|
|
|
await userAvatar.hover();
|
|
|
|
const contextMenu = page.getByTestId("user-context-menu");
|
|
await expect(contextMenu).toBeVisible();
|
|
|
|
const menuWrapper = contextMenu.locator("..");
|
|
await expect(menuWrapper).toHaveCSS("opacity", "1");
|
|
|
|
// Now test diagonal mouse movement - move from avatar to menu
|
|
// The menu should stay open due to the hover bridge in the component
|
|
const avatarBox = await userAvatar.boundingBox();
|
|
const menuBox = await contextMenu.boundingBox();
|
|
|
|
if (!avatarBox || !menuBox) {
|
|
throw new Error("Could not get bounding boxes");
|
|
}
|
|
|
|
// Move diagonally from avatar center toward the menu
|
|
const startX = avatarBox.x + avatarBox.width / 2;
|
|
const startY = avatarBox.y + avatarBox.height / 2;
|
|
const endX = menuBox.x + menuBox.width / 2;
|
|
const endY = menuBox.y + menuBox.height / 2;
|
|
|
|
// Simulate diagonal movement with multiple steps
|
|
const steps = 5;
|
|
for (let i = 0; i <= steps; i++) {
|
|
const x = startX + ((endX - startX) * i) / steps;
|
|
const y = startY + ((endY - startY) * i) / steps;
|
|
await page.mouse.move(x, y);
|
|
}
|
|
|
|
// Menu should still be visible after diagonal movement
|
|
await expect(contextMenu).toBeVisible();
|
|
await expect(menuWrapper).toHaveCSS("opacity", "1");
|
|
});
|