diff --git a/.buildkite/commands/build-ios.sh b/.buildkite/commands/build-ios.sh index 81a9696c21..2d50ac447d 100755 --- a/.buildkite/commands/build-ios.sh +++ b/.buildkite/commands/build-ios.sh @@ -2,9 +2,17 @@ PLATFORM=$(uname -s) ARCHITECTURE=$(uname -m) -PODFILE_HASH=$(hash_file gutenberg/packages/react-native-editor/ios/Podfile.lock) + +# Base Paths +REACT_NATIVE_EDITOR_PATH="gutenberg/packages/react-native-editor/ios" +BUILD_PATH="$REACT_NATIVE_EDITOR_PATH/build" +PRODUCTS_PATH="$BUILD_PATH/GutenbergDemo/Build/Products/Release-iphonesimulator" +APP_PATH="$PRODUCTS_PATH/GutenbergDemo.app" +PODS_PATH="$REACT_NATIVE_EDITOR_PATH" + +# Pods +PODFILE_HASH=$(hash_file "$REACT_NATIVE_EDITOR_PATH/Podfile.lock") PODFILE_CACHEKEY="$BUILDKITE_PIPELINE_SLUG-pods-$PLATFORM-$ARCHITECTURE-$PODFILE_HASH" -PODS_PATH="gutenberg/packages/react-native-editor/ios" PODS_FOLDER="Pods" echo '--- :desktop_computer: Clear up some disk space' @@ -12,6 +20,27 @@ rm -rfv ~/.Trash/15.1.xip .buildkite/commands/install-node-dependencies.sh +# Generate build key +find package-lock.json \ + gutenberg/packages/react-native-editor/ios \ + gutenberg/packages/react-native-aztec/ios \ + gutenberg/packages/react-native-bridge/ios \ + -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt +APP_BUILD_HASH=$(hash_file ios-checksums.txt) +APP_BUILD_CACHEKEY="$BUILDKITE_PIPELINE_SLUG-ios-app-$PLATFORM-$ARCHITECTURE-$APP_BUILD_HASH" +WDA_BUILD_CACHEKEY="$BUILDKITE_PIPELINE_SLUG-ios-wda-$PLATFORM-$ARCHITECTURE-$APP_BUILD_HASH" + +echo "--- :ios: Restore App build if present" +mkdir -p "$PRODUCTS_PATH" +pushd "$PRODUCTS_PATH" +restore_cache "$APP_BUILD_CACHEKEY" +popd + +echo "--- :ios: Restore WDA build if present" +pushd "$BUILD_PATH" +restore_cache "$WDA_BUILD_CACHEKEY" +popd + echo "--- :cocoapods: Restore Pods if present" pushd "$PODS_PATH" restore_cache "$PODFILE_CACHEKEY" @@ -20,35 +49,56 @@ popd echo '--- :ios: Set env var for iOS E2E testing' set -x export TEST_RN_PLATFORM=ios -export TEST_ENV=sauce +export TEST_ENV=local # We must use a simulator that's available on the selected Xcode version # otherwsie Xcode fallbacks to "generic destination" which requires provision # profiles to built the Demo app. export RN_EDITOR_E2E_IOS_DESTINATION="platform=iOS Simulator,name=iPhone 15" set +x +echo "--- :react: Prepare tests setup" +npm run core test:e2e:setup + echo '--- :react: Build iOS bundle for E2E testing' npm run test:e2e:bundle:ios echo '--- :react: Build iOS app for E2E testing' -npm run core test:e2e:build-app:ios - -echo '--- :compression: Prepare artifact for SauceLabs upload' -WORK_DIR=$(pwd) \ - && pushd ./gutenberg/packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator \ - && zip -r "$WORK_DIR/gutenberg/packages/react-native-editor/ios/GutenbergDemo.app.zip" GutenbergDemo.app \ - && popd - -echo '--- :saucelabs: Upload app artifact to SauceLabs' -SAUCE_FILENAME=${BUILDKITE_BRANCH//[\/]/-} -curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" \ - --location \ - --request POST 'https://api.us-west-1.saucelabs.com/v1/storage/upload' \ - --form 'payload=@"./gutenberg/packages/react-native-editor/ios/GutenbergDemo.app.zip"' \ - --form "name=Gutenberg-$SAUCE_FILENAME.app.zip" \ - --form 'description="Gutenberg"' +test -e "$APP_PATH/GutenbergDemo" || npm run core test:e2e:build-app:ios + +echo '--- :react: Build WDA for E2E testing' +test -d "$BUILD_PATH/WDA" || npm run core test:e2e:build-wda + +echo '--- :compression: Prepare artifacts' +# Set the working directory +WORK_DIR=$(pwd) + +# Compress the GutenbergDemo.app +pushd "$PRODUCTS_PATH" +zip -r "$WORK_DIR/$REACT_NATIVE_EDITOR_PATH/GutenbergDemo.app.zip" GutenbergDemo.app +popd + +# Compress the WDA directory +pushd "$BUILD_PATH/WDA" +zip -r "$WORK_DIR/$REACT_NATIVE_EDITOR_PATH/WDA.zip" ./* +popd + +echo "--- :arrow_up: Upload Build" +upload_artifact "$REACT_NATIVE_EDITOR_PATH/GutenbergDemo.app.zip" +upload_artifact "$REACT_NATIVE_EDITOR_PATH/WDA.zip" echo "--- :cocoapods: Save Pods cache if necessary" pushd "$PODS_PATH" save_cache "$PODS_FOLDER" "$PODFILE_CACHEKEY" popd + +echo "--- :ios: Save App build cache if necessary" +rm "$APP_PATH/main.jsbundle" +rm -rf "$APP_PATH/assets" +pushd "$PRODUCTS_PATH" +save_cache "GutenbergDemo.app" "$APP_BUILD_CACHEKEY" +popd + +echo "--- :ios: Save WDA build cache if necessary" +pushd "$BUILD_PATH" +save_cache "WDA" "$WDA_BUILD_CACHEKEY" +popd \ No newline at end of file diff --git a/.buildkite/commands/test-ios.sh b/.buildkite/commands/test-ios.sh index faa905564b..94ae3729aa 100755 --- a/.buildkite/commands/test-ios.sh +++ b/.buildkite/commands/test-ios.sh @@ -1,5 +1,9 @@ #!/bin/bash -eu +CONFIG_FILE="$(pwd)/gutenberg/packages/react-native-editor/__device-tests__/helpers/device-config.json" +DEVICE_NAME=$(jq -r '.ios.local.deviceName' "$CONFIG_FILE") +DEVICE_TABLET_NAME=$(jq -r '.ios.local.deviceTabletName' "$CONFIG_FILE") + MODE="iphone" INPUT="${1-}" while [ "$INPUT" != "" ]; do @@ -19,6 +23,29 @@ while [ "$INPUT" != "" ]; do INPUT="${1-}" done +if [ "$MODE" == 'canary' ]; then + SECTION='--- :react: Test iOS Canary Pages' + TESTS_CMD='device-tests-canary' +elif [ "$MODE" == "ipad" ]; then + SECTION='--- :react: Test iOS iPad' + DEVICE_NAME=$DEVICE_TABLET_NAME + TESTS_CMD='device-tests-ipad' +else + SECTION='--- :react: Test iOS iPhone' + TESTS_CMD='device-tests' +fi + +echo "--- :ios: Start booting up simulator" +xcrun simctl boot "$DEVICE_NAME" & + +echo "--- 📦 Downloading Build Artifacts" +export IOS_APP_PATH=./gutenberg/packages/react-native-editor/ios/GutenbergDemo.app.zip +download_artifact "GutenbergDemo.app.zip" "$IOS_APP_PATH" + +export WDA_PATH=./gutenberg/packages/react-native-editor/ios/build/WDA +download_artifact "WDA.zip" "$WDA_PATH/WDA.zip" +unzip "$WDA_PATH/WDA.zip" -d "$WDA_PATH" + # First, restore the caches, if any .buildkite/commands/install-node-dependencies.sh --restore-only # Second, set up the gutenberg-mobile dependencies without building the i18n cache (--ignore-scripts) @@ -27,28 +54,27 @@ echo "--- :npm: Install Node dependencies" npm ci --prefer-offline --no-progress --no-audit --ignore-scripts # Finally, set up the gutenberg submodule dependencies, bypassed by the step above. # We need them because some E2E logic lives in gutenberg. -npm ci --prefer-offline --no-progress --no-audit --prefix gutenberg +npm ci --prefer-offline --no-progress --no-audit --prefix gutenberg --ignore-scripts echo '--- :ios: Set env var for iOS E2E testing' set -x export TEST_RN_PLATFORM=ios -export TEST_ENV=sauce +export TEST_ENV=local export JEST_JUNIT_OUTPUT_FILE="reports/test-results/ios-test-results.xml" # This is a relic of the CircleCI setup. # It should be removed once the migration to Buildkite is completed. export CIRCLE_BRANCH=${BUILDKITE_BRANCH} set +x -if [ "$MODE" == 'canary' ]; then - SECTION='--- :saucelabs: Test iOS Canary Pages' - TESTS_CMD='device-tests-canary' -elif [ "$MODE" == "ipad" ]; then - SECTION='--- :saucelabs: Test iOS iPad' - TESTS_CMD='device-tests-ipad' -else - SECTION='--- :saucelabs: Test iOS iPhone' - TESTS_CMD='device-tests' -fi +echo "--- 📦 Install WDA" + +APP_PATH="$WDA_PATH/Build/Products/Debug-iphonesimulator/WebDriverAgentRunner-Runner.app" + +# Install the app to the booted simulator +xcrun simctl install booted $APP_PATH & + +echo "--- :react: Prepare tests setup" +npm run core test:e2e:setup set +e echo "$SECTION" diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 03ac4deae4..9d4df26af2 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -139,8 +139,8 @@ steps: queue: mac env: *xcode_agent_env - - label: iOS Build and Sauce Labs - key: ios-build-and-saucelabs + - label: iOS Build + key: ios-build command: .buildkite/commands/build-ios.sh plugins: - *ci_toolkit_plugin @@ -152,13 +152,14 @@ steps: env: *xcode_agent_env - label: Test iOS on Device – Canary Pages - depends_on: ios-build-and-saucelabs + depends_on: ios-build command: .buildkite/commands/test-ios.sh --canary plugins: - *ci_toolkit_plugin - *nvm_plugin artifact_paths: - reports/test-results/ios-test-results.xml + - ios-screen-recordings/* agents: queue: mac env: *xcode_agent_env @@ -198,13 +199,13 @@ steps: if: *is_branch_for_quick_ui_tests key: run-full-ui-test prompt: "Run full UI tests suites?" - depends_on: ios-build-and-saucelabs + depends_on: ios - label: Test iOS on Device – Full iPhone # The quick UI tests suite version depends on the block step being unblocked if: *is_branch_for_quick_ui_tests depends_on: - - ios-build-and-saucelabs + - ios-build - run-full-ui-test command: .buildkite/commands/test-ios.sh plugins: @@ -221,7 +222,7 @@ steps: # The full UI tests suite version depends only on the ios-build step, meaning it has no manual step that triggers it if: *is_branch_for_full_ui_tests depends_on: - - ios-build-and-saucelabs + - ios-build command: .buildkite/commands/test-ios.sh plugins: - *ci_toolkit_plugin @@ -236,7 +237,7 @@ steps: # The quick UI tests suite version depends on the block step being unblocked if: *is_branch_for_quick_ui_tests depends_on: - - ios-build-and-saucelabs + - ios-build - run-full-ui-test command: .buildkite/commands/test-ios.sh --ipad plugins: @@ -252,7 +253,7 @@ steps: # The full UI tests suite version depends only on the ios-build step, meaning it has no manual step that triggers it if: *is_branch_for_full_ui_tests depends_on: - - ios-build-and-saucelabs + - ios-build command: .buildkite/commands/test-ios.sh --ipad plugins: - *ci_toolkit_plugin diff --git a/bin/test-e2e.sh b/bin/test-e2e.sh new file mode 100755 index 0000000000..1052b684b4 --- /dev/null +++ b/bin/test-e2e.sh @@ -0,0 +1,30 @@ +#!/bin/bash -e + +set -o pipefail + +# Using the ':' operator with ':=' to check if a variable is set. +# If the variable is unset or null, the value after ':=' is assigned to it. +: ${IOS_APP_PATH:='./gutenberg/packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app'} +: ${WDA_PATH:='./gutenberg/packages/react-native-editor/ios/build/WDA'} +: ${ANDROID_APP_PATH:='./gutenberg/packages/react-native-editor/android/app/build/outputs/apk/debug/app-debug.apk'} + +export IOS_APP_PATH +export WDA_PATH +export ANDROID_APP_PATH + +export APPIUM_HOME=~/.appium +export NODE_ENV=test + +if [ "$TEST_RN_PLATFORM" == "android" ]; then + MAX_WORKERS=2 +else + MAX_WORKERS=1 +fi + +# Check for debug mode +if [ "$1" == "--debug" ]; then + shift # Remove first argument + node $NODE_DEBUG_OPTION --inspect-brk node_modules/jest/bin/jest --runInBand --detectOpenHandles --verbose --config jest_ui.config.js "$@" +else + jest --config ./jest_ui.config.js --maxWorkers $MAX_WORKERS --forceExit "$@" +fi \ No newline at end of file diff --git a/gutenberg b/gutenberg index 1ee3cc7615..923d3128af 160000 --- a/gutenberg +++ b/gutenberg @@ -1 +1 @@ -Subproject commit 1ee3cc7615773e50077bffb078904cdbe1efbe04 +Subproject commit 923d3128af4c4d40e386a2a0e14d7961a590623b diff --git a/package.json b/package.json index 2b68cb33e4..00bb1cced1 100644 --- a/package.json +++ b/package.json @@ -82,11 +82,11 @@ "test": "cross-env NODE_ENV=test jest --config ./jest.config.js", "test:update": "cross-env NODE_ENV=test jest --config ./jest.config.js --updateSnapshot", "test:debug": "cross-env NODE_ENV=test node --inspect-brk node_modules/.bin/jest --runInBand --verbose --config jest.config.js", - "device-tests": "cross-env NODE_ENV=test jest --maxWorkers=2 --testPathIgnorePatterns='canary|gutenberg-editor-rendering|ipad' --config jest_ui.config.js", - "device-tests-canary": "cross-env NODE_ENV=test jest --maxWorkers=2 --testPathPattern=@canary --config jest_ui.config.js", - "device-tests-ipad": "cross-env NODE_ENV=test IPAD=true jest --maxWorkers=2 --testPathPattern=@ipad --config jest_ui.config.js", - "device-tests:local": "APPIUM_HOME=~/.appium IOS_APP_PATH='./gutenberg/packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app' WDA_PATH='./gutenberg/packages/react-native-editor/ios/build/WDA' ANDROID_APP_PATH='./gutenberg/packages/react-native-editor/android/app/build/outputs/apk/debug/app-debug.apk' NODE_ENV=test jest --maxWorkers=2 --detectOpenHandles --config jest_ui.config.js", - "device-tests:debug": "IOS_APP_PATH='./gutenberg/packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app' WDA_PATH='./gutenberg/packages/react-native-editor/ios/build/WDA' ANDROID_APP_PATH='./gutenberg/packages/react-native-editor/android/app/build/outputs/apk/debug/app-debug.apk' cross-env NODE_ENV=test node $NODE_DEBUG_OPTION --inspect-brk node_modules/jest/bin/jest --runInBand --detectOpenHandles --verbose --config jest_ui.config.js", + "device-tests": "./bin/test-e2e.sh --testPathIgnorePatterns='canary|gutenberg-editor-rendering|ipad'", + "device-tests-canary": "./bin/test-e2e.sh --testPathPattern=@canary", + "device-tests-ipad": "IPAD=true ./bin/test-e2e.sh --testPathPattern=@ipad", + "device-tests:local": "./bin/test-e2e.sh --detectOpenHandles", + "device-tests:debug": "./bin/test-e2e.sh --debug --runInBand --detectOpenHandles --verbose", "test:e2e:bundle:android": "npm run test:e2e:bundle:android:text && npm run test:e2e:bundle:android:bytecode", "test:e2e:bundle:android:text": "mkdir -p gutenberg/packages/react-native-editor/android/app/src/main/assets && npm run rn-bundle -- -- -- -- --config ../../../metro.config.js --reset-cache --platform android --dev false --minify false --entry-file index.js --bundle-output ./android/app/src/main/assets/index.android.text.bundle --assets-dest ./android/app/src/main/res", "test:e2e:bundle:android:bytecode": "./gutenberg/node_modules/react-native/sdks/hermesc/`node -e \"const platform=require('os').platform();console.log(platform === 'darwin' ? 'osx-bin' : (platform === 'linux' ? 'linux64-bin' : (platform === 'win32' ? 'win64-bin' : 'unsupported-os')));\"`/hermesc -emit-binary -O -out gutenberg/packages/react-native-editor/android/app/src/main/assets/index.android.bundle gutenberg/packages/react-native-editor/android/app/src/main/assets/index.android.text.bundle -output-source-map",