Files
app/frontend/tests/search.spec.ts
T

132 lines
4.6 KiB
TypeScript

import { expect, test } from "@playwright/test"
import { apiPage, createFlow, deleteAll } from "./utils/api"
/**
* The global search: the only way to reach a node without knowing its flow.
*
* The load-bearing part is what the flat index turns back into — a node hit is
* a flow address with that node in focus, which no list endpoint could have
* answered.
*/
const flowName = `test_search_${Date.now().toString(36)}`
test.use({ storageState: "playwright/.auth/user.json" })
test.describe.configure({ mode: "serial" })
test.beforeAll(async ({ browser }) => {
const page = await apiPage(browser)
await createFlow(page, {
name: flowName,
title: "Searchable flow",
nodes: [
{
id: "findme",
type: "python",
title: "Find me",
params: { tuning_gain: 7, api_token: "s3cr3t-token" },
},
],
inputs: [{ spec: { name: "findme_input", dtype: "float" } }],
})
await page.close()
})
test.afterAll(async ({ browser }) => {
await deleteAll(browser, [`/flows/${flowName}`])
})
test("finds a node by name and opens its flow on it", async ({ page }) => {
await page.goto("/")
await page.getByRole("button", { name: "Search", exact: true }).click()
await page.getByTestId("global-search-input").fill("findme")
await page.getByRole("option", { name: /Find me/ }).click()
await expect(page).toHaveURL(new RegExp(`/flows/${flowName}\\?node=findme$`))
})
test("finds a flow input and reads it where its label sits", async ({
page,
}) => {
await page.goto("/")
await page.getByRole("button", { name: "Search", exact: true }).click()
await page.getByTestId("global-search-input").fill("findme_input")
await page.getByRole("option", { name: /findme_input/ }).click()
await expect(page).toHaveURL(
new RegExp(`/flows/${flowName}\\?node=input%3A${flowName}\\.findme_input$`),
)
// What clicking the label shows, and the settings one press on.
await expect(page.getByTestId("input-inspector")).toBeVisible()
await expect(page.getByTestId("flow-panel")).toBeHidden()
await page.getByTestId("edit-input").click()
await expect(page.getByTestId("flow-panel")).toBeVisible()
})
test("the label popover rides along when the canvas is panned", async ({
page,
}) => {
const id = `input:${flowName}.findme_input`
await page.goto(`/flows/${flowName}?node=${encodeURIComponent(id)}`)
const popover = page.getByTestId("input-inspector")
const label = page.locator(`.react-flow__node[data-id="${id}"]`)
await expect(popover).toBeVisible()
// Its own entrance moves it too, which would read as part of the offset.
await expect
.poll(() => popover.evaluate((el) => el.getAnimations().length))
.toBe(0)
// Where the popover hangs off the label, which a pan must not change.
const offset = async () => {
const [from, to] = [await label.boundingBox(), await popover.boundingBox()]
return [Math.round(to!.x - from!.x), Math.round(to!.y - from!.y)]
}
const [before, at] = [await offset(), await label.boundingBox()]
const { width, height } = page.viewportSize() ?? { width: 0, height: 0 }
await page.mouse.move(width * 0.8, height * 0.2)
await page.mouse.down()
await page.mouse.move(width * 0.8 - 90, height * 0.2 + 50, { steps: 6 })
await page.mouse.up()
await expect.poll(async () => (await label.boundingBox())!.x).not.toBe(at!.x)
await expect(popover).toBeVisible()
await expect.poll(offset).toEqual(before)
})
test("opens on the keyboard from anywhere", async ({ page }) => {
await page.goto("/dashboards")
// The binding lives in the sidebar, so wait for it to be there to press at.
await expect(
page.getByRole("button", { name: "Search", exact: true }),
).toBeVisible()
await page.keyboard.press("ControlOrMeta+k")
await expect(page.getByTestId("global-search-input")).toBeFocused()
})
test("finds a setting with its value, and never a credential", async ({
page,
}) => {
await page.goto("/")
await page.getByRole("button", { name: "Search", exact: true }).click()
const input = page.getByTestId("global-search-input")
await input.fill("tuning_gain")
const gain = page.getByRole("option", { name: /findme\.tuning_gain/ })
await expect(gain).toContainText(`${flowName} · python · 7`)
// The token is masked on the server, so it never reaches the page.
await input.fill("api_token")
await expect(
page.getByRole("option", { name: /findme\.api_token/ }),
).toContainText("••••")
await expect(page.getByRole("dialog")).not.toContainText("s3cr3t-token")
await input.fill("tuning_gain")
await gain.click()
await expect(page).toHaveURL(new RegExp(`/flows/${flowName}\\?node=findme$`))
})