The showPdf extension loads a PDF file in the current browser page and renders its pages so that journey steps can visually inspect the PDF and assert visible text from the rendered content.
This is useful when a test downloads or opens a PDF document and needs to verify invoice text, report content, confirmation details, or other PDF output as part of the same journey.
Parameters:
-
filerequired, the URL or downloaded-file reference for the PDF file to render; -
passwordoptional, the password string used to open the PDF when the file is encrypted.
Note: The file value must be reachable from the browser context running the journey. For downloaded files, use Virtuoso's downloaded-file variable after navigating to the helper page used for displaying downloaded files.
How to apply this to your journey
Use the extension in a journey by calling showPdf with the execute command. Pass each value to the matching extension input using as file and, when needed, as password.
Note: The extension replaces the current page body with a PDF viewer container. Run it after the PDF URL or downloaded file is available, then use normal journey assertions such as see to check text rendered from the PDF.
execute "showPdf" using "https://test.virtuoso.qa/path/file.pdf" as file returning $status
see "Some text on the PDF"execute "showPdf" using "https://test.virtuoso.qa/path/protected-file.pdf" as file, "password" as password returning $status
see "Some text on the PDF"You can also pass a downloaded PDF file reference or stored variable to the extension.
click "Download PDF"
navigate to "https://s3-eu-west-1.amazonaws.com/virtuoso-downloaded-files/test.html"
execute "showPdf" using "$LAST_DOWNLOADED_FILE" as file returning $status
see "Some text on the PDF"store value "https://test.virtuoso.qa/path/file.pdf" in $pdfFile
execute "showPdf" using "$pdfFile" as file returning $status
see "Some text on the PDF"Note: PDF files opened in a new tab can cause the download test step to time out. Where possible, make the PDF download or open in the current tab by removing target="_blank" from the link, or explicitly mark the link as a download by adding a download attribute such as download="somepdf.pdf".
Test steps or extensions can be used to remove the target attribute from links on the current page before clicking the PDF link.
execute "[...document.querySelectorAll('a[target=\"_blank\"]')].forEach(e=>e.removeAttribute('target'))"
execute "[...document.querySelectorAll('a[href*=\"pdf\"]')].forEach(e=>e.removeAttribute('target'))"Example output:
The PDF is rendered into the current page. Use assertions such as:
see "Some text on the PDF"This extension requires the following resources:
https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.jshttps://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js
The extension should be configured as:
- Run asynchronously: Yes
- Scope: Global
Limitation: This extension depends on PDF.js, the configured CDN resources, browser network access to the PDF file, and the current browser page context. It replaces document.body, renders each page to canvas, overlays extracted text in SVG, and creates transparent annotation links; therefore, it should be used on a helper/display page rather than on a page where existing application state must be preserved. The PDF must be accessible from the execution browser, so private files, authentication-controlled URLs, blocked CDN access, proxy restrictions, CORS restrictions, invalid passwords, unsupported encryption, very large PDFs, image-only/scanned PDFs, unusual fonts, and PDFs with complex annotations can produce incomplete text, failed rendering, or failed assertions. The source calls done() through .then(done()), so the Virtuoso step may complete before all pages, text layers, and annotations have finished rendering; add a visible-content assertion or wait step after showPdf before checking PDF text. Async extension scripts must complete within Virtuoso's documented 120-second maximum execution window, but large PDFs may still be impractical to render in one step. PDF links that open in a new tab can cause the PDF download step to time out; prefer current-tab downloads or remove target="_blank" before clicking the link when the journey needs to use $LAST_DOWNLOADED_FILE. Cross-browser note: PDF rendering, canvas behavior, SVG text positioning, viewport scaling, downloads, tabs, and file handling can differ from Virtuoso's default Chromium-based browser in Safari, Firefox, Edge, iOS, Android, or remote-grid executions. Validate the extension in each browser/device configuration used by your plan.
Add the extension to your Virtuoso instance
Select the domain that matches your Virtuoso account.
View source
Last updated: 01/05/2025
Resources:
https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.jshttps://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js
// Last updated: 01/05/2025, 07:14:57 UTC
// Resources:
// https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js
// https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js
async function showPdf (file, password) {
document.body.style = "background: grey; padding:5px;"
document.body.innerHTML = '<div id="pageContainer"></div>'
var PDF_PATH = file;
var SVG_NS = 'http://www.w3.org/2000/svg';
document.title = PDF_PATH
function buildSVG(viewport, textContent) {
var svg = document.createElementNS(SVG_NS, 'svg:svg');
svg.setAttribute('width', viewport.width + 'px');
svg.setAttribute('height', viewport.height + 'px');
svg.setAttribute('font-size', 1);
textContent.items.forEach(function (textItem) {
if (textItem.str.trim().length === 0){
return
}
var tx = pdfjsLib.Util.transform(
pdfjsLib.Util.transform(viewport.transform, textItem.transform),
[1, 0, 0, -1, 0, 0]);
var style = textContent.styles[textItem.fontName];
// adding text element
var text = document.createElementNS(SVG_NS, 'svg:text');
text.setAttribute('transform', 'matrix(' + tx.join(' ') + ')');
text.setAttribute('font-family', style.fontFamily);
text.setAttribute('fill-opacity', 0);
text.textContent = textItem.str;
svg.appendChild(text);
});
return svg;
}
var otherParams = password ? { password } : {}
var loadingTask = pdfjsLib.getDocument({url: PDF_PATH, ...otherParams });
loadingTask.promise.then(function (pdfDocument) {
for (let i = 0; i < pdfDocument.numPages; i++) {
const div = document.createElement("div")
div.id = "page-" + i
div.style = "border: 1px solid black; background: white; margin-bottom: 3px; relative"
document.getElementById('pageContainer').appendChild(div);
pdfDocument.getPage(i + 1).then(function (page) {
const normalViewport = page.getViewport({ scale: 1.0 })
const viewport = page.getViewport({ scale: window.innerWidth / normalViewport.width });
const scale = [
viewport.width / normalViewport.width,
viewport.height / normalViewport.height
]
page.getTextContent().then(function (textContent) {
const parentDiv = document.getElementById(div.id)
parentDiv.style.height = `${viewport.height}px`
parentDiv.style.width = `${viewport.width}px`
const pageDiv = document.createElement("div")
pageDiv.style = "position: relative; height: 100%;";
parentDiv.appendChild(pageDiv)
const canvas = document.createElement('canvas')
canvas.style = "width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; background: white;"
canvas.height = viewport.height
canvas.width = viewport.width
pageDiv.appendChild(canvas)
page.render({
canvasContext: canvas.getContext('2d'),
viewport: viewport
})
var svg = buildSVG(viewport, textContent);
svg.style = "opacity: 1; width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; z-index: 200"
pageDiv.appendChild(svg);
}).then(() => {
page.getAnnotations().then(function (annotations) {
const pageDiv = document.getElementById(div.id).lastChild
annotations.forEach(annotation => {
const link = document.createElement("a")
link.href = annotation.url
const button = document.createElement("button")
const rect = annotation.rect
link.style = `display: block; position: absolute; left: ${rect[0]*scale[0]}px; z-index:400;
bottom: ${rect[1]*scale[1]}px;`
button.style = `opacity: 0; width: ${(rect[2]-rect[0])*scale[0]}px;
height: ${(rect[3]-rect[1])*scale[1]}px;`
button.innerText = ""
link.appendChild(button)
pageDiv.appendChild(link);
})
})
});
});
}
}).then(done()).catch(error => {
console.error(error.details)
doneError(error)
})
}
try {
showPdf(file, password)
} catch (error){
console.error(error.details)
doneError(error)
}
Comments
0 comments
Please sign in to leave a comment.