From a54975d221b209b3dd4b635279e8cdde188e55b1 Mon Sep 17 00:00:00 2001 From: vrknetha Date: Sun, 8 Dec 2024 01:03:17 +0530 Subject: [PATCH] updates README and CONTRIBUTING --- CONTRIBUTING.md | 73 ++++++++++++++++ README.md | 222 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 241 insertions(+), 54 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7f391a2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,73 @@ +# Contributing to playwright-clipboard + +We love your input! We want to make contributing to playwright-clipboard as easy and transparent as possible, whether it's: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features +- Becoming a maintainer + +## We Develop with GitHub +We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. + +## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html) +Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests: + +1. Fork the repo and create your branch from `main`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. Issue that pull request! + +## Any contributions you make will be under the MIT Software License +In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using GitHub's [issue tracker](https://github.com/vrknetha/playwright-clipboard/issues) +We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/vrknetha/playwright-clipboard/issues/new); it's that easy! + +## Write bug reports with detail, background, and sample code + +**Great Bug Reports** tend to have: + +- A quick summary and/or background +- Steps to reproduce + - Be specific! + - Give sample code if you can. +- What you expected would happen +- What actually happens +- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) + +## Development Process + +1. Clone the repository: +```bash +git clone https://github.com/vrknetha/playwright-clipboard.git +cd playwright-clipboard +``` + +2. Install dependencies: +```bash +npm install +``` + +3. Run tests: +```bash +npm test +``` + +4. Run linting: +```bash +npm run lint +``` + +## Testing +We use Playwright Test for our test suite. Please ensure all tests pass before submitting a PR: + +```bash +npm test +``` + +## License +By contributing, you agree that your contributions will be licensed under its MIT License. \ No newline at end of file diff --git a/README.md b/README.md index a84da24..c2e9c6a 100644 --- a/README.md +++ b/README.md @@ -11,80 +11,166 @@ npm install --save-dev playwright-clipboard ## Features - Basic clipboard operations (copy, paste, cut) -- Text selection operations -- Word-level operations -- Clipboard content management -- Cross-browser compatibility -- TypeScript support +- Rich text operations with HTML preservation +- Text selection operations with character and word-level control +- Word-level operations for precise text manipulation +- Clipboard content management with direct access +- Cross-browser compatibility (Chromium, Firefox, WebKit) +- TypeScript support with full type definitions +- Comprehensive error handling +- Fallback mechanisms for browser-specific limitations -## Usage +## Examples ### Basic Operations ```typescript -import { test } from '@playwright/test'; +import { test, expect } from '@playwright/test'; import { PlaywrightClipboard } from 'playwright-clipboard'; -test('basic clipboard operations', async ({ page }) => { +test('basic copy/paste operations', async ({ page }) => { const clipboard = new PlaywrightClipboard(page); - - // Copy text from source + + await page.goto('http://localhost:8080'); + + // Copy from source input and paste to target await clipboard.copy('#source'); + await clipboard.paste('#target'); + + const result = await page.inputValue('#target'); + expect(result).toBe('Hello World'); +}); + +test('cut operations', async ({ page }) => { + const clipboard = new PlaywrightClipboard(page); + const initialText = 'Test Content'; - // Paste text to target + await page.fill('#source', initialText); + await clipboard.cut('#source'); await clipboard.paste('#target'); - // Cut text - await clipboard.cut('#editor'); + // Source should be empty after cut + const sourceContent = await page.inputValue('#source'); + expect(sourceContent).toBe(''); - // Get clipboard content - const content = await clipboard.getClipboardContent(); + // Target should have the cut content + const targetContent = await page.inputValue('#target'); + expect(targetContent).toBe(initialText); +}); +``` + +### Rich Text Operations - // Set clipboard content - await clipboard.setClipboardContent('Hello World'); +```typescript +test('rich text operations', async ({ page, browserName }) => { + const clipboard = new PlaywrightClipboard(page); + + // Copy rich text content + await clipboard.copyRichText('#richSource'); + await clipboard.pasteRichText('#richTarget'); + + const result = await page.$eval('#richTarget', el => el.innerHTML.trim()); + + if (browserName === 'webkit') { + // WebKit may handle rich text differently + const plainText = await page.$eval('#richTarget', + el => el.textContent?.trim() || ''); + expect(plainText).toBe('This is bold text'); + } else { + // Other browsers preserve HTML structure + expect(result).toContain('bold'); + } }); ``` -### Word-Level Operations +### Text Selection ```typescript -test('word-level operations', async ({ page }) => { +test('text selection', async ({ page }) => { const clipboard = new PlaywrightClipboard(page); + const testText = 'Select this text'; - // Copy specific words - await clipboard.copyBetweenWords('#editor', 1, 3); + await page.fill('#text', testText); + await clipboard.select('#text', 7, 11); // Selects "this" + + const selectedText = await clipboard.getSelectedText(); + expect(selectedText).toBe('this'); +}); +``` + +### Word Operations - // Paste after a specific word - await clipboard.pasteAfterWord('#editor', 2); +```typescript +test('word operations', async ({ page }) => { + const clipboard = new PlaywrightClipboard(page); + const testText = 'The quick brown fox jumps'; - // Paste before a specific word - await clipboard.pasteBeforeWord('#editor', 0); + await page.fill('#editor', testText); + await clipboard.copyBetweenWords('#editor', 2, 3); // Copy "brown fox" + await clipboard.paste('#target'); - // Replace a word - await clipboard.replaceWord('#editor', 1); + const targetContent = await page.inputValue('#target'); + expect(targetContent).toBe('brown fox'); }); ``` -### Selection Operations +### Special Characters and Multiline Text ```typescript -test('selection operations', async ({ page }) => { +test('special characters', async ({ page }) => { const clipboard = new PlaywrightClipboard(page); + const testText = 'Special @#$% characters!'; - // Select all text - await clipboard.selectAll('#editor'); + await page.fill('#source', testText); + await clipboard.copy('#source'); + await clipboard.paste('#target'); + + const result = await page.inputValue('#target'); + expect(result).toBe(testText); +}); - // Select specific range - await clipboard.select('#editor', 0, 10); +test('multiline text', async ({ page }) => { + const clipboard = new PlaywrightClipboard(page); + const testText = 'Line 1\nLine 2\nLine 3'; - // Select word range - await clipboard.selectWordRange('#editor', 1, 3); + await page.fill('#editor', testText); + await clipboard.copy('#editor'); + await clipboard.paste('#target'); - // Get selected text - const selectedText = await clipboard.getSelectedWords(); + const result = await page.inputValue('#target'); + expect(result).toBe(testText); }); ``` +## Browser Support + +| Feature | Chromium | Firefox | WebKit (Safari) | +|---------|----------|---------|-----------------| +| Basic Operations | Native Clipboard API | Keyboard Shortcuts | Clipboard API + Fallback | +| Rich Text | Full Support | Full Support | Full Support* | +| Word Operations | Full Support | Full Support | Full Support | + +\* Uses `execCommand` fallback for some operations + +## Technical Details + +The package implements several fallback mechanisms to ensure consistent behavior across browsers: + +1. **Clipboard Access**: + - Primary: Native Clipboard API + - Fallback: Keyboard shortcuts (Meta+C, Meta+V, Meta+X) + - Last Resort: execCommand for WebKit + +2. **Text Selection**: + - Input/Textarea: Uses `setSelectionRange` + - Rich Text: Uses `Range` and `Selection` APIs + - Word-Level: Custom boundary detection + +3. **Rich Text Handling**: + - Preserves HTML structure where supported + - Graceful degradation to plain text + - Browser-specific optimizations + ## API Reference ### Constructor @@ -93,41 +179,69 @@ test('selection operations', async ({ page }) => { constructor(page: Page, options?: ClipboardOptions) ``` +Options: +- `timeout?: number` - Operation timeout in milliseconds (default: 5000) + ### Methods -- `copy(selector: string): Promise` -- `paste(selector: string): Promise` -- `cut(selector: string): Promise` -- `getClipboardContent(): Promise` -- `setClipboardContent(text: string): Promise` -- `selectAll(selector: string): Promise` -- `select(selector: string, start: number, end: number): Promise` +#### Basic Operations +- `copy(selector: string): Promise` - Copy content from element +- `paste(selector: string): Promise` - Paste content to element +- `cut(selector: string): Promise` - Cut content from element + +#### Rich Text Operations +- `copyRichText(selector: string): Promise` - Copy with HTML preservation +- `pasteRichText(selector: string): Promise` - Paste with HTML preservation +- `cutRichText(selector: string): Promise` - Cut with HTML preservation + +#### Selection Operations +- `selectAll(selector: string): Promise` - Select all content +- `select(selector: string, start: number, end: number): Promise` - Select range +- `getSelectedText(): Promise` - Get selected text + +#### Word Operations - `copyBetweenWords(selector: string, startWordIndex: number, endWordIndex: number): Promise` - `pasteAfterWord(selector: string, wordIndex: number): Promise` - `pasteBeforeWord(selector: string, wordIndex: number): Promise` - `replaceWord(selector: string, wordIndex: number): Promise` - `selectWordRange(selector: string, startIndex: number, endIndex: number): Promise` - `getSelectedWords(): Promise` -- `getWordBoundaries(selector: string, wordIndex: number): Promise<{ start: number; end: number }>` + +#### Clipboard Management +- `getClipboardContent(): Promise` - Get current clipboard content +- `setClipboardContent(text: string): Promise` - Set clipboard content ## Error Handling The package includes comprehensive error handling with specific error types: -- `COPY_ERROR` -- `PASTE_ERROR` -- `CLIPBOARD_ACCESS_DENIED` -- `SELECTION_FAILED` -- `INVALID_WORD_INDEX` -- `WORD_BOUNDARY_ERROR` -- `EMPTY_SELECTION` -- `PASTE_POSITION_ERROR` +```typescript +export enum ClipboardError { + COPY_ERROR = 'Copy operation failed', + PASTE_ERROR = 'Paste operation failed', + CLIPBOARD_ACCESS_DENIED = 'Cannot access clipboard', + SELECTION_FAILED = 'Text selection failed', + INVALID_WORD_INDEX = 'Invalid word index specified', + WORD_BOUNDARY_ERROR = 'Cannot determine word boundaries', + EMPTY_SELECTION = 'No text selected', + PASTE_POSITION_ERROR = 'Invalid paste position' +} +``` ## Requirements - Node.js >= 18.0.0 - Playwright >= 1.40.0 +- TypeScript >= 4.5.0 (for TypeScript users) + +## Contributing + +Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. ## License -MIT \ No newline at end of file +MIT + +## Support + +For bugs and feature requests, please [open an issue](https://github.com/vrknetha/playwright-clipboard/issues). \ No newline at end of file