Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
acae649
refactor qasereporter into 2 subcommands, rename the binary, update b…
git-ival Dec 19, 2025
777acdf
new jenkins job that uses qase-k6-cli gather to automatically run all…
git-ival Dec 19, 2025
43c6545
update gitignore
git-ival Dec 19, 2025
64a21cc
add default web dashboard html report output, attempt to resolve vari…
git-ival Dec 19, 2025
0d30b4c
add a debug line in case a test case does not have AutomationTestName
git-ival Dec 19, 2025
69295d8
remove binaries
git-ival Dec 19, 2025
5a7c11f
attempts to fix copilot feedback
git-ival Dec 19, 2025
46c471b
sanitize qase project name to mitigate malicious inputs
git-ival Dec 19, 2025
d9f90e7
handle more specific status reporting for exceeded thresholds
git-ival Dec 19, 2025
31cebc9
sanitize parameters for s3 stage
git-ival Dec 19, 2025
e428cf8
store santize character set and use it
git-ival Dec 19, 2025
0e6ac83
invert character sanitization
git-ival Dec 19, 2025
73f4309
fix gather step
git-ival Dec 19, 2025
000237a
try using groovy's jsonSlurper() for parsing the json file
git-ival Dec 19, 2025
b3fb707
try using groovy's jsonSlurper() for parsing the json file
git-ival Dec 19, 2025
8c78cba
use yq from within container to parse the test cases json
git-ival Dec 19, 2025
14a1b57
try importing JsonSlurper()
git-ival Dec 19, 2025
535233d
try importing JsonSlurper()
git-ival Dec 19, 2025
07c00e6
try parsing the test cases 'directly' with yq since jenkins does not …
git-ival Dec 19, 2025
374325f
try parsing the test cases 'directly' with yq since jenkins does not …
git-ival Dec 19, 2025
6a35300
try resolving k6 run issues
git-ival Dec 19, 2025
12ff5b2
try resolving k6 run issues
git-ival Dec 19, 2025
f30389e
handle K6_ENV param
git-ival Dec 19, 2025
6a9adb8
loosen sanitizer regex
git-ival Dec 19, 2025
bbfad4f
loosen sanitizer regex
git-ival Dec 19, 2025
711fedd
loosen sanitizer regex
git-ival Dec 19, 2025
4840222
loosen sanitizer regex
git-ival Dec 19, 2025
44b44cb
loosen sanitizer regex
git-ival Dec 19, 2025
590b7c5
fix k6 env parsing
git-ival Dec 20, 2025
aeca4ed
output env content for debugging
git-ival Dec 20, 2025
41f3477
loosen sanitizer regex
git-ival Dec 20, 2025
b6adba9
fix k6 env parsing
git-ival Dec 20, 2025
88b9b5f
fix k6 env parsing
git-ival Dec 20, 2025
d894574
fix k6 env parsing
git-ival Dec 20, 2025
43af400
fix k6 report naming
git-ival Dec 20, 2025
7c4a25b
update k6 custom summary handler's prefix env var name
git-ival Dec 20, 2025
5cc17cd
fix k6 report naming
git-ival Dec 20, 2025
cd1182f
fix env var parsing
git-ival Dec 20, 2025
85e20f7
set K6_ENV last as final override
git-ival Dec 22, 2025
88ea136
introduce hacky workaround to generate both the native web dashboard …
git-ival Dec 22, 2025
5efb6e7
fix test run with report generator hack
git-ival Dec 22, 2025
44779ef
remove path default
git-ival Jan 5, 2026
3813d2b
fix potentially code injection vuln
git-ival Feb 25, 2026
22eb92d
add insecureSkipTLSVerify to remaining scripts
git-ival Feb 25, 2026
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ scripts/utils/export-metrics/*.tar.gz

# Binaries
/dartboard
/scripts/soak/soak
/qasereporter-k6/qasereporter-k6
qase-k6-cli/qase-k6-cli

# Vendored binaries
/internal/vendored/bin
/.bin

dist/
.env
2 changes: 1 addition & 1 deletion CI/k6-runner.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pipeline {
def k6EnvContent = """
BASE_URL=${baseURL}
KUBECONFIG=${kubeconfigContainerPath ? kubeconfigContainerPath : ''}
K6_TEST=${params.K6_TEST_FILE}
K6_REPORT_PREFIX=${params.K6_TEST_FILE}
K6_NO_USAGE_REPORT=true
${params.K6_ENV}
"""
Expand Down
377 changes: 377 additions & 0 deletions CI/qase-k6-runner.groovy

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ RUN go mod download && \
RUN cd $WORKSPACE && \
make && \
mv ./dartboard /usr/local/bin/dartboard && \
mv ./qasereporter-k6/qasereporter-k6 /usr/local/bin/qasereporter-k6
mv ./qase-k6-cli/qase-k6-cli /usr/local/bin/qase-k6-cli

FROM grafana/k6:${K6_VERSION}
COPY --from=builder /usr/local/bin/dartboard /bin/dartboard
COPY --from=builder /usr/local/bin/qasereporter-k6 /bin/qasereporter-k6
COPY --from=builder /usr/local/bin/qase-k6-cli /bin/qase-k6-cli

# Run the following commands as root user so that we can easily install some needed tools
USER root
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
DARTBOARD_BIN_NAME := dartboard
REPORTER_BIN_NAME := qasereporter-k6
QASE_K6_CLI_BIN_NAME := qase-k6-cli
LDFLAGS := -w -s
TOFU := ./internal/vendored/bin/tofu
TOFU_MAIN_DIRS := k3d aws azure harvester
Expand All @@ -9,21 +9,21 @@ TOFU_MAIN_DIRS := k3d aws azure harvester
# =============================================================================

.PHONY: build
build: internal/vendored/bin qasereporter-k6/${REPORTER_BIN_NAME}
build: internal/vendored/bin qase-k6-cli/${QASE_K6_CLI_BIN_NAME}
CGO_ENABLED=0 go build -ldflags '$(LDFLAGS)' -o ${DARTBOARD_BIN_NAME} cmd/dartboard/*.go

internal/vendored/bin:
sh download-vendored-bin.sh

qasereporter-k6/${REPORTER_BIN_NAME}: qasereporter-k6/*.go
CGO_ENABLED=0 go build -ldflags '$(LDFLAGS)' -o $@ qasereporter-k6/*.go
qase-k6-cli/${QASE_K6_CLI_BIN_NAME}: qase-k6-cli/*.go
CGO_ENABLED=0 go build -ldflags '$(LDFLAGS)' -o $@ qase-k6-cli/*.go

.PHONY: clean
clean:
rm -rfv .bin
rm -rfv internal/vendored/bin
rm -fv ${DARTBOARD_BIN_NAME}
rm -fv qasereporter-k6/${REPORTER_BIN_NAME}
rm -fv qase-k6-cli/${QASE_K6_CLI_BIN_NAME}

# =============================================================================
# Go module verification
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ You can find this summary in the form of a `.html` file on the build's page in J

See the [docs](docs) directory for a list of tests that were run with previous versions of this code and their results.

## Qase k6 Reporter
## Qase k6 CLI

The `qasereporter-k6` utility is a command-line tool included as a separate golang module that parses the output of a k6 test run and reports the results to a test case wtihin a Qase test run. It can be used in CI/CD pipelines to automatically update test cases in Qase with the results from k6 performance tests.
The `qase-k6-cli` utility is a command-line tool included as a separate golang module that bridges k6 and Qase. It can gather test case information and report k6 test results to Qase.

For detailed usage instructions, including environment variables and command-line flags, please see the [qasereporter-k6 README](qasereporter-k6/README.md).
For detailed usage instructions, including environment variables and command-line flags, please see the [qase-k6-cli README](qase-k6-cli/README.md).

## Common Troubleshooting

Expand Down
26 changes: 24 additions & 2 deletions internal/qase/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
const (
TestRunNameEnvVar = "QASE_TEST_RUN_NAME"
TestCaseNameEnvVar = "QASE_TEST_CASE_NAME"
TestCaseIDEnvVar = "QASE_TEST_CASE_ID"
)

// CustomUnifiedClient combines V1 and V2 clients for our specific needs.
Expand Down Expand Up @@ -112,14 +113,18 @@ func (c *CustomUnifiedClient) CreateTestRun(ctx context.Context, testRunName, pr
}

// GetTestRun retrieves a Qase test run by its ID.
func (c *CustomUnifiedClient) GetTestRun(ctx context.Context, projectCode string, runID int64) (*api_v1_client.Run, error) {
func (c *CustomUnifiedClient) GetTestRun(ctx context.Context, projectCode string, runID int64, include *string) (*api_v1_client.Run, error) {
logrus.Debugf("Getting test run with ID %d in project %s", runID, projectCode)

authCtx := context.WithValue(ctx, api_v1_client.ContextAPIKeys, map[string]api_v1_client.APIKey{
"TokenAuth": {Key: c.Config.TestOps.API.Token},
})

resp, res, err := c.V1Client.GetAPIClient().RunsAPI.GetRun(authCtx, projectCode, int32(runID)).Execute()
apiRunRequest := c.V1Client.GetAPIClient().RunsAPI.GetRun(authCtx, projectCode, int32(runID))
if include != nil {
apiRunRequest = apiRunRequest.Include(*include)
}
resp, res, err := apiRunRequest.Execute()
logResponseBody(res, "GetTestRun")
if err != nil {
return nil, fmt.Errorf("failed to get test run: %w", err)
Expand Down Expand Up @@ -232,6 +237,23 @@ func (c *CustomUnifiedClient) GetTestCaseByTitle(ctx context.Context, projectCod
return matchingCase, nil
}

// GetCustomFields retrieves all custom fields.
func (c *CustomUnifiedClient) GetCustomFields(ctx context.Context) (*api_v1_client.CustomFieldListResponse, error) {
logrus.Debug("Getting custom fields")

authCtx := context.WithValue(ctx, api_v1_client.ContextAPIKeys, map[string]api_v1_client.APIKey{
"TokenAuth": {Key: c.Config.TestOps.API.Token},
})

resp, res, err := c.V1Client.GetAPIClient().CustomFieldsAPI.GetCustomFields(authCtx).Execute()
logResponseBody(res, "GetCustomFields")
if err != nil {
return nil, fmt.Errorf("failed to get custom fields: %w", err)
}

return resp, nil
}

// CreateTestResultV1 creates a test result using the V1 API.
func (c *CustomUnifiedClient) CreateTestResultV1(ctx context.Context, projectCode string, runID int64, result api_v1_client.ResultCreate) error {
authCtx := context.WithValue(ctx, api_v1_client.ContextAPIKeys, map[string]api_v1_client.APIKey{
Expand Down
13 changes: 7 additions & 6 deletions internal/qase/status.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package qase

const (
StatusPassed = "passed"
StatusFailed = "failed"
StatusBlocked = "blocked"
StatusSkipped = "skipped"
StatusInvalid = "invalid"
StatusError = "error"
StatusPassed = "passed"
StatusFailed = "failed"
StatusBlocked = "blocked"
StatusSkipped = "skipped"
StatusInvalid = "invalid"
StatusError = "error"
StatusExceededThresholds = "exceeded-thresholds"
)
1 change: 1 addition & 0 deletions k6/crds/create_crds.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const timePolled = new Trend('time_polled', true);
export const handleSummary = k6Util.customHandleSummary;

export const options = {
insecureSkipTLSVerify: true,
scenarios: {
create: {
executor: 'shared-iterations',
Expand Down
1 change: 1 addition & 0 deletions k6/crds/delete_crds.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const timePolled = new Trend('time_polled', true);
export const handleSummary = k6Util.customHandleSummary;

export const options = {
insecureSkipTLSVerify: true,
scenarios: {
delete: {
executor: 'shared-iterations',
Expand Down
1 change: 1 addition & 0 deletions k6/crds/get_crds.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const handleSummary = k6Util.customHandleSummary;

// Option setting
export const options = {
insecureSkipTLSVerify: true,
setupTimeout: '8h',
scenarios: {
get: {
Expand Down
1 change: 1 addition & 0 deletions k6/crds/load_crds.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const timePolled = new Trend('time_polled', true);
export const handleSummary = k6Util.customHandleSummary;

export const options = {
insecureSkipTLSVerify: true,
scenarios: {
load: {
executor: 'shared-iterations',
Expand Down
1 change: 1 addition & 0 deletions k6/crds/update_crds.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const timePolled = new Trend('time_polled', true);
export const handleSummary = k6Util.customHandleSummary;

export const options = {
insecureSkipTLSVerify: true,
scenarios: {
update: {
executor: 'shared-iterations',
Expand Down
1 change: 1 addition & 0 deletions k6/crds/update_destructive_crds.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const timePolled = new Trend('time_polled', true);
export const handleSummary = k6Util.customHandleSummary;

export const options = {
insecureSkipTLSVerify: true,
scenarios: {
destructiveUpdate: {
executor: 'shared-iterations',
Expand Down
4 changes: 2 additions & 2 deletions k6/generic/k6_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function createReports(prefix, data) {
* A pre-configured `handleSummary` function that generates multiple reports.
*
* This function is a convenient wrapper around `createReports`. It automatically
* determines the report filename prefix from the `K6_TEST` environment variable.
* determines the report filename prefix from the `K6_REPORT_PREFIX` environment variable.
*
* To use this, import it then export it as `handleSummary`:
* ```javascript
Expand All @@ -158,6 +158,6 @@ export function createReports(prefix, data) {
* @returns {object} An object mapping filenames to report content, for k6 to write to disk.
*/
export function customHandleSummary(data) {
const prefix = __ENV.K6_TEST ? __ENV.K6_TEST.replace(/\.js$/, '') + "-" : '';
const prefix = __ENV.K6_REPORT_PREFIX ? __ENV.K6_REPORT_PREFIX.replace(/\.js$/, '') + "-" : '';
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

customHandleSummary now only looks at K6_REPORT_PREFIX. Existing CI (e.g., CI/k6-runner.groovy) sets K6_TEST and expects reports like "-summary.json"; after this change, the prefix will be empty unless K6_REPORT_PREFIX is set, and CI artifact collection/reporting will likely break. Consider falling back to K6_TEST when K6_REPORT_PREFIX is unset (or update all callers to set K6_REPORT_PREFIX).

Suggested change
const prefix = __ENV.K6_REPORT_PREFIX ? __ENV.K6_REPORT_PREFIX.replace(/\.js$/, '') + "-" : '';
const prefixSource = __ENV.K6_REPORT_PREFIX
? __ENV.K6_REPORT_PREFIX
: (__ENV.K6_TEST ? __ENV.K6_TEST : '');
const prefix = prefixSource ? prefixSource.replace(/\.js$/, '') + "-" : '';

Copilot uses AI. Check for mistakes.
return createReports(getPathBasename(prefix), data)
}
23 changes: 23 additions & 0 deletions k6/generic/report_generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { customHandleSummary } from './k6_utils.js';

