# GitHub Actions

Source: https://docs.qualflare.com/docs/cli/ci-cd/github-actions · Last updated: 2026-10-27

Upload test results from GitHub Actions to Qualflare with qualflare-action or the qf CLI: workflow YAML, the QF_TOKEN secret, inputs and pull request branches.

To upload test results from GitHub Actions to Qualflare, add the `Qualflare/qualflare-action@v1` step after your tests, or install qf and run `qf <identifier> collect <report>`, with the access token stored as a repository secret named `QF_TOKEN`. [`Qualflare/qualflare-action`](https://github.com/Qualflare/qualflare-action) is a composite action that downloads the right `qf` binary for the runner and runs the same `qf login` and `qf <identifier> collect` steps: it's the CLI, not a separate upload path.

## Before you begin

* A project access token with **Allow writes** off, which you create in **Project Settings** > **Access Tokens** (requires the Editor role or higher in the project). See [Access Tokens](https://docs.qualflare.com/docs/api/access-tokens).
* A report file your test step writes, such as JUnit XML.
* The patterns every CI shares are on [CI/CD integration](https://docs.qualflare.com/docs/cli/ci-cd).

## Store the token as a secret

1. Go to your repository **Settings** > **Secrets and variables** > **Actions**
2. Click **New repository secret**
3. Name: `QF_TOKEN`
4. Value: Your Qualflare access token
5. Click **Add secret**

The action takes the secret through its `token` input, so any secret name works (its README uses `QUALFLARE_TOKEN`). The manual install reads `QF_TOKEN`.

## Upload with qualflare-action

Add the action after your test step:

```yaml
- name: Run tests
  run: pytest --junitxml=results.xml

- name: Upload results to Qualflare
  if: ${{ !cancelled() }}   # upload even when tests fail
  uses: Qualflare/qualflare-action@v1
  with:
    token: ${{ secrets.QF_TOKEN }}
    project: myapp
    results: results.xml
```

> **Warning:** Put `if: ${{ !cancelled() }}` on the upload step so it runs when your tests
> fail. Do not add `continue-on-error: true` to the test step: it marks the failed
> step as passed, so the job and the pull request check turn green even when
> tests fail.

*Figure: Because the upload step runs unless the workflow is cancelled, results reach Qualflare when the test step fails, and the job stays failed.*

## Inputs

| Input     | Required | Default  | Description                                                                                                                                                                                                                                                    |
| --------- | :------: | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`   |    Yes   | —        | Qualflare project access token (`qfp_...`). Store it as an encrypted secret, never inline.                                                                                                                                                                     |
| `project` |    Yes   | —        | A local name for the project, used by `qf` on the runner. The token decides which project receives the results. Use lowercase letters, digits, `-` and `_`, up to 63 characters, starting with a letter or digit, and not a qf command name such as `version`. |
| `results` |    Yes   | —        | Path or glob to your test result files (e.g. `test-results/*.xml`). Framework is auto-detected.                                                                                                                                                                |
| `version` |    No    | `latest` | `qualflare-cli` version to install, e.g. `0.4.0`. A leading `v` is optional.                                                                                                                                                                                   |

## Outputs

None: the action has no `outputs:` block, so no `steps.<id>.outputs.*` value (such as a report URL) is there for a later step.

## Runner support

Linux and macOS (amd64/arm64) are fully supported. Windows runners are
best-effort — if you're on Windows and hit issues,
[install qf yourself](https://docs.qualflare.com/docs/cli/ci-cd/github-actions#install-qf-yourself).

What the action runs:

* **Install Qualflare CLI (qf)** resolves `version` (`latest` by default, through the releases page), downloads `qf_<version>_<os>_<arch>` and puts it on `PATH`.
* **Upload results to Qualflare** runs `qf login <project> <token>`, then `qf <project> collect <results>`.

*Figure: qualflare-action runs two steps: it installs qf for the runner, then logs in with your token and uploads the results files.*

## What the action doesn't expose

* The action has four inputs. qf still reads its `QF_` variables inside it, so set `QF_ENVIRONMENT`, `QF_MILESTONE`, `QF_BRANCH`, `QF_COMMIT`, `QF_UPLOAD_ARTIFACTS` or `QF_PLATFORM` in the job's `env:`, as in [Branch and commit](https://docs.qualflare.com/docs/cli/ci-cd/github-actions#branch-and-commit).
* It has no input for the `collect` flags that have no variable: `--format`, `--shard`, `--allow-mixed-runs`, `--artifacts-dir`, `--dry-run` with its `--output`, and the overall [`--timeout`](https://docs.qualflare.com/docs/cli/collect#--timeout), which stays at its 30s default, short for large uploads with videos or traces.
* It passes no `--format`, so TestNG, Maestro, XCTest and Espresso XML upload as plain `junit`. It runs `qf login` without `--force`: on a self-hosted runner that keeps its home directory between jobs, the second run fails with `identifier "myapp" already exists`. It passes the token to `qf login` as a command-line argument rather than through `QF_TOKEN`.
* `results` is not quoted, so the shell expands globs and splits it on spaces: separate several patterns with a space, and don't use a path that contains one.

[Install qf yourself](https://docs.qualflare.com/docs/cli/ci-cd/github-actions#install-qf-yourself) for any case in the last three bullets.

## Install qf yourself

Install qf in your own step for Windows, a reused runner, a flag above or a token kept out of the process list:

```yaml
- name: Upload results to Qualflare
  if: ${{ !cancelled() }}  # upload even when tests fail
  env:
    QF_TOKEN: ${{ secrets.QF_TOKEN }}
    QF_BRANCH: ${{ github.head_ref || github.ref_name }}
    QF_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
  run: |
    # Install qf CLI (ubuntu-latest runners don't ship Homebrew)
    VERSION=0.4.0
    curl -L "https://github.com/qualflare/qualflare-cli/releases/download/v${VERSION}/qf_${VERSION}_linux_amd64.tar.gz" -o qf.tar.gz
    tar -xzf qf.tar.gz && sudo mv qf /usr/local/bin/

    # Upload results (qf login reads QF_TOKEN from env)
    qf login ci --force
    qf ci collect results.xml
```

On other runners, download the matching `qf_<version>_<os>_<arch>` archive (`.zip` on Windows); for sharded jobs, upload once from a final job with `--shard` ([Sharded runs](https://docs.qualflare.com/docs/cli/sharding)).

## Branch and commit

qf reads `GITHUB_REF_NAME` and `GITHUB_SHA`. On `pull_request` runs those are the `<number>/merge` branch and a merge commit, so set the pull request's own values at job level, where the action's steps see them too:

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      QF_ENVIRONMENT: staging  # must exist in the project
      QF_BRANCH: ${{ github.head_ref || github.ref_name }}
      QF_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
```

More on this: [Pull request branches](https://docs.qualflare.com/docs/cli/ci-cd#pull-request-branches).

## Version pinning

`Qualflare/qualflare-action@v1` tracks the latest `v1.x` release of the
action itself. The `version` input is separate — it pins which `qualflare-cli`
release gets installed underneath, independent of the action's own version.
Pin `version` explicitly in CI for reproducible builds; leave it on `latest`
for local experimentation.

## Troubleshooting

* `Error: token cannot be empty`: the `token` input or `QF_TOKEN` is empty in that run. GitHub gives no secrets to workflows run for pull requests from forks.
* `Error: unknown flag: --version` in &#x2A;*Install Qualflare CLI (qf)**: harmless. The action runs `qf --version`, which qf doesn't have.

More fixes: [Troubleshooting](https://docs.qualflare.com/docs/cli/ci-cd#troubleshooting) on CI/CD integration.

## Related

* **Guides:** [CI/CD integration](https://docs.qualflare.com/docs/cli/ci-cd) · [Setting up CI for an open-source project](https://docs.qualflare.com/docs/open-source/ci-setup)
* **Reference:** [Upload results with qf collect](https://docs.qualflare.com/docs/cli/collect) · [Configuration](https://docs.qualflare.com/docs/cli/configuration)
* **CLI/API:** [Access Tokens](https://docs.qualflare.com/docs/api/access-tokens)
