From abcead4a8035a863730b52811f7f4fc9330957ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:25:26 +0000 Subject: [PATCH 1/2] fix(e2e): authenticate before testing sidebar visibility The sidebar-text-visibility E2E tests failed because the MSW mock environment returns auth.enabled: true, causing AuthGuard to redirect to the login page where no sidebar exists. - Add e2e/helpers/auth.ts with registerAndLogin() that signs up a test user via the register form UI - Update sidebar-text-visibility.spec.ts to authenticate first - Increase sidebar locator timeouts for post-auth rendering - Update CHANGELOG.md Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/c7f8c74c-dbbe-431a-b308-c1062554bbcd Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- CHANGELOG.md | 2 ++ e2e/helpers/auth.ts | 33 +++++++++++++++++++++++++++++ e2e/sidebar-text-visibility.spec.ts | 26 +++++++++++++---------- 3 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 e2e/helpers/auth.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a804327..d3449dfdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **E2E sidebar tests failing due to missing auth** (`e2e`): The `sidebar-text-visibility.spec.ts` tests failed because the MSW mock environment returns `auth.enabled: true` in its discovery response, causing `AuthGuard` to redirect unauthenticated users to the login page where no sidebar exists. Added `e2e/helpers/auth.ts` with a `registerAndLogin()` helper that signs up a test user via the register form, and updated both sidebar tests to authenticate before asserting sidebar visibility. + - **Changesets release bump escalation** (`release`): Added `___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.onlyUpdatePeerDependentsWhenOutOfRange` in `.changeset/config.json` and moved internal `@object-ui/*` entries in `@object-ui/plugin-designer` from `peerDependencies` to `dependencies` to prevent minor release PRs from incorrectly escalating fixed-group packages to a major version. - **Home page star/favorite not reactive** (`@object-ui/console`): Migrated `useFavorites` from standalone hook to React Context (`FavoritesProvider`) so all consumers (HomePage, AppCard, AppSidebar, UnifiedSidebar) share a single state instance. Previously, each component calling `useFavorites()` created independent state, so toggling a favorite in AppCard did not trigger re-render in HomePage. localStorage persistence is retained as the storage layer. diff --git a/e2e/helpers/auth.ts b/e2e/helpers/auth.ts new file mode 100644 index 000000000..0c03b6a6f --- /dev/null +++ b/e2e/helpers/auth.ts @@ -0,0 +1,33 @@ +import type { Page } from '@playwright/test'; +import { CONSOLE_BASE } from './index'; + +/** + * Register a new test user and wait for redirect to the home page. + * + * MSW auth handlers run in-memory, so each browser context starts + * without a session. This helper creates a fresh user via the + * register form so that subsequent navigation hits authenticated + * routes (HomeLayout with sidebar, etc.). + */ +export async function registerAndLogin(page: Page) { + await page.goto(`${CONSOLE_BASE}/register`); + + // Wait for React to mount and the register form to render + await page.waitForFunction( + () => (document.getElementById('root')?.children.length ?? 0) > 0, + { timeout: 30_000 }, + ); + // Wait for the form inputs to be present (MSW boot + React render) + await page.locator('input[type="email"]').waitFor({ state: 'visible', timeout: 15_000 }); + + // Fill in the registration form + const ts = Date.now(); + await page.locator('input[type="text"]').fill('E2E Test User'); + await page.locator('input[type="email"]').fill(`e2e-${ts}@test.local`); + await page.locator('input[type="password"]').first().fill('Test1234!'); + await page.locator('input[type="password"]').nth(1).fill('Test1234!'); + + // Submit and wait for navigation away from the register page + await page.locator('button[type="submit"]').click(); + await page.waitForURL(/\/console\/(home|apps\/)/, { timeout: 30_000 }); +} diff --git a/e2e/sidebar-text-visibility.spec.ts b/e2e/sidebar-text-visibility.spec.ts index dfe091f44..3428d359c 100644 --- a/e2e/sidebar-text-visibility.spec.ts +++ b/e2e/sidebar-text-visibility.spec.ts @@ -1,25 +1,30 @@ import { test, expect } from '@playwright/test'; -import { waitForReactMount, CONSOLE_BASE } from './helpers'; +import { CONSOLE_BASE } from './helpers'; +import { registerAndLogin } from './helpers/auth'; /** * Sidebar text visibility tests * * These tests validate that the sidebar displays text correctly * when toggled between collapsed (icon mode) and expanded states. + * + * The MSW mock environment requires authentication, so each test + * registers a fresh user before navigating to the home page. */ test.describe('Sidebar Text Visibility', () => { test('should show all text labels when sidebar is expanded in icon mode', async ({ page }) => { - await page.goto(`${CONSOLE_BASE}/`); - await waitForReactMount(page); + // registerAndLogin navigates to /register, signs up, and waits for the + // redirect to /home — the page is already authenticated & on the home route. + await registerAndLogin(page); - // Wait for sidebar to be visible + // Wait for sidebar to be visible (page needs time to render after auth redirect) const sidebar = page.locator('[data-sidebar="sidebar"]').first(); - await expect(sidebar).toBeVisible(); + await expect(sidebar).toBeVisible({ timeout: 15_000 }); - // Find the sidebar toggle button + // Find the sidebar toggle button (desktop trigger rendered by AppShell) const toggleButton = page.locator('[data-sidebar="trigger"]').first(); - await expect(toggleButton).toBeVisible(); + await expect(toggleButton).toBeVisible({ timeout: 10_000 }); // Get the parent sidebar element that has data-state attribute const sidebarGroup = page.locator('.group[data-collapsible="icon"]').first(); @@ -92,14 +97,13 @@ test.describe('Sidebar Text Visibility', () => { }); test('should hide text labels when sidebar is collapsed in icon mode', async ({ page }) => { - await page.goto(`${CONSOLE_BASE}/`); - await waitForReactMount(page); + await registerAndLogin(page); const sidebar = page.locator('[data-sidebar="sidebar"]').first(); - await expect(sidebar).toBeVisible(); + await expect(sidebar).toBeVisible({ timeout: 15_000 }); const toggleButton = page.locator('[data-sidebar="trigger"]').first(); - await expect(toggleButton).toBeVisible(); + await expect(toggleButton).toBeVisible({ timeout: 10_000 }); const sidebarGroup = page.locator('.group[data-collapsible="icon"]').first(); From a65eb914af9165b579012210f9113a32df036a6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:31:03 +0000 Subject: [PATCH 2/2] refactor(e2e): extract timeout constants per code review feedback Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/c7f8c74c-dbbe-431a-b308-c1062554bbcd Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- e2e/helpers/auth.ts | 7 +++++-- e2e/sidebar-text-visibility.spec.ts | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/e2e/helpers/auth.ts b/e2e/helpers/auth.ts index 0c03b6a6f..9f379befe 100644 --- a/e2e/helpers/auth.ts +++ b/e2e/helpers/auth.ts @@ -1,6 +1,9 @@ import type { Page } from '@playwright/test'; import { CONSOLE_BASE } from './index'; +/** Shared timeout for waiting on React rendering and MSW boot. */ +const REACT_READY_TIMEOUT = 30_000; + /** * Register a new test user and wait for redirect to the home page. * @@ -15,7 +18,7 @@ export async function registerAndLogin(page: Page) { // Wait for React to mount and the register form to render await page.waitForFunction( () => (document.getElementById('root')?.children.length ?? 0) > 0, - { timeout: 30_000 }, + { timeout: REACT_READY_TIMEOUT }, ); // Wait for the form inputs to be present (MSW boot + React render) await page.locator('input[type="email"]').waitFor({ state: 'visible', timeout: 15_000 }); @@ -29,5 +32,5 @@ export async function registerAndLogin(page: Page) { // Submit and wait for navigation away from the register page await page.locator('button[type="submit"]').click(); - await page.waitForURL(/\/console\/(home|apps\/)/, { timeout: 30_000 }); + await page.waitForURL(/\/console\/(home|apps\/)/, { timeout: REACT_READY_TIMEOUT }); } diff --git a/e2e/sidebar-text-visibility.spec.ts b/e2e/sidebar-text-visibility.spec.ts index 3428d359c..89800fe0d 100644 --- a/e2e/sidebar-text-visibility.spec.ts +++ b/e2e/sidebar-text-visibility.spec.ts @@ -2,6 +2,9 @@ import { test, expect } from '@playwright/test'; import { CONSOLE_BASE } from './helpers'; import { registerAndLogin } from './helpers/auth'; +/** Timeout for sidebar elements to become visible after auth + page render. */ +const SIDEBAR_VISIBLE_TIMEOUT = 15_000; + /** * Sidebar text visibility tests * @@ -20,11 +23,11 @@ test.describe('Sidebar Text Visibility', () => { // Wait for sidebar to be visible (page needs time to render after auth redirect) const sidebar = page.locator('[data-sidebar="sidebar"]').first(); - await expect(sidebar).toBeVisible({ timeout: 15_000 }); + await expect(sidebar).toBeVisible({ timeout: SIDEBAR_VISIBLE_TIMEOUT }); // Find the sidebar toggle button (desktop trigger rendered by AppShell) const toggleButton = page.locator('[data-sidebar="trigger"]').first(); - await expect(toggleButton).toBeVisible({ timeout: 10_000 }); + await expect(toggleButton).toBeVisible({ timeout: SIDEBAR_VISIBLE_TIMEOUT }); // Get the parent sidebar element that has data-state attribute const sidebarGroup = page.locator('.group[data-collapsible="icon"]').first(); @@ -100,10 +103,10 @@ test.describe('Sidebar Text Visibility', () => { await registerAndLogin(page); const sidebar = page.locator('[data-sidebar="sidebar"]').first(); - await expect(sidebar).toBeVisible({ timeout: 15_000 }); + await expect(sidebar).toBeVisible({ timeout: SIDEBAR_VISIBLE_TIMEOUT }); const toggleButton = page.locator('[data-sidebar="trigger"]').first(); - await expect(toggleButton).toBeVisible({ timeout: 10_000 }); + await expect(toggleButton).toBeVisible({ timeout: SIDEBAR_VISIBLE_TIMEOUT }); const sidebarGroup = page.locator('.group[data-collapsible="icon"]').first();