mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-03-15 20:52:39 +00:00
Compare commits
16 Commits
v3.0.1
...
fix-tests-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b422703e0 | ||
|
|
8127b64606 | ||
|
|
f78f4f55b9 | ||
|
|
b0b1582dfd | ||
|
|
b5800633c5 | ||
|
|
e1922c4325 | ||
|
|
2031bd54bd | ||
|
|
090325a01d | ||
|
|
af83a2c113 | ||
|
|
05e163931e | ||
|
|
bddb140d3d | ||
|
|
5611890570 | ||
|
|
4245173a09 | ||
|
|
015b52cf9a | ||
|
|
7335e4e2ec | ||
|
|
2711a51cb5 |
@@ -7,7 +7,7 @@ import {
|
||||
getAuthUrlSS,
|
||||
} from "@/lib/userSS";
|
||||
import { redirect } from "next/navigation";
|
||||
import { EmailPasswordForm } from "../login/EmailPasswordForm";
|
||||
import EmailPasswordForm from "../login/EmailPasswordForm";
|
||||
import SignInButton from "@/app/auth/login/SignInButton";
|
||||
import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
|
||||
import AuthErrorDisplay from "@/components/auth/AuthErrorDisplay";
|
||||
|
||||
@@ -21,7 +21,7 @@ interface EmailPasswordFormProps {
|
||||
isJoin?: boolean;
|
||||
}
|
||||
|
||||
export function EmailPasswordForm({
|
||||
export default function EmailPasswordForm({
|
||||
isSignup = false,
|
||||
shouldVerify,
|
||||
referralSource,
|
||||
@@ -32,10 +32,12 @@ export function EmailPasswordForm({
|
||||
const { user } = useUser();
|
||||
const { popup, setPopup } = usePopup();
|
||||
const [isWorking, setIsWorking] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isWorking && <Spinner />}
|
||||
{popup}
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: defaultEmail ? defaultEmail.toLowerCase() : "",
|
||||
@@ -127,13 +129,14 @@ export function EmailPasswordForm({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, values }) => (
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<TextFormField
|
||||
name="email"
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="email@yourcompany.com"
|
||||
data-testid="email"
|
||||
/>
|
||||
|
||||
<TextFormField
|
||||
@@ -141,6 +144,7 @@ export function EmailPasswordForm({
|
||||
label="Password"
|
||||
type="password"
|
||||
placeholder="**************"
|
||||
data-testid="password"
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AuthTypeMetadata } from "@/lib/userSS";
|
||||
import LoginText from "@/app/auth/login/LoginText";
|
||||
import Link from "next/link";
|
||||
import SignInButton from "@/app/auth/login/SignInButton";
|
||||
import { EmailPasswordForm } from "./EmailPasswordForm";
|
||||
import EmailPasswordForm from "./EmailPasswordForm";
|
||||
import { NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED } from "@/lib/constants";
|
||||
import { useSendAuthRequiredMessage } from "@/lib/extension/utils";
|
||||
import Text from "@/refresh-components/Text";
|
||||
|
||||
@@ -10,9 +10,11 @@ import { redirect } from "next/navigation";
|
||||
import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
|
||||
import LoginPage from "./LoginPage";
|
||||
|
||||
const Page = async (props: {
|
||||
export interface PageProps {
|
||||
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||
}) => {
|
||||
}
|
||||
|
||||
export default async function Page(props: PageProps) {
|
||||
const searchParams = await props.searchParams;
|
||||
const autoRedirectDisabled = searchParams?.disableAutoRedirect === "true";
|
||||
const nextUrl = Array.isArray(searchParams?.next)
|
||||
@@ -85,6 +87,4 @@ const Page = async (props: {
|
||||
</AuthFlowContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
getAuthUrlSS,
|
||||
} from "@/lib/userSS";
|
||||
import { redirect } from "next/navigation";
|
||||
import { EmailPasswordForm } from "../login/EmailPasswordForm";
|
||||
import EmailPasswordForm from "../login/EmailPasswordForm";
|
||||
import SignInButton from "@/app/auth/login/SignInButton";
|
||||
import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
|
||||
import ReferralSourceSelector from "./ReferralSourceSelector";
|
||||
|
||||
@@ -107,10 +107,10 @@ export const MessagesDisplay: React.FC<MessagesDisplayProps> = ({
|
||||
);
|
||||
|
||||
const handleEditWithMessageId = useCallback(
|
||||
(editedContent: string, msgId: number | null | undefined) => {
|
||||
(editedContent: string, msgId: number) => {
|
||||
onSubmit({
|
||||
message: editedContent,
|
||||
messageIdToResend: msgId || undefined,
|
||||
messageIdToResend: msgId,
|
||||
currentMessageFiles: [],
|
||||
useAgentSearch: deepResearchEnabled,
|
||||
});
|
||||
|
||||
@@ -548,6 +548,7 @@ function ChatInputBarInner({
|
||||
<div className="flex flex-row items-center gap-spacing-inline">
|
||||
<LLMPopover requiresImageGeneration />
|
||||
<IconButton
|
||||
id="onyx-chat-input-send-button"
|
||||
icon={chatState === "input" ? SvgArrowUp : SvgStop}
|
||||
disabled={chatState === "input" && !message}
|
||||
onClick={() => {
|
||||
|
||||
@@ -296,7 +296,7 @@ export function UserSettings({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col p-padding-content">
|
||||
{/* {(showPasswordSection || hasConnectors) && (
|
||||
{(showPasswordSection || hasConnectors) && (
|
||||
<div className="w-1/4 pr-4 flex-shrink-0">
|
||||
<nav>
|
||||
<ul className="space-y-2">
|
||||
@@ -343,7 +343,7 @@ export function UserSettings({
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
)} */}
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
showPasswordSection || hasConnectors ? "w-3/4" : "w-full"
|
||||
|
||||
@@ -275,6 +275,7 @@ export default function HumanMessage({
|
||||
tertiary
|
||||
tooltip="Copy"
|
||||
onClick={() => copyAll(content)}
|
||||
data-testid="HumanMessage/copy-button"
|
||||
/>
|
||||
<IconButton
|
||||
icon={SvgEdit}
|
||||
@@ -284,6 +285,7 @@ export default function HumanMessage({
|
||||
setIsEditing(true);
|
||||
setIsHovered(false);
|
||||
}}
|
||||
data-testid="HumanMessage/edit-button"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -20,10 +20,7 @@ interface InternalMemoizedHumanMessageProps
|
||||
}
|
||||
|
||||
interface MemoizedHumanMessageProps extends BaseMemoizedHumanMessageProps {
|
||||
handleEditWithMessageId: (
|
||||
editedContent: string,
|
||||
messageId: number | null | undefined
|
||||
) => void;
|
||||
handleEditWithMessageId: (editedContent: string, messageId: number) => void;
|
||||
}
|
||||
|
||||
const _MemoizedHumanMessage = React.memo(function _MemoizedHumanMessage({
|
||||
@@ -65,7 +62,14 @@ export const MemoizedHumanMessage = ({
|
||||
}: MemoizedHumanMessageProps) => {
|
||||
const onEdit = useCallback(
|
||||
(editedContent: string) => {
|
||||
handleEditWithMessageId(editedContent, messageId ?? undefined);
|
||||
if (!messageId) {
|
||||
console.warn(
|
||||
"No message id specified; cannot edit an undefined message"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
handleEditWithMessageId(editedContent, messageId);
|
||||
},
|
||||
[handleEditWithMessageId, messageId]
|
||||
);
|
||||
|
||||
@@ -30,7 +30,10 @@ export default function MessageSwitcher({
|
||||
const next = handle(totalPages, handleNext);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center gap-spacing-inline">
|
||||
<div
|
||||
className="flex flex-row items-center gap-spacing-inline"
|
||||
data-testid="MessageSwitcher/container"
|
||||
>
|
||||
<IconButton
|
||||
icon={SvgChevronLeft}
|
||||
onClick={previous}
|
||||
|
||||
@@ -358,6 +358,7 @@ export default function AIMessage({
|
||||
onClick={() => copyAll(getTextContent(rawPackets))}
|
||||
tertiary
|
||||
tooltip="Copy"
|
||||
data-testid="AIMessage/copy-button"
|
||||
/>
|
||||
<IconButton
|
||||
icon={SvgThumbsUp}
|
||||
@@ -369,6 +370,7 @@ export default function AIMessage({
|
||||
}
|
||||
tertiary
|
||||
tooltip="Good Response"
|
||||
data-testid="AIMessage/like-button"
|
||||
/>
|
||||
<IconButton
|
||||
icon={SvgThumbsDown}
|
||||
@@ -380,18 +382,21 @@ export default function AIMessage({
|
||||
}
|
||||
tertiary
|
||||
tooltip="Bad Response"
|
||||
data-testid="AIMessage/dislike-button"
|
||||
/>
|
||||
|
||||
{chatState.regenerate && (
|
||||
<LLMPopover
|
||||
currentModelName={chatState.overriddenModel}
|
||||
onSelect={(modelName) => {
|
||||
const llmDescriptor =
|
||||
parseLlmDescriptor(modelName);
|
||||
chatState.regenerate!(llmDescriptor);
|
||||
}}
|
||||
folded
|
||||
/>
|
||||
<div data-testid="AIMessage/regenerate-button">
|
||||
<LLMPopover
|
||||
currentModelName={chatState.overriddenModel}
|
||||
onSelect={(modelName) => {
|
||||
const llmDescriptor =
|
||||
parseLlmDescriptor(modelName);
|
||||
chatState.regenerate!(llmDescriptor);
|
||||
}}
|
||||
folded
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeId &&
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function LLMPopover({
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<div>{triggerContent}</div>
|
||||
<div data-testid="llm-popover-trigger">{triggerContent}</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="top"
|
||||
|
||||
@@ -74,7 +74,7 @@ const iconClasses = (active: boolean | undefined) =>
|
||||
}) as const;
|
||||
|
||||
export interface IconButtonProps
|
||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
// Button states:
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import AgentCard from "@/sections/AgentsModal/AgentCard";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
import { checkUserOwnsAssistant as checkUserOwnsAgent } from "@/lib/assistants/checkOwnership";
|
||||
@@ -13,7 +12,7 @@ import { ModalIds, useModal } from "@/refresh-components/contexts/ModalContext";
|
||||
import SvgFilter from "@/icons/filter";
|
||||
import SvgOnyxOctagon from "@/icons/onyx-octagon";
|
||||
import Button from "@/refresh-components/buttons/Button";
|
||||
import Link from "next/link";
|
||||
import InputTypeIn from "@/refresh-components/inputs/InputTypeIn";
|
||||
|
||||
interface AgentsSectionProps {
|
||||
title: string;
|
||||
@@ -95,7 +94,6 @@ function useAgentFilters() {
|
||||
export default function AgentsModal() {
|
||||
const { agents, pinnedAgents } = useAgentsContext();
|
||||
const { agentFilters, toggleAgentFilter } = useAgentFilters();
|
||||
const router = useRouter();
|
||||
const { user } = useUser();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const { toggleModal } = useModal();
|
||||
@@ -144,15 +142,17 @@ export default function AgentsModal() {
|
||||
<Modal id={ModalIds.AgentsModal} icon={SvgOnyxOctagon} title="Agents" sm>
|
||||
<div className="flex flex-col sticky top-[0rem] z-10 bg-background-tint-01 p-spacing-paragraph">
|
||||
<div className="flex flex-row items-center gap-spacing-interline">
|
||||
<input
|
||||
className="w-full h-[3rem] border bg-transparent rounded-08 p-padding-button"
|
||||
<InputTypeIn
|
||||
placeholder="Search..."
|
||||
value={searchQuery}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
/>
|
||||
<Link href="/assistants/new">
|
||||
<Button className="h-full">Create</Button>
|
||||
</Link>
|
||||
<Button
|
||||
href="/assistants/new"
|
||||
onClick={() => toggleModal(ModalIds.AgentsModal, false)}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="py-padding-content flex items-center gap-spacing-interline flex-wrap">
|
||||
|
||||
@@ -545,16 +545,18 @@ function AppSidebarInner() {
|
||||
|
||||
<SidebarWrapper folded={folded} setFolded={setFolded}>
|
||||
<div className="flex flex-col gap-spacing-interline">
|
||||
<NavigationTab
|
||||
icon={SvgEditBig}
|
||||
className="!w-full"
|
||||
folded={folded}
|
||||
onClick={() => route({})}
|
||||
active={Array.from(searchParams).length === 0}
|
||||
tooltip
|
||||
>
|
||||
New Session
|
||||
</NavigationTab>
|
||||
<div data-testid="AppSidebar/new-session">
|
||||
<NavigationTab
|
||||
icon={SvgEditBig}
|
||||
className="!w-full"
|
||||
folded={folded}
|
||||
onClick={() => route({})}
|
||||
active={Array.from(searchParams).length === 0}
|
||||
tooltip
|
||||
>
|
||||
New Session
|
||||
</NavigationTab>
|
||||
</div>
|
||||
|
||||
{folded && (
|
||||
<>
|
||||
@@ -600,13 +602,15 @@ function AppSidebarInner() {
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
<NavigationTab
|
||||
icon={SvgMoreHorizontal}
|
||||
onClick={() => toggleModal(ModalIds.AgentsModal, true)}
|
||||
lowlight
|
||||
>
|
||||
More Agents
|
||||
</NavigationTab>
|
||||
<div data-testid="AppSidebar/more-agents">
|
||||
<NavigationTab
|
||||
icon={SvgMoreHorizontal}
|
||||
onClick={() => toggleModal(ModalIds.AgentsModal, true)}
|
||||
lowlight
|
||||
>
|
||||
More Agents
|
||||
</NavigationTab>
|
||||
</div>
|
||||
</SidebarSection>
|
||||
|
||||
<SidebarSection title="Projects">
|
||||
|
||||
@@ -101,13 +101,11 @@ function SettingsPopover({
|
||||
Curator Panel
|
||||
</NavigationTab>
|
||||
),
|
||||
<NavigationTab
|
||||
key="user-settings"
|
||||
icon={SvgUser}
|
||||
onClick={onUserSettingsClick}
|
||||
>
|
||||
User Settings
|
||||
</NavigationTab>,
|
||||
<div key="user-settings" data-testid="Settings/user-settings">
|
||||
<NavigationTab icon={SvgUser} onClick={onUserSettingsClick}>
|
||||
User Settings
|
||||
</NavigationTab>
|
||||
</div>,
|
||||
<NavigationTab
|
||||
key="notifications"
|
||||
icon={SvgBell}
|
||||
@@ -216,7 +214,7 @@ export default function Settings({
|
||||
}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="flex flex-col w-full h-full">
|
||||
<div className="flex flex-col w-full h-full" id="onyx-user-dropdown">
|
||||
<NavigationTab
|
||||
className="!w-full"
|
||||
icon={({ className }) => (
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { test, expect } from "@chromatic-com/playwright";
|
||||
import { loginAsRandomUser } from "../utils/auth";
|
||||
import { loginAsRandomUser } from "@tests/e2e/utils/auth";
|
||||
import {
|
||||
navigateToAssistantInHistorySidebar,
|
||||
sendMessage,
|
||||
startNewChat,
|
||||
verifyAssistantIsChosen,
|
||||
} from "../utils/chatActions";
|
||||
import {
|
||||
GREETING_MESSAGES,
|
||||
TOOL_IDS,
|
||||
waitForUnifiedGreeting,
|
||||
openActionManagement,
|
||||
} from "../utils/tools";
|
||||
} from "@tests/e2e/utils/chatActions";
|
||||
import { TOOL_IDS, openActionManagement } from "@tests/e2e/utils/tools";
|
||||
|
||||
// Tool-related test selectors now imported from shared utils
|
||||
|
||||
@@ -26,62 +20,6 @@ test.describe("Default Assistant Tests", () => {
|
||||
await page.waitForLoadState("networkidle");
|
||||
});
|
||||
|
||||
test.describe("Greeting Message Display", () => {
|
||||
test("should display greeting message when opening new chat with default assistant", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Look for greeting message - should be one from the predefined list
|
||||
const greetingText = await waitForUnifiedGreeting(page);
|
||||
|
||||
// Verify the greeting is from the predefined list
|
||||
expect(GREETING_MESSAGES).toContain(greetingText?.trim());
|
||||
});
|
||||
|
||||
test("greeting message should remain consistent during session", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Get initial greeting
|
||||
const initialGreeting = await waitForUnifiedGreeting(page);
|
||||
|
||||
// Reload the page
|
||||
await page.reload();
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Get greeting after reload
|
||||
const greetingAfterReload = await waitForUnifiedGreeting(page);
|
||||
|
||||
// Both greetings should be valid but might differ after reload
|
||||
expect(GREETING_MESSAGES).toContain(initialGreeting?.trim());
|
||||
expect(GREETING_MESSAGES).toContain(greetingAfterReload?.trim());
|
||||
});
|
||||
|
||||
test("greeting should only appear for default assistant", async ({
|
||||
page,
|
||||
}) => {
|
||||
// First verify greeting appears for default assistant
|
||||
const greetingElement = await page.waitForSelector(
|
||||
'[data-testid="greeting-message"]',
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
expect(greetingElement).toBeTruthy();
|
||||
|
||||
// Create a custom assistant to test non-default behavior
|
||||
await page.getByRole("button", { name: "Explore Assistants" }).click();
|
||||
await page.getByRole("button", { name: "Create", exact: true }).click();
|
||||
await page.getByTestId("name").fill("Custom Test Assistant");
|
||||
await page.getByTestId("description").fill("Test Description");
|
||||
await page.getByTestId("system_prompt").fill("Test Instructions");
|
||||
await page.getByRole("button", { name: "Create" }).click();
|
||||
|
||||
// Wait for assistant to be created and selected
|
||||
await verifyAssistantIsChosen(page, "Custom Test Assistant");
|
||||
|
||||
// Greeting should NOT appear for custom assistant
|
||||
const customGreeting = await page.$('[data-testid="greeting-message"]');
|
||||
expect(customGreeting).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Default Assistant Branding", () => {
|
||||
test("should display Onyx logo for default assistant", async ({ page }) => {
|
||||
// Look for Onyx logo
|
||||
|
||||
@@ -25,8 +25,10 @@ test("Chat workflow", async ({ page }) => {
|
||||
// Verify the presence of the expected text
|
||||
await verifyAssistantIsChosen(page, "Onyx");
|
||||
|
||||
await page.pause();
|
||||
|
||||
// Test creation of a new assistant
|
||||
await page.getByRole("button", { name: "Explore Assistants" }).click();
|
||||
await page.getByTestId("AppSidebar/more-agents").click();
|
||||
await page.getByRole("button", { name: "Create", exact: true }).click();
|
||||
await page.getByTestId("name").click();
|
||||
await page.getByTestId("name").fill("Test Assistant");
|
||||
|
||||
@@ -24,7 +24,7 @@ test("LLM Ordering and Model Switching", async ({ page }) => {
|
||||
|
||||
// Configure user settings: Set default model to o3 Mini
|
||||
await page.locator("#onyx-user-dropdown").click();
|
||||
await page.getByText("User Settings").click();
|
||||
await page.getByTestId("Settings/user-settings").click();
|
||||
await page.getByRole("combobox").nth(1).click();
|
||||
await page.getByLabel("GPT 5", { exact: true }).click();
|
||||
await page.getByLabel("Close modal").click();
|
||||
|
||||
@@ -20,7 +20,9 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
// Test cancel editing
|
||||
let userMessage = page.locator("#onyx-human-message").first();
|
||||
await userMessage.hover();
|
||||
let editButton = userMessage.locator('[data-testid="edit-button"]').first();
|
||||
let editButton = userMessage
|
||||
.locator('[data-testid="HumanMessage/edit-button"]')
|
||||
.first();
|
||||
await editButton.click();
|
||||
|
||||
let textarea = userMessage.locator("textarea");
|
||||
@@ -36,7 +38,9 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
|
||||
// Edit the message for real
|
||||
await userMessage.hover();
|
||||
editButton = userMessage.locator('[data-testid="edit-button"]').first();
|
||||
editButton = userMessage
|
||||
.locator('[data-testid="HumanMessage/edit-button"]')
|
||||
.first();
|
||||
await editButton.click();
|
||||
|
||||
textarea = userMessage.locator("textarea");
|
||||
@@ -46,10 +50,10 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
await submitButton.click();
|
||||
|
||||
// Wait for the new AI response to complete
|
||||
await page.waitForSelector('[data-testid="copy-button"]', {
|
||||
await page.waitForSelector('[data-testid="AIMessage/copy-button"]', {
|
||||
state: "detached",
|
||||
});
|
||||
await page.waitForSelector('[data-testid="copy-button"]', {
|
||||
await page.waitForSelector('[data-testid="AIMessage/copy-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
@@ -62,16 +66,16 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
expect(messageContent).toContain("What is 3+3?");
|
||||
|
||||
// Verify version switcher appears and shows 2/2
|
||||
let messageSwitcher = page.locator('span:has-text("2 / 2")').first();
|
||||
let messageSwitcher = page.getByTestId("MessageSwitcher/container").first();
|
||||
await expect(messageSwitcher).toBeVisible();
|
||||
|
||||
// Get the parent div that contains the whole switcher
|
||||
messageSwitcher = messageSwitcher.locator("..").first();
|
||||
await expect(messageSwitcher).toContainText("2/2");
|
||||
|
||||
// Edit again to create a third version
|
||||
userMessage = page.locator("#onyx-human-message").first();
|
||||
await userMessage.hover();
|
||||
editButton = userMessage.locator('[data-testid="edit-button"]').first();
|
||||
editButton = userMessage
|
||||
.locator('[data-testid="HumanMessage/edit-button"]')
|
||||
.first();
|
||||
await editButton.click();
|
||||
|
||||
textarea = userMessage.locator("textarea");
|
||||
@@ -81,18 +85,19 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
await submitButton.click();
|
||||
|
||||
// Wait for the new AI response to complete
|
||||
await page.waitForSelector('[data-testid="copy-button"]', {
|
||||
await page.waitForSelector('[data-testid="AIMessage/copy-button"]', {
|
||||
state: "detached",
|
||||
});
|
||||
await page.waitForSelector('[data-testid="copy-button"]', {
|
||||
await page.waitForSelector('[data-testid="AIMessage/copy-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Verify navigation between versions
|
||||
// Find the switcher showing "3 / 3"
|
||||
let switcherSpan = page.locator('span:has-text("3 / 3")').first();
|
||||
let switcherSpan = page.getByTestId("MessageSwitcher/container").first();
|
||||
await expect(switcherSpan).toBeVisible();
|
||||
await expect(switcherSpan).toContainText("3/3");
|
||||
|
||||
// Navigate to previous version - click the first svg icon's parent (left chevron)
|
||||
await switcherSpan
|
||||
@@ -103,8 +108,9 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
.click();
|
||||
|
||||
// Check we're now at "2 / 3"
|
||||
switcherSpan = page.locator('span:has-text("2 / 3")').first();
|
||||
switcherSpan = page.getByTestId("MessageSwitcher/container").first();
|
||||
await expect(switcherSpan).toBeVisible({ timeout: 5000 });
|
||||
await expect(switcherSpan).toContainText("2/3");
|
||||
|
||||
// Navigate to first version - re-find the button each time
|
||||
await switcherSpan
|
||||
@@ -115,8 +121,9 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
.click();
|
||||
|
||||
// Check we're now at "1 / 3"
|
||||
switcherSpan = page.locator('span:has-text("1 / 3")').first();
|
||||
switcherSpan = page.getByTestId("MessageSwitcher/container").first();
|
||||
await expect(switcherSpan).toBeVisible({ timeout: 5000 });
|
||||
await expect(switcherSpan).toContainText("1/3");
|
||||
|
||||
// Navigate forward using next button - click the last svg icon's parent (right chevron)
|
||||
await switcherSpan
|
||||
@@ -127,8 +134,9 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
.click();
|
||||
|
||||
// Check we're back at "2 / 3"
|
||||
switcherSpan = page.locator('span:has-text("2 / 3")').first();
|
||||
switcherSpan = page.getByTestId("MessageSwitcher/container").first();
|
||||
await expect(switcherSpan).toBeVisible({ timeout: 5000 });
|
||||
await expect(switcherSpan).toContainText("2/3");
|
||||
});
|
||||
|
||||
test("Message regeneration with model selection", async ({ page }) => {
|
||||
@@ -153,8 +161,8 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
await aiMessage.hover();
|
||||
|
||||
// Click regenerate button using its data-testid
|
||||
const regenerateButton = aiMessage.locator(
|
||||
'[data-testid="regenerate-button"]'
|
||||
const regenerateButton = aiMessage.getByTestId(
|
||||
"AIMessage/regenerate-button"
|
||||
);
|
||||
await regenerateButton.click();
|
||||
|
||||
@@ -167,14 +175,17 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
|
||||
// Wait for regeneration to complete by waiting for feedback buttons to appear
|
||||
// The feedback buttons (copy, like, dislike, regenerate) appear when streaming is complete
|
||||
await page.waitForSelector('[data-testid="regenerate-button"]', {
|
||||
await page.waitForSelector('[data-testid="AIMessage/regenerate-button"]', {
|
||||
state: "visible",
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
// Verify version switcher appears showing "2 / 2"
|
||||
const messageSwitcher = page.locator('span:has-text("2 / 2")').first();
|
||||
const messageSwitcher = page
|
||||
.getByTestId("MessageSwitcher/container")
|
||||
.first();
|
||||
await expect(messageSwitcher).toBeVisible({ timeout: 5000 });
|
||||
await expect(messageSwitcher).toContainText("2/2");
|
||||
|
||||
// Navigate to previous version
|
||||
await messageSwitcher
|
||||
@@ -186,8 +197,9 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify we're at "1 / 2"
|
||||
let switcherSpan = page.locator('span:has-text("1 / 2")').first();
|
||||
await expect(switcherSpan).toBeVisible();
|
||||
let switcherSpan = page.getByTestId("MessageSwitcher/container").first();
|
||||
await expect(switcherSpan).toBeVisible({ timeout: 5000 });
|
||||
await expect(switcherSpan).toContainText("1/2");
|
||||
|
||||
// Verify we're back to the original response
|
||||
const firstVersionText = await messageContent.textContent();
|
||||
@@ -203,7 +215,8 @@ test.describe("Message Edit and Regenerate Tests", () => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify we're back at "2 / 2"
|
||||
switcherSpan = page.locator('span:has-text("2 / 2")').first();
|
||||
await expect(switcherSpan).toBeVisible();
|
||||
switcherSpan = page.getByTestId("MessageSwitcher/container").first();
|
||||
await expect(switcherSpan).toBeVisible({ timeout: 5000 });
|
||||
await expect(switcherSpan).toContainText("2/2");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,9 @@ export async function sendMessage(page: Page, message: string) {
|
||||
await page.locator("#onyx-chat-input-send-button").click();
|
||||
await page.waitForSelector('[data-testid="onyx-ai-message"]');
|
||||
// Wait for the copy button to appear, which indicates the message is fully rendered
|
||||
await page.waitForSelector('[data-testid="copy-button"]', { timeout: 30000 });
|
||||
await page.waitForSelector('[data-testid="AIMessage/copy-button"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Wait for up to 10 seconds for the URL to contain 'chatId='
|
||||
await page.waitForFunction(
|
||||
@@ -60,6 +62,6 @@ export async function switchModel(page: Page, modelName: string) {
|
||||
}
|
||||
|
||||
export async function startNewChat(page: Page) {
|
||||
await page.getByRole("link", { name: "New Chat" }).click();
|
||||
await expect(page.locator('div[data-testid="chat-intro"]')).toBeVisible();
|
||||
await page.getByTestId("AppSidebar/new-session").click();
|
||||
await expect(page.getByTestId("chat-intro")).toBeVisible();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": ["./src/*"],
|
||||
|
||||
"@app/*": ["./src/app/*"],
|
||||
"@tests/*": ["./tests/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
|
||||
Reference in New Issue
Block a user