mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 21:44:38 +00:00
* Replace Playwright with Appium mac2 + WebDriverIO for E2E testing and add unit tests - Remove Playwright config and old e2e/ directory - Add WebDriverIO + Appium mac2 driver for true native macOS app testing - Add wdio.conf.ts with platform-aware app path detection - Add scripts/e2e.sh to manage Appium lifecycle under Node 24 - Add E2E smoke, navigation, and Tauri integration specs - Add tsconfig.e2e.json for E2E test TypeScript config - Add unit tests for components, store slices, selectors, services, and utils - Add test infrastructure (setup.ts, MSW handlers, test-utils) - Update vitest.config.ts with coverage thresholds and setup - Update package.json scripts and dependencies Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update skills submodule to latest Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add complete E2E login flow test with mock server and onboarding walkthrough Implements end-to-end testing of the full deep link auth → onboarding → home flow using Appium mac2, WebDriverIO, and a local HTTP/WebSocket mock server. Key additions: - Mock server (port 18473) with all API routes + Socket.IO/Engine.IO WebSocket handler to prevent Rust SocketManager crashes - Deep link helpers to trigger alphahuman:// URLs via macOS `open` command - Element helpers using XPath selectors (not iOS predicates, which crash on mac2 with non-string attributes) and W3C pointer actions for clicking WKWebView content (standard element.click() doesn't trigger DOM handlers) - Login flow spec: 11 tests covering auth deep link, API call verification, all 4 onboarding steps, and home page arrival - Dedicated e2e-build.sh script with cargo clean + mock URL injection - Cache cleanup and mock URL verification in e2e.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Harden E2E tests and fix config issues from PR review - e2e.sh: fail fast when dist bundle is missing instead of silently skipping - setup.ts: add React type import for PersistGate mock annotation - app-helpers: throw descriptive error on waitForAppReady timeout - element-helpers: XPath quote escaping via concat() for special chars - login-flow: fail explicitly when invite code step retry doesn't advance - smoke.spec: replace no-op assertion with real session ID checks - vite/vitest configs: remove unused @alphahuman/skill-types alias and dead path/fileURLToPath imports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add TypeScript types to E2E element helpers and configure ESLint for E2E files Remove blanket eslint-disable/ts-nocheck from element-helpers.ts, add explicit TypeScript types with ChainablePromiseElement import, and add ESLint config block for test/e2e/ files using tsconfig.e2e.json with WebDriverIO/Mocha globals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix CI submodule checkout by adding XGH_TOKEN_READ secret The skills submodule is a private repo that requires authentication. The build and package-android workflows were missing the token that package-and-publish already had. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix CI: install skills deps, format with Prettier, lower coverage thresholds - build.yml: add skills dependency install step and remove redundant frontend build (tauri build already runs build:app via beforeBuildCommand) - Run Prettier --write on all 32 files with formatting drift - Lower vitest coverage thresholds to match actual coverage (~20%) until test coverage improves organically Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove stray globals.d.ts that failed Prettier check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix ESLint parsing for wdio.conf.ts and type remaining E2E helpers - Add wdio.conf.ts to ESLint E2E config block so TS parser covers it - Remove blanket eslint-disable/ts-nocheck from deep-link-helpers.ts and app-helpers.ts, add explicit TypeScript types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix unnecessary regex escape in useSettingsNavigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Ghost Scripter <ghostscripter@zerolend.xyz> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
267 lines
10 KiB
YAML
267 lines
10 KiB
YAML
# Separate workflow for building, packaging, and publishing the Android APK.
|
|
# Runs independently from the desktop package-and-publish workflow.
|
|
|
|
name: Package Android
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
publish:
|
|
description: 'Publish to release (requires main branch)'
|
|
type: boolean
|
|
default: false
|
|
workflow_run:
|
|
workflows: ['Version Bump']
|
|
types:
|
|
- completed
|
|
branches:
|
|
- main
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
|
|
env:
|
|
IS_PR: ${{ github.event_name == 'pull_request' }}
|
|
SHOULD_PUBLISH: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && (github.event_name != 'workflow_dispatch' || github.event.inputs.publish == 'true') }}
|
|
PUBLISH_REPO: alphahumanxyz/alphahuman
|
|
XGH_TOKEN: ${{ secrets.XGH_TOKEN }}
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
jobs:
|
|
get-version:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
package-version: ${{ steps.extract-version.outputs.package-version }}
|
|
tag-name: ${{ steps.extract-version.outputs.tag-name }}
|
|
should-publish: ${{ steps.extract-version.outputs.should-publish }}
|
|
release-name: ${{ steps.extract-version.outputs.release-name }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20.x
|
|
|
|
- name: Extract version and tag
|
|
id: extract-version
|
|
run: |
|
|
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
|
TAG_NAME="v${PACKAGE_VERSION}"
|
|
RELEASE_NAME="AlphaHuman v${PACKAGE_VERSION}"
|
|
echo "package-version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
|
|
echo "tag-name=$TAG_NAME" >> $GITHUB_OUTPUT
|
|
echo "release-name=$RELEASE_NAME" >> $GITHUB_OUTPUT
|
|
echo "should-publish=$SHOULD_PUBLISH" >> $GITHUB_OUTPUT
|
|
echo "Extracted version: $PACKAGE_VERSION"
|
|
echo "Generated tag: $TAG_NAME"
|
|
check-release:
|
|
runs-on: ubuntu-latest
|
|
needs: get-version
|
|
outputs:
|
|
release-id: ${{ steps.find-release.outputs.release-id }}
|
|
should-skip: ${{ steps.find-release.outputs.should-skip }}
|
|
steps:
|
|
- name: Find existing release
|
|
id: find-release
|
|
env:
|
|
TAG_NAME: ${{ needs.get-version.outputs.tag-name }}
|
|
run: |
|
|
if [ "$SHOULD_PUBLISH" != "true" ]; then
|
|
echo "Publishing disabled (non-main branch or PR)"
|
|
echo "should-skip=false" >> $GITHUB_OUTPUT
|
|
echo "release-id=" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
echo "Looking for release with tag: $TAG_NAME"
|
|
RESPONSE=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $XGH_TOKEN" \
|
|
-H "Accept: application/vnd.github.v3+json" \
|
|
"https://api.github.com/repos/$PUBLISH_REPO/releases/tags/$TAG_NAME")
|
|
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
if [ "$HTTP_CODE" = "200" ]; then
|
|
RELEASE_ID=$(echo "$BODY" | jq -r '.id')
|
|
IS_DRAFT=$(echo "$BODY" | jq -r '.draft')
|
|
# Check if Android AAB is already uploaded
|
|
AAB_EXISTS=$(echo "$BODY" | jq -r '.assets[]? | select(.name | endswith(".aab")) | .name' | head -1)
|
|
if [ -n "$AAB_EXISTS" ]; then
|
|
echo "Android AAB already exists in release: $AAB_EXISTS"
|
|
echo "should-skip=true" >> $GITHUB_OUTPUT
|
|
echo "release-id=$RELEASE_ID" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "Release found (ID: $RELEASE_ID, draft: $IS_DRAFT), no AAB yet"
|
|
echo "should-skip=false" >> $GITHUB_OUTPUT
|
|
echo "release-id=$RELEASE_ID" >> $GITHUB_OUTPUT
|
|
fi
|
|
elif [ "$HTTP_CODE" = "404" ]; then
|
|
echo "No release found for $TAG_NAME — will build artifact only (no publish)"
|
|
echo "should-skip=false" >> $GITHUB_OUTPUT
|
|
echo "release-id=" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "Warning: Failed to check release (HTTP $HTTP_CODE)"
|
|
echo "should-skip=false" >> $GITHUB_OUTPUT
|
|
echo "release-id=" >> $GITHUB_OUTPUT
|
|
fi
|
|
build-android:
|
|
name: Build and package Android AAB
|
|
needs: [get-version, check-release]
|
|
if: ${{ !failure() && !cancelled() && needs.check-release.outputs.should-skip != 'true' }}
|
|
environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }}
|
|
runs-on: ubuntu-22.04
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
submodules: true
|
|
token: ${{ secrets.XGH_TOKEN_READ }}
|
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
|
|
|
|
- name: Setup Node.js 24.x
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24.x
|
|
cache: 'yarn'
|
|
|
|
- name: Install Rust stable
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android
|
|
|
|
- name: Setup Java 17
|
|
uses: actions/setup-java@v4
|
|
with:
|
|
distribution: 'temurin'
|
|
java-version: '17'
|
|
|
|
- name: Setup Android SDK
|
|
uses: android-actions/setup-android@v3
|
|
|
|
- name: Install Android NDK
|
|
run: sdkmanager --install "ndk;27.0.12077973"
|
|
|
|
- name: Set NDK path
|
|
run: echo "NDK_HOME=$ANDROID_HOME/ndk/27.0.12077973" >> $GITHUB_ENV
|
|
|
|
- name: Cache Cargo registry
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
key: ${{ runner.os }}-android-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-android-cargo-
|
|
- name: Cache node modules
|
|
id: yarn-cache
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: node_modules
|
|
key: ${{ runner.os }}-build-${{ hashFiles('**/yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-build-
|
|
- name: Install dependencies
|
|
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
|
run: yarn install --frozen-lockfile
|
|
|
|
- name: Cache skills node modules
|
|
id: skills-yarn-cache
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: skills/node_modules
|
|
key: ${{ runner.os }}-skills-${{ hashFiles('skills/yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-skills-
|
|
|
|
- name: Install skills dependencies
|
|
if: steps.skills-yarn-cache.outputs.cache-hit != 'true'
|
|
run: cd skills && yarn install --frozen-lockfile
|
|
|
|
- name: Build skills
|
|
run: cd skills && yarn build
|
|
|
|
- name: Build frontend
|
|
run: yarn build
|
|
env:
|
|
NODE_ENV: production
|
|
VITE_BACKEND_URL: ${{ vars.VITE_BACKEND_URL }}
|
|
VITE_TELEGRAM_BOT_USERNAME: ${{ secrets.VITE_TELEGRAM_BOT_USERNAME }}
|
|
VITE_TELEGRAM_BOT_ID: ${{ secrets.VITE_TELEGRAM_BOT_ID }}
|
|
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
|
|
VITE_SKILLS_GITHUB_REPO: ${{ vars.VITE_SKILLS_GITHUB_REPO }}
|
|
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
|
|
|
|
- name: Initialize Tauri Android
|
|
run: npx tauri android init
|
|
|
|
- name: Decode keystore
|
|
if: needs.get-version.outputs.should-publish != ''
|
|
env:
|
|
ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }}
|
|
run: |
|
|
echo "$ANDROID_KEYSTORE" | base64 --decode > ${{ github.workspace }}/release.jks
|
|
- name: Build Android AAB (signed)
|
|
if: needs.get-version.outputs.should-publish != ''
|
|
env:
|
|
BASE_URL: ${{ vars.BASE_URL }}
|
|
ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/release.jks
|
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
run: npx tauri android build --aab true
|
|
|
|
- name: Build Android AAB (unsigned)
|
|
if: needs.get-version.outputs.should-publish == ''
|
|
env:
|
|
BASE_URL: ${{ vars.BASE_URL }}
|
|
run: npx tauri android build --aab true
|
|
|
|
- name: Find AAB
|
|
id: find-aab
|
|
run: |
|
|
AAB_ROOT="src-tauri/gen/android"
|
|
echo "Searching for AABs in $AAB_ROOT..."
|
|
find "$AAB_ROOT" -name '*.aab' -type f | sort
|
|
AAB_PATH=$(find "$AAB_ROOT" -name '*.aab' -type f | head -1)
|
|
if [ -z "$AAB_PATH" ]; then
|
|
echo "No AAB found"
|
|
exit 1
|
|
fi
|
|
AAB_FILENAME=$(basename "$AAB_PATH")
|
|
echo "Selected AAB: $AAB_PATH"
|
|
echo "path=$AAB_PATH" >> $GITHUB_OUTPUT
|
|
echo "filename=$AAB_FILENAME" >> $GITHUB_OUTPUT
|
|
- name: Upload AAB artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: android-aab
|
|
path: ${{ steps.find-aab.outputs.path }}
|
|
|
|
- name: Upload AAB to release
|
|
if: needs.get-version.outputs.should-publish == 'true' && needs.check-release.outputs.release-id != ''
|
|
env:
|
|
RELEASE_ID: ${{ needs.check-release.outputs.release-id }}
|
|
run: |
|
|
curl -X POST -H "Authorization: Bearer $XGH_TOKEN" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
--data-binary "@${{ steps.find-aab.outputs.path }}" \
|
|
"https://uploads.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets?name=${{ steps.find-aab.outputs.filename }}"
|
|
|
|
# Requires secret GOOGLE_PLAY_SERVICE_ACCOUNT_JSON and vars.GOOGLE_PLAY_UPLOAD_ENABLED = 'true'
|
|
# Track: internal | alpha | beta | production (use vars.GOOGLE_PLAY_TRACK to override)
|
|
- name: Upload AAB to Google Play
|
|
if: needs.get-version.outputs.should-publish == 'true' && vars.GOOGLE_PLAY_UPLOAD_ENABLED == 'true'
|
|
uses: r0adkll/upload-google-play@v1
|
|
with:
|
|
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
|
|
packageName: com.alphahuman.app
|
|
releaseFiles: ${{ steps.find-aab.outputs.path }}
|
|
track: ${{ vars.GOOGLE_PLAY_TRACK || 'internal' }}
|
|
releaseName: AlphaHuman v${{ needs.get-version.outputs.package-version }}
|
|
status: completed
|