import { expect, test, type APIRequestContext } from '@playwright/test' const taskPayload = { id: 'e2e_echo', name: 'E2E echo', cron: '0 0 2 * * *', timezone: 'Asia/Shanghai', enabled: true, timeout_secs: 10, action: { type: 'script', working_dir: '.', interpreter: 'cmd.exe', script: '/C', args: ['echo', 'e2e-output'], env: {}, }, } async function authHeaders(request: APIRequestContext) { const response = await request.get('/api/v1/session') expect(response.ok()).toBeTruthy() const session = (await response.json()) as { token: string } return { 'X-TaskPilot-Token': session.token } } test('manages a task and reads a real run log', async ({ page, request }) => { const headers = await authHeaders(request) const existing = await request.get('/api/v1/tasks?page=1&page_size=100') expect(existing.ok()).toBeTruthy() const existingBody = (await existing.json()) as { items: Array<{ id: string }> } for (const task of existingBody.items) { await request.delete(`/api/v1/tasks/${task.id}`, { headers }) } const createResponse = await request.post('/api/v1/tasks', { data: taskPayload, headers, }) expect(createResponse.status()).toBe(201) await page.goto('/') await expect(page.getByText('E2E echo')).toBeVisible() await page.screenshot({ path: 'test-results/dashboard-desktop.png', fullPage: true, }) await page.getByRole('button', { name: '+ 新建任务' }).click() const newTaskDialog = page.getByRole('dialog', { name: '新建定时任务' }) await expect(newTaskDialog).toBeVisible() await expect(newTaskDialog.getByLabel('任务标识')).toBeFocused() await page.screenshot({ path: 'test-results/dashboard-task-editor.png', fullPage: true, }) await page.keyboard.press('Escape') await expect(newTaskDialog).toBeHidden() await page.getByRole('button', { name: '运行' }).click() const drawer = page.getByRole('dialog', { name: 'E2E echo' }) await expect(drawer).toBeVisible() await expect(drawer.getByText('e2e-output')).toBeVisible() await expect(drawer.locator('.status-succeeded').first()).toBeVisible() await page.screenshot({ path: 'test-results/dashboard-log-drawer.png', fullPage: true, }) await drawer.getByRole('button', { name: '关闭运行日志' }).click() await page.getByRole('button', { name: '编辑' }).click() const editor = page.getByRole('dialog', { name: '编辑任务 [e2e_echo]', }) await expect(editor).toBeVisible() const nameInput = editor.getByLabel('任务名称') await nameInput.fill('E2E updated') await editor.getByRole('button', { name: '保存修改' }).click() await expect(page.getByText('E2E updated')).toBeVisible() await page.getByRole('button', { name: '停用' }).click() await expect( page .getByRole('row', { name: /E2E updated/ }) .locator('.status-disabled'), ).toBeVisible() await page.getByRole('button', { name: '删除' }).click() await page .getByRole('alertdialog') .getByRole('button', { name: '确认删除' }) .click() await expect(page.getByRole('heading', { name: '还没有任务' })).toBeVisible() }) test('keeps the dashboard usable on a narrow viewport', async ({ page, request, }) => { const headers = await authHeaders(request) await request.post('/api/v1/tasks', { headers, data: { ...taskPayload, id: 'e2e_mobile', name: 'Mobile task', }, }) await page.setViewportSize({ width: 390, height: 844 }) await page.goto('/') await expect(page.getByText('Mobile task')).toBeVisible() await expect(page.getByRole('button', { name: '+ 新建任务' })).toBeVisible() await page.screenshot({ path: 'test-results/dashboard-mobile.png', fullPage: true, }) }) test('cancels a running task from the log drawer', async ({ page, request, }) => { const headers = await authHeaders(request) const response = await request.post('/api/v1/tasks', { headers, data: { ...taskPayload, id: 'e2e_cancel', name: 'Cancel task', timeout_secs: 30, action: { ...taskPayload.action, args: ['ping', '-t', '127.0.0.1'], }, }, }) expect(response.status()).toBe(201) await page.goto('/') await page .getByRole('row', { name: /Cancel task/ }) .getByRole('button', { name: '运行' }) .click() const drawer = page.getByRole('dialog', { name: 'Cancel task' }) await expect(drawer).toBeVisible() await expect(drawer.getByText('SSE 实时连接')).toBeVisible() await drawer.getByRole('button', { name: '取消运行' }).click() await expect(drawer.locator('.status-cancelled').first()).toBeVisible() })