InDesign SDK  20.5
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Tool Kit Guide (ITK)

The Adobe InDesign Tool Kit (ITK) consists of several tools. This document includes the following:

Notation

This document uses the following notation for path names:
Notation Meaning
<InDesign> The installation directory for Adobe InDesign. By default, this is

Mac OS: /Applications/Adobe InDesign 2025/
Windows: C:\Program Files\Adobe\Adobe InDesign 2025\
Note
For InCopy or InDesign Server, substitute the appropriate application directory wherever <InDesign> appears.
<ITK> The directory containing the Adobe InDesign Tool Kit.
<SDK> The directory containing the Adobe InDesign SDK.
<username> In a path, the user's name.
<locale> in a path, the locale, such as en_US/.
<version> In a path, the current version number of the product release; for example, 9.0.

Action Automation Tool for Black-box Testing

This chapter describes how to use the Action Automation Tool (AAT) for black-box testing of Adobe® InDesign® plug-ins. The AAT editor allows you to create test sequences (action sessions) through its user interface, save them as XML (Session Files), and run them arbitrarily many times. It is a standard part of the debug build of InDesign and also can be used in the release build. AAT requires no deep programming knowledge to assemble test sequences.

The target audience for this chapter is anyone responsible for devising black-box tests for InDesign plug-ins. We do not assume that you have programmed in high-level language or written scripts, although that background makes learning the AAT easier, because it involves some basic programming concepts. We do assume that you are responsible in some way for black-box testing of code written for InDesign.

This chapter is organized as a series of procedures that take you through the major features of the AAT and gives you practical experience using the tool to write tests.

Terminology

The following key concepts related to the AAT are introduced in this chapter:

  • Action - A single command or instruction; a test step.
  • Action Automation Editor - The dialog used for assembling and editing action sequences; a test-case editor.
  • Action sequence - A container for the actions in the Sequencer portion of the Action Automation Editor. An action sequence can have other action sequences nested in it, allowing actions to be organized into logical groupings. An action sequence executes the actions and sequences it contains in order, from top to bottom or using loops.
  • Action session - A set of actions and action sequences; a test case.
  • Item editor - The dialog that appears after double-clicking an action in a sequence. The dialog contains parameters of the action that can be viewed or edited, like bounds.
  • Sequencer - The area of the Action Automation Editor where you add, remove, or re-arrange actions and action sequences during your action session. The right-hand panel in the Action Automation Editor.
  • Session file - An action session that was saved as an XML file. Each session file contains at least one action sequence. Metadata can be included with the session file.
  • Source components - The actions and containers that you can use in an action session. Source components are displayed in a tree view in the left-hand panel of the Action Automation Editor.

Installation

Overview

To work with the AAT, you must have the required assets (libraries, plug-ins, data files, etc.) in the correct places. This section describes how to install these assets.

To install the AAT, you must do the following:

  1. Move the shared libraries from <ITK>/build/win/<debug|debug64|release|releasex64>/ (Windows®) or <ITK>/build/mac/<debugcocoa64|releasecocoa64>/ packagefolder/contents/macos (Mac OS®) to the correct location as described in Shared libraries.
  2. Move the plug-ins from <ITK>/build/win/<debug|debug64|release|releasex64>/ (Windows®) or <ITK>/build/mac/<debugcocoa64|releasecocoa64>/packagefolder/contents/macos (Mac OS®) to a folder from which they can be loaded as described in Plug-ins.
  3. Move the data files from <ITK>/build/qa to a "qa" folder in the correct location relative to the installed executable (for example, InDesign.exe or /InDesign <version>.app/Contents/MacOS/) as described in Required data files.

Shared libraries

Some shared libraries must be in the same folder as the other shared libraries on which InDesign depends; for example, for the release build, DataBase.dll on Windows® and DataBaseLib.dylib on Mac OS®. These libraries are in folders named as follows:

  • Windows:
    <ITK>/build/win/<debug│debugx64|release|releasex64>/
  • Mac OS:
    <ITK>/build/mac/<debugcocoa64|releasecocoa64>/packagefolder/contents/macos

The following table lists the shared libraries required for AAT to function with the release/debug applications.
Windows Mac OS
TestLib.dll TestLib.dylib
AATLib.dll AATLib.dylib
TestUILib.dll TestUILib.dylib

It is possible that these libraries are part of a debug build that you already have. On Mac OS, if you Ctrl-right-mouse-click the InDesign executable and View Package Contents, you can inspect the packaged components to see whether the libraries already are included:

itk-guide-1.gif

Move the shared libraries to the following folders:

  • Windows: The same folder as the executable: <InDesign>.
  • Mac OS: The same folder as other libraries required by InDesign, such as DataBaseLib.dylib. They are in a folder named something like this: <InDesign>/contents/macos/

Plug-ins

To use the AAT, some Adobe QA plug-ins must be loaded. The minimum set for AAT to function is shown in the following table.
Windows Mac OS
AutoUI.apln AutoUI.InDesignPlugin
AATArch.apln AATArch.InDesignPlugin
AATArchUI.apln AATArchUI.InDesignPlugin
QAMenu.apln QAMenu.InDesignPlugin
QAScript.apln QAScript.InDesignPlugin
QAScriptTestSuite.apln QAScriptTestSuite.InDesignPlugin
QAXMLParser.apln QAXMLParser.InDesignPlugin
Snapshotting.apln Snapshotting.InDesignPlugin
TestLibPlugin.apln TestLibPlugin.InDesignPlugin
TestUILibPlugin.apln TestUILibPlugin.InDesignPlugin

These plug-ins may be part of a debug build that you already have. They are not normally part of a release build. Binaries of these plug-ins are in the ITK folders named as follows:

  • Windows:
    <ITK>/build/win/<debug|debugx64|release|releasex64>/testing
  • Mac OS:
    <ITK>/build/mac/<debugcocoa64|releasecocoa64>/packagefolder/contents/macos/testing

To load these plug-ins, use one of the following techniques:

  • Move the testing folder to a location where the plug-ins would be loaded by the application; for example, the application install folder.
  • Mac OS only: Create an alias to the testing folder, and move the alias under the plug-ins folder.
  • Windows only: Use PluginConfig.txt to ensure the testing plug-ins load.

If there are no Testing or QA menus in the application, this is one symptom that you are missing the required testing plug-ins. In that case, check your configuration to ensure that these plug-ins load.

Required data files

Some data files must be present to use AAT. The expectation is that there is a QA folder that is a peer of the <InDesign> folder. The required data files are in the following folder:

<ITK>/build/qa/aat/

The folders containing required data files are as follows:

qa/aat/requiredqa/aat/test cases/libraryqa/aat/test cases/acquisition_testing

On Windows, the default location of application installation for InDesign 2025 is as following:

C:\Program Files\Adobe\Adobe InDesign 2025\

you would place the QA folder from the InDesign Tool Kit in the following location:

C:\Program Files\Adobe\QA\

On Mac OS, the same rule applies: The QA folder should be a peer of the <InDesign> folder. If you run the application at least once with the shared libraries and plug-ins required to run AAT, the QA folder is created for you in the required location.

If you receive asserts in debug when trying to bring up the Action Automation Editor, this is one symptom of missing these AAT data files. See Launching the Action Automation Editor.

Acquire palettes for user-interface extension

If you bring up the Action Automation Editor now, there is nothing under the Loaded UI Extensions node of the Source Components. You can populate the UI Extensions node by creating a new document and executing GetAllPalettes.xml using either of the following methods:

  • Select QA > Action Automation Tool > Run Session File. In the file-selection dialog, navigate to qa/aat/test cases/acquisition_testing/GetAllPalettes.xml. See Executing a single session file.
  • In the Action Automation Editor, click Load Session File... to load GetAllPalettes.xml session, then click the Execute... button.

Both execute the GetAllPalettes.xml session file and acquire all palettes in their default state with document open. These acquired palettes are loaded into the Action Automation Editor as a Preloaded Palette UI Library, for your convenience later. For more information, see Using a preloaded palette user-interface library.

About the examples

The <ITK>/source/itksamples/aatexamples/ folder contains subfolders with sample AAT session files and related assets. Familiarize yourself with the organization of this folder before working through the examples. The subfolders include the following:

  • examplefiles - This contains most of the sample scripts referred to in this chapter.
  • extend-library - This contains sample scripts to extend the session file library. See Extending the Session File Library.
  • examplefiles-errors - This contains sample scripts that deliberately cause error reports. See "Creating custom error messages" in Log Files.
  • acquire-ui - This contains sample scripts for capturing user-interface components and sample dialog descriptions. See Working with user-interface extensions.

We recommend that you back up the aatexamples/ folder before working through the procedures in this chapter, to avoid inadvertently overwriting or changing session files on which other procedures depend.

Introduction to Action Automation

This chapter is structured as a set of procedures using the AAT. Completed examples of each procedure are provided; in some cases, these are starting points for other procedures.

Test scripts can be expressed as a sequence of actions, regardless of whether they are intended for testing code in the Print, Text, or XML subsystems. Actions are the building blocks of test cases and bug specifications: They comprise the repeatable keystrokes or menu selections necessary to create a test case that you are trying to validate or find a defect that you are trying to reproduce.

Do not confuse an action in the sense we use it here with the low-level API concept of an operation identified by an ActionID. An action in the sense used in describing the AAT need not be associated with a particular ActionID, although it can be.

Often, test cases contain implicit actions that may seem obvious to the writer of the test script but ambiguous to someone else running that script. For example, take the test case "Draw a one inch by one inch square." Several implicit steps must happen before the first step can be executed: The user needs to launch the application, create a new document, select a page of that document, decide whether to create the square with a tool or bring up a dialog, and determine the coordinates on the page where the square will be drawn.

Including the implicit steps in a test case would be useful for anyone trying to run this test case. Hence, an ideal test script explicitly states each step necessary to complete a given case.

Actions are the building blocks of the AAT. Explicit automated actions yield a test case. In essence, actions are single events assembled to consistently reproduce a given test case.

Stored externally as XML-based files, action sessions (that is, test cases) have the advantage of being portable, flexible, and straightforward to use as a tool for automating most test scripts. Action sessions can be devised with little programming knowledge. As long as you understand the fundamentals of good test-case creation (for example, making each action explicit), you can automate your test scripts, converting repetitive testing tasks into scripts that can be run automatically, with failures logged to an external text log for straightforward bug reporting.

Action sessions have several uses. For example, testers can include action sessions with most bug reports, to ensure reproducibility by engineers. Action sessions can be used to create test files, to test legacy functionality, or to create test environments for manual testing. Also, it is relatively straightforward to carry out regression testing for bugs if an action session exists for the bug steps. The session files are cross-platform, meaning that, once you define an action session on one platform (say, Windows), you can run it on another (say, Mac OS) by transferring the session file from one platform to another, without having to recompile (for example, if you were writing white-box test code).

Building a test

This section familiarizes you with the main components of the AAT and allows you to create a session file containing a set of test steps. It covers how to do the following:

Launching the Action Automation Editor

There are some prerequisites for working with the AAT in the release build, like ensuring that the testing plug-ins load; see Installation. In the debug build, the AAT should be configured correctly by default.

To open the Action Automation Editor window:

  1. Launch an InDesign build that is configured to work with AAT. See Installation.
  2. Choose QA > Action Automation Tool > Action Automation Editor. A dialog should appear:
    itk-guide-2.gif

Using the Action Automation Editor

As shown in the preceding figure, the Action Automation Editor is divided into two columns:

  • The Source Components column contains a tree of actions and supporting components (for example, loops) from which you build tests. You can consider the Source Components to be your test case tools containing both actions and ways to execute them.
  • The Sequencer column contains the actions and components that you choose from Source Components, listed in the order in which they will be executed. This window displays a view of the action session as you build it.

The Action Automation Editor is the main interface for building and executing action sequences to automate your test:

  • To load an existing session file, click the Load Session File... button.
  • To start with a new session file, click the Clear Sequencer button.
  • To change a Session file, add, remove, duplicate, or replace actions with actions from the Source Components column.
  • To change the parameters of an action, double-click an action in the Sequencer column, or select an action and click Edit, to bring up the Item Editor (see Item Editor).
  • To add an action to an action sequence, double-click an action in the Source Components column.
  • To bring up a dialog to search for an action, right-click within the Source Components column.
  • To bring up a dialog to search for an action or turn comments on/off for selected actions, right-click within the Sequencer column.
  • To load acquired palettes and dialogs, click the Extend UI... button.
  • To add your saved action sequences to the session library, click Extend library....
  • To save and execute the contents of the Sequencer column, then close the dialog, use the buttons at the bottom of the Action Automation Editor dialog.
Note
When you click the Execute button, you are asked whether you want to save before executing the session file; however, you do not have to save before executing new scripts. This allows you try the script without changing your saved scripts.

Building an action session to create a new document

To construct an action sequence to create a new document:

  1. Notice that the session file starts with a default action sequence in the Sequencer. To add any actions, you must have at least one sequence in the session:
    itk-guide-3.gif
  2. In the Source Components panel, navigate to Actions > Doc Actions > New Doc and either double-click the New Doc item in the tree view or click the Add button while New Doc is selected. The New Doc action is added to the action sequence on the right-hand, Sequencer panel:
    itk-guide-4.gif
  3. Click the Save button, and save the XML file to any location. Action session files are saved in an XML-based format and can be viewed in any Web browser or text editor. Also, they work across platforms and can be shared with other testers.
  4. Click the Execute button. A new document is created.

Item Editor

Any action in your sequence has a set of parameters; you can either specify these explicitly or accept the defaults. To specify them, you must edit the action in the Item Editor. The Item Editor is accessible by double-clicking the action in your sequence or selecting the action and clicking the Edit button. This brings up the Item Editor modal dialog, which is split into two panes: a Parameters panel on the left and a panel on the right where you can specify the value of the parameter selected:

itk-guide-5.gif

Parameters are settings that apply to the action you are editing. For example, when creating a new document with a New Doc action, there is a Landscape parameter that can take the value Yes or No; Yes creates a document in Landscape orientation, and No creates a document in Portrait orientation. The preceding figure shows parameterizing the number of pages in the new document created for a New Doc action.

Most parameters can be specified using variables. Variables can be set before executing an action as Input and can be set during execution as Output variables (See Input and output variables). You can set the variable name and value in the right pane of the Item Editor dialog. New values are updated after you close the item editor and bring it up again next time.

The set of the parameters varies according to the actions. Some parameters are used in most actions, such as Comments, Comment Out, Break, Action Descriptions, Validation level, Abort on error, etc. Other parameters are specific to the actions. The following table lists some of the most common action parameters that may be useful when you construct your AAT sessions.
Parameter Descriptions
Abort on error See "Abort on error" in Debugging and fixing action sessions.
Action Descriptions Describes the action. Usually includes the purpose, pre-conditions, etc. of the action.
Bounds Commonly used for actions that create page items.
Break If Yes, AAT stops at this action. Used for debugging. See "Break" in Debugging and fixing action sessions.
Comment Out If Yes, this action is commented out and is therefore not executed.
File Commonly used in actions that involve files, like open, place, import, and export, as well as acquisition actions to store an acquired user interface.

Editing actions in the Item Editor

In the following steps, we take the sequence from Building an action session to create a new document and modify it to create a four-page document:

  1. Select the New Doc action and open the item editor for that action using one of the two methods described above.
  2. Click the Num Pages parameter and enter "4" in the Value text field. Click OK.
  3. Click the Execute button. You are prompted with an alert to save changes. Whenever you have unsaved changes in an action sequence and you click Execute, you are prompted to save. If you click No, your changes are not saved to a session file.
  4. Click Save, and watch the sequence execute.

You should now see a four-page document created.

Practice creating a document

Create a session that generates the following document, using what you know so far about action sessions:

  1. Using the Action Automation Editor, create a new 10-page tabloid document with master text frames turned on. Hint: See the Page Size parameter on the New Doc action and Master Text Frame.
  2. Save the action session to a convenient location. Dismiss the Action Automation Editor dialog.
  3. Verify the results by choosing File > Document Set-up. Save the InDesign document to a convenient location as doc-with-master-text-frame.indd, by choosing File > Save As. This script is in the examplefiles folder, in doc-with-master-text-frame.xml.

Loading and editing test cases

This section familiarizes you with the concept of a session file, which is where an action sequence is stored.

Loading action sessions from a session file

Saved session files can be loaded into the Sequencer using the Load Session File... button. This button brings up a file-open dialog, which you can use to choose the XML session file you want to execute:

  1. Open the Action Automation Editor.
  2. Click the Load Session File... button.
  3. Open apply-swatch-to-box.xml in the examplefiles folder.

When you load a session file, it replaces the contents of the Sequencer. If you have unsaved changes in your current Action Session, clicking the Load Session File... button prompts you to save changes.

Opening session files from a shared network drive can lead to contention with other testers. If you open from a shared network drive, save the session as a new file locally, to avoid potential conflicts. Also, when the application is open, be careful about deleting session files that you opened previously with the Action Automation Editor.

Executing a single session file

Loading a session file displays the contents of the session file and allows you to run and/or edit it however you choose. If editing the session file is unnecessary and you want to run the session file directly:

  1. Close the Action Automation Editor window, if it is open.
  2. Choose QA > Action Automation Tool > Run Session File. When a file-selection dialog appears, select the apply-swatch-to-box.xml file in the examplefiles folder.

This executes the session file named apply-swatch-to-box.xml without having to open the Action Automation Editor.

Executing multiple session files

To run several session files at once:

  1. Close the Action Automation Editor, if it is open.
  2. Make sure the pointer tool is selected.
  3. Choose QA > Action Automation Tool > Run All Session Files In Directory. Select the test-subset folder, in the examplefiles folder.

This runs all session files in the folder.

To run a subset of tests, create a folder for that set of Action Session files, and use the Run All Session Files in Directory command. There is no Cmd/Ctrl selection to allow selection of multiple files from a file list.

Editing an action session

  1. Load apply-swatch-to-box.xml (in the examplefiles folder) into the Action Automation Editor. Execute the session, and notice the results in layout view.
  2. Expand the Action Sequence named Apply Swatch To Box, and select Create Rectangle by single-clicking. (Do not double-click the item, or you will open the Item Editor for this action. If you do open the Item Editor, dismiss it without making changes and continue.)
  3. In the Source Components tree view, choose Actions > PageItem Actions, select Create Regular Polygon, and click the Replace button. This replaces the rectangle action with the polygon action in the Sequencer panel:
    itk-guide-6.gif
  4. Add an action to create an oval from PageItem Actions beneath the polygon action.
  5. In the Sequencer panel, click and drag the Create Oval item above the Create Polygon item.
  6. Save the session file to a convenient location, and execute it. The edits made in steps 3 and 4 are reflected in the document that is created. The script up to this point is in the examplefiles folder, named apply-swatch-to-poly.xml.
  7. In the Sequencer, select Create Oval and click the Remove button below it. Save to a convenient location, and execute. The oval is no longer created. The script completed up to this point remains in the examplefiles folder, named apply-swatch-to-poly-oval.xml.

Creating and applying swatches

See whether you can adapt the session file (apply-swatch-to-poly.xml) created in Editing an action session" Editing an action session" to do the following:

  1. Change the New Swatch action in the session to 100% Cyan and 0% for the others. Hint: See the Color Value 1, 2, ... parameter of the New Swatch action.
  2. Change the New Swatch action name to MyCyan.
  3. Create a new spot color (C=0, M=100, Y=100, K=0) with the name MySpot. Hint: Add a New Swatch action, see the Is Spot parameter of the New Swatch action, and turn off Use Values for Name.
  4. Create a 10-sided polygon with a 10% inset. Hint: See the Number of Sides and Inset parameters on the Create Regular Polygon action.
  5. Apply MyCyan to the fill and MySpot to the stroke.

Save the session file to a convenient location, and execute it. After you execute the action sequence, zoom in to verify that the polygon's stroke is now red. For one implementation of the procedure above, see the session file apply-spot-to-poly.xml in the examplefiles folder.

Organizing and reusing action sessions

This section examines nesting of action sequences, adding your own session files to the existing library, and running one session file from within another.

Nesting action sequences

One main goal when automating testing is to create reusable, flexible components. When designing session files, create small sequences of actions; this increases the likelihood that you can reuse the sequences in other session files and makes it easier to isolate problems. Execute one sequence and verify that it works before adding another sequence to your session file.

Up to this point, we have considered action sequences that consist of one action sequence containing a set of actions. You can nest action sequences within one another, to structure your test code and make it modular. The benefits of modularizing test sequences in this way are equivalent to modularizing program code; the tests become easier to read, and you can use the name of the action sequence to indicate the intent of a block of test actions.

Session File Library

The Session File Library can be found in the Source Components tree view, as a peer of the Actions node you already used. The Session File Library allows you to access common action sequences as shown in the following figure; it lists all default session files and the session files you added. The named action sequences within each session file are displayed in the tree view as children of each session file item in the library:

itk-guide-7.gif

You should add session files that you use frequently. For example, if you realize that you are always adding the same set-up actions to every new session file you create, save those set-up actions as a session file and add it to your library.

Extending the Session File Library

The following figure shows adding to the Session File Library:

itk-guide-8.gif

When you add a sequence from the Session File Library, the sequence is no longer connected to the action sequence stored in the Library; that is, you are adding the individual actions in the session file from the Library to your current action session, rather than creating a reference to a shared session file. If you update the sequence in the session file stored in the Library, it does not update in other session files in which you have used that sequence. You must edit each instance of the sequence in each separate session file. This means that the Library is not a universal panacea to creating tests; you should ensure that the session files in the Library are correct and stable before using them widely.

In the following steps, we add an existing session file to the Session File Library:

  1. Launch the Action Automation Editor, clear the Sequencer, and click Extend Library.
  2. Select the apply-tag.xml file in the extend-library folder to add the session file to the Library.
  3. Load the create-box.xml session file from the examplefiles folder. Save to a convenient location, with a different filename.
  4. Expand the Session File Library node under Source Components, and locate the apply-tag.xml session file that you added above. Expand the node for the Library listing. Select the item Apply Tag, and you should see the Add >> button become enabled.
  5. Click the Add >> button to add the actions from the library to the action sequence, making sure you are adding it at the end of the existing action sequence. The actions from the Apply Tag sequence should be copied into your action sequence.
  6. Save to a convenient location, and execute. This script is in the examplefiles folder, named apply-tag-to-box.xml.

Executing a session file from within another session file

Instead of reusing sequences by extending the library, you could use an Execute Session File action, located under the Actions > Custom Actions node in Source Components. This action executes a particular session file that you direct it to, as part of another action sequence. The advantage of using an Execute Session File action in your sequence is that the executed session file is self-contained. Any changes are made in one place, the executed file. A disadvantage is that, if you need to isolate a problem, you may have to troubleshoot multiple session files.

A further disadvantage is that you cannot pass parameters into session files that you execute from another session file. This means, for example, if you create context in one session file, like a page item that you want to pass into the actions in the other session file, you cannot do this by executing one session file from within another. In this case, you should use the approach described in Extending the Session File Library.

Working with page items

There can be many ways to create a particular layout object. For example, to create text, you can directly create a text frame with the text tool and begin to type, place a story, click in a graphic frame with the text tool and type, or click in a text frame and paste text. Alternately, you can click a path and create text-on-a-path.

Just like manually creating a page item, usually there are multiple ways to create a given item through action automation. It is helpful to consider what method you want to use. Sometimes it does not matter, as long as the result is the same; other times, the method matters a great deal. For example, if you are testing a tool that creates specific page items, you want your automation to create new page items using the given tool, not another method.

Before you manually create a page item, you specify where you want the page item to go-not only in terms of its coordinates, but also the document and page relative to the start of the document-or you locate the spread and choose a page within that spread relative to the start of the spread. If you have multiple documents open, you choose the document in which the object goes. If multiple views are open on one document, when manually creating a page item, you choose the view (out of those opened on the document) in which you want to create the page item. You also may choose the page on which you will create the object and where on the page you will start to drag with the page-item tool.

These same decisions need to be made in your automation. You must be explicit about which document, which page, and where on the page you want to create an object in the action session. The following figure shows the wrong way to create a rectangle, where the page is not made explicit:

itk-guide-9.gif

The following figure shows the correct way, where the page is explicit. In both cases, only one document is open, so we do not have to tell the session file which document to target.

itk-guide-10.gif

When creating a new document with an action session, sometimes it is easier to use the Close All Docs action under Application Actions before starting. This eliminates the need to specify which document or window you are targeting, since there can be only one.

Creating and modifying page items

This section looks at the basics of creating and modifying page items.

Page items are drawn by default from right to left. To draw page items from left to right, use a click and drag action, not the Create (something) action. The default location for new page items is (0, 0) (upper-left corner of the page). No margins are taken into account. Page items are created at the default location, unless another location is specified in the Item Editor.

Each page in the document has an index associated with it, which is a number that identifies the page you are targeting. This is a zero-based index, so page numbering starts from 0. To put an object on page 1 of the document (as displayed in the Pages panel), the action needs to use page index 0. To put an object on page 2, specify page index 1, and so on. In other words, take the page number displayed in the Pages panel, subtract 1, and the result is the index to use.

The same applies for anything you have to target; for example, collections of documents and spreads use a zero-based index. The first document opened (Untitled-1.indd) has an index of 0; the second document opened, 1; and so on.

Creating basic page items

  1. Open the Action Automation Editor, and press Clear Sequencer if needed.
  2. From Actions > Doc Actions, add a New Doc action.
  3. From Actions > Page Actions, add a Get Nth Page action, immediately beneath the New Doc action.
  4. From Actions > Page Item Actions, add each of the following page-item actions and specify their bounds:
    • Rectangle (Create Rectangle)
    • Oval (Create Oval, etc.)
    • Line
    • Polygon
  5. Using the Item Editor, change the Action Sequence name to Create Page Items.
  6. Save the Action Sequence to a convenient location, and execute. A sample script named create-page-items.xml implements this recipe.

Note that a new Action Sequence has the default name Action Sequence. It is good practice to change the name of your action sequence to something descriptive. The name appears in the Action Automation Editor and makes organizing long sequences, troubleshooting, and re-using Action Sessions easier, especially if you use the Extend Library feature. See Extending the Session File Library.

When editing or creating page items, the location and size of the object is specified using four parameters, in this order: X (left), Y (top), H (height), W (width).

For example, add a Create Rectangle action and edit the Bounds value, making it 2, 2, 3, 4 inches. You are telling the application to start at (x, y) and drag down the page to (H) and across the page to (W). This corresponds to the following: "Go to (2 inches right, 2 inches down) from the page origin, and create an item 3 inches high and 4 inches wide."

Any item you create must fit on the pasteboard. We recommend that you do not specify negative numbers in the bounds of a create action, as the behavior of the application is undefined.

Applying attributes to page items

The following procedure creates some page items and modifies their attributes:

  1. Open the Action Automation Editor, and press Clear Sequencer.
  2. Load the create-page-items.xml session file. Change the name of the Action Sequence to Create Page Items and Attribs. Save to a convenient location, with a different filename.
  3. Modify the Rectangle to begin at X=28 points, Y=53 points, with Width and Height equal to 72 points.
  4. Modify the Oval created to begin at X=3 inches, Y=2 Inches. Make it 4 inches wide and 1 inch high.
  5. Modify the Line to begin at X=5 inches, Y=1 inch.
  6. Transform the following objects:
    • Skew the rectangle by 15 degrees. Hint: Look under Actions > Page Item Actions.
    • Resize the oval to 50% of its width and height. Hint: See Page Item Actions > Resize.
    • Change the stroke on the line to 15 pts. Hint: See Page Item Actions > Set Page Item Stroke Weight.
  7. Save the action session and execute. This script is in the examplefiles folder, named create-page-items-attribs.xml.

Typing text into a story

This procedure shows one way to enter text into a story, as if it had been typed into a window through the user interface:

  1. Open the Action Automation Editor, and press Clear Sequencer if needed.
  2. Load the new-default-doc.xml session file, and save it to a new location. Rename the action sequence (edit the Action Sequence action and change the Name parameter) to Typing Text.
  3. From Actions > Story Actions, add a New Story Action, immediately after the Get Nth Page action, at the bottom of the Typing Text sequence.
  4. From Actions > UI Actions, add an action to Type Text.
  5. Go into the Item Editor by either double-clicking the Type Text action or selecting the action and clicking the Edit button in the Action Automation Editor dialog.
  6. Specify a text file for the Type Text action. Hint: See the File parameter associated with the action. Choose the testfile.txt file in the examplefiles folder. Hint: Make this relative to the Session File's folder; see the Relative Folder widget. Because this is a QA tool, the AAT editor expects test files to be specified as paths relative to the QA folder, which is why this option appears as the default.
  7. Execute the session file. The text in the test file should be inserted into the new story. This script is in the examplefiles folder, named typing-text.xml.

When working with text, consider whether placing a text file would work better for your purposes. It is much faster to place a text file than to type text; under Page Item actions, see the Place File at Selection action.

If you want to test a feature that depends on an end user actually typing, not just putting text in a document, use the Type Text action. For example, suppose you want to set a default style, then test it was respected as text was typed. You could create an action sequence to set the text style, then type text into the story and determine the style settings; this would let you verify that the defaults in that text frame were correctly respected, which placing cannot do in all situations.

