Skip to content
Draft
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
15 changes: 8 additions & 7 deletions .github/actions/sdk-e2e-report-finalize/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ description: >

inputs:
family:
description: "SDK e2e family: desktop | electron | android | ios"
description: "SDK e2e family: desktop | electron | snap | android | ios"
required: true
platforms:
description: >
JSON array of platform keys that participated in this run, e.g.
["linux","windows","macos"] for desktop/electron or ["android"] / ["ios"].
["linux","windows","macos"] for desktop/electron, ["linux"] for snap,
or ["android"] / ["ios"].
required: true
comment-ids:
description: >
Expand Down Expand Up @@ -49,7 +50,7 @@ runs:
FAMILY: ${{ inputs.family }}
run: |
case "$FAMILY" in
desktop|electron)
desktop|electron|snap)
echo "results-pattern=results-${FAMILY}-*" >> "$GITHUB_OUTPUT"
;;
android)
Expand Down Expand Up @@ -99,7 +100,8 @@ runs:
`/actions/runs/${context.runId}`;

const resultsRoot = '.sdk-e2e-report/current-results';
const isNodeFamily = family === 'desktop' || family === 'electron';
const isNodeFamily =
family === 'desktop' || family === 'electron' || family === 'snap';

const artifactDir = (platform) =>
isNodeFamily ? `results-${family}-${platform}` : `results-${family}`;
Expand Down Expand Up @@ -155,9 +157,8 @@ runs:
// actions/download-artifact@v8 extracts a single matched artifact directly
// into `path` instead of `path/<artifact-name>/` (the per-artifact subdir is
// only created when the pattern matches >=2 artifacts). For families with one
// platform (android, ios) we therefore probe both layouts. Node consumers
// (desktop/electron) usually have multiple platforms, so the per-artifact
// subdir is created and is preferred.
// platform (android, ios, or snap) may extract directly into `path`.
// Node consumers still use platform-qualified artifact names, so probe both layouts.
function findResultsJson(platform) {
const candidates = [
path.join(resultsRoot, artifactDir(platform)),
Expand Down
128 changes: 122 additions & 6 deletions .github/workflows/test-node-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_call:
inputs:
consumer:
description: "Node-compatible consumer to run (desktop or electron)"
description: "Node-compatible consumer to run (desktop, electron, or snap)"
required: false
type: string
default: desktop
Expand Down Expand Up @@ -140,6 +140,7 @@ jobs:
MQTT_PORT: ${{ secrets.MQTT_PORT }}
MQTT_USERNAME: ${{ secrets.MQTT_USERNAME }}
MQTT_PASSWORD: ${{ secrets.MQTT_PASSWORD }}
QVAC_TEST_SNAP_BUILD_MODE: ${{ startsWith(matrix.os, 'ubuntu-') && 'destructive' || 'lxd' }}
steps:
- name: Manual Workspace Cleanup
run: rm -rf "$GITHUB_WORKSPACE" && mkdir -p "$GITHUB_WORKSPACE"
Expand Down Expand Up @@ -263,12 +264,21 @@ jobs:
working-directory: ${{ inputs.working-directory }}
run: bun run build

- name: Remove consumer package credentials
if: inputs.consumer == 'snap'
working-directory: ${{ inputs.working-directory }}
shell: node {0}
run: |
const fs = require('fs');
fs.rmSync('.npmrc', { force: true });

- name: Bundle SDK worker (custom plugins)
if: inputs.consumer == 'desktop'
working-directory: ${{ inputs.working-directory }}
run: npm run bundle:sdk

- name: Configure SDK cache directory
if: inputs.consumer != 'snap'
shell: node {0}
run: |
const fs = require('fs');
Expand All @@ -284,23 +294,23 @@ jobs:

- name: Restore models cache
id: models-cache
if: inputs.cache-models != false
uses: actions/cache/restore@v4
if: inputs.consumer != 'snap' && inputs.cache-models != false
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # 5.0.4
with:
path: ${{ inputs.working-directory }}/.qvac-cache
key: qvac-models-${{ hashFiles('packages/sdk/models/registry/models.ts') }}
enableCrossOsArchive: true

- name: Bootstrap (download models)
if: steps.models-cache.outputs.cache-hit != 'true'
if: inputs.consumer != 'snap' && steps.models-cache.outputs.cache-hit != 'true'
working-directory: ${{ inputs.working-directory }}
run: npx qvac-test run:bootstrap:${{ inputs.consumer }} --config=.

# Save right after bootstrap. On parallel runners, first to finish reserves the key;
# others get a non-fatal "unable to reserve" warning which is expected.
- name: Save models cache
if: steps.models-cache.outputs.cache-hit != 'true' && inputs.cache-models != false
uses: actions/cache/save@v4
if: inputs.consumer != 'snap' && steps.models-cache.outputs.cache-hit != 'true' && inputs.cache-models != false
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # 5.0.4
with:
path: ${{ inputs.working-directory }}/.qvac-cache
key: qvac-models-${{ hashFiles('packages/sdk/models/registry/models.ts') }}
Expand All @@ -311,6 +321,97 @@ jobs:
working-directory: ${{ inputs.working-directory }}
run: npx qvac-test build:consumer:electron --config=. --skip-install

- name: Verify Snap tooling
if: inputs.consumer == 'snap'
run: |
if ! command -v snapcraft; then
sudo snap install snapcraft --classic
fi
if [ "$QVAC_TEST_SNAP_BUILD_MODE" = "lxd" ]; then
if ! command -v lxc; then
echo "::error::LXD is required for non-destructive Snap builds on this runner"
exit 1
fi
lxc info
fi
snap version
snapcraft --version

- name: Build first Snap revision
if: inputs.consumer == 'snap'
working-directory: ${{ inputs.working-directory }}
env:
QVAC_TEST_SNAP_BUILD_ID: first
run: npx qvac-test build:consumer:snap --config=. --skip-install

- name: Preserve first Snap revision
if: inputs.consumer == 'snap'
working-directory: ${{ inputs.working-directory }}
shell: node {0}
run: |
const fs = require('fs');
fs.copyFileSync('snap/dist/qvac-sdk-e2e.snap', 'snap/dist/qvac-sdk-e2e-first.snap');

- name: Build second Snap revision
if: inputs.consumer == 'snap'
working-directory: ${{ inputs.working-directory }}
env:
QVAC_TEST_SNAP_BUILD_ID: second
run: npx qvac-test build:consumer:snap --config=. --skip-install

- name: Verify Snap refresh persistence
if: inputs.consumer == 'snap'
working-directory: ${{ inputs.working-directory }}
run: >-
npm run test:snap:refresh --
--first snap/dist/qvac-sdk-e2e-first.snap
--second snap/dist/qvac-sdk-e2e.snap

- name: Install Snap revision for e2e
if: inputs.consumer == 'snap'
working-directory: ${{ inputs.working-directory }}
run: sudo snap install --dangerous snap/dist/qvac-sdk-e2e.snap

- name: Determine Snap model requirements
id: snap-model-requirements
if: inputs.consumer == 'snap'
shell: node {0}
env:
TEST_FILTER: ${{ inputs.filter }}
run: |
const fs = require('fs');
const filters = (process.env.TEST_FILTER || '')
.split(',')
.map((filter) => filter.trim())
.filter(Boolean);
const requiresModels =
filters.length === 0 || filters.some((filter) => !filter.startsWith('snap-storage'));
fs.appendFileSync(process.env.GITHUB_OUTPUT, `required=${requiresModels}\n`);

- name: Restore Snap models cache
id: snap-models-cache
if: inputs.consumer == 'snap' && steps.snap-model-requirements.outputs.required == 'true' && inputs.cache-models != false
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # 5.0.4
with:
path: ~/snap/qvac-sdk-e2e/common/.qvac/models
key: qvac-snap-models-${{ hashFiles('packages/sdk/models/registry/models.ts') }}

- name: Bootstrap models inside Snap
if: inputs.consumer == 'snap' && steps.snap-model-requirements.outputs.required == 'true' && steps.snap-models-cache.outputs.cache-hit != 'true'
working-directory: ${{ inputs.working-directory }}
run: >-
npx qvac-test run:bootstrap:snap
--config=.
--skip-build
--skip-snap-install

- name: Save Snap models cache
if: inputs.consumer == 'snap' && steps.snap-model-requirements.outputs.required == 'true' && steps.snap-models-cache.outputs.cache-hit != 'true' && inputs.cache-models != false
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # 5.0.4
with:
path: ~/snap/qvac-sdk-e2e/common/.qvac/models
key: qvac-snap-models-${{ hashFiles('packages/sdk/models/registry/models.ts') }}

- name: Generate runId
id: runid
shell: node {0}
Expand Down Expand Up @@ -411,6 +512,8 @@ jobs:
];
if (consumerType === 'electron') {
consumerArgs.push('--skip-build');
} else if (consumerType === 'snap') {
consumerArgs.push('--skip-build', '--skip-snap-install');
}

const consumer = spawn('npx', consumerArgs, { detached: false, shell: true, stdio: 'inherit' });
Expand Down Expand Up @@ -535,6 +638,19 @@ jobs:
}
}

