fb1c781a20
**Test 3 Updated**: Shortcut URL behavior changed
- OLD: Expected HTTP 301 redirect to /export/pdf
- NEW: Expects HTTP 200 with direct PDF and Content-Disposition header
- Verifies correct filename: cv-jamr-2025-{lang}.pdf
**Test 6 Added**: Language switching links
- Tests "See this CV in Spanish/English" links
- Verifies URL correctness (/?lang=es and /?lang=en)
- Ensures no URL corruption from replaceYearPlaceholder fix
- Checks target="_blank" attribute
- Tests both English → Spanish and Spanish → English links
All 6 tests passing:
✅ EN Direct Shortcut URL
✅ ES Direct Shortcut URL
✅ Shortcut URL Direct Download (updated)
✅ Year Validation
✅ No JavaScript Required
✅ Language Switching Links (new)
434 lines
18 KiB
JavaScript
Executable File
434 lines
18 KiB
JavaScript
Executable File
#!/usr/bin/env bun
|
|
/**
|
|
* REFERENCES SECTION - DIRECT PDF SHORTCUT LINK TEST
|
|
* ====================================================
|
|
* Tests the direct PDF download shortcut links in the References section
|
|
*
|
|
* Coverage:
|
|
* - References section renders with "Download this curriculum" links
|
|
* - Links point directly to shortcut URLs (no modal)
|
|
* - English CV: /cv-jamr-{year}-en.pdf
|
|
* - Spanish CV: /cv-jamr-{year}-es.pdf
|
|
* - Year is dynamic (current year)
|
|
* - Links have target="_blank" and proper security attributes
|
|
* - No onclick handlers (direct navigation)
|
|
* - Works in non-browser environments (PDF exports, emails, etc.)
|
|
*
|
|
* Integration:
|
|
* - Data: cv-en.json and cv-es.json with action: "downloadPDF"
|
|
* - Template: references.html with shortcut URL logic
|
|
* - Backend: DefaultCVShortcut handler for year validation
|
|
*/
|
|
|
|
import { chromium } from 'playwright';
|
|
|
|
const URL = "http://localhost:1999";
|
|
const CURRENT_YEAR = new Date().getFullYear();
|
|
|
|
async function testReferencesPDFShortcutLinks() {
|
|
console.log('📚 REFERENCES SECTION - DIRECT PDF SHORTCUT LINK TEST\n');
|
|
console.log('='.repeat(70));
|
|
|
|
const browser = await chromium.launch({ headless: false });
|
|
const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
|
|
const page = await context.newPage();
|
|
|
|
const errors = [];
|
|
const testResults = [];
|
|
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
console.log(`❌ CONSOLE ERROR: ${msg.text()}`);
|
|
}
|
|
});
|
|
|
|
// ========================================================================
|
|
// TEST 1: English Version - Direct Shortcut URL
|
|
// ========================================================================
|
|
console.log("\n1️⃣ Testing English Direct Shortcut URL...");
|
|
|
|
await page.goto(`${URL}/?lang=en`);
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Scroll to references section
|
|
await page.evaluate(() => {
|
|
const referencesSection = document.querySelector('#references');
|
|
if (referencesSection) {
|
|
referencesSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
});
|
|
await page.waitForTimeout(1000);
|
|
|
|
const enReferences = await page.evaluate((currentYear) => {
|
|
const section = document.querySelector('#references');
|
|
if (!section) return { found: false };
|
|
|
|
// Find the reference-item div that contains "Download this curriculum"
|
|
const referenceItems = section.querySelectorAll('.reference-item');
|
|
let downloadLink = null;
|
|
|
|
for (const item of referenceItems) {
|
|
if (item.textContent.includes('Download this curriculum')) {
|
|
downloadLink = item.querySelector('a');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
sectionExists: !!section,
|
|
downloadLinkExists: !!downloadLink,
|
|
downloadLinkText: downloadLink ? downloadLink.textContent.trim() : null,
|
|
href: downloadLink ? downloadLink.getAttribute('href') : null,
|
|
target: downloadLink ? downloadLink.getAttribute('target') : null,
|
|
rel: downloadLink ? downloadLink.getAttribute('rel') : null,
|
|
hasOnClick: downloadLink ? downloadLink.hasAttribute('onclick') : false,
|
|
expectedHref: `/cv-jamr-${currentYear}-en.pdf`,
|
|
hrefMatches: downloadLink ? downloadLink.getAttribute('href') === `/cv-jamr-${currentYear}-en.pdf` : false
|
|
};
|
|
}, CURRENT_YEAR);
|
|
|
|
console.log(` References section: ${enReferences.sectionExists ? '✅' : '❌'}`);
|
|
console.log(` Download link exists: ${enReferences.downloadLinkExists ? '✅' : '❌'}`);
|
|
console.log(` Link text: "${enReferences.downloadLinkText}"`);
|
|
console.log(` Href: "${enReferences.href}"`);
|
|
console.log(` Expected: "${enReferences.expectedHref}"`);
|
|
console.log(` Href matches: ${enReferences.hrefMatches ? '✅' : '❌'}`);
|
|
console.log(` Target="_blank": ${enReferences.target === '_blank' ? '✅' : '❌'}`);
|
|
console.log(` Rel security attrs: ${enReferences.rel === 'noopener noreferrer' ? '✅' : '❌'}`);
|
|
console.log(` No onclick handler: ${!enReferences.hasOnClick ? '✅' : '❌'}`);
|
|
|
|
const test1Passed = enReferences.sectionExists &&
|
|
enReferences.downloadLinkExists &&
|
|
enReferences.hrefMatches &&
|
|
enReferences.target === '_blank' &&
|
|
enReferences.rel === 'noopener noreferrer' &&
|
|
!enReferences.hasOnClick;
|
|
|
|
console.log(` ${test1Passed ? '✅ PASS' : '❌ FAIL'} - English direct shortcut URL`);
|
|
testResults.push({ test: 'EN Direct Shortcut URL', passed: test1Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 2: Spanish Version - Direct Shortcut URL
|
|
// ========================================================================
|
|
console.log("\n2️⃣ Testing Spanish Direct Shortcut URL...");
|
|
|
|
await page.goto(`${URL}/?lang=es`);
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Scroll to references section
|
|
await page.evaluate(() => {
|
|
const referencesSection = document.querySelector('#references');
|
|
if (referencesSection) {
|
|
referencesSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
});
|
|
await page.waitForTimeout(1000);
|
|
|
|
const esReferences = await page.evaluate((currentYear) => {
|
|
const section = document.querySelector('#references');
|
|
if (!section) return { found: false };
|
|
|
|
// Find the reference-item div that contains "Descargar este currículum"
|
|
const referenceItems = section.querySelectorAll('.reference-item');
|
|
let downloadLink = null;
|
|
|
|
for (const item of referenceItems) {
|
|
if (item.textContent.includes('Descargar este currículum')) {
|
|
downloadLink = item.querySelector('a');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
sectionExists: !!section,
|
|
downloadLinkExists: !!downloadLink,
|
|
downloadLinkText: downloadLink ? downloadLink.textContent.trim() : null,
|
|
href: downloadLink ? downloadLink.getAttribute('href') : null,
|
|
target: downloadLink ? downloadLink.getAttribute('target') : null,
|
|
rel: downloadLink ? downloadLink.getAttribute('rel') : null,
|
|
hasOnClick: downloadLink ? downloadLink.hasAttribute('onclick') : false,
|
|
expectedHref: `/cv-jamr-${currentYear}-es.pdf`,
|
|
hrefMatches: downloadLink ? downloadLink.getAttribute('href') === `/cv-jamr-${currentYear}-es.pdf` : false
|
|
};
|
|
}, CURRENT_YEAR);
|
|
|
|
console.log(` References section: ${esReferences.sectionExists ? '✅' : '❌'}`);
|
|
console.log(` Download link exists: ${esReferences.downloadLinkExists ? '✅' : '❌'}`);
|
|
console.log(` Link text: "${esReferences.downloadLinkText}"`);
|
|
console.log(` Href: "${esReferences.href}"`);
|
|
console.log(` Expected: "${esReferences.expectedHref}"`);
|
|
console.log(` Href matches: ${esReferences.hrefMatches ? '✅' : '❌'}`);
|
|
console.log(` Target="_blank": ${esReferences.target === '_blank' ? '✅' : '❌'}`);
|
|
console.log(` Rel security attrs: ${esReferences.rel === 'noopener noreferrer' ? '✅' : '❌'}`);
|
|
console.log(` No onclick handler: ${!esReferences.hasOnClick ? '✅' : '❌'}`);
|
|
|
|
const test2Passed = esReferences.sectionExists &&
|
|
esReferences.downloadLinkExists &&
|
|
esReferences.hrefMatches &&
|
|
esReferences.target === '_blank' &&
|
|
esReferences.rel === 'noopener noreferrer' &&
|
|
!esReferences.hasOnClick;
|
|
|
|
console.log(` ${test2Passed ? '✅ PASS' : '❌ FAIL'} - Spanish direct shortcut URL`);
|
|
testResults.push({ test: 'ES Direct Shortcut URL', passed: test2Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 3: Shortcut URL Returns PDF with Correct Filename
|
|
// ========================================================================
|
|
console.log("\n3️⃣ Testing Shortcut URL Direct PDF Download...");
|
|
|
|
// Test English shortcut
|
|
const enResponse = await page.request.get(`${URL}/cv-jamr-${CURRENT_YEAR}-en.pdf`, {
|
|
maxRedirects: 0,
|
|
failOnStatusCode: false
|
|
});
|
|
|
|
const enStatus = enResponse.status();
|
|
const enContentDisposition = enResponse.headers()['content-disposition'];
|
|
const enContentType = enResponse.headers()['content-type'];
|
|
const enFilename = enContentDisposition?.match(/filename=([^;]+)/)?.[1];
|
|
|
|
console.log(` English shortcut HTTP status: ${enStatus} ${enStatus === 200 ? '✅' : '❌'}`);
|
|
console.log(` Content-Type: ${enContentType} ${enContentType === 'application/pdf' ? '✅' : '❌'}`);
|
|
console.log(` Content-Disposition: ${enContentDisposition}`);
|
|
console.log(` Filename: ${enFilename} ${enFilename === 'cv-jamr-2025-en.pdf' ? '✅' : '❌'}`);
|
|
|
|
// Test Spanish shortcut
|
|
const esResponse = await page.request.get(`${URL}/cv-jamr-${CURRENT_YEAR}-es.pdf`, {
|
|
maxRedirects: 0,
|
|
failOnStatusCode: false
|
|
});
|
|
|
|
const esStatus = esResponse.status();
|
|
const esContentDisposition = esResponse.headers()['content-disposition'];
|
|
const esContentType = esResponse.headers()['content-type'];
|
|
const esFilename = esContentDisposition?.match(/filename=([^;]+)/)?.[1];
|
|
|
|
console.log(` Spanish shortcut HTTP status: ${esStatus} ${esStatus === 200 ? '✅' : '❌'}`);
|
|
console.log(` Content-Type: ${esContentType} ${esContentType === 'application/pdf' ? '✅' : '❌'}`);
|
|
console.log(` Content-Disposition: ${esContentDisposition}`);
|
|
console.log(` Filename: ${esFilename} ${esFilename === 'cv-jamr-2025-es.pdf' ? '✅' : '❌'}`);
|
|
|
|
const test3Passed = enStatus === 200 &&
|
|
esStatus === 200 &&
|
|
enContentType === 'application/pdf' &&
|
|
esContentType === 'application/pdf' &&
|
|
enFilename === 'cv-jamr-2025-en.pdf' &&
|
|
esFilename === 'cv-jamr-2025-es.pdf';
|
|
|
|
console.log(` ${test3Passed ? '✅ PASS' : '❌ FAIL'} - Shortcut URL direct download with correct filename`);
|
|
testResults.push({ test: 'Shortcut URL Direct Download', passed: test3Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 4: Invalid Year Returns 404
|
|
// ========================================================================
|
|
console.log("\n4️⃣ Testing Invalid Year Validation...");
|
|
|
|
const invalidYear = CURRENT_YEAR - 1; // Last year
|
|
const futureYear = CURRENT_YEAR + 1; // Next year
|
|
|
|
const pastResponse = await page.request.get(`${URL}/cv-jamr-${invalidYear}-en.pdf`, {
|
|
failOnStatusCode: false
|
|
});
|
|
|
|
const futureResponse = await page.request.get(`${URL}/cv-jamr-${futureYear}-en.pdf`, {
|
|
failOnStatusCode: false
|
|
});
|
|
|
|
const pastStatus = pastResponse.status();
|
|
const futureStatus = futureResponse.status();
|
|
|
|
console.log(` Past year (${invalidYear}) status: ${pastStatus} ${pastStatus === 404 ? '✅' : '❌'}`);
|
|
console.log(` Future year (${futureYear}) status: ${futureStatus} ${futureStatus === 404 ? '✅' : '❌'}`);
|
|
|
|
const test4Passed = pastStatus === 404 && futureStatus === 404;
|
|
|
|
console.log(` ${test4Passed ? '✅ PASS' : '❌ FAIL'} - Year validation (404 for invalid years)`);
|
|
testResults.push({ test: 'Year Validation', passed: test4Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 5: Link Works in PDF Export Context (No JavaScript)
|
|
// ========================================================================
|
|
console.log("\n5️⃣ Testing Link Works Without JavaScript...");
|
|
|
|
// Navigate with JavaScript disabled
|
|
const noJSContext = await browser.newContext({
|
|
javaScriptEnabled: false,
|
|
viewport: { width: 1920, height: 1080 }
|
|
});
|
|
const noJSPage = await noJSContext.newPage();
|
|
|
|
await noJSPage.goto(`${URL}/?lang=en`);
|
|
await noJSPage.waitForTimeout(1500);
|
|
|
|
const linkWorksWithoutJS = await noJSPage.evaluate((currentYear) => {
|
|
const section = document.querySelector('#references');
|
|
if (!section) return { found: false };
|
|
|
|
const referenceItems = section.querySelectorAll('.reference-item');
|
|
let downloadLink = null;
|
|
|
|
for (const item of referenceItems) {
|
|
if (item.textContent.includes('Download this curriculum')) {
|
|
downloadLink = item.querySelector('a');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
linkExists: !!downloadLink,
|
|
href: downloadLink ? downloadLink.getAttribute('href') : null,
|
|
isDirectLink: downloadLink ? downloadLink.getAttribute('href') === `/cv-jamr-${currentYear}-en.pdf` : false,
|
|
noOnClick: downloadLink ? !downloadLink.hasAttribute('onclick') : false
|
|
};
|
|
}, CURRENT_YEAR);
|
|
|
|
console.log(` Link exists without JS: ${linkWorksWithoutJS.linkExists ? '✅' : '❌'}`);
|
|
console.log(` Href is direct PDF link: ${linkWorksWithoutJS.isDirectLink ? '✅' : '❌'}`);
|
|
console.log(` No onclick dependency: ${linkWorksWithoutJS.noOnClick ? '✅' : '❌'}`);
|
|
|
|
const test5Passed = linkWorksWithoutJS.linkExists &&
|
|
linkWorksWithoutJS.isDirectLink &&
|
|
linkWorksWithoutJS.noOnClick;
|
|
|
|
console.log(` ${test5Passed ? '✅ PASS' : '❌ FAIL'} - Works without JavaScript`);
|
|
testResults.push({ test: 'No JavaScript Required', passed: test5Passed });
|
|
|
|
await noJSContext.close();
|
|
|
|
// ========================================================================
|
|
// TEST 6: "See this CV in..." Language Switching Links
|
|
// ========================================================================
|
|
console.log("\n6️⃣ Testing Language Switching Links...");
|
|
|
|
// Test English page has Spanish link
|
|
await page.goto(`${URL}/?lang=en`);
|
|
await page.waitForTimeout(1000);
|
|
|
|
const enLanguageLink = await page.evaluate(() => {
|
|
const section = document.querySelector('#references');
|
|
if (!section) return { found: false };
|
|
|
|
const referenceItems = section.querySelectorAll('.reference-item');
|
|
let spanishLink = null;
|
|
|
|
for (const item of referenceItems) {
|
|
if (item.textContent.includes('Curriculum Vitae in PDF in')) {
|
|
spanishLink = item.querySelector('a');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
linkExists: !!spanishLink,
|
|
linkText: spanishLink ? spanishLink.textContent.trim() : null,
|
|
href: spanishLink ? spanishLink.getAttribute('href') : null,
|
|
expectedHref: '/?lang=es',
|
|
hrefMatches: spanishLink ? spanishLink.getAttribute('href') === '/?lang=es' : false,
|
|
target: spanishLink ? spanishLink.getAttribute('target') : null,
|
|
noCorruption: spanishLink ? !spanishLink.getAttribute('href').includes('EXTRA') : false
|
|
};
|
|
});
|
|
|
|
console.log(` English page - Spanish link exists: ${enLanguageLink.linkExists ? '✅' : '❌'}`);
|
|
console.log(` Link text: "${enLanguageLink.linkText}"`);
|
|
console.log(` Href: "${enLanguageLink.href}"`);
|
|
console.log(` Expected: "${enLanguageLink.expectedHref}"`);
|
|
console.log(` Href matches: ${enLanguageLink.hrefMatches ? '✅' : '❌'}`);
|
|
console.log(` No URL corruption: ${enLanguageLink.noCorruption ? '✅' : '❌'}`);
|
|
console.log(` Target="_blank": ${enLanguageLink.target === '_blank' ? '✅' : '❌'}`);
|
|
|
|
// Test Spanish page has English link
|
|
await page.goto(`${URL}/?lang=es`);
|
|
await page.waitForTimeout(1000);
|
|
|
|
const esLanguageLink = await page.evaluate(() => {
|
|
const section = document.querySelector('#references');
|
|
if (!section) return { found: false };
|
|
|
|
const referenceItems = section.querySelectorAll('.reference-item');
|
|
let englishLink = null;
|
|
|
|
for (const item of referenceItems) {
|
|
if (item.textContent.includes('Currículum Vitae en PDF en')) {
|
|
englishLink = item.querySelector('a');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
linkExists: !!englishLink,
|
|
linkText: englishLink ? englishLink.textContent.trim() : null,
|
|
href: englishLink ? englishLink.getAttribute('href') : null,
|
|
expectedHref: '/?lang=en',
|
|
hrefMatches: englishLink ? englishLink.getAttribute('href') === '/?lang=en' : false,
|
|
target: englishLink ? englishLink.getAttribute('target') : null,
|
|
noCorruption: englishLink ? !englishLink.getAttribute('href').includes('EXTRA') : false
|
|
};
|
|
});
|
|
|
|
console.log(` Spanish page - English link exists: ${esLanguageLink.linkExists ? '✅' : '❌'}`);
|
|
console.log(` Link text: "${esLanguageLink.linkText}"`);
|
|
console.log(` Href: "${esLanguageLink.href}"`);
|
|
console.log(` Expected: "${esLanguageLink.expectedHref}"`);
|
|
console.log(` Href matches: ${esLanguageLink.hrefMatches ? '✅' : '❌'}`);
|
|
console.log(` No URL corruption: ${esLanguageLink.noCorruption ? '✅' : '❌'}`);
|
|
console.log(` Target="_blank": ${esLanguageLink.target === '_blank' ? '✅' : '❌'}`);
|
|
|
|
const test6Passed = enLanguageLink.linkExists &&
|
|
enLanguageLink.hrefMatches &&
|
|
enLanguageLink.noCorruption &&
|
|
esLanguageLink.linkExists &&
|
|
esLanguageLink.hrefMatches &&
|
|
esLanguageLink.noCorruption;
|
|
|
|
console.log(` ${test6Passed ? '✅ PASS' : '❌ FAIL'} - Language switching links work correctly`);
|
|
testResults.push({ test: 'Language Switching Links', passed: test6Passed });
|
|
|
|
// ========================================================================
|
|
// FINAL RESULTS
|
|
// ========================================================================
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log('📊 TEST SUMMARY\n');
|
|
|
|
const totalTests = testResults.length;
|
|
const passedTests = testResults.filter(r => r.passed).length;
|
|
const failedTests = totalTests - passedTests;
|
|
|
|
testResults.forEach((result, index) => {
|
|
const status = result.passed ? '✅ PASS' : '❌ FAIL';
|
|
console.log(`${index + 1}. ${status} - ${result.test}`);
|
|
});
|
|
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log(`Total Tests: ${totalTests}`);
|
|
console.log(`✅ Passed: ${passedTests}`);
|
|
console.log(`❌ Failed: ${failedTests}`);
|
|
|
|
if (errors.length > 0) {
|
|
console.log(`\n⚠️ Console Errors Detected: ${errors.length}`);
|
|
errors.forEach(err => console.log(` - ${err}`));
|
|
}
|
|
|
|
const allPassed = passedTests === totalTests && errors.length === 0;
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log(allPassed ? '🎉 ALL TESTS PASSED!' : '💥 SOME TESTS FAILED');
|
|
console.log('='.repeat(70));
|
|
|
|
await browser.close();
|
|
|
|
// Exit with appropriate code
|
|
process.exit(allPassed ? 0 : 1);
|
|
}
|
|
|
|
testReferencesPDFShortcutLinks().catch(err => {
|
|
console.error('❌ FATAL ERROR:', err);
|
|
process.exit(1);
|
|
});
|