cfbed53f96
Use ProviderId as the canonical provider model identity and remove resource-level/provider-kind/source model duplication. Treat manual as the provider id for manual servers and external domains, deriving domain DNS association from same-name zones.
215 lines
7.3 KiB
TypeScript
215 lines
7.3 KiB
TypeScript
import { expect, test, type Locator, type Page } from "@playwright/test"
|
|
|
|
import { closeLegionGui, launchLegionGui, type LegionGuiApp } from "./legion-electron"
|
|
|
|
const UNLOCK_PASSWORD = "legion-gui-map-password"
|
|
const SERVER_LABEL = "MockKing24 map node"
|
|
|
|
test.setTimeout(45_000)
|
|
|
|
test("moves a planned server location through the map context menu", async () => {
|
|
const gui: LegionGuiApp | null = await launchLegionGui({
|
|
mockNetwork: true
|
|
})
|
|
|
|
try {
|
|
await test.step("plan a mock server", async () => {
|
|
await installStateProbe(gui.page)
|
|
await unlock(gui.page)
|
|
await dismissTutorial(gui.page)
|
|
await configureTribe(gui.page)
|
|
await configureMockProvider(gui.page)
|
|
await planMockServer(gui.page)
|
|
})
|
|
|
|
await test.step("move the server marker through the map context menu", async () => {
|
|
await gui.page.getByRole("button", { name: "Cluster" }).click()
|
|
const marker = gui.page.getByRole("button", { exact: true, name: SERVER_LABEL })
|
|
await expect(marker).toBeVisible({ timeout: 10_000 })
|
|
|
|
await marker.click({ button: "right" })
|
|
const moveLocation = gui.page.getByRole("button", { name: "Move map location" })
|
|
const popover = gui.page.locator(".world-map-marker-popover")
|
|
await expect(popover).toBeVisible()
|
|
await expect(moveLocation).toBeVisible()
|
|
await expectMenuButtonToReceivePointerEvents(moveLocation)
|
|
await moveLocation.click()
|
|
|
|
const map = gui.page.locator(".world-map")
|
|
const box = await map.boundingBox()
|
|
if (!box) {
|
|
throw new Error("World map bounding box was not available.")
|
|
}
|
|
|
|
await gui.page.mouse.click(box.x + box.width * 0.55, box.y + box.height * 0.55)
|
|
await expect(popover).toBeHidden()
|
|
|
|
await gui.page.mouse.click(box.x + box.width * 0.65, box.y + box.height * 0.45, {
|
|
button: "right"
|
|
})
|
|
await gui.page.getByRole("button", { name: "Move server here" }).click()
|
|
|
|
await expect
|
|
.poll(() => plannedServerLocation(gui!.page), { timeout: 10_000 })
|
|
.toMatchObject({
|
|
hasLocationOverride: true
|
|
})
|
|
})
|
|
} finally {
|
|
await closeLegionGui(gui)
|
|
}
|
|
})
|
|
|
|
async function unlock(page: Page): Promise<void> {
|
|
await expect(page.getByRole("heading", { name: "Create local Legion instance" })).toBeVisible()
|
|
await page.getByLabel("Password", { exact: true }).fill(UNLOCK_PASSWORD)
|
|
await page.getByLabel("Confirm password").fill(UNLOCK_PASSWORD)
|
|
await page.getByRole("button", { name: "Create" }).click()
|
|
await expect(page.getByRole("button", { name: "Instances" })).toBeVisible()
|
|
}
|
|
|
|
async function dismissTutorial(page: Page): Promise<void> {
|
|
const dismissCheckbox = page.getByLabel("Go away forever").last()
|
|
if ((await dismissCheckbox.count()) > 0) {
|
|
await dismissCheckbox.check({ force: true, timeout: 1_000 }).catch(() => undefined)
|
|
}
|
|
}
|
|
|
|
async function configureTribe(page: Page): Promise<void> {
|
|
await page.getByRole("button", { name: "Open settings" }).click()
|
|
await page.getByRole("textbox", { name: "Tribe name" }).fill("Legion GUI Map")
|
|
await page.getByRole("textbox", { name: "Description" }).fill("gui-map.example.test")
|
|
await page.getByRole("button", { name: "Save tribe" }).click()
|
|
await expect
|
|
.poll(() => tribeState(page), { timeout: 10_000 })
|
|
.toMatchObject({
|
|
name: "Legion GUI Map",
|
|
description: "gui-map.example.test"
|
|
})
|
|
}
|
|
|
|
async function configureMockProvider(page: Page): Promise<void> {
|
|
await page.getByRole("button", { name: "Providers" }).click()
|
|
const mockCard = page.locator("article").filter({ hasText: "MockKing24" })
|
|
await mockCard.getByLabel("Error rate").fill("0")
|
|
await mockCard.getByLabel("Average latency (ms)").fill("0")
|
|
await mockCard.getByLabel("Seed").fill("gui-map")
|
|
await mockCard.getByRole("button", { name: "Save" }).click()
|
|
await expect.poll(() => providerCount(page, "mock"), { timeout: 10_000 }).toBe(1)
|
|
await page.getByRole("button", { name: "Close dialog" }).click()
|
|
}
|
|
|
|
async function planMockServer(page: Page): Promise<void> {
|
|
await page.getByRole("button", { name: "Instances" }).click()
|
|
await page.getByRole("button", { name: "Add server" }).first().click()
|
|
await expect(page.getByRole("heading", { name: "Server" })).toBeVisible()
|
|
await page.locator("dialog select").first().selectOption("mock")
|
|
await expect(page.getByRole("button", { name: "Selected" }).first()).toBeVisible()
|
|
await page.getByRole("textbox", { name: "Label" }).fill(SERVER_LABEL)
|
|
await page.getByRole("button", { name: "Save server" }).click()
|
|
await expect(page.getByLabel(`Toggle details for ${SERVER_LABEL}`)).toBeVisible()
|
|
}
|
|
|
|
async function expectMenuButtonToReceivePointerEvents(button: Locator): Promise<void> {
|
|
await expect
|
|
.poll(async () => {
|
|
return button.evaluate((element) => {
|
|
const bounds = element.getBoundingClientRect()
|
|
const target = document.elementFromPoint(
|
|
bounds.left + bounds.width / 2,
|
|
bounds.top + bounds.height / 2
|
|
)
|
|
return element === target || element.contains(target)
|
|
})
|
|
})
|
|
.toBe(true)
|
|
}
|
|
|
|
async function plannedServerLocation(page: Page): Promise<{
|
|
hasLocationOverride: boolean
|
|
latitude?: number
|
|
longitude?: number
|
|
}> {
|
|
return page.evaluate(() => {
|
|
return (window as unknown as StateProbeWindow).__legionGuiTestState.plannedServerLocation
|
|
})
|
|
}
|
|
|
|
async function tribeState(page: Page): Promise<{ name?: string; description?: string }> {
|
|
return page.evaluate(() => {
|
|
return (window as unknown as StateProbeWindow).__legionGuiTestState.tribe
|
|
})
|
|
}
|
|
|
|
async function providerCount(page: Page, id: string): Promise<number> {
|
|
return page.evaluate((provider) => {
|
|
return (window as unknown as StateProbeWindow).__legionGuiTestState.providers.filter(
|
|
(entry) => entry === provider
|
|
).length
|
|
}, id)
|
|
}
|
|
|
|
async function installStateProbe(page: Page): Promise<void> {
|
|
await page.evaluate((serverLabel) => {
|
|
const target = window as unknown as StateProbeWindow
|
|
target.__legionGuiTestState = {
|
|
plannedServerLocation: {
|
|
hasLocationOverride: false
|
|
},
|
|
tribe: {},
|
|
providers: []
|
|
}
|
|
target.api.onSnapshot((snapshot) => {
|
|
const server = snapshot?.scheme.servers.find((entry) => entry.label === serverLabel)
|
|
target.__legionGuiTestState.plannedServerLocation = {
|
|
hasLocationOverride: Boolean(server?.locationOverride),
|
|
latitude: server?.locationOverride?.latitude,
|
|
longitude: server?.locationOverride?.longitude
|
|
}
|
|
target.__legionGuiTestState.tribe = {
|
|
name: snapshot?.tribe?.name,
|
|
description: snapshot?.tribe?.description
|
|
}
|
|
target.__legionGuiTestState.providers =
|
|
snapshot?.providers.map((provider) => provider.id) ?? []
|
|
})
|
|
}, SERVER_LABEL)
|
|
}
|
|
|
|
interface AppSnapshotLike {
|
|
scheme: {
|
|
servers: Array<{
|
|
label: string
|
|
locationOverride?: {
|
|
latitude: number
|
|
longitude: number
|
|
}
|
|
}>
|
|
}
|
|
tribe?: {
|
|
name?: string
|
|
description?: string
|
|
}
|
|
providers: Array<{
|
|
id: string
|
|
}>
|
|
}
|
|
|
|
interface StateProbeWindow {
|
|
api: {
|
|
onSnapshot(callback: (snapshot: AppSnapshotLike | null) => void): () => void
|
|
}
|
|
__legionGuiTestState: {
|
|
plannedServerLocation: {
|
|
hasLocationOverride: boolean
|
|
latitude?: number
|
|
longitude?: number
|
|
}
|
|
tribe: {
|
|
name?: string
|
|
description?: string
|
|
}
|
|
providers: string[]
|
|
}
|
|
}
|