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

93 lines
2.9 KiB
TypeScript

import { expect, test } from "@playwright/test"
import { api, apiPage, createFlow, deleteAll } from "./utils/api"
/**
* One socket serves the whole shell, and it has to survive the routes under it
* coming and going.
*
* It used to belong to whichever component's effect ran first, which on Home is
* the brain graph rather than the shell around it. Leaving Home closed the
* socket while the shell held the reference count above zero, and the page was
* deaf for the rest of its life: no live values, no pulses, and nothing that
* would ever reconnect.
*/
const flowName = `test_live_${Date.now().toString(36)}`
test.use({ storageState: "playwright/.auth/user.json" })
test.beforeAll(async ({ browser }) => {
const page = await apiPage(browser)
await createFlow(page, {
name: flowName,
title: "Live",
nodes: [
{
id: "source",
type: "python",
provides: [{ name: "reading", dtype: "float" }],
},
{
id: "sink",
type: "python",
requires: [{ name: "reading", dtype: "float" }],
},
],
})
const detail = await (await api(page, `/flows/${flowName}`)).json()
await api(page, `/flows/${flowName}/publish`, {
method: "POST",
data: { version: detail.definition.version },
})
await page.close()
})
test.afterAll(async ({ browser }) => {
await deleteAll(browser, [`/flows/${flowName}`])
})
test("the live socket survives leaving Home and coming back", async ({
page,
}) => {
await page.addInitScript(() => {
const Native = window.WebSocket
const opened: WebSocket[] = []
;(window as unknown as { __sockets: WebSocket[] }).__sockets = opened
window.WebSocket = class extends Native {
constructor(url: string | URL, protocols?: string | string[]) {
super(url, protocols)
opened.push(this)
}
}
})
await page.goto("/")
const neuron = page.locator(".brain-cell").first()
await neuron.waitFor()
// The full-bleed canvas is its own shell, so a trip through it takes the
// sidebar shell down to nothing: coming back mounts the shell and the brain
// graph in the same commit, which is the order that used to pick an owner.
await page.goto(`/flows/${flowName}`)
const bar = page.locator('[data-sidebar="sidebar"]')
await bar.getByRole("link", { name: "Home", exact: true }).click()
await neuron.waitFor()
// And this is the navigation that used to end it: a sibling route in the
// same shell, which unmounts the graph but not the shell.
await bar.getByRole("link", { name: "Flows", exact: true }).click()
await page.waitForURL(/\/flows$/)
await bar.getByRole("link", { name: "Home", exact: true }).click()
await neuron.waitFor()
await expect
.poll(() =>
page.evaluate(() =>
(window as unknown as { __sockets: WebSocket[] }).__sockets.some(
(socket) => socket.readyState === 1,
),
),
)
.toBe(true)
})