- name: Upload Snap artifacts
if: always() && inputs.consumer == 'snap'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
with:
name: snap-artifacts-${{ steps.runid.outputs.platform }}
path: ${{ inputs.working-directory }}/snap/dist/*.snap
retention-days: 7

- name: Remove Snap consumer
if: always() && inputs.consumer == 'snap'
continue-on-error: true
run: sudo snap remove --purge qvac-sdk-e2e

- name: Upload consumer logs
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
Expand Down
26 changes: 25 additions & 1 deletion .github/workflows/test-sdk.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# QVAC SDK Tests
# Runs desktop and/or mobile (Android/iOS) e2e tests for the SDK package
# Runs desktop, Electron, Snap, and/or mobile e2e tests for the SDK package
#
# Triggered via:
# - workflow_dispatch: manual runs with full customizability
Expand All @@ -17,6 +17,7 @@ on:
- all
- desktop
- electron
- snap
- mobile
- electron + desktop
- desktop + mobile
Expand Down Expand Up @@ -65,6 +66,10 @@ on:
description: "JSON array of runner labels for electron tests"
type: string
default: '["qvac-win25-x64-gpu", "qvac-ubuntu2204-x64-gpu", "qvac-macos26-arm64-gpu-gui"]'
snap-platforms:
description: "JSON array of Linux runner labels for strict Snap tests"
type: string
default: '["qvac-ubuntu2204-x64-gpu"]'
test-version:
description: "Git ref to checkout for the tested project (branch, tag, or SHA). Defaults to current branch HEAD."
type: string
Expand Down Expand Up @@ -173,6 +178,25 @@ jobs:
cache-models: ${{ inputs.cache-models }}
secrets: inherit

snap-tests:
needs: resolve
# Snap CI is manual-only. It is intentionally excluded from workflow_call
# consumers such as on-pr-test-sdk.yml.
if: github.event_name == 'workflow_dispatch' && (inputs.targets == 'all' || inputs.targets == 'snap')
uses: ./.github/workflows/test-node-sdk.yml
with:
consumer: snap
project-directory: "packages/sdk"
working-directory: "packages/sdk/e2e"
platforms: ${{ inputs.snap-platforms || '["qvac-ubuntu2204-x64-gpu"]' }}
consumer-timeout: ${{ fromJSON(inputs.desktop-consumer-timeout || '60') }}
filter: ${{ inputs.filter }}
suite: ${{ needs.resolve.outputs.suite }}
exclude-suite: ${{ inputs.exclude-suite }}
test-version: ${{ inputs.test-version || '' }}
cache-models: ${{ inputs.cache-models }}
secrets: inherit

android-tests:
needs: resolve
permissions:
Expand Down
6 changes: 6 additions & 0 deletions packages/sdk/e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
build/
out/
snap/electron-dist/
snap/dist/
snap/parts/
snap/prime/
snap/stage/
snap/*.snap
.qvac-cache/
.qvac-worker-backup/
qvac.config.json
Expand Down
Loading
Loading