Skip to content

Run Native Pipeline #675

Run Native Pipeline

Run Native Pipeline #675

name: Run Native Pipeline
on:
workflow_dispatch:
inputs:
mendix_version:
description: "Provide the SP version to be used (e.g., 10.14.0.43709) - has to be a released version (Default: latest from Mendix versions.json)"
required: false
default: ""
nt_branch:
description: "Native Template branch to use (Default: master)"
default: "master"
required: false
type: string
workspace:
description: "Select a widget to test (Default will run all)"
required: true
default: "*-native"
type: choice
options:
- "*-native"
- accordion-native
- activity-indicator-native
- animation-native
- app-events-native
- background-gradient-native
- background-image-native
- badge-native
- bar-chart-native
- barcode-scanner-native
- bottom-sheet-native
- carousel-native
- color-picker-native
- column-chart-native
- feedback-native
- floating-action-button-native
- gallery-native
- gallery-text-filter-native
- image-native
- intro-screen-native
- line-chart-native
- listview-swipe-native
- maps-native
- pie-doughnut-chart-native
- popup-menu-native
- progress-bar-native
- qr-code-native
- radio-buttons-native
- range-slider-native
- rating-native
- repeater-native
- safe-area-view-native
- signature-native
- slider-native
- switch-native
- toggle-buttons-native
- video-player-native
- web-view-native
# Disable trigger on every push to pull request for now
# pull_request:
# # branches: [master]
permissions:
packages: write
jobs:
scope:
runs-on: ubuntu-latest
outputs:
scope: ${{ steps.scope.outputs.scope }}
steps:
- name: "Determine scope"
id: scope
run: |
if [ -n "${{ github.event.inputs.workspace }}" ] && [ "${{ github.event.inputs.workspace }}" != "*-native" ]; then
selected_workspaces=$(echo "${{ github.event.inputs.workspace }}" | sed 's/,/ /g')
echo "scope=--all --include '${selected_workspaces}'" >> $GITHUB_OUTPUT
else
if [ "${{ github.event_name }}" == "pull_request" ]; then
echo "scope=--since --include '*-native'" >> $GITHUB_OUTPUT
else
echo "scope=--all --include '*-native'" >> $GITHUB_OUTPUT
fi
fi
- name: "Debug Scope Output"
run: |
echo "Scope is: ${{ steps.scope.outputs.scope }}"
mendix-version:
runs-on: ubuntu-22.04
outputs:
mendix_version: ${{ steps.set-mendix-version.outputs.MENDIX_VERSION }}
steps:
- name: "Check out code"
uses: actions/checkout@v4
- name: "Get Mendix version from JSON"
id: get-mendix-version
uses: notiz-dev/github-action-json-property@7a701887f4b568b23eb7b78bb0fc49aaeb1b68d3 # v0.2.0
with:
path: configs/e2e/mendix-versions.json
prop_path: latest
- name: Set Mendix version
id: set-mendix-version
run: |
if [[ -n "${{ github.event.inputs.mendix_version }}" ]]; then
echo "MENDIX_VERSION=${{ github.event.inputs.mendix_version }}" >> $GITHUB_OUTPUT
else
echo "MENDIX_VERSION=${{ steps.get-mendix-version.outputs.prop }}" >> $GITHUB_OUTPUT
fi
- name: "Debug Mendix Version"
run: |
echo "Mendix Version: ${{ steps.set-mendix-version.outputs.MENDIX_VERSION }}"
docker-images:
needs: mendix-version
runs-on: ubuntu-22.04
steps:
- name: "Login to GitHub Container Registry"
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: "Check if docker image already exists"
run: |
docker manifest inspect ghcr.io/mendix/native-widgets/mxbuild:${{ needs.mendix-version.outputs.mendix_version }} || EXIT_CODE=$?
echo "IMAGE_MISSING=$EXIT_CODE" >> $GITHUB_ENV
- name: "Check out code"
uses: actions/checkout@v4
if: ${{ env.IMAGE_MISSING != 0 }}
- name: "Build mxbuild image"
if: ${{ env.IMAGE_MISSING != 0 }}
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
file: ./.github/scripts/mxbuild.Dockerfile
context: ./.github/scripts
build-args: |
MENDIX_VERSION=${{ needs.mendix-version.outputs.mendix_version }}
push: true
tags: ghcr.io/mendix/native-widgets/mxbuild:${{ needs.mendix-version.outputs.mendix_version }}
resources:
needs: [scope, mendix-version]
runs-on: ubuntu-22.04
permissions:
packages: read
contents: read
steps:
- name: "Check out code"
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Set up node"
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: yarn
- name: "Install dependencies"
run: yarn install --immutable
- name: "Force rebuild resources"
run: |
yarn workspaces foreach ${{ needs.scope.outputs.scope }} run build # Ensure all resources are rebuilt
- name: "Unit test"
run: yarn workspaces foreach ${{ needs.scope.outputs.scope }} run test
- name: "Run build for development"
run: yarn workspaces foreach ${{ needs.scope.outputs.scope }} run build
env:
NODE_OPTIONS: --max_old_space_size=6144
- name: "Upload resources artifact"
uses: actions/upload-artifact@v4
with:
name: resources
path: |
packages/pluggableWidgets/**/dist/*/*.mpk
packages/jsActions/mobile-resources-native/dist/**/*
packages/jsActions/nanoflow-actions-native/dist/**/*
project:
needs: [resources, mendix-version]
runs-on: ubuntu-22.04
container:
image: ghcr.io/mendix/native-widgets/mxbuild:${{ needs.mendix-version.outputs.mendix_version }}
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: "Make sure curl is installed"
run: |
apt update && apt upgrade -y
apt install curl -y
- name: "Download test project"
run: curl -L -o project.zip https://github.com/mendix/Native-Mobile-Resources/archive/refs/heads/maestro-widgets.zip
- name: "Extract test project"
uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28 # v1.0.0
with:
args: unzip -qq project.zip -d .
- name: "Download resources artifact"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: resources
path: resources
- name: "List resources"
run: ls -R resources
- name: "Move widgets"
shell: bash
run: |
if compgen -G 'resources/pluggableWidgets/**/dist/*/*.mpk' > /dev/null; then
for oldPath in resources/pluggableWidgets/**/dist/*/*.mpk; do
newPath=Native-Mobile-Resources-maestro-widgets/widgets/$(basename $oldPath)
mv -f $oldPath $newPath
done
mx update-widgets --loose-version-check Native-Mobile-Resources-maestro-widgets/NativeComponentsTestProject.mpr
fi
- name: "Move mobile-resources"
shell: bash
run: |
if compgen -G 'resources/jsActions/mobile-resources-native/*' > /dev/null; then
mv -f resources/jsActions/mobile-resources-native/* Native-Mobile-Resources-maestro-widgets/javascriptsource/nativemobileresources/actions/
fi
- name: "Move nanoflow-actions"
shell: bash
run: |
if compgen -G 'resources/jsActions/mobile-resources-native/*' > /dev/null; then
mv -f resources/jsActions/nanoflow-actions-native/* Native-Mobile-Resources-maestro-widgets/javascriptsource/nanoflowcommons/actions/
fi
- name: "Force rebuild test project"
run: |
mxbuild -o automation.mda --loose-version-check Native-Mobile-Resources-maestro-widgets/NativeComponentsTestProject.mpr
- name: "Upload MDA"
uses: actions/upload-artifact@v4
with:
name: mda
path: automation.mda
android-bundle:
needs: [project, mendix-version]
runs-on: ubuntu-22.04
container:
image: ghcr.io/mendix/native-widgets/mxbuild:${{ needs.mendix-version.outputs.mendix_version }}
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: "Check out code"
uses: actions/checkout@v4
- name: "Download deployment package"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: mda
- name: "Create Android bundle"
uses: ./.github/actions/create-native-bundle
with:
platform: android
mda-file: automation.mda
ios-bundle:
needs: [project, mendix-version]
runs-on: ubuntu-22.04
container:
image: ghcr.io/mendix/native-widgets/mxbuild:${{ needs.mendix-version.outputs.mendix_version }}
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: "Check out code"
uses: actions/checkout@v4
- name: "Download project MDA file"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: mda
- name: "Create iOS bundle"
uses: ./.github/actions/create-native-bundle
with:
platform: ios
mda-file: automation.mda
android-app:
needs: [android-bundle]
runs-on: ubuntu-22.04
steps:
- name: "Check out Native Template for Native Components Test Project"
uses: actions/checkout@v4
with:
repository: mendix/native-template
ref: ${{ github.event.inputs.nt_branch || 'master' }}
path: native-template
- name: "Check out code"
uses: actions/checkout@v4
with:
path: native-widgets
- name: "Download Android bundle and assets"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: android-bundle
path: bundles/android
- name: "Set up Node"
uses: actions/setup-node@v4
with:
node-version-file: native-template/.nvmrc
cache: npm
cache-dependency-path: native-template/package-lock.json
- name: "Cache Android Build"
uses: actions/cache@v3
with:
path: native-template/android/app/build
key: ${{ runner.os }}-android-build-${{ hashFiles('native-template/android/app/src/**/*.java', 'native-template/android/app/src/**/*.kt', 'native-template/android/app/build.gradle') }}
restore-keys: |
${{ runner.os }}-android-build-
- name: "Copy files to the right location"
run: |
mv bundles/android/index.android.bundle native-template/android/app/src/main/assets/index.android.bundle
cp -r bundles/android/assets/* native-template/android/app/src/main/res/
mv native-widgets/configs/e2e/config.json native-template
mv native-widgets/configs/e2e/google-services.json native-template/android/app
node native-widgets/scripts/test/add-native-dependencies.js
- name: "Install dependencies"
working-directory: native-template
run: npm i
- name: "Setup JDK "
uses: actions/setup-java@v4
with:
java-version: 21
distribution: temurin
cache: gradle
- name: "Build Android app"
working-directory: native-template/android
run: |
./gradlew assembleAppstoreDebug assembleAppstoreDebugAndroidTest
if [ $? -ne 0 ]; then
echo "Build failed!"
exit 1
fi
- name: "List APK files"
run: |
echo "Listing APK files in the output directory:"
ls -R native-template/android/app/build/outputs/apk/
- name: "Archive Android app"
uses: actions/upload-artifact@v4
with:
name: android-app
path: native-template/android/app/build/outputs/apk/**/*.apk
ios-app:
needs: [ios-bundle]
runs-on: macos-13
steps:
- name: "Check out Native Template for Native Components Test Project"
uses: actions/checkout@v4
with:
repository: mendix/native-template
ref: ${{ github.event.inputs.nt_branch || 'master' }}
path: native-template
- name: "Check out code"
uses: actions/checkout@v4
with:
path: native-widgets
- name: "Download iOS bundle and assets"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: ios-bundle
path: bundles/ios
- name: "Set up Node"
uses: actions/setup-node@v4
with:
node-version-file: native-template/.nvmrc
cache: npm
cache-dependency-path: native-template/package-lock.json
- name: "Cache iOS Build"
uses: actions/cache@v3
with:
path: native-template/ios/build
key: ${{ runner.os }}-ios-build-${{ hashFiles('native-template/ios/**/*.swift', 'native-template/ios/**/*.h', 'native-template/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-ios-build-
- name: "Copy files to the right location"
run: |
mv bundles/ios/index.ios.bundle native-template/ios/Bundle/index.ios.bundle
mv bundles/ios/assets/assets native-template/ios/Bundle/
mv native-widgets/configs/e2e/config.json native-template
node native-widgets/scripts/test/add-native-dependencies.js
- name: "Install Node dependencies"
working-directory: native-template
run: npm i
- name: "Setup Pods cache"
uses: actions/cache@v3
with:
path: native-template/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: "Install Pod dependencies"
working-directory: native-template/ios
run: pod install
- name: "Build iOS app"
working-directory: native-template/ios
run: xcodebuild -workspace NativeTemplate.xcworkspace -scheme nativeTemplate -configuration Debug -sdk iphonesimulator -derivedDataPath build ONLY_ACTIVE_ARCH=YES VALID_ARCHS="i386 x86_64"
- name: "Archive iOS app"
uses: actions/upload-artifact@v4
with:
name: ios-app
path: native-template/ios/build/Build/Products/**/*.app
android-tests:
needs: [scope, mendix-version, project, android-app]
runs-on: ubuntu-22.04
timeout-minutes: 720
steps:
- name: "Check out code"
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Set up node"
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: yarn
- name: "Install dependencies"
run: yarn install --immutable
- name: "Download project MDA file"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: mda
- name: "Download Android app"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: android-app
path: android-app
- name: "Start runtime"
uses: ./.github/actions/start-runtime
with:
mda-file: automation.mda
mendix-version: ${{ needs.mendix-version.outputs.mendix_version }}
- name: "Install Maestro"
run: |
mkdir -p $HOME/.local/bin
curl -L "https://github.com/mobile-dev-inc/maestro/releases/latest/download/maestro.zip" -o maestro.zip
unzip maestro.zip -d $HOME/.local/bin
chmod +x $HOME/.local/bin/maestro/bin/maestro
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Verify maestro
run: $HOME/.local/bin/maestro/bin/maestro --version
# Needed for Linux ubuntu environment
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: "Run the android tests"
uses: reactivecircus/[email protected]
with:
api-level: 30
target: google_apis
arch: x86_64
profile: pixel
emulator-options: "-no-window -gpu swiftshader_indirect -no-boot-anim -no-snapshot -memory 4096 -cores 4"
disable-animations: true
script: |
chmod +x maestro/prepare_android.sh
chmod +x maestro/run_maestro_tests.sh
bash maestro/prepare_android.sh
bash maestro/run_maestro_tests.sh android "${{ github.event.inputs.workspace }}"
- name: "Archive runtime logs"
uses: actions/upload-artifact@v4
if: always()
with:
name: android-runtime-logs
path: log/*.log
if-no-files-found: ignore
- name: "Archive test screenshots"
uses: actions/upload-artifact@v4
if: always()
with:
name: android-screenshots
path: ${{ github.workspace }}/maestro/images/actual/android/**/*.png
if-no-files-found: ignore
ios-tests:
needs: [scope, mendix-version, project, ios-app]
runs-on: macos-15
timeout-minutes: 720
steps:
- name: "Check out code"
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Set up node"
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: yarn
- name: "Install dependencies"
run: |
yarn cache clean
yarn install --immutable
- name: "Download project MDA file"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: mda
- name: "Start runtime"
uses: ./.github/actions/start-runtime
with:
mda-file: automation.mda
mendix-version: ${{ needs.mendix-version.outputs.mendix_version }}
- name: "Download iOS app"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: ios-app
path: ios-app
- name: Check iOS App Path
run: ls ios-app
- name: "Verify Xcode CLI Tools"
run: |
if ! xcode-select --print-path; then
echo "Xcode CLI tools not set. Setting them now."
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
else
echo "Xcode CLI tools are already configured."
fi
- name: "Install Maestro"
run: |
mkdir -p $HOME/.local/bin
curl -L "https://github.com/mobile-dev-inc/maestro/releases/latest/download/maestro.zip" -o maestro.zip
unzip maestro.zip -d $HOME/.local/bin
chmod +x $HOME/.local/bin/maestro/bin/maestro
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: "Verify maestro"
run: $HOME/.local/bin/maestro/bin/maestro --version
- name: "List Available Simulators"
run: xcrun simctl list devices
- name: "Run Maestro Script"
run: |
chmod +x maestro/prepare_ios.sh
chmod +x maestro/run_maestro_tests.sh
bash maestro/prepare_ios.sh
bash maestro/run_maestro_tests.sh ios "${{ github.event.inputs.workspace }}"
- name: "Archive runtime logs"
uses: actions/upload-artifact@v4
if: always()
with:
name: ios-runtime-logs
path: log/*.log
if-no-files-found: ignore
- name: "Archive test screenshots"
uses: actions/upload-artifact@v4
if: always()
with:
name: ios-screenshots
path: ${{ github.workspace }}/maestro/images/actual/ios/**/*.png
if-no-files-found: ignore
- name: "Archive artifacts"
uses: actions/upload-artifact@v4
if: always()
with:
name: ios-artifacts
path: packages/pluggableWidgets/**/artifacts/
if-no-files-found: ignore
compare-screenshots:
needs: [android-tests, ios-tests]
runs-on: ubuntu-latest
if: always()
steps:
- name: "Check out code"
uses: actions/checkout@v4
- name: "Download Android screenshots"
uses: actions/download-artifact@v4
with:
name: android-screenshots
path: images/actual/android
continue-on-error: true
- name: "Download iOS screenshots"
uses: actions/download-artifact@v4
with:
name: ios-screenshots
path: images/actual/ios
continue-on-error: true
- name: "Set up node"
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: yarn
- name: "Install dependencies"
run: |
sudo apt-get update
sudo apt-get install -y imagemagick
- name: "Compare Android screenshots"
run: |
if [ -d "images/actual/android" ]; then
mkdir -p images/diffs/android
failed_comparisons=()
for file in images/actual/android/*.png; do
filename=$(basename $file)
if [ -f "maestro/images/expected/android/$filename" ]; then
# Detect the height of the status bar (assuming 50px height)
status_bar_height=50
actual_height=$(identify -format "%h" "images/actual/android/$filename")
cropped_height=$((actual_height - status_bar_height))
convert "images/actual/android/$filename" -crop +0+$status_bar_height "images/actual/android/cropped_$filename"
convert "maestro/images/expected/android/$filename" -crop +0+$status_bar_height "maestro/images/expected/android/cropped_$filename"
compare -metric AE -fuzz 0.3% "images/actual/android/cropped_$filename" "maestro/images/expected/android/cropped_$filename" "images/diffs/android/diff_$filename" 2>&1 | tee compare_output.txt
if grep -q "all: 0" compare_output.txt; then
echo "✅ Comparison passed for $filename"
else
echo "❌ Comparison failed for $filename"
failed_comparisons+=("$filename")
fi
fi
done
if [ ${#failed_comparisons[@]} -gt 0 ]; then
echo "❌ Failed Comparisons:"
for file in "${failed_comparisons[@]}"; do
echo " - $file"
done
echo "ANDROID_COMPARISON_FAILED=true" >> $GITHUB_ENV
else
echo "All comparisons passed!"
fi
else
echo "No Android screenshots found to compare."
fi
- name: "Compare iOS screenshots"
run: |
if [ -d "images/actual/ios" ]; then
mkdir -p images/diffs/ios
failed_comparisons=()
for file in images/actual/ios/*.png; do
filename=$(basename $file)
if [ -f "maestro/images/expected/ios/$filename" ]; then
# Detect the height of the status bar (assuming 50px height)
status_bar_height=50
actual_height=$(identify -format "%h" "images/actual/ios/$filename")
cropped_height=$((actual_height - status_bar_height))
convert "images/actual/ios/$filename" -crop +0+$status_bar_height "images/actual/ios/cropped_$filename"
convert "maestro/images/expected/ios/$filename" -crop +0+$status_bar_height "maestro/images/expected/ios/cropped_$filename"
compare -metric AE -fuzz 0.3% "images/actual/ios/cropped_$filename" "maestro/images/expected/ios/cropped_$filename" "images/diffs/ios/diff_$filename" 2>&1 | tee compare_output.txt
if grep -q "all: 0" compare_output.txt; then
echo "✅ Comparison passed for $filename"
else
echo "❌ Comparison failed for $filename"
failed_comparisons+=("$filename")
fi
fi
done
if [ ${#failed_comparisons[@]} -gt 0 ]; then
echo "❌ Failed Comparisons:"
for file in "${failed_comparisons[@]}"; do
echo " - $file"
done
echo "IOS_COMPARISON_FAILED=true" >> $GITHUB_ENV
else
echo "All comparisons passed!"
fi
else
echo "No iOS screenshots found to compare."
fi
- name: "Archive diff results"
uses: actions/upload-artifact@v4
with:
name: screenshot-diffs
path: images/diffs
if-no-files-found: ignore
- name: "Fail if comparisons failed"
run: |
if [ "$ANDROID_COMPARISON_FAILED" == "true" ] || [ "$IOS_COMPARISON_FAILED" == "true" ]; then
echo "One or more comparisons failed."
exit 1
fi