๐ŸŒต Cachi

June 29, 2026 ยท View on GitHub

Cachi is a swift tool to parse and visualize test results contained in Xcode's .xcresult files on a web interface. It additionally offers a set of APIs that can be queried to extract information in json format from previously parsed test results.

Automatic screen recording (Xcode 15) and screenshot based xcresults are supported.

Installation

Cachi is no longer distributed via a Homebrew tap. Build it locally from source:

git clone https://github.com/Subito-it/Cachi.git
cd Cachi
swift build -c release

The compiled binary will be available at .build/release/cachi.

Usage

Cachi can be launched by passing the port for the web interface and the location where it should search for the .xcresult bundles.

You can optionally pass:

  • --search_depth to specify how deep Cachi should traverse the location path. Default is 2, larger values may impact parsing speed.
  • --merge to merge multiple xcresults in the same folder as if they belong to the same test run. This can be used in advanced scenarios like for example test sharding on on multiple machines.
  • --attachment-viewer extension:/path/to/viewer.js to register a JavaScript bundle that renders attachments with a matching file extension. Repeat the flag for multiple mappings (extensions are case-insensitive and should be provided without the leading dot).
  • --max_disk_size megabytes to cap total blob (video/session-log) disk usage. When exceeded, whole sessions are evicted oldest-first โ€” but only sessions whose .xcresult has already been pruned externally (a run whose bundle is still on disk is never evicted, since its blobs are re-derivable). See Documentation/ARCHITECTURE.md.
$ cachi --port number [--search_depth level] [--merge] [--max_disk_size megabytes] path

Endpoint documentation

http://local.host:port/v1/help will return a list of available endpoint with a short overview. A full reference lives in Documentation/ENDPOINTS.md.

Data store

Cachi persists everything it ingests into a .cachi-data/ directory created inside the path you launch against (a sibling of the dated run folders, so it survives their pruning):

  • .cachi-data/cachi.sqlite โ€” structured data: run metadata, the test list, failure detail (activity trees, failures, performance metrics), and blob manifests.
  • .cachi-data/blobs/ โ€” content-addressed binaries kept out of SQLite: transcoded screen recordings and gzipped session logs, deduplicated by SHA-256.

Because history lives in this store, it outlives the .xcresult bundles that produced it. To wipe everything, stop the server and delete .cachi-data/.

Architecture & documentation

DocumentWhat it covers
ARCHITECTURE.mdHow Cachi ingests, stores (SQLite + blob store), and serves results; concurrency model; data lifecycle (with diagrams).
ENDPOINTS.mdFull HTTP endpoint reference, data sources, and identifier relationships.
XCRESULTTOOL_DATA_MODEL.mdComplete map of what xcresulttool exposes and how it maps onto Cachi's schema.
INGEST_BENCHMARK.mdMeasured cost of ingesting real runs into the store, and the sizing/retention rationale.

Test result customization

Custom attachment viewers

Use the repeatable --attachment-viewer option to associate one or more attachment file extensions with a JavaScript bundle. When a table entry links to an attachment whose filename ends with a registered extension, Cachi serves an auto-generated index.html wrapper instead of the raw file. The wrapper embeds your script and exposes the selected attachment so that custom visualizations can be rendered.

  • Scripts are proxied through the server at /attachment-viewer/script, so the JavaScript file can live anywhere accessible to the Cachi process.

  • The generated page mirrors the following structure:

    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Cachi Attachment Viewer - Example</title>
      </head>
      <body>
        <noscript>This app requires JavaScript.</noscript>
        <script>
          (function () {
            var s = document.createElement('script');
            s.src = '/attachment-viewer/script?viewer=json&attachment_filename=data.json';
            s.attachmentPath = 'resultId/attachmentHash';
            s.onload = function(){};
            document.body.appendChild(s);
          })();
        </script>
      </body>
    </html>
    
  • The relative file path (from /tmp/Cachi) is made available to your script as a property on the script element:

const attachmentPath = document.currentScript.attachmentPath;

Note: The attachmentPath is now a relative path from /tmp/Cachi. To construct the full path to the attachment file, append the attachmentPath to /tmp/Cachi/. For example, if attachmentPath is resultId/attachmentHash, the full path would be /tmp/Cachi/resultId/attachmentHash.

Remember to provide the extension without a dot (json, html, csv, โ€ฆ). Cachi normalizes extensions in a case-insensitive manner and rejects duplicate registrations to surface configuration mistakes early.

The following keys can be added to the Info.plist in the .xcresult bundle which will be used when showing results:

  • branchName
  • commitHash
  • commitMessage
  • githubBaseUrl: used to generate links to specific code lines within GitHub repositories. This allows to easily navigate to specific code segments associated to test failures. Example: https://github.com/Subito-it/Cachi
  • sourceBasePath: used to cleanup file paths removing compilation base paths from source code locations. Example: /Users/someuser/path/to/repository will convert locations such as /Users/someuser/path/to/repository/modules/Somefile.swift into /modules/Somefile.swift.
  • xcresultPathToFailedTestName: This parameter helps to clean up System Failures that occur when UI tests fail early in the execution process which results in test name not being included in the .xcresult bundle. Provide a dictionary where the key represents the .xcresult file paths, relative to the sourceBasePath and the value corresponds to the failed test name in the format testSuiteName/testName. Note: this can be used only when having a single .xcresult file generated per test.

Contributions

Contributions are welcome! If you have a bug to report, feel free to help out by opening a new issue or sending a pull request.

Authors

Tomas Camin (@tomascamin)

License

Cachi is available under the Apache License, Version 2.0. See the LICENSE file for more info.