Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9d24c56
Add public API commands
ramilamparo Mar 18, 2026
d7e3777
Move SKILL.md
ramilamparo Mar 23, 2026
f9229ed
Update @filename description
ramilamparo Mar 23, 2026
f2e1c68
Fixes
ramilamparo Mar 23, 2026
20606a8
Prevent accidental leaks
ramilamparo Mar 23, 2026
b89fb5d
Run npm audit fix
ramilamparo Mar 23, 2026
dc1a565
Better error message
ramilamparo Mar 23, 2026
446f132
Tweaks
ramilamparo Mar 23, 2026
4f7ebad
Fix live tests
ramilamparo Mar 23, 2026
3d4d125
Indicate HTML support on fields
ramilamparo Mar 23, 2026
58bf9ab
Fix uri encoding
ramilamparo Mar 23, 2026
714739b
Validate path parameters
ramilamparo Mar 24, 2026
b7784ff
Simplify test
ramilamparo Mar 24, 2026
a452c1b
Rename APIs
ramilamparo Mar 24, 2026
a53edf5
Add two-layer API validation and custom fields support
ramilamparo Mar 24, 2026
55ffc68
Use ZodError.message as the error message
ramilamparo Mar 24, 2026
8f54da7
Fix schemas
ramilamparo Mar 25, 2026
348652b
Remove schemas.ts and update claude.md
ramilamparo Mar 25, 2026
da666c1
Simplify SKILL.md
ramilamparo Mar 27, 2026
6250aee
Create specific validtors for different types of identifiers
ramilamparo Mar 27, 2026
6c2dc8d
Fix step schema
ramilamparo Mar 27, 2026
80953b8
Fix types and --include argument
ramilamparo Mar 27, 2026
953e320
Address feedback
ramilamparo Mar 27, 2026
53c652c
Fix mock test
ramilamparo Mar 27, 2026
16015b8
More fixes
ramilamparo Mar 27, 2026
0a9e4ab
Fix name
ramilamparo Mar 27, 2026
c4b1240
Fix table of contents
ramilamparo Mar 27, 2026
b2f7a1b
Rewrite to manifest based pattern
ramilamparo Mar 30, 2026
9c50097
Update md files
ramilamparo Mar 30, 2026
9909056
Fixes
ramilamparo Mar 31, 2026
7125b63
Fixes
ramilamparo Mar 31, 2026
8b1ecf8
Fix common help text usage
ramilamparo Mar 31, 2026
9ef833d
Fix error code on unknown arguments, fix live test arguments
ramilamparo Mar 31, 2026
133de6e
Fix spy restore
ramilamparo Mar 31, 2026
3b43e75
Fixes
ramilamparo Mar 31, 2026
2281c21
Improve SKILL.md
ramilamparo Mar 31, 2026
2fa1e06
Align file upload docs and tests with batch endpoint
satvik007 Apr 10, 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
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ jobs:

- name: Multi-node version test
run: cd mnode-test && ./docker-test.sh

- name: Run live API tests
env:
QAS_TEST_URL: ${{ secrets.QAS_TEST_URL }}
QAS_TEST_TOKEN: ${{ secrets.QAS_TEST_TOKEN }}
QAS_TEST_USERNAME: ${{ secrets.QAS_TEST_USERNAME }}
QAS_TEST_PASSWORD: ${{ secrets.QAS_TEST_PASSWORD }}
QAS_DEV_AUTH: ${{ secrets.QAS_DEV_AUTH }}
run: npm run test:live
Comment on lines +60 to +67
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Live tests are added that allows running automated tests with a real live qasphere instance. QAS_TEST_USERNAME and QAS_TEST_PASSWORD are required because we don't have a public API yet for deleting projects, which is required for clean-ups after the tests. So we use login credentials and call a non-public API instead.

