Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
pull_request:
branches: [master]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -24,3 +27,21 @@ jobs:
- name: Test upload task
run: npm test
working-directory: tasks/UploadPortalHtmlReport

- name: Test GitHub Action helpers
run: node --test github-action/*.test.js

publish-fixtures:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Publish fixture HTML reports
uses: ./
with:
report-dir: tasks/UploadPortalHtmlReport/tests/fixtures
name: Fixture Reports
fail-on-failed-reports: false
comment-on-pr: true
5 changes: 4 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"INPUT_REPORTDIR": "${workspaceFolder}/tasks/UploadPortalHtmlReport/tests/fixtures",
"INPUT_TABNAME": "HTML Report",
"INPUT_REDACTSECRETS": "false",
"INPUT_FAILONEMPTY": "true"
"INPUT_FAILONEMPTY": "true",
"INPUT_INLINEASSETS": "true",
"INPUT_PUBLISHARCHIVE": "true",
"INPUT_FAILONFAILEDREPORTS": "false"
},
"skipFiles": [
"<node_internals>/**"
Expand Down
96 changes: 79 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,77 @@
# Azure DevOps HTML Report Portal
# CI HTML Viewer

Publish self-contained HTML reports (Newman HTML Extra, Playwright, Cypress, coverage, or any other HTML file) and view them as a tab on Azure Pipelines build and release results.
Publish HTML reports from CI and view them where the work happens:

Each tab embeds the report in the pipeline UI and provides a download link.
- **GitHub Actions** — workflow summary, artifact download, and a sticky pull request comment
- **Azure Pipelines** — an embedded tab on the build or release result page

This project is a fork of [maciejmaciejewski/azure-pipelines-postman](https://github.com/maciejmaciejewski/azure-pipelines-postman), generalized beyond Postman reports.

## Configuration
## GitHub Actions

Add the **Upload HTML Report** task after your tests produce HTML output. Use `condition: succeededOrFailed()` so reports still publish when tests fail.
The repository is an action. On a pull request it posts (or updates) a comment with pass/fail for each HTML file and a link to the workflow artifacts. On `push` it still writes the job summary and uploads artifacts.

The task takes:
```yaml
name: Tests
on:
pull_request:
push:
branches: [main]

permissions:
contents: write # push HTML preview to gh-pages
pull-requests: write # sticky PR comment

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test -- --reporter=html --output=reports
- uses: joneja09/azure-pipelines-html-viewer@v1
if: always()
with:
report-dir: reports
name: Test Reports
fail-on-failed-reports: true
```

If this repository is renamed to `ci-html-viewer`, change that to `joneja09/ci-html-viewer@v1`. Until then, use the current repo name.

| Input | Default | Description |
| --- | --- | --- |
| `report-dir` | required | A single `.html`/`.htm` file, or a directory searched recursively |
| `name` | `HTML Report` | Artifact name, summary heading, and PR comment title |
| `inline-assets` | `true` | Embed local CSS, JS, and images |
| `publish-archive` | `true` | Include a zip of a report directory in the artifact |
| `redact-secrets` | `false` | Mask Bearer tokens and common secret keys |
| `fail-on-empty` | `true` | Fail when no HTML files are found |
| `fail-on-failed-reports` | `false` | Fail the job after publishing when a report looks unsuccessful |
| `comment-on-pr` | `true` | Post or update a sticky comment on `pull_request` workflows |
| `pages-preview` | `true` | Publish inlined HTML to the `gh-pages` branch and link each report in the PR comment |
| `upload-artifact` | `true` | Upload prepared reports as a workflow artifact |

GitHub cannot embed a full HTML report inside a PR thread (comments are markdown). The comment is the scoreboard; each report name links to a GitHub Pages preview of that HTML file. Re-runs update the same sticky comment (`<!-- ci-html-viewer:Name -->`) instead of adding a new one.

Enable **Settings → Pages → Deploy from branch `gh-pages`** once so those links render. Until that is set, the comment still posts and artifacts still upload; HTML links may 404. Preview deploy is best-effort and will not fail the job if the push is denied.

Fork PRs only get a comment when the workflow token has `pull-requests: write`. Job summaries and artifacts still publish.

- `reportDir` (required) — a single `.html`/`.htm` file, or a directory that is searched recursively
- `tabName` (optional) — tab label on the pipeline run (default: `HTML Report`)
- `redactSecrets` (optional) — mask Bearer tokens and common secret keys before upload (default: `false`)
- `failOnEmpty` (optional) — fail the task when no HTML files are found (default: `true`)
## Azure Pipelines

Reports should be **self-contained** HTML (CSS/JS inlined). Companion assets such as Playwright's `playwright-report/` folder are not published as a static site.
Add the **Upload HTML Report** task after your tests produce HTML. Use `condition: succeededOrFailed()` so reports still publish when tests fail.

| Input | Default | Description |
| --- | --- | --- |
| `reportDir` | `$(System.DefaultWorkingDirectory)` | A single `.html`/`.htm` file, or a directory searched recursively |
| `tabName` | `HTML Report` | Tab label on the pipeline run |
| `inlineAssets` | `true` | Embed local CSS, JS, and images so multi-file reports render in the tab |
| `publishArchive` | `true` | Attach a zip of a report **directory** (skipped above 50 MB; `node_modules` is omitted) |
| `redactSecrets` | `false` | Mask Bearer tokens and common secret keys (useful for Newman HTML Extra) |
| `failOnEmpty` | `true` | Fail when no HTML files are found |
| `failOnFailedReports` | `false` | Fail after upload when a report looks unsuccessful |

`index.html` is listed first when a directory contains several HTML files. A single report is expanded automatically in the tab.

```yaml
steps:
Expand All @@ -39,38 +93,46 @@ steps:
reportDir: '$(System.DefaultWorkingDirectory)/reports/newman.html'
tabName: 'Postman'
redactSecrets: true
failOnFailedReports: true
```

### Directory of reports
### Coverage or other multi-file HTML

Local `link`, `script`, and `img` references are inlined into each HTML file before upload. That is enough for typical coverage folders (JaCoCo, Istanbul). Playwright/Cypress apps that `fetch()` extra JSON at runtime still need a self-contained HTML export, or download the zip / GitHub artifact of the original folder.

```yaml
- task: UploadPortalHtmlReport@1
condition: succeededOrFailed()
inputs:
reportDir: '$(System.DefaultWorkingDirectory)/reports'
tabName: 'QA Reports'
reportDir: '$(System.DefaultWorkingDirectory)/coverage'
tabName: 'Coverage'
```

Run the task more than once with different `tabName` values to publish multiple report groups.
Run the Azure task more than once with different `tabName` values to publish multiple report groups. For GitHub Actions, run the action multiple times with different `name` values (each gets its own artifact and sticky comment).

![](./docs/postman-report-2.png)

## Example

### Report summary on the build tab
### Report summary on the Azure DevOps build tab

![](./docs/postman-report-1.png)

## Repository name

`ci-html-viewer` is a better GitHub name now that this is not Azure-only. Rename in GitHub Settings when you are ready; clone URLs will change, but `action.yml` at the repo root already matches that identity. Keep the Azure DevOps extension id (`html-report-portal`) as-is so existing installs do not break.

## Development

```bash
npm install
npm run build
npm install --prefix tasks/UploadPortalHtmlReport
npm test
node --test github-action/*.test.js
```

The upload task runs on Node 16 and Node 20 pipeline agents. Node 10 is no longer supported.
The Azure upload task runs on Node 16 and Node 20 pipeline agents. Node 10 is no longer supported.

## Contributors

Expand Down
164 changes: 164 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
name: CI HTML Viewer
description: Publish HTML reports to the workflow summary, artifacts, and the pull request
author: Jeff Jones

branding:
icon: file-code
color: blue

inputs:
report-dir:
description: Path to a single HTML file or a directory of HTML reports
required: true
name:
description: Name used for the artifact, job summary heading, and PR comment
required: false
default: HTML Report
inline-assets:
description: Embed local CSS, JS, and images into each HTML file
required: false
default: "true"
publish-archive:
description: Include a zip of the original report directory in the artifact
required: false
default: "true"
redact-secrets:
description: Mask Bearer tokens and common secret keys in HTML
required: false
default: "false"
fail-on-empty:
description: Fail when no HTML files are found
required: false
default: "true"
fail-on-failed-reports:
description: Fail the job after publishing when a report looks unsuccessful
required: false
default: "false"
comment-on-pr:
description: Post or update a sticky comment when the workflow runs on a pull request
required: false
default: "true"
pages-preview:
description: Publish inlined HTML to GitHub Pages (gh-pages branch) and link each report in the PR comment
required: false
default: "true"
upload-artifact:
description: Upload the prepared reports as a workflow artifact
required: false
default: "true"
github-token:
description: Token used to comment on pull requests
required: false
default: ${{ github.token }}

outputs:
output-dir:
description: Directory containing prepared HTML reports
value: ${{ steps.prepare.outputs.output-dir }}
artifact-name:
description: Sanitized artifact name
value: ${{ steps.prepare.outputs.artifact-name }}
report-count:
description: Number of HTML reports published
value: ${{ steps.prepare.outputs.report-count }}
failed-count:
description: Number of reports detected as unsuccessful
value: ${{ steps.prepare.outputs.failed-count }}

runs:
using: composite
steps:
- name: Install report tooling
shell: bash
run: npm ci --omit=dev --prefix "$GITHUB_ACTION_PATH/tasks/UploadPortalHtmlReport"

- name: Prepare HTML reports
id: prepare
shell: bash
env:
INPUT_REPORT_DIR: ${{ inputs.report-dir }}
INPUT_NAME: ${{ inputs.name }}
INPUT_INLINE_ASSETS: ${{ inputs.inline-assets }}
INPUT_PUBLISH_ARCHIVE: ${{ inputs.publish-archive }}
INPUT_REDACT_SECRETS: ${{ inputs.redact-secrets }}
INPUT_FAIL_ON_EMPTY: ${{ inputs.fail-on-empty }}
INPUT_FAIL_ON_FAILED_REPORTS: ${{ inputs.fail-on-failed-reports }}
run: node "$GITHUB_ACTION_PATH/github-action/run.js"

- name: Upload report artifact
if: ${{ always() && inputs.upload-artifact == 'true' && steps.prepare.outputs.output-dir != '' }}
uses: actions/upload-artifact@v4
with:
name: ${{ steps.prepare.outputs.artifact-name }}
path: ${{ steps.prepare.outputs.output-dir }}
if-no-files-found: warn

- name: Publish HTML preview to GitHub Pages
id: preview
if: ${{ always() && inputs.pages-preview == 'true' && github.event_name == 'pull_request' && steps.prepare.outputs.output-dir != '' }}
continue-on-error: true
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ inputs.github-token }}
publish_dir: ${{ steps.prepare.outputs.output-dir }}/reports
destination_dir: pr/${{ github.event.pull_request.number }}/${{ steps.prepare.outputs.artifact-name }}
keep_files: true
enable_jekyll: false
commit_message: Preview HTML reports for PR ${{ github.event.pull_request.number }}

- name: Render PR comment
id: render
if: ${{ always() && steps.prepare.outputs.output-dir != '' }}
shell: bash
env:
OUTPUT_DIR: ${{ steps.prepare.outputs.output-dir }}
REPORT_NAME: ${{ inputs.name }}
PREVIEW_OUTCOME: ${{ steps.preview.outcome }}
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: node "$GITHUB_ACTION_PATH/github-action/render-comment.js"

- name: Comment on pull request
if: ${{ always() && inputs.comment-on-pr == 'true' && github.event_name == 'pull_request' && steps.render.outputs.comment-markdown != '' }}
uses: actions/github-script@v7
env:
COMMENT_MARKDOWN: ${{ steps.render.outputs.comment-markdown }}
REPORT_NAME: ${{ inputs.name }}
with:
github-token: ${{ inputs.github-token }}
script: |
const marker = `<!-- ci-html-viewer:${process.env.REPORT_NAME} -->`
const body = process.env.COMMENT_MARKDOWN
const pr = context.payload.pull_request
if (!body || !pr) {
return
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
})
const existing = comments.find((comment) => comment.body && comment.body.indexOf(marker) !== -1)
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body
})
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body
})
}

- name: Fail on failed reports
if: ${{ always() && inputs.fail-on-failed-reports == 'true' && steps.prepare.outputs.failed == 'true' }}
shell: bash
run: |
echo "::error::${{ steps.prepare.outputs.failed-count }} HTML report(s) contain failed tests"
exit 1
2 changes: 1 addition & 1 deletion azure-devops-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"publisher": "joneja09",
"public": false,
"author": "Jeff Jones",
"version": "1.2.0",
"version": "1.3.0",
"description": "Embed HTML reports in Azure Pipelines",
"galleryFlags": [],
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ steps:
extensionId: "html-report-portal"
extensionTag: dev
extensionName: "HTML Report Portal Dev"
extensionVersion: "1.2.$(Build.BuildId)"
extensionVersion: "1.3.$(Build.BuildId)"
updateTasksVersion: true
extensionVisibility: private
extensionPricing: free
Expand Down
Loading
Loading