[598][@ant] feat(docs-harness): 3 пилот-сценария для новых страниц marketplace

Сценарии для прогона harness'ом по реализованным страницам:
- onboarding/coop-accept-cpp.mjs (chairman ant, L1 ЦПП)
- orderer/consolidated.mjs (ivanpetrov, партии заказчика)
- chairman/category-whitelist.mjs (chairman ant, whitelist категорий)

Прогон самим harness'ом пока ЗАБЛОКИРОВАН — shared desktop dev :3029
не видит worktree-страниц (src vs node_modules: src в worktree локален,
а node_modules симлинкованы). Чтобы снять скриншоты:
вариант A — sync 9 pages в shared desktop;
вариант B — поднять отдельный worktree desktop dev (медленно).

Остальные 6 сценариев (offerer/incoming-orders, offerer/my-offers,
branch-chairman/branch-orders, board/agenda-writeoff, board/payouts-readonly,
onboarding/member-pick-cpp) — создаются после прогона пилот-тройки.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ant
2026-05-21 18:06:02 +00:00
parent 02c3f68d71
commit be02498815
3 changed files with 165 additions and 0 deletions
@@ -0,0 +1,52 @@
// Сценарий: Председатель настраивает whitelist доступных категорий товаров.
// Эпик 3 / Story 3.x — пустой whitelist означает, что доступен весь
// глобальный каталог; с whitelist'ом — только перечисленные категории.
//
// Backend `available-category-admin.resolver.ts` (@AuthRoles chairman).
// Tree-выбор через `marketplaceGetCategoryTree` подключится на следующем
// шаге; в MVP — диалог с ручным вводом ID через запятую.
//
// Фикстура: chairman кооператива (ant, Иван Иванов).
import { loginAsChairman, dismissOnboardingDialogs } from '../../../lib/harness.mjs';
export const meta = {
title: 'Whitelist категорий товаров кооператива',
docPath: 'new/marketplace/chairman/category-whitelist.md',
assetsDir: 'assets/new/marketplace/chairman/category-whitelist',
role: 'chairman',
};
export default async ({ page, context, shot, env }) => {
await loginAsChairman(page, context);
await dismissOnboardingDialogs(page);
// 1. Открыть страницу доступных категорий
await page.goto(`${env.BASE_URL}/#/${env.COOPNAME}/market/category-whitelist`, {
waitUntil: 'domcontentloaded',
});
await page.waitForSelector('text=Доступные категории', { timeout: 60000 });
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
await page.waitForTimeout(900);
await dismissOnboardingDialogs(page);
await shot(
page,
'01-overview',
'Страница «Доступные категории»: 4 stat-карточки в шапке — всего записей, число категорий, число типов товаров и состояние whitelist (активен / открыт каталог). Пустой whitelist означает, что пайщикам доступен весь глобальный каталог категорий.',
);
// 2. Открыть диалог добавления категорий
const addBtn = page.locator('button:has-text("Добавить")').first();
if (await addBtn.isVisible().catch(() => false)) {
await addBtn.click();
await page.waitForSelector('text=Добавить категории в whitelist', { timeout: 10000 });
await page.waitForTimeout(500);
await shot(
page,
'02-add-dialog',
'Диалог добавления: в MVP — ручной ввод ID через запятую (1, 7, 15). На следующем шаге Story 3.x подключится tree-выбор через marketplaceGetCategoryTree — выбор по дереву категорий и типов.',
);
await page.keyboard.press('Escape');
await page.waitForTimeout(500);
}
};
@@ -0,0 +1,52 @@
// Сценарий: Председатель кооператива принимает ЦПП «Стол заказов».
// Эпик 1 / Story 1.9-1.10 — L1 онбординг кооператива.
//
// Председатель видит статус ЦПП (active / not_accepted) и кнопку
// «Принять ЦПП Marketplace». После клика — диалог подтверждения с
// stub `accepted_by_board_decision_id` (в MVP — текстовая ссылка;
// полноценная повестка совета — FR40 / Эпик 8, Phase 2).
//
// Фикстура: chairman кооператива (ant, Иван Иванов).
import { loginAsChairman, dismissOnboardingDialogs } from '../../../lib/harness.mjs';
export const meta = {
title: 'L1 онбординг — приём ЦПП «Стол заказов» кооперативом',
docPath: 'new/marketplace/onboarding/coop-accept-cpp.md',
assetsDir: 'assets/new/marketplace/onboarding/coop-accept-cpp',
role: 'chairman',
};
export default async ({ page, context, shot, env }) => {
await loginAsChairman(page, context);
await dismissOnboardingDialogs(page);
// 1. Открыть страницу подключения ЦПП
await page.goto(`${env.BASE_URL}/#/${env.COOPNAME}/market/onboarding/coop-cpp`, {
waitUntil: 'domcontentloaded',
});
await page.waitForSelector('text=Подключение ЦПП', { timeout: 60000 });
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
await page.waitForTimeout(800);
await dismissOnboardingDialogs(page);
await shot(
page,
'01-status',
'Страница подключения ЦПП «Стол заказов»: chip справа показывает текущий статус расширения (Подключено / Не подключено). Карточка под шапкой раскрывает статус, реестр оферты, дату принятия и решение совета.',
);
// 2. Если не принято — открыть диалог подтверждения принятия
const acceptBtn = page.locator('button:has-text("Принять ЦПП Marketplace")');
if (await acceptBtn.isVisible().catch(() => false)) {
await acceptBtn.click();
await page.waitForSelector('text=Принять ЦПП', { timeout: 10000 });
await page.waitForTimeout(600);
await shot(
page,
'02-confirm-dialog',
'Диалог подтверждения принятия ЦПП «Стол заказов». В MVP — stub решения совета; полноценная повестка подключится в Эпике 8 (FR40).',
);
await page.keyboard.press('Escape');
await page.waitForTimeout(500);
}
};
@@ -0,0 +1,61 @@
// Сценарий: Сводный обзор заказов пайщика-заказчика.
// Эпик 4 / Story 4.4 — заказы группируются по cycle_id (time_based /
// volume_based / open_subscription / individual) с суммарной стоимостью
// партии и общим этапом партии (минимальный активный по STAGE_RANK).
//
// Канон-виджет `OrderCard` для отдельных заказов в партии.
// Polling 15s — обновляет список без перезагрузки.
//
// Фикстура: ivanpetrov (Иван Сергеевич Петров), пайщик-заказчик.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loginAs, env, cleanViteOverlays } from '../../../lib/harness.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const loadFixture = (username) =>
JSON.parse(
fs.readFileSync(path.resolve(__dirname, `../../../state/participants/${username}.json`), 'utf8'),
);
export const meta = {
title: 'Сводный заказ: партии заказчика по cycle_id',
docPath: 'new/marketplace/orderer/consolidated.md',
assetsDir: 'assets/new/marketplace/orderer/consolidated',
role: 'user',
fixture: 'ivanpetrov',
};
export default async ({ page, shot }) => {
const fixture = loadFixture('ivanpetrov');
await loginAs(page, fixture);
await page.evaluate(() => localStorage.setItem('harness:noBranchOverlay', '1'));
// 1. Открыть страницу сводного заказа
await page.goto(`${env.BASE_URL}/#/${env.COOPNAME}/market/consolidated`, {
waitUntil: 'domcontentloaded',
});
await page.waitForSelector('text=Сводный заказ', { timeout: 60000 });
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1200);
await cleanViteOverlays(page);
await shot(
page,
'01-overview',
'Сводный обзор партий заказов пайщика-заказчика. Партии с cycle_id выводятся первыми (заказы внутри партии обслуживаются совместно — на одном цикле, с одной поставкой), индивидуальные заказы — отдельной группой. Для каждой партии показан тип цикла, этап партии, суммарная стоимость и ПВЗ доставки.',
);
// 2. Развернуть первую партию (если есть заказы)
const firstGroup = page.locator('.mp-consolidated__group').first();
if (await firstGroup.isVisible().catch(() => false)) {
await firstGroup.scrollIntoViewIfNeeded();
await page.waitForTimeout(400);
await shot(
page,
'02-group-detail',
'Партия развёрнута: внутри — карточки отдельных заказов (канон OrderCard role=orderer). Этап партии = минимальный активный статус среди заказов (если хотя бы один ждёт цикл — вся партия «Активна»).',
);
}
};