85 changes: 79 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Node.js compatibility tests: `cd mnode-test && ./docker-test.sh` (requires Docke
### Entry Point & CLI Framework

- `src/bin/qasphere.ts` — Entry point (`#!/usr/bin/env node`). Validates Node version, delegates to `run()`.
- `src/commands/main.ts` — Yargs setup. Registers three commands (`junit-upload`, `playwright-json-upload`, `allure-upload`) as instances of the same `ResultUploadCommandModule` class.
- `src/commands/main.ts` — Yargs setup. Registers three upload commands (`junit-upload`, `playwright-json-upload`, `allure-upload`) as instances of `ResultUploadCommandModule`, plus the `api` command.
- `src/commands/resultUpload.ts` — `ResultUploadCommandModule` defines CLI options shared by both commands. Loads env vars, then delegates to `ResultUploadCommandHandler`.

### Core Upload Pipeline (src/utils/result-upload/)
Expand Down Expand Up @@ -62,24 +62,52 @@ The upload flow has two stages handled by two classes, with a shared `MarkerPars
- `allureParser.ts` — Parses Allure JSON results directories (`*-result.json` and `*-container.json` files; XML/images ignored). Supports test case linking via TMS links (`type: "tms"`) or marker in test name, maps Allure statuses to QA Sphere result statuses (`unknown→open`, `broken→blocked`), strips ANSI codes and HTML-escapes messages, and resolves attachments via `attachments[].source`. Uses `formatMarker()` from `MarkerParser`. Extracts run-level failure logs from container files by checking `befores`/`afters` fixtures with `failed`/`broken` status — primarily useful for pytest (allure-junit5 and allure-playwright leave container fixtures empty).
- `types.ts` — Shared `TestCaseResult`, `ParseResult`, and `Attachment` interfaces used by both parsers.

### API Command (src/commands/api/)

The `api` command provides direct programmatic access to the QA Sphere public API: `qasphere api <resource> <action> [options]`. Each resource (e.g., `projects`, `runs`, `test-cases`) is a subcommand with its own actions. Some resources have nested subgroups (e.g., `qasphere api runs test-cases list`).

**Architecture**: The API command uses a manifest-based pattern. Each resource defines its endpoints as declarative `ApiEndpointSpec` objects (in `src/commands/api/manifests/`), and shared infrastructure handles yargs registration, validation, and execution.

**Key files**:

- `types.ts` — Defines `ApiEndpointSpec` as a discriminated union on `bodyMode` (`'none'` | `'json'` | `'file'`), plus supporting types (`ApiPathParamSpec`, `ApiFieldSpec`, `ApiQueryOptionSpec`, `ExecuteFn`)
- `manifests/` — One file per resource (e.g., `runs.ts`, `test-cases.ts`), each exporting an array of `ApiEndpointSpec` objects. `manifests/index.ts` aggregates all specs into a single `allSpecs` array. `manifests/utils.ts` has shared param definitions (e.g., `projectCodeParam`)
- `builder.ts` — `buildCommandsFromSpecs()` builds a yargs command tree from the flat specs array, handling nested command paths automatically. Creates yargs options from path params, query options, field options, and body mode
- `executor.ts` — `executeCommand()` orchestrates execution: collects and validates path params → processes body (based on `bodyMode` discriminant) → collects and validates query options → connects to API (lazy env loading) → executes with error mapping
- `main.ts` — Registers the `api` command, delegates to `buildCommandsFromSpecs()`
- `utils.ts` — Shared helpers: `printJson()`, `apiDocsEpilog()`, `formatApiError()`, `ArgumentValidationError`, `kebabToCamelCase()`, body parsing (`parseBodyInput`, `mergeBodyWithFields`), validation (`validateFieldValues`, `validateOptionValues`), collection helpers (`collectFieldValues`, `collectPathParamValues`, `collectQueryValues`), and `handleApiValidationError()` for reformatting API errors into CLI argument names

Important note: Online documentation is available at https://docs.qasphere.com. Most leaf pages have a markdown version available by appending `.md` in the URL. Use the markdown version before falling back to the original URL if the markdown version returns >= 400 status.

**Key design patterns**:

- **Manifest-based declarations**: Each endpoint is a plain object (`ApiEndpointSpec`) declaring its command path, params, options, body mode, and execute function. The builder and executor handle all yargs wiring, validation, and error handling generically
- **Lazy env loading**: `QAS_URL`/`QAS_TOKEN` are loaded only when the API is actually called (inside `executeCommand()`), so CLI validation errors are reported first
- **Validation flow**: Path params and query options are validated against optional Zod schemas. For `json` body mode, individual field values are validated (with JSON parsing for complex fields), then merged with `--body`/`--body-file` input. API-level `RequestValidationError` is caught and reformatted into CLI argument names (e.g., `--query-plans: [0].tcaseIds: not allowed for "live" runs`)

### API Layer (src/api/)

Composable fetch wrappers using higher-order functions:

- `utils.ts` — `withBaseUrl`, `withApiKey`, `withJson` decorators that wrap `fetch`
- `index.ts` — `createApi(baseUrl, apiKey)` assembles the API client from sub-modules
- Sub-modules: `projects.ts`, `run.ts`, `tcases.ts`, `file.ts`
- `utils.ts` — `withBaseUrl`, `withApiKey`, `withJson`, `withDevAuth` decorators that wrap `fetch`; `jsonResponse<T>()` for parsing responses; `appendSearchParams()` for building query strings; `resourceIdSchema` for validating resource identifiers; `printJson()` for formatted JSON output
- `index.ts` — `createApi(baseUrl, apiKey)` assembles the API client from all sub-modules
- `schemas.ts` — Shared types (`ResourceId`, `ResultStatus`, `PaginatedResponse<T>`, `PaginatedRequest`, `MessageResponse`), `RequestValidationError` class, `validateRequest()` helper, and common Zod field definitions (`sortFieldParam`, `sortOrderParam`, `pageParam`, `limitParam`)
- One sub-module per resource (e.g., `projects.ts`, `runs.ts`, `tcases.ts`, `folders.ts`), each exporting a `create<Resource>Api(fetcher)` factory function. Each module defines Zod schemas for its request types (PascalCase, e.g., `CreateRunRequestSchema`), derives TypeScript types via `z.infer`, and validates inputs with `validateRequest()` inside API functions

The main `createApi()` composes the fetch chain: `withDevAuth(withApiKey(withBaseUrl(fetch, baseUrl), apiKey))`.

### Configuration (src/utils/)

- `env.ts` — Loads `QAS_TOKEN` and `QAS_URL` from environment variables, `.env`, or `.qaspherecli` (searched up the directory tree)
- `env.ts` — Loads `QAS_TOKEN` and `QAS_URL` from environment variables, `.env`, or `.qaspherecli` (searched up the directory tree). Optional `QAS_DEV_AUTH` adds a dev cookie via the `withDevAuth` fetch decorator
- `config.ts` — Constants (required Node version)
- `misc.ts` — URL parsing, template string processing (`{env:VAR}`, date placeholders), error handling utilities. Note: marker-related functions have been moved to `MarkerParser.ts`
- `version.ts` — Reads version from `package.json` by traversing parent directories

## Testing

Tests use **Vitest** with **MSW** (Mock Service Worker) for API mocking. Test files are in `src/tests/`:
Tests use **Vitest** with **MSW** (Mock Service Worker) for API mocking. Test files are in `src/tests/`.

### Upload Command Tests

- `result-upload.spec.ts` — Integration tests for the full upload flow (JUnit, Playwright, and Allure), with MSW intercepting all API calls. Includes hyphenless and CamelCase marker tests (JUnit only)
- `marker-parser.spec.ts` — Unit tests for `MarkerParser` (detection, extraction, matching across all marker formats and command types)
Expand All @@ -90,6 +118,51 @@ Tests use **Vitest** with **MSW** (Mock Service Worker) for API mocking. Test fi

Test fixtures live in `src/tests/fixtures/` (XML files, JSON files, and mock test case data).

### API Command Tests (src/tests/api/)

Tests for the `api` command are organized by resource under `src/tests/api/`, with one spec file per action (e.g., `projects/list.spec.ts`, `runs/create.spec.ts`). Tests support both mocked and live modes.

**Shared infrastructure** (`src/tests/api/test-helper.ts`):

- `baseURL`, `token` — Configured base URL and token (mocked values or real from env vars)
- `useMockServer(...handlers)` — Sets up MSW server with lifecycle hooks (before/after each test)
- `runCli(...args)` — Invokes the CLI programmatically via `run(args)`, captures and parses JSON output. Useful only if the command prints JSON.
- `test` fixture — Extended Vitest `test` that provides a `project` fixture (mock project in mocked mode, real project with cleanup in live mode)
- `expectValidationError(runner, pattern)` — Asserts a command exits with a validation error matching the given regex
- Helper functions for live tests: `createFolder()`, `createTCase()`, `createRun()`

**Global setup** (`src/tests/global-setup.ts`): Authenticates against the live API (if env vars are set) and provides a session token for test project cleanup.

**Test pattern**: Each spec file typically contains:

1. A `describe('mocked', ...)` block with MSW handlers and assertions on request headers/params
2. Validation error tests checking CLI argument validation
3. Live tests tagged with `{ tags: ['live'] }` that run against a real QA Sphere instance

**Other tests**:

- `missing-subcommand-help.spec.ts` — Verifies incomplete commands (e.g., `api` alone, `api projects` alone) show help text
- `api/utils.spec.ts` — Unit tests for API command utility functions

### Running Tests

```bash
npm run test # Run all tests (mocked only by default)
npm run test:live # Run live tests only (requires env vars)
```

### Environment Variables for Live API Tests

Live tests require all four variables to be set; otherwise tests run in mocked mode only:

| Variable | Purpose |
| ------------------- | ----------------------------------------------------- |
| `QAS_TEST_URL` | Base URL of the QA Sphere instance |
| `QAS_TEST_TOKEN` | API token for authenticated API calls |
| `QAS_TEST_USERNAME` | Email for login endpoint (used by global setup) |
| `QAS_TEST_PASSWORD` | Password for login endpoint (used by global setup) |
| `QAS_DEV_AUTH` | (Optional) Dev auth cookie value for dev environments |

The `tsconfig.json` excludes `src/tests` from compilation output.

## Build
Expand Down
115 changes: 115 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@
[![license](https://img.shields.io/npm/l/qas-cli)](https://github.com/Hypersequent/qas-cli/blob/main/LICENSE)
[![CI](https://github.com/Hypersequent/qas-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/Hypersequent/qas-cli/actions/workflows/ci.yml)

## Table of Contents

- [Description](#description)
- [Installation](#installation)
- [Requirements](#requirements)
- [Via NPX](#via-npx)
- [Via NPM](#via-npm)
- [Shell Completion](#shell-completion)
- [Environment](#environment)
- [Command: `api`](#command-api)
- [API Command Tree](#api-command-tree)
- [Commands: `junit-upload`, `playwright-json-upload`, `allure-upload`](#commands-junit-upload-playwright-json-upload-allure-upload)
- [Options](#options)
- [Run Name Template Placeholders](#run-name-template-placeholders)
- [Usage Examples](#usage-examples)
- [Test Report Requirements](#test-report-requirements)
- [JUnit XML](#junit-xml)
- [Playwright JSON](#playwright-json)
- [Allure](#allure)
- [Development](#development-for-those-who-want-to-contribute-to-the-tool)

## Description

The QAS CLI is a command-line tool for submitting your test automation results to [QA Sphere](https://qasphere.com/). It provides the most efficient way to collect and report test results from your test automation workflow, CI/CD pipeline, and build servers.
Expand Down Expand Up @@ -34,6 +55,24 @@ Verify installation: `qasphere --version`

**Update:** Run `npm update -g qas-cli` to get the latest version.

## Shell Completion

The CLI supports shell completion for commands and options. To enable it, append the completion script to your shell profile:

**Zsh:**

```bash
qasphere completion >> ~/.zshrc
```

**Bash:**

```bash
qasphere completion >> ~/.bashrc
```

Then restart your shell or source the profile (e.g., `source ~/.zshrc`). After that, pressing `Tab` will autocomplete commands and options.

## Environment

The CLI requires the following variables to be defined:
Expand All @@ -59,6 +98,72 @@ QAS_URL=https://qas.eu1.qasphere.com
# QAS_URL=https://qas.eu1.qasphere.com
```

## Command: `api`

The `api` command provides direct access to the QA Sphere public API from the command line. Outputting JSON to stdout for easy scripting and piping.

### API Command Tree

```
qasphere api <resource> <action> [options]
```

```
qasphere api
├── audit-logs
│ └── list # List audit log entries
├── custom-fields
│ └── list --project-code # List custom fields
├── files
│ └── upload --file # Upload a file attachment
├── folders
│ ├── list --project-code # List folders
│ └── bulk-create --project-code --folders # Create/update folders
├── milestones
│ ├── list --project-code # List milestones
│ └── create --project-code --title # Create milestone
├── projects
│ ├── list # List all projects
│ ├── get --project-code # Get project by code
│ └── create --code --title # Create project
├── requirements
│ └── list --project-code # List requirements
├── results
│ ├── create --project-code --run-id --tcase-id --status # Create result
│ └── batch-create --project-code --run-id --items # Batch create results
├── runs
│ ├── create --project-code --title --type --query-plans # Create run
│ ├── list --project-code # List runs
│ ├── clone --project-code --run-id --title # Clone run
│ ├── close --project-code --run-id # Close run
│ └── test-cases
│ ├── list --project-code --run-id # List test cases in run
│ └── get --project-code --run-id --tcase-id # Get test case in run
├── settings
│ ├── list-statuses # List result statuses
│ └── update-statuses --statuses # Update custom statuses
├── shared-preconditions
│ ├── list --project-code # List shared preconditions
│ └── get --project-code --id # Get shared precondition
├── shared-steps
│ ├── list --project-code # List shared steps
│ └── get --project-code --id # Get shared step
├── tags
│ └── list --project-code # List tags
├── test-cases
│ ├── list --project-code # List test cases
│ ├── get --project-code --tcase-id # Get test case
│ ├── count --project-code # Count test cases
│ ├── create --project-code --body # Create test case
│ └── update --project-code --tcase-id --body # Update test case
├── test-plans
│ └── create --project-code --body # Create test plan
└── users
└── list # List all users
```

Note: `qasphere api files upload --file ...` uses the public batch upload endpoint internally and returns the first uploaded file from that response.

## Commands: `junit-upload`, `playwright-json-upload`, `allure-upload`

The `junit-upload`, `playwright-json-upload`, and `allure-upload` commands upload test results to QA Sphere.
Expand Down Expand Up @@ -272,6 +377,16 @@ The CLI automatically detects global or suite-level failures and uploads them as
- **Playwright JSON**: Top-level `errors` array entries (global setup/teardown failures) are extracted as run-level logs.
- **Allure**: Failed or broken `befores`/`afters` fixtures in `*-container.json` files (e.g., session/module-level setup/teardown failures from pytest) are extracted as run-level logs.

## AI Agent Skill

qas-cli includes a [SKILL.md](./SKILL.md) file that enables AI coding agents (e.g., Claude Code, Cursor) to use the CLI effectively. To add this skill to your agent:

```bash
npx skills add Hypersequent/qas-cli
```

The skill provides the agent with full documentation of the CLI commands, options, and conventions. See [skills](https://github.com/vercel-labs/skills) for more details.

## Development (for those who want to contribute to the tool)

1. Install and build: `npm install && npm run build && npm link`
Expand Down
Loading
Loading