liu
14 小时以前 089af38e1a85724364dc490ae5aea698aede01dc
test(mes): 新增种子质检三种扫码类型冒烟测试

覆盖扫码类型下拉恰好三项、三种类型分别识别后本次待检袋数正确、
提交载荷的 scanType/bagIds/qualifiedWarehouseId/indicators 正确、
冻结仓库不出现在合格库下拉、切换类型清空已识别对象,以及已作废/
有效袋码未全部入库/无待检袋码三种情况被拦下且不产生提交。

Co-Authored-By: Claude <noreply@anthropic.com>
已添加1个文件
122 ■■■■■ 文件已修改
scripts/pc-seed-quality-multitype-smoke.cjs 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
scripts/pc-seed-quality-multitype-smoke.cjs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,122 @@
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(); }
})();