156 lines
5.3 KiB
JavaScript
156 lines
5.3 KiB
JavaScript
import fs from 'node:fs';
|
|
import {createHash} from 'node:crypto';
|
|
import {chromium} from 'playwright';
|
|
|
|
const baseURL = process.env.REF_BROWSER_URL;
|
|
const screenshotPath = process.env.REF_SCREENSHOT_PATH;
|
|
const resultPath = process.env.REF_RESULT_PATH;
|
|
const username = process.env.REF_USER_ID ?? 's100user01';
|
|
const password = fs.readFileSync('/run/secrets/test_user_password', 'utf8').trim();
|
|
const expectedReason = '100기 올스타 장수는 능력치 초기화를 사용할 수 없습니다.';
|
|
const expectedWarning = '100기 올스타 장수는 장수 전환 시 능력치 성장 기록을 보존하기 위해 능력치 초기화를 사용할 수 없습니다.';
|
|
|
|
if (!baseURL || !screenshotPath || !resultPath) {
|
|
throw new Error('REF_BROWSER_URL, REF_SCREENSHOT_PATH, and REF_RESULT_PATH are required');
|
|
}
|
|
|
|
const browser = await chromium.launch({headless: true});
|
|
const context = await browser.newContext({
|
|
viewport: {width: 1280, height: 960},
|
|
deviceScaleFactor: 1,
|
|
});
|
|
const browserMessages = [];
|
|
const attachPageListeners = targetPage => {
|
|
targetPage.on('console', message => {
|
|
browserMessages.push({type: message.type(), text: message.text()});
|
|
});
|
|
targetPage.on('pageerror', error => {
|
|
browserMessages.push({type: 'pageerror', text: error.message});
|
|
});
|
|
};
|
|
let page = await context.newPage();
|
|
attachPageListeners(page);
|
|
|
|
await page.goto(baseURL, {waitUntil: 'domcontentloaded', timeout: 60_000});
|
|
const salt = await page.locator('#global_salt').inputValue();
|
|
const passwordHash = createHash('sha512')
|
|
.update(salt + password + salt)
|
|
.digest('hex');
|
|
const loginResponse = await page.request.post(
|
|
new URL('api.php?path=Login/LoginByID', baseURL).href,
|
|
{data: {username, password: passwordHash}},
|
|
);
|
|
const loginResult = await loginResponse.json();
|
|
if (!loginResponse.ok() || loginResult.result !== true) {
|
|
throw new Error(`login failed: ${String(loginResult.reason ?? loginResponse.status())}`);
|
|
}
|
|
|
|
const gameLoginResponse = await page.request.get(
|
|
new URL('hwe/api.php?path=Global/GetConst', baseURL).href,
|
|
);
|
|
const gameLoginResult = await gameLoginResponse.json();
|
|
if (!gameLoginResponse.ok() || gameLoginResult.result !== true) {
|
|
throw new Error(`game login failed: ${JSON.stringify(gameLoginResult)}`);
|
|
}
|
|
|
|
await page.close();
|
|
browserMessages.length = 0;
|
|
page = await context.newPage();
|
|
attachPageListeners(page);
|
|
await page.goto(
|
|
new URL('hwe/v_inheritPoint.php', baseURL).href,
|
|
{waitUntil: 'networkidle', timeout: 60_000},
|
|
);
|
|
const bodyText = await page.locator('body').innerText();
|
|
if (bodyText.includes('#0 /var/www/html/')) {
|
|
throw new Error('PHP stack trace was rendered in the inheritance page');
|
|
}
|
|
if (!bodyText.includes(expectedWarning)) {
|
|
await page.screenshot({path: screenshotPath, fullPage: true});
|
|
fs.writeFileSync(
|
|
resultPath,
|
|
`${JSON.stringify({
|
|
username,
|
|
url: page.url(),
|
|
bodyText: bodyText.slice(0, 4000),
|
|
browserMessages,
|
|
}, null, 2)}\n`,
|
|
{mode: 0o600},
|
|
);
|
|
throw new Error('S100 stat-reset restriction was not rendered');
|
|
}
|
|
if (await page.getByRole('button', {name: '능력치 초기화'}).count() !== 0) {
|
|
throw new Error('S100 stat-reset button must not be rendered');
|
|
}
|
|
|
|
const warning = page.getByText(expectedWarning, {exact: true});
|
|
await warning.waitFor({state: 'visible', timeout: 60_000});
|
|
const warningGeometry = await warning.evaluate(element => {
|
|
const rect = element.getBoundingClientRect();
|
|
const style = getComputedStyle(element);
|
|
return {
|
|
rect: {
|
|
x: rect.x,
|
|
y: rect.y,
|
|
width: rect.width,
|
|
height: rect.height,
|
|
},
|
|
style: {
|
|
color: style.color,
|
|
fontFamily: style.fontFamily,
|
|
fontSize: style.fontSize,
|
|
lineHeight: style.lineHeight,
|
|
},
|
|
};
|
|
});
|
|
const beforeState = await page.evaluate(() => ({
|
|
currentStat: staticValues.currentStat,
|
|
previousPoint: staticValues.items.previous,
|
|
}));
|
|
|
|
const resetResponse = await page.request.put(
|
|
new URL('hwe/api.php?path=InheritAction/ResetStat', baseURL).href,
|
|
{
|
|
data: {
|
|
leadership: 55,
|
|
strength: 55,
|
|
intel: 55,
|
|
},
|
|
},
|
|
);
|
|
const resetResult = await resetResponse.json();
|
|
if (!resetResponse.ok()
|
|
|| resetResult.result !== false
|
|
|| resetResult.reason !== expectedReason
|
|
) {
|
|
throw new Error(`unexpected ResetStat response: ${JSON.stringify(resetResult)}`);
|
|
}
|
|
|
|
await page.reload({waitUntil: 'networkidle', timeout: 60_000});
|
|
const afterState = await page.evaluate(() => ({
|
|
currentStat: staticValues.currentStat,
|
|
previousPoint: staticValues.items.previous,
|
|
}));
|
|
if (JSON.stringify(afterState) !== JSON.stringify(beforeState)) {
|
|
throw new Error(`ResetStat rejection changed state: ${JSON.stringify({beforeState, afterState})}`);
|
|
}
|
|
|
|
await page.screenshot({path: screenshotPath, fullPage: true});
|
|
fs.writeFileSync(
|
|
resultPath,
|
|
`${JSON.stringify({
|
|
username,
|
|
url: page.url(),
|
|
viewport: {width: 1280, height: 960, deviceScaleFactor: 1},
|
|
warningGeometry,
|
|
resetResult,
|
|
beforeState,
|
|
afterState,
|
|
browserMessages,
|
|
}, null, 2)}\n`,
|
|
{mode: 0o600},
|
|
);
|
|
|
|
await browser.close();
|
|
console.log(`S100 inheritance stat-reset guard verified: ${screenshotPath}`);
|