1104 lines
51 KiB
TypeScript
1104 lines
51 KiB
TypeScript
import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron';
|
||
|
||
test('guides an empty channel workspace from the sidebar back to My Agents', async ({
|
||
launchElectronApp,
|
||
}, testInfo) => {
|
||
const app = await launchElectronApp({ skipSetup: true });
|
||
try {
|
||
const page = await getStableWindow(app);
|
||
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
|
||
const applicationUrl = page.url();
|
||
await page.goto('about:blank');
|
||
await app.evaluate(({ ipcMain, BrowserWindow }) => {
|
||
BrowserWindow.getAllWindows()[0].setSize(1366, 900);
|
||
const calls: { method: string; path: string; operation?: string }[] = [];
|
||
Object.assign(globalThis, { cloudChannelEmptyFixtureCalls: calls });
|
||
const result = (json: unknown) => ({ ok: true, data: { status: 200, ok: true, json } });
|
||
ipcMain.removeHandler('hostapi:fetch');
|
||
ipcMain.handle(
|
||
'hostapi:fetch',
|
||
async (_event, request: { path?: string; method?: string; body?: string }) => {
|
||
const path = request.path ?? '';
|
||
if (path === '/api/auth/session/sync')
|
||
return result({
|
||
success: true,
|
||
session: {
|
||
accessToken: 'ui-fixture',
|
||
tokenType: 'Bearer',
|
||
expiresAt: Date.now() + 3600000,
|
||
lastActiveAt: Date.now(),
|
||
canRefresh: false,
|
||
},
|
||
});
|
||
if (path === '/api/auth/me')
|
||
return result({
|
||
success: true,
|
||
user: {
|
||
username: 'creator',
|
||
userId: 'creator',
|
||
tenantId: null,
|
||
deptId: null,
|
||
authorities: [],
|
||
},
|
||
moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true },
|
||
});
|
||
if (path === '/api/works/user/agent-profile')
|
||
return result({
|
||
success: true,
|
||
profile: {
|
||
display_name: '创作者',
|
||
age: null,
|
||
gender: null,
|
||
avatar_url: null,
|
||
share_age_with_agents: false,
|
||
share_gender_with_agents: false,
|
||
analysis_enabled: true,
|
||
completed: true,
|
||
version: 1,
|
||
updated_at: '2026-10-13T00:00:00Z',
|
||
},
|
||
});
|
||
if (!path.startsWith('/api/cloud-agents/')) return result({ success: true });
|
||
const body =
|
||
typeof request.body === 'string' ? JSON.parse(request.body) : (request.body ?? {});
|
||
calls.push({ method: request.method ?? 'GET', path, operation: body.operation });
|
||
if (path.endsWith('/recovery')) return result({ recent: null, pending: [] });
|
||
if (path.endsWith('/agents') || path.endsWith('/bootstrap'))
|
||
return result({ agents: [], next_cursor: null });
|
||
if (path.endsWith('/actions') && body.operation === 'channelAccounts')
|
||
return result({ items: [] });
|
||
throw new Error('Unexpected empty channel fixture request ' + path);
|
||
}
|
||
);
|
||
});
|
||
await page.goto(applicationUrl);
|
||
await page.getByTestId('ai-module-option-cloud_agents').click();
|
||
const sidebar = page.getByTestId('sidebar-cloud-agents-navigation');
|
||
await sidebar.getByRole('button', { name: '渠道', exact: true }).click();
|
||
await expect(page).toHaveURL(/\/cloud-agents\/channels$/);
|
||
const hint = page.getByRole('heading', { name: '还没有可连接的智能体', exact: true });
|
||
await expect(hint).toBeVisible();
|
||
await expect(
|
||
page.getByText('先创建一个智能体,配置完成后点击“开始使用”,就能连接到微信。', { exact: true })
|
||
).toBeVisible();
|
||
await expect(page.getByRole('button', { name: '添加第一个微信', exact: true })).toHaveCount(0);
|
||
await expect(page.getByRole('button', { name: '添加微信', exact: true })).toBeEnabled();
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-no-agents-desktop.png'), fullPage: true });
|
||
|
||
await page.getByRole('button', { name: '添加微信', exact: true }).click();
|
||
const dialog = page.getByRole('dialog', { name: '添加微信', exact: true });
|
||
await expect(
|
||
dialog.getByRole('heading', { name: '还没有可连接的智能体', exact: true })
|
||
).toBeVisible();
|
||
await expect(dialog.getByRole('button', { name: '下一步:扫码连接', exact: true })).toBeDisabled();
|
||
await dialog.getByRole('button', { name: '关闭', exact: true }).click();
|
||
|
||
await app.evaluate(({ BrowserWindow }) => {
|
||
const window = BrowserWindow.getAllWindows()[0];
|
||
window.setMinimumSize(800, 600);
|
||
window.setSize(980, 680);
|
||
});
|
||
await expect(hint).toBeVisible();
|
||
await expect
|
||
.poll(() =>
|
||
page.getByTestId('main-content').evaluate((element) => element.scrollWidth - element.clientWidth)
|
||
)
|
||
.toBeLessThanOrEqual(1);
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-no-agents-compact.png'), fullPage: true });
|
||
await page.getByRole('button', { name: '去我的智能体', exact: true }).click();
|
||
await expect(page).toHaveURL(/\/cloud-agents\?view=mine$/);
|
||
await expect(page.getByTestId('cloud-agents-page')).toBeVisible();
|
||
await expect(
|
||
page.getByRole('heading', { name: '做一个属于你的 AI 助手吧', exact: true })
|
||
).toBeVisible();
|
||
await expect(sidebar.getByRole('button', { name: '我的智能体', exact: true })).toHaveAttribute(
|
||
'aria-current',
|
||
'page'
|
||
);
|
||
const calls = await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & {
|
||
cloudChannelEmptyFixtureCalls: { method: string; operation?: string }[];
|
||
}
|
||
).cloudChannelEmptyFixtureCalls
|
||
);
|
||
expect(calls.filter((call) => call.operation).map((call) => call.operation)).toEqual([
|
||
'channelAccounts',
|
||
]);
|
||
expect(calls.every((call) => call.method === 'GET' || call.operation === 'channelAccounts')).toBe(
|
||
true
|
||
);
|
||
} finally {
|
||
await app
|
||
.evaluate(({ BrowserWindow }) => {
|
||
for (const window of BrowserWindow.getAllWindows()) window.destroy();
|
||
})
|
||
.catch(() => undefined);
|
||
await closeElectronApp(app);
|
||
}
|
||
});
|
||
|
||
test('WeChat binding cards connect an account and switch its Agent through the channel workspace', async ({
|
||
launchElectronApp,
|
||
}, testInfo) => {
|
||
const app = await launchElectronApp({ skipSetup: true });
|
||
const agentSlug = 'ml-' + 'a'.repeat(32);
|
||
const secondAgentSlug = 'ml-' + 'b'.repeat(32);
|
||
try {
|
||
const page = await getStableWindow(app);
|
||
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
|
||
const applicationUrl = page.url();
|
||
await page.goto('about:blank');
|
||
await app.evaluate(({ ipcMain, BrowserWindow }) => {
|
||
BrowserWindow.getAllWindows()[0].setSize(1366, 900);
|
||
const calls: { operation: string; input: unknown }[] = [];
|
||
Object.assign(globalThis, { cloudChannelFixtureCalls: calls });
|
||
let verified = false;
|
||
let qrSession = 'qr-session';
|
||
let qrStarts = 0;
|
||
let rejectUnbind = true;
|
||
const controls = {
|
||
serverPending: false,
|
||
pendingOnNextRead: false,
|
||
disabledTarget: false,
|
||
legacyPending: false,
|
||
workerOffline: true,
|
||
reconnectCreatedOnNextRead: false,
|
||
};
|
||
Object.assign(globalThis, { cloudChannelFixtureControls: controls });
|
||
const slug = 'ml-' + 'a'.repeat(32);
|
||
const configuration = {
|
||
model: 'test-model',
|
||
tools: [],
|
||
knowledges: [],
|
||
mcps: [],
|
||
skills: [],
|
||
preload_skills: [],
|
||
subagents: [],
|
||
tool_approval_mode: 'default',
|
||
max_execution_steps: 40,
|
||
max_output_tokens: 4096,
|
||
max_run_seconds: 600,
|
||
};
|
||
const draft = {
|
||
slug,
|
||
name: '我的创作助手',
|
||
purpose: '整理选题与制作演示文稿',
|
||
system_prompt: '使用简洁自然的中文',
|
||
draft_revision: 2,
|
||
updated_at: '2026-09-13T00:00:00Z',
|
||
configuration,
|
||
published_version: 1,
|
||
enabled: true,
|
||
archived: false,
|
||
};
|
||
const secondSlug = 'ml-' + 'b'.repeat(32);
|
||
const secondDraft = {
|
||
...draft,
|
||
slug: secondSlug,
|
||
name: '资料整理助手',
|
||
draft_revision: 1,
|
||
published_version: 1,
|
||
};
|
||
const channel = {
|
||
id: 'wechat-a',
|
||
address: 'wechat:personal',
|
||
display_name: '创作微信',
|
||
target_agent_address: 'yuxi:makelore:' + slug,
|
||
route_revision: 3,
|
||
provider_generation: 'generation-a',
|
||
binding_id: 'binding-a',
|
||
enabled: false,
|
||
status: 'paused',
|
||
worker_online: true,
|
||
agent_slug: slug,
|
||
published_version: 1,
|
||
desired_state: 'paused',
|
||
sync_state: 'ready',
|
||
health: 'connected',
|
||
last_confirmed_at: '2026-09-13T01:00:00Z',
|
||
revision: 3,
|
||
blockers: [],
|
||
access_mode: 'self_only',
|
||
policy_revision: 0,
|
||
target_agent_slug: slug,
|
||
target_agent_name: draft.name,
|
||
target_agent_published_version: 1,
|
||
};
|
||
const spare = {
|
||
id: 'wechat-b',
|
||
address: 'wechat:spare',
|
||
display_name: '备用微信',
|
||
target_agent_address: null,
|
||
route_revision: 1,
|
||
provider_generation: 'generation-b',
|
||
binding_id: 'binding-b',
|
||
enabled: false,
|
||
status: 'paused',
|
||
worker_online: true,
|
||
agent_slug: null,
|
||
published_version: null,
|
||
desired_state: 'disabled',
|
||
sync_state: 'ready',
|
||
health: 'login_required',
|
||
last_confirmed_at: null,
|
||
revision: 1,
|
||
blockers: ['target_required'],
|
||
access_mode: 'self_only',
|
||
policy_revision: 0,
|
||
target_agent_slug: null,
|
||
target_agent_name: null,
|
||
target_agent_published_version: null,
|
||
};
|
||
let channelAccounts = [channel, spare];
|
||
const result = (json: unknown) => ({ ok: true, data: { status: 200, ok: true, json } });
|
||
ipcMain.removeHandler('hostapi:fetch');
|
||
ipcMain.handle(
|
||
'hostapi:fetch',
|
||
async (_event, request: { path?: string; method?: string; body?: string }) => {
|
||
const path = request.path ?? '';
|
||
if (path === '/api/auth/session/sync')
|
||
return result({
|
||
success: true,
|
||
session: {
|
||
accessToken: 'ui-fixture',
|
||
tokenType: 'Bearer',
|
||
expiresAt: Date.now() + 3600000,
|
||
lastActiveAt: Date.now(),
|
||
canRefresh: false,
|
||
},
|
||
});
|
||
if (path === '/api/auth/me')
|
||
return result({
|
||
success: true,
|
||
user: {
|
||
username: 'creator',
|
||
userId: 'creator',
|
||
tenantId: null,
|
||
deptId: null,
|
||
authorities: [],
|
||
},
|
||
moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true },
|
||
});
|
||
if (path === '/api/works/user/agent-profile')
|
||
return result({
|
||
success: true,
|
||
profile: {
|
||
display_name: '创作者',
|
||
age: null,
|
||
gender: null,
|
||
avatar_url: null,
|
||
share_age_with_agents: false,
|
||
share_gender_with_agents: false,
|
||
analysis_enabled: true,
|
||
completed: true,
|
||
version: 1,
|
||
updated_at: '2026-09-13T00:00:00Z',
|
||
},
|
||
});
|
||
if (!path.startsWith('/api/cloud-agents/')) return result({ success: true });
|
||
const body =
|
||
typeof request.body === 'string' ? JSON.parse(request.body) : (request.body ?? {});
|
||
if (path.endsWith('/recovery'))
|
||
return result({
|
||
recent: null,
|
||
pending: controls.legacyPending
|
||
? [
|
||
{
|
||
id: 'fixture-legacy-unbind',
|
||
operation: 'unbindChannelAccount',
|
||
input: {
|
||
channel_account_id: 'wechat-b',
|
||
operation_id: 'legacy-original-id',
|
||
expected_revision: 1,
|
||
},
|
||
created_at: '2026-10-13T00:00:00Z',
|
||
},
|
||
]
|
||
: [],
|
||
});
|
||
if (path.endsWith('/recovery/pending')) {
|
||
calls.push({ operation: 'resolvePending', input: body });
|
||
if (body.discard) {
|
||
controls.legacyPending = false;
|
||
return result({ discarded: true });
|
||
}
|
||
return result({
|
||
operation: 'unbindChannelAccount',
|
||
pending: true,
|
||
result: { error_code: 'channel_operation_legacy' },
|
||
});
|
||
}
|
||
if (path.endsWith('/recovery/recent')) return result({ saved: true });
|
||
if (!path.endsWith('/actions'))
|
||
return result(
|
||
path.endsWith('/agents') || path.endsWith('/bootstrap')
|
||
? {
|
||
agents: [
|
||
controls.disabledTarget ? { ...draft, enabled: false } : draft,
|
||
secondDraft,
|
||
],
|
||
next_cursor: null,
|
||
}
|
||
: path.endsWith(secondSlug)
|
||
? secondDraft
|
||
: draft
|
||
);
|
||
const { operation, input = {} } = body;
|
||
calls.push({ operation, input });
|
||
if (operation === 'threads') return result({ threads: [], next_offset: null });
|
||
if (operation === 'history')
|
||
return result({ thread_id: 'desktop-thread', messages: [], run: null, next_offset: null });
|
||
if (operation === 'attachments' || operation === 'files')
|
||
return result({ attachments: [], files: [] });
|
||
if (operation === 'catalog')
|
||
return result({
|
||
models: [{ id: 'test-model', name: '创作模型' }],
|
||
resources: {},
|
||
pricing: null,
|
||
});
|
||
if (operation === 'access')
|
||
return result({
|
||
published_version: 1,
|
||
enabled: true,
|
||
versions: [],
|
||
grants: [],
|
||
applications: [],
|
||
share_url: 'niancode://agents/' + slug,
|
||
});
|
||
if (operation === 'budget')
|
||
return result({
|
||
request_limit_points: '2.50',
|
||
daily_limit_points: '10.00',
|
||
daily_committed_points: '0.20',
|
||
});
|
||
if (operation === 'costs')
|
||
return result({
|
||
items: [],
|
||
next_offset: null,
|
||
summary: { count: 0, settled_points: '0', pending_points: '0' },
|
||
});
|
||
if (operation === 'channelAccounts') {
|
||
if (controls.reconnectCreatedOnNextRead) {
|
||
controls.reconnectCreatedOnNextRead = false;
|
||
channelAccounts = channelAccounts.map((item) => item.id === 'wechat-created'
|
||
? { ...item, health: 'login_required', enabled: false, desired_state: 'paused', blockers: ['binding_paused'] }
|
||
: item);
|
||
}
|
||
if (controls.pendingOnNextRead) {
|
||
controls.serverPending = true;
|
||
controls.pendingOnNextRead = false;
|
||
}
|
||
return result({
|
||
items: channelAccounts.map((item) => {
|
||
if (controls.serverPending && item.id === 'wechat-b')
|
||
return {
|
||
...item,
|
||
health: 'unknown',
|
||
sync_state: 'degraded',
|
||
blockers: ['wechat_disconnect_pending'],
|
||
};
|
||
if (controls.disabledTarget && item.id === 'wechat-created')
|
||
return {
|
||
...item,
|
||
enabled: false,
|
||
desired_state: 'paused',
|
||
blockers: ['binding_paused'],
|
||
};
|
||
if (
|
||
controls.workerOffline &&
|
||
['wechat-a', 'wechat-created'].includes(item.id) &&
|
||
item.health === 'connected'
|
||
)
|
||
return { ...item, worker_online: false, blockers: ['worker_offline'] };
|
||
return item;
|
||
}),
|
||
});
|
||
}
|
||
if (operation === 'createChannelAccount') {
|
||
const created = {
|
||
...spare,
|
||
id: 'wechat-created',
|
||
display_name: input.display_name,
|
||
binding_id: 'binding-created',
|
||
};
|
||
channelAccounts.push(created);
|
||
return result({ channel: created });
|
||
}
|
||
if (operation === 'routeChannelAccount') {
|
||
const stale = { ...channelAccounts.find((item) => item.id === input.channel_account_id) };
|
||
const target =
|
||
input.target_agent_slug === secondSlug
|
||
? secondDraft
|
||
: input.target_agent_slug === slug
|
||
? draft
|
||
: null;
|
||
channelAccounts = channelAccounts.map((item) =>
|
||
item.id !== input.channel_account_id
|
||
? item
|
||
: {
|
||
...item,
|
||
revision: item.revision + 1,
|
||
enabled: input.enabled,
|
||
desired_state: input.enabled ? 'enabled' : 'disabled',
|
||
target_agent_slug: target?.slug ?? null,
|
||
target_agent_name: target?.name ?? null,
|
||
target_agent_published_version: target?.published_version ?? null,
|
||
agent_slug: target?.slug ?? null,
|
||
published_version: target?.published_version ?? null,
|
||
target_agent_address: target ? 'yuxi:makelore:' + target.slug : null,
|
||
blockers: target ? [] : ['target_required'],
|
||
}
|
||
);
|
||
return result({ channel: stale, target_agent: target?.slug ?? null });
|
||
}
|
||
if (operation === 'enableChannelAccount') {
|
||
const enabled = true;
|
||
channelAccounts = channelAccounts.map((item) =>
|
||
item.id !== input.channel_account_id
|
||
? item
|
||
: {
|
||
...item,
|
||
revision: item.revision + 1,
|
||
enabled,
|
||
desired_state: 'enabled',
|
||
blockers: [],
|
||
}
|
||
);
|
||
return result({
|
||
channel: channelAccounts.find((item) => item.id === input.channel_account_id),
|
||
});
|
||
}
|
||
if (operation === 'unbindChannelAccount') {
|
||
if (rejectUnbind) {
|
||
rejectUnbind = false;
|
||
return {
|
||
ok: true,
|
||
data: {
|
||
status: 409,
|
||
ok: false,
|
||
json: {
|
||
error: '解除暂时失败,请重试',
|
||
code: 'channel_operation_conflict',
|
||
commandOutcome: 'definitive_failure',
|
||
},
|
||
},
|
||
};
|
||
}
|
||
channelAccounts = channelAccounts.map((item) =>
|
||
item.id === input.channel_account_id
|
||
? {
|
||
...item,
|
||
health: 'login_required',
|
||
enabled: false,
|
||
desired_state: 'paused',
|
||
sync_state: 'ready',
|
||
target_agent_slug: null,
|
||
target_agent_name: null,
|
||
target_agent_address: '',
|
||
target_agent_published_version: null,
|
||
agent_slug: null,
|
||
published_version: null,
|
||
provider_generation: 'logged-out-generation',
|
||
revision: item.revision + 1,
|
||
}
|
||
: item
|
||
);
|
||
return result({
|
||
channel: channelAccounts.find((item) => item.id === input.channel_account_id),
|
||
disconnected: true,
|
||
unbound: true,
|
||
});
|
||
}
|
||
if (operation === 'channelBindings')
|
||
return result({
|
||
items: channelAccounts.filter((item) => item.target_agent_slug === input.slug),
|
||
available_accounts: [],
|
||
});
|
||
if (
|
||
operation === 'channelAccountWechatBindStart' ||
|
||
operation === 'channelAccountWechatBindStatus'
|
||
) {
|
||
if (operation === 'channelAccountWechatBindStart' && ++qrStarts > 1)
|
||
qrSession = 'qr-session-' + qrStarts;
|
||
if (verified) channelAccounts = channelAccounts.map((item) =>
|
||
item.id === input.channel_account_id ? { ...item, health: 'connected' } : item
|
||
);
|
||
return result({
|
||
session_key: qrSession,
|
||
status: verified ? 'confirmed' : 'verification_required',
|
||
qrcode_url: 'https://login.example.test/confirm?token=fixture',
|
||
message: '请确认微信验证码',
|
||
});
|
||
}
|
||
if (operation === 'channelAccountWechatBindVerification') {
|
||
verified = true;
|
||
channelAccounts = channelAccounts.map((item) =>
|
||
item.id === input.channel_account_id ? { ...item, health: 'connected' } : item
|
||
);
|
||
return result({ session_key: qrSession, status: 'confirmed', message: '连接已确认' });
|
||
}
|
||
if (operation === 'schedules') return result({ jobs: [] });
|
||
if (operation === 'scheduleContext')
|
||
return result({
|
||
version: 1,
|
||
enabled: true,
|
||
configuration,
|
||
result_destination: '任务历史',
|
||
payer: 'creator',
|
||
});
|
||
throw new Error('Unexpected channel UI fixture operation ' + operation);
|
||
}
|
||
);
|
||
});
|
||
await page.goto(applicationUrl);
|
||
await page.getByTestId('ai-module-option-cloud_agents').click();
|
||
await page
|
||
.getByRole('article', { name: '我的创作助手', exact: true })
|
||
.getByRole('button', { name: '和它聊聊', exact: true })
|
||
.click();
|
||
await expect(page.getByRole('region', { name: '智能体对话', exact: true })).toBeVisible();
|
||
await expect(page.getByRole('heading', { name: '我的创作助手', exact: true })).toBeVisible();
|
||
await expect(page.getByRole('button', { name: '微信对话', exact: true })).toHaveCount(0);
|
||
await expect(page.getByRole('navigation', { name: '对话来源', exact: true })).toHaveCount(0);
|
||
await expect(page.getByRole('region', { name: '微信会话', exact: true })).toHaveCount(0);
|
||
await page.getByRole('button', { name: '返回智能体列表', exact: true }).click();
|
||
await page.getByRole('button', { name: '渠道', exact: true }).click();
|
||
await expect(page.getByRole('heading', { name: '连接到微信', exact: true })).toBeVisible();
|
||
const legacyCard = page.getByRole('article', { name: '创作微信微信', exact: true });
|
||
const spareCard = page.getByRole('article', { name: '备用微信微信', exact: true });
|
||
await expect(legacyCard.getByText('我的创作助手', { exact: true })).toBeVisible();
|
||
await expect(legacyCard.getByText('微信服务离线', { exact: true })).toBeVisible();
|
||
await expect(
|
||
legacyCard.getByText(
|
||
'智能体的微信服务暂时离线,暂时无法自动回复。微信登录和智能体选择已保留,服务恢复后请刷新状态。',
|
||
{ exact: true }
|
||
)
|
||
).toHaveCount(0);
|
||
await expect(legacyCard.getByText('状态待确认', { exact: true })).toHaveCount(0);
|
||
await expect(spareCard.getByRole('button', { name: '绑定智能体', exact: true })).toBeVisible();
|
||
const callsBeforeRemarks = await app.evaluate(() => (globalThis as typeof globalThis & {
|
||
cloudChannelFixtureCalls: { operation: string }[];
|
||
}).cloudChannelFixtureCalls.filter((item) => item.operation !== 'channelAccounts'));
|
||
await spareCard.getByRole('button', { name: '修改微信卡片名称:备用微信', exact: true }).click();
|
||
const cardNameField = page.getByRole('textbox', { name: '微信卡片名称', exact: true });
|
||
await cardNameField.fill('未保存备注');
|
||
await cardNameField.press('Escape');
|
||
await expect(cardNameField).toHaveCount(0);
|
||
await spareCard.getByRole('button', { name: '修改微信卡片名称:备用微信', exact: true }).click();
|
||
await cardNameField.fill('A'.repeat(64));
|
||
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].setSize(980, 700));
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-card-name-edit-compact.png'), fullPage: true });
|
||
await cardNameField.press('Enter');
|
||
const remarkedCard = page.getByRole('article', { name: 'A'.repeat(64) + '微信', exact: true });
|
||
await expect(remarkedCard).toBeVisible();
|
||
expect(await remarkedCard.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true);
|
||
await page.reload();
|
||
await expect(remarkedCard).toBeVisible();
|
||
await expect(remarkedCard.getByRole('button', { name: '绑定智能体', exact: true })).toBeVisible();
|
||
await remarkedCard.getByRole('button', { name: '修改微信卡片名称:' + 'A'.repeat(64), exact: true }).click();
|
||
await cardNameField.fill('备用微信');
|
||
await cardNameField.press('Enter');
|
||
await expect(spareCard).toBeVisible();
|
||
const callsAfterRemarks = await app.evaluate(() => (globalThis as typeof globalThis & {
|
||
cloudChannelFixtureCalls: { operation: string }[];
|
||
}).cloudChannelFixtureCalls.filter((item) => item.operation !== 'channelAccounts'));
|
||
expect(callsAfterRemarks).toEqual(callsBeforeRemarks);
|
||
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].setSize(1366, 900));
|
||
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||
await expect(page.getByText('活动记录', { exact: true })).toHaveCount(0);
|
||
await expect(page.getByRole('button', { name: '微信对话', exact: true })).toHaveCount(0);
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-binding-cards.png'), fullPage: true });
|
||
|
||
await page.getByRole('button', { name: '添加微信', exact: true }).click();
|
||
const adding = page.getByRole('dialog', { name: '添加微信', exact: true });
|
||
await adding
|
||
.getByRole('radiogroup', { name: '选择智能体' })
|
||
.getByText('我的创作助手', { exact: true })
|
||
.click();
|
||
await expect(adding.getByRole('radio', { name: '我的创作助手', exact: true })).toBeChecked();
|
||
await adding.getByRole('button', { name: '下一步:扫码连接', exact: true }).click();
|
||
await expect(adding.getByRole('img', { name: '个人微信登录二维码', exact: true })).toHaveAttribute(
|
||
'src',
|
||
/^data:image\//
|
||
);
|
||
await expect
|
||
.poll(async () =>
|
||
app.evaluate(() =>
|
||
(
|
||
globalThis as typeof globalThis & {
|
||
cloudChannelFixtureCalls: { operation: string; input: Record<string, unknown> }[];
|
||
}
|
||
).cloudChannelFixtureCalls
|
||
.filter((call) => call.operation === 'routeChannelAccount')
|
||
.some(
|
||
(call) =>
|
||
call.input.channel_account_id === 'wechat-created' &&
|
||
call.input.target_agent_slug === 'ml-' + 'a'.repeat(32) &&
|
||
call.input.enabled === false
|
||
)
|
||
)
|
||
)
|
||
.toBe(true);
|
||
await expect(adding.getByRole('button', { name: '完成绑定并启用', exact: true })).toHaveCount(0);
|
||
await adding.getByLabel('微信验证码', { exact: true }).fill('123456');
|
||
await adding.getByRole('button', { name: '提交验证码', exact: true }).click();
|
||
await expect(adding.getByRole('img', { name: '个人微信登录二维码', exact: true })).toHaveCount(0);
|
||
await expect(adding.getByLabel('微信验证码', { exact: true })).toHaveCount(0);
|
||
await expect(adding.getByText('微信登录已确认', { exact: true })).toBeVisible();
|
||
await expect(adding.getByRole('alert')).toHaveText(
|
||
'智能体的微信服务暂时离线,暂时无法自动回复。微信登录和智能体选择已保留,服务恢复后请刷新状态。'
|
||
);
|
||
await expect(adding.getByRole('heading', { name: '微信绑定成功', exact: true })).toHaveCount(0);
|
||
await expect(adding.getByRole('button', { name: '检查连接并完成绑定', exact: true })).toBeEnabled();
|
||
// The open modal hides background cards from the accessibility tree.
|
||
await expect(
|
||
page
|
||
.getByRole('article', { name: '微信 3微信', exact: true, includeHidden: true })
|
||
.getByText('我的创作助手', { exact: true })
|
||
).toBeVisible();
|
||
expect(
|
||
await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation === 'enableChannelAccount').length
|
||
)
|
||
).toBe(0);
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-confirmed-login-service-offline.png'),
|
||
fullPage: true,
|
||
});
|
||
const qrStartsBeforeRecovery = await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation === 'channelAccountWechatBindStart')
|
||
.length
|
||
);
|
||
await app.evaluate(() => {
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureControls: { workerOffline: boolean } }
|
||
).cloudChannelFixtureControls.workerOffline = false;
|
||
});
|
||
await adding.getByRole('button', { name: '检查连接并完成绑定', exact: true }).click();
|
||
await expect(adding.getByRole('heading', { name: '微信绑定成功', exact: true })).toBeVisible();
|
||
expect(
|
||
await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation === 'enableChannelAccount').length
|
||
)
|
||
).toBe(1);
|
||
expect(
|
||
await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation === 'channelAccountWechatBindStart')
|
||
.length
|
||
)
|
||
).toBe(qrStartsBeforeRecovery);
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-automatically-connected.png'),
|
||
fullPage: true,
|
||
});
|
||
await expect(adding.getByRole('button', { name: '完成', exact: true })).toBeVisible();
|
||
await adding.getByRole('button', { name: '完成', exact: true }).click();
|
||
await expect(
|
||
page
|
||
.getByRole('article', { name: '微信 3微信', exact: true })
|
||
.getByText('我的创作助手', { exact: true })
|
||
).toBeVisible();
|
||
|
||
await expect(legacyCard.getByText('使用中', { exact: true })).toBeVisible();
|
||
await expect(legacyCard.getByRole('button', { name: '更换智能体', exact: true })).toBeEnabled();
|
||
expect(await app.evaluate(() =>
|
||
(globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string; input: Record<string, unknown> }[] })
|
||
.cloudChannelFixtureCalls.filter((call) => call.operation === 'enableChannelAccount' && call.input.channel_account_id === 'wechat-a')
|
||
.map((call) => call.input)
|
||
)).toEqual([{ channel_account_id: 'wechat-a', expected_revision: 3, operation_id: expect.any(String) }]);
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-legacy-replies-restored.png'), fullPage: true });
|
||
await legacyCard.getByRole('button', { name: '更换智能体', exact: true }).click();
|
||
const managing = page.getByRole('dialog', { name: '更换智能体', exact: true });
|
||
const qrStartsBeforeSwitch = await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation === 'channelAccountWechatBindStart')
|
||
.length
|
||
);
|
||
await managing
|
||
.getByRole('radiogroup', { name: '选择智能体' })
|
||
.getByText('资料整理助手', { exact: true })
|
||
.click();
|
||
await expect(managing.getByRole('radio', { name: '资料整理助手', exact: true })).toBeChecked();
|
||
await expect(page.getByRole('dialog')).toHaveCount(1);
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-agent-choice.png'), fullPage: true });
|
||
await managing.getByRole('button', { name: '确认更换', exact: true }).click();
|
||
await expect
|
||
.poll(async () =>
|
||
app.evaluate(() =>
|
||
(
|
||
globalThis as typeof globalThis & {
|
||
cloudChannelFixtureCalls: { operation: string; input: Record<string, unknown> }[];
|
||
}
|
||
).cloudChannelFixtureCalls
|
||
.filter((call) => call.operation === 'routeChannelAccount')
|
||
.some(
|
||
(call) =>
|
||
call.input.channel_account_id === 'wechat-a' &&
|
||
call.input.target_agent_slug === 'ml-' + 'b'.repeat(32) &&
|
||
call.input.enabled === true
|
||
)
|
||
)
|
||
)
|
||
.toBe(true);
|
||
await expect(managing).toHaveCount(0);
|
||
await expect(page.getByText('已更换智能体,微信连接和历史保留。', { exact: true })).toBeVisible();
|
||
expect(
|
||
await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation === 'channelAccountWechatBindStart')
|
||
.length
|
||
)
|
||
).toBe(qrStartsBeforeSwitch);
|
||
await expect(page.getByText('活动记录', { exact: true })).toHaveCount(0);
|
||
await expect(page.getByRole('button', { name: '微信对话', exact: true })).toHaveCount(0);
|
||
await expect(page.getByText(/本人|受邀|邀请|使用范围|使用权限|授权调用者/)).toHaveCount(0);
|
||
await app.evaluate(({ BrowserWindow }) => {
|
||
const win = BrowserWindow.getAllWindows()[0];
|
||
win.setMinimumSize(800, 600);
|
||
win.setSize(980, 680);
|
||
});
|
||
await expect
|
||
.poll(() =>
|
||
page.getByTestId('main-content').evaluate((element) => element.scrollWidth - element.clientWidth)
|
||
)
|
||
.toBeLessThanOrEqual(1);
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-binding-cards-compact.png'),
|
||
fullPage: true,
|
||
});
|
||
|
||
await expect(legacyCard.getByText('资料整理助手', { exact: true })).toBeVisible();
|
||
const finalCalls = await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & {
|
||
cloudChannelFixtureCalls: { operation: string; input: Record<string, unknown> }[];
|
||
}
|
||
).cloudChannelFixtureCalls
|
||
);
|
||
expect(
|
||
finalCalls.some((call) =>
|
||
[
|
||
'channelActivity',
|
||
'channelConversations',
|
||
'channelConversation',
|
||
'channelConversationControl',
|
||
'channelFiles',
|
||
'downloadChannelArtifact',
|
||
'channelPolicy',
|
||
'createChannelPairing',
|
||
'channelCallers',
|
||
'channelSelfCallers',
|
||
'revokeChannelCaller',
|
||
'submit',
|
||
'preview',
|
||
'resume',
|
||
].includes(call.operation)
|
||
)
|
||
).toBe(false);
|
||
expect(finalCalls.filter((call) => call.operation === 'createChannelAccount')).toHaveLength(1);
|
||
expect(finalCalls.find((call) => call.operation === 'createChannelAccount')?.input).toEqual({
|
||
display_name: '微信 3',
|
||
operation_id: expect.any(String),
|
||
});
|
||
expect(
|
||
finalCalls.find((call) => call.operation === 'channelAccountWechatBindVerification')?.input
|
||
).toEqual({
|
||
channel_account_id: 'wechat-created',
|
||
session_key: 'qr-session',
|
||
verify_code: '123456',
|
||
operation_id: expect.any(String),
|
||
});
|
||
expect(finalCalls.find((call) => call.operation === 'enableChannelAccount')?.input).toEqual({
|
||
channel_account_id: 'wechat-created',
|
||
expected_revision: 2,
|
||
operation_id: expect.any(String),
|
||
});
|
||
expect(
|
||
finalCalls.filter((call) => call.operation === 'routeChannelAccount').map((call) => call.input)
|
||
).toEqual([
|
||
{
|
||
channel_account_id: 'wechat-created',
|
||
target_agent_slug: agentSlug,
|
||
expected_revision: 1,
|
||
enabled: false,
|
||
operation_id: expect.any(String),
|
||
},
|
||
{
|
||
channel_account_id: 'wechat-a',
|
||
target_agent_slug: secondAgentSlug,
|
||
expected_revision: 4,
|
||
enabled: true,
|
||
operation_id: expect.any(String),
|
||
},
|
||
]);
|
||
await app.evaluate(() => {
|
||
(globalThis as typeof globalThis & { cloudChannelFixtureControls: { reconnectCreatedOnNextRead: boolean } })
|
||
.cloudChannelFixtureControls.reconnectCreatedOnNextRead = true;
|
||
});
|
||
await page.getByRole('button', { name: '刷新微信列表', exact: true }).click();
|
||
const reconnectCard = page.getByRole('article', { name: '微信 3微信', exact: true });
|
||
await expect(reconnectCard.getByRole('button', { name: '重新扫码', exact: true })).toBeEnabled();
|
||
await reconnectCard.getByRole('button', { name: '重新扫码', exact: true }).click();
|
||
const reconnecting = page.getByRole('dialog', { name: '重新连接微信', exact: true });
|
||
await expect(reconnecting.getByRole('heading', { name: '微信重新连接成功', exact: true })).toBeVisible();
|
||
await expect(reconnecting.getByText('现在可以回微信和我的创作助手聊聊。', { exact: true })).toBeVisible();
|
||
expect(await app.evaluate(() =>
|
||
(globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string; input: Record<string, unknown> }[] })
|
||
.cloudChannelFixtureCalls.filter((call) => call.operation === 'enableChannelAccount' && call.input.channel_account_id === 'wechat-created').length
|
||
)).toBe(2);
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-reconnect-replies-enabled.png'), fullPage: true });
|
||
await reconnecting.getByRole('button', { name: '完成', exact: true }).click();
|
||
await legacyCard.getByRole('button', { name: '更多操作', exact: true }).click();
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-binding-more-compact.png'),
|
||
fullPage: true,
|
||
});
|
||
await expect(legacyCard.getByRole('button', { name: '暂停自动回复', exact: true })).toHaveCount(0);
|
||
await expect(legacyCard.getByRole('button', { name: '恢复自动回复', exact: true })).toHaveCount(0);
|
||
await expect(legacyCard.getByRole('button', { name: '解除微信绑定', exact: true })).toBeVisible();
|
||
await legacyCard.getByRole('button', { name: '解除微信绑定', exact: true }).click();
|
||
const unbinding = page.getByRole('dialog', { name: '解除微信绑定', exact: true });
|
||
await unbinding.getByRole('button', { name: '确认解除微信绑定', exact: true }).click();
|
||
await expect(unbinding.getByRole('alert')).toHaveText(/解除暂时失败/);
|
||
await expect(page.getByRole('dialog')).toHaveCount(1);
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-unbind-visible-failure.png'),
|
||
fullPage: true,
|
||
});
|
||
await unbinding.getByRole('button', { name: '确认解除微信绑定', exact: true }).click();
|
||
await expect(unbinding).toHaveCount(0);
|
||
await expect(legacyCard.getByText('未绑定', { exact: true })).toBeVisible();
|
||
await expect(legacyCard.getByRole('button', { name: '绑定智能体', exact: true })).toBeVisible();
|
||
await expect(page.getByText('断开微信连接', { exact: true })).toHaveCount(0);
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-unbound-card-compact.png'),
|
||
fullPage: true,
|
||
});
|
||
const unbindCalls = await app.evaluate(() =>
|
||
(
|
||
globalThis as typeof globalThis & {
|
||
cloudChannelFixtureCalls: { operation: string; input: Record<string, unknown> }[];
|
||
}
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation === 'unbindChannelAccount')
|
||
);
|
||
expect(unbindCalls).toHaveLength(2);
|
||
expect(unbindCalls[0].input).toEqual(unbindCalls[1].input);
|
||
|
||
await app.evaluate(() => {
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureControls: { serverPending: boolean } }
|
||
).cloudChannelFixtureControls.serverPending = true;
|
||
});
|
||
await page.getByRole('button', { name: '刷新微信列表', exact: true }).click();
|
||
await expect(spareCard.getByText('状态待确认', { exact: true })).toBeVisible();
|
||
await expect(page.getByText(/请回到发起操作的设备确认原来的操作/)).toBeVisible();
|
||
await expect(page.getByRole('button', { name: '添加微信', exact: true })).toBeDisabled();
|
||
await expect(legacyCard.getByRole('button', { name: '绑定智能体', exact: true })).toBeDisabled();
|
||
const createdCard = page.getByRole('article', { name: '微信 3微信', exact: true });
|
||
await expect(createdCard.getByRole('button', { name: '更多操作', exact: true })).toBeDisabled();
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-server-pending-owner-lock.png'),
|
||
fullPage: true,
|
||
});
|
||
|
||
await app.evaluate(() => {
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureControls: { serverPending: boolean } }
|
||
).cloudChannelFixtureControls.serverPending = false;
|
||
});
|
||
await page.getByRole('button', { name: '刷新微信列表', exact: true }).click();
|
||
await expect(legacyCard.getByRole('button', { name: '绑定智能体', exact: true })).toBeEnabled();
|
||
await legacyCard.getByRole('button', { name: '绑定智能体', exact: true }).click();
|
||
const latePending = page.getByRole('dialog', { name: '更换智能体', exact: true });
|
||
await latePending
|
||
.getByRole('radiogroup', { name: '选择智能体' })
|
||
.getByText('资料整理助手', { exact: true })
|
||
.click();
|
||
const managementCallsBefore = await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation !== 'channelAccounts').length
|
||
);
|
||
await app.evaluate(() => {
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureControls: { pendingOnNextRead: boolean } }
|
||
).cloudChannelFixtureControls.pendingOnNextRead = true;
|
||
});
|
||
await latePending.getByRole('button', { name: '下一步:扫码连接', exact: true }).click();
|
||
await expect(latePending.getByRole('alert')).toHaveText(/微信退出还在确认中/);
|
||
await expect(
|
||
latePending.getByRole('button', { name: '下一步:扫码连接', exact: true })
|
||
).toBeDisabled();
|
||
expect(
|
||
await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation !== 'channelAccounts').length
|
||
)
|
||
).toBe(managementCallsBefore);
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-late-pending-dialog.png'),
|
||
fullPage: true,
|
||
});
|
||
await latePending.getByRole('button', { name: '关闭', exact: true }).click();
|
||
|
||
await app.evaluate(() => {
|
||
const control = (
|
||
globalThis as typeof globalThis & {
|
||
cloudChannelFixtureControls: { serverPending: boolean; disabledTarget: boolean };
|
||
}
|
||
).cloudChannelFixtureControls;
|
||
control.serverPending = false;
|
||
control.disabledTarget = true;
|
||
});
|
||
await page.getByRole('button', { name: '刷新微信列表', exact: true }).click();
|
||
await expect(createdCard.getByText('智能体已停用', { exact: true })).toBeVisible();
|
||
await expect(createdCard.getByText('使用中', { exact: true })).toHaveCount(0);
|
||
await createdCard.getByRole('button', { name: '更多操作', exact: true }).click();
|
||
await expect(createdCard.getByRole('button', { name: '恢复自动回复', exact: true })).toHaveCount(0);
|
||
await expect(createdCard.getByRole('button', { name: '暂停自动回复', exact: true })).toHaveCount(0);
|
||
await expect(createdCard.getByRole('button', { name: '解除微信绑定', exact: true })).toBeEnabled();
|
||
await createdCard
|
||
.getByRole('button', { name: '解除微信绑定', exact: true })
|
||
.scrollIntoViewIfNeeded();
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-disabled-target-safe-actions.png'),
|
||
fullPage: true,
|
||
});
|
||
|
||
await app.evaluate(() => {
|
||
const control = (
|
||
globalThis as typeof globalThis & {
|
||
cloudChannelFixtureControls: {
|
||
serverPending: boolean;
|
||
disabledTarget: boolean;
|
||
legacyPending: boolean;
|
||
};
|
||
}
|
||
).cloudChannelFixtureControls;
|
||
control.serverPending = true;
|
||
control.disabledTarget = false;
|
||
control.legacyPending = true;
|
||
});
|
||
await page.getByRole('button', { name: '刷新微信列表', exact: true }).click();
|
||
await page.getByRole('button', { name: '待处理记录(1)', exact: true }).click();
|
||
const recovering = page.getByRole('dialog', { name: '待处理记录', exact: true });
|
||
await recovering.getByRole('button', { name: '再试一次', exact: true }).click();
|
||
const recoveryRecord = recovering.getByRole('article', { name: '解除微信绑定', exact: true });
|
||
await expect(recoveryRecord.getByRole('status')).toHaveText(
|
||
'这次旧微信操作无法安全恢复,请联系管理员核对后再操作。'
|
||
);
|
||
await expect(recoveryRecord.getByRole('button', { name: '再试一次', exact: true })).toBeDisabled();
|
||
await expect(recoveryRecord.getByRole('button', { name: '刷新状态', exact: true })).toHaveCount(0);
|
||
await expect(recovering.getByText('解除微信绑定的结果已确认。', { exact: true })).toHaveCount(0);
|
||
await expect(recovering.getByText(/不会取消微信服务端仍在处理的操作/)).toBeVisible();
|
||
await page.screenshot({
|
||
path: testInfo.outputPath('wechat-legacy-recovery-attention.png'),
|
||
fullPage: true,
|
||
});
|
||
await recoveryRecord.getByRole('button', { name: '不再提醒', exact: true }).click();
|
||
await expect(recoveryRecord).toHaveCount(0);
|
||
await recovering.getByRole('button', { name: '返回列表', exact: true }).click();
|
||
await expect(page.getByRole('button', { name: '添加微信', exact: true })).toBeDisabled();
|
||
await expect(spareCard.getByText('状态待确认', { exact: true })).toBeVisible();
|
||
await expect(page.getByText(/请回到发起操作的设备确认原来的操作/)).toBeVisible();
|
||
expect(
|
||
await app.evaluate(() =>
|
||
(
|
||
globalThis as typeof globalThis & {
|
||
cloudChannelFixtureCalls: { operation: string; input: Record<string, unknown> }[];
|
||
}
|
||
).cloudChannelFixtureCalls
|
||
.filter((call) => call.operation === 'resolvePending')
|
||
.map((call) => call.input)
|
||
)
|
||
).toEqual([
|
||
{ id: 'fixture-legacy-unbind', discard: false },
|
||
{ id: 'fixture-legacy-unbind', discard: true },
|
||
]);
|
||
expect(
|
||
await app.evaluate(
|
||
() =>
|
||
(
|
||
globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] }
|
||
).cloudChannelFixtureCalls.filter((call) => call.operation === 'unbindChannelAccount').length
|
||
)
|
||
).toBe(2);
|
||
|
||
await app.evaluate(() => {
|
||
const control = (globalThis as typeof globalThis & {
|
||
cloudChannelFixtureControls: { serverPending: boolean; disabledTarget: boolean; legacyPending: boolean };
|
||
}).cloudChannelFixtureControls;
|
||
control.serverPending = false;
|
||
control.disabledTarget = false;
|
||
control.legacyPending = false;
|
||
});
|
||
await page.getByRole('button', { name: '刷新微信列表', exact: true }).click();
|
||
const createdMore = createdCard.getByRole('button', { name: '更多操作', exact: true });
|
||
await expect(createdMore).toBeEnabled();
|
||
if (await createdMore.getAttribute('aria-expanded') !== 'true') await createdMore.click();
|
||
await createdCard.getByRole('button', { name: '解除微信绑定', exact: true }).click();
|
||
const removeBinding = page.getByRole('dialog', { name: '解除微信绑定', exact: true });
|
||
await removeBinding.getByRole('button', { name: '确认解除微信绑定', exact: true }).click();
|
||
await expect(removeBinding).toHaveCount(0);
|
||
await expect(createdCard.getByText('未绑定', { exact: true })).toBeVisible();
|
||
const mutationsBeforeCardRemoval = await app.evaluate(() =>
|
||
(globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] })
|
||
.cloudChannelFixtureCalls.filter((call) => ['createChannelAccount', 'routeChannelAccount', 'enableChannelAccount', 'pauseChannelAccount', 'unbindChannelAccount'].includes(call.operation)).length
|
||
);
|
||
await createdCard.getByRole('button', { name: '更多操作', exact: true }).click();
|
||
await expect(createdCard.getByRole('button', { name: '解除微信绑定', exact: true })).toHaveCount(0);
|
||
await createdCard.getByRole('button', { name: '删除微信卡片', exact: true }).click();
|
||
const deleting = page.getByRole('dialog', { name: '删除微信卡片', exact: true });
|
||
await expect(deleting.getByText(/这台设备的微信列表移除/)).toBeVisible();
|
||
await expect(deleting.getByText(/其他设备的记录和聊天历史保留/)).toBeVisible();
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-delete-card-confirmation.png'), fullPage: true });
|
||
await deleting.getByRole('button', { name: '确认删除卡片', exact: true }).click();
|
||
await expect(deleting).toHaveCount(0);
|
||
await expect(page.getByText('微信卡片已从这台设备移除,聊天历史保留。', { exact: true })).toBeVisible();
|
||
await expect(createdCard).toHaveCount(0);
|
||
await expect(legacyCard).toBeVisible();
|
||
await expect(spareCard).toBeVisible();
|
||
await page.getByRole('button', { name: '刷新微信列表', exact: true }).click();
|
||
await expect(createdCard).toHaveCount(0);
|
||
await page.reload();
|
||
await expect(page.getByRole('heading', { name: '连接到微信', exact: true })).toBeVisible();
|
||
await expect(createdCard).toHaveCount(0);
|
||
await expect(legacyCard).toBeVisible();
|
||
await expect(spareCard).toBeVisible();
|
||
await page.screenshot({ path: testInfo.outputPath('wechat-card-removed-this-device.png'), fullPage: true });
|
||
expect(await app.evaluate(() =>
|
||
(globalThis as typeof globalThis & { cloudChannelFixtureCalls: { operation: string }[] })
|
||
.cloudChannelFixtureCalls.filter((call) => ['createChannelAccount', 'routeChannelAccount', 'enableChannelAccount', 'pauseChannelAccount', 'unbindChannelAccount'].includes(call.operation)).length
|
||
)).toBe(mutationsBeforeCardRemoval);
|
||
} finally {
|
||
await app
|
||
.evaluate(({ BrowserWindow }) => {
|
||
for (const window of BrowserWindow.getAllWindows()) window.destroy();
|
||
})
|
||
.catch(() => undefined);
|
||
await closeElectronApp(app);
|
||
}
|
||
});
|