feat: implement full-stack TaskPilot scheduler
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
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()
|
||||
})
|
||||
Reference in New Issue
Block a user