feat: streamline victory debrief flow
This commit is contained in:
@@ -2400,6 +2400,10 @@ try {
|
||||
});
|
||||
assert(resultProbe.outcome === 'victory', `Expected forced victory result: ${JSON.stringify(resultProbe)}`);
|
||||
assert(resultProbe.resultTexts.includes('목표 정산'), `Expected result objective settlement title: ${JSON.stringify(resultProbe.resultTexts)}`);
|
||||
assert(
|
||||
['소요 턴', '생존 아군', '격파 적군', '달성 목표'].every((label) => resultProbe.resultTexts.includes(label)),
|
||||
`Expected the result summary to expose four exact debrief metrics ending with achieved objectives: ${JSON.stringify(resultProbe.resultTexts)}`
|
||||
);
|
||||
assert(
|
||||
['전투 수입', '최고 전공', '획득 물자', '새 전력 · 해금'].every((label) => resultProbe.resultTexts.includes(label)),
|
||||
`Expected the result reward panel to expose four scannable reward cards: ${JSON.stringify(resultProbe.resultTexts)}`
|
||||
@@ -2428,6 +2432,12 @@ try {
|
||||
`Expected the completed victory result to expose one correctly named story CTA: ${JSON.stringify(firstResultState?.resultActions)}`
|
||||
);
|
||||
const firstResultSave = await readCampaignSave(page);
|
||||
const persistedFirstRewards = firstResultSave.current?.firstBattleReport?.campaignRewards;
|
||||
assertBattleResultDebriefText(
|
||||
resultProbe.resultTexts,
|
||||
persistedFirstRewards,
|
||||
'first battle'
|
||||
);
|
||||
assert(
|
||||
firstResultSave.current?.pendingAftermathBattleId === 'first-battle-zhuo-commandery' &&
|
||||
firstResultSave.slot1?.pendingAftermathBattleId === 'first-battle-zhuo-commandery',
|
||||
@@ -2687,8 +2697,26 @@ try {
|
||||
firstCampSaveAfterAftermath.slot1?.pendingAftermathBattleId === undefined,
|
||||
`Expected CampScene entry to complete the persisted aftermath exactly once: ${JSON.stringify(firstCampSaveAfterAftermath)}`
|
||||
);
|
||||
const firstRewardDisplaysByPage = Object.fromEntries(
|
||||
firstCampTransition.rewardDisplays.map((display) => [display.pageId, display])
|
||||
const firstVictoryStoryPageIds = [
|
||||
'first-victory-village',
|
||||
'first-victory-liu-bei',
|
||||
'first-victory-guan-yu',
|
||||
'first-victory-zhang-fei',
|
||||
'first-victory-next'
|
||||
];
|
||||
assert(
|
||||
sameJsonValue(
|
||||
firstCampTransition.rewardDisplays.map((display) => display.pageId),
|
||||
firstVictoryStoryPageIds
|
||||
) &&
|
||||
firstCampTransition.rewardDisplays.every((display) => (
|
||||
display.source === 'campaign' &&
|
||||
display.battleId === 'first-battle-zhuo-commandery' &&
|
||||
display.rewardGold === null &&
|
||||
Array.isArray(display.items) &&
|
||||
display.items.length === 0
|
||||
)),
|
||||
`Expected all five first-victory aftermath pages to retain campaign context without duplicating exact rewards: ${JSON.stringify(firstCampTransition.rewardDisplays)}`
|
||||
);
|
||||
const firstNarrativeMemoriesByPage = Object.fromEntries(
|
||||
firstCampTransition.narrativeMemories.map((memory) => [memory.pageId, memory])
|
||||
@@ -2714,49 +2742,9 @@ try {
|
||||
),
|
||||
`Expected Guan Yu and Zhang Fei to remember the persisted first command in distinct voices: ${JSON.stringify(firstCampTransition.narrativeMemories)}`
|
||||
);
|
||||
const persistedFirstRewards = firstResultSave.current?.firstBattleReport?.campaignRewards;
|
||||
const expectedFirstStorySupplies = [
|
||||
...(persistedFirstRewards?.supplies ?? []),
|
||||
...firstSortieOrderBonus.rewardItems
|
||||
];
|
||||
assert(
|
||||
firstCampTransition.rewardDisplays.length === 5 &&
|
||||
firstCampTransition.rewardDisplays.every((display) => (
|
||||
display.source === 'campaign' && display.battleId === 'first-battle-zhuo-commandery'
|
||||
)) &&
|
||||
firstRewardDisplaysByPage['first-victory-village']?.rewardGold === expectedFirstDisplayedRewardGold &&
|
||||
sameJsonValue(
|
||||
firstRewardDisplaysByPage['first-victory-village']?.items.filter((item) => item.category === 'supply').map((item) => item.label),
|
||||
expectedFirstStorySupplies
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstRewardDisplaysByPage['first-victory-liu-bei']?.items.filter((item) => item.category === 'equipment').map((item) => item.label),
|
||||
persistedFirstRewards?.equipment
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstRewardDisplaysByPage['first-victory-liu-bei']?.items.filter((item) => item.category === 'reputation').map((item) => item.label),
|
||||
persistedFirstRewards?.reputation
|
||||
) &&
|
||||
sameJsonValue(
|
||||
[
|
||||
...(firstRewardDisplaysByPage['first-victory-guan-yu']?.items ?? []),
|
||||
...(firstRewardDisplaysByPage['first-victory-zhang-fei']?.items ?? [])
|
||||
].filter((item) => item.category === 'recruit').map((item) => item.id.replace('recruit-', '')),
|
||||
persistedFirstRewards?.recruits.map((recruit) => recruit.unitId)
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstRewardDisplaysByPage['first-victory-next']?.items.filter((item) => item.category === 'unlock').map((item) => item.id.replace('unlock-', '')),
|
||||
persistedFirstRewards?.unlocks.map((unlock) => unlock.battleId)
|
||||
),
|
||||
`Expected every first-victory story reward card to match the persisted battle report: ${JSON.stringify({ displays: firstCampTransition.rewardDisplays, persistedFirstRewards })}`
|
||||
);
|
||||
await waitForCampSkinTransition(page, 'yellow-turban');
|
||||
await waitForCampSoundscapeTransition(page, 'camp-rally', 'campfire-ambience');
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement?.visible === true,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
await waitForCampVictoryRewardLedgerReady(page, 'first-battle-zhuo-commandery');
|
||||
await page.screenshot({ path: `${screenshotDir}/rc-first-camp.png`, fullPage: true });
|
||||
await assertCanvasPainted(page, 'first camp');
|
||||
|
||||
@@ -2773,32 +2761,52 @@ try {
|
||||
}
|
||||
});
|
||||
assert(firstCampProbe.state?.campaign?.step === 'first-camp', `Expected victory to return to first camp: ${JSON.stringify(firstCampProbe.state)}`);
|
||||
const firstArrivalReward = firstCampProbe.state?.victoryRewardAcknowledgement;
|
||||
const firstArrival = firstCampProbe.state?.victorySettlementArrival;
|
||||
const firstArrivalLedger = firstCampProbe.state?.victoryRewardAcknowledgement;
|
||||
const firstArrivalSaveBeforeLedger = await readCampaignSave(page);
|
||||
const firstArrivalAcknowledgements = campaignVictoryAcknowledgementSnapshot(
|
||||
firstArrivalSaveBeforeLedger,
|
||||
'first-battle-zhuo-commandery'
|
||||
);
|
||||
const firstArrivalNoticeDismissals = campaignVictoryNoticeDismissalSnapshot(
|
||||
firstArrivalSaveBeforeLedger,
|
||||
'first-battle-zhuo-commandery'
|
||||
);
|
||||
assert(
|
||||
firstArrivalReward?.visible === true &&
|
||||
firstArrivalReward.battleId === 'first-battle-zhuo-commandery' &&
|
||||
firstArrivalReward.cards.length === 6 &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'gold')?.items.includes(`+${expectedFirstDisplayedRewardGold} 군자금`) &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'supplies')?.items.includes('상처약 1') &&
|
||||
firstArrivalReward.cards.every((card) => card.iconReady === true) &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'equipment')?.iconKind === 'item' &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'equipment')?.new === true &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'equipment')?.actionId === 'equipment' &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'equipment')?.interactive === true &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'recruits')?.iconKind === 'portrait' &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'recruits')?.new === true &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'recruits')?.actionId === 'sortie' &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'unlocks')?.new === true &&
|
||||
firstArrivalReward.cards.find((card) => card.id === 'unlocks')?.actionId === 'sortie' &&
|
||||
firstArrivalReward.cards.every((card) => boundsWithinFhdViewport(card.bounds)) &&
|
||||
firstArrivalReward.actions.map((action) => action.id).join(',') === 'equipment,supplies,guided,sortie,close' &&
|
||||
firstArrivalReward.actions.filter((action) => action.primary).length === 1 &&
|
||||
firstArrivalReward.actions.find((action) => action.id === 'guided')?.label === '북문 정찰막 둘러보기' &&
|
||||
firstArrivalReward.actions.find((action) => action.id === 'guided')?.primary === true &&
|
||||
firstArrivalReward.actions.find((action) => action.id === 'sortie')?.label === '바로 출진' &&
|
||||
firstArrivalReward.actions.find((action) => action.id === 'sortie')?.primary === false &&
|
||||
firstArrivalReward.actions.every((action) => action.interactive && boundsWithinFhdViewport(action.bounds)) &&
|
||||
firstCampProbe.state?.sortieCommand?.label === '정찰막 둘러보기' &&
|
||||
firstArrival?.battleId === 'first-battle-zhuo-commandery' &&
|
||||
firstArrival.battleTitle === persistedFirstReport?.battleTitle &&
|
||||
typeof firstArrival.message === 'string' &&
|
||||
firstArrival.message.includes(firstArrival.battleTitle) &&
|
||||
firstArrival.message.includes('보상 장부') &&
|
||||
sameJsonValue(firstArrival.settledBattleIds, ['first-battle-zhuo-commandery']) &&
|
||||
sameJsonValue(firstArrival.pendingCategories, ['equipment', 'supplies', 'recruits', 'unlocks']) &&
|
||||
sameJsonValue(firstArrival.passiveCategoriesAcknowledged, ['gold', 'reputation']) &&
|
||||
firstArrival.blocking === false &&
|
||||
firstArrivalNoticeDismissals.current === true &&
|
||||
firstArrivalNoticeDismissals.slot1 === true &&
|
||||
['current', 'slot1'].every((saveKey) => (
|
||||
firstArrivalAcknowledgements[saveKey].battleAcknowledged === false &&
|
||||
sameJsonValue(firstArrivalAcknowledgements[saveKey].categories, ['gold', 'reputation'])
|
||||
)),
|
||||
`Expected first-camp arrival to settle the notice nonblockingly, acknowledge only passive rewards, and leave destination rewards NEW: ${JSON.stringify({
|
||||
arrival: firstArrival,
|
||||
acknowledgements: firstArrivalAcknowledgements,
|
||||
noticeDismissals: firstArrivalNoticeDismissals
|
||||
})}`
|
||||
);
|
||||
assert(
|
||||
firstArrivalLedger?.role === 'on-demand-ledger' &&
|
||||
firstArrivalLedger.available === true &&
|
||||
firstArrivalLedger.visible === false &&
|
||||
firstArrivalLedger.battleId === 'first-battle-zhuo-commandery' &&
|
||||
boundsWithinFhdViewport(firstArrivalLedger.toggleButtonBounds) &&
|
||||
firstArrivalLedger.toggleInteractive === true &&
|
||||
firstArrivalLedger.cards.length === 0 &&
|
||||
firstArrivalLedger.actions.length === 0,
|
||||
`Expected first-camp rewards to remain closed behind one FHD-safe on-demand ledger button: ${JSON.stringify(firstArrivalLedger)}`
|
||||
);
|
||||
assert(
|
||||
firstCampProbe.state?.sortieCommand?.label === '정찰막 둘러보기' &&
|
||||
firstCampProbe.state?.sortieCommand?.guidedKind === 'visit' &&
|
||||
firstCampProbe.state?.sortieCommand?.targetId === firstPursuitScoutVisitId &&
|
||||
firstCampProbe.state?.sortieCommand?.interactive === true &&
|
||||
@@ -2819,8 +2827,7 @@ try {
|
||||
firstCampProbe.state?.campTabs?.find((tab) => tab.id === 'visit')?.visualState === 'active' &&
|
||||
firstCampProbe.state?.campTabs?.find((tab) => tab.id === 'visit')?.newBadgeVisible === true &&
|
||||
firstCampProbe.state?.campTabs?.filter((tab) => ['supplies', 'equipment'].includes(tab.id)).every((tab) => tab.newBadgeVisible === true),
|
||||
`Expected first camp arrival to expose bounded reward cards, deep links, and NEW badges: ${JSON.stringify({
|
||||
reward: firstArrivalReward,
|
||||
`Expected first camp arrival to preserve its guided next action, recruit history, and destination NEW badges without a blocking modal: ${JSON.stringify({
|
||||
sortieCommand: firstCampProbe.state?.sortieCommand,
|
||||
activeTab: firstCampProbe.state?.activeTab,
|
||||
selectedVisitId: firstCampProbe.state?.selectedVisitId,
|
||||
@@ -2828,6 +2835,79 @@ try {
|
||||
sortieRoster: firstCampProbe.state?.sortieRoster
|
||||
})}`
|
||||
);
|
||||
const firstArrivalLedgerTogglePoint = await readCampVictoryRewardLedgerTogglePoint(page);
|
||||
await page.mouse.click(firstArrivalLedgerTogglePoint.x, firstArrivalLedgerTogglePoint.y);
|
||||
await page.waitForFunction(() => {
|
||||
const ledger = window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement;
|
||||
return ledger?.visible === true && ledger.cards?.length === 6 && ledger.actions?.length === 4;
|
||||
}, undefined, { timeout: 30000 });
|
||||
const firstArrivalReward = await page.evaluate(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement
|
||||
);
|
||||
const firstArrivalCardsById = Object.fromEntries(
|
||||
firstArrivalReward.cards.map((card) => [card.id, card])
|
||||
);
|
||||
const expectedFirstLedgerSupplies = [
|
||||
...(persistedFirstRewards?.supplies ?? []),
|
||||
...firstSortieOrderBonus.rewardItems
|
||||
];
|
||||
assert(
|
||||
firstArrivalReward.role === 'on-demand-ledger' &&
|
||||
firstArrivalReward.available === true &&
|
||||
firstArrivalReward.visible === true &&
|
||||
firstArrivalReward.battleId === 'first-battle-zhuo-commandery' &&
|
||||
sameJsonValue(
|
||||
firstArrivalReward.cards.map((card) => card.id),
|
||||
['gold', 'equipment', 'supplies', 'reputation', 'recruits', 'unlocks']
|
||||
) &&
|
||||
firstArrivalCardsById.gold?.items[0] === `+${expectedFirstDisplayedRewardGold} 군자금` &&
|
||||
(
|
||||
firstSortieOrderBonus.rewardGold === 0 ||
|
||||
firstArrivalCardsById.gold?.items[1]?.includes(`+${firstSortieOrderBonus.rewardGold}`)
|
||||
) &&
|
||||
sameJsonValue(firstArrivalCardsById.equipment?.items, persistedFirstRewards?.equipment ?? []) &&
|
||||
sameJsonValue(firstArrivalCardsById.supplies?.items, expectedFirstLedgerSupplies) &&
|
||||
sameJsonValue(firstArrivalCardsById.reputation?.items, persistedFirstRewards?.reputation ?? []) &&
|
||||
sameJsonValue(
|
||||
firstArrivalCardsById.recruits?.items,
|
||||
(persistedFirstRewards?.recruits ?? []).map((recruit) => recruit.name)
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstArrivalCardsById.unlocks?.items,
|
||||
(persistedFirstRewards?.unlocks ?? []).map((unlock) => unlock.title)
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstArrivalReward.cards.map((card) => card.iconKind),
|
||||
['mark', 'item', 'battle-ui', 'mark', 'portrait', 'battle-ui']
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstArrivalReward.cards.filter((card) => card.new).map((card) => card.id),
|
||||
['equipment', 'supplies', 'recruits', 'unlocks']
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstArrivalReward.cards.map((card) => ({ id: card.id, actionId: card.actionId })),
|
||||
[
|
||||
{ id: 'gold', actionId: null },
|
||||
{ id: 'equipment', actionId: 'equipment' },
|
||||
{ id: 'supplies', actionId: 'supplies' },
|
||||
{ id: 'reputation', actionId: null },
|
||||
{ id: 'recruits', actionId: 'sortie' },
|
||||
{ id: 'unlocks', actionId: 'sortie' }
|
||||
]
|
||||
) &&
|
||||
firstArrivalReward.cards.every((card) => (
|
||||
card.iconReady === true &&
|
||||
card.interactive === ['equipment', 'supplies', 'recruits', 'unlocks'].includes(card.id) &&
|
||||
boundsWithinFhdViewport(card.bounds)
|
||||
)) &&
|
||||
sameJsonValue(
|
||||
firstArrivalReward.actions.map((action) => action.id),
|
||||
['equipment', 'supplies', 'sortie', 'close']
|
||||
) &&
|
||||
firstArrivalReward.actions.filter((action) => action.primary).map((action) => action.id).join(',') === 'close' &&
|
||||
firstArrivalReward.actions.every((action) => action.interactive && boundsWithinFhdViewport(action.bounds)),
|
||||
`Expected the requested first-camp ledger to expose six exact reward cards and four actions with close as the sole primary: ${JSON.stringify(firstArrivalReward)}`
|
||||
);
|
||||
const firstArrivalEquipmentPoint = await readCampVictoryRewardCardControlPoint(page, 'equipment');
|
||||
await page.mouse.click(firstArrivalEquipmentPoint.x, firstArrivalEquipmentPoint.y);
|
||||
await page.waitForFunction(() => {
|
||||
@@ -2836,23 +2916,67 @@ try {
|
||||
}, undefined, { timeout: 30000 });
|
||||
const acknowledgedFirstArrival = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const acknowledgedFirstArrivalSave = await readCampaignSave(page);
|
||||
const acknowledgedFirstArrivalCategories = campaignVictoryAcknowledgementSnapshot(
|
||||
acknowledgedFirstArrivalSave,
|
||||
'first-battle-zhuo-commandery'
|
||||
);
|
||||
assert(
|
||||
!acknowledgedFirstArrivalSave.current?.acknowledgedVictoryRewardBattleIds?.includes('first-battle-zhuo-commandery') &&
|
||||
!acknowledgedFirstArrivalSave.slot1?.acknowledgedVictoryRewardBattleIds?.includes('first-battle-zhuo-commandery') &&
|
||||
acknowledgedFirstArrivalSave.current?.acknowledgedVictoryRewardCategories?.['first-battle-zhuo-commandery']?.includes('equipment') &&
|
||||
acknowledgedFirstArrivalSave.slot1?.acknowledgedVictoryRewardCategories?.['first-battle-zhuo-commandery']?.includes('equipment') &&
|
||||
sameJsonValue(
|
||||
campaignSaveWithoutVictoryRewardAcknowledgementEffects(acknowledgedFirstArrivalSave),
|
||||
campaignSaveWithoutVictoryRewardAcknowledgementEffects(firstArrivalSaveBeforeLedger)
|
||||
) &&
|
||||
['current', 'slot1'].every((saveKey) => (
|
||||
acknowledgedFirstArrivalCategories[saveKey].battleAcknowledged === false &&
|
||||
sameJsonValue(
|
||||
acknowledgedFirstArrivalCategories[saveKey].categories,
|
||||
['gold', 'equipment', 'reputation']
|
||||
)
|
||||
)) &&
|
||||
campaignVictoryNoticeDismissalSnapshot(
|
||||
acknowledgedFirstArrivalSave,
|
||||
'first-battle-zhuo-commandery'
|
||||
).current === true &&
|
||||
campaignVictoryNoticeDismissalSnapshot(
|
||||
acknowledgedFirstArrivalSave,
|
||||
'first-battle-zhuo-commandery'
|
||||
).slot1 === true &&
|
||||
acknowledgedFirstArrival.campTabs.find((tab) => tab.id === 'equipment')?.newBadgeVisible === false &&
|
||||
acknowledgedFirstArrival.campTabs.find((tab) => tab.id === 'supplies')?.newBadgeVisible === true,
|
||||
`Expected an equipment deep link to acknowledge only equipment while preserving other reward NEW states: ${JSON.stringify({ acknowledgedFirstArrival, acknowledgedFirstArrivalSave })}`
|
||||
`Expected an equipment ledger deep link to add only equipment acknowledgement while preserving passive settlement, other NEW states, and all unrelated save data: ${JSON.stringify({
|
||||
before: firstArrivalAcknowledgements,
|
||||
after: acknowledgedFirstArrivalCategories,
|
||||
state: acknowledgedFirstArrival
|
||||
})}`
|
||||
);
|
||||
await clickLegacyUi(page, 576, 38);
|
||||
await page.waitForFunction(() => window.__HEROS_DEBUG__?.camp()?.activeTab === 'status', undefined, { timeout: 30000 });
|
||||
firstCampProbe = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
const reportBounds = scene?.reportPanelBackground?.getBounds?.();
|
||||
const texts = textValues(scene?.contentObjects);
|
||||
return {
|
||||
state: window.__HEROS_DEBUG__?.camp(),
|
||||
summary: scene?.reportSummary?.(),
|
||||
texts: textValues(scene?.contentObjects)
|
||||
texts,
|
||||
reportTexts: reportBounds
|
||||
? (scene?.contentObjects ?? [])
|
||||
.filter((object) => object?.type === 'Text')
|
||||
.filter((object) => {
|
||||
const bounds = object.getBounds?.();
|
||||
if (!bounds) {
|
||||
return false;
|
||||
}
|
||||
const centerX = bounds.x + bounds.width / 2;
|
||||
const centerY = bounds.y + bounds.height / 2;
|
||||
return (
|
||||
centerX >= reportBounds.x &&
|
||||
centerX <= reportBounds.x + reportBounds.width &&
|
||||
centerY >= reportBounds.y &&
|
||||
centerY <= reportBounds.y + reportBounds.height
|
||||
);
|
||||
})
|
||||
.map((object) => object.text)
|
||||
: []
|
||||
};
|
||||
|
||||
function textValues(objects = []) {
|
||||
@@ -2909,17 +3033,33 @@ try {
|
||||
`Expected first camp deployment preview to avoid duplicate tiles: ${JSON.stringify(firstCampProbe.state?.sortieDeploymentPreview)}`
|
||||
);
|
||||
assert(
|
||||
firstCampProbe.summary?.includes('군자금') && !firstCampProbe.summary.includes('\n'),
|
||||
`Expected the compact camp summary to show held gold on one line: ${JSON.stringify(firstCampProbe)}`
|
||||
firstCampProbe.summary?.includes('군영 준비 중') &&
|
||||
!firstCampProbe.summary.includes('군자금') &&
|
||||
!firstCampProbe.summary.includes('\n'),
|
||||
`Expected the compact camp summary to show readiness on one line without repeating an exact reward amount: ${JSON.stringify(firstCampProbe)}`
|
||||
);
|
||||
assert(firstCampProbe.texts.some((text) => text.includes('전투 보상')), `Expected camp report to show battle reward: ${JSON.stringify(firstCampProbe.texts)}`);
|
||||
const firstExactRewardLabels = [
|
||||
`+${expectedFirstDisplayedRewardGold} 군자금`,
|
||||
...(persistedFirstRewards?.supplies ?? []),
|
||||
...firstSortieOrderBonus.rewardItems,
|
||||
...(persistedFirstRewards?.equipment ?? []),
|
||||
...(persistedFirstRewards?.reputation ?? []),
|
||||
...(persistedFirstRewards?.recruits ?? []).map((recruit) => recruit.name),
|
||||
...(persistedFirstRewards?.unlocks ?? []).map((unlock) => unlock.title)
|
||||
];
|
||||
assert(
|
||||
firstCampProbe.texts.some((text) => text.includes('명령 기록') && text.includes('재정비')),
|
||||
`Expected the camp report to show the persisted command record: ${JSON.stringify(firstCampProbe.texts)}`
|
||||
firstCampProbe.reportTexts.some((text) => text.includes('세부 보상과 전과는 보상 장부에서 확인')) &&
|
||||
firstExactRewardLabels.every(
|
||||
(rewardLabel) => !firstCampProbe.reportTexts.some((text) => text.includes(rewardLabel))
|
||||
),
|
||||
`Expected the always-on camp report to route exact rewards to the ledger without repeating names, titles, items, or amounts: ${JSON.stringify({
|
||||
reportTexts: firstCampProbe.reportTexts,
|
||||
hiddenRewardLabels: firstExactRewardLabels
|
||||
})}`
|
||||
);
|
||||
assert(
|
||||
firstCampProbe.texts.some((text) => text.includes(`다음 ${firstBattleUnlockLabel}`)),
|
||||
`Expected camp report to surface first battle unlock label: ${JSON.stringify(firstCampProbe.texts)}`
|
||||
firstCampProbe.reportTexts.some((text) => text.includes('명령 기록') && text.includes('재정비')),
|
||||
`Expected the camp report to show the persisted command record: ${JSON.stringify(firstCampProbe.reportTexts)}`
|
||||
);
|
||||
assert(
|
||||
firstCampProbe.state?.report?.campaignRewards?.unlocks?.some(
|
||||
@@ -3079,15 +3219,22 @@ try {
|
||||
undefined,
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
await page.waitForTimeout(250);
|
||||
if (await page.evaluate(() => window.__HEROS_DEBUG__?.camp?.()?.victoryRewardAcknowledgement?.visible === true)) {
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp?.()?.victoryRewardAcknowledgement?.visible === false,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
await page.waitForFunction(() => {
|
||||
const ledger = window.__HEROS_DEBUG__?.camp?.()?.victoryRewardAcknowledgement;
|
||||
return (
|
||||
ledger?.role === 'on-demand-ledger' &&
|
||||
ledger.available === true &&
|
||||
ledger.visible === false &&
|
||||
ledger.toggleInteractive === true
|
||||
);
|
||||
}
|
||||
}, undefined, { timeout: 30000 });
|
||||
const returnedFirstCampLedger = await page.evaluate(
|
||||
() => window.__HEROS_DEBUG__?.camp?.()?.victoryRewardAcknowledgement
|
||||
);
|
||||
assert(
|
||||
boundsWithinFhdViewport(returnedFirstCampLedger?.toggleButtonBounds),
|
||||
`Expected returning from camp exploration to keep the reward ledger closed and available on demand: ${JSON.stringify(returnedFirstCampLedger)}`
|
||||
);
|
||||
const returnedFirstCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp?.());
|
||||
if (
|
||||
returnedFirstCampState?.activeTab !== 'visit' ||
|
||||
@@ -7356,8 +7503,22 @@ try {
|
||||
|
||||
await page.evaluate(() => window.__HEROS_DEBUG__?.forceBattleOutcome('victory'));
|
||||
await waitForBattleOutcome(page, 'victory');
|
||||
const midResultState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
|
||||
const midResultProbe = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
|
||||
return {
|
||||
state: window.__HEROS_DEBUG__?.battle(),
|
||||
texts: (scene?.resultObjects ?? [])
|
||||
.filter((object) => object?.type === 'Text')
|
||||
.map((object) => object.text)
|
||||
};
|
||||
});
|
||||
const midResultState = midResultProbe.state;
|
||||
const midResultSaveBeforeUpdate = await readCampaignSave(page);
|
||||
assertBattleResultDebriefText(
|
||||
midResultProbe.texts,
|
||||
midResultSaveBeforeUpdate.current?.firstBattleReport?.campaignRewards,
|
||||
'mid-campaign battle'
|
||||
);
|
||||
const midResultPerformance = midResultState?.sortieEvaluation?.snapshot;
|
||||
const midResultSortieReviewBeforePresetUpdate = structuredClone(
|
||||
midResultSaveBeforeUpdate.current?.battleHistory?.[midCampNextBattleId]?.sortieReview
|
||||
@@ -7581,16 +7742,16 @@ try {
|
||||
midImprovementAftermath.rewardDisplays.length === 0,
|
||||
`Expected next-sortie improvement to show only the completed battle's aftermath before camp: ${JSON.stringify(midImprovementAftermath)}`
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement?.visible === true,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
await waitForCampVictoryRewardLedgerReady(page, midCampNextBattleId);
|
||||
const pendingMidImprovementReward = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const pendingMidImprovementSave = await readCampaignSave(page);
|
||||
const midImprovementCampEntrySaveUnchanged = sameJsonValue(
|
||||
campaignSaveWithoutCampRecruitmentEffects(pendingMidImprovementSave),
|
||||
campaignSaveWithoutCampRecruitmentEffects(updatedMobileResultSave)
|
||||
campaignSaveWithoutVictoryRewardAcknowledgementEffects(
|
||||
campaignSaveWithoutCampRecruitmentEffects(pendingMidImprovementSave)
|
||||
),
|
||||
campaignSaveWithoutVictoryRewardAcknowledgementEffects(
|
||||
campaignSaveWithoutCampRecruitmentEffects(updatedMobileResultSave)
|
||||
)
|
||||
);
|
||||
const midImprovementAftermathCompleted =
|
||||
updatedMobileResultSave.current?.pendingAftermathBattleId === midCampNextBattleId &&
|
||||
@@ -7601,29 +7762,91 @@ try {
|
||||
pendingMidImprovementSave,
|
||||
midCampNextBattleId
|
||||
);
|
||||
const midImprovementNoticeDismissals = campaignVictoryNoticeDismissalSnapshot(
|
||||
pendingMidImprovementSave,
|
||||
midCampNextBattleId
|
||||
);
|
||||
const midImprovementArrival = pendingMidImprovementReward?.victorySettlementArrival;
|
||||
const midImprovementLedger = pendingMidImprovementReward?.victoryRewardAcknowledgement;
|
||||
const midImprovementRewardDestinationPending = Object.values(midImprovementRewardAcknowledgements).every(
|
||||
(entry) => !entry.battleAcknowledged &&
|
||||
!entry.categories.includes('equipment') &&
|
||||
!entry.categories.includes('supplies')
|
||||
(entry) => (
|
||||
!entry.battleAcknowledged &&
|
||||
sameJsonValue(entry.categories, midImprovementArrival?.passiveCategoriesAcknowledged ?? []) &&
|
||||
(midImprovementArrival?.pendingCategories ?? []).every(
|
||||
(category) => !entry.categories.includes(category)
|
||||
)
|
||||
)
|
||||
);
|
||||
assert(
|
||||
pendingMidImprovementReward?.openSortiePrepOnCreate === true &&
|
||||
pendingMidImprovementReward.sortieVisible === false &&
|
||||
pendingMidImprovementReward.victoryRewardAcknowledgement?.battleId === midCampNextBattleId &&
|
||||
midImprovementArrival?.battleId === midCampNextBattleId &&
|
||||
midImprovementArrival.battleTitle === pendingMidImprovementReward.report?.battleTitle &&
|
||||
typeof midImprovementArrival.message === 'string' &&
|
||||
midImprovementArrival.message.includes('보상 장부') &&
|
||||
sameJsonValue(midImprovementArrival.settledBattleIds, [midCampNextBattleId]) &&
|
||||
midImprovementArrival.passiveCategoriesAcknowledged.every(
|
||||
(category) => ['gold', 'reputation'].includes(category)
|
||||
) &&
|
||||
midImprovementArrival.pendingCategories.length > 0 &&
|
||||
midImprovementArrival.pendingCategories.every(
|
||||
(category) => !['gold', 'reputation'].includes(category)
|
||||
) &&
|
||||
midImprovementArrival.blocking === false &&
|
||||
midImprovementNoticeDismissals.current === true &&
|
||||
midImprovementNoticeDismissals.slot1 === true &&
|
||||
midImprovementLedger?.role === 'on-demand-ledger' &&
|
||||
midImprovementLedger.available === true &&
|
||||
midImprovementLedger.visible === false &&
|
||||
midImprovementLedger.battleId === midCampNextBattleId &&
|
||||
boundsWithinFhdViewport(midImprovementLedger.toggleButtonBounds) &&
|
||||
midImprovementLedger.toggleInteractive === true &&
|
||||
midImprovementLedger.cards.length === 0 &&
|
||||
midImprovementLedger.actions.length === 0 &&
|
||||
midImprovementCampEntrySaveUnchanged &&
|
||||
midImprovementAftermathCompleted &&
|
||||
midImprovementRewardDestinationPending,
|
||||
`Expected next-sortie improvement to preserve its requested prep while showing the unacknowledged victory reward first: ${JSON.stringify({
|
||||
`Expected next-sortie improvement arrival to remain nonblocking with passive settlement and an unopened on-demand ledger: ${JSON.stringify({
|
||||
openSortiePrepOnCreate: pendingMidImprovementReward?.openSortiePrepOnCreate,
|
||||
sortieVisible: pendingMidImprovementReward?.sortieVisible,
|
||||
rewardBattleId: pendingMidImprovementReward?.victoryRewardAcknowledgement?.battleId,
|
||||
arrival: midImprovementArrival,
|
||||
ledger: midImprovementLedger,
|
||||
expectedBattleId: midCampNextBattleId,
|
||||
saveUnchanged: midImprovementCampEntrySaveUnchanged,
|
||||
aftermathCompleted: midImprovementAftermathCompleted,
|
||||
rewardDestinationPending: midImprovementRewardDestinationPending,
|
||||
rewardAcknowledgements: midImprovementRewardAcknowledgements
|
||||
rewardAcknowledgements: midImprovementRewardAcknowledgements,
|
||||
noticeDismissals: midImprovementNoticeDismissals
|
||||
})}`
|
||||
);
|
||||
const midImprovementLedgerTogglePoint = await readCampVictoryRewardLedgerTogglePoint(page);
|
||||
await page.mouse.click(midImprovementLedgerTogglePoint.x, midImprovementLedgerTogglePoint.y);
|
||||
await page.waitForFunction(() => {
|
||||
const ledger = window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement;
|
||||
return ledger?.visible === true && ledger.cards?.length === 6 && ledger.actions?.length === 4;
|
||||
}, undefined, { timeout: 30000 });
|
||||
const openedMidImprovementLedger = await page.evaluate(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement
|
||||
);
|
||||
const openedMidImprovementLedgerSave = await readCampaignSave(page);
|
||||
assert(
|
||||
openedMidImprovementLedger?.role === 'on-demand-ledger' &&
|
||||
openedMidImprovementLedger.available === true &&
|
||||
openedMidImprovementLedger.battleId === midCampNextBattleId &&
|
||||
sameJsonValue(
|
||||
openedMidImprovementLedger.cards.map((card) => card.id),
|
||||
['gold', 'equipment', 'supplies', 'reputation', 'recruits', 'unlocks']
|
||||
) &&
|
||||
sameJsonValue(
|
||||
openedMidImprovementLedger.actions.map((action) => action.id),
|
||||
['equipment', 'supplies', 'sortie', 'close']
|
||||
) &&
|
||||
openedMidImprovementLedger.actions.filter((action) => action.primary).map((action) => action.id).join(',') === 'close' &&
|
||||
openedMidImprovementLedger.cards.every((card) => boundsWithinFhdViewport(card.bounds)) &&
|
||||
openedMidImprovementLedger.actions.every((action) => action.interactive && boundsWithinFhdViewport(action.bounds)) &&
|
||||
sameJsonValue(openedMidImprovementLedgerSave, pendingMidImprovementSave),
|
||||
`Expected opening the mid-campaign ledger to reveal the six-card/four-action contract without mutating either save: ${JSON.stringify(openedMidImprovementLedger)}`
|
||||
);
|
||||
await page.screenshot({ path: `${screenshotDir}/rc-mid-improvement-victory-reward.png`, fullPage: true });
|
||||
const midImprovementSortiePoint = await readCampVictoryRewardControlPoint(page, 'sortie');
|
||||
await page.mouse.click(midImprovementSortiePoint.x, midImprovementSortiePoint.y);
|
||||
@@ -7909,8 +8132,9 @@ try {
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
|
||||
const finalBattleId = 'sixty-sixth-battle-wuzhang-final';
|
||||
await seedCampaignSave(page, {
|
||||
battleId: 'sixty-sixth-battle-wuzhang-final',
|
||||
battleId: finalBattleId,
|
||||
battleTitle: '오장원 최종전',
|
||||
step: 'sixty-sixth-camp',
|
||||
gold: 9800,
|
||||
@@ -7925,12 +8149,17 @@ try {
|
||||
await waitForCamp(page);
|
||||
await waitForCampSkinTransition(page, 'northern');
|
||||
await waitForCampSoundscapeTransition(page, 'camp-frontier', 'mountain-wind-ambience');
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement?.visible === true,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
await waitForCampVictoryRewardLedgerReady(page, finalBattleId);
|
||||
const finalCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const finalCampArrivalSave = await readCampaignSave(page);
|
||||
const finalCampArrivalAcknowledgements = campaignVictoryAcknowledgementSnapshot(
|
||||
finalCampArrivalSave,
|
||||
finalBattleId
|
||||
);
|
||||
const finalCampArrivalNoticeDismissals = campaignVictoryNoticeDismissalSnapshot(
|
||||
finalCampArrivalSave,
|
||||
finalBattleId
|
||||
);
|
||||
assert(finalCampState?.campaign?.step === 'sixty-sixth-camp', `Expected final camp save to continue: ${JSON.stringify(finalCampState)}`);
|
||||
assertCampSkinState(finalCampState, 'northern', 'final camp');
|
||||
assertCampSoundscapeState(finalCampState, {
|
||||
@@ -7954,11 +8183,71 @@ try {
|
||||
`Expected final camp deployment preview to stay empty: ${JSON.stringify(finalCampState?.sortieDeploymentPreview)}`
|
||||
);
|
||||
assert(
|
||||
finalCampState.victoryRewardAcknowledgement?.visible === true &&
|
||||
finalCampState.victoryRewardAcknowledgement?.battleId === 'sixty-sixth-battle-wuzhang-final',
|
||||
`Expected the seeded final victory to expose its undismissed reward notice: ${JSON.stringify(finalCampState.victoryRewardAcknowledgement)}`
|
||||
finalCampState.victorySettlementArrival?.battleId === finalBattleId &&
|
||||
finalCampState.victorySettlementArrival.battleTitle === '오장원 최종전' &&
|
||||
finalCampState.victorySettlementArrival.message.includes('보상 장부') &&
|
||||
sameJsonValue(finalCampState.victorySettlementArrival.settledBattleIds, [finalBattleId]) &&
|
||||
sameJsonValue(
|
||||
finalCampState.victorySettlementArrival.pendingCategories,
|
||||
['equipment', 'supplies']
|
||||
) &&
|
||||
sameJsonValue(
|
||||
finalCampState.victorySettlementArrival.passiveCategoriesAcknowledged,
|
||||
['gold', 'reputation']
|
||||
) &&
|
||||
finalCampState.victorySettlementArrival.blocking === false &&
|
||||
finalCampArrivalNoticeDismissals.current === true &&
|
||||
finalCampArrivalNoticeDismissals.slot1 === true &&
|
||||
['current', 'slot1'].every((saveKey) => (
|
||||
finalCampArrivalAcknowledgements[saveKey].battleAcknowledged === false &&
|
||||
sameJsonValue(finalCampArrivalAcknowledgements[saveKey].categories, ['gold', 'reputation'])
|
||||
)) &&
|
||||
finalCampState.victoryRewardAcknowledgement?.role === 'on-demand-ledger' &&
|
||||
finalCampState.victoryRewardAcknowledgement.available === true &&
|
||||
finalCampState.victoryRewardAcknowledgement.visible === false &&
|
||||
finalCampState.victoryRewardAcknowledgement.battleId === finalBattleId &&
|
||||
boundsWithinFhdViewport(finalCampState.victoryRewardAcknowledgement.toggleButtonBounds) &&
|
||||
finalCampState.victoryRewardAcknowledgement.toggleInteractive === true &&
|
||||
finalCampState.victoryRewardAcknowledgement.cards.length === 0 &&
|
||||
finalCampState.victoryRewardAcknowledgement.actions.length === 0 &&
|
||||
finalCampState.campTabs?.find((tab) => tab.id === 'equipment')?.newBadgeVisible === true &&
|
||||
finalCampState.campTabs?.find((tab) => tab.id === 'supplies')?.newBadgeVisible === true,
|
||||
`Expected seeded final victory arrival to settle passives nonblockingly and leave its destination rewards behind an unopened ledger: ${JSON.stringify({
|
||||
arrival: finalCampState.victorySettlementArrival,
|
||||
ledger: finalCampState.victoryRewardAcknowledgement,
|
||||
acknowledgements: finalCampArrivalAcknowledgements,
|
||||
noticeDismissals: finalCampArrivalNoticeDismissals,
|
||||
tabs: finalCampState.campTabs
|
||||
})}`
|
||||
);
|
||||
const finalCampLedgerTogglePoint = await readCampVictoryRewardLedgerTogglePoint(page);
|
||||
await page.mouse.click(finalCampLedgerTogglePoint.x, finalCampLedgerTogglePoint.y);
|
||||
await page.waitForFunction(() => {
|
||||
const ledger = window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement;
|
||||
return ledger?.visible === true && ledger.cards?.length === 6 && ledger.actions?.length === 4;
|
||||
}, undefined, { timeout: 30000 });
|
||||
const finalCampOpenedLedger = await page.evaluate(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.victoryRewardAcknowledgement
|
||||
);
|
||||
const finalCampRewardSaveBeforeClose = await readCampaignSave(page);
|
||||
assert(
|
||||
finalCampOpenedLedger?.role === 'on-demand-ledger' &&
|
||||
finalCampOpenedLedger.available === true &&
|
||||
finalCampOpenedLedger.battleId === finalBattleId &&
|
||||
sameJsonValue(
|
||||
finalCampOpenedLedger.cards.map((card) => card.id),
|
||||
['gold', 'equipment', 'supplies', 'reputation', 'recruits', 'unlocks']
|
||||
) &&
|
||||
sameJsonValue(
|
||||
finalCampOpenedLedger.actions.map((action) => action.id),
|
||||
['equipment', 'supplies', 'sortie', 'close']
|
||||
) &&
|
||||
finalCampOpenedLedger.actions.filter((action) => action.primary).map((action) => action.id).join(',') === 'close' &&
|
||||
finalCampOpenedLedger.cards.every((card) => boundsWithinFhdViewport(card.bounds)) &&
|
||||
finalCampOpenedLedger.actions.every((action) => action.interactive && boundsWithinFhdViewport(action.bounds)) &&
|
||||
sameJsonValue(finalCampRewardSaveBeforeClose, finalCampArrivalSave),
|
||||
`Expected the requested final-camp ledger to open with six cards and four actions without changing settlement state: ${JSON.stringify(finalCampOpenedLedger)}`
|
||||
);
|
||||
const finalCampRewardClosePoint = await readCampVictoryRewardControlPoint(page, 'close');
|
||||
await page.mouse.click(finalCampRewardClosePoint.x, finalCampRewardClosePoint.y);
|
||||
await page.waitForFunction(
|
||||
@@ -7968,23 +8257,24 @@ try {
|
||||
);
|
||||
const finalCampAfterRewardClose = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const finalCampRewardSaveAfterClose = await readCampaignSave(page);
|
||||
const finalBattleId = 'sixty-sixth-battle-wuzhang-final';
|
||||
assert(
|
||||
sameJsonValue(
|
||||
campaignSaveWithoutVictoryRewardNoticeDismissals(finalCampRewardSaveAfterClose),
|
||||
campaignSaveWithoutVictoryRewardNoticeDismissals(finalCampRewardSaveBeforeClose)
|
||||
) &&
|
||||
sameJsonValue(finalCampRewardSaveAfterClose, finalCampRewardSaveBeforeClose) &&
|
||||
sameJsonValue(
|
||||
campaignVictoryAcknowledgementSnapshot(finalCampRewardSaveAfterClose, finalBattleId),
|
||||
campaignVictoryAcknowledgementSnapshot(finalCampRewardSaveBeforeClose, finalBattleId)
|
||||
) &&
|
||||
campaignVictoryNoticeDismissalSnapshot(finalCampRewardSaveBeforeClose, finalBattleId).current === false &&
|
||||
campaignVictoryNoticeDismissalSnapshot(finalCampRewardSaveBeforeClose, finalBattleId).slot1 === false &&
|
||||
campaignVictoryNoticeDismissalSnapshot(finalCampRewardSaveBeforeClose, finalBattleId).current === true &&
|
||||
campaignVictoryNoticeDismissalSnapshot(finalCampRewardSaveBeforeClose, finalBattleId).slot1 === true &&
|
||||
campaignVictoryNoticeDismissalSnapshot(finalCampRewardSaveAfterClose, finalBattleId).current === true &&
|
||||
campaignVictoryNoticeDismissalSnapshot(finalCampRewardSaveAfterClose, finalBattleId).slot1 === true &&
|
||||
finalCampAfterRewardClose?.victorySettlementArrival?.battleId === finalBattleId &&
|
||||
finalCampAfterRewardClose?.victorySettlementArrival?.blocking === false &&
|
||||
finalCampAfterRewardClose?.victoryRewardAcknowledgement?.role === 'on-demand-ledger' &&
|
||||
finalCampAfterRewardClose?.victoryRewardAcknowledgement?.available === true &&
|
||||
finalCampAfterRewardClose?.victoryRewardAcknowledgement?.visible === false &&
|
||||
finalCampAfterRewardClose?.campTabs?.find((tab) => tab.id === 'equipment')?.newBadgeVisible === true &&
|
||||
finalCampAfterRewardClose?.campTabs?.find((tab) => tab.id === 'supplies')?.newBadgeVisible === true,
|
||||
`Expected closing the final-camp reward notice to persist only its dismissal while preserving destination-specific NEW states: ${JSON.stringify({
|
||||
`Expected closing an on-demand final-camp ledger to leave its already settled save and destination-specific NEW states completely unchanged: ${JSON.stringify({
|
||||
beforeDismissal: campaignVictoryNoticeDismissalSnapshot(finalCampRewardSaveBeforeClose, finalBattleId),
|
||||
afterDismissal: campaignVictoryNoticeDismissalSnapshot(finalCampRewardSaveAfterClose, finalBattleId),
|
||||
beforeAcknowledgement: campaignVictoryAcknowledgementSnapshot(finalCampRewardSaveBeforeClose, finalBattleId),
|
||||
@@ -8003,10 +8293,18 @@ try {
|
||||
const resumedFinalCampAfterRewardClose = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
assert(
|
||||
resumedFinalCampAfterRewardClose?.campaign?.step === 'sixty-sixth-camp' &&
|
||||
resumedFinalCampAfterRewardClose?.victorySettlementArrival === null &&
|
||||
resumedFinalCampAfterRewardClose?.victoryRewardAcknowledgement?.role === 'on-demand-ledger' &&
|
||||
resumedFinalCampAfterRewardClose?.victoryRewardAcknowledgement?.available === true &&
|
||||
resumedFinalCampAfterRewardClose?.victoryRewardAcknowledgement?.visible === false &&
|
||||
resumedFinalCampAfterRewardClose?.victoryRewardAcknowledgement?.battleId === finalBattleId &&
|
||||
boundsWithinFhdViewport(
|
||||
resumedFinalCampAfterRewardClose?.victoryRewardAcknowledgement?.toggleButtonBounds
|
||||
) &&
|
||||
resumedFinalCampAfterRewardClose?.victoryRewardAcknowledgement?.toggleInteractive === true &&
|
||||
resumedFinalCampAfterRewardClose?.campTabs?.find((tab) => tab.id === 'equipment')?.newBadgeVisible === true &&
|
||||
resumedFinalCampAfterRewardClose?.campTabs?.find((tab) => tab.id === 'supplies')?.newBadgeVisible === true,
|
||||
`Expected title continue to keep the dismissed reward notice closed without consuming NEW states: ${JSON.stringify(resumedFinalCampAfterRewardClose)}`
|
||||
`Expected title continue not to replay the settled arrival while keeping the on-demand ledger and destination NEW states available: ${JSON.stringify(resumedFinalCampAfterRewardClose)}`
|
||||
);
|
||||
await captureStableCampScreenshot(page, `${screenshotDir}/rc-final-camp.png`, 0, 'final camp');
|
||||
await assertFinalCampRosterPagination(page);
|
||||
@@ -10169,6 +10467,35 @@ async function waitForCamp(page) {
|
||||
}));
|
||||
}
|
||||
|
||||
async function waitForCampVictoryRewardLedgerReady(page, battleId) {
|
||||
await page.waitForFunction((expectedBattleId) => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
const camp = window.__HEROS_DEBUG__?.camp?.();
|
||||
const arrival = camp?.victorySettlementArrival;
|
||||
const ledger = camp?.victoryRewardAcknowledgement;
|
||||
const bounds = ledger?.toggleButtonBounds;
|
||||
return (
|
||||
activeScenes.includes('CampScene') &&
|
||||
arrival?.battleId === expectedBattleId &&
|
||||
arrival.blocking === false &&
|
||||
ledger?.role === 'on-demand-ledger' &&
|
||||
ledger.available === true &&
|
||||
ledger.visible === false &&
|
||||
ledger.battleId === expectedBattleId &&
|
||||
ledger.toggleInteractive === true &&
|
||||
Number.isFinite(bounds?.x) &&
|
||||
Number.isFinite(bounds?.y) &&
|
||||
Number.isFinite(bounds?.width) &&
|
||||
Number.isFinite(bounds?.height) &&
|
||||
bounds.width > 0 &&
|
||||
bounds.height > 0
|
||||
);
|
||||
}, battleId, { timeout: 30000 });
|
||||
await page.evaluate(() => new Promise((resolve) => {
|
||||
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
||||
}));
|
||||
}
|
||||
|
||||
async function waitForCampSkinTransition(page, expectedSkinId) {
|
||||
await page.waitForFunction((skinId) => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
@@ -10798,6 +11125,43 @@ async function readSortieOrderControlPoint(page, control, orderId) {
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function readCampVictoryRewardLedgerTogglePoint(page) {
|
||||
const probe = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
const canvas = document.querySelector('canvas');
|
||||
const ledger = window.__HEROS_DEBUG__?.camp?.()?.victoryRewardAcknowledgement;
|
||||
const bounds = ledger?.toggleButtonBounds;
|
||||
if (
|
||||
!scene ||
|
||||
!canvas ||
|
||||
!bounds ||
|
||||
ledger?.role !== 'on-demand-ledger' ||
|
||||
ledger.available !== true ||
|
||||
ledger.visible !== false ||
|
||||
ledger.toggleInteractive !== true
|
||||
) {
|
||||
return { ready: false, ledger, bounds: bounds ?? null };
|
||||
}
|
||||
const canvasBounds = canvas.getBoundingClientRect();
|
||||
const centerX = bounds.x + bounds.width / 2;
|
||||
const centerY = bounds.y + bounds.height / 2;
|
||||
return {
|
||||
ready: true,
|
||||
bounds,
|
||||
x: canvasBounds.left + centerX * canvasBounds.width / scene.scale.width,
|
||||
y: canvasBounds.top + centerY * canvasBounds.height / scene.scale.height
|
||||
};
|
||||
});
|
||||
assert(
|
||||
probe.ready === true &&
|
||||
Number.isFinite(probe.x) &&
|
||||
Number.isFinite(probe.y) &&
|
||||
boundsWithinFhdViewport(probe.bounds),
|
||||
`Expected an FHD-safe interactive camp reward-ledger toggle: ${JSON.stringify(probe)}`
|
||||
);
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function readCampVictoryRewardControlPoint(page, actionId) {
|
||||
const probe = await page.evaluate((requestedActionId) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
@@ -12194,6 +12558,40 @@ function sameJsonValue(value, expected) {
|
||||
return stableJson(value) === stableJson(expected);
|
||||
}
|
||||
|
||||
function assertBattleResultDebriefText(resultTexts, rewards, context) {
|
||||
const texts = Array.isArray(resultTexts) ? resultTexts : [];
|
||||
const recruits = rewards?.recruits ?? [];
|
||||
const unlocks = rewards?.unlocks ?? [];
|
||||
const progressCount = recruits.length + unlocks.length;
|
||||
const expectedProgressLine = progressCount > 0
|
||||
? `신규 합류 ${recruits.length}명 · 새 전장 ${unlocks.length}곳`
|
||||
: '새로운 합류·해금 없음';
|
||||
const hiddenProgressNames = [
|
||||
...recruits.map((recruit) => recruit.name),
|
||||
...unlocks.map((unlock) => unlock.title)
|
||||
].filter((label) => typeof label === 'string' && label.length > 0);
|
||||
const progressSummaryVisible = progressCount > 0
|
||||
? texts.some(
|
||||
(text) =>
|
||||
text === expectedProgressLine ||
|
||||
text.startsWith(`신규 합류 ${recruits.length}명 · 새`)
|
||||
)
|
||||
: texts.includes(expectedProgressLine);
|
||||
|
||||
assert(
|
||||
['소요 턴', '생존 아군', '격파 적군', '달성 목표'].every((label) => texts.includes(label)) &&
|
||||
progressSummaryVisible &&
|
||||
hiddenProgressNames.every(
|
||||
(label) => !texts.some((text) => text.includes(label))
|
||||
),
|
||||
`Expected ${context} debrief to end its four metrics with achieved objectives and summarize recruits/unlocks only by count: ${JSON.stringify({
|
||||
expectedProgressLine,
|
||||
hiddenProgressNames,
|
||||
texts
|
||||
})}`
|
||||
);
|
||||
}
|
||||
|
||||
function campaignSaveWithoutCampRecruitmentEffects(save) {
|
||||
const stableState = (state) => {
|
||||
if (!state) {
|
||||
@@ -12248,24 +12646,6 @@ function campaignVictoryNoticeDismissalSnapshot(save, battleId) {
|
||||
};
|
||||
}
|
||||
|
||||
function campaignSaveWithoutVictoryRewardNoticeDismissals(save) {
|
||||
const stableState = (state) => {
|
||||
if (!state) {
|
||||
return state;
|
||||
}
|
||||
const {
|
||||
updatedAt: _updatedAt,
|
||||
dismissedVictoryRewardNoticeBattleIds: _dismissedVictoryRewardNoticeBattleIds,
|
||||
...stableFields
|
||||
} = state;
|
||||
return stableFields;
|
||||
};
|
||||
return {
|
||||
current: stableState(save?.current),
|
||||
slot1: stableState(save?.slot1)
|
||||
};
|
||||
}
|
||||
|
||||
function campaignSaveWithoutVictoryRewardAcknowledgementEffects(save) {
|
||||
const stableState = (state) => {
|
||||
if (!state) {
|
||||
|
||||
Reference in New Issue
Block a user