fix(init-wallet): URL-fallback на coopname + harness scenario chairman dashboard

- init-wallet: info.coopname может быть undefined из-за race между login и
  loadSystemInfo. Когда login завершается быстрее, init-wallet вызывает
  loadUserWallet({coopname: undefined}) → GraphQL 400 → catch → reload →
  фронт никогда не редиректит после login. URL-fallback извлекает coopname
  из window.location.hash/pathname как safety net.
- harness scenarios/marketplace/chairman/dashboard-overview.mjs: обзорный
  сценарий по 5 marketplace URL-ам (/marketplace, /marketplace/catalog,
  /marketplace/orders, /chairman, /chairman/approval-requests). Без
  waitForURL — фронт сейчас не делает redirect после login.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ant
2026-05-20 21:26:42 +00:00
parent db48339051
commit 6a523db8b1
2 changed files with 65 additions and 1 deletions
@@ -19,6 +19,17 @@ function walletInitTimeoutPromise(): Promise<never> {
});
}
// URL-based fallback на случай если info.coopname ещё не подгрузился
// из getSystemInfo (race с login: init-wallet стартует до того как
// useSystemStore.loadSystemInfo() заполнил info из бэка).
function getCoopnameFromUrl(): string {
if (typeof window === 'undefined') return '';
const src = window.location.hash || window.location.pathname || '';
// hash вида "#/voskhod/..." или path "/voskhod/..."
const m = src.replace(/^#/, '').match(/^\/?([a-z0-9_-]+)\b/i);
return m ? m[1] : '';
}
export function useInitWalletProcess() {
const session = useSessionStore();
const wallet = useWalletStore();
@@ -60,8 +71,12 @@ export function useInitWalletProcess() {
// вернёт пустой кошелёк, а UI начнёт показывать подписи документов,
// которых юзер на этом этапе подписывать не должен.
if (session.isFullyActive) {
// info.coopname может быть undefined если getSystemInfo ещё не
// отдала ответ (race с login). URL-fallback гарантирует, что мы
// не вызовем loadUserWallet({coopname: undefined}) → GraphQL 400.
const coopname = info.coopname || getCoopnameFromUrl();
await wallet.loadUserWallet({
coopname: info.coopname,
coopname,
username: session.username,
});
}
@@ -0,0 +1,49 @@
// Сценарий: Стол председателя кооператива → обзорная экскурсия по marketplace URLs.
// Делает login как chairman (`ant`/`ivanov@example.com`), затем напрямую
// goto к каждому marketplace-маршруту и снимает что вышло.
//
// На текущем стенде session.loadComplete зависает на 5с (init-wallet падает),
// поэтому waitForURL после клика «Войти» бесполезен — используем waitForTimeout
// и goto. После клика «Войти» token успевает сохраниться в IndexedDB, что
// позволяет последующим goto'ам захватить authenticated layout (хедер).
import { cleanViteOverlays, env } from '../../../lib/harness.mjs';
export const meta = {
title: 'Стол председателя ПК: экскурсия по marketplace',
docPath: 'new/marketplace/chairman/dashboard.md',
assetsDir: 'assets/new/marketplace/chairman/dashboard',
role: 'chairman',
};
const ROUTES = [
{ name: '01-marketplace-main', path: '/marketplace', caption: 'Главная marketplace' },
{ name: '02-marketplace-catalog', path: '/marketplace/catalog', caption: 'Каталог витрины' },
{ name: '03-marketplace-orders', path: '/marketplace/orders', caption: 'Мои заказы' },
{ name: '04-chairman', path: '/chairman', caption: 'Стол председателя' },
{ name: '05-chairman-approvals', path: '/chairman/approval-requests', caption: 'Запросы одобрений' },
];
export default async ({ page, shot, context }) => {
// --- Login (без ожидания редиректа — фронт не редиректит на этом стенде)
await page.goto(`${env.BASE_URL}/${env.COOPNAME}/auth/signin`, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForSelector('button:has-text("Войти")', { timeout: 60000 });
await cleanViteOverlays(page);
await page.locator('label:has-text("электронную почту")').locator('input').fill(env.CHAIRMAN_EMAIL);
await page.locator('label:has-text("ключ доступа")').locator('input').fill(env.CHAIRMAN_WIF);
await cleanViteOverlays(page);
await page.locator('button:has-text("Войти")').click();
// Подождать сохранения токенов в IndexedDB (login API + globalStore.setTokens)
await page.waitForTimeout(4000);
await shot(page, '00-after-login', 'Состояние сразу после клика «Войти» (фронт ещё не редиректит автоматически — баг с реактивностью info.coopname).');
// --- Обзорная экскурсия по marketplace маршрутам
for (const route of ROUTES) {
const url = `${env.BASE_URL}/${env.COOPNAME}${route.path}`;
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(5000);
await cleanViteOverlays(page);
await shot(page, route.name, `${route.caption}: ${url.replace(env.BASE_URL, '')}`);
}
};