Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
60 changes: 60 additions & 0 deletions .cursor/rules/sdk/tests-qvac.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
description: E2E test suite policy for packages/sdk β€” impact checks on SDK changes, executor placement, smoke-suite policy, rebuild-on-change
globs: packages/sdk/**
alwaysApply: false
---

# SDK e2e test suite (`packages/sdk/tests-qvac`)

## When editing SDK source (`packages/sdk/**` outside `tests-qvac/`)

- **Evaluate e2e impact before finishing.** Check whether existing definitions or executors under `packages/sdk/tests-qvac/tests/` need updates: new params, changed expectations, new `testId`s, suite tagging. Call it out in the PR description.
- **On SDK API surface or model constant changes:** rebuild the tests package to catch type breaks and import failures early.

```bash
cd packages/sdk/tests-qvac && npm run install:build
```

Adapt existing tests or add new ones as needed.

## Adding new e2e tests

### Definitions

- Live in `packages/sdk/tests-qvac/tests/<feature>-tests.ts`.
- MUST be aggregated in `packages/sdk/tests-qvac/tests/test-definitions.ts`.

### Executor placement decision tree

1. Pure SDK API usage (no Node stdlib, no RN APIs) β†’ `tests/shared/executors/`. Runs on desktop and mobile.
2. Needs `node:fs`, `node:path`, `process.cwd()`, or other Node stdlib β†’ `tests/desktop/executors/`.
3. Needs React Native platform APIs or bundled mobile assets β†’ `tests/mobile/executors/`.

**Never import `node:*` modules from `tests/shared/` or `tests/mobile/`** β€” it will break the mobile/Bare bundler.

Register new executors in the relevant consumer entry: `tests/desktop/consumer.ts` and/or `tests/mobile/consumer.ts`.

### Mobile platform skips

`SkipExecutor` entries at the top of `tests/mobile/consumer.ts`, before real executors (first match wins).

### Assets

New assets go under `tests-qvac/assets/`. Update `tests-qvac/qvac-test.config.js` `consumers.mobile.assets.patterns` if the new files aren't covered by existing globs.

## Smoke suite policy

- Only tag a new test with `suites: ["smoke"]` when the feature has **no existing smoke coverage**.
- Cap at **1-2** smoke tests per feature. Pick the most representative, fastest, least-flaky one.
- Before tagging, verify the test is stable β€” predictable pass, not flaky, reasonably performant β€” on **both desktop and mobile**.
- Do not inflate smoke for features already covered. Smoke must stay focused and fast.

## Local iteration before pushing

```bash
cd packages/sdk/tests-qvac
npm run install:build
npx qvac-test run:local:desktop --filter <prefix>
```

See [tests-qvac/README.md](../../../packages/sdk/tests-qvac/README.md) for full local-run instructions and CI trigger labels.
5 changes: 5 additions & 0 deletions .github/workflows/test-android-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ permissions:

jobs:
build:
name: "[android] build"
runs-on: ai-run-linux
environment: release
timeout-minutes: 30
Expand Down Expand Up @@ -239,6 +240,7 @@ jobs:
retention-days: 7

device-farm:
name: "[android] device-farm"
needs: build
runs-on: ubuntu-latest
environment: release
Expand Down Expand Up @@ -416,6 +418,7 @@ jobs:
retention-days: 1

run-producer:
name: "[android] run-producer"
needs: build
runs-on: ubuntu-latest
environment: release
Expand Down Expand Up @@ -569,6 +572,7 @@ jobs:
overwrite: true

cleanup-device-farm:
name: "[android] cleanup-device-farm"
needs: [build, run-producer, device-farm]
if: always()
runs-on: ubuntu-latest
Expand Down Expand Up @@ -654,6 +658,7 @@ jobs:
retention-days: 30

compare-results:
name: "[android] compare-results"
needs: [build, run-producer]
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test-desktop-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ permissions:

jobs:
test-desktop:
name: "[desktop] test (${{ matrix.os }})"
strategy:
matrix:
os: ${{ fromJSON(inputs.platforms) }}
Expand Down Expand Up @@ -435,6 +436,7 @@ jobs:
}

compare-results:
name: "[desktop] compare-results"
needs: test-desktop
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test-ios-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ permissions:

jobs:
build:
name: "[ios] build"
runs-on: macos-14
environment: release
timeout-minutes: 60
Expand Down Expand Up @@ -348,6 +349,7 @@ jobs:
retention-days: 7

device-farm:
name: "[ios] device-farm"
needs: build
runs-on: ubuntu-latest
environment: release
Expand Down Expand Up @@ -532,6 +534,7 @@ jobs:
retention-days: 1

run-producer:
name: "[ios] run-producer"
needs: build
runs-on: ubuntu-latest
environment: release
Expand Down Expand Up @@ -685,6 +688,7 @@ jobs:
overwrite: true

cleanup-device-farm:
name: "[ios] cleanup-device-farm"
needs: [build, run-producer, device-farm]
if: always()
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/tests-qvac/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
build/
.qvac-cache/
qvac.config.json
.env
.env.bak-*
rag-hyperdb/
201 changes: 104 additions & 97 deletions packages/sdk/tests-qvac/README.md
Original file line number Diff line number Diff line change
@@ -1,105 +1,112 @@
# SDK Tests

SDK dogfooding tests built on top of `@qvac/test-suite`.
SDK dogfooding tests built on [`@tetherto/qvac-test-suite`](https://github.com/tetherto/qvac-test-suite).
A producer orchestrates a shared queue of tests over MQTT; a consumer runs them on desktop (Node) or mobile
(Bare + React Native).

## Layout

- `tests/test-definitions.ts` - full SDK test catalog
- `tests/desktop/consumer.ts` - desktop consumer with broad handler coverage
- `tests/mobile/consumer.ts` - mobile consumer entry point
- `qvac-test.config.js` - producer and consumer framework config
- `metro.config.js` - SDK-specific Metro config for mobile consumers

## Install

From `packages/sdk/tests-qvac`:

```bash
npm i
npm run build
```

## Local iOS Run

This flow builds the generated mobile consumer app, installs it on a physical iPhone, then runs the producer locally.

### Prerequisites

- Xcode installed
- iPhone connected and trusted by Xcode
- Apple signing configured for the generated app
- `ios-deploy` installed for CLI install:

```bash
brew install ios-deploy
```

### 1. Export MQTT settings

Use values reachable from the iPhone on the same local network. In most local runs, `MQTT_HOST` should be the IP of the machine running the producer and broker.

```bash
export MQTT_PROTOCOL=ws
export MQTT_HOST=<broker-ip>
export MQTT_PORT=8080
export MQTT_PATH=/mqtt
```

If your broker requires auth, also export:

```bash
export MQTT_USERNAME=...
export MQTT_PASSWORD=...
```

### 2. Build the generated iOS consumer
## Running locally

```bash
npx qvac-test build:consumer:ios --runId <run-id> --config .
```

### 3. Build the Xcode app for the device

Before building, open the generated workspace in Xcode and verify signing:

- open `build/consumers/ios/ios/QVACTestConsumer.xcworkspace`
- select the `QVACTestConsumer` target
- set a valid Apple Team under Signing & Capabilities
- if signing fails, change the bundle identifier to a unique value for your Apple account
cd packages/sdk/tests-qvac
npm run install:build # installs deps + builds tests
cp .env.example .env # only needed if you want to point at a remote broker

After signing is configured, you can build from Xcode UI or from the command line below.

```bash
cd build/consumers/ios/ios

xcodebuild \
-workspace QVACTestConsumer.xcworkspace \
-scheme QVACTestConsumer \
-configuration Release \
-destination 'id=<device-udid>'
npx qvac-test run:local:desktop
npx qvac-test run:local:android
npx qvac-test run:local:ios
```

### 4. Install the app on the iPhone

```bash
ios-deploy \
--bundle ~/Library/Developer/Xcode/DerivedData/<derived-data-dir>/Build/Products/Release-iphoneos/QVACTestConsumer.app
```

### 5. Start the producer

Run this in a terminal that still has the same `MQTT_*` exports:

```bash
npx qvac-test run:producer --runId <run-id> --config .
```

### 6. Start tests from the phone

Open the installed app on the iPhone and start the automated run. The producer should detect the mobile consumer over MQTT and begin assigning tests.

## Notes

- The generated mobile app uses the local checked-out SDK from `packages/sdk`.
- The iOS build currently uses the generated Xcode scheme `QVACTestConsumer`.
- The Metro config includes explicit audio asset extensions required by the mobile test assets.
**MQTT broker.** `run:local:*` requires a broker serving WebSockets on port 8080 and MQTT/TCP on 1883.
If nothing is detected on localhost, the command prompts to install `aedes` + `websocket-stream` globally and
runs an embedded broker for the duration of the test run. Bring your own broker if you prefer β€” just expose
`ws://...:8080` and `mqtt://...:1883`.

**Common flags.** All `run:local:*` commands accept `--filter`, `--suite`, `--exclude-suite`, `--runId`.
Mobile adds `--skip-build` to reuse a previously built APK/IPA (pairs with the baked `runId`). Run
`npx qvac-test run:local:<platform> --help` for the full list.

**Platform prerequisites.**

- iOS: Xcode + connected device trusted in Xcode. Team ID auto-detected; override with `QVAC_IOS_TEAM_ID`.
- Android: `adb` + USB-debuggable device.
- Desktop: Node 22+.

## Running in CI

### Label-triggered on PRs

See [`.github/workflows/on-pr-test-sdk.yml`](../../../.github/workflows/on-pr-test-sdk.yml).

- `test-e2e-smoke` β€” runs the `smoke` suite on all platforms.
- `test-e2e-full` β€” runs the full catalog on all platforms.
- Release-branch PRs with SDK changes auto-run the full suite.
- Success applies the `e2e-tested` label.

### Manual runs

Open [Actions β†’ QVAC Tests (sdk) β†’ Run workflow](https://github.com/tetherto/qvac/actions/workflows/test-sdk.yml)
and submit the form.

Non-obvious inputs:

- **"Use workflow from" (GitHub's own selector) vs `test-version`** β€” these are independent. The selector
picks the branch that supplies the *workflow YAML*; `test-version` is the git ref that gets checked out for
the *code under test* (and the tests-qvac package). Leave `test-version` blank to test the same branch the
workflow was loaded from. Set it to test workflow edits from one branch against SDK code on another.
- `suite` + `suite-custom` β€” pick `custom` to pass arbitrary comma-separated suite tags via `suite-custom`.
- `desktop-platforms` β€” JSON array of runner labels; defaults to all three GPU runners. Narrow to one during
debugging.

The remaining inputs (`targets`, `filter`, `exclude-suite`, timeouts, `cache-models`) are self-explanatory in
the form.

## Developing new tests

- **Definitions** live in [`tests/<feature>-tests.ts`](./tests), aggregated in
[`tests/test-definitions.ts`](./tests/test-definitions.ts). Each entry is a `TestDefinition` with `testId`,
`params`, `expectation`, optional `suites`, and `metadata`.
- **Executors β€” pick one of three locations based on runtime requirements:**
- [`tests/shared/executors/`](./tests/shared/executors) β€” **default**. Pure SDK API calls, no Node stdlib,
no RN APIs. Runs on both desktop and mobile. Example:
[`completion-executor.ts`](./tests/shared/executors/completion-executor.ts).
- [`tests/desktop/executors/`](./tests/desktop/executors) β€” needs `node:fs`, `node:path`, `process.cwd()`,
or other Node-only APIs. Example: [`rag-executor.ts`](./tests/desktop/executors/rag-executor.ts) reads
documents from disk.
- [`tests/mobile/executors/`](./tests/mobile/executors) β€” needs React Native-specific asset loading
(`Platform`, bundled assets). Example:
[`mobile/executors/ocr-executor.ts`](./tests/mobile/executors/ocr-executor.ts).
- Register new executors in [`tests/desktop/consumer.ts`](./tests/desktop/consumer.ts) and
[`tests/mobile/consumer.ts`](./tests/mobile/consumer.ts) as applicable. Mobile platform skips go through
`SkipExecutor` at the top of the mobile consumer (first match wins).
- **Smoke suite policy.** If a new feature introduces core functionality that has no existing smoke coverage,
tag **1-2** tests with `suites: ["smoke"]` β€” preferring the most representative, fastest, least-flaky test.
Verify it passes predictably on both desktop and mobile before tagging. Smoke must stay focused and fast; do
not tag additional tests for a feature that is already covered.
- Assets go under [`assets/`](./assets). Update [`qvac-test.config.js`](./qvac-test.config.js)
`consumers.mobile.assets.patterns` if the new files aren't covered by existing globs.
- One-time setup (model pre-download, warmup) goes in the exported `bootstrap()` function of each consumer
entry.

## Troubleshooting

- **No device detected** β€” `adb devices` (Android) or `xcrun devicectl list devices` (iOS). USB
trust/debugging must be enabled.
- **iOS signing errors** β€” open [`build/consumers/ios/ios/QVACTestConsumer.xcworkspace`](./) in Xcode once and
set the Team under Signing & Capabilities, or export `QVAC_IOS_TEAM_ID`. If Xcode keeps failing, change
`QVAC_IOS_BUNDLE_ID` to a suffix unique to your Apple account.
- **MQTT broker unreachable** β€” the embedded broker needs `aedes` + `websocket-stream`. `run:local:*` offers
to install them globally; accept, or run `npm install -g aedes websocket-stream` yourself.
- **Manual iOS build fallback** β€” when the automated flow fails, build from the generated Xcode workspace
manually:

```bash
npx qvac-test build:consumer:ios --runId <run-id> --config .
cd build/consumers/ios/ios
xcodebuild \
-workspace QVACTestConsumer.xcworkspace \
-scheme QVACTestConsumer \
-configuration Release \
-destination 'id=<device-udid>'
ios-deploy --bundle ~/Library/Developer/Xcode/DerivedData/<derived-data-dir>/Build/Products/Release-iphoneos/QVACTestConsumer.app
npx qvac-test run:producer --runId <run-id> --config .
```
3 changes: 2 additions & 1 deletion packages/sdk/tests-qvac/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
"description": "SDK tests using QVAC test framework",
"scripts": {
"build": "npm run clean && tsc",
"install:build": "npm install --install-links && npm run build",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist",
"consumer:desktop:run": "qvac-test run:consumer:desktop"
},
"dependencies": {
"@qvac/sdk": "file:..",
"@tetherto/qvac-test-suite": "^0.5.1",
"@tetherto/qvac-test-suite": "^0.6.0",
"mqtt": "^5.14.1",
"react-native-bare-kit": "^0.11.4"
},
Expand Down
Loading