In addition to the typing-text.xml session file, there is a related session file in the examplefiles folder, named placing-text.xml. In the placing-text.xml, a Place File at Selection action replaces Type Text. Execute both session files, and compare the relative speeds of the two scripts to create text.

Cutting, copying, and pasting text

  1. Save typing-text.xml as a new session file in a convenient location.
  2. From Actions > Story Actions, add a Select Text action immediately after the Type Text action.
  3. Cut the text. Hint: Use Cut selection under Actions > Doc Actions.
  4. Add a New Story Action. Specify the bounds, so this story will not overlap the story you created previously; for example, X=4 inches, Y=1 inch.
  5. Paste into the last story you created. Hint: Use Paste into Story, under Story actions.
  6. Create another New Story, again specifying the bounds so it does not overlap either of the existing two stories; for example, X=1 inch, Y=5 inches. Paste into it.
  7. Save the session file to a convenient location, and execute. You should now see three text frames, the first of which is empty after the cut-selection operation. The other two should have the content that was typed into the first story. This script is in the examplefiles folder, named cut-copy-text.xml.

Filling with placeholder text

  1. Open the Action Automation Editor, and press Clear Sequencer.
  2. Load the new-default-doc.xml session file from the examplefiles folder.
  3. Edit the Action Sequence, and retitle it as Placeholder Text.
  4. From Actions > Story Actions, add a New Story Action, immediately after the Get Nth Page action, at the bottom of Placeholder Text sequence.
  5. From Actions > Story Actions, add a Fill with Placeholder text action, immediately after the New Story action.
  6. Save the session file to a convenient location, and execute. You should find you have a text frame containing placeholder text. This script is in the examplefiles folder, named placeholder-text.xml.

Placing files

The objectives of this section are to introduce you to the basics of placing assets.

When placing graphics and text, consider whether you will run the test on more than one machine or platform. If so, create a relative path to the graphic or text file, rather than an absolute path. Otherwise, the AAT cannot automatically target the path to the asset on the new machine, and your test will fail.

Placing graphics

  1. Open the Action Automation Editor, and clear the Sequencer.
  2. Load the new-default-doc.xml session file. Change the name of the action session in the Sequencer to Place Graphics.
  3. From Actions > PageItem Actions, add a Place File at Point action, immediately after the Get Nth Page action, at the bottom of Place Graphics sequence.
  4. In the Item Editor, specify the desired coordinates. The default is 0,0,pts. Change this to X=50pts, Y=100pts.
  5. Under Parameters, click File and browse to the file named bottle.tif in the examplefiles/images folder.
  6. Save to a convenient location, and execute the action sequence. You should see a graphic placed on the page. This script is in the examplefiles folder, named place-graphics.xml.

Placing text to autoflow

  1. Open the Action Automation Editor, and clear the Sequencer.
  2. Load the new-default-doc.xml session file. Rename the Action Sequence, by setting its Name parameter to be Place RTF.
  3. From Actions > PageItem Actions, add a Place File at Point w/ Autoflow action, immediately after the Get Nth Page action at the bottom of Place RTF sequence.
  4. In the Item Editor, specify the desired coordinates. The default is 0,0,pts. Change this to X=36pts, Y=36pts.
  5. Under Parameters for the Place File action, click File, and browse to the text file named testfile.rtf in the examplefiles folder.
  6. Save to a convenient location, and execute the action sequence. You should find an RTF file placed on the page. This script is in the examplefiles folder, named place-rtf.xml.

Placing text into threaded frames

  1. Launch the Action Automation Editor, and clear the Sequencer.
  2. Load the new-default-doc.xml session file. Change the name of the Action Sequence in the Sequencer to be Place Linked.
  3. From Actions > Story Actions, add a New Story Action, immediately after the Get Nth Page action, at the bottom of Place Linked sequence.
  4. From Actions > PageItem Actions, add a Place File at Selection action, immediately after the New Story Action. Because the New Story action leaves a text-insertion point in the new story, which counts as a selection, you can place a file at the "selection" (insertion point).
  5. Edit the File value for the Place File action: choose large.rtf in the examplefiles folder.
  6. From Actions > PageItem Actions, add a Create Linked Text Frame immediately after the Place File at Selection action.
  7. Save the session file to a convenient location, and execute the action sequence. You should see two text frames, with a story flowing between them that came from the placed RTF file. This script is in the examplefiles folder, named place-linked.xml.

Placing inline graphics

To create inline graphics, you must first have some text, then either create a text insertion or select a range of text. You then place a graphic at the selection (or insertion point) to create an inline graphic.

Some Select Text action parameters are important to know about when creating inlines:

  • TextStart Index - The Nth character specifies where you want the selection to start.
  • Selection Length - Specifies the number of characters included in the selection. A value of 0 gives you a text insertion. A value of (-1) selects to the end of the story.

To place inline graphics:

  1. Open the Action Automation Editor, and clear the Sequencer.
  2. Load the new-default-doc.xml session file. Change the name of the Action Sequence, editing its Name parameter to be Place Inline.
  3. From Actions > Story Actions, add a New Story Action immediately after the Get Nth Page action at the bottom of Place Inline sequence. Set the frame origin to x=1 inch, y=1 inch, with Width=6 inches and Height=6 inches.
  4. From Actions > Story Actions, add a Fill with Placeholder Text action immediately after the New Story action.
  5. From Actions > Story Actions, add a Select Text action immediately after the Fill with Placeholder Text action.
  6. Change the Selection Length parameter to 0 for the Select Text action (it defaults to -1). This creates an insertion point but does not select any text.
  7. From Actions > PageItem Actions, add a Place File at Selection action immediately after the Select Text action. Set the File parameter to bottle.tif, from the examplefiles/images folder.
  8. Save to a convenient location, and execute the action sequence. You should see a graphic placed as an inline at the start of the story within a text frame. This script in the examplefiles folder, named place-inline.xml.

Linking text frames

This section explores how to create linked text frames using the Action Automation Editor.

Expand Text Frame Actions in your Source Component list, and notice the Link To Text Frame and the Link From Text Frame actions. To manipulate text-frame threads, we use variables to refer to different text frames and use these as action parameters. For more detail about using variables to refer to objects we create in scripts, see Input and output variables.

Link two existing stories

  1. Launch the Action Automation Editor, and clear the Sequencer.
  2. Load the new-default-doc.xml session file from the examplefiles folder. Set the name of the Action Sequence to be Link Two Stories.
  3. From Actions > Story Actions, add a New Story Action immediately after the Get Nth Page action, at the bottom of Link Two Stories sequence. Set the Bounds to X=1 inch, Y=1 inch, with both Width and Height 3 inches. Edit the Output Frame parameter to be TextFrame1, rather than the default TextFrame.
  4. Add another New Story action right after the new New Story action. Set the frame origin to X=1 inch, Y=5 inches, with both Width and Height 3 inches. Edit the Output Frame parameter to be TextFrame2.
  5. From Actions > Text Frame Actions, add a Link To Text Frame after the new New Story action. Specify that you want to link From TextFrame1 and link To TextFrame2.
  6. From Actions > Text Frame Actions, add a Get Story from Text Frame action immediately after the Link From Text Frame action. Specify that you want to get the story from TextFrame1.
  7. From Actions > Story Actions, add a Fill with Placeholder Text action immediately after the Get Story from Text Frame action. This should put placeholder text into the story that begins at TextFrame1 and flows through TextFrame2.
  8. Save to a convenient location, and execute the session file. You should see two linked text frames. Confirm this by choosing View > Show Text Threads and selecting one of the text frames with the pointer tool This script is in the examplefiles folder, named thread-two-stories.xml.

Practice threading (linking) text frames

  • Start from thread-two-stories.xml, and save to a new filename. Specify different variables for three text frames, and thread them from the first frame to the last frame. This is really just an extension of the procedure in Linking text frames. For a completed script, see thread-three-stories.xml.
  • Save the session file to a new name, and reverse the order of threading; that is, thread from the last frame to the first frame. For a completed script, see thread-three-stories-reverse.xml.
  • Save the session file under a new name, and thread from the middle frame to the last frame, then to the first frame. For a completed script, see thread-three-stories-middle.xml.

Programming concepts

The AAT provides testers with a user interface, the AAT Editor, with which to create automated tests. The tool makes it possible for a tester to create automated tests without requiring an extensive technical background. That said, there are certain programming concepts and methods built into the tool, which may require some study for those who have never scripted or programmed before.

The concepts presented in this section will help you understand how each action affects every other action by way of input and output variables, how loop statements are used in action sequences, and how to use logic to control the flow of action sequences. The concepts introduced in this section include the following:

  • In variable - The variable that refers to the object that is the target (input) for an action. The action executes on the In variable.
  • Index Value - A numerical representation of an object in relation to other objects of the same type. For example, if you imagine a queue of 100 cars stretching back from a traffic light, each car has a unique index value. The index count begins at 0. If you count from the traffic light back down the queue, you begin with the car at the light (index=0), the one behind it (index=1), and so on.
  • Loop - A loop is a sequence that starts at the beginning with some initial conditions and continues repeating the same steps in order until it completes, with a terminating condition that indicates when to stop; for example, when the loop is carried out 100 times.
  • Nth - A way of referring to an object based on its index value. In the context of the AAT, Nth values always are zero-based, and objects in a collection are referred to as zeroth, first, second, and so on.
  • Object - Any entity associated with InDesign. Objects are created by actions. You can manipulate an object only if you have a variable that refers to the object.
  • Out variables - The variable that refers to the object that is emitted (output) by an action.
  • Parameter - The name of a property of an action or sequence that can be specified by its value. For example, Selection Length is a parameter of the Select Text action.
  • Value - The setting applied to a parameter. For example, the Selection Length parameter could be set to the value zero.
  • Variable - A that refers to an object to allow you to manipulate it; for example, TextFrame1 might identify the first of a series of text frames that you want to link together.

Variables in Action Automation

With AAT, variables are the objects an action takes in (the In variable) or produces (the Out variable). Variables can be anything-rectangles, pages, XML tags, documents, swatches, text strings, numbers-and are identified by a name the action specifies.

Every action that produces an object outputs a default name to represent that variable. For example, if you use the New Doc command, the output variable is Doc. In addition, most actions that require an input variable reference an expected variable. For example, if you select the Insert Pages action, it expects an Input variable called Doc.

Parameters of an action may accept values directly or values stored in variables. For the New Doc action example, you can specify the number of pages of the new document as 10 using the "Num Pages" parameter in the top right of the Item Editor dialog, or you can provide a variable name (for example, "numPages") at the bottom right of the dialog and initialize the variable to 10.

AAT variables do not specify a type. You can assign a string to a variable that was used as an integer.

Input and output variables

If you examine the thread-two-stories.xml session file from Link two existing stories, note how Doc is an output variable in the New Doc action, but it is an input variable in the Get Nth Page action. Variables are neither exclusively input nor exclusively output. When an output variable is assigned based on an action, that variable is available for input to another action later. In short, a variable gives you a name with which to refer to an object later.

An output variable is the name of the object the action created. For example, Page is the default output variable for the Insert Pages action.

An input variable is the name of the object the action needs to successfully create its object. For example, the Insert Pages action requires an input variable that references a document, as shown in the following figure. By default, the Insert Pages action expects a variable named Doc to be available and to refer to a document created by an action like New Doc.

itk-guide-11.gif

The Sequencer pane of the Action Automation Editor displays input and output variables in parentheses next to the action name for any actions that have variables associated with them. In and Out are used for input and output variables, respectively.

Sometimes an action requires variables other than the In variable shown when the action is added to an action sequence. For example, the Apply Swatch To Page Item action (Actions >\ Swatches) depends on two input variables, Target Page Item and Swatch. Only the Target Page Item is shown as an In variable. The AAT tool typically reports an error if an input variable is not defined and gives you an indication of how to fix the problem.

Variable names

Variables can be renamed, depending on how you want to use them. For any action that outputs an object, you can edit the Out variable if desired. Sometimes this is necessary, as in the example of linking two TextFrame objects, where you might choose to name them TextFrame1 and TextFrame2, to specify the From and To frames.

Note
The scope of a variable is the entire session. Choose your variable names carefully.

Using custom variables

Suppose you want to create two documents and work with both throughout an action sequence. If you execute the New Doc action with the default output variable, the first document you create is assigned a variable named Doc, but as soon as the second New Doc action is executed, the variable named Doc is reassigned to the second document; see the following figure. A straightforward workaround to this problem is to change the default name of the New Doc output variable to something different in each case, say Doc1 and Doc2.

itk-guide-12.gif

To define custom variable names:

  1. Clear the Sequencer. Rename the sequence to be Custom-Variables.
  2. From the Doc actions, add a Close All Docs action to your action sequence.
  3. Add a New Doc action to your action sequence. Edit the action, changing the Output Doc parameter of the action to Doc01.
  4. Add another New Doc action, setting its Output Doc parameter to Doc02.
  5. Assume that we want to create an oval page item in the first document and a rectangle page item in the second document. Before we can create the page items, we need variables that can refer to pages in the documents.
  6. From Page Actions, add a Get Nth Page action to the action sequence. By default, this action requires an input variable called Doc, specified in the Target Document parameter. For this action, we want a page from the first document, referred to by the Doc01 variable, so edit the action and change the Target Document input variable to Doc01; see the following figure. We must do this, because we renamed the output variable for both New Doc actions, and the default variable Doc does not exist in our sequence. We now have two variables that reference open documents, so we can refer to the two documents independently.
    itk-guide-13.gif
  7. Edit the Output Page parameter of the Get Nth Page action, to be Page01. The Get Nth Page action assigns a variable to the page object with a default name of Page, and we want to make it clear that the page we are referring to is in the first document.
  8. Add another Get Nth Page action, but this time, set the Target Document parameter of the action to Doc02, and set the Output Page parameter to Page02.
  9. From Page Item actions, add a Create Oval action, which we will create in the first document. Before editing the action, notice how the action is phrased. Actions that require an input variable have a default variable assigned to them when you add them to an action sequence. In this case, the Create Oval action expects an input variable named Page, but this variable does not exist in the action sequence. We need to edit the action to reflect the page on which we want the page item to be created.
  10. Edit the Target Page parameter of the Create Oval action, to specify an input variable named Page01. Edit the Bounds parameter of the action to be X=1 inch, Y=1 inch.
  11. Add a Create Rectangle action. Again, the action expects an input variable named Page, which does not exist in our action sequence.
  12. Edit the Target Page parameter of the Create Rectangle action, to specify an input variable named Page02. Edit the Bounds parameter of the action to be X=2 inch, Y=2 inch.
  13. When you are finished, your action sequence should look like the following figure. Save it to a convenient location, and execute the sequence. This should create two documents, one with an oval page item, and the other with a rectangle page item. This script is in the examplefiles folder, named custom-variables.xml.
    itk-guide-14.gif

Variable operations

The AAT provides a collection of miscellaneous actions; for example, for mathematical functions and string manipulations. These actions can be found under Actions > Variable Operation and are listed in the following table.
Operation Meaning
Math Left value [+, -, *, /,%], right value = value.
Unary Takes a single variable and performs floor, ceiling, and round operations.
Compare Compares two values based on an operations (<, >, =, etc).
Random Number Generates a random, real number specified by min and max parameters.
Output Rect Outputs a variable for the "x1, y1, x2, y2, units" values specified in the action.
Output Point Outputs a variable for the "x,y,units" values specified in the action.
Offset Rect Outputs a variable for a new set of "x1,y1,x2,y2,units" based on previous coordinates, offset by "x,y" as specified in the action.
Offset Point Outputs a variable for a new set of "x1,y1,units" based on previous coordinates, offset by "x,y" as specified in the action.
Get Point From Rect Outputs a variable for a new set of "x, y, units" based on an input rect as specified in the action parameter.
Get Value From Point Outputs a variable for x or y value of an input point specified in the action parameter. The units for the variable also can be set in the action parameter.
String Actions Manipulates a string based on the supplied parameters. For example, you can append one string of text to another.
String Information Returns information about a string, based on the supplied parameters. For example, you can return the total number of characters in the string.
Output Path Specifies where you want a file to go.
Extract File Name from Path Returns a string contain the filename.
Get Session File Name Returns the filename of the session.
Get Platform Folder Separator Returns "\" or ":".
Get Local Point from Global Converts global coordinate to local.
Get Global Point from Local Converts local points to global.

Get Nth object

An InDesign document can be viewed as a collection of object collections. Suppose you have a one-page document that contains a rectangle, linked graphic file, and text frame. That one-document object includes a page object, and that page object includes three page items-the text frame, the link, and the box.

In Using custom variables, we considered how variables are assigned to new objects created by your action sequence. In this section, we discuss how variables can be assigned to existing objects in your document.

Consider the following scenario. Suppose you want to create a new document and draw a box on the first page. Through the InDesign user interface, you choose File > New, supply the document parameters and click OK, select the Rectangle Tool, click and hold down the mouse button in the area of the page where you want the box to start, drag the mouse to where you want the box to stop, and release the mouse button.

The AAT lets you automate this operation with the New Doc and Create Rectangle actions. Superficially, it might seem that, to automate the action of creating a box as described above, all you would need to do is add a New Doc action followed by a Create Rectangle action to your action sequence; however, there is a missing step: if you created the action sequence (New Doc, Create Rectangle), you would find the Create Rectangle action fails, with an error like this:

Error: CreateRectAATAction: failed to obtain document or page. Did you set a page?

Many actions require an input variable, including Create Rectangle, which has the default input variable of Page. If you change the Target Page parameter of the Create Rectangle action to be Doc, then execute the action sequence, the Create Rectangle action succeeds.

Suppose we want to create a new document with five pages, with the rectangle created on the third page (index=2). If we edit the New Doc action so the number of pages is five and execute the action sequence again, we get a five-page document but with a rectangle on the first page (index=0). Clearly, we need another variable to specify that we should use the third page in the document, not the first.

In this situation, when you need to specify a position within an indexed collection, using an action of the form Get Nth object can solve our problem; we can obtain a variable that refers to an existing object and use it throughout our action sequence.

We need to have a variable that refers to the third page in the document, and use that as our input variable for the Create Rectangle action. Using a Get Nth Page with the Index value set to 2 (since the pages are indexed from 0), then assigning the output variable (Output Page) from that action as the input variable (Target Page) for the Create Rectangle action, we can create a rectangle page item on the third page of the document.

When to use Get Nth object

You may have to use an action of the form Get Nth object when another action you want to use requires an input variable that was not specified previously in your action sequence.

An action of the form Get Nth object allows you to refer to an object of a specific type based on its index value. The index enumerates all objects of a given type in a particular context. For example, if you have 10 documents open, N can take the values N=0, 1, 2, ..., 9.

An action to Get Nth object is available for many object types. You can Get Nth Page, Get Nth Swatch, Get Nth Document, and so on. Occasionally, Get Nth is the only way to obtain a variable referring to an object in your document.

Get Nth object and Get Num object look similar but have different meanings: Get Nth lets you choose an object by its position in a collection, while Get Num tells you the size of a collection. For example, when a Get Num Pages action executes, the value it outputs is the number of pages in the document, which is not zero-based.

Adding a rectangle to a given page

  1. Close all docs, and create a New Doc with five pages.
  2. From the Page Actions, add a Get Nth Page action.
  3. Edit the Page Index parameter of the action, and set the Page Index to 2. Because we want to draw a rectangle on the third page of the document, we need to enter the value as 2 because the values taken by the Page Index are zero-based.
  4. Before dismissing the Item Editor, note the name of the output variable, specified by Output Page for this action. The default output variable is named Page.
  5. Add a Create Rectangle action from Actions > PageItem Actions. Edit the action, and double-check the In Variable value of the Target Page parameter. By default, it should match the value from the preceding step. If not, make the appropriate change.
  6. Save the session, and execute. When the session completes, a rectangle should be on the third page of the document. This script is in the examplefiles folder, named rect-on-page3.xml.

Practice using Get Nth

Use actions of the form Get Nth object to do the following. Create a new document with five pages. Add a different type of page item to each page, using Get Nth Page actions. For a sample solution, see five-page-items.xml in the examplefiles folder.

Loops and flow of control

A loop in an action sequence is one way to build a repeating action. Loops allow you to reduce the number of actions you list in your action sequence, while maintaining the number of repetitions you want.

For example, suppose you want to draw 1,000 boxes in a document. One way of doing that is to add 1,000 Create Rectangle actions to your action sequence. A more efficient way is to add an iterative loop to your action sequence that performs the Create Rectangle action 1,000 times. Either way, you end up with the same number of boxes in your documents, but the former approach yields a much longer action sequence than the latter.

The number of times a loop executes can be based on the following:

In each case, the loop executes a set of actions (the actions within the loop) based on a condition specified by the loop statement. Loops can be found under the Action Sequences and Loops node in Source Components.

Iterative loop

An iterative loop is a sequence of actions that repeats the number of times specified in the loop action. The Iterative Loop action has an Iterations parameter that controls how many times the actions in the loop are executed.

Building an iterative loop

In this procedure, the action sequence creates a new document five times, using an Iterative loop:

  1. Clear the Sequencer. From Actions > Doc Actions, add a Close All Docs action.
  2. From Action Sequences and Loops, add an Iterative Loop action to the Action Sequence. By default, the loop is set to run once.
  3. Edit the loop, and set the Iterations parameter to 5.
  4. Add a New Doc action to the action sequence, which should be nested underneath Iterative loop:
    itk-guide-15.gif
  5. Save to a convenient location, and execute the script. Five new documents should be created. This script is in the examplefiles folder, named iterative-loop.xml.

Practice using iterative loop

Create an action sequence using Iterative Loop to do the following:

  • Create five documents of two pages each. In each document, add a story that spans two pages, filled with placeholder text. Hint: if you use Link From Text Frame, you may have to use Get Story From Text Frame to get the story to fill with placeholder text, unless you used custom variables to refer to the outputs from New Story. For one solution, see iterative-loop-stories.xml.
  • Advanced: Use an Iterative Loop to create 12 rectangle page items, each 8 picas square, with 2 picas between them, on one page, starting at the top-left corner (X=3 picas, Y=3 picas). Arrange the boxes so there are four across and three deep. For one solution, see iterative-loop-tiled-boxes.xml.

File loop

A file loop is a sequence of actions that executes for all files in a specified location of a specified file extension.

When using a File Loop action, you specify which folder to target and which file extension to look for in the folder. The default folder is the build/qa folder, and the default file extension is INDD, native InDesign documents.

You can use a file loop if you want to quickly place all graphics of the same type into a document.

Constructing a file loop

In this procedure, a file loop is used to perform an Place File action on each file with the "tif" file extension in the examplefiles/images folder.

  1. Clear the Sequencer, and add a Close All Docs action.
  2. Add a New Doc action, and add a Get Nth Page action to your action sequence.
  3. From Variable Operations, add an Output Point action, starting with X=3 picas, Y=3 picas.
  4. From Action Sequences And Loops, add a File Loop to the action sequence.
  5. Edit the File Loop, and set the File(s) in Folder value to the path of the examplefiles/images folder. Edit the File Extension value to be tif.
  6. Add a Place File at Point in the context of the File Loop (nested in its scope within the action sequence), and specify the input variable to be Point, and the output variable from the Output Point action you added above the File Loop. Do not bother to set the File parameter of the Place File at Point action, because it will be set by the File Loop.
  7. Add an Offset Point, with input variable Point and offset by X=2 picas, Y=3 picas.
  8. Save to a convenient location, and execute the action sequence. You should see a variety of images placed on the first page of a new document. This script is in the examplefiles folder, named file-loop.xml.
    itk-guide-16.gif

Practice using file loops

Try this using file loops and different types of actions.

Try the task in "Constructing a file loop" (in File loop), but use the CurIter output variable of the loop sequence to create a unique place point for each image. Hint: Use actions from Variable Operations, like Math, if you want to use div (/) or modulo (%) operators to create a tiled layout.

Parameter loop

A parameter loop is a sequence of actions that executes for each available value of a particular action. Parameter loops are helpful when you want to apply each available value in a list or combo box. For example, when you create a new document with InDesign Roman, you have 11 page-size parameters to choose from (Legal, Letter, A5, etc.). If you want to create a new document for each available size, you could construct a parameter loop using the Page Size values of the New Document dialog.

Constructing a parameter loop

In this example, the action sequence draws a line on a page, selects the line, and executes a parameter loop over the Stroke Type values in the Stroke palette to apply each type to the selected line:

  1. Bring up the Stroke palette, and choose Show Options to show the stroke drop-down widget list.
  2. Load the draw-line.xml session file from the examplefiles folder.
  3. From Action Sequence And Loops, add a Parameter Loop action to the action sequence. Make sure the action is added at the bottom of the sequence.
  4. Nested inside the Parameter Loop action in your action sequence, add the action(s) you want to execute. To iterate the stroke types that would be available to an end user, open the Loaded UI Extensions node in the Source Components panel, and navigate to Stroke Palette > Stroke Actions >> Type: Stroke Drop Down. Add the action to your action sequence.
  5. Edit the Test Entry parameter of the Parameter Loop action, to execute over the List Value of the Stroke List action. Any action nested under the Parameter Loop can be selected for a parameter loop:
    itk-guide-17.gif
  6. Save to a convenient location, and execute the action sequence. You should see the application cycle through the different stroke types and apply them in turn to a line. This script is in the examplefiles folder, named stroke-line.xml.

Do/while loop

Do/while loops execute a series of actions while a specified condition is true. There are two distinct types of do/while action:

  • A do/while variant, where a condition, the result of a logical expression, is evaluated after the loop, meaning the actions in the loop are executed at least once:
    Do { <execute actions...> } While (condition)
  • The while variant, where a condition is evaluated before the loop, meaning it is possible for the actions in the loop not to be executed at all:
    While (condition) { <execute actions...> }

A do/while loop is controlled by four parameters, described in the following table. These specify whether the loop is a do/while variant or a while variant, and the logical expression that controls the loop's execution.
Parameter Meaning
Do/while Specifies whether the loop is the do/while variant or while variant. The while variant evaluates the condition first, before executing. The do/while variant executes the actions in the loop at least once before evaluating the condition.
Operator The evaluative statement between the left value and right value (see the following figure). Operator values can be Equal, Not Equal, Less Than, Less Than or Equal To, Greater Than, or Greater Than or Equal To.
Left value The variable or value that precedes the operator statement (see the following figure).
Right value The variable or value that follows the operator statement (see the following figure).

itk-guide-18.gif

When constructing a do/while loop, you evaluate the relationship between one entity and the other; for example, whether value "a" is greater than value "b," or the logical expression (a > b). When evaluating expressions containing a logical operator (in this case, >, greater than), you need to consider the type of variable on either side of the operator, to be sure you are evaluating an expression that makes sense. The variables on either side of the logical operator (also called operands) are represented by the left-value and right-value parameters.

Creating a do/while loop

This procedure creates a page item and pastes it repeatedly, until there are six page items in the spread:

  1. Construct an action sequence based on new-default-doc.xml. Save it to a convenient location, and rename the action sequence to be Do-While-Paste.
  2. From Page Item actions, add a Create Rectangle action after the Get Nth Page action.
  3. From Page Item actions, add a Select Page Item action after the Create Rectangle action.
  4. From UI Actions, add a Menu action after the Select Page Item action. Edit the Commands parameter of this Menu action to be Edit > Copy.
  5. From Spread actions, add a Get Nth Spread action after the Menu action, to acquire the first spread (at index 0) in the document.
  6. From Spread actions, add a Get Num Items on Spread action after the Get Nth Spread action. Edit the Result parameter of the Get Num Items on Spread action, and change it from Value to NumItemsOnSpread.
    itk-guide-19.gif
  7. From the Action Sequences and Loops, insert a Do/While Loop action after the Get Num Items on Spread action. We will edit the parameters of this Do/While Loop action to set up the conditions for it to evaluate. Change the name of the action to Paste-Loop.
  8. Specify the type of loop required. Edit the Do/While parameter of the Do/While Loop action, setting it to do/while; note the default value for this parameter is while. In this procedure, we require the do/while variant, because we want to paste at least one time before checking whether we pasted enough page items.
  9. Specify the operator that will produce the desired behavior. Edit the Operator parameter of the Do/While Loop action, and set the operator value to Less Than. In this case, we will terminate when there are six page items in the spread; we want the loop to iterate while the number of page items is less than six.
  10. Edit the left-value parameter of the Do/While Loop action. The left value should be the number of page items on the spread of interest. This is referred to by NumItemsOnSpread. Set the Variable field of the left-value parameter to the NumItemsOnSpread. If you set only the Value field of the left-value parameter, the variable name is treated as a string literal, and your loop will not iterate as intended.
  11. Edit the right-value parameter of the Do/While Loop action. We want six page items in the spread; therefore, enter 6 for the Value field. Now that we have set up the parameters for our Do/While loop, we need to specify the actions to execute.
  12. Nested within the do/while loop, we will add an action to paste the selected page item. From UI Actions, add a Menu action. Edit the Commands parameter of this Menu action to be Edit > Paste.
  13. Again nested within the do/while loop, we need to count the number of items on the spread again. If we do not add this action, our loop might run infinitely, because NumItemsOnSpread always would be 1. The Do/While Loop action guards against this by providing a check against infinite loops, turned on by default.
    itk-guide-20.gif
  14. Save the action sequence, and execute. You should observe six rectangle page items on the spread (though some may be on top of others, because they are pasted on the same location). This script is in the examplefiles folder, named do-while-paste.xml.

