Simplify connection flow, mobile transfer UI, and header actions
This commit is contained in:
@@ -48,18 +48,20 @@ try {
|
||||
}
|
||||
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: '다른 기기 연결', 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: 'QR · 코드로 연결', exact: true }).click();
|
||||
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.getByRole('button', { name: '참여하기', exact: true }).click();
|
||||
await guest.locator('.room-leave').waitFor();
|
||||
await guest.getByRole('dialog', { name: 'QR · 코드로 연결' }).waitFor({ state: 'hidden' });
|
||||
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);
|
||||
|
||||
@@ -46,6 +46,27 @@ try {
|
||||
});
|
||||
await page.goto(url);
|
||||
await page.locator('.app-shell[data-online="true"]').waitFor();
|
||||
assert.equal(
|
||||
await page
|
||||
.locator('.header-actions > .theme-toggle,.header-actions > .pwa-install-button')
|
||||
.count(),
|
||||
0,
|
||||
);
|
||||
const more = page.getByRole('button', { name: '더보기', exact: true });
|
||||
await more.click();
|
||||
await page.getByRole('menu', { name: '더보기', exact: true }).waitFor();
|
||||
await page.keyboard.press('End');
|
||||
assert.equal(await page.evaluate(() => document.activeElement.textContent.trim()), '서비스 정보');
|
||||
await page.keyboard.press('ArrowUp');
|
||||
assert.match(await page.evaluate(() => document.activeElement.textContent.trim()), /테마/);
|
||||
await page.keyboard.press('Escape');
|
||||
assert.equal(await more.getAttribute('aria-expanded'), 'false');
|
||||
await more.click();
|
||||
await page.getByRole('menuitem', { name: '서비스 정보', exact: true }).click();
|
||||
await page.getByRole('dialog', { name: '서비스 정보', exact: true }).waitFor();
|
||||
assert.equal(await more.getAttribute('aria-expanded'), 'false');
|
||||
await page.getByRole('button', { name: '대화상자 닫기', exact: true }).click();
|
||||
|
||||
assert.equal(await page.getByText('어디로 보낼까요?', { exact: true }).count(), 0);
|
||||
assert.equal(await page.locator('.devices-stage .spin,.section-heading h2 span').count(), 0);
|
||||
await page.screenshot({ path: 'tests/e2e/main-empty-device.png', fullPage: true });
|
||||
@@ -165,6 +186,65 @@ try {
|
||||
assert.equal(linkBounds.width, bounds.width);
|
||||
await page.screenshot({ path: 'tests/e2e/send-sheet.png' });
|
||||
await page.getByRole('button', { name: '전송 화면 닫기' }).click();
|
||||
await page.locator('.footer-device-summary').click();
|
||||
const settings = page.getByRole('dialog', { name: '설정', exact: true });
|
||||
assert.equal(
|
||||
await page
|
||||
.getByLabel('기기 이름', { exact: true })
|
||||
.evaluate((el) => document.activeElement === el),
|
||||
false,
|
||||
'모바일 설정에서 키보드를 자동으로 열지 않음',
|
||||
);
|
||||
assert.equal(
|
||||
await page
|
||||
.getByLabel('기기 이름', { exact: true })
|
||||
.evaluate((el) => getComputedStyle(el).fontSize),
|
||||
'16px',
|
||||
);
|
||||
const settingsBounds = await settings.boundingBox();
|
||||
assert(Math.abs(settingsBounds.y + settingsBounds.height - 900) < 2, '모바일 설정 하단 시트');
|
||||
await page.getByLabel('기기 이름', { exact: true }).focus();
|
||||
await page.evaluate(() => {
|
||||
Object.defineProperty(visualViewport, 'height', { configurable: true, value: 480 });
|
||||
visualViewport.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => document.documentElement.style.getPropertyValue('--hop-visible-height') === '480px',
|
||||
);
|
||||
const keyboardBounds = await settings.boundingBox();
|
||||
assert(
|
||||
keyboardBounds.y >= 0 && Math.abs(keyboardBounds.y + keyboardBounds.height - 480) < 2,
|
||||
'키보드가 열린 가시 영역 안의 설정 시트',
|
||||
);
|
||||
await page.screenshot({ path: 'tests/e2e/device-settings-mobile-keyboard.png' });
|
||||
await page.evaluate(() => {
|
||||
delete visualViewport.height;
|
||||
visualViewport.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
await page.getByRole('button', { name: '대화상자 닫기', exact: true }).click();
|
||||
await page.getByRole('button', { name: '로그인', exact: false }).first().click();
|
||||
for (const input of [
|
||||
page.getByLabel('이메일', { exact: true }),
|
||||
page.getByLabel('비밀번호', { exact: true }),
|
||||
]) {
|
||||
assert.equal(await input.evaluate((el) => getComputedStyle(el).fontSize), '16px');
|
||||
await input.focus();
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth),
|
||||
true,
|
||||
);
|
||||
}
|
||||
await page.screenshot({ path: 'tests/e2e/login-mobile.png' });
|
||||
await more.click();
|
||||
const menuBounds = await page.getByRole('menu', { name: '더보기', exact: true }).boundingBox();
|
||||
assert(
|
||||
menuBounds.x >= 0 && menuBounds.x + menuBounds.width <= 390,
|
||||
'모바일 더보기 메뉴 가로 넘침 없음',
|
||||
);
|
||||
await page.screenshot({ path: 'tests/e2e/header-more-mobile.png' });
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await page.getByRole('button', { name: 'hop. 홈', exact: true }).click();
|
||||
await page.setViewportSize({ width: 1280, height: 1000 });
|
||||
await page.locator('.peer-card').first().click();
|
||||
const dialog = await page.locator('.send-dialog').boundingBox();
|
||||
@@ -186,8 +266,32 @@ try {
|
||||
await page.getByLabel('다른 기기에서 발견 허용').check();
|
||||
await page.locator('.footer-device-summary').filter({ hasText: '발견 허용' }).waitFor();
|
||||
await page.getByRole('button', { name: '대화상자 닫기', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'QR · 코드로 연결', exact: true }).click();
|
||||
await page.getByRole('button', { name: '연결 공간 만들기' }).click();
|
||||
await page.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
|
||||
assert.equal(await page.locator('.connection-choice').count(), 2);
|
||||
assert.equal(await page.getByRole('checkbox', { name: '참여할 때 내 승인 받기' }).count(), 0);
|
||||
assert.equal(await page.getByLabel('연결 코드', { exact: true }).count(), 0);
|
||||
await page.screenshot({ path: 'tests/e2e/connection-choose-desktop.png' });
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.getByRole('button', { name: '연결 공간 참여', exact: true }).click();
|
||||
const joinInput = page.getByLabel('연결 코드', { exact: true });
|
||||
assert.equal(await joinInput.getAttribute('placeholder'), '초대 코드 4자리');
|
||||
assert.equal(await joinInput.evaluate((el) => getComputedStyle(el).fontSize), '16px');
|
||||
await joinInput.fill('ABC');
|
||||
assert.equal(
|
||||
await page.getByRole('button', { name: '참여하기', exact: true }).isDisabled(),
|
||||
true,
|
||||
);
|
||||
await joinInput.fill('ABCD');
|
||||
assert.equal(await page.getByRole('button', { name: '참여하기', exact: true }).isEnabled(), true);
|
||||
await page.screenshot({ path: 'tests/e2e/connection-join-mobile.png' });
|
||||
await page.getByRole('button', { name: '연결 방법 선택으로 돌아가기', exact: true }).click();
|
||||
await page.getByRole('button', { name: '연결 공간 만들기', exact: true }).click();
|
||||
await page.getByRole('checkbox', { name: '참여할 때 내 승인 받기' }).waitFor();
|
||||
assert.equal(await page.getByLabel('연결 코드', { exact: true }).count(), 0);
|
||||
await page.getByRole('button', { name: '만들기', exact: true }).click();
|
||||
assert.match(await page.locator('.invite-code').innerText(), /^[A-HJ-NP-Z2-9]{4}$/);
|
||||
assert.equal(await page.locator('.local-qr-note,.invite-display .spin').count(), 0);
|
||||
await page.screenshot({ path: 'tests/e2e/connection-invite-mobile.png' });
|
||||
const code = (await page.locator('.invite-code').innerText()).replace(/\s/g, '');
|
||||
const cookie = sockets[1].io.opts.extraHeaders.Cookie;
|
||||
const token = cookie.split('=')[1];
|
||||
@@ -202,13 +306,10 @@ try {
|
||||
body: JSON.stringify({ code }),
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
await page.getByRole('dialog', { name: 'QR · 코드로 연결' }).waitFor({ state: 'hidden' });
|
||||
await page.locator('.modal-backdrop').waitFor({ state: 'hidden' });
|
||||
await page.waitForFunction(() => document.querySelectorAll('.peer-card').length === 1);
|
||||
await page.screenshot({ path: 'tests/e2e/main-single-device.png', fullPage: true });
|
||||
assert.equal(
|
||||
await page.getByRole('button', { name: 'QR · 코드로 연결', exact: true }).count(),
|
||||
0,
|
||||
);
|
||||
assert.equal(await page.getByRole('button', { name: '다른 기기 연결', exact: true }).count(), 0);
|
||||
await page.getByRole('button', { name: '방 나가기', exact: true }).click();
|
||||
await page.waitForFunction(() => document.querySelectorAll('.peer-card').length === 12);
|
||||
assert.deepEqual(errors, []);
|
||||
|
||||
@@ -167,7 +167,7 @@ try {
|
||||
'권한 거절 후 반복 요청 없음',
|
||||
);
|
||||
assert.equal(
|
||||
await receiver.getByRole('button', { name: 'QR · 코드로 연결', exact: true }).count(),
|
||||
await receiver.getByRole('button', { name: '다른 기기 연결', exact: true }).count(),
|
||||
1,
|
||||
);
|
||||
assert.deepEqual(await sender.evaluate(() => caches.keys()), []);
|
||||
|
||||
@@ -83,16 +83,18 @@ try {
|
||||
await sender.getByRole('textbox', { name: '기기 이름' }).fill('보내는 Mac');
|
||||
await sender.getByRole('button', { name: '이름 저장' }).click();
|
||||
await sender.locator('.device-name').filter({ hasText: '보내는 Mac' }).waitFor();
|
||||
await sender.getByRole('button', { name: 'QR · 코드로 연결', exact: true }).click();
|
||||
await sender.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
|
||||
await sender.getByRole('button', { name: '연결 공간 만들기' }).click();
|
||||
await sender.getByRole('button', { name: '만들기', exact: true }).click();
|
||||
await sender.locator('.invite-code').waitFor();
|
||||
const code = (await sender.locator('.invite-code').innerText()).replace(/\s/g, '');
|
||||
await receiver.getByRole('button', { name: 'QR · 코드로 연결', exact: true }).click();
|
||||
await receiver.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
|
||||
await receiver.getByRole('button', { name: '연결 공간 참여', exact: true }).click();
|
||||
await receiver.getByRole('textbox', { name: '연결 코드' }).fill(code);
|
||||
await receiver.getByRole('button', { name: '참여', exact: true }).click();
|
||||
await receiver.getByRole('button', { name: '참여하기', exact: true }).click();
|
||||
await receiver.locator('.room-leave').waitFor();
|
||||
await sender.getByRole('dialog', { name: 'QR · 코드로 연결' }).waitFor({ state: 'hidden' });
|
||||
await receiver.getByRole('dialog', { name: 'QR · 코드로 연결' }).waitFor({ state: 'hidden' });
|
||||
await sender.locator('.modal-backdrop').waitFor({ state: 'hidden' });
|
||||
await receiver.locator('.modal-backdrop').waitFor({ state: 'hidden' });
|
||||
await sender.locator('.peer-card').first().waitFor();
|
||||
await sender.locator('.peer-card').first().click();
|
||||
const secret = 'https://example.com/?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E';
|
||||
@@ -262,6 +264,13 @@ try {
|
||||
uploadCaptured,
|
||||
new Promise((_, reject) => setTimeout(() => reject(Error('업로드 시작 대기 실패')), 10000)),
|
||||
]);
|
||||
await sender.setViewportSize({ width: 390, height: 844 });
|
||||
await sender.locator('.file-transfer-progress progress').waitFor();
|
||||
const listText = await sender.locator('.content-pane').innerText();
|
||||
assert.equal(listText.split('browser.txt').length - 1, 1, '파일명은 목록에 한 번만 표시');
|
||||
assert(!listText.includes('전송 중입니다.'), '진행률이 있으면 전송 중 안내를 반복하지 않음');
|
||||
assert.equal(await sender.locator('.composer-progress').count(), 0);
|
||||
await sender.screenshot({ path: 'tests/e2e/file-progress-mobile.png' });
|
||||
await sender.locator('.send-button').filter({ hasText: '전송 취소' }).click();
|
||||
releaseUpload();
|
||||
await receiver.locator('.receiving-card').waitFor({ state: 'hidden' });
|
||||
@@ -277,14 +286,17 @@ try {
|
||||
await sender.evaluate(() => getComputedStyle(document.documentElement).backgroundColor),
|
||||
'rgb(0, 0, 0)',
|
||||
);
|
||||
await sender.setViewportSize({ width: 1440, height: 1050 });
|
||||
await sender.screenshot({ path: 'tests/e2e/desktop.png', fullPage: true });
|
||||
await sender.getByRole('button', { name: '라이트 테마로 전환', exact: true }).click();
|
||||
await sender.getByRole('button', { name: '더보기', exact: true }).click();
|
||||
await sender.getByRole('menuitem', { name: '라이트 테마로 전환', exact: true }).click();
|
||||
assert.equal(
|
||||
await sender.evaluate(() => getComputedStyle(document.documentElement).backgroundColor),
|
||||
'rgb(250, 250, 250)',
|
||||
);
|
||||
await sender.screenshot({ path: 'tests/e2e/light-desktop.png', fullPage: true });
|
||||
await sender.getByRole('button', { name: '다크 테마로 전환', exact: true }).click();
|
||||
await sender.getByRole('button', { name: '더보기', exact: true }).click();
|
||||
await sender.getByRole('menuitem', { name: '다크 테마로 전환', exact: true }).click();
|
||||
await receiver.screenshot({ path: 'tests/e2e/mobile.png', fullPage: true });
|
||||
assert.equal(
|
||||
await receiver.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth),
|
||||
@@ -303,11 +315,12 @@ try {
|
||||
await urlReceiver.goto(url);
|
||||
await urlReceiver.locator('.device-name').waitFor();
|
||||
const receiverCode = (await urlReceiver.locator('.footer-device-code').innerText()).trim();
|
||||
await urlReceiver.getByRole('button', { name: 'QR · 코드로 연결', exact: true }).click();
|
||||
await urlReceiver.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
|
||||
await urlReceiver.getByRole('button', { name: '연결 공간 참여', exact: true }).click();
|
||||
await urlReceiver.getByRole('textbox', { name: '연결 코드' }).fill(code);
|
||||
await urlReceiver.getByRole('button', { name: '참여', exact: true }).click();
|
||||
await urlReceiver.getByRole('button', { name: '참여하기', exact: true }).click();
|
||||
await urlReceiver.locator('.room-leave').waitFor();
|
||||
await urlReceiver.getByRole('dialog', { name: 'QR · 코드로 연결' }).waitFor({ state: 'hidden' });
|
||||
await urlReceiver.locator('.modal-backdrop').waitFor({ state: 'hidden' });
|
||||
await second.locator('.peer-card').filter({ hasText: receiverCode }).waitFor();
|
||||
await second.evaluate(() => {
|
||||
window.__hopClipboard = '일반 텍스트는 전송하지 않습니다.';
|
||||
|
||||
@@ -94,15 +94,17 @@ try {
|
||||
await navigate(sender, '기기');
|
||||
await navigate(receiver, '기기');
|
||||
assert.equal(await sender.locator('.hero').count(), 0);
|
||||
await sender.getByRole('button', { name: 'QR · 코드로 연결', exact: true }).click();
|
||||
await sender.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
|
||||
await sender.getByRole('button', { name: '연결 공간 만들기' }).click();
|
||||
await sender.getByRole('button', { name: '만들기', exact: true }).click();
|
||||
const code = (await sender.locator('.invite-code').innerText()).replace(/\s/g, '');
|
||||
await sender.getByRole('button', { name: '대화상자 닫기' }).click();
|
||||
await receiver.getByRole('button', { name: 'QR · 코드로 연결', exact: true }).click();
|
||||
await receiver.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
|
||||
await receiver.getByRole('button', { name: '연결 공간 참여', exact: true }).click();
|
||||
await receiver.getByRole('textbox', { name: '연결 코드' }).fill(code);
|
||||
await receiver.getByRole('button', { name: '참여', exact: true }).click();
|
||||
await receiver.getByRole('button', { name: '참여하기', exact: true }).click();
|
||||
await receiver.locator('.room-leave').waitFor();
|
||||
await receiver.getByRole('dialog', { name: 'QR · 코드로 연결' }).waitFor({ state: 'hidden' });
|
||||
await receiver.locator('.modal-backdrop').waitFor({ state: 'hidden' });
|
||||
await sender.locator('.peer-card').first().click();
|
||||
await sender.getByRole('button', { name: '대신 링크 붙여넣기', exact: true }).click();
|
||||
await sender.getByLabel('보낼 URL').fill('https://example.com/received');
|
||||
|
||||
@@ -35,9 +35,16 @@ try {
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.addInitScript(() => {
|
||||
window.addEventListener('beforeinstallprompt', (event) => {
|
||||
if (!event.__hopTest) { event.preventDefault(); event.stopImmediatePropagation(); }
|
||||
}, { capture: true });
|
||||
window.addEventListener(
|
||||
'beforeinstallprompt',
|
||||
(event) => {
|
||||
if (!event.__hopTest) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
},
|
||||
{ capture: true },
|
||||
);
|
||||
});
|
||||
await page.goto(url);
|
||||
await page.locator('.app-shell[data-online="true"]').waitFor();
|
||||
@@ -48,7 +55,8 @@ try {
|
||||
assert.equal((await context.request.get(url + icon.src)).status(), 200);
|
||||
await page.waitForFunction(() => navigator.serviceWorker.controller !== null);
|
||||
assert.deepEqual(await page.evaluate(() => caches.keys()), []);
|
||||
const install = page.getByRole('button', { name: 'hop. 앱 설치', exact: true });
|
||||
await page.getByRole('button', { name: '더보기', exact: true }).click();
|
||||
const install = page.getByRole('menuitem', { name: 'hop. 앱 설치', exact: true });
|
||||
await install.click();
|
||||
await page.getByRole('dialog', { name: '앱 설치 안내' }).waitFor();
|
||||
assert.match(await page.locator('.install-help').innerText(), /Safari.*공유/s);
|
||||
@@ -67,6 +75,7 @@ try {
|
||||
event.userChoice = Promise.resolve({ outcome: 'accepted' });
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
await page.getByRole('button', { name: '더보기', exact: true }).click();
|
||||
await install.click();
|
||||
assert.equal(await page.evaluate(() => window.__installCalls), 1);
|
||||
await install.waitFor({ state: 'hidden' });
|
||||
|
||||
Reference in New Issue
Block a user