106 lines
4.3 KiB
JavaScript
106 lines
4.3 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import process from 'node:process';
|
|
import { createServer } from 'vite';
|
|
|
|
const { chromium } = await import(
|
|
process.env.PLAYWRIGHT_MODULE ?? 'playwright'
|
|
);
|
|
const server = await createServer({
|
|
configFile: false,
|
|
optimizeDeps: { noDiscovery: true, include: [] },
|
|
server: { host: '127.0.0.1', port: 0, hmr: false },
|
|
appType: 'custom',
|
|
});
|
|
server.middlewares.use((request, response, next) => {
|
|
if (request.url !== '/') {
|
|
return next();
|
|
}
|
|
response.setHeader('Content-Type', 'text/html');
|
|
response.end(`<!doctype html><html><body>
|
|
<div role="dialog"><input id="draft" value="unsaved draft"><button id="copy">Copy</button></div>
|
|
<script type="module">
|
|
import { copyText } from '/resources/js/lib/clipboard.ts';
|
|
window.originalClipboard = navigator.clipboard;
|
|
window.copyText = copyText;
|
|
document.querySelector('#copy').onclick = async () => {
|
|
window.result = 'pending';
|
|
const input = document.querySelector('#draft');
|
|
input.focus(); input.setSelectionRange(1, 5);
|
|
try { await copyText('kan55 selected text'); window.result = 'copied'; }
|
|
catch { window.result = 'failed'; }
|
|
};
|
|
</script></body></html>`);
|
|
});
|
|
await server.listen();
|
|
const browser = await chromium.launch({ channel: 'chrome', headless: true });
|
|
const context = await browser.newContext({
|
|
permissions: ['clipboard-read', 'clipboard-write'],
|
|
});
|
|
const page = await context.newPage();
|
|
try {
|
|
await page.goto(`http://127.0.0.1:${server.httpServer.address().port}/`);
|
|
await page.waitForFunction(() => typeof window.copyText === 'function');
|
|
for (const mode of ['modern', 'absent', 'denied']) {
|
|
await page.evaluate(async (mode) => {
|
|
Object.defineProperty(navigator, 'clipboard', {
|
|
configurable: true,
|
|
value:
|
|
mode === 'modern'
|
|
? window.originalClipboard
|
|
: mode === 'absent'
|
|
? undefined
|
|
: {
|
|
writeText: () =>
|
|
Promise.reject(
|
|
new DOMException(
|
|
'Denied',
|
|
'NotAllowedError',
|
|
),
|
|
),
|
|
},
|
|
});
|
|
await window.originalClipboard.writeText('previous');
|
|
// Simulate the modal's focus boundary used by the two-factor setup dialog.
|
|
document.onfocusin = (event) => {
|
|
if (!event.target.closest('[role="dialog"]')) {
|
|
document.querySelector('#draft').focus();
|
|
}
|
|
};
|
|
}, mode);
|
|
await page.getByRole('button', { name: 'Copy', exact: true }).click();
|
|
await page.waitForFunction(() => window.result === 'copied');
|
|
assert.equal(
|
|
await page.evaluate(() => window.originalClipboard.readText()),
|
|
'kan55 selected text',
|
|
mode,
|
|
);
|
|
assert.deepEqual(
|
|
await page.evaluate(() => {
|
|
const input = document.querySelector('#draft');
|
|
return [
|
|
document.activeElement === input,
|
|
input.value,
|
|
input.selectionStart,
|
|
input.selectionEnd,
|
|
document.querySelectorAll('textarea').length,
|
|
];
|
|
}),
|
|
[true, 'unsaved draft', 1, 5, 0],
|
|
mode,
|
|
);
|
|
}
|
|
await page.evaluate(() => {
|
|
document.execCommand = () => false;
|
|
});
|
|
await page.getByRole('button', { name: 'Copy', exact: true }).click();
|
|
await page.waitForFunction(() => window.result === 'failed');
|
|
assert.equal(await page.locator('textarea').count(), 0);
|
|
assert.equal(await page.locator('#draft').inputValue(), 'unsaved draft');
|
|
console.log(
|
|
'Clipboard browser checks passed: async API, unavailable/denied API fallback, modal focus, input selection and failure cleanup.',
|
|
);
|
|
} finally {
|
|
await browser.close();
|
|
await server.close();
|
|
}
|