Object loop

An object loop loops over each object type specified. An object type could be anything in the document: pages, lines, boxes, words, etc. When you create an object loop, you are telling the action sequence to perform the actions nested in the scope of the loop on every object of the specified type.

When building an object loop, you must be aware of the parameters that control the behavior of the loop, listed in the following table. These specify the type of container object you want to look inside, the type you want to iterate over within a container object, and the range of container objects over which you want to search. You can think of the specification controlling the behavior of the object loop as a query.
Parameter Meaning
Finder Type The type of container in which you want to search. For example, you would look for inline graphics inside a Story or Table, but you would look for Stories or Swatches inside a Document.
Find Object of Class The object type for which you want to search.
Find In Target The object variable in which you want to search. For example, if you want to search only for text-frame objects on page 3, you would search only within that page variable.

Building an object loop

In this example, we use an object loop to apply a new stroke weight and type to a collection of page items. Follow these steps; the following figure summarizes these actions:

itk-guide-21.gif
  1. Load the new-default-doc.xml session file, and save it to a convenient location. Change the name of the action sequence to Object Loop Stroker.
  2. From Actions > Page Items Actions, add a Create Line action after the Get Nth Page action, to draw a page item on the first page of the document. Set the bounds to X=1 inch, Y=1 inch, W=2 inches, H=2 inches.
  3. From Page Items Actions, Add a Create Rectangle action after the Create Line action. Set the bounds to X=1 inch, Y=4 inch, W=2 inches, H=2 inches.
  4. From Page Items Actions, Add a Create Oval action after the Create Rectangle action. Set the bounds to X=4 inch, Y=1 inch, W=2 inches, H=2 inches.
  5. From Page Items Actions, Add a Create Regular Polygon action after the Create Oval action. Set the bounds to X=4 inch, Y=4 inch, W=2 inches, H=2 inches.
  6. From Action Sequences and Loops, add an Object Loop action to the sequence, immediately after the Create Regular Polygon action. We will edit the Object loop action to iterate the page items in the document.
  7. For the Object Loop action, set the Finder Type parameter to Document Content Finder, by choosing it from the drop-down list.
  8. For the Object Loop action, set the Find Object of Class parameter to SplineItem, by choosing it from the drop-down list.
  9. For the Object Loop action, set the OutputObject parameter to MyObject, rather than the default, Object.
  10. From PageItem Actions, add a Select Page Item action. This action should be nested within the Object Loop action above. Specify MyObject as the Target page Item parameter of the action.
  11. Open the Loaded UI Extensions node in the Source Components panel, and navigate to Stroke Palette > Stroke Actions >> Type: Stroke Drop Down. Add the action to your action sequence, nested inside the Object Loop action. Edit the action you just added, and set a List Value of Wavy from the drop-down list.
  12. Open the Loaded UI Extensions node in the Source Components panel, and navigate to Stroke Palette > Stroke Actions >> Weight: Combo Box. Add the action to your action sequence, nested inside the Object Loop action. Edit the action you just added, and set the Combo Box value to 4.
  13. Save to a convenient location, and execute the action sequence. You should see four page items created with a wavy stroke. This script is in the examplefiles folder, named object-loop-stroker.xml.

Logic sequences and clusters

Logic sequences allow you to control the flow of an action sequence, using if, else-if, and else statements. Informally, an if statement involves a test to see whether a condition holds. If it does, the AAT executes one set of actions; otherwise, it does something else.

The practical topics covered in this section include the following:

Conditional expressions and decisions based on the results of these expressions are common. For example, suppose you walk past the cinema with 5 euros in your pocket. If the price of the ticket is less than or equal to 5 euros, you buy a ticket; if the price is higher, but you have a friend with you, you borrow some money and then buy a ticket; and if you do not have a friend with you, you do not buy a ticket. There are several conditions you would evaluate one after the other. Using AAT terminology, this is a logic sequence.

Expressions in a logic sequence begin with "if," "else-if," or "else." When writing conditional expressions for AAT, you must follow these rules:

  • The logic sequence must start with "if"; it cannot start with "else" or "else-if."
  • An if statement does not need to be followed by an else or else-if statement, if your evaluation does not require it. That is, you do not have to supply a default or fallback set of actions when the condition of interest does not hold.

The operator within a conditional expression in a logic sequence can be any of the following: Equal, Not Equal, Less Than, Less Than or Equal To, Greater Than, or Greater Than or Equal To. Logical sequences can be nested, to create relatively elaborate (though possibly hard to understand) logic.

The simplest useful logic sequence, written in some notional scripting language, is something like this:

If (a > c) then-do-this

This is an if statement, consisting of a keyword "if," a condition (in this case, "a > c "), and operations to perform (in this case, "then-do-this"). The operations indicated by then-do-this are performed only if the condition is true, making (a > c) a decision point for this example.

Here is an example of a slightly more elaborate logic sequence:

If (a > c) then-do-this
else if(a > b) then-do-that
else do-the-other

The action (do-the-other) is performed if "a" fails to be greater than either "b" or "c." For example, if a=1, b=3, and c=2, the do-the-other action is performed.

Building a logic sequence is like building a do/while loop, described in Do/while loop. The Action Sequences and Loops node in the Source Components panel contains an action named Logic Sequence. You can parameterize an instance of this action with a left value (left operand), operator, and right value (right operand). See the following table for information about these parameters. You also need to specify the type of the Logic Sequence; that is, whether it is an if, else-if, or else statement.
Parameter Meaning
Operator Operator can be Equal, Not Equal, Less Than, Less Than or Equal To, Greater Than, or Greater Than or Equal To.
Left Value Left operand. The variable or value that precedes the operator statement; for example, in the statement (A > B), A is the left value.
Right Value Right operand. The variable or value that follows the operator statement; for example, in the statement (A < B), B is the right value.

Constructing a logic sequence: if statement

A fundamental type of logic sequence is an if statement, where we perform an operations if a specified condition is true.

This procedure creates a new document and a story in it, fills it with placeholder text, and changes the point size of the text to a random value. If the point size of text in a given text frame is greater than a threshold value (in this case, the default point size for placeholder text, 12 pts), we assume that the text is overset. In that case, an additional linked text frame is created to hold the overset text. An if statement is used to determine whether a linked text frame is needed, based on the point size of the text in the text frame under consideration.

  1. Load the new-default-doc.xml session file, and save it to a convenient location.
  2. From Story actions in the source component, add a New Story action after the Get Nth Page action. Name the output story Story01, and name the text frame TextFrame01. Set the Bounds parameter of the action, with units=picas, X=3, Y=3, W=20, H=60.
  3. From UI actions, add a Menu Action to fill with placeholder text, after the New Story action. Edit the Command parameter of the Menu Action, and look under the Type Menu node.
  4. From Page Item actions, add a Select Page Item action after the Menu action. Set the Target Page Item parameter of this action to TextFrame01.
  5. From the Variable Operations node, add a Random Number after the Select Page Item action. Edit the parameters of this action, setting the Min parameter to 8 and the Max parameter to 20. Set the Result parameter to a custom variable named RandPointSize. Edit the Real or Integer parameter to be Int32, so only integers for (random) point sizes are produced.
  6. From Actions > Variable Operations, add a String Action. Set the In Variable field of the Input String parameter to RandPointSize, and set the Output String to randStr. Set the Action Type to None.
  7. Add another String Action. Set the Value field of the Input String to "pt" (space intentional). Set the In Variable field of the Target String parameter to randStr; this target string is the base string to which the AAT appends the Input String. Set the Output String to randStr. Set the Action Type to Append.
  8. From Loaded UI Extensions, add Character Palette >> Character Actions >> 12 pt Combo Box after the Random Number action. Edit the Combo Box parameter, and set the In Variable to randStr. The randStr variable holds the value of RandPointSize, with the string "pt" appended to it, which is the expected format when setting this particular Combo Box.
  9. Insert a Logic Sequence action after the 12 pt Combo Box action. Verify the parameter Logic Type of this action is set to if.
  10. The default point size for placeholder text is 12 points, so assume a point size greater than this value is overset. This is only an approximation, but it is sufficient for our example. Edit the Operator parameter of the Logic Sequence to be Greater Than.
  11. Edit the Left Value parameter of the Logic Type action, setting the In Variable to RandPointSize and Var Type to Int32.
  12. Set the Right Value parameter of the Logic Type action to 12, and set Var Type to Int32.
  13. Nested within the if action, add a New Story action. Set Output Story to Story02 and Output Frame to TextFrame02. Set the Bounds parameter of the action, with units=picas, X=28, Y=3, W=20, H=60.
  14. From Actions > Text Frame Actions, again nested within the if action, add a Link From Text Frames action, immediately after the new New Story action. Set the From Text Frame parameter to TextFrame01 and the To Text Frame parameter to TextFrame02.
  15. Save to a convenient location, and execute the action sequence. This script is in the examplefiles folder, named if-link.xml.

Constructing a logic-sequence cluster: else-if

A logic-sequence cluster supports else-if logic. Suppose we want to modify the procedure in Constructing a logic sequence: if statement, so when the point size of text is less than 12 points, we fit the frame to the content. Follow these steps:

  1. Load the session file named if-link.xml, and save it to a convenient location.
  2. Edit the Random Number action in the action sequence, to set the Min parameter to 4 and the Max parameter to 16, increasing the chance of underset text.
  3. From Logic Sequences, insert an action of type Logic Cluster Sequence. This should be positioned immediately after the Character... action and before the If... (Logic Sequence) action.
  4. Nest the original If ... (Logic Sequence) inside the new Logic Sequence Cluster action, by dragging and dropping within the Sequencer. You will know when you have nested the If ... (Logic Sequence) correctly by closing the Logic Sequence Cluster node, which should hide the If ... (Logic Sequence) when it is correctly nested.
  5. Select the If ... (Logic Sequence) action, and duplicate it. This should create a duplicate action within the Logic Sequence Cluster.
  6. Edit the second If action. Change the Logic Type parameter of the second If... to "else if," because we want to make sure (RandPointSize < 12) is evaluated, even when we know RandPointSize is not greater than 12, because we will not take any action if RandPointSize is 12.
  7. Now the action should appear as Else.... Change the Operator of this action from "greater than" to "less than."
  8. Edit the sequence of actions within the Else... (Logic Sequence) action. Remove the New Story and Link From Text Frame actions, since we will not create a new text story when the point size is less than 12 pts.
  9. From UI Actions, add a Menu Action, and edit the Command parameter to the path Object Menu > Fitting > Fit Frame to Content.
  10. Save to a convenient location, and execute the action sequence. Run this action sequence several times, and you should find that the linked frame is created when the point size is greater than 12 pts, but below this value, the frame is fitted to the content. This script is in the examplefiles folder, named if-link-else-fit.xml.

Logging and debugging

A principal objective of writing action sequences is automating some of your testing. As you write and run your tests, you may encounter the following bugs:

  • Bugs in the plug-ins you are testing.
  • Bugs in your action sequences; the sequences may not do exactly what you intended.

This section describes the logging features of AAT, how to use the AAT logs in your own testing, and how to determine the likely location of defects you observe during testing with the AAT.

Log files

The objectives of this section are as follows:

  • Identify what logs tell you.
  • Identify how you can use the logs to help develop tests.
  • Learn to use the logs to identify bugs.

Log files are an integral part of using the AAT to validate tests. Automating certain tasks saves time in several ways and allows you to focus on other testing areas that cannot benefit from automation; however, simply running an action sequence does not absolve you from ever looking at that area again, and this is where log files can come into play.

Each time an action sequence is run, two log are created that report on the status of the test while it ran:

  • QASessionLog.txt/xml.
  • AATSessionLog.txt.

These logs record information about the test that ran, including when it started and when it finished, build, platform, what steps (actions) were performed, and any errors that occurred. By keeping track of this information, you can use your action sequences to verify different test criteria, like performance, functionality, and errors.

AAT session log

The AAT session log, shown in the following figure, contains an account of every action and its supplied parameters of the action sequence, as well as information about the time it took the sequence and each of its individual actions to complete. Viewing this log is another method of determining which actions were run during the test; the other method is inspecting the action sequence in the Action Automation Editor.

itk-guide-22.gif

QA session log

The QASessionLog.txt/xml log files list errors that occurred during the test, as well as build and machine information. These are in the qa folder.

The QA session log includes errors encountered because of a problem with your action sequence, errors caused by bugs in the code you are testing, and/or forced errors.

Action sequence errors

While you are learning the tool, the most common type of error reported by the AAT is errors in your action sequence. These errors can arise for several reasons. If you construct the sequence of actions (Open Doc, Save Doc), the AAT tool reports an error something like this:

Error: Trying to save a new or converted document. You must call Save As

This is because the Save action is valid only for a modified document. The error provides you with an attempt at a resolution, to substitute another action for the inappropriate Save Doc. You can find a transcript of the error message in the action sequence if you inspect QASessionLog.txt. You also can find a detailed description of the behavior of the AAT action behind each action while the test ran, in AATSessionLog.txt.

Diagnosing and fixing an error

  1. Load the aat-error.xml session file from the examplefiles-errors folder. Execute the action sequence, and observe the error.
  2. Inspect QASessionLog.txt and AATSessionLog.txt, which are in the QA/logs/InDesign folder. This is the QA folder that is a peer of the installation folder for InDesign, not the one in the saved data folder. For more information, see Where is the QA folder I keep hearing about?.
  3. Using your judgment and the Action Sequence Editor, fix the problem, and save the action sequence to a convenient location.
  4. Execute the action sequence again with the edits, and check the logs again to see if any errors were reported this time.

Creating custom error messages

Built into the AAT is an action called Report Error, available under the Actions > Custom Actions node. This action allows you to write into the QASessionLog.txt file, by setting a parameter named Error String. The action also generates a warning dialog when the action sequence completes, indicating there are errors in the action sequence.

Using a custom error to indicate success or failure

In the following procedure, we consider how to use the Report Error action to report success or failure of a test. A minimal document is created, with a page item in the pasteboard, to the left of the first page of the document. A logic-sequence cluster is used to write a custom error, indicating whether the page item is on the first page of the document.

  1. Load create-rect-in-pasteboard.xml from the examplefiles folder. The sequence creates a new document, switches to the rectangle tool, and draws a box. Save the action sequence to a convenient location with a different filename.
  2. Select the last item in the sequence, the Click Drag action. From the Page Item action, add a Get Num Items on Page action at the end of the action sequence. Edit the action, and set the Result parameter, the output variable, to a custom variable named NumItemsOnPage. This variable is used later in a Logic Sequence.
  3. From Action Sequences and Loops in Source Components, add a Logic Sequence Cluster action.
  4. Nested in the scope of the Logic Sequence Cluster, add a Logic Sequence action from the Action Sequences and Loops node. Edit the Left Value, Operator, and Right Value parameters of the action, to construct an If statement NumItemsOnPage > 0. To make this explicit, the Operator parameter should be Greater Than, and the Left Value should take an In variable of NumItemsOnPage. The Right Value need not change, as the value of 0 is the default. Set the Var Type to Int32 for the Left Value and Right Value parameters.
  5. Nested in the scope of the If... (Logic Sequence), add a Report Error action, from the Custom Actions node. Edit the action and set the Error String parameter to "Error: Rectangle is reported as a page item on first page."
  6. From Action Sequences and Loops, add another Logic Sequence action beneath the If... (Logic Sequence) action but on the same level. You can achieve this by closing the If... node in the tree view of the sequence and leaving it selected while you add a new Logic Sequence action. Both these Logic Sequence actions should be nested inside the logic-sequence cluster. Edit the Logic Type parameter of the action just added to be "else."
  7. Nested within the scope of the Else statement, add another Report Error action. Set the Error String parameter value to. "Success: page item not reported on first page."
  8. Save the session file, and execute. You should see an error report with the string "Success..." that was entered in the previous step. This script is in the examplefiles-errors folder, named custom-error.xml.

The Report Error action can be used in developing action sequences. By reporting values, names, and other variables to the log file, it can help determine the state of objects that otherwise may be confusing.

Custom error messages using string actions

The following procedure uses String Actions to demonstrate how to construct more informative error messages. After the action sequence executes, the error message reports how many page items are on the first page, rather than just success or failure.

  1. Load custom-error.xml from the examplefiles folder, and save it to a convenient location with a different filename.
  2. From Variable Operations, add a String Actions action nested within the scope of the first if statement. The action should be added immediately above the Report Error action that is there already. By selecting the If... statement before adding the new action, you can add the action at the correct point in the sequence.
  3. Edit the String Actions action just added. Set the value of the Target String parameter to "Error: AAT reports the number of page items is " (the space at the end of string is intentional). Set the In Variable of the Input String parameter of the action to NumItemsOnPage. Ensure the Action Type parameter is set to Append, which is the default. Set the Output String to be CustomErrStr, a new custom variable.
  4. Edit the Report Error action. Set the In Variable of the Error String parameter to CustomErrStr.
  5. Modify the existing Click and Drag action in the action sequence to create a page item that is now on the first page. To do this, set the Start Point to X=1 inch, Y=1 inch and the End Point to 3 inch, 3 inch.
  6. Save and execute the action sequence. You should see a custom error message reported. This script is in the examplefiles-errors folder, named custom-error-string-actions.xml.

String Actions can be used to build logging statements based on different types of data, including variables, strings, and integers.

Debugging and fixing action sessions

Developing a methodology

There is a basic methodology you can use to ensure your action sequence works in the way you intend, by asking questions as actions are added:

  • What is the target? Does it need to be acquired; for example, using Get Nth <object>?
  • Is the target really the object reference needed?
  • Is a dialog or some other user interface needed to complete the action?
  • What controls a given loop? Does it need to be specified each time around the loop, or does the loop statement take care of that?

When you run into problems with your action sequence, the log files may provide answers. For example, if you want to create a new page item but do not have a page-object reference, an error is reported that the object does not exist.

If the action sequence does not generate an error but fails to do what you intend, it may be harder to determine the source of the problem. Perhaps there is a page-object reference already in the action sequence, but for a page other than the one intended. Experience with the AAT tool makes it easier to solve problems.

You can nest a series of actions in an action-sequence cluster. This can allow you to isolate and debug sets of actions, as well as remove sequences quickly if you no longer need them.

Abort on error

Built into all actions is a parameter called Abort on Error. By default, this parameter is set to No. When it is set to Yes for a specific action, if the action fails for any reason, the whole test is terminated and an error is reported.

For example, suppose an action sequence has a Get Nth Page action, which may try to acquire a page not available in the document. If you set the Abort On Error parameter to Yes for the Get Nth Page action, you can terminate the action sequence whenever the Get Nth Page fails.

You should set the Abort on Error parameter to Yes for actions that are critical to what you are validating. Also, use it to help troubleshoot your action sequences as you develop them.

Break

Another parameter that is useful for debugging is Break. Like Abort on Error, Break is set to No by default. When it is set to Yes for a specific action, it serves as a break point of the Session: AAT stops at the action and waits for user intervention. You can press Option Alt+S to step to the next action, or press Option Alt+R to restart the program. If the action is within a loop, it stops every time the loop executes.

Working with the AAT script debugger

AAT script debugger is a separate panel that displays the same session file as the one displayed in the Action Automation Editor. Changes made in either window effect the other. The debugger is useful when you debug an AAT script. When you set a break point in the action sequence using the Break parameter and execute the session, the Action Automation Editor disappears. When the AAT stops at the action, the action is highlighted in the AAT script debugger, and if you press Option Alt+S, the highlight moves to the next action, showing what is going on in your action sequence.

Automated tests: user interface or commands?

The objectives of this section are as follows:

  • Identify differences between command-based and user-interface-based action sequences.
  • Construct a user-interface-based action sequence.

Session files can include two types of actions: command actions and user-interface actions; see the following tables. Each action type has benefits and drawbacks, and understanding these will help you write tests faster and more efficiently. For example, user-interface-based actions tend to be slower, but they simulate end-user actions more convincingly.

The following table shows the benefits of command and user-interface actions:
User-interface actions Command actions
Reproduces end-user actions by automating palette and dialog widgets and window operations like click and click-and-drag. Quick and straightforward shortcut for the provided actions you want to run that would take more actions if done via the user interface.
Accompanying user interface for requested actions can be monitored while the test runs. Outputs object references that can be referenced later in the automation sequence.
Most InDesign features are accessible via user-interface actions. Command-based automation runs faster than user-interface-based automation.

The following table shows the drawbacks of command and user-interface actions:
User-interface actions Command actions
Requires more actions for the desired result. Does not accurately reproduce end-user actions in the application.
While building tests, the black-box tester must acquire dialogs and palettes. A limited number of commands are written relative to the number of available features in InDesign.
User-interface-based automation takes longer to complete than command-based automation. Command actions do not display the accompanying user interface for the requested action.

Command actions

Command actions execute without involving the user interface when they are running; for example, the action to create a New Doc is a command action. There is an application user interface (the New Document dialog) to create a new document, but it is not involved when the New Doc action executes. With the exception of the actions listed in User-interface actions, the actions under the Actions node of Source Components are command actions.

Command actions are intended to enable black-box testers to build tests quickly, when the application user interface associated with those actions is not important to the test. Command actions invoke code written by white-box testers (AATActions), which in turn uses the InDesign API directly.

User-interface actions

User-interface actions include menu commands, key presses, and mouse clicks. User-interface actions also include any actions that set data in widgets. When building an action sequence that reproduces the actions an end user would take with the user interface, this is described as "testing through the user interface." (See Working with the user interface).

Creating a new document with command and user-interface actions

The following procedure loads and runs a session file that creates a new document using the New Doc command and using user-interface actions. This example illustrates the differences between using commands and the user interface.

  1. Load the create-new-doc-two-ways.xml session file from the examplefiles folder. Expand the Action Sequences so you can view the actions they contain.
  2. Inspect the first action sequence, New Doc: Commands. It creates a 10-page document with facing pages turned off, with 1 New Doc action parameterized as required.
  3. Inspect the next action sequence, New Doc: UI. It has the same intent as above but requires several more steps. The extra steps automate the individual actions an end user would perform to create the 10-page document, with facing pages turned off.
  4. Execute the sequence.

When the first action sequence (New Doc: Commands) executes, a new 10-page document is created. When the second action sequence (New Doc: UI) executes, the New Document dialog appears. The action sequence sets the state of widgets in the New Document dialog; for example, it modifies the state of the number-of-pages edit box and facing-pages check box to something other than the defaults.

Building command and user-interface action sequences

The following two procedures create a cyan rectangle on the first page of a document. The first example uses command actions; the second, user-interface actions. The following figure shows the sequences side by side.

itk-guide-23.gif

Cyan rectangle - command actions

  1. Clear the Sequencer. From Actions > Doc Actions, add a Close All Docs action and a New Doc action. Add a Get Nth Page action, to acquire the first page.
  2. From PageItem Actions, add a Create Rectangle action. Set the Bounds to X=1 inch, Y=1 inch, Width=3 inches, Height=3 inches.
  3. From Swatch Actions, add a Get Nth Swatch action. Cyan is the fifth swatch in the swatch list, giving it an index of 4 (since the index is 0-based). Set the Swatch Index parameter of this action to 4.
  4. From Swatch Actions, add an Apply Swatch to Page Item action. Set the Stroke parameter of this action to No.
  5. Save to a convenient location, and execute. You should see a cyan rectangle created on the first page. A completed example of this is in the examplefiles folder, named create-cyan-rect-command-actions.xml.
Note
This example has additional actions to set up the document unit and increment preferences, before the Get Nth Page action.

Cyan box- user-interface actions

The main focus here is on how to create a page item with user-interface actions, and how to apply a swatch in the same way. To create the rectangle with user-interface actions, we need to switch tools to the Rectangle Tool. There are several ways to select the Rectangle tool:

  • Use the keyboard shortcut "m." To do this, we add the Key Press action from the UI Actions node and set its key press parameter to "m."
  • Select the Rectangle Tool from Loaded UI Extensions > Tools Palette, and locate the Rectangle Tool... action.
  • Select the rectangle tool from a menu. Add a Menu Action from Actions > UI Actions. Set its command parameter value to Tools > Rectangle Tool.

In the following procedure, we select the tool through the Tools palette:

  1. Clear the Sequencer, and add a Close All Docs action and a New Doc action.
  2. We need to provide a user-interface action to select the Rectangle Tool. Navigate through Loaded UI Extensions > Tools Palette >> Tools Actions > Rectangle Tool (M) Icon Action. Add this to the action sequence.
  3. Add a Get Nth Page action and a Get Nth Window action. Since we want the window and the first page of the document, the defaults for each action are sufficient.
  4. From Actions > Window Actions, add a Get Nth Window action. From Page Actions, add a Get Nth Page action.
  5. From Actions > UI Actions, add a Click and Drag action, and click it to edit it. The action has Target Window and On Page parameters; the defaults of Window and Page, respectively, are sufficient.
  6. Continue editing the Click and Drag action just added. Set the Start Point to 1 inch, 1 inch. Set the End Point to 4 inches, 4 inches.
  7. Navigate to Loaded UI Extensions > Swatches Palette > Swatch Actions, and add the user-interface action that appears as Name List Box. Edit the action, setting the List Value parameter to C=100 M=0 Y=0 K=0, the 6th item in the drop-down list.
  8. Save to a convenient location, and execute. You should see a cyan rectangle created. This script is in the examplefiles folder, named create-cyan-rect-ui-actions.xml.
Note
For multiple document views, specify the document with Actions > Doc Actions > Make Document Active before the Click and Drag action.

Choosing between command and user-interface actions

As the previous procedures illustrate, using user-interface actions for your automation can take several additional steps compared to the command-action equivalent. On the other hand, user-interface-driven automation has the major advantage of reproducing end-user actions more closely, so you can test your end-users' code paths more effectively.

We recommend that you focus on building tests that run through user-interface actions, if you are simulating black-box testing. When deciding whether to use command actions or user-interface actions, use the following questions to guide your decision:

  1. What user interface needs to be tested? You may have been asked directly to test some functionality through the user interface; in that case, use user-interface actions.
  2. Is there an existing command action for a particular operation? If there is no command action that can perform the required operation, you must use a user-interface action. Commands include actions frequently used in test cases, like New Doc or Create Rectangle. For features added by third-party plug-ins, for example, there are likely to be no command actions to exercise the features, but they still can be tested through user-interface actions. To write action sequences to test third-party code through the user interface, it may be necessary to acquire any third-party-specific user interface; see Working with user-interface extensions.
  3. Does one type of action (command or user interface) do something the other cannot? Both command actions and user-interface actions have limitations that may influence your decision to choose one over the other in a particular test case.

Working with the user interface

Several user-interface actions are available to manipulate the InDesign user interface. These can be found under Actions > UI Actions in the Source Components panel. They include the following:

  • Key Press - Presses the key or key plus modifiers specified.
  • Menu Action - Selects an item in a menu or pop-out menu.
  • Click Action - Clicks the mouse one or more times at the specified location.
  • Click and Drag Action - Clicks and holds down the mouse as it drags from the specified start point to the end point.

To see a more complete list, inspect the actions nested under Actions > UI Actions in the Source Components panel.

Using the user-interface actions to group page items

The following procedure uses a combination of user-interface actions in an action sequence that creates rectangle page items and groups them:

  1. Clear the Sequencer. Load the new-default-doc.xml session file, and save it with a different filename.
  2. Select the Get Nth Page action in the Sequencer, to start adding actions at the end of the action sequence.
  3. From Page Item Actions, add a Create Rectangle action. Set the Bounds to X=2 inch, Y=1 inch, Width=2 inches, Height=3 inches.
  4. From Page Item Actions, add a Select Page Item action.
  5. From UI Actions, add a Menu Action. Edit the action, and set the Command Parameter to Window Menu > Transform.
  6. To rotate the page item, navigate to Loaded UI Extensions > Transform Palette > Transform Actions > Transform Actions > 0° Combo Box (the one just above Rotation Angle Icon). Set the Combo Box Value parameter to 90 degree.
  7. From Page Item actions, add another Create Rectangle action. Set the Bounds to X=5 inches, Y=1 inch, Width = 2 inches, Height=3 inches.
  8. From Loaded UI Extensions > Tools Palette, add a Selection Tool Icon action.
  9. A Click and Drag action is added next. This action requires a Page and a Window target. The Page was acquired early in the sequence, and that object reference is still valid. From Actions > Window Actions, add a Get Nth Window action.
  10. From Actions > UI Actions, add a Click and Drag Action. Edit it, setting the Start Point to X=1 inch, Y=1 inch and the End Point to X=7 inches, Y=8 inches.
  11. Add a Key Press action to the bottom of the sequence. Edit the Key Press action. Set the Modifier parameter to Command/Ctrl, and set the Press Key value to "g." Ignore the values in the drop-down list, which provide some special characters; see Key presses.
  12. Save and execute the session file. You should see two rectangles have been grouped. A completed example of this is in the examplefiles folder, named group-rects.xml.

