Add plain text clipboard transfers and refine input feedback

This commit is contained in:
2026-10-13 14:07:03 +09:00
parent af004a2942
commit 8a8df417aa
32 changed files with 1263 additions and 197 deletions

View File

@@ -39,12 +39,46 @@ try {
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
value: {
readText: async () => window.__layoutClipboard ?? 'https://example.com/layout',
readText: async () => {
window.__layoutReads = (window.__layoutReads ?? 0) + 1;
return window.__layoutClipboard ?? 'https://example.com/layout';
},
writeText: async () => {},
},
});
});
let releaseStartup;
const startupGate = new Promise((resolve) => {
releaseStartup = resolve;
});
await page.route('**/api/device/snapshot', async (route) => {
await startupGate;
await route.continue();
});
await page.addInitScript(() => {
const query = navigator.permissions.query.bind(navigator.permissions);
Object.defineProperty(navigator, 'permissions', {
configurable: true,
value: {
query: (descriptor) =>
descriptor.name === 'clipboard-read' && window.__clipboardNeedsPaste
? Promise.reject(new TypeError('unsupported clipboard permission'))
: query(descriptor),
},
});
});
await page.goto(url);
await page.locator('.initial-device-loading').waitFor();
const loadingCard = await page.locator('.initial-device-loading .peer-card').boundingBox();
const loadingText = await page.locator('.initial-device-loading p').boundingBox();
assert(
loadingText.y - loadingCard.y - loadingCard.height >= 12,
'초기 안내 문구와 스켈레톤 사이 간격',
);
await page.screenshot({ path: 'tests/e2e/initial-loading.png' });
releaseStartup();
await page.locator('.initial-device-loading').waitFor({ state: 'hidden' });
await page.unroute('**/api/device/snapshot');
await page.locator('.app-shell[data-online="true"]').waitFor();
await page.locator('.wordmark .brand-logo svg').waitFor();
const letterColor = () =>
@@ -178,7 +212,36 @@ try {
);
await page.screenshot({ path: `tests/e2e/layout-${width}.png`, fullPage: true });
}
await page.evaluate(() => {
window.__clipboardNeedsPaste = true;
});
const readsBeforeSelection = await page.evaluate(() => window.__layoutReads ?? 0);
await page.locator('.peer-card').first().click();
await page.getByLabel('보낼 텍스트', { exact: true }).waitFor();
assert.equal(
await page.evaluate(() => window.__layoutReads ?? 0),
readsBeforeSelection,
'명시적 확인이 필요한 환경에서는 카드 선택만으로 클립보드를 읽지 않음',
);
assert.equal(
await page
.locator('.peer-card')
.first()
.evaluate((el) => getComputedStyle(el).userSelect),
'none',
);
await page.getByRole('button', { name: '다시 가져오기', exact: true }).click();
await page.waitForFunction(
() => document.querySelector('.current-url')?.value === 'https://example.com/layout',
);
assert.equal(
await page.evaluate(() => window.__layoutReads),
readsBeforeSelection + 1,
'사용자가 붙여넣기를 선택하면 클립보드를 읽음',
);
await page.evaluate(() => {
window.__clipboardNeedsPaste = false;
});
const sheet = page.getByRole('dialog', { name: /에 보내기/ });
await sheet.waitFor();
const bounds = await sheet.boundingBox();
@@ -206,11 +269,11 @@ try {
await page.evaluate(() => {
window.__layoutClipboard = '';
});
await page.getByRole('button', { name: '대신 링크 붙여넣기', exact: true }).click();
await page.getByLabel('보낼 URL').waitFor();
await page.getByRole('button', { name: '대신 클립보드에서 가져오기', exact: true }).click();
await page.getByLabel('보낼 텍스트').waitFor();
assert.equal(await page.locator('.selected-files').count(), 0);
assert.equal(await page.locator('.content-submit').isDisabled(), true);
await page.getByLabel('보낼 URL').fill('https://example.com/manual');
await page.locator('.current-url, .current-text').fill('https://example.com/manual');
assert.equal(await page.locator('.content-submit').isEnabled(), true);
const linkBounds = await sheet.boundingBox();
assert.equal(linkBounds.height, bounds.height);
@@ -315,6 +378,7 @@ try {
assert(Math.abs(desktopInput.y - desktopSave.y) < 1, 'PC 입력창과 버튼이 같은 위치에 정렬됨');
await page.screenshot({ path: 'tests/e2e/device-settings-desktop.png' });
await page.getByLabel('기기 이름', { exact: true }).fill('나의 테스트 기기');
assert.equal(await page.locator('.name-count').innerText(), '9/40');
await page.getByRole('button', { name: '저장', exact: true }).click();
await page.locator('.footer-device-name').filter({ hasText: '나의 테스트 기기' }).waitFor();
await page.locator('.footer-device-summary').click();