119 lines
6.1 KiB
JavaScript
119 lines
6.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { chromium } from '@playwright/test';
|
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { resolve } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { loadConfig } from '../../apps/server/src/config.ts';
|
|
import { createApp } from '../../apps/server/src/app.ts';
|
|
const folder = await mkdtemp(resolve(tmpdir(), 'hop-devices-e2e-'));
|
|
const runtime = await createApp({
|
|
...loadConfig({
|
|
APP_URL: 'http://127.0.0.1',
|
|
DATABASE_PATH: resolve(folder, 'db.sqlite'),
|
|
UPLOAD_DIR: resolve(folder, 'uploads'),
|
|
MIN_FREE_DISK_MB: '0',
|
|
SESSION_SECRET: 's'.repeat(40),
|
|
DISCOVERY_SECRET: 'd'.repeat(40),
|
|
}),
|
|
webRoot: resolve('apps/web/dist'),
|
|
});
|
|
let browser;
|
|
try {
|
|
await runtime.app.listen({ host: '127.0.0.1', port: 0 });
|
|
const { port } = runtime.app.server.address(),
|
|
url = `http://127.0.0.1:${port}`;
|
|
runtime.setDevOrigin(url);
|
|
browser = await chromium.launch({
|
|
headless: true,
|
|
...(process.env.HOP_BROWSER_EXECUTABLE
|
|
? { executablePath: process.env.HOP_BROWSER_EXECUTABLE }
|
|
: {}),
|
|
});
|
|
const hostContext = await browser.newContext({ viewport: { width: 1440, height: 1050 } }),
|
|
guestContext = await browser.newContext({
|
|
viewport: { width: 390, height: 844 },
|
|
isMobile: true,
|
|
hasTouch: true,
|
|
});
|
|
const host = await hostContext.newPage(),
|
|
guest = await guestContext.newPage(),
|
|
errors = [];
|
|
for (const page of [host, guest]) page.on('pageerror', (error) => errors.push(error.message));
|
|
await Promise.all([host.goto(url), guest.goto(url)]);
|
|
async function rename(page, name) {
|
|
await page.locator('.device-name').click();
|
|
await page.getByRole('textbox', { name: '기기 이름' }).fill(name);
|
|
await page.getByRole('button', { name: '이름 저장' }).click();
|
|
await page.locator('.device-name').filter({ hasText: name }).waitFor();
|
|
}
|
|
await rename(host, '방장 Mac');
|
|
await rename(guest, '외부 기기');
|
|
await host.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
|
|
await host.getByRole('button', { name: '연결 공간 만들기' }).click();
|
|
await host.getByRole('checkbox', { name: '참여할 때 내 승인 받기' }).check();
|
|
await host.getByRole('button', { name: '만들기', exact: true }).click();
|
|
await host.locator('.invite-code').waitFor();
|
|
const code = (await host.locator('.invite-code').innerText()).replace(/\s/g, '');
|
|
await host.getByRole('button', { name: '대화상자 닫기' }).click();
|
|
await host.locator('.my-device-info .host-badge').waitFor();
|
|
await guest.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
|
|
await guest.getByRole('button', { name: '연결 공간 참여', exact: true }).click();
|
|
await guest.getByRole('textbox', { name: '연결 코드' }).fill(code);
|
|
await guest.getByRole('button', { name: '참여하기', exact: true }).click();
|
|
await guest.locator('.room-leave').waitFor();
|
|
await guest.locator('.modal-backdrop').waitFor({ state: 'hidden' });
|
|
const guestCard = host.locator('.peer-entry').filter({ hasText: '외부 기기' });
|
|
await guestCard.getByText('승인 대기', { exact: true }).waitFor();
|
|
assert.equal(await guestCard.locator('.peer-card').isDisabled(), true);
|
|
await guestCard.getByRole('button', { name: '외부 기기 기기 더보기' }).click();
|
|
await guestCard.getByRole('menuitem', { name: '참여 승인', exact: true }).click();
|
|
await guestCard.getByText('온라인', { exact: true }).waitFor();
|
|
assert.equal(await guestCard.locator('.peer-card').isEnabled(), true);
|
|
const hostCard = guest.locator('.peer-entry').filter({ hasText: '방장 Mac' });
|
|
await hostCard.locator('.host-badge').waitFor();
|
|
assert.equal(await guest.getByRole('button', { name: '방장 Mac 기기 더보기' }).count(), 0);
|
|
for (const page of [host, guest]) {
|
|
assert.equal(await page.locator('.spaces-list,.space-member').count(), 0);
|
|
assert(!(await page.locator('body').innerText()).includes('참여 중'));
|
|
}
|
|
await guestCard.getByRole('button', { name: '외부 기기 기기 더보기' }).click();
|
|
const kick = guestCard.getByRole('menuitem', { name: '내보내기', exact: true });
|
|
await kick.waitFor();
|
|
const rgb = await kick.evaluate((node) => getComputedStyle(node).color);
|
|
const components = rgb.match(/\d+/g).map(Number);
|
|
assert(components[0] > components[1] && components[0] > components[2]);
|
|
await host.keyboard.press('Escape');
|
|
assert.equal(await host.locator('.device-menu').count(), 0);
|
|
await guestCard.getByRole('button', { name: '외부 기기 기기 더보기' }).click();
|
|
await host.locator('.section-heading h2').click();
|
|
assert.equal(await host.locator('.device-menu').count(), 0);
|
|
await guestCard.getByRole('button', { name: '외부 기기 기기 더보기' }).click();
|
|
await host.screenshot({ path: 'tests/e2e/device-menu-desktop.png', fullPage: true });
|
|
await guest.screenshot({ path: 'tests/e2e/device-menu-mobile.png', fullPage: true });
|
|
await guestCard.getByRole('menuitem', { name: '내보내기', exact: true }).click();
|
|
await guestCard.waitFor({ state: 'hidden' });
|
|
await hostCard.waitFor({ state: 'hidden' });
|
|
assert.equal(await guest.locator('.room-leave').count(), 0);
|
|
await host.getByRole('button', { name: '방 나가기', exact: true }).click();
|
|
await host.locator('.room-leave').waitFor({ state: 'hidden' });
|
|
await host.locator('.my-device-info .host-badge').waitFor({ state: 'hidden' });
|
|
assert.equal(
|
|
await guest.evaluate(() => document.documentElement.scrollWidth <= innerWidth),
|
|
true,
|
|
);
|
|
assert.deepEqual(errors, []);
|
|
console.log(
|
|
'PASS: HOST badge on current/peer devices; approval and red kick action in main card menu; non-host has no kick menu; Escape/outside close; no duplicate participant list; kick propagates to both browsers; space close and mobile viewport.',
|
|
);
|
|
} catch (error) {
|
|
console.error(error);
|
|
for (const context of browser?.contexts() ?? [])
|
|
for (const page of context.pages())
|
|
console.error((await page.locator('body').innerText()).slice(0, 4000));
|
|
throw error;
|
|
} finally {
|
|
await browser?.close();
|
|
await runtime.app.close();
|
|
await rm(folder, { recursive: true, force: true });
|
|
}
|