If the Click and Drag actions failed to select both page items, try to slow down the drag by increasing the "Num Steps" parameter of the action.

Click and Click and Drag actions can fail if palettes or dialogs obstruct the area of layout in which you want to click. Avoid having palettes in the way when running user-interface actions.

You also can suppress the AAT progress bar with a menu item on the QA menu. This is at the path QA > Action Automation Tool > Settings > Suppress Progress Bar.

Note
For multiple document views, specify the document with Actions > Doc Actions > Make Document Active before the Click and Drag action.

Using modifier keys with the key-press action

Many keyboard shortcuts depend on keyboard modifiers. The Key Press action, under Actions > UI Actions in the Source Components panel, can be parameterized with keyboard modifiers required to create the keystrokes for the keyboard shortcuts understood by InDesign, as shown in the following figure:

itk-guide-24.gif

This procedure sends the key press Cmd+Option+n (Ctrl-Alt-n), which creates a new document without showing the New Document dialog:

  1. Clear the Sequencer.
  2. Under Actions > UI actions, add a Key Press action.
  3. Edit the Key Press action. Set the Value field of the Modifier parameter to Command/Ctrl from the drop-down list. Type "+Option/Alt" (no spaces) after Command/Ctrl; see the following figure.
    itk-guide-25.gif
  4. Continue to edit the Key Press action. Set the Value field of the Press Key parameter to the character "n."
  5. Save and execute. You should see a new document created, without the New Document dialog appearing. This script is in the examplefiles folder, named new-default-doc-ui.xml.

Key presses

In Using modifier keys with the key-press action, a character "n" was entered as part of the keystrokes for the shortcut to create a new default document. Beside the basic alphanumeric characters, a Key Press action (under UI Actions) can be used to send the keystrokes that correspond to the keys like Enter (Return) and Delete. The following table shows so me keypress references.
Key Value
Enter (Return) \r
Delete \b
Tab \t
Home \h
End \e
Right Arrow >
Left Arrow \<
Up \u
Down \d

To press more than one modifier key, a second modifier can be entered, exactly as it appears in the drop-down list for modifiers. In Using modifier keys with the key-press action, the string "+Option/Alt" was appended to the modifier, to form the keystroke Cmd+Option+n.

If you enter a capital letter in the "Press Key" field, you do not need to enter Shift as a modifier. The same rule applies for the characters above number keys, like @ (shifted 2 on a US keyboard).

itk-guide-26.gif

Measurements

The Action Automation Editor understands several abbreviations for units and measurements. For example, any of the abbreviations in the following table works when specifying bounds or other measurements.
Measurements Abbreviations
Centimeters cm, centimeters
Ciceros cicero
Inches in, ins, inch, inches
Millimeters mm, millimeters
Picas p, picas
Pointspt, pts, points

Working with user-interface extensions

A key feature of the AAT is the ability to drive the InDesign user interface. The InDesign user interface needs to be captured or acquired to an XML file, representing the widgets in a palette or dialog.

The Action Automation Editor has a built-in set of user-interface extensions for palettes in their default states; you can use these preloaded user-interface extensions as described in Using a preloaded palette user-interface library. These preloaded user-interface extensions are XML-based descriptions of the default set of InDesign palettes, acquired during the installation of the AAT data files (see "Acquire palettes for user-interface extension" in Required data files). After a palette or dialog is acquired, actions available through its user interface become available in the Action Automation Editor for use in action sequences.

There are actions available through AAT that enable a palette or dialog to be acquired as part of an action sequence; these actions are under Extension Tools > Acquisition Actions. When a dialog or palette is acquired, the state of the dialog or palette at that moment is saved to an XML description.

The AAT obtains menu items from the keyboard-shortcut editor. Palette pop-out menus are considered menu items and are not part of the palette. This is true in both the keyboard shortcut editor and the Action Automation Editor. The menu items that appear on the palette pop-out menus are available to parameterize Menu Action actions (under Actions > UI Actions) through the Command parameter; to use these, no user interface needs to be acquired.

Acquiring a dialog targets the front-most dialog. The Acquire Dialog action (see Extension Tools > Acquisition Actions) can be parameterized to specify the output filename and folder to which the dialog-dump XML file should be written. By default, this action creates a file named Dialog_Dump.xml inside the QA folder. For more detail, see Acquiring a dialog.

When acquiring a palette, the target needs to be specified explicitly through the Command parameter of the Acquire Palette action (see Extension Tools > Acquisition Actions). The output filename and the folder to which the palette description XML file is saved are parameters of this action. By default, the palette-description XML file is saved to <palette-name>.xml; for example Align.xml for the Align palette. For more details, see Acquiring a palette.

After some user-interface components like a palette or dialog are acquired, you can create action sequences based on the palette or dialog. These sequences can be executed on another machine without the acquired user-interface XML files being present. To create action sequences based on the acquired user-interface component on other machines, you need to transfer one or more XML files; see Transferring a saved user-interface library between machines.

When you create action sequences based on user-interface extensions, particularly when testing before your product is released, the user interface you acquired can change. If you are using an acquired user interface in your action sequences, and this user interface changes, it is important to acquire the dialog/palette again and replace actions using that dialog/palette in existing session files with actions from the updated user-interface extensions.

Using a preloaded palette user-interface library

The default set of InDesign palettes should be available when the Action Automation Editor is launched, if AAT is correctly installed. These palettes are listed under Loaded UI Extensions in the Source Components panel. They reflect the user interface of the palettes when nothing is selected. If the palette of interest has other states that show different sets of controls, the palette may need to be acquired in that state. If no Loaded UI Extensions can be found, see How can I solve common problems, like loading AAT under release?.

Palette pop-out menus are accessed through a Menu action, not a UI Extension action. The menu items from the palette pop-out menus are in a subtree under the Palette Menus node; for example, when editing the Command parameter of a Menu Action (from Actions > UI Actions).

Aligning objects with a preloaded user-interface extension

  1. Open the Action Automation Editor and clear the Sequencer.
  2. Load the new-default-doc.xml session file from the examplefiles folder.
  3. From Page Item Actions, add a Create Rectangle Page Item action. Set the bounds to X=1 inches, Y=1 inches, Width=2 inches, Height=2 inches.
  4. Add another Create Rectangle Page Item action. Set the bounds to X=5 inches, Y=5 inches, Width=2 inches, Height=2 inches.
  5. From UI Actions, add a Menu Action, with the Command parameter set to Edit > Select All.
  6. From Loaded UI Extensions > Align Palette, add the action that appears as "Align horizontal centers Icon." This has the same effect as clicking the iconic button on the Align panel to align the selected page items:
    itk-guide-27.gif
  7. Save the session file to a convenient location, and execute. When the action sequence has executed, there should be two rectangle page items, aligned horizontally by their centers. This script is in the examplefiles folder, named align-rect-centres.xml.

Acquiring a dialog

Unlike palettes, there is no preloaded user-interface library for dialogs. To test the user interface of a dialog or test a feature accessible only through a dialog, the dialog first needs to be acquired.

To acquire a dialog, set up actions to open the dialog (for example, using UI Actions > Menu Action), then add an Acquire Dialog action from Extension Tools > Acquisition Actions.

Acquiring the New Paragraph Style dialog

The following procedure acquires the New Paragraph Style dialog:

  1. Open the Action Automation Editor, and clear the Sequencer.
  2. From Doc Actions, add a Close All Docs action and a New Doc action.
  3. From UI Actions, add a Menu action. Set the Command parameter of this action to New Paragraph Style. This is under Panel Menus > Paragraph Styles > New Paragraph Style...
  4. From Extension Tools > Acquisition Actions, add an Acquire Dialog action. Set the Value field of the Description File parameter of this action to something like NewParaStyleDialog_Dump.xml, in a convenient folder.
  5. Execute but do not save the action sequence. Note how the action to acquire the dialog cycles through the selectable dialogs of the New Paragraph Style dialog.
  6. The acquired dialog is appears under the Loaded UI Extensions part of the Source Components panel, with the New Paragraph Style dialog." You also may want to browse to NewParaStyleDialog_Dump.xml in the folder location you specified above. You should see a description of the dialog and the widgets it contains in this XML file. This script is in the acquire-ui folder, named acquire-para-style-dialog.xml.

When acquiring dialogs, be sure to use a Command parameter for the Menu Action (from Actions > UI Actions) that will bring up the precise dialog you require. For example, in the procedure above, a Menu Action was needed to bring up the New Paragraph Style dialog rather than, say, using the iconic button on the Paragraph Styles palette.

Acquiring the Move dialog

This procedure is another example of capturing a dialog, this time by adapting an existing action sequence:

  1. Open the Action Automation Editor, and clear the Sequencer.
  2. Load the align-rect-centres.xml session file, and save it to a convenient location with a different filename.
  3. Click the last action in the action sequence, Align – Align horizontal centers icon Action.
  4. From UI Actions, add a Menu Action. Set the Command parameter of this action to Object Menu > Transform > Move.
  5. From Extension Tools > Acquisition Actions, add an Acquire Dialog action.
  6. Edit the Acquire Dialog action. Set the Value field of the Description File parameter to MoveDialog_Dump.xml. This should save the dialog-dump XML file to the default folder, which is the QA folder, a peer of the application-installation folder.
  7. Save and execute. The Move Dialog should appear at the end of the sequence, then the Action Automation Editor dialog re-appears and the Move dialog appear under Loaded UI Extensions. At this point, you can inspect the MoveDialog_Dump.xml file saved to the QA folder. This script is in the acquire-ui folder, named acquire-move-dialog.xml.
Note
Step 6 indicates that the default folder for the dialog-dump XML file is the QA folder. In the sample script in the ITK, however, the dialog-dump file is saved in the same folder as the session file, using the "Relative to Session File Folder" option in the Item Editor dialog.

Acquiring a palette

Although palette user-interface libraries are preloaded in the AAT, you may need to capture a palette in a state other than its default state. If you are a third-party developer, you may have your own palettes and want to capture these.

Unlike acquiring a dialog, you do not need to bring up a palette in the user interface to acquire it. There is an Acquire Palette action under Extension Tools > Acquisition Actions. If you add an Acquire Palette action and edit the action, you can specify the palette you want to acquire through the Command parameter of this action.

Acquiring the Tags palette

  1. Launch the Action Automation Editor, and clear the Sequencer.
  2. From Extension Tools > Acquisition Actions, add an Acquire Palette action.
  3. Edit the Acquire Palette action, setting the Command parameter to Window > Tags. Set the Value of the Description File parameter to TagsPal.xml. This will save into the default location of the QA folder.
  4. Execute but do not save the sequence. Inspect the TagsPal.xml file created in the QA folder. This script is in the acquire-ui folder, named acquire-tags-palette.xml. It has the variation that it saves to the same folder as the session file.

If you want to use an acquired user interface on more than one machine, you can move the file to the second machine and load it through the Action Automation Editor; alternately, see Transferring a saved user-interface library between machines.

Using acquired dialogs and palettes

Entering text into an edit box of an acquired dialog

The New Hyperlink dialog is of medium complexity. It uses buttons, edit boxes, list boxes, and combo boxes. We begin by extending the user interface to include the New Hyperlink dialog after acquiring it:

  1. Create a New Document manually, type a word, and select it with the text tool. Choose Window > Interactive > Hyperlinks. For the New Hyperlink menu item (on the Hyperlinks palette pop-out menu) to be enabled, there must be a selection.
  2. Open the Action Automation Editor, and clear the Sequencer if needed.
  3. From Actions > UI Actions, add a Menu Action. Set the Command parameter to Panel Menus > Hyperlinks > New Hyperlink action.
  4. From UI Extension > Acquisition Actions, add an Acquire Dialog action. Set the Value field of the Description File parameter to NewHyperlinkDialog_Dump.xml.
  5. Execute the sequence. A script that implements these actions is in the acquire-ui folder, named acquire-hyperlink-dialog.xml. It was modified to automate the manual actions in the scenario above.

Having created a dialog dump for the New Hyperlink dialog, we can now load it and begin to use the actions it specifies in a new action sequence. The following procedure sets the name of the hyperlink created through this acquired-dialog user interface and dismisses the dialog through its OK button:

  1. Bring up the Action Automation Editor, and clear the Sequencer.
  2. If the New Hyperlink Dialog does not appear under the Loaded UI Extensions part of Source Components, click the Extend UI button in the bottom-left of the Action Automation Editor dialog, and select NewHyperlinkDialog_Dump.xml from the location where it was saved.
  3. Load the new-default-doc.xml session file from the examplefiles folder, and save it to a convenient location with a different filename. Select the last action in this action sequence, to ensure that further actions are added at the end of the sequence.
  4. From Actions > Story Actions, add a New Story action.
  5. From Story Actions, add a Fill With Placeholder Text action.
  6. From Story Actions, add a Select Text action. Set the Selection Length to 9 characters.
  7. From Actions > UI Actions, add a Menu Action. Set the Command parameter of this action to Panel Menus > Hyperlinks > New Hyperlink action, to bring up the New Hyperlink Dialog. From this point on, we will use the user-interface extension we have created to enter values for this dialog.
  8. From Loaded UI Extensions > New Hyperlink Dialog >> New Hyperlink Actions, add an action named Name: Edit Box action.
  9. Edit this Name: Edit Box action in the Item Editor. Change the Value field of the Edit Box Value parameter to MyHyperlink1.
  10. From Loaded UI Extensions > New Hyperlink Dialog >> New Hyperlink Actions, add an action named OK Button, which dismisses the dialog when executed.
  11. Save and execute. You should see a new hyperlink created.

We created a new hyperlink and gave it a name, but it does not yet have a destination; this is left as an exercise for the reader.

When using user-interface extensions like an acquired dialog, as in this example, it is important to remember that if the action sequence edits parameters of a dialog, you must add an action to click the OK button or Cancel to dismiss the dialog, or your action session will fail.

Setting a drop-down list-box value in an acquired dialog

The following procedure acquires the New Gradient Swatch dialog and uses it to create a new gradient swatch with a radial gradient:

  1. Launch the Action Automation Editor, and clear the Sequencer.
  2. From Actions > Doc Actions, add a Close All Docs action and a New Doc action.
  3. From Actions > UI Action, add a Menu Action. Set the Command parameter to Panel Menus > Swatches > New Gradient Swatch...
  4. From Extension Tools > Acquisition Actions, add an Acquire Dialog action. Edit the Description File parameter to NewGradientSwatchDialog_Dump.xml.
  5. Execute without saving. This takes a modest time to execute, since the acquisition of the dialog requires swatch libraries to be loaded. You can dismiss the New Gradient Swatch dialog when the Action Automation Editor re-appears behind it. This script is in the acquire-ui folder, named acquire-gradient-swatch-dialog.xml.

The following procedure uses the acquired-dialog user interface to set the state of one of the list boxes on the New Gradient Swatch dialog:

  1. Launch the Action Automation Editor, and clear the Sequencer.
  2. If New Gradient Swatch Dialog does not appear under Loaded UI Extensions, click the Extend UI button on the bottom left of the Action Automation Editor dialog, and select NewGradientSwatchDialog_Dump.xml from the location where it was saved by the previous procedure.
  3. From Actions > Doc Actions, add a Close All Docs action and a New Doc action.
  4. From Actions > UI Actions, add a Menu Action. Set the Command parameter of this action to Panel Menus > Swatches > New Gradient Swatch...
  5. From Loaded UI Extensions > New Gradient Swatch > New Gradient Swatch Actions > Gradient Options Actions, add an action named Type: Drop Down. Edit this action:, setting the Value field of the List Value parameter to Radial.
  6. Save to a convenient location, and execute the sequence. A radial gradient swatch should be created. This script is in the examplefiles folder, named create-new-gradient-swatch.xml.

Transferring a saved user-interface library between machines

Although it is not necessary to give dialog descriptions to execute session files on another system, if you want to create Action Sessions on multiple machines, you may want to set up your dialog extension library and use that library of dialogs on multiple machines.

When the AAT extends the user interface, an XML file named MyDialogExtensionLibrary.xml is created in the cached data folder under the QA folder (see Where is the QA folder I keep hearing about?). This file indicates where the loaded user-interface extension files are located (see the following figure). Relative links work just as with graphics, to the documents or my documents folders, desktop, QA folder, or anywhere inside those locations.

itk-guide-28.gif

If you have only one or two dialogs that you have captured, it is straightforward to just click Extend UI and browse to the Dialog Description files on any system; for example, if you place the captured user interface on a server volume that can be seen from all the test machines you are working on, use Extend UI, and point the AAT on each machine at the user-interface-extension files on the server. If you have many acquired user-interface-extension files and you want to create action sequences on many machines, consider putting the user-interface extensions on a server, but transfer the MyDialogExtensionLibrary.xml file between machines, to avoid having to click the Extend UI button for each user-interface extension.

The following procedure goes under the hood to examine how Loaded UI Extensions are represented on your local system and how to transfer them to another machine. There is an acquired user interface for the Move Dialog in the examplefiles folder. This defines the information the AAT needs to automate actions through the Move Dialog. In the procedure, we consider how data stored on one machine about a loaded user-interface extension can be transferred to another machine, to make the captured user interface available on the other machine.

  1. Be sure you executed the procedure in "Acquiring the Move dialog" in Acquiring a dialog, to create the dialog description XML file named MoveDialog_Dump.xml.
  2. Launch the Action Automation Editor, and clear the Sequencer.
  3. Navigate to Loaded UI Extensions in Source Components, and open the node to list the available user-interface extensions. If this is the machine on which you executed the procedure in "Acquiring the Move dialog" in Acquiring a dialog, you already should have a Move Dialog node that points to the MoveDialog_Dump.xml file.
  4. Otherwise, if this another machine, Click the Extend UI button, locate the MoveDialog_Dump.xml file from the server where it was saved in "Acquiring the Move dialog" in Acquiring a dialog, and click Open. Notice the Move Dialog node is added.
  5. Locate the folder with the InDesign cached data folder. For Windows and Mac OS, respectively, this path is of the following form:
    • Windows:
      C:\Documents and Settings\<username>\Local Settings\Application Data\Adobe\InDesign\Version<version>\<locale>\Caches
    • Mac:
      /Users/<username>/Library/Caches/Adobe InDesign/Version<version>/<locale>
  6. Locate the QA folder within this cached data folder, and inspect the XML file named MyDialogExtensionLibrary.xml; see the preceding figure. If this file refers to user-interface extensions on a file server that is visible from the machine on which you intend to run AAT tests, you should be able to transfer MyDialogExtensionLibrary.xml to the remote machine, as long as you put it into QA folder within the cached data folder on the remote machine.

Frequently asked questions

How can I solve common problems, like loading AAT under release?

The following table lists common issues getting started with the AAT:
Issue Cause Resolution
I don't see the QA menu. I'm running the release build of InDesign. Be sure you have the testing plug-ins loaded. By default, these are not in the profile that loads under release. See Launching the Action Automation Editor and Installation.
I don't see anything under the "Loaded UI Extensions" of the source components. You need to have the qa/aat folder in the correct location and execute GetAllPalettes script. See Installation.
I can't find the Type Text to Story action. This action is obsolete and is hidden in the source components tree. Old action sequences that use it will still run, but new action sequences should use Type Text, from Actions > UI Actions.
How do I create a new paragraph style? I don't see the action I expect under the Styles actions. There is no New Paragraph Style action, as it is relatively elaborate to parameterize. Use a Load Styles action from a document that contains the styles you want.

Where is the QA folder I keep hearing about?

You will encounter two different QA folders, and the context in this document should specify which of the two QA folders is being discussed. One is in the cached data folder, and the other is a peer of the folder in which the application (for example, InDesign) is installed. The default locations of the cached folder are as follows:

  • Windows:
    C:\Documents and Settings\<username>\Local Settings\Application Data\Adobe\InDesign\Version<version>\<locale>\Caches
  • Mac OS:
    /Users/<username>/Library/Caches/Adobe InDesign/Version<version>/<locale>

Can I use AAT with Adobe InDesign Server?

Yes. AAT scripts that use only command actions can be used with InDesign Server.

Can I run AAT Scripts developed for InDesign within Adobe InCopy?

Yes, AAT can be used with InCopy, but if you run a script that uses InDesign-only functionality in InCopy, it is likely to fail or crash. Scripts written for InDesign may not function as intended if the corresponding functionality is not in InCopy.

What do I need to do to get the AAT working under the release build?

Follow the procedure in Installation. You need to be sure the Testing plug-ins are loaded. These plug-ins are loaded by default under the debug build. For the release build, however, you need to take some action to ensure they load; for example, either add a Path in your PluginConfig.txt file or copy these plug-ins to the application's plug-ins folder.

How do I find an action given its name or partial name?

Right-mouse-click (Ctrl-click) on the Source Components panel, choose Search from the context-sensitive menu that appears, and enter the name or partial name of the action. You should be presented with a list of matches. Select one from the dialog that appears, and the Action Automation Editor navigates to the action in the Source Components that matches your choice.

Where do I find the actions when creating menu actions?

Menu Actions are based on the keyboard shortcuts list in InDesign. If you find an expected item missing, check in InDesign and in the Keyboard Shortcut editor to make sure the item appears on the menu where you expect it to be.

What is object loop target?

An object loop finds every object in a specified target and outputs a reference to it for manipulation within the loop. There are only two kinds of targets in which it will look: stories and documents (the table-content finder shows up but is unimplemented). So, we cannot say, "For Each rectangle on Page 1, apply swatch Black," but we can say, "For Each spline in document foo.indd, apply swatch Black."

What is the Create Linked Text Frame action for?

The Create Linked Text Frame action is supposed to be like clicking in the overset-text adornment on one frame (whether or not there is overset text) and dragging a new frame. You need to specify the frame to which you are linking and the page and bounds of the new frame. The After Frame parameter allows you to make the new linked frame appear before the one you are linking to in the story.

Is there a straightforward way to insert a text cursor into any story?

If you specify the length of the range to select as 0, the cursor (with no selected text) is inserted at the specified index.

How do I select one or all items on a page after they are placed?

You just need a reference to the page item you want to select. If you used one of the Place actions under Page Item Actions, they should give you one, and you can use that variable name to specify which page item to select in the Select Page Item action. If you used the Import Load Place Gun action under Import/Export Actions, you must get the reference in another way (for example, using the Get Nth Page Item action under Page Actions). You also can use Select All (and Deselect All, if desired) under Doc Actions.

What is the difference between String Actions and String Information?

String Actions act on a string, and String Information gets information about a string. String Actions has four action types, and String Information has four information types; see the following tables. Of the other parameters in the actions, not every action type uses all of them.

The following table lists parameters used by String Actions:
Action Type Parameters Used
Remove Target String, Output String, Index, and Length.
Append Target String, Input String, Output String.
Set Input String and Output String.
Insert Target String, Input String, Index, and Output String.

The following table lists parameters used by String Information:
Information Type Parameters Used
Get Index of Character Target String, Start Index, Character, and Output Int.
Get Last Index of Character Target String, Character, and Output Int.
Contains Substring Target String, Substring, and Output Int.
Get Num Characters Target String and Output Int.

What are the four values for bounds referred to in a text box?

The four values represent the text frame's left, top, right, and bottom, in that order. For example, 1,1,3,6 creates a frame that starts at (1,1) and ends at (3,6). (Recall that points are described as (x, y); thus, the bounds are (x1, y1, x2, y2).)

Can I apply XML tags to text through AAT?

You can use the Apply XML Tag to Text action found under both Story Actions and XML Actions. It takes in tag, story, and range information. It ignores the current selection.

What is Alert Watcher (found under Action Sequences and Loops)?

Alert Watcher can watch for all alerts (hitting the default button) or a specific alert. For specific alerts, it can check the alert text and hit a specific button (based on the button string). It behaves like any other sequence or loop and can be nested, have multiple children, etc.

Alert Watcher has four parameters:

  • Watch Type - If this is set to Hit Default Button on Any Alert, both the Button to Press and Expected Alert Text parameters are ignored.
  • Button to Press - This is the label of your desired button. This has a combo box containing [Default] for choosing the default button, whatever it may be.
  • Expected Alert Text - This is the translated alert text; that is, what you see on your screen.
  • Error if No Alert - This causes the watcher to report an error if no error matching its criteria is observed.

How can I close the progress bar on the fly?

There are two ways to do this:

  • Show the AAT Progress Bar and Hide AAT Progress Bar actions, under Custom Actions.
  • Use a parameter on the Session File that lets you hide the progress bar for the duration of the session file. If you do a Show action during that session file, the progress bar is shown.

In both cases, if the application preference (from the QA menu) is set to suppress the progress bar, it never appears (even if you call Show). So, you can hide the progress bar for your click actions and bring it back when you are done, or you can have an entire session file that does not show it at all.

Why am I having trouble selecting the graphic in a placed image?

The page item output from place is the frame that contains the graphic you have placed, not the graphic itself. As a workaround, add a menu action that changes to the Direct select tool through the Tools menu, then add a mouse-click action on a point you know is on the image.

How can I specify a key press with two modifiers, like Ctrl+Alt+n to make a new doc?

You need to type the second modifier as it appears in the drop-down for the combo box. You can, but do not need to, use spaces, commas, or other separators for each modifier ("Ctrloption" equals "Ctrl option" equals "Ctrl+option" equals "Ctrl,option").

If you enter a capital letter in the Press Key field, you do not need Shift as a modifier as the AAT sorts that out for you. The same applies for the characters above number keys, like 2 and @ on a US keyboard.

What is the Finder Type Parameter of an object loop?

The Finder Type is the type of container in which you want to search. For example, you would look for inline graphics inside a story or table, but you would look for stories or swatches inside a document.

Finder Loops loop for each object they find and post out a reference to each instance they find. For example, a given script opens a document that contains three stories. Consider a Finders set to the following:

Finder Type Parameter = Document ContentFind Object of Class = TextStory

This will loop three times, posting references to each story it finds. The order in which items are found depends on the container in which you are searching. In documents, it is based on UID; that is, the order in which each content item was created initially. In stories or tables, the order is based on the text index in the story or table cell.

How do I select a specific word in some text?

Suppose you have text like this question's title, and you want to select the word "specific." You could use the Select Text action (from Actions > Story Actions), with Text Start Index set to 18 and Selection Length set to 8.

I am confused about getting an object in AAT; it does not seem to select the object.

Getting does not manipulate an item. It is not selected, deleted, modified or altered. You can think of Get as a pathway to another action or set of actions. When you insert a Get action into a sequence, what you are doing is "specifying" the object you want to manipulate with a later action. For example, suppose you have a test file where you have made three page items, and you want to select the first one. To do this, you need to Get the 0th Page Item, then call Select Page Item.

How do you say, "For every document that is open, do this"?

Suppose for every open file, we want to set the name of the document to something different, then save and close. Here are the steps to do this:

