diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..b2b78bb --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,31 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + env: + NEXT_PUBLIC_NODE_ENV: test + API_BASE: https://tts.richardr.dev/v1 + API_KEY: not-needed + run: npx playwright test --reporter=list,github,html + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 6c0e4a9..63da9a3 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,10 @@ next-env.d.ts # documents /docstore + +# Playwright +node_modules/ +/tests/results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index cc4af47..028b655 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openreader-webui", - "version": "0.1.3", + "version": "0.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openreader-webui", - "version": "0.1.3", + "version": "0.2.3", "dependencies": { "@headlessui/react": "^2.2.0", "@types/howler": "^2.2.12", @@ -31,6 +31,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@playwright/test": "^1.50.1", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -990,6 +991,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", + "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.50.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@react-aria/focus": { "version": "3.19.1", "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.1.tgz", @@ -5474,6 +5491,53 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", + "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.50.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", + "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", diff --git a/package.json b/package.json index 857390a..b924c4c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.2.3", "private": true, "scripts": { - "dev": "next dev --turbopack", + "dev": "next dev --turbopack -p 3003", "build": "next build", "start": "next start -p 3003", "lint": "next lint" @@ -32,6 +32,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@playwright/test": "^1.50.1", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..87c177a --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,83 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + timeout: 30 * 1000, + outputDir: './tests/results', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 1 : 0, + /* Opt out of parallel tests on CI. */ + workers: undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:3003', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run build && npm run start', + url: 'http://localhost:3003', + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, + }, +}); diff --git a/public/sample.epub b/public/sample.epub new file mode 100644 index 0000000..1c0c25b Binary files /dev/null and b/public/sample.epub differ diff --git a/src/components/EPUBViewer.tsx b/src/components/EPUBViewer.tsx index d8f6f55..110cd4d 100644 --- a/src/components/EPUBViewer.tsx +++ b/src/components/EPUBViewer.tsx @@ -71,6 +71,7 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
} key={'epub-reader'} location={locationRef.current} locationChanged={handleLocationChanged} diff --git a/tests/e2e.spec.ts b/tests/e2e.spec.ts new file mode 100644 index 0000000..baa0b28 --- /dev/null +++ b/tests/e2e.spec.ts @@ -0,0 +1,62 @@ +import { test, expect, Page } from '@playwright/test'; + +// Upload a sample epub or pdf +async function uploadFile(page: Page, filePath: string) { + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByText('Drop your file here, or click to select').click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(filePath); +} + +test.describe('Document Upload and Display', () => { + test.beforeEach(async ({ page }) => { + // Navigate to the home page before each test + await page.goto('http://localhost:3003'); + + // Click the "done" button to dismiss the welcome message + await page.getByText('Done').click(); + }); + + test.describe('PDF handling', () => { + test('should upload a PDF document', async ({ page }) => { + // Upload the file + await uploadFile(page, './public/sample.pdf'); + + // Verify upload success + await expect(page.getByText('sample.pdf')).toBeVisible(); + }); + + test('should display a PDF document', async ({ page }) => { + // Upload the file + await uploadFile(page, './public/sample.pdf'); + + // Click on the uploaded document + await page.getByText('sample.pdf').click(); + + // Verify PDF viewer is displayed + await expect(page.locator('.react-pdf__Document')).toBeVisible(); + await expect(page.locator('.react-pdf__Page')).toBeVisible(); + }); + }); + + test.describe('EPUB handling', () => { + test('should upload and display EPUB document', async ({ page }) => { + // Upload the file + await uploadFile(page, './public/sample.epub'); + + // Verify upload success + await expect(page.getByText('sample.epub')).toBeVisible(); + }); + + test('should display an EPUB document', async ({ page }) => { + // Upload the file + await uploadFile(page, './public/sample.epub'); + + // Click on the uploaded document + await page.getByText('sample.epub').click(); + + // Verify EPUB viewer is displayed + await expect(page.locator('.epub-container')).toBeVisible(); + }); + }); +});