| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- import assert from 'node:assert/strict';
- import { readFile } from 'node:fs/promises';
- // 从浏览器可见页面验证业务行为,不读取组件状态或替换 HTTP 响应。
- export async function checkCustomer(page, baseUrl, accounts) {
- const results = [];
- const content = page.locator('.mobile .content');
- async function navigate(target) {
- await page.evaluate(value => { window.location.hash = `/customer/${value}`; }, target);
- await page.waitForURL(new RegExp(`#/?customer/${target}$`));
- await page.locator(`.mobile[data-page="${target}"]`).waitFor();
- await page.waitForFunction(() => !document.querySelector('.mobile .content[aria-busy="true"]'));
- }
- async function click(name) { await content.getByRole('button', { name, exact: true }).click(); }
- async function confirm() { await page.getByRole('dialog').getByRole('button', { name: '确认操作', exact: true }).click(); await page.getByRole('dialog').waitFor({ state: 'hidden' }); }
- async function expectText(text) { await content.getByText(text, { exact: false }).first().waitFor(); }
- await page.goto(`${baseUrl}/#customer/login`);
- await content.getByLabel('账户', { exact: true }).fill('customer01');
- await content.getByLabel('密码', { exact: true }).fill(`${accounts.password}-invalid`);
- await content.getByRole('checkbox').check();
- await click('登录并继续');
- await content.getByRole('alert').waitFor();
- assert.equal(await content.getByLabel('密码', { exact: true }).inputValue(), '');
- assert.equal(await content.getByLabel('账户', { exact: true }).inputValue(), 'customer01');
- await content.getByLabel('密码', { exact: true }).fill(accounts.password);
- await click('登录并继续');
- await page.waitForURL(/#\/?customer\/chat$/);
- await expectText('已登录');
- results.push('H5 真实身份登录与返回咨询');
- // 限制真实网络速度,检查旧页面仍在读取时进入新页面的行为,不替换任何服务响应。
- await navigate('profile');
- const network = await page.context().newCDPSession(page);
- await network.send('Network.enable');
- await network.send('Network.emulateNetworkConditions', { offline: false, latency: 180, downloadThroughput: 375000, uploadThroughput: 375000 });
- try {
- const readingChat = page.waitForRequest(request => request.method() === 'GET' && request.url().includes('/openapi/v1/conversations'));
- await page.evaluate(() => { location.hash = '/customer/chat'; });
- await readingChat;
- await navigate('orders');
- await content.locator('.order-card').filter({ hasText: accounts.order_id }).waitFor();
- } finally {
- await network.send('Network.emulateNetworkConditions', { offline: false, latency: 0, downloadThroughput: -1, uploadThroughput: -1 });
- await network.detach();
- }
- results.push('真实慢速网络中从咨询快速进入订单,订单请求及内容正常');
- const pages = ['home','categories','product','cart','profile','messages','address','favorites','settings','chat','orders','order','policy','apply','proposal','ticket','memory','identity','login'];
- for (const target of pages) {
- await navigate(target);
- assert.equal(await content.getByRole('alert').count(), 0, `${target} 页面出现服务错误`);
- assert.ok((await page.locator('.mobile-header strong').last().innerText()).length > 0, `${target} 缺少标题`);
- for (const width of [390, 1440]) {
- await page.setViewportSize({ width, height: 900 });
- const geometry = await page.locator('body').evaluate(element => ({ width: document.documentElement.clientWidth, scroll: element.scrollWidth }));
- assert.ok(geometry.scroll <= geometry.width + 1, `${target} ${width}px 页面横向溢出`);
- }
- }
- results.push('19 个 H5 页面可访问,390px 与 1440px 宽度无横向溢出');
- const catalogResponse = await page.request.get(`${baseUrl}/openapi/v1/stores/${accounts.store_id}/catalog?limit=100`);
- assert.equal(catalogResponse.status(), 200);
- const catalog = await catalogResponse.json();
- await navigate('login');
- assert.equal(await content.locator('.customer-login-store strong').innerText(), catalog.store_name);
- await navigate('home');
- assert.equal(await content.locator('.shop-banner small').innerText(), catalog.store_name);
- assert.deepEqual(await content.locator('.goods-info strong').allTextContents(), catalog.items.slice(0, 2).map(item => item.name));
- results.push('首页商品、分类与登录店铺名称来自实际目录接口');
- await page.locator('.shop-tabs').getByRole('tab', { name: '分类', exact: true }).click();
- await page.waitForURL(/#\/?customer\/categories$/);
- await page.locator('.shop-tabs').getByRole('tab', { name: '首页', exact: true }).click();
- await page.waitForURL(/#\/?customer\/home$/);
- await content.locator('.ai-entry').click();
- await page.waitForURL(/#\/?customer\/chat$/);
- await page.waitForFunction(() => !document.querySelector('.mobile .content[aria-busy="true"]'));
- const humanButton = content.getByRole('button', { name: '申请人工协助', exact: true });
- if (!await humanButton.isDisabled()) await humanButton.click();
- await expectText('等待客服接管');
- assert.equal(await content.getByRole('button', { name: '申请人工协助', exact: true }).isDisabled(), true);
- await navigate('profile');
- await navigate('chat');
- await expectText('等待客服接管');
- results.push('人工协助真实持久登记、重新进入恢复及重复操作禁用');
- // 使用实际客服身份接管会话后验证客户消息,全程不产生生成模型请求。
- await navigate('profile');
- const conversationResponse = page.waitForResponse(response => response.request().method() === 'GET' && /\/conversations\/[0-9a-f-]+$/.test(new URL(response.url()).pathname));
- await navigate('chat');
- const conversation = await (await conversationResponse).json();
- const staffAuth = await page.request.post(`${baseUrl}/openapi/v1/auth/sessions`, { data: { tenant_id: accounts.tenant_id, username: 'staff01', password: accounts.password } });
- assert.equal(staffAuth.status(), 200);
- const staffToken = (await staffAuth.json()).access_token;
- const staffHeaders = { Authorization: `Bearer ${staffToken}` };
- const conversationPath = `${baseUrl}/openapi/v1/conversations/${conversation.conversation_id}`;
- let taken = false;
- try {
- const takeover = await page.request.post(`${conversationPath}/takeover`, { headers: { ...staffHeaders, 'Idempotency-Key': crypto.randomUUID() }, data: { expected_control_version: conversation.control_version } });
- assert.equal(takeover.status(), 200, await takeover.text());
- taken = true;
- await navigate('orders');
- await content.locator('.order-card').filter({ hasText: accounts.order_id }).getByRole('button', { name: '联系 AI 客服', exact: true }).click();
- await expectText('人工客服正在为你服务');
- const question = `请核实当前订单的服务进度 ${Date.now()}`;
- await content.getByLabel('补充问题', { exact: true }).fill(question);
- const sent = page.waitForResponse(response => response.request().method() === 'POST' && response.url() === `${conversationPath}/messages`);
- await click('发送');
- const saved = await sent;
- assert.equal(saved.status(), 201, await saved.text());
- const event = await saved.json();
- assert.equal(event.payload.text, `咨询对象:订单 ${accounts.order_id}\n${question}`);
- assert.equal(event.payload.run_id, null);
- await navigate('profile');
- await navigate('chat');
- await expectText(question);
- results.push('订单咨询上下文和客户问题真实保存,重新进入恢复同一消息');
- } finally {
- if (taken) {
- const latest = await page.request.get(conversationPath, { headers: staffHeaders });
- assert.equal(latest.status(), 200);
- const release = await page.request.post(`${conversationPath}/release`, { headers: { ...staffHeaders, 'Idempotency-Key': crypto.randomUUID() }, data: { expected_control_version: (await latest.json()).control_version } });
- assert.equal(release.status(), 200, await release.text());
- }
- const logout = await page.request.delete(`${baseUrl}/openapi/v1/auth/sessions/current`, { headers: staffHeaders });
- assert.equal(logout.status(), 204);
- }
- // 开发验收订单可能保留已登记的申请,通过本人确认取消后创建本次独立方案。
- let prepared = false;
- let cancelled = 0;
- for (let attempt = 0; attempt < 100; attempt++) {
- await navigate('orders');
- const order = content.locator('.order-card').filter({ hasText: accounts.order_id });
- await order.getByRole('button', { name: '查看订单', exact: true }).click();
- await expectText(accounts.order_id);
- await click('填写换货申请');
- await page.waitForFunction(() => !document.querySelector('.mobile .content[aria-busy="true"]'));
- const createButton = content.getByRole('button', { name: '生成方案并检查', exact: true });
- if (await createButton.count() && await createButton.isEnabled()) { prepared = true; break; }
- await navigate('proposal');
- assert.equal(await content.getByRole('button', { name: '取消申请', exact: true }).isEnabled(), true, '验收订单存在不可取消申请,须准备可创建方案的真实订单');
- const cancelling = page.waitForResponse(response => response.request().method() === 'POST' && /\/tickets\/[^/]+\/cancel$/.test(new URL(response.url()).pathname));
- await click('取消申请');
- await confirm();
- const cancelledResponse = await cancelling;
- assert.equal(cancelledResponse.status(), 200, await cancelledResponse.text());
- assert.equal((await cancelledResponse.json()).status, 'CANCELLED');
- cancelled++;
- }
- assert.equal(prepared, true, '验收订单在限定次数内未进入可创建方案状态');
- if (cancelled) results.push(`通过本人确认取消 ${cancelled} 个既有验收申请,准备独立方案`);
- await content.getByLabel('申请数量', { exact: true }).fill('1');
- await content.getByLabel('故障描述', { exact: true }).fill('键盘空格键无法使用,申请换货。');
- const creatingProposal = page.waitForResponse(response => response.request().method() === 'POST' && new URL(response.url()).pathname === '/openapi/v1/after-sales/proposals');
- await click('生成方案并检查');
- const proposalResponse = await creatingProposal;
- assert.equal(proposalResponse.status(), 201, await proposalResponse.text());
- const proposal = await proposalResponse.json();
- assert.equal(proposal.proposal_version, 1);
- assert.equal(proposal.order_id, accounts.order_id);
- await page.waitForURL(/#\/?customer\/proposal$/);
- await expectText('待客户确认');
- await content.getByRole('checkbox').check();
- await click('确认并提交审批');
- await page.waitForURL(/#\/?customer\/ticket$/);
- await expectText('等待主管审批');
- await click('修订方案');
- await content.getByLabel('故障描述', { exact: true }).fill('键盘空格键和回车键无法使用,申请换货。');
- await click('保存新版本并检查');
- await page.waitForURL(/#\/?customer\/proposal$/);
- await expectText('方案版本:V2');
- await click('取消申请');
- await confirm();
- await expectText('申请已取消');
- assert.equal(await content.getByRole('button', { name: '取消申请', exact: true }).isDisabled(), true);
- results.push('真实订单查询、方案创建、本人确认、修订失效及取消');
- await navigate('memory');
- const authorize = content.getByRole('button', { name: '授权保存', exact: true });
- if (await authorize.count()) { await authorize.click(); await confirm(); }
- await content.locator('.memory-style-field').getByRole('button').click();
- await page.getByRole('menuitem', { name: '详细回复', exact: true }).click();
- await content.locator('.memory-language-field').getByRole('button').click();
- await page.getByRole('menuitem', { name: 'English', exact: true }).click();
- await click('保存沟通习惯');
- await expectText('已保存:详细回复 / English');
- await navigate('profile');
- await navigate('memory');
- await expectText('已保存:详细回复 / English');
- await click('清空已保存内容');
- await confirm();
- await expectText('当前没有已保存的沟通习惯');
- assert.match(await content.locator('.memory-style-field').innerText(), /简洁回复/);
- assert.match(await content.locator('.memory-language-field').innerText(), /中文/);
- await click('撤销授权');
- await confirm();
- await expectText('尚未授权');
- results.push('真实沟通习惯授权、保存、清空及撤销');
- await navigate('identity');
- if (!await content.getByText('Webhook 渠道已关联', { exact: true }).count()) {
- const creating = page.waitForResponse(response => response.request().method() === 'POST' && response.url().includes('/me/identity-challenges'));
- await click('生成验证代码');
- const created = await creating;
- if (created.status() === 409) {
- assert.equal((await created.json()).code, 'CHANNEL_UNAVAILABLE');
- await expectText('渠道尚未启用');
- assert.equal(await content.getByRole('button', { name: '确认关联', exact: true }).isDisabled(), true);
- results.push('渠道未启用时真实拒绝验证且关联按钮保持禁用');
- return results;
- }
- assert.equal(created.status(), 200, await created.text());
- await content.locator('.identity-code').waitFor();
- assert.equal(await content.getByRole('button', { name: '确认关联', exact: true }).isDisabled(), true);
- const challengeId = await content.locator('.identity-code').innerText();
- const credentials = JSON.parse(await readFile(new URL('../../.work/storefront-channel.json', import.meta.url), 'utf8'));
- const response = await page.request.post(`${baseUrl}/openapi/v1/auth/sessions`, { data: { tenant_id: credentials.tenant_id, username: credentials.username, password: credentials.password } });
- assert.equal(response.status(), 200);
- const token = (await response.json()).access_token;
- const verified = await page.request.post(`${baseUrl}/openapi/v1/channel/identity-challenges/${challengeId}/verify`, {
- headers: { Authorization: `Bearer ${token}` },
- data: { store_id: credentials.store_id, channel: 'webhook', external_subject: 'frontend-channel-customer01', display_name: '渠道客户 customer01' },
- });
- assert.equal(verified.status(), 200, await verified.text());
- await click('查询验证结果');
- await expectText('渠道客户 customer01');
- await click('确认关联');
- await confirm();
- await page.request.delete(`${baseUrl}/openapi/v1/auth/sessions/current`, { headers: { Authorization: `Bearer ${token}` } });
- }
- await expectText('Webhook 渠道已关联');
- await navigate('profile');
- await navigate('identity');
- await expectText('Webhook 渠道已关联');
- results.push('真实渠道服务验证、本人再次确认及持久关联恢复');
- return results;
- }
|