const { chromium } = require('playwright');
|
const fs = require('fs'), path = require('path'), assert = require('assert/strict');
|
const base = process.env.PC_TEST_URL || 'http://127.0.0.1:5667';
|
const out = path.resolve('.export_tmp/pc-seed-quality'); fs.mkdirSync(out, { recursive: true });
|
(async () => {
|
const browser = await chromium.launch({ headless: true, channel: 'msedge' });
|
const context = await browser.newContext({ viewport: { width: 1512, height: 1100 } });
|
const page = await context.newPage(), writes = [], errors = [];
|
page.on('pageerror', e => errors.push(e.message));
|
// 三种类型的待检袋清单:袋码 1 袋、托盘 2 袋、批次 3 袋
|
const pending = { BAG: [11], PALLET: [21, 22], BATCH: [31, 32, 33] };
|
const codeOf = { BAG: 'BAG-TRACE-1', PALLET: 'PALLET-100', BATCH: 'BATCH-100' };
|
let resolveMode = 'ok'; // ok | canceled | notInbound | inspected
|
await context.route('**/*', async route => {
|
const req = route.request(), u = new URL(req.url()), p = u.pathname;
|
if (u.origin !== base) return route.abort();
|
if (!p.startsWith('/admin-api/')) return route.continue();
|
let data = null;
|
if (p.endsWith('/auth/login')) data = { accessToken: 'test-token', refreshToken: 'test-refresh', userId: 7, expiresTime: Date.now() + 3600000 };
|
else if (p.endsWith('/get-permission-info')) data = {
|
user: { id: 7, nickname: '质检员', username: 'fixture', homePath: '/mes/qc/seedQuality' },
|
roles: ['worker'], permissions: ['mes:qc-seed-quality:submit', 'mes:qc-seed-quality:query'],
|
menus: [{ id: 1, name: '种子质检', path: '/mes/qc/seedQuality', component: 'mes/qc/seedQuality/index', visible: true }],
|
};
|
else if (p.endsWith('/warehouse/simple-list')) data = [
|
{ id: 8, code: 'WK-01', name: '种子合格库', type: 20 },
|
{ id: 9, code: 'WK-02', name: '冻结合格库', type: 20, frozen: true },
|
{ id: 10, code: 'WK-03', name: '暂存库', type: 10 },
|
];
|
else if (p.endsWith('/seed-quality/resolve')) {
|
const t = u.searchParams.get('scanType') || 'BAG';
|
const shell = { scanType: t, code: codeOf[t], businessCode: '8888888888888888', batchCode: 'BATCH-1' };
|
if (resolveMode === 'canceled') data = { ...shell, canceled: true, inbound: true, totalCount: 0, pendingBagIds: [], completedBagIds: [] };
|
else if (resolveMode === 'notInbound') data = { ...shell, canceled: false, inbound: false, totalCount: 2, pendingBagIds: [11], completedBagIds: [] };
|
else if (resolveMode === 'inspected') data = { ...shell, canceled: false, inbound: true, totalCount: 2, pendingBagIds: [], completedBagIds: [11] };
|
else data = { ...shell, canceled: false, inbound: true, totalCount: pending[t].length + 1, pendingBagIds: pending[t], completedBagIds: [99] };
|
} else if (p.endsWith('/seed-quality/submit-group')) {
|
const body = req.postDataJSON(); writes.push(body); data = { processedCount: body.bagIds.length };
|
} else if (p.endsWith('/get-unread-count')) data = 0;
|
else if (p.endsWith('/simple-list')) data = [];
|
else if (p.endsWith('/page')) data = { list: [], total: 0 };
|
return route.fulfill({ json: { code: 0, data, msg: '' } });
|
});
|
// 下拉选项只取当前可见的那一个:Ant 会把已打开过的下拉留在 DOM 里
|
const visibleOption = (label) => page.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option').filter({ hasText: label });
|
async function pickOption(label) { await visibleOption(label).first().click(); }
|
async function identify(type, label) {
|
await page.locator('.scan-type').click(); await pickOption(label);
|
await page.locator('.scan-input input').fill(codeOf[type]);
|
// Ant 在两字中文按钮中插入空格,可访问名是「识 别」,故用 class 定位
|
await page.locator('.scan-input .ant-input-search-button').click();
|
// 摘要卡与检验对象面板都有「本次待检」字样,故直接等面板里的袋数渲染
|
await page.locator('.target-stats .highlight strong').waitFor();
|
}
|
try {
|
await page.goto(base + '/auth/login');
|
await page.getByRole('radio', { name: 'PC 端', exact: true }).check();
|
await page.locator('input[autocomplete=username]').fill('fixture');
|
await page.locator('input[autocomplete=current-password]').fill('fixture');
|
await page.getByRole('button', { name: '登录 PC 端', exact: true }).click();
|
await page.waitForURL('**/mes/qc/seedQuality');
|
await page.locator('#__app-loading__').waitFor({ state: 'detached' });
|
await page.getByRole('heading', { name: '种子质检' }).waitFor();
|
// 1) 扫码类型下拉必须是 袋码 / 托盘码 / 批次码 三项
|
await page.locator('.scan-type').click();
|
for (const label of ['袋码', '托盘码', '批次码']) {
|
assert.equal(await visibleOption(label).count() > 0, true, '下拉缺少选项:' + label);
|
}
|
assert.equal(await page.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option').count(), 3, '扫码类型下拉应恰好三项');
|
await page.screenshot({ path: path.join(out, 'scan-type-options.png'), fullPage: true });
|
await page.keyboard.press('Escape');
|
// 2) 切换扫码类型必须清空已识别对象,避免拿上一个类型的码值/清单提交
|
await identify('BAG', '袋码');
|
await page.locator('.scan-type').click(); await pickOption('托盘码');
|
await page.waitForFunction(() => document.querySelector('.target-panel') === null);
|
// 3) 三种类型逐一识别并提交
|
for (const [type, label] of [['BAG', '袋码'], ['PALLET', '托盘码'], ['BATCH', '批次码']]) {
|
await identify(type, label);
|
const shownPending = (await page.locator('.target-stats .highlight strong').innerText()).trim();
|
assert.equal(shownPending, pending[type].length + ' 袋', type + ' 本次待检袋数不符,页面显示:' + shownPending);
|
const shownCompleted = (await page.locator('.target-stats div').nth(1).locator('strong').innerText()).trim();
|
assert.equal(shownCompleted, '1', type + ' 已检袋数不符,页面显示:' + shownCompleted);
|
assert.equal((await page.locator('.target-head .target-code').innerText()).trim(), codeOf[type], type + ' 识别对象码值不符');
|
for (const field of await page.getByPlaceholder('实测值').all()) await field.fill('90');
|
await page.locator('.base-grid .ant-select').click();
|
assert.equal(await visibleOption('冻结合格库').count(), 0, '冻结仓库不应出现在合格库下拉中');
|
await pickOption('种子合格库');
|
await page.screenshot({ path: path.join(out, 'filled-' + type.toLowerCase() + '.png'), fullPage: true });
|
await page.getByRole('button', { name: '提交质检' }).click();
|
await page.getByRole('button', { name: '确认提交' }).click();
|
// 提示条会叠留,按本次袋数定位,避免匹配到上一次的提示
|
await page.locator('.ant-message-notice-content').filter({ hasText: '本次 ' + pending[type].length + ' 袋已提交:质检合格,已转入所选合格库' }).last().waitFor();
|
const w = writes.at(-1);
|
assert.equal(w.scanType, type, type + ' 提交的 scanType 不正确');
|
assert.deepEqual(w.bagIds, pending[type], type + ' 提交的 bagIds 与识别清单不一致');
|
assert.equal(w.qualifiedWarehouseId, 8, type + ' 未带入选中的合格库');
|
assert.equal(w.indicators.length, 4, type + ' 四项指标未完整提交');
|
assert.equal(w.code, codeOf[type], type + ' 提交的码值不正确');
|
await page.waitForFunction(() => document.querySelector('.target-panel') === null);
|
}
|
// 4) 已作废 / 未全部入库 / 无待检袋码时必须拦住,且不产生任何提交
|
const before = writes.length;
|
for (const [mode, expected] of [
|
['canceled', '该质检对象已作废,不能质检'],
|
['notInbound', '该对象的有效袋码尚未全部入库,暂不能质检'],
|
['inspected', '该对象没有待质检袋码,请在质检记录中查看已有结果'],
|
]) {
|
resolveMode = mode;
|
await page.locator('.scan-input input').fill(codeOf.BAG);
|
await page.locator('.scan-input .ant-input-search-button').click();
|
await page.locator('.ant-message-notice-content').filter({ hasText: expected }).last().waitFor();
|
await page.waitForFunction(() => document.querySelector('.target-panel') === null);
|
assert.equal(await page.getByRole('button', { name: '提交质检' }).isDisabled(), true, mode + ' 时提交按钮应保持不可用');
|
}
|
assert.equal(writes.length, before, '被拦住的识别不应产生提交');
|
assert.deepEqual(errors, []);
|
console.log('PASS: 种子质检 袋码/托盘码/批次码 三类型下拉、识别范围核对、分组提交、合格库过滤与异常拦截');
|
} catch (e) {
|
await page.screenshot({ path: path.join(out, 'failure.png'), fullPage: true });
|
console.error(e); process.exitCode = 1;
|
} finally { await browser.close(); }
|
})();
|