mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 20:21:39 +03:00
443 lines
22 KiB
TypeScript
443 lines
22 KiB
TypeScript
import { expect, test, type Locator, type Page } from "@playwright/test"
|
|
import { seed, sessionHref, type SeedInput } from "../utils/app"
|
|
import { browseFiles, openFileLauncher } from "../utils/side-panel"
|
|
import { fixture, mockStressTimeline } from "../utils/session-fixture"
|
|
import { fileNode } from "../utils/workspace"
|
|
|
|
// The source and target sessions; the child session is not a home row.
|
|
const rows = 2
|
|
|
|
async function openStress(page: Page, extra: SeedInput = {}) {
|
|
await seed(page, {
|
|
usedProjects: { local: [fixture.project.id] },
|
|
tabs: [fixture.sourceID, fixture.targetID],
|
|
...extra,
|
|
})
|
|
}
|
|
|
|
for (const position of ["top", "bottom"] as const) {
|
|
test(`mobile session tabs switch views and work with ${position} navigation`, async ({
|
|
page,
|
|
}) => {
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await mockStressTimeline(page, { fileList: () => [] })
|
|
await openStress(page, {
|
|
settings: { general: { mobileTitlebarPosition: position } },
|
|
})
|
|
await page.goto(sessionHref(fixture.targetID))
|
|
|
|
const tabs = page.getByRole("tablist", { name: "Session view", exact: true })
|
|
const navigation = page.locator('[data-slot="session-mobile-view-navigation"]')
|
|
const more = tabs.getByRole("tab", { name: "More...", exact: true })
|
|
const views = page.getByRole("dialog", { name: "More options", exact: true })
|
|
const picker = tabs.getByRole("tab", { selected: true })
|
|
|
|
const message = page.locator(
|
|
`[data-timeline-row="UserMessage"][data-message-id="${fixture.expected.targetMessageIDs.at(-1)}"]`,
|
|
)
|
|
|
|
const composer = page.getByRole("textbox", { name: "Prompt", exact: true })
|
|
await expect(picker).toHaveText("Session")
|
|
await expect(navigation).toHaveCount(1)
|
|
await expect(message).toBeVisible()
|
|
await expect(composer).toBeVisible()
|
|
await expect(tabs.getByRole("tab")).toHaveText(["Session", "Changes", "More..."])
|
|
await expect(tabs).toHaveCSS("padding-left", "0px")
|
|
await expect(tabs).toHaveCSS("padding-right", "0px")
|
|
|
|
if (position === "top") {
|
|
const titlebar = page.locator('[data-slot="titlebar-v2"]')
|
|
await expect(titlebar).toHaveCSS("padding-top", "8px")
|
|
await expect(titlebar).toHaveCSS("height", "36px")
|
|
}
|
|
|
|
await expect
|
|
.poll(async () => {
|
|
const bounds = await navigation.boundingBox()
|
|
|
|
return !!bounds && bounds.x >= 1 && bounds.x <= 2 && bounds.width >= 386 && bounds.width <= 388
|
|
})
|
|
.toBe(true)
|
|
await expect
|
|
.poll(async () => {
|
|
const bar = await tabs.boundingBox()
|
|
const input = await composer.boundingBox()
|
|
const dock = await page.locator('[data-component="session-composer-dock"]').boundingBox()
|
|
const panel = await page.locator('[data-slot="session-chat-panel"]').boundingBox()
|
|
|
|
if (!bar || !input || !dock || !panel) return false
|
|
|
|
if (position === "bottom")
|
|
return bar.y >= dock.y + dock.height && Math.abs(bar.y + bar.height - panel.y - panel.height) <= 1
|
|
|
|
return Math.abs(bar.y - panel.y) <= 1 && bar.y + bar.height <= input.y
|
|
})
|
|
.toBe(true)
|
|
await expect(page.locator("[data-session-title]")).toHaveCount(0)
|
|
await expect(page.locator('[data-slot="mobile-tabs-trigger"]')).toContainText(fixture.expected.targetTitle)
|
|
await page.getByRole("button", { name: "Tabs", exact: true }).click()
|
|
const drawer = page.getByRole("dialog", { name: "Tabs", exact: true })
|
|
await expect(drawer).toHaveAttribute("data-open", "")
|
|
await expect(drawer).not.toHaveAttribute("data-transitioning")
|
|
await expect(drawer.getByRole("button", { name: "Settings", exact: true })).toBeInViewport()
|
|
await drawer.getByRole("button", { name: "Settings", exact: true }).click()
|
|
await expect(page.getByTestId("settings-screen")).toBeVisible()
|
|
await page.getByRole("button", { name: "Back to app", exact: true }).click()
|
|
await page.getByRole("button", { name: "Tabs", exact: true }).click()
|
|
await expect(drawer).not.toHaveAttribute("data-transitioning")
|
|
await expect(drawer.getByRole("button", { name: "Settings", exact: true })).toBeInViewport()
|
|
await page.keyboard.press("Escape")
|
|
await expect(drawer).toBeHidden()
|
|
|
|
await more.click()
|
|
await expect(views).toBeVisible()
|
|
await expect(more).toHaveAttribute("aria-expanded", "true")
|
|
|
|
for (const name of ["Files", "Terminal", "Usage", "Session details"])
|
|
await expect(views.getByRole("button", { name, exact: true })).toBeVisible()
|
|
await expect(views).not.toHaveAttribute("data-transitioning")
|
|
await page.keyboard.press("Escape")
|
|
await expect(views).toBeHidden()
|
|
await expect(more).toHaveAttribute("aria-expanded", "false")
|
|
await expect(more).toBeFocused()
|
|
await expect(picker).toHaveText("Session")
|
|
await more.press("Enter")
|
|
await views.getByRole("button", { name: "Usage", exact: true }).click()
|
|
await expect(views).toBeHidden()
|
|
await expect(picker).toHaveText("More...")
|
|
await expect(page.getByText("Total Cost", { exact: true })).toBeVisible()
|
|
const usage = page.locator('[data-slot="session-usage-content"]')
|
|
await expect(usage).toHaveCSS("padding-top", "16px")
|
|
await expect(usage).toHaveCSS("padding-inline-start", "16px")
|
|
await expect(usage).toHaveCSS("padding-inline-end", "16px")
|
|
await expect(composer).toBeHidden()
|
|
|
|
await more.click()
|
|
await expect(views.getByRole("button", { name: "Usage", exact: true })).toHaveAttribute("aria-pressed", "true")
|
|
await expect(views.getByRole("button", { name: "Status", exact: true })).toHaveCount(0)
|
|
await views.evaluate((element) => element.setAttribute("data-drawer-probe", "single"))
|
|
await views.getByRole("button", { name: "Session details", exact: true }).click()
|
|
const details = page.getByRole("dialog", { name: "Session details", exact: true })
|
|
await expect(details).toHaveAttribute("data-drawer-probe", "single")
|
|
await expect(page.getByRole("dialog")).toHaveCount(1)
|
|
await expect(page.locator('[data-slot="mobile-drawer-overlay"]')).toHaveCount(1)
|
|
await expect(details.locator('[data-slot="mobile-panel-header"]')).toHaveCount(0)
|
|
await expect(details.getByRole("button", { name: "Close", exact: true })).toHaveCount(0)
|
|
await expect(details.getByRole("heading", { name: "Session details", exact: true })).toHaveCSS("width", "1px")
|
|
await expect(details.getByRole("button", { name: "Local repository", exact: true })).toHaveCount(0)
|
|
await expect(details.getByRole("button", { name: "No changes", exact: true })).toHaveCount(0)
|
|
await expect(details.getByRole("button", { name: "MCP", exact: true })).toBeVisible()
|
|
await page.locator('[data-slot="mobile-drawer-overlay"]').click({ position: { x: 10, y: 10 } })
|
|
await expect(details).toBeHidden()
|
|
await expect(more).toBeFocused()
|
|
await more.click()
|
|
await views.getByRole("button", { name: "Session details", exact: true }).click()
|
|
await expect(details.getByRole("heading", { name: "Extensions", exact: true })).toBeVisible()
|
|
await expect(details).not.toHaveAttribute("data-transitioning")
|
|
await page.keyboard.press("Escape")
|
|
await expect(details).toBeHidden()
|
|
await expect(more).toBeFocused()
|
|
await tabs.getByRole("tab", { name: "Session", exact: true }).click()
|
|
await page.locator('[data-slot="session-location-bar"] [data-slot="session-location-trigger"]').click()
|
|
const projectMenu = page.locator('[data-slot="session-location-card"]')
|
|
await projectMenu.getByRole("menuitem", { name: "No changes", exact: true }).click()
|
|
await expect(projectMenu).toBeHidden()
|
|
await expect(picker).toHaveText("Changes")
|
|
await expect(page.getByText("No uncommitted changes yet", { exact: true })).toBeVisible()
|
|
await expect(page.locator('[data-slot="session-review-header"]')).toHaveCSS("height", "40px")
|
|
await expect(page.locator('[data-slot="session-review-header"]')).toHaveCSS("padding-left", "8px")
|
|
await expect(composer).toBeHidden()
|
|
|
|
await more.click()
|
|
await views.getByRole("button", { name: "Files", exact: true }).click()
|
|
await expect(views).toBeHidden()
|
|
await expect(picker).toHaveText("More...")
|
|
await expect(page.getByRole("combobox", { name: "Filter files", exact: true })).toBeVisible()
|
|
await expect(composer).toBeHidden()
|
|
|
|
await more.click()
|
|
await expect(views.getByRole("button", { name: "Files", exact: true })).toHaveAttribute("aria-pressed", "true")
|
|
await page.keyboard.press("Escape")
|
|
await expect(views).toBeHidden()
|
|
await tabs.getByRole("tab", { name: "Session", exact: true }).click()
|
|
await expect(message).toBeVisible()
|
|
|
|
// The view resets to Session whenever the routed session changes, including through Home.
|
|
const trigger = page.locator('[data-slot="mobile-tabs-trigger"]')
|
|
|
|
const openTabs = async () => {
|
|
await trigger.click()
|
|
await expect(drawer).not.toHaveAttribute("data-transitioning")
|
|
}
|
|
|
|
const openTab = async (title: string) => {
|
|
await openTabs()
|
|
await drawer.locator('[data-slot="tab-link"]').filter({ hasText: title }).click()
|
|
await expect(drawer).toBeHidden()
|
|
await expect(trigger).toContainText(title)
|
|
}
|
|
|
|
await more.click()
|
|
await views.getByRole("button", { name: "Usage", exact: true }).click()
|
|
await expect(page.getByText("Total Cost", { exact: true })).toBeVisible()
|
|
await openTab(fixture.expected.sourceTitle)
|
|
await expect(picker).toHaveText("Session")
|
|
await more.click()
|
|
await views.getByRole("button", { name: "Files", exact: true }).click()
|
|
await expect(picker).toHaveText("More...")
|
|
await openTab(fixture.expected.targetTitle)
|
|
await expect(picker).toHaveText("Session")
|
|
await openTab(fixture.expected.sourceTitle)
|
|
await expect(picker).toHaveText("Session")
|
|
await more.click()
|
|
await views.getByRole("button", { name: "Files", exact: true }).click()
|
|
await expect(picker).toHaveText("More...")
|
|
await openTabs()
|
|
await drawer.getByRole("button", { name: "Home", exact: true }).click()
|
|
await expect(page).toHaveURL("/")
|
|
await expect(drawer).toBeHidden()
|
|
await openTab(fixture.expected.sourceTitle)
|
|
await expect(picker).toHaveText("Session")
|
|
|
|
await page.setViewportSize({ width: 1280, height: 900 })
|
|
await expect(picker).toBeHidden()
|
|
await expect(page.locator("[data-session-title]")).toBeVisible()
|
|
})
|
|
}
|
|
|
|
test("the project menu's changes row switches the view and keeps the side tabs", async ({ page }) => {
|
|
await page.setViewportSize({ width: 1280, height: 900 })
|
|
await mockStressTimeline(page, {
|
|
fileList: (path) => (path ? [] : [fileNode(fixture.directory, "README.md")]),
|
|
fileContent: (path) => ({ type: "text", content: `contents:${path}` }),
|
|
})
|
|
await openStress(page)
|
|
await page.goto(sessionHref(fixture.targetID))
|
|
|
|
// One click previews the file in place of the Open file tab.
|
|
const panel = page.locator("#review-panel")
|
|
const readme = panel.getByRole("tab", { name: "README.md", exact: true })
|
|
await page.getByRole("button", { name: "Toggle review", exact: true }).click()
|
|
await openFileLauncher(panel)
|
|
await panel.getByRole("treeitem", { name: "README.md", exact: true }).click()
|
|
await expect(readme).toHaveAttribute("aria-selected", "true")
|
|
await expect(panel.getByRole("tab", { name: "Open file", exact: true })).toHaveCount(0)
|
|
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
const tabs = page.getByRole("tablist", { name: "Session view", exact: true })
|
|
const more = tabs.getByRole("tab", { name: "More...", exact: true })
|
|
const views = page.getByRole("dialog", { name: "More options", exact: true })
|
|
await page.locator('[data-slot="session-location-bar"] [data-slot="session-location-trigger"]').click()
|
|
const projectMenu = page.locator('[data-slot="session-location-card"]')
|
|
await projectMenu.getByRole("menuitem", { name: "No changes", exact: true }).click()
|
|
await expect(projectMenu).toBeHidden()
|
|
await expect(tabs.getByRole("tab", { selected: true })).toHaveText("Changes")
|
|
await more.click()
|
|
await views.getByRole("button", { name: "Files", exact: true }).click()
|
|
await expect(
|
|
page.getByRole("tablist", { name: "Open files", exact: true }).getByRole("tab", { name: "README.md", exact: true }),
|
|
).toHaveAttribute("aria-selected", "true")
|
|
|
|
await page.setViewportSize({ width: 1280, height: 900 })
|
|
await expect(readme).toHaveAttribute("aria-selected", "true")
|
|
|
|
// After a reload forgets the preview, a stored Open file tab is still the slot a narrow-screen file open replaces.
|
|
const launcher = panel.getByRole("tab", { name: "Open file", exact: true })
|
|
await browseFiles(page)
|
|
await expect(launcher).toHaveAttribute("aria-selected", "true")
|
|
// The launcher opens beside the preview; closing the file leaves only the launcher stored.
|
|
await readme.click({ button: "middle" })
|
|
await expect(readme).toHaveCount(0)
|
|
await expect(launcher).toHaveAttribute("aria-selected", "true")
|
|
await page.reload()
|
|
await expect(launcher).toHaveAttribute("aria-selected", "true")
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await more.click()
|
|
await views.getByRole("button", { name: "Files", exact: true }).click()
|
|
const files = page.locator('[data-slot="session-mobile-files"]')
|
|
await files.getByRole("treeitem", { name: "README.md", exact: true }).click()
|
|
await expect(files.getByText("contents:README.md", { exact: true })).toBeVisible()
|
|
await page.setViewportSize({ width: 1280, height: 900 })
|
|
await expect(readme).toHaveAttribute("aria-selected", "true")
|
|
await expect(launcher).toHaveCount(0)
|
|
})
|
|
|
|
test("a narrow-screen palette pick keeps the view, and the side region shows the file when wide", async ({ page }) => {
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await mockStressTimeline(page, {
|
|
fileList: (path) => (path ? [] : [fileNode(fixture.directory, "README.md")]),
|
|
fileContent: (path) => ({ type: "text", content: `contents:${path}` }),
|
|
findFiles: ({ query }) => ("README.md".includes(query) ? [fileNode(fixture.directory, "README.md")] : []),
|
|
persistentPty: {},
|
|
})
|
|
await openStress(page)
|
|
await page.goto(sessionHref(fixture.targetID))
|
|
|
|
const tabs = page.getByRole("tablist", { name: "Session view", exact: true })
|
|
const terminal = page.locator("#terminal-panel")
|
|
const views = page.getByRole("dialog", { name: "More options", exact: true })
|
|
await tabs.getByRole("tab", { name: "More...", exact: true }).click()
|
|
await views.getByRole("button", { name: "Terminal", exact: true }).click()
|
|
await expect(views).toBeHidden()
|
|
await expect(terminal.locator("textarea")).toBeEditable()
|
|
await page.keyboard.press("ControlOrMeta+p")
|
|
const dialog = page.getByRole("dialog")
|
|
await dialog.getByRole("combobox").fill("README")
|
|
await dialog.getByRole("option", { name: "/ README.md", exact: true }).click()
|
|
await expect(dialog).toHaveCount(0)
|
|
await expect(tabs.getByRole("tab", { selected: true })).toHaveText("More...")
|
|
await expect(terminal).toBeVisible()
|
|
|
|
await page.setViewportSize({ width: 1280, height: 900 })
|
|
const panel = page.locator("#review-panel")
|
|
await expect(panel.getByRole("tab", { name: "README.md", exact: true })).toHaveAttribute("aria-selected", "true")
|
|
await expect(panel.getByText("contents:README.md", { exact: true })).toBeVisible()
|
|
})
|
|
|
|
test.describe("touch", () => {
|
|
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true })
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await mockStressTimeline(page)
|
|
await openStress(page)
|
|
await page.goto("/")
|
|
await expect(page.locator('[data-component="home-session-row"]')).toHaveCount(rows)
|
|
})
|
|
|
|
test("mobile project selection and drawer navigation preserve session identity", async ({ page }) => {
|
|
const projects = page.getByRole("button", { name: "Projects", exact: true })
|
|
await projects.click()
|
|
const picker = page.getByRole("dialog", { name: "Projects", exact: true })
|
|
await expect(picker.getByRole("button", { name: "All projects", exact: true })).toBeVisible()
|
|
await picker.getByRole("button", { name: new RegExp(` ${fixture.project.name}$`) }).click()
|
|
await expect(picker).toBeHidden()
|
|
await expect(projects).toContainText(fixture.project.name)
|
|
|
|
const trigger = page.locator('[data-slot="mobile-tabs-trigger"]')
|
|
const drawer = page.locator('[data-slot="mobile-tabs-drawer"]')
|
|
await trigger.click()
|
|
await expect(trigger).toHaveAttribute("aria-expanded", "true")
|
|
await expect(drawer.locator('[data-slot="tab-project"]')).toHaveCount(0)
|
|
const settings = drawer.getByRole("button", { name: "Settings", exact: true })
|
|
const help = drawer.getByRole("button", { name: "Help", exact: true })
|
|
await expect(settings).toBeVisible()
|
|
await expect(help).toBeVisible()
|
|
expect((await settings.boundingBox())?.width).toBeGreaterThan((await help.boundingBox())?.width ?? 0)
|
|
|
|
await drawer.locator('[data-slot="tab-link"]').filter({ hasText: fixture.expected.sourceTitle }).click()
|
|
await expect(page).toHaveURL(new RegExp(`/session/${fixture.sourceID}$`))
|
|
await expect(drawer).toBeHidden()
|
|
await expect(trigger).toContainText(fixture.expected.sourceTitle)
|
|
await expect(trigger).toBeFocused()
|
|
await trigger.click()
|
|
await drawer.getByRole("button", { name: "Home", exact: true }).click()
|
|
await expect(page).toHaveURL("/")
|
|
await expect(drawer).toBeHidden()
|
|
await expect(page.locator('[data-component="home-session-row"]')).toHaveCount(rows)
|
|
})
|
|
|
|
test("mobile settings section menu stays above a full-width panel", async ({ page }) => {
|
|
await page.locator('[data-slot="mobile-tabs-trigger"]').click()
|
|
await page
|
|
.locator('[data-slot="mobile-tabs-drawer"]')
|
|
.getByRole("button", { name: "Settings", exact: true })
|
|
.click()
|
|
const settings = page.getByTestId("settings-screen")
|
|
const menu = settings.getByRole("button", { name: "Preferences", exact: true })
|
|
const panel = settings.getByRole("tabpanel")
|
|
await expect(settings.getByRole("heading", { name: "General", exact: true })).toBeVisible()
|
|
await expect(page).toHaveURL("/settings")
|
|
await expect(menu).toBeVisible()
|
|
await menu.click()
|
|
await expect(page.getByRole("menuitemradio", { name: "Preferences", exact: true })).toBeChecked()
|
|
await page.keyboard.press("Escape")
|
|
await expect(page.getByRole("menuitemradio", { name: "Preferences", exact: true })).toBeHidden()
|
|
await expect(settings).toBeVisible()
|
|
await menu.click()
|
|
await page.getByRole("menuitemradio", { name: "Models", exact: true }).click()
|
|
await expect(settings.getByRole("heading", { name: "Models", exact: true })).toBeVisible()
|
|
await expect(settings.getByRole("button", { name: "Models", exact: true })).toBeVisible()
|
|
await expect(settings.getByRole("switch", { name: "Claude Opus 4.6", exact: true })).toBeAttached()
|
|
await expect
|
|
.poll(async () => {
|
|
const navigation = await settings.getByRole("button", { name: "Models", exact: true }).boundingBox()
|
|
const content = await panel.boundingBox()
|
|
|
|
return !!navigation && !!content && navigation.y + navigation.height <= content.y
|
|
})
|
|
.toBe(true)
|
|
await expect.poll(async () => (await panel.boundingBox())?.width ?? 0).toBeGreaterThan(350)
|
|
await settings.getByRole("button", { name: "Back to app", exact: true }).click()
|
|
await expect(page).toHaveURL("/")
|
|
await expect(settings).toBeHidden()
|
|
await expect(page.locator('[data-component="home-session-row"]')).toHaveCount(rows)
|
|
})
|
|
|
|
test.describe("tab drawer", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.locator('[data-slot="mobile-tabs-trigger"]').click()
|
|
await expect(page.locator('[data-slot="mobile-tabs-drawer"]')).toBeVisible()
|
|
const drawer = page.locator('[data-slot="mobile-drawer-content"]')
|
|
await expect
|
|
.poll(() => drawer.evaluate((element) => new DOMMatrixReadOnly(getComputedStyle(element).transform).m42))
|
|
.toBe(0)
|
|
await expect(drawer).not.toHaveAttribute("data-transitioning")
|
|
})
|
|
|
|
test("reorders session tabs with touch", async ({ page }) => {
|
|
const tabs = page.locator('[data-slot="vertical-tabs"] a')
|
|
await expect(tabs).toContainText([fixture.expected.sourceTitle, fixture.expected.targetTitle])
|
|
const target = tabs.filter({ hasText: fixture.expected.targetTitle })
|
|
const source = tabs.filter({ hasText: fixture.expected.sourceTitle })
|
|
const targetBox = await target.boundingBox()
|
|
const sourceBox = await source.boundingBox()
|
|
expect(targetBox).not.toBeNull()
|
|
expect(sourceBox).not.toBeNull()
|
|
|
|
await touchDrag(page, source, {
|
|
from: { x: sourceBox!.x + sourceBox!.width / 2, y: sourceBox!.y + sourceBox!.height / 2 },
|
|
to: { x: targetBox!.x + targetBox!.width / 2, y: targetBox!.y + targetBox!.height / 2 },
|
|
})
|
|
|
|
await expect(tabs).toContainText([fixture.expected.targetTitle, fixture.expected.sourceTitle])
|
|
})
|
|
|
|
test("dismisses a touch context menu by tapping outside", async ({ page }) => {
|
|
const drawer = page.locator('[data-slot="mobile-tabs-drawer"]')
|
|
const tab = drawer.locator('[data-slot="tab-link"]').filter({ hasText: fixture.expected.sourceTitle })
|
|
const homeBox = await drawer.getByRole("button", { name: "Home", exact: true }).boundingBox()
|
|
const box = await tab.boundingBox()
|
|
expect(homeBox).not.toBeNull()
|
|
expect(box).not.toBeNull()
|
|
const point = { x: box!.x + box!.width / 2, y: box!.y + box!.height / 2 }
|
|
const touch = { pointerType: "touch", pointerId: 1, isPrimary: true, clientX: point.x, clientY: point.y }
|
|
|
|
await tab.dispatchEvent("pointerdown", touch)
|
|
const rename = page.getByRole("menuitem", { name: "Rename", exact: true })
|
|
await expect(rename).toBeVisible()
|
|
await expect(drawer).toBeVisible()
|
|
await tab.filter({ visible: true }).dispatchEvent("pointerup", touch)
|
|
|
|
await page.touchscreen.tap(homeBox!.x + homeBox!.width / 2, homeBox!.y + homeBox!.height / 2)
|
|
await expect(rename).toBeHidden()
|
|
await expect(drawer).toBeVisible()
|
|
})
|
|
})
|
|
})
|
|
|
|
async function touchDrag(
|
|
page: Page,
|
|
source: Locator,
|
|
input: { from: { x: number; y: number }; to: { x: number; y: number } },
|
|
) {
|
|
const client = await page.context().newCDPSession(page)
|
|
await client.send("Input.dispatchTouchEvent", { type: "touchStart", touchPoints: [{ ...input.from, id: 1 }] })
|
|
await client.send("Input.dispatchTouchEvent", {
|
|
type: "touchMove",
|
|
touchPoints: [{ x: (input.from.x + input.to.x) / 2, y: (input.from.y + input.to.y) / 2, id: 1 }],
|
|
})
|
|
await expect(source).toHaveCount(2)
|
|
await client.send("Input.dispatchTouchEvent", { type: "touchMove", touchPoints: [{ ...input.to, id: 1 }] })
|
|
await client.send("Input.dispatchTouchEvent", { type: "touchEnd", touchPoints: [] })
|
|
}
|