“Actions” make PDF documents interactive and more complex. “Complex” means that they should be tested, especially when interactive documents are part of a workflow. Those actions need to work correctly.
An “action” is a dictionary object inside PDF containing the keys
/S
and /Type
.
The key /Type
always maps to the value “Action”.
And the the key /S
(Subtype) has different values:
// Types of actions: GoTo: Set the focus to a destination in the current PDF document GoToR: Set the focus to a destination in another PDF document GoToE: Go to a destination inside an embedded file GoTo3DView: Set the view to a 3D annotation Hide: Set the hidden flag of the specified annotation ImportData: Import data from a file to the current document JavaScript: Execute JavaScript code Movie: Play a specified movie Named: Execute an action, which is predefined by the PDF viewer Rendition: Control the playing of multimedia content ResetForm: Set the values of form fields to default SetOCGState: Set the state of an OCG Sound: Play a specified sound SubmitForm: Send the form data to an URL Launch: Execute an application Thread: Set the viewer to the beginning of a specified article Trans: Update the display of a document, using a transition dictionary URI: Go to the remote URI
PDFUnit provides tags for some of these actions:
<!-- Tags to test actions: --> <hasNumberOfActions /> <hasNumberOfJavaScriptActions /> <hasAnyAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasAnyAction> ... continued
... continuation: <hasChainedAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasChainedAction> <hasCloseAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasCloseAction> <hasImportDataAction> <matchingComplete filename=".." /> (tag optional, attribute required) </hasImportDataAction> <hasJavaScriptAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasJavaScriptAction> <hasLaunchAction> <toLaunch /> (optional) </hasLaunchAction> ... continue
... continuation <hasLocalGotoAction> <toDestination /> (optional) </hasLocalGotoAction> <hasNamedAction> <withName /> (optional) </hasNamedAction> <hasOpenAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... <withDestinationTo /> ... are optional) </hasOpenAction> <hasPrintAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasPrintAction> ... continued
... continuation: <hasRemoteGotoActionTo file=".." (required) destination=".." (optional) page=".." (optional) /> <hasResetFormAction /> (no attributes and no nested tags!) <hasSaveAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasSaveAction> <hasSubmitFormAction> <withDestination /> (optional) </hasSubmitFormAction> <hasURIAction> <containing /> (all nested tags ... <matchingComplete /> ... <matchingRegex /> ... are optional) </hasURIAction> ... (end of list)
The following sections show examples for different types of actions.
Close-Actions are executed when the document is being closed:
<!-- The fundamental way to compare actions with expected values: --> <testcase name="Principle_ComparingActionValues"> <assertThat testDocument="actions/documentCloseAction.pdf"> <hasCloseAction> <containing>app.alert('A sample for a DOCUMENT_CLOSE-action');</containing> </hasCloseAction> </assertThat> </testcase>
The content of a Close-Action can also be compared with the content of a file:
<testcase name="hasCloseAction_MatchingComplete_ContentFromFile"> <assertThat testDocument="actions/documentCloseAction.pdf"> <hasCloseAction> <matchingComplete filename="actions/documentCloseAction.js" whitespaces="IGNORE" /> </hasCloseAction> </assertThat> </testcase>
The attribute whitespaces=".."
is optional.
The default whitespace processing is NORMALIZE
.
ImportData-Actions import data from a file. They need the filename as a parameter:
<testcase name="hasImportDataAction_MatchingFilename"> <assertThat testDocument="actions/chainedActions.pdf"> <hasImportDataAction> <matchingComplete filename="build.xml" /> </hasImportDataAction> </assertThat> </testcase>
PDFUnit checks whether the action contains the expected filename. The file's existence is not checked.
Since JavaScript code is generally quite long, it makes sense to read the expected text for a JavaScript-Action from a file:
<testcase name="hasJavaScriptAction_MatchingComplete_ContentFromFile"> <assertThat testDocument="javascript/bookmarkWithJavaScriptAction_OneSimpleAlert.pdf"> <hasJavaScriptAction> <matchingComplete filename="javascript/javascriptAction_OneSimpleAlert.js" /> </hasJavaScriptAction> </assertThat> </testcase>
The content of the JavaScript file is completely compared with the content of the JavaScript action. White spaces are normalized.
Launch-Actions are launching applications or scripts. This can be tested like this:
<testcase name="hasLaunchAction_Notepad_Print"> <assertThat testDocument="actions/launchActionToFile.pdf"> <hasLaunchAction> <toLaunch application="c:/windows/notepad.exe" operation="print" /> </hasLaunchAction> </assertThat> </testcase>
PDFUnit compares the content of the attributes application=".."
and operation=".."
with the actual values of the Launch-Action.
It is not checked whether the application can be started.
The name of Named-Actions should be verified:
<testcase name="hasNamedAction_WithName_NextPage"> <assertThat testDocument="actions/namedActionsNextPages.pdf"> <hasNamedAction> <withName> <matchingComplete>/NextPage</matchingComplete> </withName> </hasNamedAction> </assertThat> </testcase>
Goto-Actions need a destination in the same PDF document:
<testcase name="hasGotoAction_ToNamedDestination"> <assertThat testDocument="actions/bookmarksWithPdfOutline.pdf"> <hasLocalGotoAction> <toDestination name="destination2.1" /> </hasLocalGotoAction> </assertThat> </testcase>
The test is successful, when the current test PDF contains the expecte destination “destination2.1”.
GotoRemote-Actions need a destination in another PDF file.
<testcase name="hasGotoRemoteActionTo_NamedDestination"> <assertThat testDocument="actions/gotoRemotePageAction.pdf"> <hasRemoteGotoActionTo file="destination.pdf" destination="destination-3" /> </assertThat> </testcase> <testcase name="hasGotoRemoteAction_ToPage"> <assertThat testDocument="actions/gotoRemotePageAction.pdf"> <hasRemoteGotoActionTo file="destination.pdf" page="4" /> </assertThat> </testcase>
PDFUnit checks that the action in the PDF document under test contains an action with the expected destination. PDFUnit does not check whether the remote file or the destination in the remote file exist.
Open-Actions are executed when the PDF document is loaded. Often they are JavaScript- or Goto-Actions.
<testcase name="hasOpenAction_MultipleInvocation"> <assertThat testDocument="actions/documentOpenAction_Print.pdf"> <hasOpenAction> <matchingRegex>(?ms).*print(.*)</matchingRegex> <matchingComplete>this.print(true);</matchingComplete> </hasOpenAction> </assertThat> </testcase>
In addition to the tags for comparing text, Open-Actions can be tested
using the tag <withDestinationTo />
:
<testcase name="hasOpenAction_GotoPage2"> <assertThat testDocument="actions/documentOpenAction_Goto.pdf"> <hasOpenAction> <withDestinationTo page="2" /> </hasOpenAction> </assertThat> </testcase>
Print-Actions are JavaScript-Actions which are processed immediately
before or after printing a PDF document. They are associated internally
with the events WILL_PRINT
or DID_PRINT
.
<testcase name="hasPrintAction_WillPrint"> <assertThat testDocument="actions/documentPrintActions.pdf"> <hasPrintAction> <matchingComplete>app.alert('A sample for a WILL_PRINT-action');</matchingComplete> </hasPrintAction> </assertThat> </testcase>
As for JavaScript-Actions the content of the Print-Action is compared with the expected string. The whitespaces are normalized before.
ResetForm-Actions have no parameters and it is unnecessary to compare text. Only the existence will be verified:
<testcase name="hasResetFormAction"> <assertThat testDocument="acrofields/javaScriptForFields.pdf"> <hasResetFormAction /> </assertThat> </testcase>
Save-Actions are JavaScript-Actions which are processed immediately before
or after saving a PDF document. The actions are associated with the
PDF-Events WILL_SAVE
or DID_SAVE
.
<testcase name="hasSaveAction_MultipleInvocation"> <assertThat testDocument="actions/documentSaveActions.pdf"> <hasSaveAction> <matchingComplete>app.alert('A sample for a DID_SAVE-action');</matchingComplete> </hasSaveAction> </assertThat> </testcase>
Again, the comparison is performed only after a normalization of whitespace characters. All known tags can be used to compare the texts.
SubmitForm-Actions need a destination to which forms can be sent:
<testcase name="hasSubmitFormAction_ToUri"> <assertThat testDocument="acrofields/javaScriptForFields.pdf"> <hasSubmitFormAction> <withDestination toURI="http://www.geek-tutorials.com/java/itext/submit.php" /> </hasSubmitFormAction> </assertThat> </testcase>
PDFUnit does not check whether the destination exists. It only checks that the currently tested action contains a destination with the expected value.
URI-Actions need a target URI:
<testcase name="hasURIAction"> <assertThat testDocument="actions/noBookmarks-manyActions.pdf"> <hasURIAction> <matchingComplete>http://www.imdb.com/</matchingComplete> </hasURIAction> </assertThat> </testcase>
PDFUnit does not access the internet. So this test merely checks that the PDF under test contains a URI with the expected value.
The following example tests 4 different types of actions which should all exist in one PDF document:
<testcase name="hasAnyAction_DifferentKindOfActions"> <assertThat testDocument="actions/chainedActions.pdf"> <hasAnyAction> <matchingComplete>app.alert('Demo: the first action of five.');</matchingComplete> <matchingComplete>http://www.google.de</matchingComplete> <matchingComplete>c:/windows/notepad.exe</matchingComplete> <matchingComplete>this.print(true);</matchingComplete> </hasAnyAction> </assertThat> </testcase>
You can control how whitespaces are processed when comparing text. In the following example line breaks and blank lines are ignored:
<testcase name="hasCloseAction_Containing_ContentFromReader"> <assertThat testDocument="actions/documentCloseAction.pdf"> <hasCloseAction> <containing filename="actions/documentCloseAction.js" whitespaces="IGNORE" /> </hasCloseAction> </assertThat> </testcase>
The chapter 13.4: “Whitespace Processing” explains the flexible handling of whitespaces.