diff --git a/apps/nextjs/src/app/[locale]/init/page.tsx b/apps/nextjs/src/app/[locale]/init/page.tsx index 9588452d8..b754a2919 100644 --- a/apps/nextjs/src/app/[locale]/init/page.tsx +++ b/apps/nextjs/src/app/[locale]/init/page.tsx @@ -64,6 +64,7 @@ export default async function InitPage() { kubernetesConfigured: canReadSetupContext && dockerEnv.ENABLE_KUBERNETES, workshopApiUrl, workshopUrl: workshopWebUrl, + serverOrigin: baseUrl, mcpEndpoint: `${baseUrl}/api/mcp`, canConfigurePrivileged, hasUsers: Boolean(firstUser), diff --git a/e2e/onboarding.spec.ts b/e2e/onboarding.spec.ts index e417b619f..e068cfd51 100644 --- a/e2e/onboarding.spec.ts +++ b/e2e/onboarding.spec.ts @@ -101,9 +101,9 @@ describe("Onboarding", () => { await page.locator("css=button[type='submit']").click(); await page.getByRole("heading", { name: "Start with familiar defaults" }).waitFor({ timeout: 30_000 }); - const serverOrigin = page.getByLabel("Domain, IP, or reverse-proxy path"); + const serverOrigin = page.getByLabel("Usual server address"); await serverOrigin.fill("https://home.example/homarr"); - await page.getByRole("combobox", { name: "Default language" }).click(); + await page.getByLabel("Default language").click(); await page.getByRole("option", { name: /Français/ }).click(); expect(await serverOrigin.inputValue()).toBe("https://home.example/homarr"); expect(await page.getByRole("heading", { name: "Start with familiar defaults" }).isVisible()).toBe(true); @@ -129,7 +129,7 @@ describe("Onboarding", () => { ["Discover", "See what this installation can actually reach"], ["Connect", "Connect what makes the board useful"], ["Board", "Shape the board before it is built"], - ["Extend", "Add intelligence when it helps"], + ["Extend", "Advanced features"], ] as const; for (const [section, heading] of sections) { await page.getByRole("button", { name: new RegExp(`^${section}`) }).click(); @@ -147,7 +147,6 @@ describe("Onboarding", () => { expect(await boardPreview.getAttribute("data-layout-columns")).toBe("12"); await page.getByRole("button", { name: /^Connect/ }).click(); - await page.getByRole("button", { name: "Add another integration" }).click(); const sonarr = page.locator('button[aria-label="Sonarr"]'); await sonarr.click(); await page.getByLabel("Service URL").waitFor(); @@ -449,15 +448,18 @@ describe("Onboarding", () => { expect(await page.locator('meta[name="homarr-workshop-api-url"]').getAttribute("content")).toBe( configuredWorkshopApiUrl, ); - await page.getByRole("button", { name: /^Extend/ }).click(); - await page.getByRole("heading", { name: "Add intelligence when it helps" }).waitFor(); - await page.getByLabel("Provider URL").waitFor(); + await page.getByRole("button", { name: "Extend (5/6)" }).click(); + await page.getByRole("heading", { name: "Advanced features" }).waitFor(); + await page.getByRole("tab", { name: "Assistant" }).click(); + await page.getByLabel("API base URL").waitFor(); const provider = page.getByRole("combobox", { name: "Provider", exact: true }); await expect.poll(() => provider.inputValue(), { timeout: 10_000 }).toBe("Custom endpoint"); - expect(await page.getByLabel("Provider URL").inputValue()).toBe("http://assistant.local/v1"); + expect(await page.getByLabel("API base URL").inputValue()).toBe("http://assistant.local/v1"); expect(await page.locator('input[value="local-model"]').inputValue()).toBe("local-model"); - expect(await page.getByText("Model discovery is unavailable.").count()).toBe(1); + expect(await page.getByText("Discovery is unavailable. You can still enter a model ID manually.").count()).toBe( + 1, + ); } finally { try { await Promise.all([browser.close(), homarrContainer.stop()]); diff --git a/e2e/shared/actions/onboarding-actions.ts b/e2e/shared/actions/onboarding-actions.ts index 0edeb48e8..89195292b 100644 --- a/e2e/shared/actions/onboarding-actions.ts +++ b/e2e/shared/actions/onboarding-actions.ts @@ -52,6 +52,7 @@ export class OnboardingActions { } public async processIntegrationsStepAsync() { + await this.page.getByRole("button", { name: "Review (6/6)" }).click(); await this.page.getByRole("button", { name: "Build my board" }).click(); } } diff --git a/packages/onboarding/src/setup-studio.tsx b/packages/onboarding/src/setup-studio.tsx index 52512aae2..46a4e514b 100644 --- a/packages/onboarding/src/setup-studio.tsx +++ b/packages/onboarding/src/setup-studio.tsx @@ -212,7 +212,7 @@ export const SetupStudio = ({ environment, assistantConfiguration }: OnboardingS const [activeSection, setActiveSection] = useState("essentials"); const [incompleteIntegrationConfirmationOpened, setIncompleteIntegrationConfirmationOpened] = useState(false); const [selectedLocale, setSelectedLocale] = useState(currentLocale); - const [serverOrigin, setServerOrigin] = useState(""); + const [serverOrigin, setServerOrigin] = useState(environment.serverOrigin); const [urlMode, setUrlMode] = useState("hostPort"); const [analyticsEnabled, setAnalyticsEnabled] = useState(true); const [selectedKinds, setSelectedKinds] = useState([]); @@ -282,10 +282,6 @@ export const SetupStudio = ({ environment, assistantConfiguration }: OnboardingS [discoveredIntegrations], ); - useEffect(() => { - setServerOrigin((current) => current || window.location.origin); - }, []); - useEffect(() => { if (!docker.data) return; const newIntegrationSourceIds = takeNewSourceIds( @@ -894,6 +890,8 @@ const SectionHeading = ({ const Essentials = (props: StudioSectionProps) => { const t = useScopedI18n("init.studio.essentials"); + const [isHydrated, setIsHydrated] = useState(false); + useEffect(() => setIsHydrated(true), []); return ( @@ -921,9 +919,9 @@ const Essentials = (props: StudioSectionProps) => { placeholder="home.lan · 192.168.1.10 · https://homarr.example.com" value={props.serverOrigin} onChange={(event) => props.setServerOrigin(event.currentTarget.value)} + readOnly={!isHydrated} required withAsterisk - autoFocus />