Get Num Docs [output: numDocs]
While(numDocs > 0)
numDocs = numDocs - 1 [Math operation]
Get Nth Doc(numDocs)
[Do the new naming thing
Save Doc [with new Name]
Close Doc

Ensure that you set the parameters for the While loop to be integers.

How can I access commands found in the character palette pop-out menu?

Pop-out menus are available by adding a Menu Action, from Actions > UI Actions. If you edit the Command Parameter of this action, you will find a node called Palette Menus. Within the subtree below that node are the pop-out menu items.

The basic idea is that our Menu Action really is a reflection of the keyboard-shortcut editor's list of entries. Anything for which you can set a keyboard shortcut can be accessed there. Hide/Show toggles are not updated, so if you call Show Layout Grid twice, and it originally was not shown, it ends up not shown again.

How can I automate right-clicking to get to the context menu?

All menus are accessible through the Menu Action item. We do not provide custom/special methods for accessing contextual menus. If you know where an item is in the keyboard shortcut editor, that is where it appears in our menus. Thus, contextual-menu actions and palette-menu actions are done through the Menu Action.

Why are the palette pop-out menus not shown with the palette under UI extensions?

Again, this happens because the AAT locates menu items based on the keyboard-shortcut editor. Under the hood, the palette pop-out menu items are considered menu items, so that is where they show up. Look under the Palette Menus subtree when editing a Menu Action (Actions > UI Actions > Menu Action).

Is there a straightforward way to select a paragraph?

Use the GetNthParagraph action from Actions > Story Actions to get the offset and length you need, then call Select Text, again from Actions > Story Actions.

Why did an assert show up when I tried to write out an AAT file?

If you see the following assertion when writing AAT files, it is because the file you are trying to write to is locked, is in a locked folder, or is in use:

UTF16to8streamBuf::ConvertAndOutputLocal() - unable to write to outputbuf!

Simply unlock it or its parent folder, or save to a new location or with a different filename.

Where does the AAT come up with the descriptions for items in a dialog or palette?

InDesign reports what is in a dialog or palette by including the possible values, the ID of each widget, the bounds, and the default state. The representation of the dialog or palette is said to be acquired or captured, and it can be loaded as a user-interface extension by the AAT. See Working with user-interface extensions.

Where can you change the name and path of the Dialog Description file when acquiring a user interface?

You can change this in the Description File parameter on the Acquire Dialog action. For details, see Acquiring a dialog and the sections following it.

Can you take an acquired dialog to another machine and load it?

Yes. The description is in XML and should have no machine- or platform-specific information. See Transferring a saved user-interface library between machines.

If you have a session file with an acquired user interface, do you need to provide the Dialog Dump to run the file?

The part of the user interface that is used is included with any session file. There is no reason to provide the Dialog Description file just to run the session file; you need the Dialog Description file only if you want to add new actions to it.

Is there any reason to take a Dialog Description XML file to another machine?

Only the part used in a session file is included If you want to create more session files using this user interface, or add actions based on this acquired user interface to an existing action sequence, it makes sense to copy the XML file and extend the user interface with it.

How can I drag and drop a page item?

Use the click and drag action. If you will drag and drop on a different window, you need to convert the location from local to global and manage active windows accordingly.

Extending the Action Automation Tool

This chapter explains how to extend the Action Automation Tool (AAT).

Background

With the default AAT, you can automate many black-box testing tasks by implementing test scripts, as explained in Action Automation Tool for Black-box Testing . The AAT comes with many common actions ready to be plugged into your action script. It also comes with a list of acquired user-interface actions, so a QA engineer can use the user-interface action to "simulate" user actions.

Also, the AAT is very smart in "acquiring" a third-party plug-in's user interface. If you have a panel (or dialog) in your plug-in, it is possible for the AAT to get information about the panel's widgets, as long as the widget class hierarchy for the control includes a known InDesign widget type.

There are times, however, when your QA testing requirements are not covered by the default AAT. Consider the following situations:

  • You want to provide a command-level action that appears in the Actions List of the AAT Editor source tree, enabling a tester to use that action in conjunction with existing actions. For example, if you have an action that acts on a page item, your test sequence would make a new document, create the page item, and pass it on to your new custom action.
  • You have a widget that does not descend from any existing InDesign widgets that the AAT knows about. When the AAT acquires the user interface, it cannot recognize your control, which is ignored. Alternately, you could have a widget that descends from a standard widget, but the AAT cannot extract values.

Fortunately, AAT is designed for flexibility, functionality, and extensibility. By writing a plug-in using the supplied AAT-related APIs, you can extend the default AAT tool so it can adapt to your specific QA requirements.

This chapter explains how to implement AAT extensions, and provides specific implementation tips in Building blocks for the two situations described above.

Setup

Overview

This section describes how to set up the AAT extension environment. To work with the AAT extension, you must have the required assets (libraries, header and source files, and so on) in the correct places.

Shared libraries

You must move the shared libraries described in Shared libraries to an additional location.

Move the shared libraries to SDK folders named as follows:

  • Windows:
    <SDK>/build/win/<debug|debugx64│release|releasex64>
  • Mac OS:
    <SDK>/build/mac/<debugcocoa64|releasecocoa64>/packagefolder/contents/macos

Header and source files

The source files for the AAT extension are in InDesign Tool Kit folders named as follows:

<ITK>/source/components<ITK>/source/precomp<ITK>/source/private

Normally, they are not part of the InDesign SDK. To compile the ITK source files, move them to the SDK folders named as follows:

<SDK>/source/components<SDK>/source/precomp<SDK>/source/private

Project files

AAT's project files are provided in:

<ITK>/build/<win|mac>/prj

Move them to the SDK folder:

<SDK>/build/<win|mac>/prj

Concepts

The core of the AAT system comprises action/verification pairs called AATActions. AATActions allow us to remove the data from the code and store it in external files. These files, which are a series of actions described in XML, are processed by the AAT as tests.

Each AATAction encompasses one kind of action, but it can store its own attributes. Each action displayed under the AAT editor's Source Components is an AATAction. To extend the AAT system as described in Background, you need to create your own AATAction or, in some cases, add to an existing AATAction.

AAT architecture

The following figure shows how the AAT system is represented by boss classes and interfaces.

itk-guide-29.gif

When a user clicks the Execute button in the AAT editor, the AAT queries the ITestSessionList on the kWorkspaceBoss for a list of test sessions to execute. An ITestSessionList contains a list of kCTestSessionBoss(es) that aggregate ITestSession(s). Only the first ITestSession is used during an AAT session; that is, you can have only one top-level session file per session. In the preceding figure, this is shown in the AAT Editor under the Sequencer box as the Session File.

An ITestSession manages a list of sequence lists. A sequence list, as depicted in the following figure, is the Action Sequences item in the Sequencer box, which is represented by service provider bosses that aggregate any descendent of ISequenceList.

itk-guide-30.gif

The purpose of a sequence list is to provide flow control, data sharing, and logic for a series of actions. A sequence list provides an environment in which AATActions run and communicate. A sequence list can contain a list of AATActions, managed by an IAATActionList or another ISequenceList like kCDoWhileSequenceListBoss, which aggregates an implementation of ISequenceList that basically manages other AATActions or other sequence lists. ISequencesList is a list of ISequenceItem(s). ISequenceItem is the parent class for ISequenceList and IAATActionList. This arrangement allows ISequenceList to contain another ISequenceList; therefore, it satisfies the requirement for nested sequences. The following figure shows the main AAT classes in a C++ class hierarchy diagram.

itk-guide-31.gif

AATAction

An AATAction is a C++ instance of VAATAction. CAATAction is a (partial) abstract class that provides most of the default implementations for an AATAction; your AATAction should use CAATAction as the base class and override only the necessary methods. There are a few pure virtual methods and one static method in CAATAction for which you must provide implementations; these are listed in the following table.
Method name Note
CreateAATAction A static method that returns an instance of the AATAction class to the caller.
Execute Test code goes here. For example, if want to exercise your code that adds custom data to a page item, you should put the code in this method. Then, when this AATAction is called to perform the action, it adds the data to the target page item.
GetAATActionID Returns the hex ID declared for this AATAction. For more information about AATAction IDs, see AATAction ID.

Think of an AATAction as a self-contained test unit. You use input data (of a type based on CAATData; see AATAction data) in the Execute method. Usually, the input data is needed to exercise the code you want to test, then you put the result in one or more output variables. The top-most sequence list is responsible for passing data among the AATActions it manages. The following figure shows AAT PageItem Actions > Create Rectangle used in a sequence.

itk-guide-32.gif

By default, the Create Rectangle action expects an input variable (In Variable, as labeled in the action item's editor) named Page, and it puts its test result, a rectangle page item, in an output variable (Out Variable) named PageItem. When a correct action sequence is constructed using the Create Rectangle action, the Create Rectangle action should be preceded by an action that outputs a variable that Create Rectangle expects; in this example, it is a variable called Page, output by the Get Nth Page In Spread action. The sequence list that manages the AATActions (from New Doc to Resize) matches all the input and output request by each AATAction and distributes proper data at run time, based on the variable names set up by each AATAction. AATAction data discusses in more detail how input data can be collected and how the output data is being broadcast.

The following table lists the data members of CAATAction. Most are initialized by CAATAction, but your AATAction implementation might need to change the values in some data members. Building blocks gives examples of when you should override the default values.
Member Type Note
fAbortOnError qaBool16 Default variable indicating whether execution should stop if this action fails. The implementation of your action should check the value of fAbortOnError. If it is set to kTrue or "Yes," you should return kCancel rather than kFailure when your action fails.
fActionDescription qaString AATAction's information string, usually a description of the action's use.
fDefault qaBool16 Deprecated. Do not use.
fInited ProdBool CAATAction initializes fInited to kFalse by default. In some custom AATActions, it needs a specialized user-interface handler. When fInited is set to false, the handler is set up properly.
fItems K2Vector <ISequenceItem*> For internal use.
fName qaString Deprecated. Do not use.
fParamExtensions VirtualDataList* Not used.
fParentContainer VAATAction* For internal use.
fParentSeq ISequenceList* The sequence list that contains the AATAction.
fProduct qaUIDRef Output variable for the AATAction.
fProductCompatibility ProdInt32 Indicates the AATAction's availability in different products. See VAATAction.h for enum values.
fSessProps IAATActionSessionProperties* Provides access to IAATActionSessionProperties. Initialized by CAATAction.
fShowInTree qaBool16 Used by DialogAndPanelActions to prevent "empty" user-interface actions from appearing in the source tree. If this value is set to kFalse, the item is not displayed in the Actions List of the source tree. Defaults to kTrue.
fSubject qaUIDRef Input variable for the AATAction. It could be a document UIDRef, page item UIDRef, and so on, or nothing.
fSupportsExtensions ProdBool Not used.
fType qaString AATAction action name, initialized by the first parameter of CAATAction constructor, typeStr.
fTypeCode qaString AATAction's eight-digit hex ID.
fValidationLevel qaString Deprecated. Do not use.
fVersion ProdInt32 Used for conversion. The value of fVersion is additive, so if CAATAction changes, your descendants also change. You should never hard-code this value; instead, you should increment it in your action's constructor.

AATAction data

Each AATAction action is an atomic unit designed to perform one test step. The question is: How does an action like Insert Pages know to which document to add pages?

Like all programming systems, the AAT architecture allows for the passing of values through variables of the CAATData type. Virtually any parameter in the AAT architecture is simultaneously a static value and a variable. Variables in AAT can be assigned and used to assign specific objects, values, or strings. Unlike traditional algebraic models, variables in the AATAction architecture can receive values or assign (post) values, but never both; that is, some AAT variables receive values and some post. The distinction can be noted in the Item Editor of an AATAction, where a receiving variable is indicated as In Variable, while a posting variable is Out Variable.

If a CAATData variable never receives a new value, it uses the default value set up by the AATAction where the variable belongs. If you examine the test case in the figure shown earlier in AATAction, it contains New Doc, Get Nth Spread, Get Nth Page in Spread, Create Rectangle, and Resize actions. After each action title, there is an entry in parentheses indicating the posting and receiving keys for the action's primary variables. These are the default fSubject and fProduct members of CAATAction.

Whenever a test action with a posting variable executes, every receiver has its value set to the value posted. This means that, if two actions post the same key, they overwrite each other. For example, New Doc and Open Doc both post Doc by default. If an Open Doc action is inserted just below the New Doc in the previously mentioned figure, the rectangle is created in the document that is opened, not the new one.

Variable communication is scoped by the top-most sequence; that is, every test item below the sequence that is added to the session can communicate with every other item in that sequence tree. Sequences at the session level cannot communicate with items in other session-level sequences.

Many of the CAATAction's data members listed in the preceding table are of types prefixed with "qa." For example, fActionDescription is of type qaString, and fSubject is of type qaUIDRef. The qa*** classes are the most fundamental data classes in AAT for an AATAction's input and output. All these qa*** types are defined in the AATDataTypes.h file and are based on CAATData. All the most common types in InDesign have a corresponding qa*** class defined in AATDataTypes.h. An AATAction always should use variables of the qa*** type to collect and store data.

CAATData is a data class that can generate a user interface to be used in the AATAction action's Item Editor (see AATAction action-item editor). Every instance of CAATData must have a user-interface code associated with it. CAATAction creates a user interface in the Item Editor based on the user-interface code. For example, CAATAction has a derived class called BaseDialogAATAction, which has a data member called fCheckEnabled. It is a qaString and is constructed with a kDropDownListCode user-interface code. In BaseDialogAATAction's constructor, three expected enable-state strings (Don't Check, Disabled, and Enabled) are added to the fCheckEnabled using CAATData::AddAcceptableValues. When Expected Enable State is clicked in the Item Editor of a user-interface extension action, a drop-down list that contains the three enable-state strings set up in BaseDialogAATAction's constructor is available for the user to choose. The current selection is translated into a string and stored in the qaString. Then the AATAction's implementation can use the BaseDialogAATAction::fCheckEnabled to find the user's choice.

The following table lists the available user-interface element codes to be used in a CAATData-based class. The default user-interface code for CAATData is kInvalidUICode, which does not display any user interface in the Item Editor.
User-interface code User-interface element shown in item editor
kButtonCode No user interface.
kCheckBoxCode Single-selection, drop-down list with string values of Yes/No.
kCheckBoxTreeViewCode Tree view with check-box tree node. An example is the Snapshots parameter of Validation Actions > Snapshot.
kChooseDirCode Like kChooseFileCode, but the browser window is a "choose folder" dialog.
kChooseFileCode Text-edit box with a Browse button. When clicked, a browser window opens for the user to choose a file.
kComboBoxCode Combo box.
kDropDownListCode Single-selection, drop-down list.
kFileComboCode Deprecated. Do not use.
kIndeterminateCode Provides an interface that allows users to set the data type of this data point to whatever they want. For example, logic may sometimes call for an integer value but other times need a string, bool, or even UID or UIDRef.
kInt32EditBoxCode Text-edit box.
kInvalidUICode None.
kMeasurementCode Edit box for a value in the unit of measurement available from a drop-down list. An example is the Weight parameter of the Set Page Item Stroke Weight action.
kMultiLineTextEditBoxCode Multiline text-edit box.
kPointCode A group of widgets with edit boxes for X and Y (values of a point) plus a drop-down list for the unit of measurement for the point. An example is the Center Point parameter of the Rotate action.
kRadioButtonCode Same as kCheckBoxCode.
kRadioClusterCode Single-selection, drop-down list. The list of strings in the drop-down represents the values of the radio cluster.
kRealEditBoxCode Text-edit box.
kRectCode A group of widgets with edit boxes for X, Y, Width, and Height values of a rectangle, plus a drop-down list for the unit of measurement for the rectangle. An example is the Expected Bounds parameter of the Get Window Dimension action.
kSaveAsDependentFileCode To be deprecated. Use kSaveAsFileCode instead.
kSaveAsFileCode Like kChooseFileCode, but the browser window is the Save As... dialog.
kStaticTextCode Static text.
kTextEditBoxCode Text-edit box.
kTextListBoxCode Single-selection, drop-down list.
kTreeViewCode Scrollable tree view. An example is the Command parameter of UI Action > Menu Action.

Note
AATAction data does not have to show itself in the same way that the actual InDesign user interface draws. For example, a table action that applies strokes to cells could simply have a string list of sides, rather than drawing the user interface in the Tables:Cell Strokes dialog panel.

AATAction action-item editor

Sometimes you want to create tests that do not use default values when executed. Perhaps you want to create a document with a specific page size or place a file at a specific location. To do this, you need to be able to edit individual actions and change their values. When you insert an item into the sequencer, it starts life with its default values. By double-clicking an item in the sequencer or selecting an item and clicking the Edit button, you access the Item Editor dialog. The Item Editor, shown in the following figure, allows you to set values and variables for any parameter of a test action, sequence, or session. By default, the AAT puts up the parameters shown in the following table.

itk-guide-33.gif
Parameter Note
Comments For AATAction users to insert their own comments.
Comment Out For the AATAction user to disable the AATAction in the sequence. When "commented out," an AATAction is not executed.
Action Description For the AATAction author to describe the use of the AATAction. This corresponds to CAATAction::fActionDescription, a qaString variable. You can use the variable to set up your action description.
Validation Level For the AATAction user to specify test-validation level. By default, CAATAction uses a drop-down list to provide three validation levels, using the strings constants (kExtensiveValidationStr, kStandardValidationStr, and kMinimumValidationStr) declared in AATXMLTagDefs.h. The current selected item from the drop-down list is stored in the CAATAction::fValidationLevel, a qaString. Typically, in your CAATAction::Execute method, you use the validation level to provide different levels of testing.
Abort on Error For the AATAction user to decide what to do when the AATAction encounters an exception. Abort on Error causes the test session to terminate if the action fails. For example, if your test began with New Doc, and you set the Abort On Error drop down to "Yes," then if the creation of a new document fails, the test does not try to continue execution. The Abortion Error parameter corresponds to CAATAction::fAbortOnError, a qaBool16. Every CAATAction::Execute should return an ErrorCode. During exception, it is an AATAction's responsibility to return a proper ErrorCode based on the user's choice in the Abort on Error parameter. If the user's choice is True, CAATAction::Execute should returns kCancel; otherwise, kFailure.

Item Editor dialog is generated when the user double-clicks on an action in a sequence list. This is done by asking the sequence item (a AATAction) for its data list. The list contains all member variables for the AATAction. As discussed in AATAction data, each of these variables is an instance of CAATData. The implementations of CAATData can provide a user-interface code for the type of user-interface element needed to collect input from the user.

You can supply your own parameter(s) to collect the necessary data and broadcast your test result to meet your AATAction's needs. This is done by adding a CAATData-based class (qa*** type, discussed in AATAction data) data member to your AATAction class and constructing it with proper user-interface code. For example, your test code may process a command that requires a string as its command data. To collect string data in the AAT, you can add qaString-type member data to your AATAction implementation. Depending on the user-interface code associated with your qaString variable, a corresponding user interface is displayed in the Item Editor.

When you add your own parameter to the AATAction, by default, the parameter is created as a receiving variable (that is, input variable). To make it a posting variable (that is, output variable), you need to call the VAATData::SetIsPosting method and pass kTrue to the function. Typically, you need to assign your output variable with the object you want to output, then call fParentSeq->PostMsg (fParentSeq is a member of CAATAction) to post the output, so the parent sequence can propagate the result to proper receivers for that variables. The CAATAction class declares two UID-based (type qaUIDRef) data members, fSubject and fProduct, that you can use as input/output variables. fSubject (used as input variable by default) and fProduct (used as output variable by default) are initialized by CAATAction. For the corresponding user interface to show up in the AATAction's Item Editor, you need to call the CAATData::SetUITextString and give the editor panel a name.

AATAction factory and registry

The AAT system uses the service-provider paradigm to find all AATActions during run time and make them available in the AAT editor. An AATAction boss typically is a service provider that aggregates IAATActionList, which maintains a list of test implementations that can be queried. The test implementation is a regular C++ instance of the VAATAction class. The AATAction factory (IAATFactory) is an AddIn interface to the global workspace. The core of the factory is a tree data structure that stores the function pointer to each AATAction's static CreateAATAction method. The tree is sorted by hexadecimal character strings, each eight characters long. This provides up to eight bytes of possible AATActions. Every AATAction needs to define a unique ID to be identified inside the factory. For the reserved ID range, see AATAction ID.

To register into the factory, an AATAction must be registered in the Initialize method of at least one IAATActionList interface. All AATActions lists register with their boss ID and impl ID to the factory's registry. When the factory initializes, an instance of each IAATActionList is created and initialized. When the list is initialized, it is passed the optional IAATFactory pointer. The list, in turn, adds the AATAction IDs into the registry, as well as its own list. If two or more IAATActionLists try to add a duplicate pointer to the same leaf of the registry, an assert is thrown only if the leaf pointers do not match; otherwise, this is simply ignored.

Due to the cumbersome nature of repeating the same code to add items to the registry, it was decided that an AATAction must define an ID internally that corresponds to its class name plus "ID." This allows a simple macro that needs only the class name to expand for registration. (The macro is k##a##ID, which expands to the name with the ID).

AATAction ID

Each AATAction is associated with an ID of the AATActionHexID class (see IAATActionList.h). You should define the ID as an eight-character string, but you should treat it as a four-byte hexadecimal number. The AAT system actually expects the string to be in the range 0x00000000 to 0xFFFFFFFF. with DEADFACE and a certain range of IDs reserved for internal use. The ID should be registered in IAATActionList::AddAATActionID, and the VAATAction::GetAATActionID should return the ID.

Note
The letter in the AATAction ID that represents a hexadecimal digit needs to be in uppercase. If you specify a lowercase digit, the AAT will be unable to register your AATAction and will crash the application.

The following example shows a typical way to define an AATAction ID:

#define kANABPINewAATActionID "00C5D001"
// OK!#define kANABPINewAATActionID "00c5d001"
// NOT OK! c and d are lower case.
#define kANABPINewAATActionID "GYMK0001"
// NOT OK!

Assume that a third-party AATAction ID is declared as follows:

#define kMYAATActionID "@@###$%%"

The following table shows how the AATAction ID should be declared.
@@: Product ID ###: Plug-in ID space $: AATAction list IDs %%: AATAction IDs
For InDesign and InCopy, the product ID should be declared as 00. Third-party partners should use the first three characters from their plug-in ID. For example, if your plug-in prefix ID is 0xC5D00, you would use C5D. Use this digit to manage your AATAction List. Since it is really a hex value, you can have up to 16 (0-F) AATAction lists per plug-in ID space. Use these two digits to manage your individual AATAction IDs. You can specify anything from 00-FF, which allows up to 4096 potential actions, since you can specify up to 16 action lists in the proceeding digit.

Key client APIs

The APIs provided to manipulate layout-related objects are summarized in the following tables.

The following table lists key AAT APIs:
API Note
CAATAction Provides a basic implementation for VAATAction. This is the base class you should use for your own AATAction, if you are adding a new AAT action.
CAATData Provides a basic implementation for VAATData.
IAATActionList An AATAction list is a kind of sequence item that holds related AATActions. When added to a sequence list, the AATAction list has one of its member AATAction activated. When Execute is called on the list, the active AATAction is executed.
IAATFactory Serves as the constructor/provider of all AATAction items.
ISequenceList A sequence list is a list of sequence items. The list is itself an item and can be embedded in another sequence list.
ITestSession A session is a list of ISequenceLists that represents a specific test or set of tests. A session is responsible for releasing all its list contents when destroyed.
ITestSessionList A list of sessions. The session list serves as a driver for a test file and is filled in when the file is read to memory.
VAATAction Not an interface. An AATAction is a specific test or action to be performed and verified.
VAATData The "variable" to be used for communication between AAT actions. A VAATData is a virtual class that defines such data.

The following table lists APIs for add-on:
API Note
IAATActionListAddOn CAATActionListAddOn.
IAATUIHandler When you want to add a custom widget to the AAT, you need to tell the AAT how to handle the widget through IAATUIHandler. This includes information such as widget ID and widget bound.
BaseDialogAATAction The base class of an AATAction for dialog add-ons, like a custom widget.
BaseUIAction Descendent of CAATAction. This is the base class for BaseDialogAATAction.

Building blocks

This section describes two common scenarios, listed in Background, to illustrate the necessary steps to extend the AAT system.

Add an AATAction to the AAT editor source tree

In the first scenario, you want to add a new custom action to the action list of the main AAT editor window, so users can insert your action into their test sequences. You need to add an AATAction to the AAT system. Here is the critical information that you need to know to be able to add an AATAction to the AAT source tree.

Implement an AATAction

You always should implement your AATAction based on CAATAction.

AATAction constructor guideline

In your AATAction constructor, you always should call the CAATAction base class's constructor, which is declared as follows:

CAATAction(const ProdString &typeStr, const ProdString &subject, const ProdString &product);
  • typeStr is the AATAction's name as shown in the sequence source list.
  • subject is the default input variable (CAATAction::fSubject) name.
  • product is the default output variable(CAATAction::fProduct) name.

An AATAction's data variables should have their values set in the AATAction's constructor. If you specified a variable name in the CAATAction constructor, a proper construction of the qa*** class should set up the user interface with a proper user-interface string associated with it. For fSubject and fProduct, call the variable's SetUITextString to set up the user-interface string for the parameter. The user-interface string shows up in the left side of the Item Editor as a parameter that the user can click and then edit. Most of the CAATAction's data members are initialized by CAATAction, but you may want to set up at least fProductCompatibility, to make sure your AATAction is available only in proper products. Use the eProductCompatibility defined in the VAATAction.h to declare your AATAction compatibility.

To add an additional input variable, declare a new data member in one of the qa*** types (see AATDataTypes.h) in your CAATAction-derived class. The qa*** uses two macros to expand the preprocessor macro-generated classes (QA_##a##_Data) into a fully qualified class, MAKE_NONORDINAL_DATA_HEADER for a UID-based data class and MAKE_QADATA_HEADER for an ordinal data class. Both preprocessor macro-generated classes are based on CAATData, which has four constructors:

CAATData(void);
CAATData(const ProdString &tag);
CAATData(const ProdString &tag, const ProdString &uiString, ProdInt32 code);
CAATData(const ProdString &tag, const ProdString &uiString, const ProdString &varName,ProdInt32 code);

Use the first three constructors either if the AATAction data will not be visible in the user interface or if the AATAction data's user-interface information will be set in the body of the constructor.

The fourth constructor is used most often. Its last parameter has a default value of kInvalidUICode and may be left blank for AATAction data that does not need a UI Element (value set by variable only). When you construct a qa*** type variable, you typically use its constructor of the following form, so it expands to be based on the fourth CAATData constructor. (For details, see AATDataTypes.h.)

QA_##a##_Data(a var, ProdString tag, \ ProdString varName, \ ProdString uiString = "", \ ProdInt32 code = kInvalidUICode); \
  • var is the default value for the variable in the Value field of the item editor.
  • tag is the XML tag used by AAT to identify the variable. You can define your own tag or use the ones defined in AATXMLTagDefs.h. No two data items may have the same tag. If two items with the same tag are added to VirtualDataList, the list will ASSERT that the items have duplicate strings and include the string of the offending XML tag.
  • varName is the default variable name used for In/Out Variable in the item editor.
  • uiString is the variable 's label as it appeared in the Parameters list of the item editor.
  • code is the corresponding UI code to be used for this variable. See the table in AATAction data.

In the body of the constructor, set user-interface information for standard variables, if necessary. Also, if your AATAction data uses user-interface code such as drop-down lists or tree elements, acceptable values should be populated. The value of fProductCompatibility should be set as well. For more information, see AATAction data.

Declare an ID for the AATAction

The AATAction ID should be returned in the VAATAction::GetAATActionID. Also, it is used in the AATAction list. See "Add an AATAction list" in Add an AATAction to the AAT editor source tree.

Provide VAATAction implementation

See the table in AATAction for the minimal set of functions you should implement. If you added your own variable to the AATAction, you also should implement VAATAction::CollectReadWriteParams.

Note
Your call to CollectReadWriteParams must begin by calling CAATAction::CollectReadWriteParams(theList); otherwise, all dependent code cannot access the data points in CAATAction.

Add an AATAction list

You need to provide your own IAATActionList implementation that will manage the AATActions you are adding. Your IAATActionList class always should be based on CAATActionList. There are only three things you need to do in your IAATActionList implementation:

  1. Construct the class, assigning the data member fName a string. All AATActions added to this IAATActionList appear under this name in the Source Components > Actions list of the AAT editor. The IAATActionList is sorted alphabetically, but the individual actions are listed in the order in which they are added to the parent list.
  1. In IAATActionList::Initialize, call IAATActionList::AddAATActionID once for each AATAction you are adding. Pass in the AATAction ID you declared in "Declare an ID for the AATAction".
  2. In IAATActionList::RegisterAATActions, use the ADD_AAT_ACTION_TO_LIST_REG macro to add the AATAction class to the list.

After the AATAction list is implemented, add it to a service-provider boss, to be defined in your plug-in's .fr file. Use kAATFactoryServiceProviderImpl for the IK2ServiceProvider implementation, which adds the IAATActionList implementation in the boss to the AAT system.

Add to an existing AATAction

If you have a custom widget on a panel or dialog that does not descend from any InDesign widget boss, then, when the containing panel/dialog is acquired by AAT through the Acquisition Actions, the widget is not acquired, because the AAT does not know how to handle the widget. As a result, the AAT user cannot script that widget. The following definition is an example of such a widget:

Class{
kAATExtGoButtonBoss,
kInvalidClass,
{
// widget implementation.
}
},
type AATExtGoButton (kViewRsrcType) : RollOverIconButtonWidget (ClassID = kAATExtGoButtonBoss) {
CControlViewObservers;
};

The kAATExtGoButtonBoss class descends from kInvalidClass, instead of an Indesign widget boss like kRollOverIconButtonBoss. Even though the widget may reuse some of the provided implementations through the InDesign SDK, the AAT still cannot recognize the widget. If you have such a widget in your user interface, you need to write an AAT plug-in to tell the AAT how to handle your custom widget.

Note
In this scenario, we assume that the custom widget is to be used from a panel or dialog action, which usually is available through AAT user-interface extensions. For more information on how to acquire a panel or dialog of your own, see Action Automation Tool for Black-box Testing

The following steps are necessary to make a custom widget available in the AAT system.

Provide widget handler

Each custom widget that you are about to acquire through AAT should have a widget handler that tells the AAT how to handle the widget. The widget handler should be a service provider (a boss that aggregates IK2ServiceProvider) that identifies itself using the kAcquireService service ID. Normally, the service-provider boss simply uses the default implementation kAcquireServiceProviderImpl to identify itself.

Another interface that the widget-handler service provider needs to aggregate is IAATUIHandler; for the widget handler to work, you must provide your own implementation. The following table describes what should be implemented for each method in IAATUIHandler.
Method Description
AcquireView Returns a VAcquisitionElement, which is the pure virtual base class of AcquisitionElement. You should instantiate an AcquisitionElement and populate the object with the widget you are handling, whose IControlView is passed in as a parameter. The basic data you need to set up for the AcquisitionElement includes:

- widget type (using AcquisitionElement::SetWidgetType with the supported token of this handler)
- widget ID (using AcquisitionElement::SetWidgetID with the widget ID you can acquire from the IControlView::GetWidgetID)
- widget bounding box (using AcquisitionElement::SetBounds with the bounds acquired from the IControlView::GetBBox),
- enable state of the widget (using SetEnabled with the data returned from IControlView::IsEnabled).

If the widget has data associated with it, like a combo box's drop-down menu item, you also should add each item's value to the AcquisitionElement through its AddValueEntry() method.
GetSupportedClassIDs Connects the custom widget boss with the widget handler. Normally, you write one widget handler per widget, so you would append the custom widget's boss ID to the ID list passed in to this method.
GetSupportedTokens A string token that identifies the widget handler. This token is used in the AAT's data file (XML) for the acquired containing panel/dialog, to identify the widget. It provides a hook from the data file into the C++ handler code. Normally, you write one handler per custom widget, so you can just append the only token into the token list passed in to this method.
GetSupportedWidgetIDs Further connects the custom widget to the widget handler through the custom widget's ID. You can safely provide just an empty implementation, since the GetSupportedClassIDs method already provides the hook.
GetValueAsString Mainly represents a control's value as a string, for users to understand what they are doing.
GetWidgetUIString A string that identifies the type of the widget.
IsSupportedToken The AAT acquires a panel/dialog and saves its data in an XML data file. All acquired widgets are identified by a tag called WIDGET_CODE. The value of this tag is a token defined in the IAATUIHandler::GetSupportedTokens() by the widget handler. When the AAT looks for widget-handler code to handle the widget, it passes the token to all widget handlers, to see which one handles it. So, in this method, you should check that the token passed in matches the supported token, and pass the result back to the calling function.
SetName String that identifies the control.
SetValue Mainly sets the value to the current state of a widget, such as a drop-down list or edit box where you want to set the control to a certain value. You can use the control's ITextControlData (if any) to mirror the widget's value and AcquisitionElement.

Add an AATAction based on naseDialogAATAction for the widget

Because the widget normally is acquired through an action like Acquire Palette or Acquire Dialog, the AAT user will try to use the widget through the acquired panel/dialog. The widget handler that you write connects the widget with the AAT system and tells the AAT about the attributes of the widget, like widget bounds, so the AAT knows how to interpret the custom widget and present it along with the acquired panel/dialog.

There is still one piece missing when users try to use the widget in their script: you need to provide a test function for the widget, when it is being added to a script. The test function should be provided through an AATAction. Consider a panel with a custom widget that you provided with a widget handler. When a user adds that widget action into his AAT script from Loaded UI Extension, typically the user wants to perform the user-interface function provided by that widget. For example, suppose we acquire and extend the ITK's AATExtensionTest panel so the panel is available in the AAT. When we add the Go button from the AAT source into the Script's Sequencer list, we are trying to simulate a click on the Go button panel. What happens after the click is not the concern of that widget. The C++ code of the widget's controller performs the proper action, as you expect. Basically, from the AAT script, you are trying to provide an automatic way of simulating a click on the button. That is the main purpose of an AATAction for the custom widget.

So, the next step in adding a custom widget to a panel/dialog is writing an AATAction for the widget.

What is BaseDialogAATAction?

A widget's AATAction always should derive from the BaseDialogAATAction class, which is descended from CAATAction. BaseDialogAATAction keeps tracks of the widget's geometry and hierarchy, so when a widget is used in the AAT script, it has enough information to perform proper user-interface interaction when necessary. The table in AATAction lists the functions you should override for a typical CAATAction derivation, and it should be applied to your own custom widget. Another method you should override is GetInstanceName(), which returns a name string that represents the action.

Note
Loaded UI Extension is where all the acquired panels are located after they are extended into AAT using the Extend UI function

The following table lists some of the important member variables you may need to use in your BaseDialogAATAction-derived AATAction. If the description of the member indicates it needs to be set up, you should do so early in the construction of the AATAction.
Member Description
fDlg The parent window (IWindow) that contains the widget.
fMyView The IControlView of the widget this AATAction represents.
fSetGet Represents how the widget's AATAction is being used. The widget's item editor has a Set/Get field, to allow the user to specify how he wants to use the widget. The Get state means the user wants to get the value out of the widget; the value can be used to make further script logic. The Set state means the user wants to set the value of the widget, as if the user interacts with the widget through the InDesign user interface.
fShowInTree Always should be set to kFalse; otherwise, a bogus Dialogs and Panels Actions is appended to the Action source tree. The real widget action should appear only under the acquired panel's tree under Loaded UI Extensions.
fSupportedTokens The AATAction's supported token should be the same token as is set up in the AATAction's associated widget handler.
fWidgetValue Use the widget code from the table in AATAction data to assign a user-interface representation to be used in the widget's value field in the item editor. For a button type of widget, you probably do not need a user interface, since when you use the widget, there usually is no further user choice; that is, when you use the widget, it should mean that you want a click the button. In this case, simply assign a kInvalidUICode to fWidgetValue using its SetUIElement() method.

If you do not want the variable to show up at all in the editor, calling SetEditable(kFalse) would make the variable invisible.

For other types of widget, such as a check box, you can assign kCheckBoxCode to fWidgetValue. When kCheckBoxCode is used, AAT uses a drop-down list with a yes/no value in the item editor, for the AAT user to choose the value of the check box. Do not be confused by the name of the data member fWidgetValue. In the internal code for the Icon or Button action, the fWidgetValue data member is used to determine the number of clicks the user wants to perform (Single, Double, Triple, etc).

BaseDialogAATAction::Execute()

You always must override the Execute() method of the BaseDialogAATAction base class. Think about what the user wants to do in terms of user-interface interaction. When users interact with a button in InDesign, they want to click the button. With a check box, the user wants to select/deselect the box. With a drop-down menu, the user wants to select an item. In your Execute() override, try to "simulate" what the user wants to do with the widget. This is different from Execute() in Add an AATAction to the AAT editor source tree, where a new action represents the execution of a feature; for example, creating a document.

If you write code to perform the action that is supposed to happen after the user interacts with the user interface, this widget's AATAction loses the real purpose of providing this widget AATAction as a user-interface extension. Also, it may not accurately test the code written for that widget boss.

So, how do you simulate a user action? AAT provides a utility class, IUIDriverUtils, that you can use to perform user actions like mouse down, key press, and select a check box. When IUIDriverUtils is called on to perform user actions, you will see the widget being interacted with as if a phantom mouse is being directed by your code. For example, a IUIDriverUtils::MouseDown on the center of a button causes the cursor to be moved to the center point of the button, then the button to be highlighted as if clicked by a mouse. The real user-interface event is to the widget's controller code, as if a real mouse clicked it.

Another important aspect of Execute() in a BaseDialogAATAction-derived class is the need to distinguish among the different ways that a user intends to use the widget. The state is stored in the fSetGet member variable. In the Get state, the user wants to get the value of the widget. Typically, you should call the CAATAction's member fParentSeq's PostMsg() method to post any variable value. The standard member variable to use is fGetProduct, although you could use a member of your own definition. In the Set state, the user wants to change the value of the widget; in such a state, you should perform the user-action simulation. If you changed the widget value in Execute(), you always should call the fParentSeq->PostMsg() (as explained in AATAction data) to broadcast the change to interested parties.

Add the widget AATAction to existing panel/dialog action

Instead of providing your own IAATActionList implementation, as explained in "Add an AATAction list" (in Add an AATAction to the AAT editor source tree), you need to provide a CAATActionListAddOn-based class implementation. In this class, you declare that you are adding to an existing dialog/panel action by calling CAATActionListAddOn::AddAATActionID and passing kDialogAndPanelActionsStrKey for the first parameter, idStr.

Note
You could use AddOns to add actions to any AATAction List. You could add your own commands to Doc Actions, using the same process defined here. The key is to use the name of the AATAction list.

Sample code

The AATWidgetHandler sample shows how to make a custom widget available in the AAT. It illustrates all the steps covered in this section. The custom widgets it covers includes a button, check box, and combo box. To test the code for AATWidgetHandler, it uses a plug-in called AATExtensionTest, available in the ITK, where its Go button, check box, and combo box are customized.

Frequently asked questions

Why doesn't the qa*** data member in my AATAction implementation show up in the Item Editor?

Make sure you called the qa*** class's SetUITextString() method, to give it a user-interface string.

What is the kPostingKey and kReceivingKey declared in the AATXMLTagDefs.h?

kPostingKey is defined as #, and kReceivingKey is defined as $. Internally, they are used in some places for input/output variables; however, they are in the process of being deleted. Do not use # or $ as the prefix for your AAT variables, as they will cause unknown problems.

Diagnostic Logger

This chapter describes the installation, use, and design of the Diagnostic Logger tool in the Adobe® InDesign® CC Tool Kit (ITK). The Diagnostic Logger collects information about application events occurring during execution of InDesign and related applications in both debug and release (production) environments.

Terminology

The following terms are related to the Diagnostic Logger:

  • Consumer - You operate in the consumer role when programming InDesign to automate its function through the existing API, rather than implementing extension patterns.
  • Context event - An application event with a definite beginning and end, like a command.
  • Dispatcher - A component that dispatches information about application events to the Diagnostic Logger.
  • Event source - An abstraction capable of emitting information about an application event, such as a command being processed, a user-interface event taking place, or an action being performed.
  • Extender - To extend the application, your plug-in must implement one or more extension patterns.
  • Extension pattern - A pattern you can implement to extend the application. Extension patterns are implemented in the extender role, which takes advantage of the design patterns the API supports, to enable your code to be called in its correct context by the application core.
  • Information collector - An abstraction that is a sink for application events created by an event source.
  • Logger - Shorthand for the Diagnostic Logger tool.
  • Message - An application event that does not qualify as a context event (that is, does not have a distinct beginning and end). For example, a user-interface event like mouse-up is a message, rather than a context event.
  • Provided interface - Defines the services that a component (like a boss class or plug-in) delivers to other components.
  • Required interface - Defines the services that a component (like a boss class or plug-in) needs, to function properly in its execution context.
  • Roll over, rotate - Refers to a log file. When a log file reaches a specified maximum size, subsequent output is logged to a new log file. When the maximum number of log files is reached, the first log file is overwritten for subsequent output.

Installation

The main steps to install and start working with the Diagnostic Logger are as follows:

  1. Decide whether you will use the supplied binaries of the DiagnosticLog plug-in in the ITK or compile the DiagnosticLog plug-in. See Configuration.
  2. If you choose to use the supplied binaries, make sure you move them from the <ITK>/build/win/<debug|debugx64|release|releasex64>/future version/ (on Windows®) folder or the <ITK>/build/mac/<debugcocoa64|releasecocoa64>/packagefolder/contents/macos/future version/ (on Mac OS®) folder to a location where they will be loaded by the application. Alternately, use the PluginConfig.txt mechanism. See Executing the Logger.
  3. If you choose to compile the DiagnosticLog plug-in, compile both the debug and release builds. Move the plug-ins to a location where they will be loaded, or use the PluginConfig.txt mechanism.
  4. Run the InDesign debug build, and exit the application. You need to run the debug build of the DiagnosticLog plug-in at least once to create its configuration files, so you can control its behavior next time.
  5. Read Generated files to find out where the generated files are located. You can then configure the Diagnostic Logger by modifying these generated files, to produce the output you are interested in; see Configuration XML file. Next time you run the application, the configuration options specified in these files determines what output is created.
  6. If you run the release build, make sure its configuration file refers to the symbol database-file named DiagnosticLogIDDB.xml, created by the debug application to enable symbol resolution in the release build. See Symbol database file.

After you successfully install and use the logger, you can install and build the logger-related samples provided in the ITK. See Installing and building the samples.

Configuration

Executing the Logger

The Diagnostic Logger can be started without any configuration; it generates a configuration file based on its default settings after an initial run. The recommended way to configure the Diagnostic Logger is to run the debug version of the application once, to generate a default configuration file and a symbol database file that is used for the release build to resolve symbols.

By default, the use of a database to resolve symbols is disabled, even in the debug build; although it generates the database, you still need to turn on the "load" attribute in the <iddatabase> element to resolve symbols.

By default, the DiagnosticLog plug-in supplied with the ITK compiles into a "future version" folder. Also, prebuilt DiagnosticLog plug-ins for the debug and release versions are included in the following folders:

  • Windows:
    <ITK>/build/win/<debug|debugx64│release|releasex64>/future version/
  • Mac OS:
    <ITK>/build/mac/<debugcocoa64|releasecocoa64>/packagefolder/contents/macos/future version/

To have InDesign load the DiagnosticLog plug-in:

  1. Point the application to a given folder, by modifying PluginConfig.txt on Windows® or dragging an alias to the folder into the plug-ins folder on Mac OS®.
  2. Copy the plug-in somewhere else, like the plug-ins/ folder inside the InDesign application folder, so it will be loaded.

Generated files

If InDesign is running without saved data (an empty Version 9.0 folder), by default the DiagnosticLog plug-in produces no logging information; however, both debug and release versions of InDesign produce configuration files after being run without saved data. The main difference is that the symbol database that is filed (DiagnosticLogIDDB.xml) is generated only when running under the debug build.

By default, the output files are generated to a folder called DiagLog in the user's InDesign Caches folder, in the following location:

  • Windows XP:
    C:\Documents and Settings\<username>\Local Settings\Application Data\Adobe\InDesign\Version<version>\<locale>\Caches\DiagLog
  • Windows Vista:
    C:\Users\<username>\AppData\Local\Adobe\InDesign\Version<version>\<locale>\Caches\DiagLog
  • Mac OS:
    /Users/<username>/Library/Caches/Adobe InDesign/Version<version>/<locale>/DiagLog

The debug and release builds of the DiagnosticLog plug-in generate slightly different sets of files in the output folder, as shown in the following tables.

Files generated in the debug version:
File Description
DiagnosticLogConfiguration_debug.xml Debug configuration file. Controls the operation of the logger in the debug version of the application.
DiagnosticLogIDDB.xml Symbol database that can be used in both debug and release versions to resolve numeric identifiers to symbolic ones.
DiagnosticLogOutput_<n>.txt Contains the output of the text-based logger.
DiagnosticLogOutput_<n>.xml Contains the output of the XML-based logger.
DiagnosticLogRTEnvironment.xml Run-time environment, plug-ins loaded when the Diagnostic Logger runs, platform, product, feature set, etc.

Files generated in the release version:
File Description
DiagnosticLogConfiguration_release.xml Release configuration file. Controls the operation of the logger in the release version of the application.
DiagnosticLogOutput_<n>.txt Contains the output of the text-based logger, with numeric identifiers resolved if requested and the symbol database file is present.
DiagnosticLogOutput_<n>.xml Contains output of the XML-based logger, with numeric identifiers resolved if requested and the symbol database file is present.
DiagnosticLogRTEnvironment.xml Run-time environment, plug-ins loaded when the Diagnostic Logger runs, platform, product, feature set, etc.

Configuration XML file

To control logging options, you edit an XML-based configuration file in the output folder, which is one of the following:

  • DiagnosticLogConfiguration_Debug.xml, for the debug build.
  • DiagnosticLogConfiguration_Release.xml, for the release build.

By default, both text and text loggers are disabled, so you need to turn on at least one of them to see meaningful debug information. Also, the DiagnosticLog plug-in allows you to specify the number and maximum size of output files for logging. For details on what happens when the logged output exceeds a specified size, see Log-file rotation.

The XML vocabulary used by the configuration files is described in Configuration XML: element catalog and subsequent sections. Also see the IDiagLogConfigManager interface, which lets you programmatically vary many (but not all) of the properties specified in the XML.

Symbol database file

The symbol database file is an XML-based file named DiagnosticLogIDDB.xml, which contains look-up tables for the different ID spaces that the application supports, like kActionIDSpace, kClassIDSpace, and kServiceIDSpace. You can configure the DiagnosticLog plug-in to load this symbol database file via its configuration XML file. Alternately, you can force the DiagnosticLog plug-in to load the database file programmatically through the IDiagLogConfigManager API.

Log-file rotation

When a log file logs enough information to reach its file-size limit, the DiagnosticLog creates a new file if you chose to do so, until the maximum number of files specified is created; at this point, the DiagnosticLog plug-in starts overwriting the first file it created, and it continues to recycle the created files for output. DiagnosticLog also allows the user to specify the output buffer size, and it writes out the log in the size specified by this parameter. For more information, see the <loggers> element in the configuration file, described in <loggers>.

Diagnostic Logger architecture

Information sources and event generators

There are several information sources or event generators within the application that can generate output that can be picked up by the diagnostic logger. These event generators were added specifically for the purpose of logging. The application events can be partitioned into contexts, where there is a definite beginning and end to the application event (like processing a command), and a message, which may report in any context; for example, a memory purge. For details, see id_contexts_and_messages "Contexts and messages".

Dispatcher

The dispatcher component is part of the application core, delivered as a component in the Public shared library. The dispatcher is a mediator between the event generators and the logger component. It expects the logger plug-in to have the kDiagnosticLogPluginID identifier, and it requires the following interfaces to be implemented by that plug-in:

  • IDiagnosticLog, added into kSessionBoss.
  • IDiagLogConfigManager, on its own boss class.
  • IDiagLoggerManager, which can be on a different boss class than the IDiagLogConfigManager implementation.

If there is a plug-in with the kDiagnosticLogPluginID identifier, information emitted by the event sources can be logged by that plug-in.

<logger>

The logger component is a (sample) plug-in that shows how to interact with the dispatcher component. You can use the logger plug-in (DiagnosticLog) as a client or consumer of services implemented by the DiagnosticLog plug-in. For instance, you can use it to log information about application events; control how much information you want it to log through its API; and control whether you want text output, XML output, or both.

The logger plug-in also is an extensible component, since it enables third parties to add their own diagnostic-logger service (IDiagnosticLogger), which can be turned on and off through its XML-based configuration file. You also can implement a log-modifier service (IDiagLogModifier) to take control over what is logged at each logger service. For more details on these service types, see Extension patterns.

In principle, you can replace the logger component entirely with your own, as long as you reuse the plug-in identifier expected by the dispatcher component and implement the required interfaces for the dispatcher, described in Dispatcher. However, you should do this only after studying the implementation of the existing logger component, to be sure you understand the contract with the dispatcher component.

Contexts and messages

The application events to which the logger responds can be broken into contexts and messages, in the terminology of the diagnostic logger. A context has a definite beginning and end; a message is sent out without regard for context.

Contexts are associated with extension patterns. The following contexts are supported by the logger:

  • Action
  • Command
  • Event
  • Idle task
  • Observer
  • Responder

A message supplies information about an application event and is not as directly connected as contexts with particular extension patterns. A message sent to the logger is about one of the following:

  • Command sequence
  • Tracking
  • Transaction level
  • Memory purge
  • Global error state
  • Text-damage mark

Crash handling

Information buffered in memory is written to disk on an idle task (approximately every five seconds) and on a protective shut-down or other abnormal program termination, like a program crash.

Logging output level

The following table shows what information is logged at different levels across the categories supported by the Diagnostic Logger. The minimum amount of information is shown for a logging level kDiagLogMinimum. Higher levels for some categories contribute additional information; for instance, for commands. There are categories where increasing the logging level does not yield extra information; for instance, actions. From this table, you can see that, for most categories, increasing the logging level does not yield any extra information for the particular category; commands and command sequences are the exceptions.
CategoryLogging level
kDiagLogMinimumkDiagLogMedium kDiagLogMaximum
ActionActionID, action nameSame as the previous levelSame as the previous level
CommandCommand ClassIDCommand type, name, undoabilityCreator ID, target database, input item list, output item list
Command sequenceSequence name, sequence address (as ID)Ending status (end, abort), undoabilitySame as the previous level
Global error stateCurrent error code, new error code, error string, context stringSame as the previous levelSame as the previous level
Idle taskIdle task ClassID, task name (see lIdleTask)Same as the previous levelSame as the previous level
Memory purgePurge levelSame as the previous levelSame as the previous level
ObserverSubject ClassID, message ID, protocol (interface) PMIID, observer ImplementationIDSame as the previous levelSame as the previous level
ResponderServiceID, signal manager ClassID, responder ImplementationIDSame as the previous levelSame as the previous level
Text-damage markTextIndex of wax line, span of wax lineSame as the previous levelSame as the previous level
TrackingState (begin, end, abort), tracker ClassIDSame as the previous levelSame as the previous level
TransactionDatabase ClassID, state (begin or end transaction), database filenameSame as the previous levelSame as the previous level

Client API of the Logger

Required interfaces of the Logger component

  • IDiagLogConfigManager - Interface to control the behavior of the logger, as an alternative to manipulating the XML configuration file before the application loads. This is a required interface because it is used by the Dispatcher.
  • IDiagLoggerManager - Interface to manage individual logger services (IDiagnosticLogger). This is a required interface because it is used by the Dispatcher.
  • IDiagnosticLog - Interface the Dispatcher uses to acquire references to the logger manager (IDiagLoggerManager) and configuration manager (IDiagLogConfigManager).

Provided interfaces of the Logger component

  • IDiagLogIDDatabase - Interface to translate a numeric ID value into a human-readable string. For instance, as a client, you might use FindID() to look up an identifier in a given ID space. For this to work in the release version, the symbol database file must have been loaded through either the configuration file or DiagnosticLog API (IDiagLogConfigManager).
  • IDiagLoggingAPI - Superclass for some of the service interfaces of DiagnosticLog plug-in.
  • IDiagnosticLogger - Service (provided) interface for a logger service, See Logger service.
  • IDiagLogModifier - Service (provided) interface for a log-modifier service. See Log-modifier service.

Extension patterns

The DiagnosticLog plug-in is extensible. It offers several extension patterns; implement these to extend its basic capabilities. For instance, you can add a new type of logger service, for logging to a console or over the network. Alternately, you can modify (supplement or filter out) information sent to each logger service, by implementing a log-modifier service.

Logger service

This extension service enables a new type of logger service to be added to the basic text and XML logger services supported by the DiagnosticLog plug-in. A logger service is implemented as a service provider, with the following characteristics:

  • It implements the IDiagnosticLogger service interface (provided interface).
  • It implements the IK2ServiceProvider required interface, with a ServiceID of kDiagnosticLoggerService. You can reuse an existing implementation (kDiagLoggerServiceProviderImpl), delivered by the DiagnosticLog plug-in.

If you implement a logger service, your logger is sent information about application events by the dispatcher component. See Dispatcher.

Log-modifier service

This extension pattern lets you modify what is logged by each logger service. See Logger service. A log-modifier service is implemented as a service provider, with the following characteristics:

  • It implements the IDiagLogModifier service interface (provided interface).
  • It implements the IK2ServiceProvider required interface, with a ServiceID of kDiagLogModifierService. You can reuse an existing implementation (kDiagLogModifierServiceProviderImpl), delivered by the DiagnosticLog plug-in.

The log manager (IDiagLogManager) within the DiagnosticLog plug-in delegates to a log modifier on the following messages from the dispatcher: EnterContext(), ExitContext(), and LogMessage().

Configuration XML: element catalog

As mentioned in Configuration XML file, the behavior of the DiagnosticLog plug-in and associated logger services can be controlled by an XML file, which is named DiagnosticLogConfiguration_<*>.xml, where <*> is "debug" or "release." In this section, we consider the XML vocabulary of this configuration file and provide constraints on its structure and the values that its elements and attributes can take.

The following example shows an abridged form of the default XML configuration file for the DiagnosticLog plug-in. Ellipses (...) indicates content that was omitted to keep the example short. One point to note about the default configuration is that neither the text-logger service nor the XML-based logger service is turned on. This means that, unless you vary the default configuration to enable at least one of these loggers, you will not see any output to either log file.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DiagnosticLogConfiguration>
<filelocation outputpath="/Users/auser/Library/Caches/Adobe InDesign/Version 9.0/en_US/DiagLog"></filelocation>
<!-- Configuration (this) file location :"/Users/auser/Library/Caches/Adobe InDesign/Version 9.0/en_US/DiagLog" -->
<iddatabase name="DiagnosticLogIDDB.xml" load="true"></iddatabase>
<logging enabled="true" level="kDiagLogMaximum" timeinfo="false" timethreshold="10">
<category name="kDiagLogAction" enabled="true">
<includelist></includelist>
<excludelist></excludelist>
</category>
... Other category elemendebugcocoa64|releasecocoa64ts omitted
<category name="kDiagLogEvents" enabled="true">
<eventtype name="kActivate" enabled="true"></eventtype>
... Other category elements omitted
</category>
... Other category elements omitted
</logging>
<loggers>
<!-- file size and buffer size are in kilobytes (KB) -->
<logger name="text" enabled="false" outputfilesize="2048"outputfilecount="3" outputfilebuffersize="256"></logger>
<logger name="xml" enabled="false" outputfilesize="2048"outputfilecount="3" outputfilebuffersize="256"></logger>
</loggers>
</DiagnosticLogConfiguration>

In the sections from <category> to <logging>, we consider the organization of the XML configuration file in more detail and indicate how to configure this file to make the logger behave the way you want. One representation of the logical structure of this file can be seen in the following figure, which shows parent-child relationships between elements. This shows a view of the content model for the XML configuration file. The root element (<DiagnosticLogConfiguration>) is at the left of the diagram, and the tree extends to the right. For instance, you can see that the <category> element can have <includelist> and <excludelist> children. This same information also is represented in the sections describing each element (that is, the sections from <category>), along with additional information specifying constraints.

This is the content model of the configuration XML:

itk-guide-34.gif

<category>

The <category> element represents information that controls how activity is logged for different categories of event source. The event category lists all event types as elements. It also has two attributes: name and enabled. Users can enable or disable an event category using a flag. The enabled flag at the event category level determines whether individual <eventtype> enabled flags are used.

Content model

category ::= (includelist?, excludelist?, eventtype*)

Parent

  • <logging>

Children

  • <excludelist>
  • <includelist>
  • <eventtype>

Attributes
Attribute name Type Summary Required?
name CDATA Name of the category. Required
enabled CDATA Whether activity in this category is logged. Required

name

This is a string value that corresponds to the name of the category for which you want to log activity. The complete list of values is in the IDiagLogConfigManager.h header file. This list consists of the following contexts and messages (see Contexts and messages):

kDiagLogAction, kDiagLogCommand, kDiagLogIdleTask, kDiagLogObserver, kDiagLogResponder, kDiagLogEvents, kDiagLogTracking, kDiagLogTransaction, kDiagLogMemoryPurge, kDiagLogGlobalErrorState, kDiagLogTextDamageMark

enabled

This is a string value representing a Boolean, where "true" means logging is enabled for the category, and "false" means it is disabled. Enabling or disabling logging at the category level determines whether enabled attributes at the level of individual <eventtype> elements are used.

Example

The following example shows a populated <category> element without any dependents.

<category name="kDiagLogMemoryPurge" enabled="true"></category>

<DiagnosticLogConfiguration>

The <DiagnosticLogConfiguration> element is the root element of the configuration file.

Children

  • <filelocation>
  • <iddatabase>
  • <logging>
  • <loggers>

<eventtype>

The <eventtype> element controls whether particular user-interface events within the category kDiagLogEvents are logged.

The enabled flag of the parent <category> flag determines whether a specification at the <eventtype> level is used; for instance, if the parent category is not enabled, the enabling of individual <eventtype> elements has no effect.

Content model

eventtype ::= (EMPTY)

Parent

  • <category>

Attributes
Attribute name Type Summary Required?
name CDATA Event name. Required
enabled CDATA Whether event activity should be logged. Required

name

This is a string value that identifies the event type to enable or disable. The values this attribute can have are in the IEvent::IEventType enumeration. You should leave out the IEvent: qualifier; hence, the values are as follows:

kActivate, kDeactivate, ...

enabled

This is a string value representing a Boolean, where "true" means logging is enabled for the category, and "false" means it is disabled. Enabling or disabling events on the <category> parent determines whether this attribute on the <eventtype> is used.

Example

The following example shows a populated <eventtype> element in the scope of a <category> element. Other elements are indicated by ellipses (...).

<category name="kDiagLogEvents" enabled="true">
...
<eventtype name="kLButtonDn" enabled="true"></eventtype>
...
</category>

<excludelist>

The <excludelist> element can be used to explicitly filter out some application events, so information about them is not logged. It applies only to the categories listed in the following table. To identify application events to filter out, <id> elements are used.

If the <category> has both <excludelist> and <includelist> elements, and there is contention between them, the <includelist> always takes precedence.

Specifying values for each category:
Category Value ID Space
kDiagLogAction ActionID, in hex. kActionIDSpace
kDiagLogCommand Command boss ClassID, in hex. kClassIDSpace
kDiagLogIdleTask Idle-task boss ClassID, in hex. kClassIDSpace
kDiagLogObserver Observer ImplementationID, in hex. kImplementationIDSpace
kDiagLogResponder Responder ImplementationID, in hex. kImplementationIDSpace
kDiagLogTracking Tracker boss ClassID, in hex. kClassIDSpace

Content model

excludelist ::= (id)*

Parent

  • <category>

Child

  • <id>

Attribute
Attribute name Type Summary Required?
value CDATA Hex value specifying an identifier in a particular ID space. Required

value

This must correspond to the hex value of the identifier; for instance, 0x1e0 would be used for kMouseWatcherIdleTaskBoss. The ID space is associated with the parent <category> element. See the <excludelist> table, which gives more information about the ID spaces in which numeric identifiers are defined, for the categories that support this element.

Example

The following example logs all the idle tasks, except one associated with ClassID of kMouseWatcherIdleTaskBoss (ClassID = 0x1e0).

<category name="kDiagLogIdleTask" enabled="true">
<includelist></includelist>
<excludelist>
<id value="0x1e0"></id>
</excludelist>
</category>

<filelocation>

The <filelocation> element specifies the directory where the generated files should be placed, and stores the path to itself when last loaded by the application.

Content model

filelocation ::= (EMPTY)

Parent

  • <DiagnosticLogConfiguration>

Attributes
Attribute name Type Summary Required?
outputpath CDATA Folder where output files should go. Required

outputpath

This specifies the directory where the output file should go, as a platform-dependent path. Output files include all log file(s), the symbol-database file, and the run-time environment file.

Example

The following example shows a populated <filelocation> element for Mac OS.

<filelocation outputpath="/Users/auser/Library/Caches/Adobe InDesign/Version 9.0/en_US/DiagLog" >

<id>

The <id> element specifies a category ID to be included or excluded in the include/exclude list.

Content model

id ::= (EMPTY)

Parent

  • <excludelist> or <includelist>

Attributes
Attribute name Type Summary Required?
value CDATA Hex-valued identifier. Required

value

This is a numeric identifier, as a hex value; for instance, 0x1e0 or 0x1E0. This should specify something that makes sense within the ID space. The value could be an ImplementationID, ActionID, or ClassID. For more details, see the table in <excludelist>.

Example

<id value="0x1e0"></id>

<iddatabase>

The <iddatabase> element represents information about the logger's symbol database file, such as whether it should be loaded. A symbol database file always is created when the Diagnostic Logger is running in the debug environment. It enables you to map numeric identifiers (for example, 0xE05, in ID space 0x101) into symbolic identifiers (for example, kOpenDocCmdBoss).

If you do not load the symbol database file, you will not get numeric identifiers mapped to symbolic identifiers, even in the debug build. To ensure that numeric identifiers are resolved to symbolic ones in both debug and release builds, change the this element's load attribute from its default of "false" to "true."

Parent

  • <DiagnosticLogConfiguration>

Content model

iddatabase ::= (EMPTY)

Attributes
Attribute name Type Summary Required?
name CDATA Name of associated symbol database file. Required
load CDATA Whether the symbol database should be loaded. Required

name

This is the name of the symbol database file. This is expected to be in the folder specified by the outputpath attribute of the <filelocation> element.

load

This is a string value representing a Boolean, where "true" means the symbol database is to be loaded (used to map numeric values to symbolic identifiers), and "false" (the default) means no numeric-to-symbolic mapping occurs.

Example

The following example shows a populated <iddatabase> element, with a default name for the symbol database file, and the load attribute set to ensure that numeric identifiers also are logged as symbolic identifiers.

<iddatabase name="DiagnosticLogIDDB.xml" load="true"></iddatabase>

<includelist>

The <includelist> element specifies particular application events that should be logged within a given category. If it is populated, other events in this category are ignored. It is a way of asserting the primacy of some application events within a particular category: if you add one or more application events to the <includelist>, all other events in the category are ignored. See the table in <excludelist> for the list of categories for which an <includelist>child is supported; it is the same as the list categories that support an <excludelist> element.

If the <category> has both <excludelist> and <includelist> elements, and there is contention between them, the <includelist> always takes precedence.

Content model

includelist ::= (id)*

Parent

  • <category>

Child

  • <id>

Attributes
Attribute name Type Summary Required?
value CDATA Hex value specifying an identifier in a particular ID space. Required

value

This must correspond to the hex value of the identifier; for instance, 0xE05 would be used for kOpenDocCmdBoss. The ID space is associated with the parent <category> element. See the table in <excludelist> for more information about the ID spaces in which numeric identifiers are defined, for the categories that support this element.

Example

The following example shows a populated <includelist> element, The example specifies that only information about a small subset of commands should be captured by logger services.

<category name="kDiagLogCommand" enabled="true">
\<includelist\>
<id value="0xe05"></id>
<id value="0xe06"></id>
<id value="0xe07"></id>
<id value="0xe08"></id>
</includelist>
...
</category>

<logger>

The <logger> element controls a particular logger service (IDiagnosticLogger). Each logger service must serialize its state to XML, and each logger should choose a different name attribute, enabling it to serialize its state to XML and deserialize its state from XML. See Logger service.

Content model

logger ::= (EMPTY)

Parent

  • <category>

Attributes
Attribute name Type Summary Required?
name NMTOKEN Required
Enabled CDATA Required
OutputFileSize CDATA Optional, specified only for the existing text and logger service.
OutputFileCount CDATA Optional, specified only for the existing text and logger service.
OutputFileBufferSize CDATA Optional, specified only for the existing text and logger service.

name

This is associated with a particular logger service. The logger services delivered by the DiagnosticLog component itself are named "text" and "xml." If you implement your own logger service, it would have a corresponding <logger> element with a unique name to identify it.

Enabled

This is a string value, where "true" means this logger is turned on, and "false" means it is off. The default is "false" for text and XML logger services.

OutputFileSize

This specifies the maximum allowable log-file size before rollover, in kilobytes. Clients can use this attribute, in conjunction with OutputFileCount, to control the maximum amount of data the logger can write, in terms of file size and number. This attribute is supported by the text and XML logger services. When the amount of data already logged exceeds OutputFileSize, the log is rolled over or rotated to another log file.

Suppose the text-based logger starts emitting output to DiagnosticLogOutput_1.txt. When it emits an amount equal to OutputFileSize, it rolls over to start writing to DiagnosticLogOutput_2.txt. When the number of log files created reaches OutputFileCount, the logger returns to DiagnosticLogOutput_1.txt. The log files are cyclic (a bounded set); there cannot be more log files than OutputFileCount, and the logger wraps around to start logging at the start of the log-file set when required.

OutputFileCount

This is the maximum number of files the log rollover uses. This attribute is supported by the text and XML logger services. It specifies the number of output files to be used by log services, as explained above.

OutputFileBufferSize

This is the memory cache size, in kilobytes.This attribute is supported by the text and XML logger services. It controls the size of the memory cache that the DiagnosticLog plug-in uses to buffer output; the larger the buffer, the less often it needs to be flushed to disk.

Example

<logger name="text" Enabled="true" OutputFileSize="2048" OutputFileCount="3" OutputFileBufferSize="256"></logger>

<loggers>

The <loggers> element specifies a collection of <logger> elements with which the DiagnosticLog component works. Some of these can be added by third parties, and the text and XML loggers are those provided by default by the DiagnosticLog component. By default, neither text nor XML logger services is enabled, so if you do not change the Enabled flag, you see no output from either service.

Parent

  • <DiagnosticLogConfiguration>

Child

  • <logger>

Content model

loggers ::= (logger)+

Example

<loggers>
<logger name="text" Enabled="true" OutputFileSize="2048" OutputFileCount="3" OutputFileBufferSize="256"></logger>
...
</loggers>

<logging>

The <logging> element determines whether the DiagnosticLog component should create log files and specifies other parameters to control it, such as the logging level. It also contains a list of <category> elements, each controlling how application events of different types are logged.

Parent

  • <DiagnosticLogConfiguration>

Child

  • <category>

Content model

logging ::= (category)+

Attributes
Attribute name Type Summary Required?
enabled CDATA Whether logging is turned on. Required
level CDATA Detail level of logging. Required
timeinfo CDATA Whether to log with timestamps. Required
timethreshold CDATA Closest spacing of timestamps (msec). Required

enabled

This is a string value representing a Boolean. It overrides all other enabling flags in its dependents, such as <category> elements. If this flag is "false," no logging output takes place.

level

This is the detail level. It controls the detail about what is logged for different categories; see the table in Logging output level. The attribute is a string that should be one of the following values:

kDiagLogMinimumkDiagLogMediumkDiagLogMaximum

timeinfo

This indicates whether timestamp information is logged.This information is logged as distinct application events, rather than being associated with specific events like an action or command being executed. The smaller the timethreshold attribute is set, the closer these entries are spaced in the log file.

timethreshold

This specifies how closely spaced the timestamp messages that get logged will be in the log file. This attribute specifies the lower time limit that is recorded, in msec. For example, in the current implementation, we use std::clock for timing information; since the resolution of clock is about 10 msec, do not set a value below 10.

Example

The following example shows a populated <logging> element with logging turned on, at an intermediate detail level, and with time information turned on.

<logging enabled="true" level="kDiagLogMedium" timeinfo="true" timethreshold="10">
...
</logging>

Troubleshooting

The following table provides some troubleshooting tips.
Issue Cause Resolution or workaround
I turned on the <eventtype> element, but the DiagnosticLog does not log anything. There is hierarchical control over what information is logged. The parent element <category> determines whether the settings of its dependents are used. You need to also turn on the kDiagLogEvents <category> element, to have individual events logged. See <category>.
When I run InDesign with DiagnosticLog, it does not log anything. There are at least three elements in the configuration file whose "Enabled" attributes need to be turned on before any log is output: <logging>, <category>, and <logger>. None of the <loggers> is turned on by default. The only category turned on by default is the Action. So, first enable a <loggers>, then turn on the category in which you are interested.
When I run InDesign with DiagnosticLog for the first time, in the default output location, I cannot find the DiagnosticLogConfiguration_debug.xml configuration file. The configuration file is not written until InDesign quits. Quit InDesign, then check the default output location again. You also can force the file to be written out programmatically, by calling IDiagLogConfigManager::Save().
When I choose XML as my logger output option, sometimes when I try to open the XML, my XML viewer complains it cannot validate the XML and cannot open the file. In some cases (for example, when an idle task yields to another thread), the context enter/exit are mismatched. This results in an XML-based log failing to be validated. Try using the non-XML-based logger. You may not see perfect context matching, but all the context and events are still logged chronologically. Alternately, if you know the context that is causing the mismatching, try to exclude it from the logging.

Logging-related solutions

Selectively logging information about individual commands or actions

Suppose you want to log information about only a one-application event or a small set of application events, and you know the ActionID or ClassID (of command) or other identifiers for these events.

Solution

One way to do this is to define an <includelist> element (see <includelist>) in the configuration XML file (see Configuration XML file). This lets you define one or more application events within a given category that are of interest to you. Only information about those events is logged, if you define anything in your <includelist>.

The <includelist> lets you specify the list of application events in which you are interested. For instance, you might want to log information about only the kSetRedlineTrackingCmdBoss command, associated with turning tracked changes on and off. You would set up the <includelist> for the <category> element kDiagLogCommand; see the IDiagLogConfigManager::DiagLogCategory enumeration for other types. This <includelist> would have an <id> element with the correct identifier; for instance, 0xa415 for kSetRedlineTrackingCmdBoss.

You should find that the log produced by the DiagnosticLog plug-in contains information about only the application event you specified, within the specified category.

If you are interested in all application events in a particular category, you do not need to define an <includelist> element in the configuration XML, or it can be included in the configuration XML but with no dependents.

See also

  • <includelist>

Sample code

  • LoggerPreferences

API

  • IDiagLogConfigManager::AddCategoryList()

Mapping numeric values to symbolic identifiers in logger output

Suppose you are writing code intended for a release build, you have a numeric value for something like a ClassID (for example, 0xa40a, or 41994 in decimal), and you want to turn it into a symbolic form (for example, kDeletedTextBoss).

Solution

The symbol database file (see Symbol database file), produced under the InDesign debug build by the DiagnosticLog component, contains key-value pairs mapping numeric values to symbolic identifiers within the ID spaces used in the InDesign API. Follow these steps:

  1. Run the InDesign debug build with DiagnosticLog loaded, so the symbol database file, DiagnosticLogIDDB.xml, is generated.
  2. Configure the release build of the DiagnosticLog plug-in to load the symbol database file. See <iddatabase>.

See also

Sample code

  • LoggerPreferences

API

  • IDiagLogIDDatabase::FindID()

Sample code for Diagnostic Logger

Installing and building the samples

There are plug-in samples provided in the ITK that implement the patterns described in Extension patterns and/or use the Diagnostic Logger client API. To build these samples, you need to first copy their projects and source files from the ITK into an existing InDesign SDK; the following table shows where the files/folders should be moved. After the projects and source files are moved, they can be built as normal SDK samples.
Files/Folders to copy from ITKCopy to CC SDK folder
<ITK>/build/mac|win/prj/ConsoleLogger.itk.(xcodeproj|vcxproj)<SDK>/build/<mac|win>/prj/
<ITK>/build/mac|win/prj/DetailSwitchLogger.itk.(xcodeproj|vcxproj)<SDK>/build/<mac|win>/prj/
<ITK>/build/mac|win/prj/LoggerPreferences.itk.(xcodeproj|vcxproj)<SDK>/build/<mac|win>/prj/
<ITK>/source/itksamples/<SDK>/source/
<ITK>/source/private/<SDK>/source/

LoggerPreferences

LoggerPreferences provides a user interface to manipulate a subset of the settings that control the Diagnostic Logger. The main purpose of the plug-in is to demonstrate how to use the IDiagLogConfigManager interface to change settings for the Diagnostic Logger at run time.

The normal pattern of using Diagnostic Logger, described in Configuration, requires you to first run InDesign and exit the application to generate an XML configuration file (if no previous configuration exists), turn on at least one logging service through this XML configuration file, then restart InDesign before logging output is produced. In other words, you must exit the application before you can change any settings that control the Diagnostic Logger component. By default, the XML configuration file is read only on start-up by the Diagnostic Logger, so changes to that XML file would affect only the next run of InDesign.

The LoggerPreferences plug-in allows you to configure the Diagnostic Logger dynamically; for instance, to turn on a particular logger and determine which categories of output to log, and potentially see logging information being output immediately when a logger is enabled. The LoggerPreferences user interface allows you to manipulate a subset of the configuration settings for Diagnostic Logger. For more complete control over its settings, you can modify the plug-in to provide a more comprehensive user interface.

LoggerPreferences has an option to enable the console log (Console Logger sample logger service), which is not one of the default logger services provided by the DiagnosticLog plug-in. Make sure the ConsoleLogger plug-in (see ConsoleLogger) is loaded when LoggerPreferences is executed.

Using the LoggerPreferences sample

To start working with the LoggerPreferences sample:

  1. Compile the ConsoleLogger and LoggerPreferences projects for the build of the application you are targeting.
  2. Ensure that the ConsoleLogger and LoggerPreferences plug-ins will be loaded; for instance, put them in the InDesign plug-ins folder, then launch InDesign.
  3. To invoke the LoggerPreferences plug-in after it is loaded, choose Plug-ins > SDK > LoggerPreferences > Configure Log Preferences:
    itk-guide-35.gif
    A dialog displays to let you manipulate the settings that control the Diagnostic Logger component:
    itk-guide-36.gif

The first group of widgets on the LoggerPreferences user interface, "Logging master switch," corresponds to the enabled attribute of the <logging> element in the configuration XML file (see <logging>). This master switch must be turned on before any log can be output.

The second group of widgets, "Which log to turn on?", determines whether a given logging service is enabled and other global properties of the logging behavior. This corresponds to the Enabled attribute of the <logger> element (see <logger>). The "Resolve symbols with database" check box corresponds to the load attribute of the <iddatabase> element (see <iddatabase>). The "Logging detail level" drop-down list corresponds to the level attribute of the <logging> element (see <logging>).

To see some logging output, you must turn on at least one logger service (Text, XML, or Console).

The last group of widgets corresponds to the <category> element; see <category>. Three of the more frequently used categories were chosen for demonstration purposes. For command and action categories, there are "exclude" and "include" text-entry boxes, corresponding to the <excludelist> and <includelist> elements in the configuration file. You should enter the command/action IDs in hex format. Multiple IDs are supported; use commas to separate IDs. For the Event category, click on the event you want to log; for multiple events, hold down the command/control key while clicking.

Note
The LoggerPreferences cannot clear out the command/action's exclude and include lists between sessions; it adds on to whatever is in the configuration file. This is due to a limitation on the IDiagLogConfigManager, which lacks the API that returns the current exclude/include list IDs.

After the LoggerPreferences dialog is dismissed by clicking the OK button, the options are written out to the configuration XML file, and some logging information may be output, if at least one logger service is enabled. For instance, if ConsoleLogger is loaded, and a console window is open as described in ConsoleLogger and enabled in the LoggerPreferences user interface, you should see logging output in the console window.

ConsoleLogger

The ConsoleLogger sample plug-in implements a console-based logging service. On Mac OS, its output is written to Console.app (which must be running before InDesign). On Windows, the ConsoleLogger writes output to a command-prompt window launched by the plug-in itself.

ConsoleLogger implements an extension pattern described in Logger service (also see IDiagnosticLogger).

Using ConsoleLogger

To turn on the ConsoleLogger for real-time logging feedback:

  1. Make sure you already compiled the LoggerPreferences sample for the build of the application you are targeting; see LoggerPreferences.
  2. On Mac OS, launch Console.app, located in the Application/Utilities folder. On Windows, a command-prompt window is launched automatically by the ConsoleLogger plug-in itself.
  3. Ensure that the ConsoleLogger and LoggerPreferences plug-ins will be loaded; for instance, put them in the plug-ins folder within the InDesign installation folder, then launch InDesign.
  4. Bring up LoggerPreferences, as described in LoggerPreferences. Make sure the logging master switch is turned on, the Console log check box is turned on, and at least one category is turned on. For initial testing purposes, turn on at least the action category.
  5. Click OK, to dismiss the LoggerPreferences dialog. Move the console window to somewhere you can observe while running InDesign, then execute a menu action; for example, create a new document. Observe the output in the console; you should see some logging output related to the operations being performed.
  6. To see logging information related to Diagnostic Logger settings without the subset manipulated through the LoggerPreferences user interface, exit InDesign, modify the configuration XML file (see Configuration XML file), then relaunch InDesign.

DetailSwitchLogger

The DetailSwitchLogger sample plug-in provided in the ITK implements an extension pattern described in Log-modifier service (also see IDiagLogModifier). In this sample, two extensibility scenarios for the Diagnostic Logger are illustrated:

  1. Event-driven configuration - Changing the Diagnostic Logger configuration when a particular application event occurs.
  2. Customizing logging output - Modifying the information that is passed through to all logger services.

Event-driven configuration

To demonstrate this feature, DetailSwitchLogger is hard-coded to wait for the menu item Layout > Pages > Add Page (action ID kAddNewPageActionID) to be executed. After this menu-item action is executed, the DetailSwitchLogger sample plug-in (if loaded) changes the logging detail to maximum.

To see the event-driven configuration in operation, follow these steps:

  1. Make sure the DetailSwitchLogger plug-in is in a position to be loaded by the application; for instance, copy the plug-ins into the InDesign plug-ins folder.
  2. Make sure the logging level is minimal, using LoggerPreferences or the XML configuration file. See LoggerPreferences and <logging>, in particular the level attribute. Also turn on the action-category logging.
  3. Create a new document.
  4. Choose Layout > Pages > Add Page
  5. Now, if you examine the log output for any enabled logger service, you should see the following output nested somewhere in the log:
    Action actionid="0x5f1a(kAddNewPageActionID)"
    actionname="Add New Page"
    DswLgrDiagLogModifier:= "===== log detail level is about to change to maximum ====="
  6. Execute a few more actions or command. You can verify that the log level now is more detailed, because it was changed to maximum by DetailSwitchLogger.

Customized Logger output

To demonstrate this feature, DetailSwitchLogger is hard-coded to wait for a particular command (kCreateMultiColumnItemCmdBoss) to be processed. After the command is processed, the DetailSwitchLogger sample plug-in (if loaded) gathers extra information from data interfaces on the command boss object, which propagates to the logger services.

To see customized logger output:

  1. Make sure the DetailSwitchLogger plug-in is in a position to be loaded by the application; for instance, copy the plug-ins into the InDesign plug-ins folder. Make sure command category logging is turned on.
  2. Create a new document.
  3. Using the text tool, create a new text frame.
  4. Examine the output produced by any enabled logger service. You should see something like the following:
    Command classid="0x264(kCreateMultiColumnItemCmdBoss)"
    cmdtype="normal"
    targetdb=""
    creatorid="612"
    Command Status:="FALSE"
    Frame orientation is ="horizontal"

Note that the information on the frame orientation is not part of the default log output, but because the DiagnosticLog supplies a reference to the command (ICommand) being processed to the log modifier, we can customize the log output with this additional information from the context of the log-modifier (IDiagLogModifier) implementation. If you want to log extra information through Diagnostic Logger about arbitrary commands being processed, you can implement a log modifier and follow the pattern illustrated in this scenario.

Symbol Resolver

This chapter discusses how to use the Adobe ® InDesign® symbol files provided in the InDesign Tool Kit (ITK). It describes the process of resolving symbols for development purposes, and it discusses a tool (CrashResolver) provided with the ITK to help resolve symbols from crash dumps on Mac OS®.

Introduction

Third-parties developers often have two major debugging concerns when developing and deploying InDesign-based applications:

  • When the debugger breaks at a breakpoint in source file, the stack crawl usually is meaningless, because it contains only an address.
  • When an application crashes at the customer's site, it is very hard to get a snapshot of exactly what caused the crash.

By providing most InDesign source symbols, we address these concerns and make the development process easier. The call stack provides information on how the program reached the breakpoint or crash; sometimes it can provide hints on performance issues.

We discuss how to debug with the InDesign symbols for two scenarios:

  • The scenario in Real-time, interactive debugging is useful when your plug-in is under development and you have the plug-in project file as the starting point for a debugging session.
  • The scenario in Postmortem debugging is relevant when your plug-ins are deployed at a customer site and you are trying to debug after a crash on a customer's machine.

The Mac OS version of InDesign has a crash-reporting feature; it generates a crash report similar to Apple's CrashReporter, but instead of being routed to Apple, the Adobe crash report can be sent to Adobe. You also can use the Adobe or Apple crash report to gain some insight into the condition of InDesign at the time of crash.

Terminology

The following terms are related to the resolution of Adobe InDesign symbols:

  • Adobe CrashReporter - A crash-reporting tool similar to the standard crash reporter included with Mac OS X and Windows. It provides back-trace and register information from the time of the crash.
  • Adobe CrashResolver - A tool provided with ITK that converts the call stack and register dumps in output from the Apple CrashReporter or Adobe CrashReporter into a useful symbolic form. See Adobe CrashResolver.
  • Call stack - The execution history of a program, making it easier and faster to determine the root and triggering causes of particular issues. This also is referred to as backtrace or stack crawl.
  • Core dump file - A file that contains information that can be used to analyze the crash. The core dump represents a snapshot of the program memory at the time of its crash. This also is referred to as a crash dump.
  • Debugging symbol file - Holds information needed to locate and relocate a program's symbolic definitions and references.

Installation

The symbols in the ITK are packaged in a compressed archive; decompress the archive into a folder in your hard drive. Move the symbols folder to a place where you can find it easily later, and make sure the debug and release symbols are labeled correctly. The symbols are distributed in a folder structure described in this section.

Windows symbols

On Windows, the InDesign build process extracts the symbol (.pdb) files into one debug|debugx64|release|releasex64 folder in the ITK. For example, the binary for the required plug-in AppFramework is in the required folder inside the application install folder with this path:

<InDesign>/Required/

You will find its symbol file, AppFramework.pdb, in the symbols folder in the ITK at the following location:

<ITK>/symbols/win/<debug|debugx64|release|releasex64>/Required/

By default, the Visual Studio debugger searches for a symbol file in the same folder as the executable; however, that is not an absolute requirement, as you can always tell the debugger where to find a symbol file later. Similarly, when you load a symbol file and the debugger cannot find its corresponding executable, it asks you to locate the matching binary. It is not necessary, therefore, to move the symbols to be in the same folder as their binaries.

Mac OS symbols

On Mac OS, all symbol files (.dSYM) are extracted into one debug|release folder in the ITK, which does not maintain the same application install folder structure. Xcode requires the symbol file to be located with the binary, so the debugger can find the symbol in a debugging session. Since the symbols are distributed in a flat folder, you will need to move them to be in the same folder as their binaries. For example, the binary for the required plug-in AppFramework is in the applicaInDesign CC 2014l folder (for example, "Adobe InDesign CC") at this path:

<Adobe InDesign <version>.app>/Contents/MacOS/Required/AppFramework.InDesignPlugin

You should move the symbol for AppFramework.InDesignPlugin (AppFramework.InDesignPlugin.dSYM) to the following path:

<Adobe InDesign <version>.app>/Contents/MacOS/Required/AppFramework.InDesignPlugin.dSYM

If you have many symbol files to open, consider creating a script that copies your frequently used symbol files to the same folder as their binaries. If you use Adobe CrashResolver to resolve symbols from Adobe CrashReporter output, you do not have to organize the symbols as described above, see Adobe CrashResolver. To install CrashResolver, see Setup and preferences.

Symbol files

When applications or libraries are linked during build, the linker that creates the application and the libraries' executable files also can create several additional files, known as symbol files. Typically, you can configure the linker through project settings in the IDE to generate a symbol file. On Windows, Visual Studio generates symbol files with an extension of .pdb (program debug database). On Mac OS, Xcode can generate symbol files in different formats, InDesign is built with the DWARF symbol format, and the symbol file has an extension of .dSYM.

Symbol files store information about function names and the addresses of their entry points, local/global variables, and the memory address or registers that hold the values of the variables. Both real-time, interactive debugging and postmortem debugging require symbols. You must obtain the symbols corresponding to the application that you want to debug and load these symbols into the debugger, so the debugger can resolve the symbols into something more readable by people.

In addition to the external symbol file, Mac OS programs and libraries built with Xcode can be compiled with some debugging symbols embedded. There is an option in the linker preferences to include local symbols in the binary. With embedded symbols, Xcode debuggers can be used to display the stack crawl during execution of the program directly, without the dSYM file. The debugger can give you not only memory addresses but also the names of routines and variables. The inclusion of debugging symbols, however, enlarges a program or library significantly; hence, during the final release build of the program, most applications, including InDesign, are stripped of symbols.

It is necessary to distinguish between the external symbol files and the embedded symbol. When we refer to the symbol file in this chapter, we mean the external symbol files included with the ITK, namely .pdb and .dSYM files.

Windows release symbol files

For space-saving optimization purpose on Windows, we set EnableCOMDATFolding= "2" for most of our projects in release project properties.

In Visual Studio, this behavior is called COMDAT folding, also known as "code folding." Within a single linkage (a single plug-in or library), when multiple functions share the same object code, only one copy of the code actually appears in the final linked output.

This is useful to reduce code size when using common C++ accessor paradigms that cannot be inlined because they are virtual; for example, simple accessors that return a value in a class may share the same object code.

This also is very useful to fight template bloat. The downside of this behavior is that debugging is more difficult: only one symbol name survives, which is confusing when you expect another one. This results in some stack frames in release appearing to be incorrect. It is not a problem in the symbol files. You can get around this by using the debug symbol files.

Real-time, interactive debugging

In this section, we discuss how to use symbols while your plug-in is under development. We discuss how to debug InDesign and your plug-in with the symbols, so you can see the call stack and variables (availability varies) when a breakpoint is encountered in the required IDEs; i.e., Visual Studio on Windows and Xcode on Mac OS.

Windows: Visual Studio debugger

Start a debug session from a plug-in vcxproj file

By default, the SDK projects were built with the option to generate a .pdb file, so if your vcxproj file is based on an SDK project, the pdb file is generated when you build your project. If the plug-in binary is in the same directory as the .pdb file, Visual Studio finds and loads the symbol file. To load the InDesign symbols supplied with the ITK:

  1. Open your plug-in's project in Visual Studio. Build the plug-in, and make sure InDesign will load it.
  2. In Solution Explorer, right-click the project name (for example, not the solution) and select Properties. In Configuration Properties > Debugging, set Command to be the path to InDesign.exe.
  3. Visual Studio will tell you that InDesign.exe does not contain debugging information. Click OK to continue debugging.
  4. If you did not previously set a breakpoint in your plug-in's source file, set a breakpoint at the place where you want to see the stack crawl, and try to execute InDesign to the breakpoint. If the breakpoint is encountered, Visual Studio stops at the line of code where the breakpoint is set. If the call-stack window is not displayed, choose Debug > Windows > Call Stack from Visual Studio to bring up the call stack.
  5. Some entries on the call stack are grayed out; this is because the symbol file for that function is not loaded. Because you have the InDesign symbols in your hard drive, you can tell Visual Studio where to load the symbol file by right-clicking the grayed-out entry, selecting Load Symbols... from the contextual menu, and navigating to the folder that contains the symbol file for the plug-in. When you click the Open button in the Find Symbols dialog, Visual Studio will try to find the symbol in that folder. For example, if the grayed-out entry is WidgetBin.dll!4d7e6be1, try to open the folder that contains WidgetBin.pdb. After the symbol file is loaded, the grayed-out entry resolves to a function name. Repeat this step as needed to load all the symbols you want to use.
  6. When you double-click an entry in the call stack, Visual Studio probably will ask you to locate the source file. If the source file is in the SDK, you can point to the file so you can see the source for the corresponding symbol; otherwise, Visual Studio displays assembly code if you wish.

Another way to reload an individual symbol file is to specify its path in the solution properties. Before starting the debugging session:

  1. Choose Tools > Options... from the menu bar in Visual Studio.
  2. Choose Debugging > Symbols, to bring up a list of paths to search for symbol files.
  3. If your symbol file's path is not in the list, click the New Line icon to select a path for Visual Studio to search for symbol files. Visual Studio does not search recursively from the folder you specify, so if you have nested folders to search, you need to explicitly specify them in the list.
  4. After the search path is specified, you can start your debugging session as usual. When the program stops at a breakpoint or crashes, you will see a resolved call stack with the help of symbol files.

Start a debug session by attaching to a running InDesign process

With Visual Studio, you also can debug a program without a project file. To do this, attach your debugging session to a running process:

  1. Launch InDesign.
  2. If Visual Studio is not running, launch it.
  3. Choose Tools > Attach To Process... This brings up a list of processes currently running. Select InDesign.exe, and click the Attach... button.
  4. Visual Studio creates a temporary solution for you. Choose Tools > Options, and choose Debugging > Symbols to specify symbols location.
  5. Click the Debug Symbol Files tab, and specify the symbol paths as described earlier.
  6. After the symbol files' paths are specified, you can set a breakpoint by invoking the Debug > New Breakpoint... dialog. The caveat is that you need to know the location where you want to set the breakpoint. If you use this method to start a debug session, very likely you do not have your plug-in project to start the session, so the location of the breakpoint might take some guesswork.
  7. After the breakpoint and symbol search paths are specified, you can try to execute the running InDesign to the point where you think you will hit the breakpoint. If the breakpoint is encountered, execution is stopped and the debugger is brought up. You can examine the stack crawl, as long as the symbols are loaded.

Instead of attaching the process from Visual Studio, you could start the debug session from the Windows Task Manager. Right-click InDesign.exe from the Processes tab, and select Debug from the contextual menu. This brings up a dialog listing possible debuggers that you can use to debug. Select Visual Studio, then follow the steps described earlier.

Mac OS: Xcode debugger

Start a debug session from a plug-in project .xcodeproj file

  1. We assume that you are building your plug-in directly into InDesign's plug-in folder. Your plug-in is built as a framework package. In Xcode's Debug Information Format setting, you can choose the DWARF or DWARF with dSYM option. Also, you should have moved the symbols to be with their binaries.
  2. Open your plug-in project in Xcode and select Product > Scheme > Edit Scheme...
  3. If not already select, select the Info tab.
  4. Using the Executable drop-down menu, select Other... and browse to select InDesign's app file. To close the dialog, click the OK button.
  5. To start debugging, select Product > Run.
  6. After the application launches, switch to the Xcode. Set a breakpoint in the source file where you want to debug. Then return to InDesign, and try to run InDesign to the point where the breakpoint is encountered. You also can also set the break point before the application is launched.
  7. When the debugger stops at the breakpoint that you set in step 6, you will see the stack crawl in the Stack pane of the InDesign main thread window. If you moved the dSym file to the proper location, you will see the stack crawl in solid black, often with variable values available for debugging. If an entry in the stack crawl is grayed out, that means Xcode cannot load the symbol file that contains that function; most likely, the symbol file is not in the right place.

Start a debug session by attaching to a running InDesign process

Xcode allows you to debug a running process so you do not have to start up the application from the Xcode to debug. You still need a project file to attach to a running InDesign process. The most logical choice is the project file for your plug-in. You also need to set up the executable so Xcode can find the host that loads your plug-in:

  1. Launch the InDesign application with symbols installed properly.
  2. Open your plug-in project in Xcode and select Product > Attach to Process > Adobe InDesign.
  3. Set a breakpoint, as described in "Start a debug session from a plug-in project .xcodeproj file" (in Mac OS: Xcode debugger). When the breakpoint is encountered or a crash occurs in InDesign, the Xcode debugger is brought to the front of the window, with debugging information.

Postmortem debugging

Windows: WinDbg

WinDbg is part of Debugging Tools for Windows from Microsoft ®, available from Microsoft's website. It provides source-level debugging through a graphical user interface that wraps around CDB/NTSD and KD, command-line debuggers that also are available from Debugging Tools for Windows. WinDbg also can read a core dump after an exception is thrown from an application. This feature is particularly useful for post-crash debugging. If you have the symbols and core-dump file, you can get a view into the memory, registers, and other debugging information at the time of the crash.

To set up WinDbg for postmortem debugging:

  1. Set WinDbg as the default debugger for postmortem debugging: at the command prompt, change directory (cd) to the WinDbg directory, and enter the following command:
    windbg -I
  2. Now, if you run InDesign and it crashes, WinDbg is invoked. Specify your symbols path, and ask WinDbg to reload the symbols.
  3. After the symbols are loaded, ask WinDbg to display the call stack, which should be resolved by the symbol files.
  4. You also can ask WinDbg to save the core-dump file, by issuing the following WinDbg command in its command line. Then you can reopen the core dump file later, on another machine with the same symbol files.
    .dump /mfh myfile.dmp

This procedure is not the only way to do postmortem debugging using WinDbg; there are many useful links in References for further reading. The WinDbg help file also contains useful details about how to debug using the Windows debugging tool.

Mac OS: GDB

The most effective postmortem debugger on Mac OS is GDB (the GNU project debugger). Mac OS X can generate a core dump after a program crashes, and GDB can then start a core-dump debugging session. The core dump does not contain symbolic information, so you must supply the symbols using the GDB add-symbol-file command. Remember that you can enable core dumps on a system-wide basis, by adding the line "limit core unlimited" to /etc/launchd.conf. Alternately, you can use the following shell command in Terminal to generate a core dump on a per-session basis (and you need to start the application from Terminal):

ulimit -c unlimited

When using core dumps, be aware of the core dump file in the /cores folder. Each core file is large, so clean up the /cores folder often, especially when you enable the core dump on a system-wide basis: every time there is a program crash, several hundred megabytes of core dumps are added to your hard drive. The folder usually is hidden from the Finder.

For more information about debugging a core file using GDB, see Apple's technical note #2124, "Mac OS X Debugging Magic." There might be a limitation on using GDB with a core file; for example, a bt command may not show an entire backtrace. Apple Radar bug 5046663 was logged to address this issue.

Adobe CrashReporter

CrashReporter is a package for recording crash logs out of Adobe applications. When an application linked with CrashReporter crashes, it gives the user the option of submitting the crash logs to Adobe via e-mail.

The CrashReporter log is in XML format. It contains several sections of information, discussed in CrashReporter log.

When InDesign crashes, the CrashReporter log is written to the ~/Library/Logs/Adobe/LogTransport folder, and the user is presented with the following dialog:

itk-guide-37.gif
Note
When CrashReporter is installed and set to record a crash log, it takes over Apple's default crash report, so no log is sent to Apple. The default path for the Apple's CrashReporter is ~/Library/Logs/CrashReporter/.

If the user clicks Cancel, he exits gracefully, and the log file is deleted. If the user clicks Continue, he is presented with the following dialog:

itk-guide-38.gif

The Problem Description edit field is a user-editable field that allows the user to add any information about the crash.

The Privacy Policy link opens a browser window with Adobe's privacy policy.

The Crash Data link opens a text editor with a text file that is loaded from a temporary location. It contains all the information about the crash, including a stack crawl, registers, library load locations and sizes, and any application-specific data the application adds.

Note
All log files that are sent are kept in an archived folder located at ~/Library/Logs/Adobe/LogTransport/Archive/<Product name>="">/<version>

CrashReporter log

The first few elements in the CrashReporter log are basic information, like the computer system where the crash occurred and the application that crashed. The following example shows an XML snippet extracted from the crash report, showing the general information section.

itk-guide-39.gif

The <crash exception>=""> element shows the crash as "Read Only Memory Exception," and the <backtrace> element tells us that the backtrace for this crash is displayed in crash thread 0, which is discussed in the next section.

Backtrace

The last line in the previous example shows that the crash process's backtrace is available in crash thread 0. The following example is extracted from that thread.

itk-guide-40.gif

Each stackStatement element in the backtrace describes a nested-function invocation (also referred to as a stack frame), with the most recently executed function at the top and the least recently executed at the bottom. The address attribute of the stackStatement element is the program-counter address in the frame. For frame 0 (that is, stackStatement index 0), this typically is the address of the instruction that caused the exception. For higher frames, this is the return address for the frame. The symbolName attribute of the stackStatement element is the symbolic name for the program-counter address in the address attribute. In this example, some stack frames have resolved the function name, because the symbols are available at the time of the crash. Some stack frames have symbol name "unknown," because the symbols are not available. In the final release build, the symbols are stripped; therefore, the symbolName attribute probably will be unknown (or the name of a nearby, exported function). To resolve this, see Adobe CrashResolver.

Registers information

The element following <backtrace> is <registerSet>, which is a snapshot of the register's state at the time of the crash. The following is an example of the register set. Apple developer technical note 2123 and "Mach-O Runtime Architecture" (see References) contain excellent information on how to decipher register values. One thing to note is that the register name "PC" in the Adobe CrashReporter is the "srr0" in the Apple CrashReporter, which is the program counter at the time the exception occurred. That address should be the same as the address you see in the backtrace stackStatement 0.

itk-guide-41.gif

Binary-image description

The final part of the CrashReporter log tells you which plug-ins/libraries are loaded in the process. The following example is part of a sample binaryImageSet element.

<binaryImageSet>
<binaryImage start="0x00001000" end="0x00007fff" type="__TEXT" path="/Volumes/Nagano/Basil 440/Packages/debug/Adobe InDesign 2025.app/Contents/ MacOS/Adobe InDesign 2025"/>
<binaryImage start="0x0000b000" end="0x0000dfff" type="__TEXT" path="/Volumes/Nagano/Basil 440/Packages/debug/Adobe InDesign 2025.app/Contents/ MacOS/InDesignModel.framework/Versions/A/InDesignModel"/>

There is a command-line tool that you can use to debug without symbols, as described in Apple developer technical note 2123 (see References). Because the final release build is stripped of symbols, however, the technique is not particularly useful for third-party developers; therefore, a tool was developed to help resolve symbol names in a CrashReporter log, which is discussed in Adobe CrashResolver.

Adobe CrashResolver

You may wonder what to do when a final release build InDesign crashes on a Macintosh ® at a customer site. There is no easy way to determine what causes the crash. This section discusses an ITK tool called CrashResolver, which you can use in a situation like this to get a meaningful stack crawl.

CrashResolver is an application that converts the stack crawl and register dumps in an Apple CrashReporter or Adobe CrashReporter log into a more informative stack crawl with function names, source filenames, some variable values, and source code (if available). To do this, CrashResolver must have access to the actual libraries and dSYM files that correspond to the correct version of the application that produced the crash log. If the source files for the build also are available, CrashResolver also provides code snippets for each function in the stack crawl.

To reduce the size of the InDesign binaries, we build the final release target of InDesign with the per-function local symbol stripped off (this is achieved by turning on the target setting "Strip Debug Symbols During Copy"). With the symbols stripped, neither Adobe or Apple's crash report can resolve the stack crawl into meaningful function names; the crash reports contain only the hex address of the function (or the offset to the nearest exported function, which is not very useful). For example, if you strip the symbols of your plug-in, the backtrace for the function in your plug-in would be something like this:

<stackStatement index="0" address="0x13a8ccac" symbolname="unknown"/>

You can resolve the plug-in name using CrashReporter with the address 0x0904a674 and look up the loaded binary image's address range. The dyld_stub_binding_helper is the nearest export symbol, which is not very useful in this case; even if you have symbol files, there is no way to tell the crash report to resolve the symbols. Therefore, Adobe CrashResolver is provided in the ITK. Along with the symbol files, it can resolve the address and report a human-readable backtrace with more information. An example follows.

With BasicDialog built with symbols stripped, and when we forced a crash in BscDlgDialogController::ApplyDialogFields, the Adobe CrashReporter reported a crash trace as follows:

<stackStatement index="0" address="0x13a8ccac" symbolname="unknown"/>
<stackStatement index="1" address="0x03042824" symbolname="__ZN15CDialogObserver6UpdateERK6IDTypeI11ClassID_tagEP8ISubjectRKS0_I9PMIID_tagEPv"/>
<stackStatement index="2" address="0x13a8cda4" symbolname="unknown"/>
<stackStatement index="3" address="0x0ee9ff9c" symbolname="_GetPlugIn"/>

With the symbols resolver set to minimal detail level, it reported the following:

[0] BasicDialog.sdk (-) : _ZN22BscDlgDialogController17ApplyDialogFieldsEP14IActiveContextRK6IDTypeI12WidgetID_tagE + 0x0000001C /Volumes/Nagano/Basil 440/Adobe InDesign CC Products SDK/source/sdksamples/basicdialog/BscDlgDialogController.cpp + 114 lines
[1] ••WidgetBinLib.dylib (-) : 0x03042824 : __ZN15CDialogObserver6UpdateERK6IDTypeI11ClassID_tagEP8ISubjectRKS0_I9PMIID_tagEPv // couldn't find SYM file
[2] BasicDialog.sdk (-) : _ZN20BscDlgDialogObserver6UpdateERK6IDTypeI11ClassID_tagEP8ISubjectRKS0_I9PMIID_tagEPv + 0x00000024 /Volumes/Nagano/Basil 440/Adobe InDesign CC Products SDK/source/sdksamples/basicdialog/BscDlgDialogObserver.cpp + 155 lines
[3] AppFramework (-) : _ZN14CChangeManager24UpdateMarkedDependenciesERK17CDependencyObject6IDTypeI11ClassID_tagES2_RKS3_I9PMIID_tagEPvlP8K2VectorI17CDependencyMarker11K2AllocatorISC_EEb + 0x0000039C /Development/Basil/source/components/appframework/messaging/CChangeManager.cpp + 747 lines

You can see the unknown function in Adobe CrashReporter is replaced by the mangled _ZN22BscDlgDialogController17ApplyDialogFields. Also, in some situations, the variable values can be reported if they are saved in registers. The second entry of the Adobe CrashReporter output shows a meaningful function name, because the function is an exported function located in WidgetBinLib.dylib. Most of the exported and imported functions from a dylib will be visible in a stack frame even without a symbol file.

Setup and preferences

Before using CrashResolver, set up the application as described in this section.

Requirements

  • Apple CrashReporter (in ~/Library/Logs/CrashReporter) or Adobe CrashReporter logs from version 10.5 or later of Mac OS X. You can save a copy of the Adobe CrashReporter log when it is displayed in TextEdit, after you choose to view the Crash Data in the Adobe CrashReporter; alternately, a sent log is archived in ~/Library/Logs/Adobe/LogTransport/Archive/Adobe InDesign <version>/<version number>="">, as explained in Adobe CrashResolver.
  • Symbol files and executables (application and shared libraries) that correspond exactly to the version of the application that produced the crash log. For a Mach-O application like InDesign, dSYM files and the CrashReporter log are all that are needed to resolve the log.
  • If your symbol files are in dSYM format, they need to be located in a writable volume, CrashResolver will write to the dSym's package folder when it parses the symbol. So, if you obtain the dSYM from a CD, you need to copy the symbols into a writable volume.
Note
CrashResolver works not only for InDesign, it also resolves all log file that meet the requirement; therefore we use "symbol" in stead of "dSYM" in this section.

Setting up CrashResolver

The version and build targets (debug, release, etc.) for the application and libraries also must match those of the application that produced the crash. Using versions of libraries and symbol files that do not exactly match that of the crashing application is likely to produce incorrect results. In addition, if the crash occurs in an application built without trace-back tables or built-in debugging symbols, the symbol files and libraries must have been built the same way.

Before using CrashResolver for the first time, launch it and go the Preferences dialog to set it up, as described in next section.

CrashResolver preferences

All set-up for CrashReporter is done through its Preferences dialog:

itk-guide-42.gif

The top of the dialog shows the location and name of the CrashResolver application.

Output Mode specifies how the output is recorded and displayed. You have the option of logging the info to the DebugWindow application (part of the InDesign SDK installation) or the Apple console.app, or writing the log to a text file. If you select the File option, you also can choose to have each output file opened automatically in your default text editor when CrashResolver finishes (the TextEdit option). CrashResolver tries to write some information out of order (that is, it appends warning messages to the header as it evaluates the stack trace). It can do this only if the File option is selected; with the other output options, warnings are interspersed within the stack trace, which may make them more difficult to read. The defaults selected are File and TextEdit.

Detail Level specifies how much information is included in the output. Final Function Source Code displays a source-code snippet (if source code is available) for the top (crashed) function in the trace. All Source Code displays a source-code snippet for each function in the trace. Both these options also display the contents of register variables in the top function in the trace. Since this register-variables feature does not require source code, you probably will want to leave the detail level at the highest level. In addition, you will want to use the displayed source-code snippets to verify that the correct version of symbol files was used to resolve the crash log.

The tabs in the dialog lets you specify four different folder locations. Click the Set button to set a path for each of the following:

  • Primary - This specifies the location (folder or application package) where CrashResolver searches for libraries and symbol files with which it resolves your crash logs. If this is not specified-or you click the Clear button-this is the CrashResolver application parent folder.
  • Alternate - This specifies an additional folder that CrashResolver searches for libraries and symbol files with which to resolve your crash logs. If libraries or symbol files with the same name are found in both the primary and alternate locations, those in the primary location are used. This option allows you to resolve crash logs using incomplete builds of an application. For example, to set up CrashResolver for an InDesign Master Archive (MA) release, set the Primary path to the folder containing the MA libs/SYMs and the Alternate path to the complete build of the application on which the MA is based. Click the Clear button to use only the primary search path.
  • Source Code - Symbol files can contain full paths for each source file from which the associated library was built. If the symbol files were not built on the local machine, the source-file paths in the symbol file are not correct. This path specifies where CrashResolver should look for source-code files when they cannot be found at the paths given in a symbol file. This search is faster and more accurate if the folder hierarchy (and folder names) on the local machine match the hierarchy of source folders used during the build. If you click the Clear button, CrashResolver does not try to search for source files that are not at the paths specified in the symbol file.
  • Output - This specifies where CrashResolver output files should be written (if you select the File output mode above). You also have the option of entering a name for the output files. If a file with the given name already exists in the output folder, CrashResolver appends a number to the name. CrashResolver never overwrites existing output files. If you click the Clear button, CrashResolver writes the output files into its parent folder.

All but the last of these paths let you specify a Follow Aliases When Searching option. With this selected, CrashResolver also searches any folders referenced by aliases found in the search path.

If you change the primary or alternate library/symbol file folder in the preferences dialog, CrashResolver automatically updates itself to reflect the change when you close the dialog (searching the folders again). If you add, remove, or rebuild libraries or symbol files while CrashResolver is running, you must quit and relaunch CrashResolver for the changes to take effect.

For simplicity, CrashResolver is designed to work with only one version of an application. If you need to be able to resolve crash logs from different applications or different versions of an application, you must create duplicates of CrashResolver and set up each one accordingly. For this reason, CrashResolver is a completely self-contained application package. It stores its preferences in its internal /Contents/Resources folder.

Updating CrashResolver

Because CrashResolver stores its preferences inside its application package, you normally would have to set up your preferences again when you upgrade to a new version of CrashResolver; however, CrashResolver includes a means for retaining your preferences when upgrading to a new version. After dragging the new copy of CrashResolver to its desired location, drag the old copy of CrashResolver onto the new one. You will be asked whether you want to copy your preferences. Click OK, and the old preferences are copied to the new copy of CrashResolver.

Using the crash log

Drag the CrashReporter log that you want to decipher onto the CrashResolver application. You get a log containing a stack crawl for each crash contained in the CrashReporter log (CrashReporter logs may be cumulative, containing all crashes of the given application). You also can copy a selection of a CrashReporter log, launch CrashResolver, and choose Edit > Paste. (You can copy and paste only the stack-crawl portion of an Apple CrashReporter log; however, you will need to copy and paste an entire Adobe CrashReporter log.) Where and how the output is displayed depends on the option you selected in the preferences dialog. See CrashResolver output.

Note
If the application and libraries contain CFBundleShortVersionString version strings in their info.plists, these strings are included in the CrashReporter log for each library of the crashed application. CrashResolver checks these version strings against the versions of the libraries used to resolve the log. If there are any mismatches, the output file includes a warning in its header; however, CrashResolver still tries to resolve the crash log.

It is important to visually verify that the crash log was resolved with the correct version of libraries and symbol files. If source files are available, CrashResolver displays a snippet of code at each function call in the stack crawl. By checking to see that the function indicated in this snippet matches the name of the function in the stack crawl above it, you can determine that the output probably is valid. See CrashResolver output.

If the CrashReporter log-stack crawl already includes local debugging symbols (function names), you can still run the file through CrashResolver to get additional information, like the source-code snippets and the values of any register variables in the crashed function.

Also, for MachO executables that include no debugging symbols, the CrashReporter log may instead give offsets relative to exported functions. These exported functions often are not the functions from which the calls in the stack crawl were made. Running these log files through CrashResolver gives you the names of the correct function calls.

CrashResolver output

You have several options for recording the output of CrashResolver, selected in the preferences dialog: trace to the DebugWindow application or the Mac Console.app, or log to a file. If this last option is selected, the log file is written to the output folder with the name selected in the CrashResolver preferences. Every CrashReporter log dragged to CrashResolver is written to a separate output file. If you choose to write to a file, you also have the option of having the file open automatically in your default text application (usually TextEdit) after it is created; this is the default behavior.

The format of CrashResolver output is described below.

The output file header is as follows:

CrashResolver - version: 2.3 build: Jan 28, 2010
Adobe Systems Incorporated
File: <the name of the crash log file being processed>
Using: <the primary folder/app containing the libraries and SYM files used to resolve the log>
and: <alternate folder of lib/SYM files (see the preferences dialog)>
Crash incidents in this log: 1

The header is followed by a list of crash incidents. Some CrashReporter logs include all crashes that occurred with the given application since the log file was last deleted. Each incident is preceded by the following header:

1) ------------------------------- CRASH ---------------------------------
OS Version: <Mac OS version at the time of the crash>
Report Version: <CrashReporter version number>
Application Version: <Crashed application version number>
Date: <Date and time of the crash>
Exception: <The exception from the log>

The crash-incident header is immediately followed by any warning messages concerning CrashResolver's attempt to resolve the stack crawl, like a mismatch in version numbers between any libraries in the crashed application and the lib/symbol files used to resolve the log.

Note
The absence of warning messages does not necessarily mean that you are using the correct version of libraries and symbol files. You still should visually verify that the stack crawl looks correct.

This is followed by the resolved stack crawl. The stack crawl includes one entry for each line in the stack crawl from the CrashReporter log. A stack-crawl entry is preceded by a line number in brackets. If the entry could not be resolved, the line number is followed by "••." The reason for the failure follows in a C-style comment. The stack crawl is displayed in the same order in which it is presented in the CrashReporter log, that is, crashed function at the top, and application entry point at the bottom. The "lib version string" displayed is the version string taken from the CrashReporter log (if available) and is the version of the library in the crashed application.

[1] <library name> (<lib version string>) : <mangled C++ function name> + <offset><source file and path> - line: <source file line number>

This entry header is followed by the source code, if the source code is available and the appropriate detail level is selected in the preferences. The line at which the jump to the next line in the stack crawl occurred is marked in this source code snippet with an asterisk (*).

if (lwProcessing)
{
// bypass command manager*
cmd->LWDoImmediate();
}
else
cmdMgr->Do(cmd);

You can verify that the resolution of the stack crawl appears valid by matching the function name at this mark against the "mangled C++ function name" in the line above this one in the stack crawl. They should be the same; for example:

[3] PublicDbgLib (-) : .LWDoImmediate__7CommandFv + 0x00000088 ...:dragontail_gm:source:public:commands:Command.cpp - line: 548 (source snippet omitted)
[4] AppFramework (-) : .CommonProcessCommand__16CommandFP8ICommandUc + 0x00001120 ...:appframework:commandmgmt:CommandProcessor.cpp - line: 341
if (lwProcessing)
{
// bypass command manager*
cmd->LWDoImmediate(); (matches '.LWDoImmediate__7CommandFv' above)
}
else
cmdMgr->Do(cmd);
Note
The method for mangling C++ function names is not standardized. Sometimes, the class and method names are reversed; however, you should be able to recognize the pattern.

How CrashResolver works

CrashResolver works by first searching the primary (and, possibly, alternate) directories and all their subdirectories for applications, shared libraries, and symbol files. Executables are matched to their corresponding symbol files by filename.

In addition to the symbol files, the libraries are needed for two reasons:

  • Both MachO and CFM executables contain nonexecutable information in the header of the library file. The executable portion is offset from the start of the container file. All offsets in a symbol file are relative to the start of the executable portion, not the start of the file. The stack traces in (Apple) CrashReporter logs report offsets relative to the start of the executable file if it is CFM, but relative to the start of the executable portion if it is MachO. Since symbol files do not specify the offset of the executable portion from the start of the container file, the libraries themselves are needed (the MachO libraries are needed to verify they are MachO). If you do not specify the libraries, CrashResolver assumes Mach-O; so, if you will resolve an InDesign crash log with its dSYMs, you do not really need to specify the libraries,.
  • CrashResolver matches the version string (if any) stored with the library against the version string listed for the library in the CrashReporter log, to verify that they match.

When a CrashReporter log is dragged onto a copy of CrashResolver, it searches for all libraries and symbol files (when it is first launched). It then parses the CrashReporter log, looking for each stack trace corresponding to a crash. For each line in the stack trace, CrashResolver uses the symbol file of the corresponding name to determine the function name, source file, and (for the function at the top of the stack trace) the values of any variables stored in registers.

CrashResolver requires that the CrashReporter log contain both library names and offsets. Mac OS X 10.3 was the first version that included library names in CrashReporter logs. Crash logs from previous versions of Mac OS cannot be deciphered.

CrashResolver also can use the source files from which the application was built, if they are available. Whether CrashResolver looks for the source files depends on the "Detail Level" you selected in the preferences dialog. If you select a high level of detail and the source files are not available, the additional information they would have provided is not logged. CrashResolver uses the source files to calculate source-line numbers for each function in a stack trace. Also, having the source available allows CrashResolver to display a code snippet at each function call in the stack trace. This provides a convenient way to verify that the output looks correct; that is, the version of the symbol files used matches the version of the crashed application.

Because CrashReporter logs contain a dump of the contents of the registers at the time of the crash, CrashResolver can provide the values of any variables stored in registers at the time of the crash. CrashReporter stack traces, however, do not always end with the function in which the crash occurred: they sometimes stop with the function that called the crashing function. If this is the case, the variable values listed may not be correct.

Frequently asked questions

Can I access symbols from a server?

You should be able to open a symbol file located on a server, as long as you have access to the server. Both Xcode and Visual Studio support remote debugging. For set-up information, see the IDE's debugging manual.

After I load the symbol file, why don't I see the source code?

The symbol file does not contain source code. If you want to do true, source-level debugging, you still need the original source code used to compile the project. The presence of symbols lets you examine the call stack and variables.

Can I disable the Adobe CrashReporter?

CrashReporter can be set to any of three modes:

  • Always show this dialog.
  • Don't show this dialog again and always send crashes to Adobe.
  • Don't show this dialog again and never send crashes to Adobe.

Use the Adobe Crash Reporter Preference Chooser to set the InDesign's preference in /Applications/Adobe InDesign 2025/Adobe InDesign 2025.

  1. Run:
    Contents/Frameworks/AdobeCrashReporter.framework/Versions/A/Resources/Adobe Crash Reporter.
  2. Set the mode.

Why don't I see Adobe CrashReporter output generated on the Mac after a crash?

Check the option described in Can I disable the Adobe CrashReporter? to make sure Adobe CrashReporter is not disabled. Also, if you run the application from within a debugger, Adobe CrashReporter output is not generated after a crash.

How can I suppress the Adobe CrashReporter dialog box?

See the Apple Technical Q&A on suppressing "unexpected quit" alerts: http://developer.apple.com/qa/qa2001/qa1288.html

Adobe CrashReporter respects this setting.

Why don't I see any core dump file generated on the Macintosh after a crash?

Apple's Technical Note TN2124 has the latest information on how to generate a core dump. To summarize, you should do one of the following:

  • Add a "limit core unlimited" line to your /etc/launchd.conf file.
  • From Terminal, raise the core dump size limit by "ulimit -c unlimited," then launch your program from the Terminal. If the program crashes, a core dump should be created in /cores.

If neither of these methods works, try to disable Adobe Crash Reporter as described in Can I disable the Adobe CrashReporter?. The CrashReporter may have intercepted the crash signal. (The latest CrashReporter should not do this, but the older version may exhibit this behavior.)

Why doesn't CrashResolver display the variable value all the time?

CrashResolver relies on the CrashReporter and symbols to provide the information. During a crash, the CrashReporter captures the values in registers, so if the variable is stored in the register, CrashResolver can display the variable value. Not all variables are stored in registers; they could be in the stack/heap. Also, it depends on the type of the variable: the content of the register may contain an address to the real data. So, the variable value is not always available.

When I am debugging an attached Windows process, why doesn't the debugger stop at the breakpoint?

Most likely, Visual Studio cannot find the symbol file. Perhaps you did not specify the symbol search path, or you set the breakpoint before you specified the path. Try to delete the breakpoint, make sure the debug symbol path covers the path of your symbol, then set the breakpoint again. When a breakpoint is in effect, you should see a red dot next to the breakpoint name in the Breakpoints window.

References