diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2620538..32d55ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,39 +2,47 @@ name: CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: test: + runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.49.0-jammy + image: mcr.microsoft.com/playwright:v1.40.1-jammy options: --ipc=host - runs-on: ubuntu-latest - + steps: - - uses: actions/checkout@v3 - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Lint - run: npm run lint - - - name: Run tests - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" npm test - env: - CI: true - DEBUG: pw:api,pw:webserver - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 \ No newline at end of file + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Lint + run: npm run lint + + - name: Run tests + run: npm test + env: + CI: true + DEBUG: pw:api,pw:webserver + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v3 + with: + name: playwright-report + path: | + playwright-report/ + test-results/ + retention-days: 30 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 059375d..46acd31 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,23 +6,27 @@ on: jobs: publish: + runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.49.0-jammy + image: mcr.microsoft.com/playwright:v1.40.1-jammy options: --ipc=host - runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Setup Node.js for NPM - uses: actions/setup-node@v3 + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' + cache: 'npm' - name: Install dependencies run: npm ci - + - name: Build run: npm run build @@ -30,12 +34,22 @@ jobs: run: npm run lint - name: Test - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" npm test + run: npm test env: CI: true DEBUG: pw:api,pw:webserver - - name: Publish - run: npm publish + - name: Publish to NPM + run: npm publish --access public env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v3 + with: + name: playwright-report + path: | + playwright-report/ + test-results/ + retention-days: 30 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ccf80a4..0000000 --- a/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -FROM mcr.microsoft.com/playwright:v1.49.0-jammy - -# Set working directory -WORKDIR /app - -# Copy package files first for better caching -COPY package*.json ./ -COPY tsconfig.json ./ - -# Install dependencies -RUN npm ci - -# Copy source files -COPY src ./src -COPY tests ./tests -COPY index.html ./ -COPY playwright.config.ts ./ - -# Build the project -RUN npm run build - -# Install only Chromium browser -RUN npx playwright install chromium - -# Set environment variables -ENV CI=true -ENV DEBUG=pw:api,pw:webserver - -# Command to run tests -CMD xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" \ - npx playwright test \ - --project=chromium \ - --reporter=list \ - --workers=1 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d87a40c..f5bb726 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@eslint/js": "^9.16.0", - "@playwright/test": "^1.40.0", + "@playwright/test": "^1.49.0", "@types/node": "^20.10.0", "@typescript-eslint/eslint-plugin": "^6.13.0", "@typescript-eslint/parser": "^6.13.0", @@ -25,7 +25,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@playwright/test": "^1.40.0" + "@playwright/test": "^1.49.0" } }, "node_modules/@eslint-community/eslint-utils": { diff --git a/package.json b/package.json index 5522d80..2c48f18 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,12 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "tsc", + "build": "tsc", "test": "playwright test", "test:report": "playwright show-report", - "test:docker": "docker build -t playwright-clipboard-tests . && docker run --rm playwright-clipboard-tests", "prepare": "npm run build", - "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"", - "lint:fix": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" --fix", + "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"playwright.config.ts\"", + "lint:fix": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"playwright.config.ts\" --fix", "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"", "serve": "http-server -p 8080", "preversion": "npm run lint && npm test", @@ -49,7 +48,7 @@ "LICENSE" ], "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "repository": { "type": "git", diff --git a/playwright.config.ts b/playwright.config.ts index 7432538..d855040 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -4,9 +4,7 @@ export default defineConfig({ testDir: './tests', fullyParallel: false, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: 1, - reporter: 'html', + reporter: 'list', timeout: 120000, expect: { timeout: 30000, @@ -16,7 +14,7 @@ export default defineConfig({ trace: 'on-first-retry', actionTimeout: 30000, navigationTimeout: 30000, - headless: true + headless: true, }, webServer: { command: 'npm run serve', @@ -31,25 +29,36 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'], + viewport: { width: 1280, height: 720 }, permissions: ['clipboard-read', 'clipboard-write'], - viewport: { width: 1280, height: 720 } }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'], - permissions: ['clipboard-read', 'clipboard-write'], - viewport: { width: 1280, height: 720 } + viewport: { width: 1280, height: 720 }, + launchOptions: { + firefoxUserPrefs: { + 'dom.events.testing.asyncClipboard': true, + 'dom.events.asyncClipboard.readText': true, + 'dom.events.asyncClipboard.clipboardItem': true, + 'dom.events.asyncClipboard.writeText': true, + 'permissions.default.clipboard-read': 1, + 'permissions.default.clipboard-write': 1, + }, + }, }, }, { name: 'webkit', use: { ...devices['Desktop Safari'], - permissions: ['clipboard-read', 'clipboard-write'], - viewport: { width: 1280, height: 720 } + viewport: { width: 1280, height: 720 }, + launchOptions: { + args: ['--enable-clipboard-read', '--enable-clipboard-write'], + }, }, - } + }, ], -}); \ No newline at end of file +}); diff --git a/tests/clipboard.spec.ts b/tests/clipboard.spec.ts index fb45dac..4f8b412 100644 --- a/tests/clipboard.spec.ts +++ b/tests/clipboard.spec.ts @@ -1,26 +1,33 @@ import { test, expect } from '@playwright/test'; import { PlaywrightClipboard } from '../src'; +// Helper function to normalize HTML for comparison +function normalizeHtml(html: string): string { + return html + .replace(/\s+/g, ' ') + .replace(/>\s+<') + .replace(/ /g, ' ') + .trim(); +} + test.describe('PlaywrightClipboard', () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, browserName, page }) => { + // Grant clipboard permissions based on browser + if (browserName === 'chromium') { + await context.grantPermissions(['clipboard-read', 'clipboard-write']); + } + // Navigate to the test page await page.goto('http://localhost:8080'); - // Clear both source and target elements - await page.fill('#source', ''); - await page.fill('#target', ''); - await page.fill('#text', ''); - await page.fill('#editor', ''); - await page.evaluate(() => { - const richSource = document.querySelector('#richSource'); - const richTarget = document.querySelector('#richTarget'); - if (richSource) richSource.innerHTML = ''; - if (richTarget) richTarget.innerHTML = ''; - }); }); test('should perform basic copy/paste operations', async ({ page }): Promise => { const clipboard = new PlaywrightClipboard(page); const initialText = 'Hello World'; + + // Set initial text in source await page.fill('#source', initialText); + expect(await page.inputValue('#source')).toBe(initialText); + await clipboard.copy('#source'); await clipboard.paste('#target'); @@ -32,10 +39,6 @@ test.describe('PlaywrightClipboard', () => { const clipboard = new PlaywrightClipboard(page); const initialText = 'Test Content'; - // Clear source and target first - await page.fill('#source', ''); - await page.fill('#target', ''); - // Then set test content await page.fill('#source', initialText); await clipboard.cut('#source'); await clipboard.paste('#target'); @@ -63,7 +66,6 @@ test.describe('PlaywrightClipboard', () => { await page.fill('#editor', testText); await clipboard.copyBetweenWords('#editor', 2, 3); // Copy "brown fox" - await page.fill('#target', ''); // Clear target first await clipboard.paste('#target'); const targetContent = await page.inputValue('#target'); @@ -74,15 +76,6 @@ test.describe('PlaywrightClipboard', () => { const clipboard = new PlaywrightClipboard(page); const richText = 'This is bold text'; - // Clear both source and target - await page.evaluate(() => { - const source = document.querySelector('#richSource'); - const target = document.querySelector('#richTarget'); - if (source) source.innerHTML = ''; - if (target) target.innerHTML = ''; - }); - - // Set test content await page.evaluate(text => { const editor = document.querySelector('#richSource') as HTMLElement; editor.innerHTML = text; @@ -91,12 +84,24 @@ test.describe('PlaywrightClipboard', () => { await clipboard.copyRichText('#richSource'); await clipboard.pasteRichText('#richTarget'); + const result = await page.evaluate(() => { + const target = document.querySelector('#richTarget') as HTMLElement; + return target.innerHTML.trim(); + }); + if (browserName === 'webkit') { - const plainText = await page.$eval('#richTarget', el => el.textContent?.trim() || ''); - expect(plainText).toBe('This is bold text'); + const plainText = await page.evaluate(() => { + const target = document.querySelector('#richTarget') as HTMLElement; + return target.textContent?.trim() || ''; + }); + const normalizedExpected = 'This is bold text'; + const normalizedActual = plainText.replace(/\s+/g, ' ').trim(); + expect(normalizedActual).toBe(normalizedExpected); } else { - const html = await page.$eval('#richTarget', el => el.innerHTML.trim()); - expect(html).toContain('bold'); + // Chromium should preserve the HTML structure + const normalizedExpected = richText.replace(/\s+/g, ' ').trim(); + const normalizedResult = normalizeHtml(result); + expect(normalizedResult).toBe(normalizedExpected); } }); @@ -124,11 +129,19 @@ test.describe('PlaywrightClipboard', () => { const clipboard = new PlaywrightClipboard(page); const testText = 'Line 1\nLine 2\nLine 3'; - await page.fill('#editor', testText); + // Use textarea for multiline text + await page.evaluate(text => { + const textarea = document.querySelector('#editor') as HTMLTextAreaElement; + textarea.value = text; + }, testText); + await clipboard.copy('#editor'); await clipboard.paste('#target'); - const result = await page.inputValue('#target'); + const result = await page.evaluate(() => { + const target = document.querySelector('#target') as HTMLInputElement; + return target.value; + }); expect(result).toBe(testText); }); }); diff --git a/tsconfig.json b/tsconfig.json index f09a278..12ff1ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,6 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, - "include": ["src/**/*", "tests/**/*"], + "include": ["src/**/*", "tests/**/*", "playwright.config.ts"], "exclude": ["node_modules", "dist"] } \ No newline at end of file