520 lines
23 KiB
TypeScript
520 lines
23 KiB
TypeScript
import path from 'node:path';
|
|
import { randomUUID } from 'node:crypto';
|
|
|
|
import { expect, test, type Page } from '@playwright/test';
|
|
import { encryptGameSessionToken } from '@sammo-ts/common/auth/gameToken';
|
|
import {
|
|
createGamePostgresConnector,
|
|
type GamePrisma,
|
|
type GamePrismaClient,
|
|
} from '@sammo-ts/infra';
|
|
|
|
const gameTokenSecret = process.env.SELECT_POOL_LIVE_GAME_SECRET;
|
|
const databaseUrl = process.env.SELECT_POOL_LIVE_DATABASE_URL;
|
|
const userId = process.env.SELECT_POOL_LIVE_USER_ID;
|
|
const profile = process.env.PLAYWRIGHT_GAME_PROFILE ?? 'hwe:903';
|
|
const hasLiveFixture = Boolean(gameTokenSecret && databaseUrl && userId);
|
|
const workspaceRoot =
|
|
process.env.SAMMO_WORKSPACE_ROOT ??
|
|
path.resolve(import.meta.dirname, '../../../../../sam_rebuild');
|
|
const defaultIcon = path.resolve(
|
|
process.env.SELECT_POOL_LIVE_DEFAULT_ICON ??
|
|
path.join(workspaceRoot, 'image/icons/default.jpg')
|
|
);
|
|
const walnutTexture = path.join(workspaceRoot, 'image/game/back_walnut.jpg');
|
|
const greenTexture = path.join(workspaceRoot, 'image/game/back_green.jpg');
|
|
const fixtureNationIds = [990_901, 990_902, 990_903];
|
|
|
|
interface AssetTracker {
|
|
userIconRequests: number;
|
|
}
|
|
|
|
const installSession = async (page: Page, tracker?: AssetTracker): Promise<void> => {
|
|
const now = new Date();
|
|
const gameToken = encryptGameSessionToken(
|
|
{
|
|
version: 1,
|
|
profile,
|
|
issuedAt: now.toISOString(),
|
|
expiresAt: new Date(now.getTime() + 3_600_000).toISOString(),
|
|
sessionId: `select-pool-live-${randomUUID()}`,
|
|
user: {
|
|
id: userId!,
|
|
username: 'select-pool-live',
|
|
displayName: '선택실사용자',
|
|
roles: ['user'],
|
|
legacyMemberNo: 42,
|
|
},
|
|
sanctions: {},
|
|
identity: {
|
|
kakaoVerified: true,
|
|
canCreateGeneral: true,
|
|
requiresKakaoVerification: false,
|
|
graceEndsAt: null,
|
|
},
|
|
},
|
|
gameTokenSecret!
|
|
);
|
|
await page.addInitScript(
|
|
({ token, gameProfile }) => {
|
|
if (!window.localStorage.getItem('sammo-game-token')) {
|
|
window.localStorage.setItem('sammo-game-token', token);
|
|
}
|
|
window.localStorage.setItem('sammo-game-profile', gameProfile);
|
|
},
|
|
{ token: gameToken, gameProfile: profile }
|
|
);
|
|
await page.addInitScript(() => {
|
|
const values = [1, 0];
|
|
Object.defineProperty(window.crypto, 'getRandomValues', {
|
|
configurable: true,
|
|
value: <T extends ArrayBufferView | null>(array: T): T => {
|
|
if (array && (array as Uint32Array).length > 0) {
|
|
(array as Uint32Array)[0] = values.shift() ?? 0;
|
|
}
|
|
return array;
|
|
},
|
|
});
|
|
});
|
|
await page.route('**/image/icons/**', (route) =>
|
|
route.fulfill({ path: defaultIcon, contentType: 'image/jpeg' })
|
|
);
|
|
await page.route('**/gateway/api/user-icons/**', (route) => {
|
|
if (tracker) {
|
|
tracker.userIconRequests += 1;
|
|
}
|
|
return route.fulfill({ status: 404, body: '' });
|
|
});
|
|
await page.route('**/image/game/back_walnut.jpg', (route) =>
|
|
route.fulfill({ path: walnutTexture, contentType: 'image/jpeg' })
|
|
);
|
|
await page.route('**/image/game/back_green.jpg', (route) =>
|
|
route.fulfill({ path: greenTexture, contentType: 'image/jpeg' })
|
|
);
|
|
};
|
|
|
|
const waitForPool = async (page: Page): Promise<void> => {
|
|
await expect(page.locator('.card-holder > .general-card')).toHaveCount(14);
|
|
await page.evaluate(async () => {
|
|
await document.fonts.ready;
|
|
});
|
|
};
|
|
|
|
test.describe('scenario 903 live selection pool', () => {
|
|
test.skip(!hasLiveFixture, 'live selection-pool token and database are required');
|
|
|
|
let db: GamePrismaClient;
|
|
let closeDb: (() => Promise<void>) | undefined;
|
|
|
|
test.beforeAll(async () => {
|
|
const connector = createGamePostgresConnector({ url: databaseUrl! });
|
|
await connector.connect();
|
|
db = connector.prisma;
|
|
closeDb = () => connector.disconnect();
|
|
});
|
|
|
|
test.afterAll(async () => {
|
|
await closeDb?.();
|
|
});
|
|
|
|
test('renders Ref-width desktop/mobile cards, tooltip, focus, and expiration states', async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await page.clock.install({ time: new Date() });
|
|
const assetTracker: AssetTracker = { userIconRequests: 0 };
|
|
await installSession(page, assetTracker);
|
|
await page.setViewportSize({ width: 1200, height: 900 });
|
|
await page.goto('select-general');
|
|
await waitForPool(page);
|
|
await expect(page.locator('.server-info-table')).toContainText(
|
|
'현재 : 180年 1月 (5분 턴 서버)'
|
|
);
|
|
await expect(page.locator('.server-info-table')).toContainText(
|
|
'등록 장수 : 유저 0 / 500 명'
|
|
);
|
|
await expect(page.locator('.invitation-table')).toContainText('임관 권유 메시지');
|
|
|
|
const geometry = await page.evaluate(() => {
|
|
const root = document.querySelector<HTMLElement>('.select-pool-page')!;
|
|
const cards = Array.from(
|
|
document.querySelectorAll<HTMLElement>('.card-holder > .general-card')
|
|
);
|
|
const images = Array.from(
|
|
document.querySelectorAll<HTMLImageElement>('.card-holder .portrait img')
|
|
);
|
|
const selectionBody = document.querySelector<HTMLElement>('.selection-body')!;
|
|
const createSection = document.querySelector<HTMLElement>('.create-section')!;
|
|
const createBody = document.querySelector<HTMLElement>('.create-body')!;
|
|
const pageTitle = document.querySelector<HTMLElement>('.page-title')!;
|
|
const serverInfoTable =
|
|
document.querySelector<HTMLElement>('.server-info-table')!;
|
|
const invitationTable =
|
|
document.querySelector<HTMLElement>('.invitation-table')!;
|
|
const footerBack = document.querySelector<HTMLElement>('.footer-back')!;
|
|
const footerBanner = document.querySelector<HTMLElement>('.footer-banner')!;
|
|
const firstButton = document.querySelector<HTMLElement>(
|
|
'.card-holder .select-button'
|
|
)!;
|
|
return {
|
|
root: root.getBoundingClientRect().toJSON(),
|
|
pageTitle: pageTitle.getBoundingClientRect().toJSON(),
|
|
serverInfoTable: serverInfoTable.getBoundingClientRect().toJSON(),
|
|
invitationTable: invitationTable.getBoundingClientRect().toJSON(),
|
|
cards: cards.map((card) => card.getBoundingClientRect().toJSON()),
|
|
images: images.map((image) => ({
|
|
rect: image.getBoundingClientRect().toJSON(),
|
|
naturalWidth: image.naturalWidth,
|
|
naturalHeight: image.naturalHeight,
|
|
objectFit: getComputedStyle(image).objectFit,
|
|
})),
|
|
selectionBody: selectionBody.getBoundingClientRect().toJSON(),
|
|
createSection: createSection.getBoundingClientRect().toJSON(),
|
|
createBody: createBody.getBoundingClientRect().toJSON(),
|
|
footerBack: footerBack.getBoundingClientRect().toJSON(),
|
|
footerBanner: footerBanner.getBoundingClientRect().toJSON(),
|
|
firstButton: firstButton.getBoundingClientRect().toJSON(),
|
|
rootStyle: {
|
|
fontFamily: getComputedStyle(root).fontFamily,
|
|
fontSize: getComputedStyle(root).fontSize,
|
|
lineHeight: getComputedStyle(root).lineHeight,
|
|
backgroundImage: getComputedStyle(root).backgroundImage,
|
|
},
|
|
scrollWidth: document.documentElement.scrollWidth,
|
|
};
|
|
});
|
|
expect(geometry.root).toMatchObject({ x: 100, y: 8, width: 1000 });
|
|
expect(geometry.pageTitle).toMatchObject({ x: 100, y: 8, width: 1000 });
|
|
expect(Math.abs(geometry.pageTitle.height - 42.1875)).toBeLessThan(0.6);
|
|
expect(Math.abs(geometry.serverInfoTable.y - 50.1875)).toBeLessThan(0.6);
|
|
expect(Math.abs(geometry.serverInfoTable.height - 40.375)).toBeLessThan(0.6);
|
|
expect(Math.abs(geometry.invitationTable.x - 553)).toBeLessThan(0.6);
|
|
expect(Math.abs(geometry.invitationTable.y - 90.5625)).toBeLessThan(0.6);
|
|
expect(geometry.invitationTable.width).toBe(94);
|
|
expect(Math.abs(geometry.invitationTable.height - 20.1875)).toBeLessThan(0.1);
|
|
expect(geometry.rootStyle).toMatchObject({
|
|
fontSize: '14px',
|
|
lineHeight: '18.2px',
|
|
});
|
|
expect(geometry.rootStyle.fontFamily).toContain('Pretendard');
|
|
expect(geometry.rootStyle.backgroundImage).toContain('back_walnut.jpg');
|
|
expect(geometry.cards.every((card) => card.width === 127)).toBe(true);
|
|
expect(new Set(geometry.cards.map((card) => card.y)).size).toBe(2);
|
|
const shortestCard = Math.min(...geometry.cards.map((card) => card.height));
|
|
expect(Math.abs(shortestCard - 254.875)).toBeLessThan(0.6);
|
|
expect(
|
|
geometry.images.every(
|
|
(image, index) =>
|
|
image.rect.width === 64 &&
|
|
image.rect.height === 64 &&
|
|
Math.abs(
|
|
image.rect.x -
|
|
(geometry.cards[index]!.x +
|
|
(geometry.cards[index]!.width - image.rect.width) / 2)
|
|
) < 0.1
|
|
)
|
|
).toBe(true);
|
|
expect(
|
|
geometry.images.every(
|
|
(image) => image.naturalWidth > 0 && image.naturalHeight > 0
|
|
)
|
|
).toBe(true);
|
|
expect(geometry.images.every((image) => image.objectFit === 'fill')).toBe(true);
|
|
const fallbackImages = page.locator(
|
|
'.card-holder .portrait img[data-general-icon-fallback-source]'
|
|
);
|
|
await expect(fallbackImages).not.toHaveCount(0);
|
|
expect(assetTracker.userIconRequests).toBe(await fallbackImages.count());
|
|
expect(Math.abs(geometry.selectionBody.y - 130.9375)).toBeLessThan(0.6);
|
|
expect(Math.abs(geometry.createSection.height - 87.375)).toBeLessThan(0.6);
|
|
expect(geometry.firstButton.height).toBe(19);
|
|
expect(geometry.footerBanner.height).toBeCloseTo(20.1875, 3);
|
|
await expect(page.locator('.invitation-table tbody tr')).toHaveCount(0);
|
|
await expect(page.locator('.footer-banner')).toContainText(
|
|
'삼국지 모의전투 HiDCHe core2026'
|
|
);
|
|
await expect(page.locator('.footer-banner a')).toHaveText('Credit');
|
|
|
|
const firstTrait = page.locator('.card-holder .trait-tooltip').first();
|
|
await firstTrait.hover();
|
|
await expect(firstTrait.getByRole('tooltip')).toBeVisible();
|
|
await page.screenshot({
|
|
path: testInfo.outputPath('select-general-desktop-hover.png'),
|
|
fullPage: true,
|
|
});
|
|
|
|
const firstButton = page.locator('.card-holder .select-button').first();
|
|
await firstButton.focus();
|
|
await expect(firstButton).toHaveCSS('outline-style', 'auto');
|
|
await expect(firstButton).toHaveCSS('outline-width', '1px');
|
|
await firstButton.hover();
|
|
await expect(firstButton).toHaveCSS('background-color', 'rgb(25, 25, 25)');
|
|
|
|
await page.setViewportSize({ width: 500, height: 900 });
|
|
await expect
|
|
.poll(() => page.evaluate(() => document.documentElement.scrollWidth))
|
|
.toBeGreaterThanOrEqual(1008);
|
|
await page.screenshot({
|
|
path: testInfo.outputPath('select-general-mobile.png'),
|
|
fullPage: true,
|
|
});
|
|
|
|
const validText = await page.locator('.selection-body small span').textContent();
|
|
expect(validText).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
|
|
const displayedExpiry = new Date(`${validText!.replace(' ', 'T')}+09:00`).getTime();
|
|
const delta = Math.max(displayedExpiry - Date.now(), 0);
|
|
await page.clock.fastForward(delta);
|
|
await expect(page.locator('.expired-text')).toHaveCount(0);
|
|
await page.clock.fastForward(2_000);
|
|
await expect(page.locator('.expired-text')).toHaveText('- 만료 -');
|
|
});
|
|
|
|
test('shuffles non-neutral invitation nations with Ref-style row content and colors', async ({
|
|
page,
|
|
}) => {
|
|
await db.nation.deleteMany({ where: { id: { in: fixtureNationIds } } });
|
|
await db.nation.createMany({
|
|
data: [
|
|
{
|
|
id: fixtureNationIds[0]!,
|
|
name: '테스트국A',
|
|
color: '#330000',
|
|
level: 1,
|
|
meta: { infoText: 'A 권유' },
|
|
},
|
|
{
|
|
id: fixtureNationIds[1]!,
|
|
name: '테스트국B',
|
|
color: '#FFFF00',
|
|
level: 1,
|
|
meta: { infoText: 'B 권유' },
|
|
},
|
|
{
|
|
id: fixtureNationIds[2]!,
|
|
name: '테스트국C',
|
|
color: '#000080',
|
|
level: 1,
|
|
meta: { infoText: 'C 권유' },
|
|
},
|
|
],
|
|
});
|
|
try {
|
|
await installSession(page);
|
|
await page.setViewportSize({ width: 1200, height: 900 });
|
|
await page.goto('select-general');
|
|
await waitForPool(page);
|
|
|
|
const rows = page.locator('.invitation-table tbody tr');
|
|
await expect(rows.locator('.invitation-nation')).toHaveText([
|
|
'테스트국C',
|
|
'테스트국A',
|
|
'테스트국B',
|
|
]);
|
|
await expect(rows.locator('.invitation-message')).toHaveText([
|
|
'C 권유',
|
|
'A 권유',
|
|
'B 권유',
|
|
]);
|
|
await expect(rows.nth(0)).toHaveCSS('background-color', 'rgb(0, 0, 128)');
|
|
await expect(rows.nth(1)).toHaveCSS('background-color', 'rgb(51, 0, 0)');
|
|
await expect(rows.nth(2)).toHaveCSS('background-color', 'rgb(255, 255, 0)');
|
|
await expect(rows.nth(0)).toHaveCSS('color', 'rgb(255, 255, 255)');
|
|
await expect(rows.nth(2)).toHaveCSS('color', 'rgb(0, 0, 0)');
|
|
const invitationGeometry = await page.locator('.invitation-table').evaluate((table) => {
|
|
const rect = table.getBoundingClientRect();
|
|
return { x: rect.x, width: rect.width };
|
|
});
|
|
expect(invitationGeometry).toEqual({ x: 100, width: 1000 });
|
|
} finally {
|
|
await db.nation.deleteMany({ where: { id: { in: fixtureNationIds } } });
|
|
}
|
|
});
|
|
|
|
test('creates, rejects cooldown, exposes MyPage action, and reselects through the live API', async ({
|
|
page,
|
|
}) => {
|
|
const dialogs: string[] = [];
|
|
const createClientRequestIds: string[] = [];
|
|
let injectCreateTimeout = true;
|
|
page.on('dialog', async (dialog) => {
|
|
dialogs.push(dialog.message());
|
|
await dialog.accept();
|
|
});
|
|
await page.route('**/trpc/join.selectPoolGeneral?batch=1', async (route) => {
|
|
const findClientRequestId = (value: unknown): string | undefined => {
|
|
if (!value || typeof value !== 'object') return undefined;
|
|
if (
|
|
'clientRequestId' in value &&
|
|
typeof value.clientRequestId === 'string'
|
|
) {
|
|
return value.clientRequestId;
|
|
}
|
|
for (const nested of Object.values(value)) {
|
|
const found = findClientRequestId(nested);
|
|
if (found) return found;
|
|
}
|
|
return undefined;
|
|
};
|
|
const clientRequestId = findClientRequestId(route.request().postDataJSON());
|
|
expect(clientRequestId).toMatch(/^[0-9a-f-]{36}$/);
|
|
createClientRequestIds.push(clientRequestId!);
|
|
if (!injectCreateTimeout) {
|
|
await route.continue();
|
|
return;
|
|
}
|
|
injectCreateTimeout = false;
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify([
|
|
{
|
|
error: {
|
|
message:
|
|
'장수 선택 요청은 접수됐지만 처리 결과를 아직 확인하지 못했습니다. 같은 요청으로 다시 시도해 주세요.',
|
|
code: -32008,
|
|
data: {
|
|
code: 'TIMEOUT',
|
|
httpStatus: 408,
|
|
path: 'join.selectPoolGeneral',
|
|
},
|
|
},
|
|
},
|
|
]),
|
|
});
|
|
});
|
|
const assetTracker: AssetTracker = { userIconRequests: 0 };
|
|
await installSession(page, assetTracker);
|
|
await page.setViewportSize({ width: 1200, height: 900 });
|
|
await page.goto('select-general');
|
|
await waitForPool(page);
|
|
|
|
const candidateCards = page.locator('.card-holder > .general-card');
|
|
const fallbackCard = candidateCards
|
|
.filter({ has: page.locator('img[data-general-icon-fallback-source]') })
|
|
.first();
|
|
await expect(fallbackCard).toBeVisible();
|
|
const initialName = await fallbackCard.locator('h4').first().textContent();
|
|
const userIconRequestsBeforePreview = assetTracker.userIconRequests;
|
|
await fallbackCard.locator('.select-button').click();
|
|
await expect(page.locator('.selected-card')).toHaveCount(1);
|
|
await expect(
|
|
page.locator('.selected-card img[data-general-icon-fallback-source]')
|
|
).toBeVisible();
|
|
expect(assetTracker.userIconRequests).toBeGreaterThan(userIconRequestsBeforePreview);
|
|
await page.locator('.custom-form select').selectOption('che_안전');
|
|
await page.getByRole('button', { name: '다시입력' }).click();
|
|
await expect(page.locator('.custom-form select')).toHaveValue('Random');
|
|
await expect(page.locator('.selected-card')).toHaveCount(1);
|
|
await page.locator('.custom-form select').selectOption('che_안전');
|
|
await page.locator('#build-general').click();
|
|
await expect.poll(() => dialogs).toContain(
|
|
'실패했습니다: 장수 선택 요청은 접수됐지만 처리 결과를 아직 확인하지 못했습니다. 같은 요청으로 다시 시도해 주세요.'
|
|
);
|
|
await waitForPool(page);
|
|
const retryCard = page
|
|
.locator('.card-holder > .general-card')
|
|
.filter({ has: page.locator('h4', { hasText: initialName?.trim() ?? '' }) })
|
|
.first();
|
|
await retryCard.locator('.select-button').click();
|
|
await page.locator('.custom-form select').selectOption('che_안전');
|
|
await page.locator('#build-general').click();
|
|
await expect(page).toHaveURL(/\/hwe\/$/);
|
|
expect(dialogs.filter((message) => message === '이 장수로 생성할까요?')).toHaveLength(2);
|
|
await expect.poll(() => dialogs).toContain('선택한 장수로 생성했습니다.');
|
|
expect(createClientRequestIds).toHaveLength(2);
|
|
expect(createClientRequestIds[1]).toBe(createClientRequestIds[0]);
|
|
|
|
const created = await db.general.findFirstOrThrow({ where: { userId } });
|
|
expect(created.name).toBe(initialName?.trim());
|
|
expect(created.personalCode).toBe('che_안전');
|
|
expect(created.specialCode).toMatch(/^che_event_/);
|
|
const createEvent = await db.inputEvent.findFirstOrThrow({
|
|
where: { actorUserId: userId, eventType: 'selectPoolCreate' },
|
|
orderBy: { sequence: 'desc' },
|
|
});
|
|
expect(createEvent).toMatchObject({ status: 'SUCCEEDED', attempts: 1 });
|
|
expect(createEvent.requestId).toMatch(
|
|
new RegExp(`^select-pool:${userId}:[0-9a-f-]{36}:create$`)
|
|
);
|
|
expect(
|
|
await page.evaluate(() =>
|
|
window.sessionStorage.getItem('sammo-select-pool-pending-action')
|
|
)
|
|
).toBeNull();
|
|
|
|
await page.goto('my-page');
|
|
const actionLink = page.locator('.select-general-link');
|
|
await expect(actionLink).toBeVisible();
|
|
await expect(actionLink.locator('..')).toContainText(
|
|
/다른 장수 선택\s*\(\d{4}-\d{2}-\d{2}/
|
|
);
|
|
await expect(actionLink).toHaveCSS('width', '160px');
|
|
await expect(actionLink).toHaveCSS('height', '30px');
|
|
|
|
dialogs.length = 0;
|
|
await page.goto('select-general');
|
|
await expect.poll(() => dialogs).toContain('실패했습니다: 아직 다시 고를 수 없습니다');
|
|
await expect(page.locator('.error-text')).toHaveText('아직 다시 고를 수 없습니다');
|
|
|
|
const availableAt = '2026-07-29T00:00:00.000Z';
|
|
const cooldownRequestId = `select-pool-live-cooldown-${randomUUID()}`;
|
|
await db.inputEvent.create({
|
|
data: {
|
|
requestId: cooldownRequestId,
|
|
target: 'ENGINE',
|
|
eventType: 'patchGeneral',
|
|
payload: {
|
|
type: 'patchGeneral',
|
|
requestId: cooldownRequestId,
|
|
generalId: created.id,
|
|
patch: {
|
|
meta: {
|
|
next_change: availableAt,
|
|
nextChangeAt: availableAt,
|
|
},
|
|
},
|
|
} as GamePrisma.InputJsonValue,
|
|
},
|
|
});
|
|
await expect
|
|
.poll(
|
|
async () =>
|
|
(
|
|
await db.inputEvent.findUniqueOrThrow({
|
|
where: { requestId: cooldownRequestId },
|
|
})
|
|
).status
|
|
)
|
|
.toBe('SUCCEEDED');
|
|
|
|
dialogs.length = 0;
|
|
await page.reload();
|
|
await waitForPool(page);
|
|
const cards = page.locator('.card-holder > .general-card');
|
|
const names = await cards.locator('h4').allTextContents();
|
|
const targetIndex = names.findIndex((name) => name.trim() !== created.name);
|
|
expect(targetIndex).toBeGreaterThanOrEqual(0);
|
|
const targetName = names[targetIndex]!.trim();
|
|
await cards.nth(targetIndex).locator('.select-button').click();
|
|
await expect(page).toHaveURL(/\/hwe\/$/);
|
|
await expect.poll(() => dialogs).toContain(`이 장수를 선택할까요? : ${targetName}`);
|
|
await expect.poll(() => dialogs).toContain('선택한 장수로 변경했습니다.');
|
|
|
|
await expect
|
|
.poll(async () => (await db.general.findUniqueOrThrow({ where: { id: created.id } })).name)
|
|
.toBe(targetName);
|
|
const reselectEvent = await db.inputEvent.findFirstOrThrow({
|
|
where: { actorUserId: userId, eventType: 'selectPoolReselect' },
|
|
orderBy: { sequence: 'desc' },
|
|
});
|
|
expect(reselectEvent).toMatchObject({ status: 'SUCCEEDED', attempts: 1 });
|
|
expect(reselectEvent.requestId).toMatch(
|
|
new RegExp(`^select-pool:${userId}:[0-9a-f-]{36}:reselect$`)
|
|
);
|
|
expect(
|
|
await page.evaluate(() =>
|
|
window.sessionStorage.getItem('sammo-select-pool-pending-action')
|
|
)
|
|
).toBeNull();
|
|
});
|
|
});
|