Files
deepseek-harness/apps/web/tests/schedule-manual.e2e.ts
T
ZiyaandMagolor 341ea6eea4 feat(schedule): integrate standalone tasks with the existing WebUI (#6189)
* feat(schedule): manage Host tasks with typed timing and delivery targets

* fix(schedule): align task creation button halves

* refactor(schedule): remove obsolete Session reminder runtime

* refactor(schedule): simplify task UI and preserve keyboard navigation

* fix(schedule): address task delivery and interface review findings

* test(schedule): normalize bundle module paths on Windows

* fix(schedule): cover reserved targets and trim unrelated changes

* refactor(schedule): minimize task management changes after rebase

* test(schedule): align rebased fixtures and documentation records

* test(schedule): refresh SDK scenarios added on master

---------

Co-authored-by: Magolor <[email protected]>
2026-10-29 18:11:48 +08:00

148 lines
11 KiB
TypeScript

/** Manual task creation and editing through the shipped Web composition. */
import { chromium } from 'playwright'
import { expect, it } from 'vitest'
import { launchWebScaffold, watchConsole } from './scaffold.ts'
it('creates from a paused empty state, selects the task, and saves an inline edit without starting an Agent', async () => {
// First use registers the default Workspace, which a new task's directory starts with.
const scaffold = await launchWebScaffold({ firstUse: true })
let browser: Awaited<ReturnType<typeof chromium.launch>> | undefined
try {
browser = await chromium.launch()
const page = await browser.newPage({ viewport: { width: 1440, height: 960 }, locale: 'zh-CN', timezoneId: 'Asia/Shanghai' })
await page.addInitScript(() => { localStorage.setItem('dsh.locale', 'zh') })
const tripwire = watchConsole(page)
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await page.getByRole('button', { name: '自动化任务', exact: true }).click()
const manager = page.getByTestId('task-manager-page')
const createStyles = await manager.getByRole('group', { name: '创建', exact: true }).getByRole('button').evaluateAll(buttons => buttons.map((button) => {
const style = getComputedStyle(button)
return { height: style.height, topLeft: style.borderTopLeftRadius, bottomLeft: style.borderBottomLeftRadius,
topRight: style.borderTopRightRadius, bottomRight: style.borderBottomRightRadius }
}))
expect(createStyles).toEqual([
{ height: '32px', topLeft: '16px', bottomLeft: '16px', topRight: '0px', bottomRight: '0px' },
{ height: '32px', topLeft: '0px', bottomLeft: '0px', topRight: '16px', bottomRight: '16px' },
])
await manager.getByRole('button', { name: '已暂停', exact: true }).click()
await manager.getByRole('heading', { name: '没有已暂停的自动化任务', exact: true }).waitFor()
await manager.getByRole('button', { name: '选择创建方式', exact: true }).click()
await page.getByRole('menuitem', { name: '手动创建', exact: true }).waitFor()
await page.keyboard.press('Escape')
await manager.getByRole('button', { name: '新建自动化任务', exact: true }).click()
const dialog = page.getByRole('dialog', { name: '手动创建', exact: true })
await dialog.getByRole('textbox', { name: '自动化任务名称', exact: true }).fill('AI 新闻简报')
await dialog.getByRole('textbox', { name: '自动化任务内容', exact: true }).fill('整理过去 24 小时的 AI 新闻,附原文链接。')
await dialog.getByRole('button', { name: /^重复 / }).click()
await page.getByRole('menuitem', { name: '周一至周五', exact: true }).click()
await dialog.getByRole('button', { name: '时间', exact: true }).click()
await page.getByRole('listbox', { name: '时', exact: true }).getByRole('option', { name: '09', exact: true }).click()
await page.getByRole('listbox', { name: '分', exact: true }).getByRole('option', { name: '00', exact: true }).click()
await page.getByRole('listbox', { name: '秒', exact: true }).getByRole('option', { name: '00', exact: true }).click()
await page.keyboard.press('Escape')
await dialog.getByRole('button', { name: /^执行位置 / }).click()
await page.getByRole('menuitem', { name: '新建会话并复用 首次运行时创建,之后沿用这个会话', exact: true }).waitFor()
expect(await page.getByRole('menuitem').count()).toBe(2)
const workspace = dialog.getByRole('button', { name: /^关联工作区 / })
await expect.poll(() => workspace.textContent()).toContain('默认工作区')
await page.keyboard.press('Escape')
// The workspace menu states each directory, so the default workspace names its folder.
const defaultPath = scaffold.ctx.workspaceRegistry.list()[0]?.path
if (defaultPath === undefined) throw new Error('first use did not register the default workspace')
await workspace.click()
await page.getByRole('menuitem').getByTitle(defaultPath, { exact: true }).waitFor()
await page.keyboard.press('Escape')
expect(await dialog.count()).toBe(1)
await dialog.getByRole('button', { name: '创建', exact: true }).click()
const detail = manager.getByRole('complementary', { name: '任务详情', exact: true })
await detail.getByRole('heading', { name: 'AI 新闻简报', exact: true }).waitFor()
expect(await manager.getByRole('button', { name: '全部', exact: true }).getAttribute('aria-pressed')).toBe('true')
expect(await manager.getByRole('list', { name: '任务列表', exact: true }).getByRole('button', { name: 'AI 新闻简报', exact: true }).count()).toBe(1)
await detail.getByText('待创建', { exact: true }).waitFor()
const task = (await scaffold.ctx.schedule.catalog()).find(task => task.title === 'AI 新闻简报')
if (task === undefined || task.target.kind !== 'new_once') throw new Error('manual task was not created with the reusable target')
expect(task.target.create.cwd).toBe(defaultPath)
await detail.getByRole('button', { name: '编辑', exact: true }).click()
await detail.getByRole('textbox', { name: '自动化任务内容', exact: true }).fill('整理过去 24 小时的 AI 新闻,附原文链接,按重要性排序。')
await detail.getByRole('button', { name: '保存修改', exact: true }).click()
await detail.getByRole('textbox', { name: '自动化任务内容', exact: true }).waitFor({ state: 'detached' })
await detail.getByText('整理过去 24 小时的 AI 新闻,附原文链接,按重要性排序。', { exact: true }).waitFor()
const saved = await scaffold.ctx.schedule.view({ id: task.id })
if ('code' in saved) throw new Error('edited task was not retained')
expect(saved.prompt).toBe('整理过去 24 小时的 AI 新闻,附原文链接,按重要性排序。')
await manager.getByRole('button', { name: '选择创建方式', exact: true }).click()
await page.getByRole('menuitem', { name: '手动创建', exact: true }).click()
await dialog.getByRole('textbox', { name: '自动化任务名称', exact: true }).fill('检查最新进展')
await dialog.getByRole('textbox', { name: '自动化任务内容', exact: true }).fill('汇总最近的更新')
await dialog.getByRole('button', { name: /^重复 / }).click()
await page.getByRole('menuitem', { name: '每 N 分钟', exact: true }).click()
await dialog.getByRole('spinbutton', { name: '重复间隔', exact: true }).fill('5')
await dialog.getByRole('button', { name: /^执行位置 / }).click()
await page.getByRole('menuitem', { name: '每次新建会话 每次运行都创建独立会话', exact: true }).click()
await dialog.getByText('运行间隔至少 1分钟', { exact: true }).waitFor()
const beforeCreate = Date.now()
await dialog.getByRole('button', { name: '创建', exact: true }).click()
await detail.getByRole('heading', { name: '检查最新进展', exact: true }).waitFor()
const intervalTask = (await scaffold.ctx.schedule.catalog()).find(item => item.title === '检查最新进展')
if (intervalTask === undefined) throw new Error('interval task was not created')
expect(intervalTask.target.kind).toBe('new')
expect(Date.parse(intervalTask.scheduledAt) - beforeCreate).toBeGreaterThan(240_000)
expect(Date.parse(intervalTask.scheduledAt) - beforeCreate).toBeLessThanOrEqual(300_000)
await detail.getByRole('button', { name: '暂停任务', exact: true }).click()
await detail.getByRole('button', { name: '恢复任务', exact: true }).waitFor()
expect(await detail.getByText('待创建', { exact: true }).count()).toBe(0)
expect(await detail.getByText('最近运行会话', { exact: true }).count()).toBe(0)
expect(await detail.getByText('运行间隔至少', { exact: false }).count()).toBe(0)
expect(tripwire.pageErrors).toEqual([])
expect(scaffold.ctx.agents.get(task.target.sessionId)).toBeUndefined()
} finally {
try { await browser?.close() } finally { await scaffold.close() }
}
})
it('creates and edits Custom Cron through the built UI while retaining its explicit zone and record kind', async () => {
const scaffold = await launchWebScaffold({ firstUse: true })
let browser: Awaited<ReturnType<typeof chromium.launch>> | undefined
try {
browser = await chromium.launch()
const page = await browser.newPage({ viewport: { width: 1440, height: 960 }, locale: 'zh-CN', timezoneId: 'Asia/Shanghai' })
await page.addInitScript(() => { localStorage.setItem('dsh.locale', 'zh') })
const tripwire = watchConsole(page)
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await page.getByRole('button', { name: '自动化任务', exact: true }).click()
const manager = page.getByTestId('task-manager-page')
await manager.getByRole('button', { name: '选择创建方式', exact: true }).click()
await page.getByRole('menuitem', { name: '手动创建', exact: true }).click()
const dialog = page.getByRole('dialog', { name: '手动创建', exact: true })
await dialog.getByRole('textbox', { name: '自动化任务名称', exact: true }).fill('Cron deployment check')
await dialog.getByRole('textbox', { name: '自动化任务内容', exact: true }).fill('Check the deployment status.')
await dialog.getByRole('button', { name: /^重复 / }).click()
await page.getByRole('menuitem', { name: '自定义', exact: true }).click()
await dialog.getByRole('textbox', { name: '自定义', exact: true }).fill('0 9 * * 1-5')
await dialog.getByRole('button', { name: /^时区 / }).waitFor()
await dialog.getByRole('button', { name: '创建', exact: true }).click()
const detail = manager.getByRole('complementary', { name: '任务详情', exact: true })
await detail.getByRole('heading', { name: 'Cron deployment check', exact: true }).waitFor()
const created = (await scaffold.ctx.schedule.catalog()).find(task => task.title === 'Cron deployment check')
if (created === undefined) throw new Error('Custom Cron creation did not store a task')
expect(created).toMatchObject({ kind: 'cron', expression: '0 9 * * 1-5', timeZone: 'Asia/Shanghai' })
expect(created).not.toHaveProperty('rrule')
await detail.getByRole('button', { name: '编辑', exact: true }).click()
await detail.getByRole('textbox', { name: '自定义', exact: true }).fill(' ')
await detail.getByRole('button', { name: '保存修改', exact: true }).click()
await detail.getByRole('alert').getByText('请检查规则内容和时区', { exact: true }).waitFor()
expect(await scaffold.ctx.schedule.view({ id: created.id })).toEqual(created)
await detail.getByRole('textbox', { name: '自定义', exact: true }).fill('0 10 * * 1-5')
await detail.getByRole('button', { name: /^时区 / }).waitFor()
await detail.getByRole('button', { name: '保存修改', exact: true }).click()
await detail.getByRole('textbox', { name: '自定义', exact: true }).waitFor({ state: 'detached' })
const edited = await scaffold.ctx.schedule.view({ id: created.id })
expect(edited).toMatchObject({ id: created.id, kind: 'cron', expression: '0 10 * * 1-5', timeZone: 'Asia/Shanghai' })
expect(edited).not.toHaveProperty('rrule')
expect(tripwire.pageErrors).toEqual([])
} finally {
try { await browser?.close() } finally { await scaffold.close() }
}
})