191 lines
10 KiB
TypeScript
191 lines
10 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
// The default Agent and Help integrations over real Postgres and the simulated Cloud protocol.
|
|
import { expect, type Page, test } from "@playwright/test"
|
|
import { moduleSelected } from "../modules/inclusion"
|
|
import { call, makeOrganization, signIn, unique, violations } from "./support/app"
|
|
|
|
test.skip(
|
|
!["agent", "guides", "support"].every((id) => moduleSelected(id as "agent" | "guides" | "support")),
|
|
"Assistance was explicitly excluded",
|
|
)
|
|
|
|
async function prepare(page: Page) {
|
|
await signIn(page, `${unique("owner")}@example.test`)
|
|
const scope = await makeOrganization(page)
|
|
await page.goto(`/${scope.org}/${scope.workspace}/modules/notes`)
|
|
// Functional panel checks start after the person resolves privacy choices.
|
|
// The first-visit consent flow has its own keyboard and accessibility suite.
|
|
await page.getByRole("button", { name: "Reject analytics and replay", exact: true }).click()
|
|
await expect(page.getByRole("region", { name: "Privacy choices", exact: true })).toHaveCount(0)
|
|
return scope
|
|
}
|
|
|
|
test("Agent streams and persists a marked answer, and keeps a draft when switching panels", async ({ page }, info) => {
|
|
const errors: string[] = []
|
|
page.on("pageerror", (error) => errors.push(error.message))
|
|
const scope = await prepare(page)
|
|
await expect(page.locator("[data-conusai-guide-root]")).toHaveCount(0)
|
|
await page.getByRole("button", { name: "Agent", exact: true }).click()
|
|
await expect(page.getByText("Ask the Agent", { exact: true })).toBeVisible()
|
|
await expect(page.getByText("The Agent is AI and can make mistakes. Check anything important.")).toBeVisible()
|
|
const input = page.getByRole("textbox", { name: "Message the Agent" })
|
|
await input.fill("Explain this workspace")
|
|
await input.press("Shift+Enter")
|
|
await input.press("x")
|
|
await expect(input).toHaveValue("Explain this workspace\nx")
|
|
await input.press("Enter")
|
|
const answer = page.getByRole("log").getByText("This is the fake platform: no model is behind it.", { exact: true })
|
|
await expect(answer).toBeVisible()
|
|
await expect
|
|
.poll(async () => {
|
|
const list = await call<{ conversations: { conversationId: string }[] }>(page, "assistant.conversation.list", {
|
|
org: scope.org,
|
|
workspace: scope.workspace,
|
|
})
|
|
const id = list.conversations[0]?.conversationId
|
|
if (!id) return null
|
|
const conversation = await call<{ turns: { status: string; answerProvenance: unknown }[] }>(
|
|
page,
|
|
"assistant.conversation.get",
|
|
{ org: scope.org, workspace: scope.workspace, conversationId: id },
|
|
)
|
|
return conversation.turns.at(-1)
|
|
})
|
|
.toMatchObject({ status: "complete", answerProvenance: { ai_generated: true } })
|
|
await expect(input).toBeFocused()
|
|
await expect(page.locator(".kit-side-panel-head")).toBeInViewport({ ratio: 1 })
|
|
expect(await page.evaluate(() => window.scrollY)).toBe(0)
|
|
await page.screenshot({ path: info.outputPath("agent-desktop.png") })
|
|
expect(await violations(page)).toEqual([])
|
|
await page.reload()
|
|
await page.getByRole("button", { name: "Agent", exact: true }).click()
|
|
await expect(answer).toBeVisible()
|
|
await input.fill("Keep this draft")
|
|
await page.getByRole("button", { name: "Full screen", exact: true }).click()
|
|
const full = page.getByRole("dialog", { name: "Agent", exact: true })
|
|
await expect(full).toBeVisible()
|
|
await expect(full.getByRole("textbox", { name: "Message the Agent" })).toHaveValue("Keep this draft")
|
|
await expect(answer).toBeVisible()
|
|
expect(await violations(page)).toEqual([])
|
|
await page.screenshot({ path: info.outputPath("agent-fullscreen.png") })
|
|
await full.getByRole("button", { name: "Restore panel", exact: true }).click()
|
|
await expect(full).toBeHidden()
|
|
await expect(input).toHaveValue("Keep this draft")
|
|
await page.getByRole("button", { name: "Help", exact: true }).click()
|
|
await page.getByRole("tab", { name: "Agent", exact: true }).click()
|
|
await expect(input).toHaveValue("Keep this draft")
|
|
await input.focus()
|
|
await page.keyboard.press("Escape")
|
|
const guard = page.getByRole("alertdialog")
|
|
await expect(guard).toBeVisible()
|
|
await guard.getByRole("button", { name: /Keep/ }).click()
|
|
await expect(input).toHaveValue("Keep this draft")
|
|
await input.fill("")
|
|
await page.getByRole("button", { name: "Help", exact: true }).click()
|
|
await page.getByRole("tab", { name: "Agent", exact: true }).click()
|
|
await expect(answer).toBeVisible()
|
|
expect(errors).toEqual([])
|
|
})
|
|
|
|
test("Help runs a requested Notes tour and explains local Support on a phone", async ({ page }, info) => {
|
|
const scope = await prepare(page)
|
|
await page.setViewportSize({ width: 375, height: 812 })
|
|
await page.goto(`/${scope.org}/${scope.workspace}/${scope.project}/notes`)
|
|
await expect(page.locator("[data-conusai-guide-root]")).toHaveCount(0)
|
|
await page.getByRole("button", { name: "Help", exact: true }).click()
|
|
const panel = page.getByRole("dialog", { name: "Help", exact: true })
|
|
await expect(panel.getByText("Get started with notes", { exact: true })).toBeVisible()
|
|
await panel.getByRole("checkbox", { name: "Tell your team where the notes are" }).check()
|
|
await expect(panel.getByRole("checkbox", { name: "Tell your team where the notes are" })).toBeChecked()
|
|
await panel.getByRole("button", { name: "Start guide: Take the notes tour", exact: true }).click()
|
|
const tour = page.locator("[data-conusai-guide-root]")
|
|
await expect(tour.getByRole("heading", { name: "Write a note", exact: true })).toBeVisible()
|
|
await expect(panel).toBeHidden()
|
|
await page.screenshot({ path: info.outputPath("guides-mobile-tour.png") })
|
|
await tour.getByRole("button", { name: "Next", exact: true }).click()
|
|
await tour.getByRole("button", { name: "Next", exact: true }).click()
|
|
await tour.getByRole("button", { name: "Done", exact: true }).click()
|
|
await page.getByRole("button", { name: "Help", exact: true }).click()
|
|
await panel.getByRole("tab", { name: "Support", exact: true }).click()
|
|
await expect(panel.getByText("Support is not connected in this local demo", { exact: true })).toBeVisible()
|
|
await expect(panel.getByRole("button", { name: "Try again" })).toHaveCount(0)
|
|
await expect(page.locator(".support-launcher")).toHaveCount(0)
|
|
expect(await violations(page)).toEqual([])
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
|
await page.screenshot({ path: info.outputPath("help-mobile-support.png") })
|
|
})
|
|
|
|
test("Support distinguishes loading, a retryable failure and a permission refusal", async ({ page }, info) => {
|
|
await prepare(page)
|
|
let release: (() => void) | undefined
|
|
const wait = new Promise<void>((resolve) => {
|
|
release = resolve
|
|
})
|
|
await page.route("**/api/v1/support.widget.config", async (route) => {
|
|
await wait
|
|
await route.fulfill({
|
|
status: 503,
|
|
contentType: "application/problem+json",
|
|
body: JSON.stringify({ status: 503, title: "Unavailable" }),
|
|
})
|
|
})
|
|
await page.getByRole("button", { name: "Help", exact: true }).click()
|
|
const panel = page.locator("[data-slot=side-panel]")
|
|
await panel.getByRole("tab", { name: "Support", exact: true }).click()
|
|
await expect(panel.locator("[data-slot=support-loading]")).toBeVisible()
|
|
await page.screenshot({ path: info.outputPath("support-loading.png") })
|
|
release?.()
|
|
await expect(panel.locator("[data-slot=support-unavailable]")).toBeVisible()
|
|
await page.unroute("**/api/v1/support.widget.config")
|
|
await panel.getByRole("button", { name: "Try again", exact: true }).focus()
|
|
await page.keyboard.press("Enter")
|
|
await expect(panel.locator("[data-slot=support-offline]")).toBeVisible()
|
|
await page.route("**/api/v1/support.widget.config", (route) =>
|
|
route.fulfill({
|
|
status: 403,
|
|
contentType: "application/problem+json",
|
|
body: JSON.stringify({ status: 403, title: "Forbidden" }),
|
|
}),
|
|
)
|
|
await page.reload()
|
|
await page.getByRole("button", { name: "Help", exact: true }).click()
|
|
await panel.getByRole("tab", { name: "Support", exact: true }).click()
|
|
await expect(panel.locator("[data-slot=support-denied]")).toBeVisible()
|
|
await expect(panel.getByRole("button", { name: "Try again" })).toHaveCount(0)
|
|
})
|
|
|
|
test("navigation keeps the Agent draft and respects reduced motion", async ({ page }) => {
|
|
const errors: string[] = []
|
|
page.on("pageerror", (error) => errors.push(error.message))
|
|
const scope = await prepare(page)
|
|
await page.emulateMedia({ reducedMotion: "no-preference" })
|
|
await page.evaluate(() => {
|
|
const native = document.startViewTransition.bind(document)
|
|
const proof = window as unknown as { transitions: number; sameDocument: string }
|
|
proof.transitions = 0
|
|
proof.sameDocument = "preserved"
|
|
document.startViewTransition = ((...args: Parameters<typeof native>) => {
|
|
proof.transitions += 1
|
|
return native(...args)
|
|
}) as typeof document.startViewTransition
|
|
})
|
|
await page.getByRole("button", { name: "Agent", exact: true }).click()
|
|
const input = page.getByRole("textbox", { name: "Message the Agent" })
|
|
await input.fill("Keep this while navigating")
|
|
await page.getByRole("link", { name: "Workspace overview", exact: true }).click()
|
|
await expect(page).toHaveURL(new RegExp(`/${scope.org}/${scope.workspace}$`))
|
|
await expect(input).toHaveValue("Keep this while navigating")
|
|
await expect
|
|
.poll(() => page.evaluate(() => (window as unknown as { transitions: number }).transitions))
|
|
.toBeGreaterThan(0)
|
|
await page.waitForFunction(() => !document.documentElement.matches(":active-view-transition"))
|
|
const before = await page.evaluate(() => (window as unknown as { transitions: number }).transitions)
|
|
await page.emulateMedia({ reducedMotion: "reduce" })
|
|
await page.getByRole("link", { name: "Notes in this workspace", exact: true }).click()
|
|
await expect(page).toHaveURL(new RegExp(`/${scope.org}/${scope.workspace}/modules/notes$`))
|
|
await expect(input).toHaveValue("Keep this while navigating")
|
|
expect(await page.evaluate(() => (window as unknown as { transitions: number }).transitions)).toBe(before)
|
|
expect(await page.evaluate(() => (window as unknown as { sameDocument: string }).sameDocument)).toBe("preserved")
|
|
expect(errors).toEqual([])
|
|
})
|