liu
2 天以前 089af38e1a85724364dc490ae5aea698aede01dc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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(); }
})();