Initial hop. implementation and UGREEN NAS deployment guide
This commit is contained in:
116
tests/e2e/device-menu.mjs
Normal file
116
tests/e2e/device-menu.mjs
Normal file
@@ -0,0 +1,116 @@
|
||||
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: 'QR · 코드로 연결', exact: true }).click();
|
||||
await host.getByRole('checkbox', { name: '참여할 때 내 승인 받기' }).check();
|
||||
await host.getByRole('button', { name: '연결 공간 만들기' }).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: 'QR · 코드로 연결', 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.getByRole('dialog', { name: 'QR · 코드로 연결' }).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('.stage-heading').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 });
|
||||
}
|
||||
Reference in New Issue
Block a user