CTRF allows you to create personalized test result reports in GitHub Actions. This guide will show you how to create your own custom report template using CTRF, Handlebars and GitHub flavored markdown.
- Full control over the layout and content of your test results
- Ability to highlight information that matters most to your team
- Flexibility to match your organization's reporting standards
- Integration with GitHub-specific data and workflow information
- Share your report with others by submitting it to the community reports section
Creating a Handlebars markdown template allows you to have full control over how your test results are displayed. With CTRF, GitHub and Handlebars you can inject dynamic content into your reports, making your them flexible to suit your needs.
You can apply custom templates when using the custom-report method.
All of our reports are built using handlebars, for inspiration check out the built-in reports and community reports.
And for a practical example, see the custom report example.
The high level of control and flexibility allows for endless customization and a wide range of reports that can be built by the community. That's why we've created a community reports section where users can share their reports to be used by others.
- CTRF schema - for the schema of the data available to use in your template
- Handlebars documentation - for the templating language
- GitHub Markdown - for the markdown syntax
- GitHub Context - for the context available to use in your template
Here's a practical example of a Handlebars template that creates a test summary with explanations:
This template demonstrates:
- Using basic CTRF properties (
ctrf.summary.*) - Accessing GitHub context (
github.*) - Using helper functions (
countFlaky,formatDurationFromTimes,stripAnsi) - Conditional rendering with
{{#if}}blocks - Iterating over tests with
{{#each}}
And what it looks like:
Handlebars is a simple templating language that lets you insert data into your markdown in a declarative way. You can use placeholders, conditionals, and loops to dynamically generate content based on your test results.
When writing your template, you can use Handlebars helpers:
{{eq arg1 arg2}}: Compares two arguments and returns true if they are equal.
See available helpers handlebars-helpers-ctrf repository.
We welcome contributions for additional helpers.
The ctrf object provides access to your test results data. Here are the main
properties:
tests: Total number of testspassed: Number of passed testsfailed: Number of failed testsskipped: Number of skipped testsstart: Test suite start timestop: Test suite end time
An array of test results, each containing:
name: Test namestatus: Test status ("passed", "failed", "skipped")message: Test output/error messageduration: Test duration in millisecondsretries: Number of retries (for flaky tests)
Example accessing test data:
GitHub properties are made available to use in your template. You can access these properties by using the github property, for example github.repoName or github.actor.
Access workflow and execution information:
Workflow Information:
github.workflow- Name of the workflowgithub.action/github.action_name- Current action namegithub.job/github.job_id/github.jobName- Current job identifiergithub.runNumber/github.run_number- Unique number for each workflow rungithub.runId/github.run_id/github.workflowId- Unique identifier for the workflow rungithub.workflowName- Workflow name (legacy property)
Event Information:
github.eventName/github.event_name- Event that triggered the workflow (e.g., "push", "pull_request")github.actor/github.actor_name/github.actorName- User who triggered the workflowgithub.sha- Commit SHA that triggered the workflowgithub.ref- Git ref that triggered the workflow (e.g., "refs/heads/main")github.branchName- Current branch name
Repository & URLs:
github.repoName- Repository namegithub.buildUrl/github.build_url- URL to the workflow run summarygithub.serverUrl/github.server_url/github.baseURL- GitHub server URL (e.g.,https://github.com)github.apiUrl/github.api_url- GitHub API URL (e.g.,https://api.github.com)github.graphqlUrl/github.graphql_url- GitHub GraphQL API URL
Pull Request (at root level):
github.pullRequestNumber- PR number (legacy property, prefergithub.pullRequest.number)
Access repository information:
Basic Info:
github.repository.name- Repository namegithub.repository.fullName/github.repository.full_name- Owner and repository name (e.g., "owner/repo")github.repository.description- Repository descriptiongithub.repository.language- Primary languagegithub.repository.defaultBranch/github.repository.default_branch- Default branch namegithub.repository.licenseName/github.repository.license_name- License name
Statistics:
github.repository.size- Repository size in KBgithub.repository.stargazersCount/github.repository.stargazers_count- Number of starsgithub.repository.openIssuesCount/github.repository.open_issues_count- Number of open issues
URLs:
github.repository.htmlUrl/github.repository.html_url- Repository URLgithub.repository.cloneUrl/github.repository.clone_url- HTTPS clone URLgithub.repository.sshUrl/github.repository.ssh_url- SSH clone URLgithub.repository.compareUrl/github.repository.compare_url- Compare URLgithub.repository.contributorsUrl/github.repository.contributors_url- Contributors page URLgithub.repository.deploymentsUrl/github.repository.deployments_url- Deployments URLgithub.repository.downloadsUrl/github.repository.downloads_url- Downloads URLgithub.repository.eventsUrl/github.repository.events_url- Events URLgithub.repository.forksUrl/github.repository.forks_url- Forks URLgithub.repository.stargazersUrl/github.repository.stargazers_url- Stargazers URLgithub.repository.statusesUrl/github.repository.statuses_url- Commit statuses URLgithub.repository.subscriptionUrl/github.repository.subscription_url- Subscription URLgithub.repository.tagsUrl/github.repository.tags_url- Tags URLgithub.repository.teamsUrl/github.repository.teams_url- Teams URL
Settings:
github.repository.allowForking/github.repository.allow_forking- Whether forking is allowedgithub.repository.createdAt/github.repository.created_at- Repository creation timestamp
Access pull request information (available when triggered by PR events):
Basic Info:
github.pullRequest.id- PR IDgithub.pullRequest.number- PR numbergithub.pullRequest.title- PR titlegithub.pullRequest.body- PR descriptiongithub.pullRequest.state- PR state ("open", "closed")github.pullRequest.draft- Whether PR is a draft (boolean)github.pullRequest.rebaseable- Whether PR can be rebased (boolean or null)
Changes:
github.pullRequest.additions- Lines addedgithub.pullRequest.deletions- Lines deletedgithub.pullRequest.changedFiles/github.pullRequest.changed_files- Number of files changed
Collaboration:
github.pullRequest.assignee- Assigned user object (or null)github.pullRequest.assignees- Array of assigned usersgithub.pullRequest.authorAssociation/github.pullRequest.author_association- Author's association with the repositorygithub.pullRequest.requestedReviewers/github.pullRequest.requested_reviewers- Array of requested reviewersgithub.pullRequest.requestedTeams/github.pullRequest.requested_teams- Array of requested teamsgithub.pullRequest.comments- Number of commentsgithub.pullRequest.reviewComments/github.pullRequest.review_comments- Number of review commentsgithub.pullRequest.labels- Array of labels
URLs:
github.pullRequest.htmlUrl/github.pullRequest.html_url- PR URLgithub.pullRequest.diffUrl/github.pullRequest.diff_url- Diff URLgithub.pullRequest.patchUrl/github.pullRequest.patch_url- Patch URL
Timestamps:
github.pullRequest.createdAt/github.pullRequest.created_at- Creation timestampgithub.pullRequest.closedAt/github.pullRequest.closed_at- Closed timestamp (or null)github.pullRequest.pushedAt/github.pullRequest.pushed_at- Last push timestamp
Merge Settings:
github.pullRequest.autoMerge/github.pullRequest.auto_merge- Auto-merge configuration (or null)
Access information about the user who triggered the workflow:
github.sender.login- Usernamegithub.sender.id- User IDgithub.sender.nodeId/github.sender.node_id- Node IDgithub.sender.type- User type (e.g., "User", "Bot")github.sender.siteAdmin/github.sender.site_admin- Whether user is a site admin (boolean)github.sender.htmlUrl/github.sender.html_url- User profile URLgithub.sender.avatarUrl/github.sender.avatar_url- User avatar URLgithub.sender.gravatarId/github.sender.gravatar_id- Gravatar ID
You can access the complete webhook event payload via github.context. This contains the raw event payload data sent by GitHub when the workflow was triggered (e.g., the full pull_request object, repository object, issue object, etc. depending on the event type).
This is useful for accessing event-specific data that isn't exposed in the structured properties above. For example:
github.context.pull_request.head.ref- Head branch namegithub.context.pull_request.base.ref- Base branch namegithub.context.issue- Issue data (for issue events)github.context.comment- Comment data (for comment events)
See the GitHub Actions events documentation for available event types.
Note: Contexts, objects, and properties will vary significantly under different workflow run conditions. For example, pull request properties are only available when the workflow is triggered by a pull request event.
To see all available properties for your specific workflow, print the context in your workflow logs:
- name: Print GitHub Context
env:
CONTEXT: ${{ toJson(github) }}
run: echo "$CONTEXT" | jq .