Answer
Scenario: Testing Clipboard Copy Functionality
Many modern apps have "Copy to Clipboard" buttons. Testing clipboard access requires browser permissions.
Method 1 — Grant Clipboard Permission and Read
TypeScript
import { test, expect } from '@playwright/test';
test('copy to clipboard button copies the correct text', async ({ page, context }) => {
// Grant clipboard-read permission
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
await page.goto('/share');
// Get the text that should be copied
const expectedUrl = await page.locator('.share-url').textContent();
// Click the copy button
await page.getByRole('button', { name: 'Copy Link' }).click();
// Verify visual feedback (most apps show this)
await expect(page.getByRole('button', { name: 'Copied!' })).toBeVisible();
// Read from clipboard to verify what was actually copied
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardText).toBe(expectedUrl?.trim());
});
Method 2 — Mock the Clipboard API
TypeScript
test('copy button uses clipboard API', async ({ page }) => {
// Mock clipboard.writeText to capture what was written
await page.addInitScript(() => {
let copiedText = '';
Object.defineProperty(navigator, 'clipboard', {
value: {
writeText: (text: string) => {
copiedText = text;
(window as any).__copiedText = text;
return Promise.resolve();
},
readText: () => Promise.resolve(copiedText),
},
writable: true,
});
});
await page.goto('/api-docs');
await page.getByRole('button', { name: 'Copy API Key' }).click();
// Read what the mock captured
const copiedText = await page.evaluate(() => (window as any).__copiedText);
expect(copiedText).toMatch(/^[A-Za-z0-9]{32}$/); // API key format
});
Method 3 — Intercept the Paste (Write then Read)
TypeScript
test('copy and paste workflow', async ({ page, context }) => {
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
await page.goto('/code-editor');
// Select code snippet
const codeBlock = page.locator('.code-snippet').first();
await codeBlock.getByRole('button', { name: 'Copy' }).click();
// Navigate to another input and paste
const pasteTarget = page.locator('#code-input');
await pasteTarget.focus();
await page.keyboard.press('Control+V'); // Or Meta+V on Mac
// Verify pasted content
const pastedValue = await pasteTarget.inputValue();
expect(pastedValue).toContain('npm install playwright');
});
Method 4 — Test Keyboard Shortcut Copy
TypeScript
test('Ctrl+C copies selected text', async ({ page, context }) => {
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
await page.goto('/docs');
const paragraph = page.locator('p').first();
// Select all text in the paragraph
await paragraph.click({ clickCount: 3 }); // Triple-click to select all
// Copy with keyboard
await page.keyboard.press('Control+C');
// Read clipboard
const copied = await page.evaluate(() => navigator.clipboard.readText());
const expected = await paragraph.textContent();
expect(copied.trim()).toBe(expected?.trim());
});
Common Clipboard Test Patterns
TypeScript
// Verify the "Copied!" tooltip appears briefly
test('copy feedback tooltip', async ({ page, context }) => {
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
await page.goto('/profile');
await page.getByRole('button', { name: 'Copy ID' }).click();
// Verify tooltip appears
await expect(page.getByRole('tooltip')).toHaveText('Copied!');
// Verify tooltip disappears after 2 seconds
await expect(page.getByRole('tooltip')).not.toBeVisible({ timeout: 3000 });
});
Note: Playwright requires explicit
grantPermissions(['clipboard-read', 'clipboard-write'])since clipboard access is a browser security permission. Without it,navigator.clipboard.readText()will throw.
