Merge branch 'main' into feature/best-general-hall-of-fame-parity
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import { chromium } from '@playwright/test';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const baseUrl = process.env.REF_MENU_URL ?? 'http://127.0.0.1:3400/sam/';
|
||||
const username = process.env.REF_MENU_USER ?? 'refuser1';
|
||||
const passwordFile = process.env.REF_MENU_PASSWORD_FILE;
|
||||
const artifactRoot = resolve(process.env.REF_MENU_ARTIFACT_DIR ?? 'test-results/reference-ingame-menus');
|
||||
|
||||
if (!passwordFile) {
|
||||
throw new Error('REF_MENU_PASSWORD_FILE is required.');
|
||||
}
|
||||
|
||||
const password = (await readFile(passwordFile, 'utf8')).trim();
|
||||
await mkdir(artifactRoot, { recursive: true });
|
||||
|
||||
const login = async (context, page) => {
|
||||
await page.goto(baseUrl, { waitUntil: 'networkidle' });
|
||||
const globalSalt = await page.locator('#global_salt').inputValue();
|
||||
const passwordHash = createHash('sha512')
|
||||
.update(globalSalt + password + globalSalt)
|
||||
.digest('hex');
|
||||
const response = await context.request.post(new URL('api.php?path=Login/LoginByID', baseUrl).toString(), {
|
||||
data: { username, password: passwordHash },
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok() || result.result !== true) {
|
||||
throw new Error('Reference login failed.');
|
||||
}
|
||||
};
|
||||
|
||||
const rectAndStyle = (element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
||||
style: {
|
||||
display: style.display,
|
||||
gridTemplateColumns: style.gridTemplateColumns,
|
||||
fontFamily: style.fontFamily,
|
||||
fontSize: style.fontSize,
|
||||
lineHeight: style.lineHeight,
|
||||
color: style.color,
|
||||
backgroundColor: style.backgroundColor,
|
||||
backgroundImage: style.backgroundImage,
|
||||
borderTopColor: style.borderTopColor,
|
||||
borderTopWidth: style.borderTopWidth,
|
||||
padding: style.padding,
|
||||
margin: style.margin,
|
||||
cursor: style.cursor,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const measure = async (page, selectors) =>
|
||||
page.evaluate(
|
||||
({ selectors, measureSource }) => {
|
||||
const measureElement = new Function(`return (${measureSource})`)();
|
||||
const result = {};
|
||||
for (const [name, selector] of Object.entries(selectors)) {
|
||||
const element = document.querySelector(selector);
|
||||
result[name] = element ? measureElement(element) : null;
|
||||
}
|
||||
return {
|
||||
elements: result,
|
||||
document: {
|
||||
width: document.documentElement.scrollWidth,
|
||||
height: document.documentElement.scrollHeight,
|
||||
},
|
||||
};
|
||||
},
|
||||
{ selectors, measureSource: rectAndStyle.toString() }
|
||||
);
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
try {
|
||||
const output = {};
|
||||
for (const viewport of [
|
||||
{ name: 'desktop', width: 1000, height: 900 },
|
||||
{ name: 'mobile', width: 500, height: 900 },
|
||||
]) {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: viewport.width, height: viewport.height },
|
||||
deviceScaleFactor: 1,
|
||||
locale: 'ko-KR',
|
||||
timezoneId: 'Asia/Seoul',
|
||||
colorScheme: 'dark',
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const consoleErrors = [];
|
||||
const failedResources = [];
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error') consoleErrors.push(message.text());
|
||||
});
|
||||
page.on('response', (response) => {
|
||||
if (response.status() >= 400) failedResources.push(`${response.status()} ${response.url()}`);
|
||||
});
|
||||
await login(context, page);
|
||||
await page.goto(new URL('hwe/', baseUrl).toString(), { waitUntil: 'networkidle' });
|
||||
|
||||
await page.goto(new URL('hwe/b_myPage.php', baseUrl).toString(), { waitUntil: 'networkidle' });
|
||||
await page.locator('#container').waitFor();
|
||||
const myPage = await measure(page, {
|
||||
body: 'body',
|
||||
container: '#container',
|
||||
title: '#container > .row:first-child',
|
||||
infoColumn: '#container > .row:nth-child(2) > .col:first-child',
|
||||
settingsColumn: '#container > .row:nth-child(2) > .col:nth-child(2)',
|
||||
saveButton: '#set_my_setting',
|
||||
firstSelect: 'select',
|
||||
customCss: '#custom_css',
|
||||
firstLogTitle: '#generalActionPlate',
|
||||
});
|
||||
await page.screenshot({ path: resolve(artifactRoot, `ref-my-page-${viewport.name}.png`), fullPage: true });
|
||||
|
||||
await page.goto(new URL('hwe/a_traffic.php', baseUrl).toString(), { waitUntil: 'networkidle' });
|
||||
const traffic = await measure(page, {
|
||||
body: 'body',
|
||||
title: 'body > table:first-of-type',
|
||||
chartLayout: 'body > table:nth-of-type(2)',
|
||||
refreshChart: 'body > table:nth-of-type(2) > tbody > tr > td:first-child > table',
|
||||
onlineChart: 'body > table:nth-of-type(2) > tbody > tr > td:nth-child(2) > table',
|
||||
firstBigBar: '.big_bar',
|
||||
suspectTable: 'body > table:nth-of-type(3)',
|
||||
});
|
||||
await page.screenshot({ path: resolve(artifactRoot, `ref-traffic-${viewport.name}.png`), fullPage: true });
|
||||
|
||||
await page.goto(new URL('hwe/a_npcList.php', baseUrl).toString(), { waitUntil: 'networkidle' });
|
||||
const npcList = await measure(page, {
|
||||
body: 'body',
|
||||
title: 'body > table:first-of-type',
|
||||
sortSelect: 'select[name="type"]',
|
||||
list: 'body > table:nth-of-type(2)',
|
||||
header: 'body > table:nth-of-type(2) tr:first-child',
|
||||
footer: 'body > table:nth-of-type(3)',
|
||||
});
|
||||
await page.screenshot({ path: resolve(artifactRoot, `ref-npc-list-${viewport.name}.png`), fullPage: true });
|
||||
|
||||
await page.goto(new URL('hwe/v_battleCenter.php', baseUrl).toString(), { waitUntil: 'networkidle' });
|
||||
try {
|
||||
await page.locator('#container').waitFor({ timeout: 10_000 });
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Reference battle center failed to mount: ${JSON.stringify({
|
||||
url: page.url(),
|
||||
text: (await page.locator('body').innerText()).slice(0, 500),
|
||||
html: (await page.content()).slice(-1_000),
|
||||
consoleErrors,
|
||||
failedResources,
|
||||
})}`
|
||||
);
|
||||
}
|
||||
const battleCenter = await measure(page, {
|
||||
body: 'body',
|
||||
container: '#container',
|
||||
topBar: '#container > :first-child',
|
||||
selectorRow: '#container > .row:nth-child(2)',
|
||||
previousButton: '#container > .row:nth-child(2) button:first-child',
|
||||
firstSelect: '#container > .row:nth-child(2) select:first-of-type',
|
||||
generalCard: '.header-cell',
|
||||
firstLogHeader: '.header-cell:nth-of-type(1)',
|
||||
});
|
||||
await page.screenshot({
|
||||
path: resolve(artifactRoot, `ref-battle-center-${viewport.name}.png`),
|
||||
fullPage: true,
|
||||
});
|
||||
output[viewport.name] = { myPage, traffic, npcList, battleCenter };
|
||||
await context.close();
|
||||
}
|
||||
await writeFile(resolve(artifactRoot, 'computed-dom.json'), `${JSON.stringify(output, null, 2)}\n`);
|
||||
process.stdout.write(`${JSON.stringify({ ok: true, artifactRoot, viewports: Object.keys(output) })}\n`);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
Reference in New Issue
Block a user