const summaryPath = __ENV.K6_SUMMARY_JSON_FILE;

// Load summary data during initialization
let data = null;
if (summaryPath) {
try {
data = JSON.parse(open(summaryPath));
} catch (e) {
console.error(`Failed to parse summary JSON from ${summaryPath}: ${e.message}`);
}
}

export function handleSummary() {
if (!data) {
console.log("No summary data available to generate reports.");
return {};
}
return customHandleSummary(data);
}

export default function() {}
1 change: 1 addition & 0 deletions k6/schemas/verify_schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const timePolled = new Trend('time_polled', true);
export const handleSummary = k6Util.customHandleSummary;

export const options = {
insecureSkipTLSVerify: true,
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'count'],
scenarios: {
verify: {
Expand Down
2 changes: 1 addition & 1 deletion k6/tests/load_projects_rbac.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ let createdNamespaceIds = []
export const handleSummary = k6Util.customHandleSummary;

export const options = {
insecureSkipTLSVerify: true,
scenarios: {
load: {
executor: 'per-vu-iterations',
Expand Down Expand Up @@ -128,7 +129,6 @@ export const options = {
},
setupTimeout: '30m',
teardownTimeout: '30m',
insecureSkipTLSVerify: true,
// httpDebug: 'full',
}

Expand Down
33 changes: 24 additions & 9 deletions qasereporter-k6/README.md → qase-k6-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# k6 Qase Reporter
# Qase k6 CLI

`qasereporter-k6` is a command-line tool that parses the output of a k6 test run and reports the results to a Qase test run.
`qase-k6-cli` is a command-line tool that integrates k6 with Qase. It provides functionality to gather test case information from Qase and report k6 test results back to Qase.

It supports two modes of operation for parsing k6 results:

Expand All @@ -10,17 +10,21 @@ It supports two modes of operation for parsing k6 results:

## Build

You can build the reporter using the `make` target from the root of the `dartboard` repository:
You can build the cli using the `make` target from the root of the `dartboard` repository:

```shell
make qasereporter-k6
make qase-k6-cli
```

This will produce a `qasereporter-k6` binary inside the `qasereporter-k6/` directory.
This will produce a `qase-k6-cli` binary inside the `qase-k6-cli/` directory.

## Usage

The reporter is configured via environment variables and command-line flags.
The tool is configured via environment variables and command-line flags and has two subcommands: `report` and `gather`.

### Report

Parses k6 metrics/summary and reports the result to Qase.

```shell
# Example for Summary Mode
Expand All @@ -32,7 +36,7 @@ export QASE_TEST_CASE_NAME="My test case"
export K6_SUMMARY_JSON_FILE="/path/to/summary.json"
export K6_SUMMARY_HTML_FILE="/path/to/report.html" # Optional

./qasereporter-k6
./qase-k6-cli report

# Example for Granular Mode
export QASE_TESTOPS_API_TOKEN=TOKEN
Expand All @@ -42,7 +46,18 @@ export QASE_TEST_CASE_NAME="My k6 Test"
export K6_OUTPUT_FILE="/path/to/k6-metrics-output.json"
export K6_SUMMARY_HTML_FILE="/path/to/report.html" # Optional

./qasereporter-k6 -granular
./qase-k6-cli report -granular
```

### Gather

Retrieves test cases from a Qase test run and outputs the 'AutomationTestName' custom field value for each case as JSON.

```shell
export QASE_TESTOPS_API_TOKEN=TOKEN
export QASE_TESTOPS_PROJECT=PRJ

./qase-k6-cli gather -runID 42
```

### Environment Variables
Expand All @@ -57,7 +72,7 @@ export K6_SUMMARY_HTML_FILE="/path/to/report.html" # Optional
| `K6_SUMMARY_JSON_FILE` | Path to the k6 summary JSON file. (Used in **Summary Mode**). | For Summary Mode |
| `K6_SUMMARY_HTML_FILE` | (Optional) Path to the k6 HTML report file to be attached to the Qase result. (Used in **Summary Mode**). | No |
| `K6_OUTPUT_FILE` | Path to the k6 raw JSON output file. (Used in **Granular Mode**). | For Granular Mode |
| `QASE_DEBUG` | A string ("true" or "false") that enables or disables debug logs. | No |
| `QASE_DEBUG` | A string ("true" or "false") that enables or disables debug logs. | No |

### Command-line Flags

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#!/usr/bin/env bash
set -e

# Path to the Go source code for the k6 reporter
REPORTER_SRC_DIR="$(dirname "$0")"
# Path to the Go source code for the qase-k6-cli
QASE_K6_CLI_SRC_DIR="$(dirname "$0")"

# Output binary path
OUTPUT_BINARY="${REPORTER_SRC_DIR}/reporter-k6"
OUTPUT_BINARY="${QASE_K6_CLI_SRC_DIR}/qase-k6-cli"

echo "Building k6 Qase reporter..."
echo "Source directory: ${REPORTER_SRC_DIR}"
echo "Building qase-k6-cli..."
echo "Source directory: ${QASE_K6_CLI_SRC_DIR}"
echo "Output binary: ${OUTPUT_BINARY}"

# Ensure we are in the correct directory to resolve modules
cd "${REPORTER_SRC_DIR}"
cd "${QASE_K6_CLI_SRC_DIR}"

# Tidy and build the Go application
go mod tidy
Expand Down
Loading