feat: restore main record panels
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const frontendPort = 15112;
|
||||
const apiPort = 15113;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: '.',
|
||||
testMatch: ['main-records.spec.ts'],
|
||||
fullyParallel: false,
|
||||
workers: 1,
|
||||
timeout: 60_000,
|
||||
expect: { timeout: 10_000 },
|
||||
reporter: [['list']],
|
||||
outputDir: resolve(repositoryRoot, 'test-results/main-records-live'),
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
baseURL: `http://127.0.0.1:${frontendPort}/che/`,
|
||||
colorScheme: 'dark',
|
||||
deviceScaleFactor: 1,
|
||||
locale: 'ko-KR',
|
||||
timezoneId: 'UTC',
|
||||
trace: 'retain-on-failure',
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
webServer: [
|
||||
{
|
||||
command:
|
||||
`GAME_API_HOST=127.0.0.1 GAME_API_PORT=${apiPort} ` +
|
||||
'GAME_TRPC_PATH=/che/api/trpc GAME_API_EVENTS_PATH=/che/api/events ' +
|
||||
'PROFILE=che SCENARIO=default GAME_PROFILE_NAME=che:default node app/game-api/dist/index.js',
|
||||
cwd: repositoryRoot,
|
||||
url: `http://127.0.0.1:${apiPort}/che/api/trpc/health.ping?input=%7B%22json%22%3Anull%7D`,
|
||||
reuseExistingServer: false,
|
||||
timeout: 120_000,
|
||||
},
|
||||
{
|
||||
command:
|
||||
`VITE_APP_BASE_PATH=/che VITE_GAME_API_URL=http://127.0.0.1:${apiPort}/che/api/trpc ` +
|
||||
`VITE_GAME_SSE_URL=http://127.0.0.1:${apiPort}/che/api/events ` +
|
||||
`pnpm --filter @sammo-ts/game-frontend dev --host 127.0.0.1 --port ${frontendPort}`,
|
||||
cwd: repositoryRoot,
|
||||
url: `http://127.0.0.1:${frontendPort}/che/`,
|
||||
reuseExistingServer: false,
|
||||
timeout: 120_000,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,356 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { expect, test, type Page, type Route } from '@playwright/test';
|
||||
import {
|
||||
createGamePostgresConnector,
|
||||
createRedisConnector,
|
||||
resolvePostgresConfigFromEnv,
|
||||
resolveRedisConfigFromEnv,
|
||||
type GamePrismaClient,
|
||||
type RedisConnector,
|
||||
} from '../../packages/infra/src/index.js';
|
||||
|
||||
const RECORD_MARKER = `main-records-live-${randomUUID()}`;
|
||||
const accessToken = `ga_${randomUUID()}`;
|
||||
let postgres: ReturnType<typeof createGamePostgresConnector>;
|
||||
let redis: RedisConnector;
|
||||
let prisma: GamePrismaClient;
|
||||
let createdIds: number[] = [];
|
||||
|
||||
const accessKey = (token: string) => `sammo:game:access:che:default:${token}`;
|
||||
const trpcResponse = (data: unknown) => ({ result: { data } });
|
||||
const trpcError = (path: string, message: string) => ({
|
||||
error: {
|
||||
message,
|
||||
code: -32000,
|
||||
data: { code: 'INTERNAL_SERVER_ERROR', httpStatus: 500, path },
|
||||
},
|
||||
});
|
||||
const operationNames = (route: Route): string[] => {
|
||||
const pathname = decodeURIComponent(new URL(route.request().url()).pathname);
|
||||
return pathname.slice(pathname.lastIndexOf('/trpc/') + 6).split(',');
|
||||
};
|
||||
|
||||
const inspectRecordLayout = async (page: Page) =>
|
||||
page.evaluate(() => {
|
||||
const inspect = (selector: string) => {
|
||||
const element = document.querySelector(selector);
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
throw new Error(`missing ${selector}`);
|
||||
}
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
rect: {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
},
|
||||
padding: style.padding,
|
||||
color: style.color,
|
||||
backgroundImage: style.backgroundImage,
|
||||
borderTop: style.borderTop,
|
||||
borderBottom: style.borderBottom,
|
||||
fontFamily: style.fontFamily,
|
||||
fontSize: style.fontSize,
|
||||
fontWeight: style.fontWeight,
|
||||
lineHeight: style.lineHeight,
|
||||
textAlign: style.textAlign,
|
||||
};
|
||||
};
|
||||
const buckets = [...document.querySelectorAll<HTMLElement>('[data-record-bucket]')];
|
||||
const mobileZone = document.querySelector<HTMLElement>('.record-zone-mobile');
|
||||
const zoneSelector =
|
||||
mobileZone && getComputedStyle(mobileZone).display !== 'none' ? '.record-zone-mobile' : '.record-zone';
|
||||
return {
|
||||
zone: inspect(zoneSelector),
|
||||
global: inspect('[data-record-bucket="global"]'),
|
||||
general: inspect('[data-record-bucket="general"]'),
|
||||
history: inspect('[data-record-bucket="history"]'),
|
||||
title: inspect('[data-record-bucket="global"]'),
|
||||
firstLine: inspect('[data-record-bucket="global"] .record-line'),
|
||||
panels: buckets.map((bucket) => {
|
||||
const panel = bucket.closest<HTMLElement>('.record-panel');
|
||||
const title = panel?.querySelector<HTMLElement>('.record-title');
|
||||
if (!panel || !title) throw new Error('record panel structure missing');
|
||||
return {
|
||||
bucket: bucket.dataset.recordBucket,
|
||||
panel: panel.getBoundingClientRect().toJSON(),
|
||||
title: title.getBoundingClientRect().toJSON(),
|
||||
lineCount: bucket.querySelectorAll('.record-line').length,
|
||||
titleStyle: {
|
||||
fontSize: getComputedStyle(title).fontSize,
|
||||
fontWeight: getComputedStyle(title).fontWeight,
|
||||
lineHeight: getComputedStyle(title).lineHeight,
|
||||
textAlign: getComputedStyle(title).textAlign,
|
||||
borderTop: getComputedStyle(title).borderTop,
|
||||
borderBottom: getComputedStyle(title).borderBottom,
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
test.beforeAll(async () => {
|
||||
postgres = createGamePostgresConnector(resolvePostgresConfigFromEnv({ schema: 'che' }));
|
||||
redis = createRedisConnector(resolveRedisConfigFromEnv());
|
||||
await postgres.connect();
|
||||
await redis.connect();
|
||||
prisma = postgres.prisma;
|
||||
|
||||
const general = await prisma.general.findFirst({
|
||||
where: { userId: { not: null } },
|
||||
orderBy: { id: 'asc' },
|
||||
select: { id: true, userId: true, name: true },
|
||||
});
|
||||
if (!general?.userId) {
|
||||
throw new Error('The live che profile needs an owned general.');
|
||||
}
|
||||
const world = await prisma.worldState.findFirst({
|
||||
orderBy: { id: 'asc' },
|
||||
select: { currentYear: true, currentMonth: true },
|
||||
});
|
||||
if (!world) {
|
||||
throw new Error('The live che profile needs a world state.');
|
||||
}
|
||||
|
||||
const drafts = [
|
||||
...Array.from({ length: 15 }, (_, index) => ({
|
||||
scope: 'SYSTEM' as const,
|
||||
category: 'SUMMARY' as const,
|
||||
text: `<C>●</>${RECORD_MARKER} 장수 동향 ${15 - index}`,
|
||||
})),
|
||||
...Array.from({ length: 7 }, (_, index) => ({
|
||||
scope: 'GENERAL' as const,
|
||||
category: 'ACTION' as const,
|
||||
generalId: general.id,
|
||||
text: `<Y>●</>${RECORD_MARKER} 개인 기록 ${7 - index}`,
|
||||
})),
|
||||
...Array.from({ length: 15 }, (_, index) => ({
|
||||
scope: 'SYSTEM' as const,
|
||||
category: 'HISTORY' as const,
|
||||
text: `<R>●</>${RECORD_MARKER} 중원 정세 ${15 - index}`,
|
||||
})),
|
||||
];
|
||||
const rows = await Promise.all(
|
||||
drafts.map((draft) =>
|
||||
prisma.logEntry.create({
|
||||
data: {
|
||||
...draft,
|
||||
year: world.currentYear,
|
||||
month: world.currentMonth,
|
||||
meta: { fixture: RECORD_MARKER },
|
||||
},
|
||||
select: { id: true },
|
||||
})
|
||||
)
|
||||
);
|
||||
createdIds = rows.map((row: { id: number }) => row.id);
|
||||
|
||||
const issuedAt = new Date();
|
||||
const expiresAt = new Date(issuedAt.getTime() + 30 * 60 * 1000);
|
||||
await redis.client.set(
|
||||
accessKey(accessToken),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
profile: 'che:default',
|
||||
issuedAt: issuedAt.toISOString(),
|
||||
expiresAt: expiresAt.toISOString(),
|
||||
sessionId: `main-records-${randomUUID()}`,
|
||||
user: {
|
||||
id: general.userId,
|
||||
username: 'main-records-live',
|
||||
displayName: general.name,
|
||||
roles: [],
|
||||
},
|
||||
sanctions: {},
|
||||
}),
|
||||
{ EX: 1800 }
|
||||
);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
if (createdIds.length > 0) {
|
||||
await prisma.logEntry.deleteMany({ where: { id: { in: createdIds } } });
|
||||
}
|
||||
await redis.client.del(accessKey(accessToken));
|
||||
await redis.disconnect();
|
||||
await postgres.disconnect();
|
||||
});
|
||||
|
||||
test('renders all three real database record buckets with legacy computed geometry', async ({ page }) => {
|
||||
const liveResponse = await page.request.get(
|
||||
`http://127.0.0.1:15113/che/api/trpc/general.getRecentRecords?input=${encodeURIComponent(
|
||||
JSON.stringify({
|
||||
json: {
|
||||
lastGeneralRecordId: 0,
|
||||
lastWorldHistoryId: 0,
|
||||
},
|
||||
})
|
||||
)}`,
|
||||
{ headers: { authorization: `Bearer ${accessToken}` } }
|
||||
);
|
||||
expect(liveResponse.ok()).toBe(true);
|
||||
const livePayload = (await liveResponse.json()) as {
|
||||
result: { data: { global: unknown[]; general: unknown[]; history: unknown[] } };
|
||||
};
|
||||
const liveRecords = livePayload.result.data;
|
||||
expect([liveRecords.global.length, liveRecords.general.length, liveRecords.history.length]).toEqual([15, 7, 15]);
|
||||
let failRecords = false;
|
||||
|
||||
await page.addInitScript(
|
||||
({ token }) => {
|
||||
window.localStorage.setItem('sammo-game-token', token);
|
||||
window.localStorage.setItem('sammo-game-profile', 'che:default');
|
||||
},
|
||||
{ token: accessToken }
|
||||
);
|
||||
await page.route('**/che/api/trpc/**', async (route) => {
|
||||
const results = operationNames(route).map((operation) => {
|
||||
if (operation === 'general.me') {
|
||||
return trpcResponse({
|
||||
general: {
|
||||
id: 1,
|
||||
name: '관리자',
|
||||
npcState: 0,
|
||||
nationId: 0,
|
||||
cityId: 1,
|
||||
troopId: 0,
|
||||
picture: null,
|
||||
imageServer: 0,
|
||||
officerLevel: 0,
|
||||
stats: { leadership: 55, strength: 55, intelligence: 55 },
|
||||
gold: 1000,
|
||||
rice: 1000,
|
||||
crew: 0,
|
||||
train: 0,
|
||||
atmos: 0,
|
||||
injury: 0,
|
||||
experience: 0,
|
||||
dedication: 0,
|
||||
items: { horse: null, weapon: null, book: null, item: null },
|
||||
},
|
||||
city: null,
|
||||
nation: null,
|
||||
settings: {},
|
||||
penalties: {},
|
||||
});
|
||||
}
|
||||
if (operation === 'general.getRecentRecords') {
|
||||
return failRecords
|
||||
? trpcError(operation, '동향 정보를 불러오지 못했습니다.')
|
||||
: trpcResponse(liveRecords);
|
||||
}
|
||||
if (operation === 'lobby.info') {
|
||||
return trpcResponse({
|
||||
year: 190,
|
||||
month: 1,
|
||||
userCnt: 1,
|
||||
npcCnt: 0,
|
||||
nationCnt: 0,
|
||||
maxUserCnt: 50,
|
||||
turnTerm: 60,
|
||||
fictionMode: '가상',
|
||||
otherTextInfo: '',
|
||||
starttime: '0190-01-01T00:00:00.000Z',
|
||||
myGeneral: { id: 1, name: '관리자', nationId: 0 },
|
||||
});
|
||||
}
|
||||
if (operation === 'world.getMapLayout') {
|
||||
return trpcResponse({
|
||||
mapName: 'che',
|
||||
cityList: [{ id: 1, name: '낙양', level: 7, region: 1, x: 350, y: 245, path: [] }],
|
||||
regionMap: { 1: '사예' },
|
||||
levelMap: { 7: '수도' },
|
||||
});
|
||||
}
|
||||
if (operation === 'world.getMap') {
|
||||
return trpcResponse({
|
||||
year: 190,
|
||||
month: 1,
|
||||
startYear: 190,
|
||||
cityList: [[1, 7, 0, 0, 1, 1]],
|
||||
nationList: [],
|
||||
spyList: {},
|
||||
shownByGeneralList: [],
|
||||
myCity: 1,
|
||||
myNation: 0,
|
||||
});
|
||||
}
|
||||
if (operation === 'turns.getCommandTable') return trpcResponse({ general: [], nation: [] });
|
||||
if (operation === 'turns.reserved.getGeneral' || operation === 'turns.reserved.getNation') {
|
||||
return trpcResponse([]);
|
||||
}
|
||||
if (operation === 'messages.getRecent') {
|
||||
return trpcResponse({
|
||||
private: [],
|
||||
public: [],
|
||||
national: [],
|
||||
diplomacy: [],
|
||||
permission: 0,
|
||||
latestRead: { private: 0, diplomacy: 0 },
|
||||
canRespondDiplomacy: false,
|
||||
});
|
||||
}
|
||||
if (operation === 'messages.getContacts') return trpcResponse([]);
|
||||
if (operation === 'board.getAccess') {
|
||||
return trpcResponse({ canMeeting: false, canSecret: false, permission: 0 });
|
||||
}
|
||||
if (operation === 'tournament.getState') return trpcResponse({ stage: 0 });
|
||||
if (operation === 'public.recordAccess') return trpcResponse({ recorded: true });
|
||||
throw new Error(`Unhandled live main operation: ${operation}`);
|
||||
});
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(results),
|
||||
});
|
||||
});
|
||||
await page.route('**/image/**', async (route) => {
|
||||
const source = new URL(route.request().url());
|
||||
const response = await route.fetch({
|
||||
url: `https://dev-sam-ref.hided.net${source.pathname}`,
|
||||
});
|
||||
await route.fulfill({ response });
|
||||
});
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 900 });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
await expect(page.locator('[data-record-bucket="global"] .record-line')).toHaveCount(15);
|
||||
await expect(page.locator('[data-record-bucket="general"] .record-line')).toHaveCount(7);
|
||||
await expect(page.locator('[data-record-bucket="history"] .record-line')).toHaveCount(15);
|
||||
await expect(page.locator('[data-record-bucket="global"]')).toContainText(RECORD_MARKER);
|
||||
|
||||
const desktop = await inspectRecordLayout(page);
|
||||
expect(desktop.panels.map((panel) => panel.lineCount)).toEqual([15, 7, 15]);
|
||||
expect(desktop.panels[0]?.panel.width).toBeCloseTo(500, 0);
|
||||
expect(desktop.panels[1]?.panel.x).toBeCloseTo(500, 0);
|
||||
expect(desktop.panels[2]?.panel.width).toBeCloseTo(1000, 0);
|
||||
expect(desktop.panels[0]?.panel.height).toBeCloseTo(338, 0);
|
||||
expect(desktop.panels[2]?.panel.height).toBeCloseTo(338, 0);
|
||||
expect(desktop.panels[0]?.title.height).toBeCloseTo(23, 0);
|
||||
expect(desktop.panels[0]?.titleStyle).toMatchObject({
|
||||
fontSize: '14px',
|
||||
fontWeight: '400',
|
||||
lineHeight: '21px',
|
||||
textAlign: 'center',
|
||||
borderTop: '1px solid rgb(128, 128, 128)',
|
||||
borderBottom: '1px solid rgb(128, 128, 128)',
|
||||
});
|
||||
|
||||
await page.setViewportSize({ width: 500, height: 900 });
|
||||
await page.getByRole('button', { name: '동향', exact: true }).click();
|
||||
await expect(page.locator('.record-zone-mobile')).toBeVisible();
|
||||
const mobile = await inspectRecordLayout(page);
|
||||
expect(mobile.panels.map((panel) => panel.lineCount)).toEqual([15, 7, 15]);
|
||||
expect(mobile.panels.every((panel) => Math.round(panel.panel.width) === 500)).toBe(true);
|
||||
expect(mobile.panels[0]?.panel.height).toBeCloseTo(338, 0);
|
||||
expect(mobile.panels[1]?.panel.height).toBeCloseTo(170, 0);
|
||||
expect(mobile.panels[2]?.panel.height).toBeCloseTo(338, 0);
|
||||
|
||||
failRecords = true;
|
||||
await page.getByRole('button', { name: '새로고침', exact: true }).click();
|
||||
await expect(page.getByRole('alert').first()).toContainText('동향 정보를 불러오지 못했습니다.');
|
||||
await expect(page.getByRole('heading', { name: '장수 동향', exact: true })).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
import { chromium } from '@playwright/test';
|
||||
|
||||
const baseUrl = process.env.REF_MAIN_URL ?? 'http://127.0.0.1:3400/sam/';
|
||||
const username = process.env.REF_USER_ID ?? 'refuser1';
|
||||
const passwordFile =
|
||||
process.env.REF_USER_PASSWORD_FILE ??
|
||||
'/home/letrhee/sam_rebuild/docker_compose_files/reference/secrets/user1_password';
|
||||
|
||||
const password = (await readFile(passwordFile, 'utf8')).trim();
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
|
||||
try {
|
||||
const context = await browser.newContext({
|
||||
colorScheme: 'dark',
|
||||
deviceScaleFactor: 1,
|
||||
locale: 'ko-KR',
|
||||
timezoneId: 'UTC',
|
||||
});
|
||||
const page = await context.newPage();
|
||||
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 },
|
||||
});
|
||||
if (!response.ok()) {
|
||||
throw new Error(`reference login failed: HTTP ${response.status()}`);
|
||||
}
|
||||
|
||||
for (const viewport of [
|
||||
{ name: 'desktop', width: 1000, height: 900 },
|
||||
{ name: 'mobile', width: 500, height: 900 },
|
||||
]) {
|
||||
await page.setViewportSize(viewport);
|
||||
await page.goto(new URL('hwe/', baseUrl).toString(), { waitUntil: 'networkidle' });
|
||||
await page.locator('.RecordZone').waitFor({ state: 'visible' });
|
||||
await page.waitForFunction(() =>
|
||||
[...document.querySelectorAll('.RecordZone > div')].every((panel) => panel.children.length > 1)
|
||||
);
|
||||
const measurement = await page.evaluate(() => {
|
||||
const inspect = (selector) => {
|
||||
const element = document.querySelector(selector);
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
throw new Error(`missing ${selector}`);
|
||||
}
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
rect: {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
},
|
||||
display: style.display,
|
||||
padding: style.padding,
|
||||
color: style.color,
|
||||
backgroundColor: style.backgroundColor,
|
||||
backgroundImage: style.backgroundImage,
|
||||
borderTop: style.borderTop,
|
||||
fontFamily: style.fontFamily,
|
||||
fontSize: style.fontSize,
|
||||
fontWeight: style.fontWeight,
|
||||
lineHeight: style.lineHeight,
|
||||
textAlign: style.textAlign,
|
||||
};
|
||||
};
|
||||
return {
|
||||
zone: inspect('.RecordZone'),
|
||||
global: inspect('.PublicRecord'),
|
||||
general: inspect('.GeneralLog'),
|
||||
history: inspect('.WorldHistory'),
|
||||
title: inspect('.PublicRecord .title'),
|
||||
firstLine: inspect('.PublicRecord > div:nth-child(2)'),
|
||||
counts: {
|
||||
global: document.querySelectorAll('.PublicRecord > div:not(.title)').length,
|
||||
general: document.querySelectorAll('.GeneralLog > div:not(.title)').length,
|
||||
history: document.querySelectorAll('.WorldHistory > div:not(.title)').length,
|
||||
},
|
||||
};
|
||||
});
|
||||
console.log(JSON.stringify({ viewport, measurement }));
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const pm2Candidates = [
|
||||
'/home/letrhee/.nvm/versions/node/v22.15.1/bin/pm2',
|
||||
'/home/letrhee/.nvm/versions/node/v24.13.0/bin/pm2',
|
||||
];
|
||||
|
||||
let processes;
|
||||
for (const candidate of pm2Candidates) {
|
||||
try {
|
||||
const output = execFileSync(candidate, ['jlist'], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
processes = JSON.parse(output.slice(output.indexOf('[')));
|
||||
break;
|
||||
} catch {
|
||||
// Try the next locally installed PM2 client.
|
||||
}
|
||||
}
|
||||
if (!processes) {
|
||||
throw new Error('Unable to read the local PM2 process list.');
|
||||
}
|
||||
|
||||
const source = processes.find((process) => process.name === 'sammo-verify-che-api')?.pm2_env;
|
||||
if (!source) {
|
||||
throw new Error('sammo-verify-che-api is required as the live environment source.');
|
||||
}
|
||||
|
||||
const allowed = /^(DATABASE_URL|POSTGRES_|REDIS_|GAME_|GATEWAY_|PROFILE$|SCENARIO$)/;
|
||||
const liveEnvironment = Object.fromEntries(
|
||||
Object.entries(source).filter(([key, value]) => allowed.test(key) && typeof value === 'string')
|
||||
);
|
||||
const result = spawnSync(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
'playwright',
|
||||
'test',
|
||||
'main-records.spec.ts',
|
||||
'--config',
|
||||
'tools/frontend-legacy-parity/main-records.playwright.config.mjs',
|
||||
],
|
||||
{
|
||||
cwd: repositoryRoot,
|
||||
env: { ...process.env, ...liveEnvironment },
|
||||
stdio: 'inherit',
|
||||
}
|
||||
);
|
||||
process.exitCode = result.status ?? 1;
|
||||
Reference in New Issue
Block a user