EternalAI/e2e/creator.spec.js

242 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { test, expect } = require('@playwright/test');
const testData = require('./fixtures/test-data');
const { cleanDatabase, seedExistingUser, disconnect, prisma } = require('./fixtures/database');
test.describe('创作者中心与角色发布/编辑', () => {
let existingUser;
let existingRole;
test.beforeAll(async () => {
await cleanDatabase();
await seedExistingUser();
existingUser = await prisma.user.findUnique({
where: { account: testData.users.existing.account },
});
existingRole = await prisma.role.create({
data: {
creatorId: existingUser.id,
displayName: '已有角色',
gender: 'female',
personality: '温柔体贴',
background: '来自南方',
speechStyle: '轻声细语',
greeting: '你好~',
desc: '温柔体贴的角色',
price: 19.9,
status: 'running',
reviewStatus: 'synced',
temperature: 0.8,
maxTokens: 2048,
enableMemory: true,
enableTools: false,
model: 'gpt-4o',
},
});
});
test.afterAll(async () => {
await cleanDatabase();
await disconnect();
});
async function loginAsExisting(page) {
await page.goto('/');
await page.locator('[data-action="open-characters"]').first().click();
await expect(page.locator('#auth')).toBeVisible();
await page.fill('[data-form="login"] [name="account"]', testData.users.existing.account);
await page.fill('[data-form="login"] [name="password"]', testData.users.existing.password);
await page.locator('[data-form="login"] button[type="submit"]').click();
await expect(page.locator('#creator-center')).toBeVisible({ timeout: 5000 });
}
test('创作者中心显示三个 tab', async ({ page }) => {
await loginAsExisting(page);
await expect(page.locator('[data-center-tab="roles"]')).toBeVisible();
await expect(page.locator('[data-center-tab="income"]')).toBeVisible();
await expect(page.locator('[data-center-tab="settings"]')).toBeVisible();
});
test('角色 tab 显示已有角色', async ({ page }) => {
await loginAsExisting(page);
// 默认在 roles tab
await expect(page.locator('#creator-role-list .role-card')).toHaveCount(1, { timeout: 5000 });
await expect(page.locator('#creator-role-list .role-card__name')).toHaveText('已有角色');
});
test('收入 tab 显示余额和流水', async ({ page }) => {
await loginAsExisting(page);
await page.locator('[data-center-tab="income"]').click();
// income 面板可见(#center-income 带 active class
await expect(page.locator('#center-income')).toBeVisible();
// 余额元素可见
await expect(page.locator('#income-balance')).toBeVisible({ timeout: 5000 });
// 流水列表容器存在
await expect(page.locator('#income-list')).toBeVisible();
});
test('设置 tab 可保存昵称和库名', async ({ page }) => {
await loginAsExisting(page);
await page.locator('[data-center-tab="settings"]').click();
await expect(page.locator('#center-settings')).toBeVisible();
// 修改设置
await page.fill('#settings-form [name="creatorName"]', '新昵称');
await page.fill('#settings-form [name="libraryName"]', '新库名');
// 保存成功会弹 alert「设置已保存」
const dialogPromise = page.waitForEvent('dialog');
await page.locator('#settings-form button[type="submit"]').click();
const dialog = await dialogPromise;
expect(dialog.message()).toContain('保存');
await dialog.accept();
});
test('新建角色完整流程4步表单', async ({ page }) => {
await loginAsExisting(page);
// 点击新建角色
await page.locator('[data-action="new-role"]').click();
await expect(page.locator('#creator')).toBeVisible();
// Step 1: 基础身份agentId 必填pattern [a-zA-Z0-9_]+displayName 必填)
await page.fill('#character-form [name="agentId"]', 'e2e_test_role');
await page.fill('#character-form [name="displayName"]', testData.role.displayName);
await page.selectOption('#character-form [name="gender"]', testData.role.gender);
await page.fill('#character-form [name="age"]', testData.role.age);
await page.locator('.form-step.active [data-action="next"]').click();
// Step 2: 灵魂设定background / personality / speechStyle 必填)
await page.fill('#character-form [name="background"]', testData.role.background);
await page.fill('#character-form [name="personality"]', testData.role.personality);
await page.fill('#character-form [name="speechStyle"]', testData.role.speechStyle);
await page.fill('#character-form [name="likes"]', testData.role.likes);
await page.fill('#character-form [name="dislikes"]', testData.role.dislikes);
await page.locator('.form-step.active [data-action="next"]').click();
// Step 3: 关系与记忆greeting 必填)
await page.fill('#character-form [name="relationship"]', testData.role.relationship);
await page.fill('#character-form [name="memories"]', testData.role.memories);
await page.fill('#character-form [name="secrets"]', testData.role.secrets);
await page.fill('#character-form [name="greeting"]', testData.role.greeting);
await page.locator('.form-step.active [data-action="next"]').click();
// Step 4: 运行配置model/temperature/maxTokens 有默认值;无 price 字段)
// 捕获可能的错误弹窗
let dialogMessage = '';
page.on('dialog', async (dialog) => {
dialogMessage = dialog.message();
await dialog.accept();
});
await page.locator('.form-step.active [data-action="publish"]').click();
// 应显示生成结果面板
await expect(page.locator('#result-panel')).toBeVisible({ timeout: 15000 });
// 验证 Soul.md 内容(单个 #preview-code 元素,默认显示 soul
await expect(page.locator('#preview-code')).toBeVisible();
const soulContent = await page.locator('#preview-code').textContent();
expect(soulContent).toContain(testData.role.displayName);
// generateSoulMd 会把 personality 按 [,] 拆分成 " | " 连接的标签
const personalityTag = testData.role.personality.split(/[,]/)[0].trim();
expect(soulContent).toContain(personalityTag);
// 切换到 config.yaml 预览preview-tabs 内的 [data-tab="config"]
await page.locator('.preview-tabs [data-tab="config"]').click();
const configContent = await page.locator('#preview-code').textContent();
expect(configContent).toContain('model:');
expect(configContent).toContain('temperature:');
});
test('新建角色后数据库有记录', async ({ page }) => {
await loginAsExisting(page);
await page.locator('[data-action="new-role"]').click();
await expect(page.locator('#creator')).toBeVisible();
// Step 1: 填写最小必填字段
await page.fill('#character-form [name="agentId"]', 'e2e_db_role');
await page.fill('#character-form [name="displayName"]', 'DB验证角色');
await page.selectOption('#character-form [name="gender"]', 'female');
await page.locator('.form-step.active [data-action="next"]').click();
// Step 2
await page.fill('#character-form [name="personality"]', '测试性格');
await page.fill('#character-form [name="background"]', '测试背景');
await page.fill('#character-form [name="speechStyle"]', '测试风格');
await page.locator('.form-step.active [data-action="next"]').click();
// Step 3
await page.fill('#character-form [name="greeting"]', '测试问候');
await page.locator('.form-step.active [data-action="next"]').click();
// Step 4: 直接发布(无 price 字段,使用默认值)
let dialogMessage = '';
page.on('dialog', async (dialog) => {
dialogMessage = dialog.message();
await dialog.accept();
});
await page.locator('.form-step.active [data-action="publish"]').click();
await expect(page.locator('#result-panel')).toBeVisible({ timeout: 15000 });
// 验证数据库
const dbRole = await prisma.role.findFirst({
where: { displayName: 'DB验证角色' },
});
expect(dbRole).not.toBeNull();
expect(dbRole.personality).toBe('测试性格');
expect(dbRole.greeting).toBe('测试问候');
expect(dbRole.soulMd).not.toBeNull();
expect(dbRole.configYaml).not.toBeNull();
});
test('编辑角色加载已有数据', async ({ page }) => {
await loginAsExisting(page);
// 等待角色列表加载,找到已 seeded 的角色(其他测试可能已创建额外角色)
await expect(page.locator('#creator-role-list .role-card')).not.toHaveCount(0, { timeout: 5000 });
// 定位到"已有角色"那张卡片的编辑按钮
const seededCard = page.locator('#creator-role-list .role-card', { hasText: '已有角色' }).first();
await expect(seededCard).toBeVisible({ timeout: 5000 });
await seededCard.locator('[data-action="edit-role"]').click();
await expect(page.locator('#creator')).toBeVisible();
// 验证表单已预填loadRoleForEdit 会拉取 /roles/:id/full 填充表单)
await expect(page.locator('#character-form [name="displayName"]')).toHaveValue('已有角色', { timeout: 5000 });
await expect(page.locator('#character-form [name="personality"]')).toHaveValue('温柔体贴');
});
test('表单验证 - 必填字段为空时阻止提交', async ({ page }) => {
await loginAsExisting(page);
await page.locator('[data-action="new-role"]').click();
await expect(page.locator('#creator')).toBeVisible();
// 不填任何字段直接点下一步
await page.locator('.form-step.active [data-action="next"]').click();
// 应仍停留在 step 0agentId / displayName 必填,验证失败)
// step 0 仍 active => agentId 字段仍可见
await expect(page.locator('#character-form [name="agentId"]')).toBeVisible();
await expect(page.locator('#character-form [name="displayName"]')).toBeVisible();
});
test('步骤导航 - 上一步按钮', async ({ page }) => {
await loginAsExisting(page);
await page.locator('[data-action="new-role"]').click();
await expect(page.locator('#creator')).toBeVisible();
// 填写 step 1 并前进
await page.fill('#character-form [name="agentId"]', 'e2e_nav_test');
await page.fill('#character-form [name="displayName"]', '导航测试');
await page.selectOption('#character-form [name="gender"]', 'female');
await page.locator('.form-step.active [data-action="next"]').click();
// 进入 step 2 后点上一步data-action="prev",非 prev-step
await expect(page.locator('#character-form [name="background"]')).toBeVisible();
await page.locator('.form-step.active [data-action="prev"]').click();
// 应回到 step 1且数据保留
await expect(page.locator('#character-form [name="displayName"]')).toBeVisible();
await expect(page.locator('#character-form [name="displayName"]')).toHaveValue('导航测试');
});
});