Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: CI

on:
pull_request:
branches:
- '*'
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js 22.12.0
uses: actions/setup-node@v4
with:
node-version: 22.12.0
cache: 'npm'
cache-dependency-path: package.json
- name: Install dependencies
run: npm install --no-audit --no-fund
- name: Run unit build
run: npm run build

e2e:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js 22.12.0
uses: actions/setup-node@v4
with:
node-version: 22.12.0
cache: 'npm'
cache-dependency-path: package.json
- name: Install dependencies
run: npm install --no-audit --no-fund
- name: Build application
run: npm run build
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run Playwright tests
env:
PLAYWRIGHT_TEST_PORT: 4400
run: npx playwright test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Thumbs.db

# Otros
*.tgz
playwright-report/
test-results/

# Archivo de bloqueo de paquetes
package-lock.json
97 changes: 97 additions & 0 deletions e2e/pdf-annotator.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { expect, test, type Download } from '@playwright/test';
import { promises as fs } from 'node:fs';
import { join } from 'node:path';
import { Readable } from 'node:stream';

async function streamToBuffer(stream: Readable): Promise<Buffer> {
const chunks: Buffer[] = [];
for await (const chunk of stream) {
const bufferChunk =
typeof chunk === 'string' ? Buffer.from(chunk, 'utf-8') : Buffer.from(chunk);
chunks.push(bufferChunk);
}
return Buffer.concat(chunks);
}

async function downloadToBuffer(download: Download): Promise<Buffer> {
const stream = await download.createReadStream();
if (stream) {
return streamToBuffer(stream);
}
const filePath = await download.path();
if (!filePath) {
throw new Error('Unable to read download contents');
}
return fs.readFile(filePath);
}

test.describe('PDF annotation end-to-end', () => {
test('creates, duplicates and exports annotations', async ({ page }) => {
await page.goto('/');

const fileInput = page.locator('input.input-file[type="file"]');
await expect(fileInput).toBeVisible();

const samplePdfPath = join(
process.cwd(),
'public',
'assets',
'test-documents',
'sample.pdf'
);

await fileInput.setInputFiles(samplePdfPath);

const viewer = page.locator('main.viewer');
await expect(viewer).toBeVisible();
await expect(page.locator('.hitbox')).toBeVisible();

const hitbox = page.locator('.hitbox');
await hitbox.click({ position: { x: 200, y: 250 } });

const previewEditor = page.locator('.annotation-editor:not(.editing)');
await expect(previewEditor).toBeVisible();

const previewFields = previewEditor.locator('label.field');
await previewFields.nth(0).locator('input').fill('Test map field');
await previewFields.nth(1).locator('select').selectOption('text');
await previewFields.nth(2).locator('input').fill('Test annotation');
await previewEditor.locator('button', { hasText: '✅' }).click();

const annotations = page.locator('.annotations-layer .annotation');
await expect(annotations).toHaveCount(1);
await expect(annotations.first()).toContainText('Test annotation');

await annotations.first().click();

const editingPanel = page.locator('.annotation-editor.editing');
await expect(editingPanel).toBeVisible();

await editingPanel.locator('button', { hasText: '⧉' }).click();

const duplicateEditor = page.locator('.annotation-editor.editing');
await expect(duplicateEditor).toBeVisible();
await duplicateEditor.locator('button', { hasText: '✅' }).click();
await expect(duplicateEditor).toHaveCount(0);

await expect(annotations).toHaveCount(2);
await expect(annotations).toContainText(['Test annotation', 'Test annotation']);

const downloadJsonPromise = page.waitForEvent('download');
await page.getByRole('button', { name: /Descargar JSON/i }).click();
const jsonDownload = await downloadJsonPromise;
const jsonBuffer = await downloadToBuffer(jsonDownload);
const jsonData = JSON.parse(jsonBuffer.toString('utf-8'));
const pages = Array.isArray(jsonData.pages) ? jsonData.pages : [];
expect(pages.length).toBeGreaterThan(0);
const firstPage = pages[0] ?? { fields: [] };
const fields = Array.isArray(firstPage.fields) ? firstPage.fields : [];
expect(fields.length).toBeGreaterThanOrEqual(2);

const downloadPdfPromise = page.waitForEvent('download');
await page.getByRole('button', { name: /Descargar PDF/i }).click();
const pdfDownload = await downloadPdfPromise;
const pdfBuffer = await downloadToBuffer(pdfDownload);
expect(pdfBuffer.subarray(0, 4).toString()).toBe('%PDF');
});
});
11 changes: 11 additions & 0 deletions e2e/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"target": "ES2022",
"types": ["node", "@playwright/test"],
"resolveJsonModule": true,
"esModuleInterop": true
},
"include": ["./**/*.ts"]
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"build": "npm run i18n:check && ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"e2e": "playwright test",
"serve:ssr:pdf-annotator": "node dist/pdf-annotator/server/server.mjs",
"i18n:check": "node scripts/check-translations.mjs"
},
Expand All @@ -26,6 +27,9 @@
]
},
"private": true,
"engines": {
"node": "22.12.0"
},
"dependencies": {
"@angular/common": "^20.2.0",
"@angular/compiler": "^20.2.0",
Expand All @@ -48,6 +52,7 @@
"@angular/build": "^20.2.1",
"@angular/cli": "^20.2.1",
"@angular/compiler-cli": "^20.2.0",
"@playwright/test": "^1.48.2",
"@types/express": "^5.0.1",
"@types/jasmine": "~5.1.0",
"@types/node": "^20.17.19",
Expand Down
33 changes: 33 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { defineConfig, devices } from '@playwright/test';

const PORT = process.env.PLAYWRIGHT_TEST_PORT ? Number(process.env.PLAYWRIGHT_TEST_PORT) : 4300;

export default defineConfig({
testDir: './e2e',
timeout: 120_000,
expect: {
timeout: 10_000,
},
retries: process.env.CI ? 2 : 0,
fullyParallel: true,
reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list',
use: {
baseURL: `http://127.0.0.1:${PORT}`,
headless: true,
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: `npm run start -- --host 0.0.0.0 --port ${PORT}`,
url: `http://127.0.0.1:${PORT}`,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
stderr: 'pipe',
timeout: 180_000,
},
});
Binary file added public/assets/test-documents/sample.pdf
Binary file not shown.
Loading