72 lines
5.5 KiB
JavaScript
72 lines
5.5 KiB
JavaScript
// Real Portal login; credentials stay in the caller's ignored private fixture.
|
|
import fs from 'node:fs';
|
|
import assert from 'node:assert/strict';
|
|
import { chromium, expect } from '@playwright/test';
|
|
|
|
(async () => {
|
|
const phase = process.argv[2];
|
|
assert(['guard', 'remove', 'repeat'].includes(phase));
|
|
const login = JSON.parse(fs.readFileSync(process.env.MMAPP_PORTAL_FIXTURE || 'tmp/portal195-private.json'));
|
|
const own = JSON.parse(fs.readFileSync(process.env.MMAPP_CONTAINER_FIXTURE || 'tmp/state248-public.json'));
|
|
const reportPath = 'docs/ux-ui/acceptance/containers248-live-ui.json';
|
|
const report = fs.existsSync(reportPath) ? JSON.parse(fs.readFileSync(reportPath)) : { date: '2026-10-14', block: '12.5.248', realPortalLogin: true, noMocks: true, steps: [], pageErrors: [] };
|
|
const browser = await chromium.launch({ channel: 'msedge', headless: true });
|
|
try {
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
|
|
page.on('pageerror', error => report.pageErrors.push(error.message));
|
|
await page.goto((process.env.MMAPP_TEST_URL || 'http://localhost:3000') + '/containers');
|
|
await page.getByLabel('Email', { exact: true }).fill(login.email);
|
|
await page.getByLabel('Пароль', { exact: true }).fill(login.password);
|
|
await page.getByRole('button', { name: 'Войти', exact: true }).click();
|
|
await expect(page.getByRole('navigation', { name: 'Путь страницы' })).toContainText('Контейнеры', { timeout: 30000 });
|
|
const endpoint = `/api/servers/${own.serverId}/containers/remove`;
|
|
const direct = () => page.evaluate(async ({ endpoint, rosId }) => {
|
|
const r = await fetch(endpoint, { method: 'POST', headers: { Authorization: 'Bearer ' + localStorage.getItem('mmapp_token'), 'Content-Type': 'application/json' }, body: JSON.stringify({ rosId }) });
|
|
const body = await r.json();
|
|
return { status: r.status, ok: body.ok === true, error: body.error };
|
|
}, { endpoint, rosId: own.rosId });
|
|
if (phase === 'repeat') {
|
|
const result = await direct(); assert.equal(result.status, 409); assert.equal(result.ok, false); assert.match(result.error, /уже отсутствует/);
|
|
report.steps.push({ phase, status: result.status, absentConflict: true });
|
|
} else {
|
|
await page.getByRole('button', { name: 'Действия контейнера ' + own.name, exact: true }).click();
|
|
await page.getByRole('menuitem', { name: 'Удалить', exact: true }).click();
|
|
const dialog = page.getByRole('alertdialog');
|
|
await expect(dialog).toContainText(own.name);
|
|
if (phase === 'guard') {
|
|
report.layout = [];
|
|
for (const width of [1440, 390]) {
|
|
await page.setViewportSize({ width, height: 1000 });
|
|
await expect(dialog).toContainText('после подтверждённой остановки');
|
|
await expect(dialog.getByRole('button', { name: 'Удалить', exact: true })).toBeDisabled();
|
|
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth > innerWidth), { timeout: 5000 }).toBe(false);
|
|
const layout = await page.evaluate(() => ({ viewport: innerWidth, scrollWidth: document.documentElement.scrollWidth }));
|
|
(report.layout ??= []).push(layout);
|
|
if (layout.scrollWidth > layout.viewport) {
|
|
report.overflow = await page.evaluate(() => Array.from(document.querySelectorAll('*')).map(el => ({tag:el.tagName,slot:el.getAttribute('data-slot'),left:el.getBoundingClientRect().left,right:el.getBoundingClientRect().right,width:el.getBoundingClientRect().width,position:getComputedStyle(el).position})).filter(el=>el.right>innerWidth+1).slice(0,20));
|
|
await page.screenshot({ path: `tmp/containers248-guard-${width}.png`, fullPage: true });
|
|
}
|
|
assert.equal(layout.scrollWidth > layout.viewport, false);
|
|
}
|
|
const result = await direct(); assert.equal(result.status, 409); assert.equal(result.ok, false);
|
|
report.steps.push({ phase, viewports: [1440, 390], uiDeleteDisabled: true, backendConflict: result.status });
|
|
} else {
|
|
await expect(dialog).toContainText('остановленный контейнер');
|
|
const response = page.waitForResponse(r => new URL(r.url()).pathname === endpoint && r.request().method() === 'POST');
|
|
await dialog.getByRole('button', { name: 'Удалить', exact: true }).click();
|
|
const r = await response; assert.equal(r.request().postDataJSON().rosId, own.rosId); assert.equal(r.status(), 200); assert.equal((await r.json()).ok, true);
|
|
await expect(dialog).toBeHidden();
|
|
await expect(page.getByRole('button', { name: 'Действия контейнера ' + own.name, exact: true })).toHaveCount(0);
|
|
report.steps.push({ phase, status: 200, receiptOk: true, targetMatched: true, rowAbsent: true });
|
|
}
|
|
}
|
|
assert.deepEqual(report.pageErrors, []);
|
|
delete report.overflow;
|
|
if (report.failure?.phase === phase) { (report.harnessFailures ??= []).push(report.failure); delete report.failure; }
|
|
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
|
|
console.log('Live container removal: ' + phase + ' passed');
|
|
} catch (error) {
|
|
report.failure = { phase, name: error.name, ...(error.name === 'AssertionError' ? { actual: error.actual, expected: error.expected } : {}) }; fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n'); throw error;
|
|
} finally { await browser.close(); }
|
|
})().catch(error => { console.error('Live container removal failed: ' + error.name); process.exitCode = 1; });
|