Validate iOS PDF Exports on a Cloud Mac with PDFKit

Validate iOS PDF Exports on a Cloud Mac with PDFKit

You tap “Export” in an iOS app, and the file appears in the share sheet. But when the recipient opens it, a page is missing. For a failure like this, it is not enough to assert that the export function returned successfully. The test job needs to retrieve the final PDF file, check what can be verified automatically, and leave the rest for visual review. The workflow below works with simulator tests on a cloud Mac and can also be run locally.

Define an output you can verify

Choose a fixed input, such as two line items and a specific report title. The test should know the expected filename, minimum page count, and a stable phrase from the report body. Avoid asserting exact strings for timestamps, random identifiers, or total amounts; those fields may change with the test data. A more reliable approach is to fix the input data and set explicit format rules for dynamic fields.

Have the test build of the app write the PDF to its own Documents/export.pdf. This makes verification easier; it does not require the production app to use the same delivery path. Delete the old file before each run, then check that the new file exists and is not empty after export. Otherwise, an artifact from the previous run could produce a false pass.

A file existing, being parseable, and containing the right content are three separate checks. Passing one does not establish the next.

Retrieve the file from the simulator

Run the app and perform the export using the simulator identifier specified by the test job. When the job finishes, query the app container on the device with that same identifier. Do not use booted to guess which device ran the test; it is especially easy to pick the wrong one when multiple simulators are running. The identifier and bundle ID in this example should come from the test configuration:

UDID="替换为测试使用的模拟器标识"
BUNDLE_ID="替换为测试应用的包名"
CONTAINER="$(xcrun simctl get_app_container "$UDID" "$BUNDLE_ID" data)" || exit 1
mkdir -p artifacts
test -s "$CONTAINER/Documents/export.pdf" || exit 1
cp "$CONTAINER/Documents/export.pdf" artifacts/export.pdf
file artifacts/export.pdf

get_app_container requires the app to be installed on the specified simulator. If the query fails, check the device identifier actually used by the test, the bundle ID, and whether the app was installed. Do not switch to searching the entire disk. After copying, the file under the job’s artifacts directory is the one being checked. Use that copy for subsequent parsing and archiving so another write by the app cannot change the file under inspection. The output of file can help with diagnosis, but it does not replace PDF parsing.

Check structure and text with PDFKit

Save the following Swift code as scripts/check-pdf.swift. It accepts a file path and an expected phrase, checks the page bounds on every page, and confirms that at least one page contains the phrase. A nonzero exit code lets the build job mark the automated check as failed.

import Foundation
import PDFKit

guard CommandLine.arguments.count == 3,
      let pdf = PDFDocument(url: URL(fileURLWithPath: CommandLine.arguments[1])),
      pdf.pageCount > 0 else {
    fputs("PDF is missing or cannot be parsed\n", stderr)
    exit(2)
}

let expected = CommandLine.arguments[2]
var extracted = ""
for index in 0..<pdf.pageCount {
    guard let page = pdf.page(at: index) else {
        fputs("PDF page is missing\n", stderr)
        exit(2)
    }
    let media = page.bounds(for: .mediaBox)
    let crop = page.bounds(for: .cropBox)
    guard media.width > 0, media.height > 0,
          crop.width > 0, crop.height > 0,
          media.contains(crop) else {
        fputs("PDF page bounds are invalid\n", stderr)
        exit(2)
    }
    extracted += page.string ?? ""
}
guard extracted.contains(expected) else {
    fputs("Expected text was not extracted\n", stderr)
    exit(2)
}
print("Checked \(pdf.pageCount) PDF page(s)")

Run swift scripts/check-pdf.swift artifacts/export.pdf "Quarterly Report". Replace the example phrase with text that is actually present in the fixed test data. If the requirement is exactly two pages, also assert pdf.pageCount == 2; checking only that the count is greater than zero will not catch pagination changes. You can check page dimensions against the product’s requirements as well, but do not reject otherwise valid paper sizes without an agreed specification.

Text extraction is not visual verification

page.string checks extractable text. It cannot guarantee that glyphs have not been clipped or that charts, images, and colors are correct. Conversely, a scanned page or text rendered as an image may look fine but yield no extractable text. If a text assertion fails, retain the original PDF and determine whether the content was never written or text extraction is unsuitable for that page. Do not simply remove the assertion to make the job pass.

Pinpoint the failing stage

Record the outcome of the export operation, the file copy, and the PDFKit process separately for each job. When a failure occurs, you can then check in order whether the app performed the export, whether this run produced a file in the container, whether the copy can be parsed, and whether its page count, bounds, and text match expectations. A single “PDF test failed” message leaves you needing to rerun the entire workflow to investigate.

Symptom Check first
Container not found Simulator identifier, bundle ID, installation result
Empty file Whether the old file was removed and the write completed
PDF cannot be parsed Copy operation, final file format
Text does not match Fixed input, page content, limits of text extraction

On failure, archive artifacts/export.pdf and the test logs—but first confirm that the report contains no real user data. If the file could contain sensitive information, use a synthetic sample, restrict access to the artifacts, and clean them up according to your team’s policy. Do not upload production reports as artifacts in a public pipeline.

Finish with a reproducible run

Before submitting the verification workflow, run it twice in succession with the same fixed input. Both runs should export afresh, overwrite the target file, and produce consistent page-count and text-check results. Then deliberately remove the expected phrase and confirm that the script fails. Next, have the test app produce no file and confirm that the copy step fails. These two negative checks rule out a test that passes because its assertions never ran.

After the automated checks pass, open PDFs from representative samples and inspect page breaks, wrapping of long text, and chart edges. Let the machine enforce stable structural and text requirements, and let a person confirm the rendered result. Record those conclusions separately so the next export failure has a clear baseline for comparison.

Frequently asked questions

Does empty extracted text mean the PDF export failed?

Not necessarily. Text rendered as an image or encoded with some fonts may not extract. Keep the file and inspect its rendered pages.

How can a test job collect a PDF from the simulator?

Have the app write it to Documents, locate its data container with simctl get_app_container, then copy the file into the job’s artifacts directory.

Hand your next build to a dedicated cloud Mac.

Compare ViewMini models, regions, and rental terms, then choose a physical node that fits your workflow.

Configure your cloud Mac

Compare devices and rental terms

Choose from three models, with daily, weekly, monthly, and quarterly pricing. Review the full plans before ordering.

View models and pricing Configure your cloud Mac