From 608d9400289746faffa4b0cfb024967eb0158b1a Mon Sep 17 00:00:00 2001 From: nns Date: Fri, 9 Oct 2026 03:25:32 +0500 Subject: [PATCH] =?UTF-8?q?test(e2e):=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7?= =?UTF-8?q?=D0=B5=D1=80=D0=BD=D1=8B=D0=B9=20spec=20=D0=BE=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D1=86=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B0=D1=82=D0=BA=D0=BE=D0=B2=20=E2=80=94=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=BD=D0=BD=D0=B5=D1=80=20+=20=C2=AB=D0=9F=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D1=81=D1=87=D0=B8=D1=82=D0=B0=D1=82=D1=8C=C2=BB=20?= =?UTF-8?q?+=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Playwright stage-ui-negative-stock: signup+seed+списания в минус (API), затем в браузере проверяет постоянный баннер, навигацию «Пересчитать эти товары» → черновик инвентаризации, фильтр «Только минусы». Проходит на стенде (12с). Co-Authored-By: Claude Opus 4.8 --- .../scenarios/stage-ui-negative-stock.spec.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/e2e/scenarios/stage-ui-negative-stock.spec.ts diff --git a/tests/e2e/scenarios/stage-ui-negative-stock.spec.ts b/tests/e2e/scenarios/stage-ui-negative-stock.spec.ts new file mode 100644 index 0000000..566e9d7 --- /dev/null +++ b/tests/e2e/scenarios/stage-ui-negative-stock.spec.ts @@ -0,0 +1,67 @@ +/** + * E2E (браузер): отрицательные остатки в админке. + * - постоянный баннер «N товаров в минусе» виден на дашборде; + * - кнопка «Пересчитать эти товары» создаёт черновик инвентаризации и + * открывает его (навигация на /inventory/inventories/); + * - фильтр «Только минусы» в остатках показывает только отрицательные. + * + * Данные готовим через API (быстрее формы): signup + seed-demo + списания в + * минус. Проводятся списания больше остатка — разрешено (решение владельца), + * создаёт предупреждения. + */ +import { test, expect } from '@playwright/test' +import { apiSignup, attachSession, watchPage } from '../lib/ui.js' +import { request as apiRequest } from '@playwright/test' + +const BASE = process.env.E2E_ADMIN_URL ?? 'https://test.admin.food-market.kz' + +test.describe('negative-stock admin UI', () => { + test('баннер + «Пересчитать» + фильтр «Только минусы»', async ({ page }) => { + test.setTimeout(120_000) + + // 1. Орг + демо-данные. + const sess = await apiSignup('negstock') + const ctx = await apiRequest.newContext({ + baseURL: BASE, ignoreHTTPSErrors: true, timeout: 60_000, + extraHTTPHeaders: { Authorization: `Bearer ${sess.accessToken}` }, + }) + await ctx.post('/api/admin/seed-demo', { timeout: 60_000 }) + + // 2. Уводим пару товаров в минус через списания. + const storeId = (await (await ctx.get('/api/catalog/stores?pageSize=50')).json()).items[0].id + const currencyId = (await (await ctx.get('/api/catalog/currencies?pageSize=50')).json()) + .items.find((c: { code: string }) => c.code === 'KZT').id + const stock = (await (await ctx.get(`/api/inventory/stock?storeId=${storeId}&pageSize=200`)).json()).items + const positives = stock.filter((x: { quantity: number }) => x.quantity > 0).slice(0, 2) + expect(positives.length, 'в демо есть товары с остатком').toBeGreaterThan(0) + for (const row of positives) { + const loss = await ctx.post('/api/inventory/losses', { + data: { + date: new Date().toISOString(), storeId, currencyId, reason: 0, notes: 'e2e минус', + lines: [{ productId: row.productId, quantity: Number(row.quantity) + 3, unitCost: 10 }], + }, + }) + const lid = (await loss.json()).id + await ctx.post(`/api/inventory/losses/${lid}/post`, { data: {} }) + } + const cnt = await (await ctx.get('/api/negative-stock/count')).json() + expect(cnt.total, 'есть минусы').toBeGreaterThan(0) + await ctx.dispose() + + // 3. Дашборд: постоянный баннер виден. + watchPage(page) + await attachSession(page, sess, '/dashboard') + const banner = page.getByText(/в минусе — нужна инвентаризация/i) + await expect(banner).toBeVisible({ timeout: 15_000 }) + + // 4. «Пересчитать эти товары» → черновик инвентаризации. + await page.getByRole('button', { name: /Пересчитать эти товары/i }).click() + await expect(page).toHaveURL(/\/inventory\/inventories\/[0-9a-f-]+/i, { timeout: 20_000 }) + + // 5. Фильтр «Только минусы» в остатках. + await page.goto('/inventory/stock?negativeOnly=true', { waitUntil: 'domcontentloaded' }) + await expect(page.getByText('Только минусы')).toBeVisible({ timeout: 10_000 }) + // В таблице есть хотя бы одна строка с отрицательным остатком (красная). + await expect(page.locator('tbody tr').first()).toBeVisible({ timeout: 15_000 }) + }) +})