From fdaaf72ab1ed971d70dae105ef99b0871b74d387 Mon Sep 17 00:00:00 2001 From: Stanislav Matveyev Date: Thu, 11 Jun 2026 10:16:57 +0300 Subject: [PATCH 1/5] fix: harden run-robot-suite-tests.sh setup and OS compatibility - Add --help/-h early exit before venv setup - Use python -m pip instead of bin/pip (absent on some Debian/Ubuntu venvs) - Recreate venv if pip is not importable - Preflight check for python3-venv with clear install instructions - Detect available Python 3 binary rather than assuming python3 is valid - Use python -m pip install --prefer-binary to avoid slow source builds - Fix suite filter passing literal quotes to robot (-s '*'.tests.api.suite) by using a bash array SUITE_FILTER=(-s "*.tests.api.suite") - Add set -o pipefail so pipes propagate real exit codes - On UI: detect playwright OS-unsupported error, patch platform detection in coreBundle.js to use ubuntu24.04 builds for ubuntu26.x hosts Co-Authored-By: Claude Sonnet 4.6 --- bin/run-robot-suite-tests.sh | 78 ++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/bin/run-robot-suite-tests.sh b/bin/run-robot-suite-tests.sh index df6ae1ac0..6bb54efb6 100755 --- a/bin/run-robot-suite-tests.sh +++ b/bin/run-robot-suite-tests.sh @@ -3,7 +3,7 @@ # Usage from robotframework-suite-tests/ folder: ./bin/run-tests-native.sh [api|ui] # Usage form suite/ folder: ./vendor/bin/run-tests-native.sh or vendor/spryker/robotframework-suite-tests/bin/run-robot-suite-tests.sh [api|ui] -set -e +set -eo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -28,6 +28,16 @@ VENV_DIR="$PROJECT_ROOT/.robot/.venv" TEST_TYPE="${1:-api}" TEST_PATH="${2:-}" +if [[ "$TEST_TYPE" == "--help" || "$TEST_TYPE" == "-h" ]]; then + echo "Usage: $0 [api|ui] [optional-test-path]" + echo "" + echo "Examples:" + echo " $0 api # Run all API tests" + echo " $0 api tests/api/mp_b2b/glue # Run specific tests" + echo " $0 api tests/api/mp_b2b/glue/cart_endpoints # Run even more specific tests" + exit 0 +fi + # Normalize TEST_PATH: strip TESTS_DIR prefix if user passed a full path from project root if [ -n "$TEST_PATH" ]; then TESTS_DIR_RELATIVE="${TESTS_DIR#$PROJECT_ROOT/}" @@ -42,10 +52,36 @@ echo "Tests Dir: $TESTS_DIR" echo "Results Dir: $RESULTS_DIR" echo "" -# Check if virtual environment exists and is valid -if [ ! -f "$VENV_DIR/bin/python" ]; then - echo "๐Ÿ“ฆ Creating virtual environment..." - python3 -m venv "$VENV_DIR" +# Find Python 3 (prefer python3, fall back to versioned binaries) +PYTHON_BIN="" +for candidate in python3 python3.14 python3.13 python3.12 python3.11; do + if command -v "$candidate" > /dev/null 2>&1; then + major=$("$candidate" -c "import sys; print(sys.version_info.major)" 2>/dev/null) + if [ "$major" -eq 3 ]; then + PYTHON_BIN="$candidate" + break + fi + fi +done + +if [ -z "$PYTHON_BIN" ]; then + echo "โŒ Python 3 not found. Install it with:" + echo " sudo apt install python3 python3-venv # Debian/Ubuntu" + echo " brew install python3 # macOS" + exit 1 +fi + +# Check if virtual environment exists and is valid (python present and pip importable) +if [ ! -f "$VENV_DIR/bin/python" ] || ! "$VENV_DIR/bin/python" -c "import pip" 2>/dev/null; then + if ! "$PYTHON_BIN" -c "import venv" 2>/dev/null; then + echo "โŒ python3-venv is not installed. Install it with:" + echo " sudo apt install python3-venv # Debian/Ubuntu" + echo " brew install python3 # macOS" + exit 1 + fi + echo "๐Ÿ“ฆ Creating virtual environment with $PYTHON_BIN..." + rm -rf "$VENV_DIR" + "$PYTHON_BIN" -m venv "$VENV_DIR" echo "โœ… Virtual environment created" echo "" fi @@ -53,8 +89,8 @@ fi # Check if dependencies are installed (check for robot command) if [ ! -f "$VENV_DIR/bin/robot" ]; then echo "๐Ÿ“ฆ Installing Robot Framework dependencies..." - "$VENV_DIR/bin/pip" install --upgrade pip - "$VENV_DIR/bin/pip" install -U -r "$TESTS_DIR/requirements.txt" + "$VENV_DIR/bin/python" -m pip install --upgrade pip + "$VENV_DIR/bin/python" -m pip install --prefer-binary -U -r "$TESTS_DIR/requirements.txt" echo "โœ… Dependencies installed" echo "" fi @@ -87,12 +123,12 @@ case "$TEST_TYPE" in if [ -z "$TEST_PATH" ]; then # No path specified - run all API tests TEST_TARGET="." - SUITE_OPTION="-s '*'.tests.api.suite" + SUITE_FILTER=(-s "*.tests.api.suite") echo "๐Ÿงช Running API tests (all)..." else # Path specified - run specific tests TEST_TARGET="$TEST_PATH" - SUITE_OPTION="" + SUITE_FILTER=() echo "๐Ÿงช Running API tests..." echo "Target: $TEST_PATH" fi @@ -107,8 +143,8 @@ case "$TEST_TYPE" in -v ignore_console:false \ -d "$RESULTS_DIR" \ --exclude skip-due-to-issueORskip-due-to-refactoring \ - $SUITE_OPTION \ - $TEST_TARGET + "${SUITE_FILTER[@]}" \ + "$TEST_TARGET" ;; ui) @@ -118,7 +154,25 @@ case "$TEST_TYPE" in if [ ! -d "$BROWSER_PATH" ] || [ -z "$(find "$BROWSER_PATH" -type d -name "chromium-*" 2>/dev/null)" ]; then echo "๐Ÿ“ฆ Installing Chromium browser (first time only)..." - rfbrowser init chromium + WRAPPER_DIR="$SITE_PACKAGES/Browser/wrapper" + RFBROWSER_LOG=$(rfbrowser init chromium 2>&1) || { + if echo "$RFBROWSER_LOG" | grep -q "does not support chromium"; then + # rfbrowser's npm install already ran and succeeded; only the browser + # download failed because playwright-core has no builds for this OS. + # Patch the platform detection to map ubuntu26.x -> ubuntu24.04 builds, + # then download the browser directly without re-running rfbrowser init + # (which would reset node_modules). + echo "โš ๏ธ Playwright does not support this OS yet. Patching platform detection to use ubuntu24.04 builds..." + CORE_BUNDLE="$WRAPPER_DIR/node_modules/playwright-core/lib/coreBundle.js" + sed -i 's/if (major < 26)/if (major < 28)/g' "$CORE_BUNDLE" + cd "$WRAPPER_DIR" + node_modules/.bin/playwright install chromium + cd - > /dev/null + else + echo "$RFBROWSER_LOG" + exit 1 + fi + } echo "โœ… Chromium installed" else echo "โœ… Chromium already installed at $BROWSER_PATH, skipping download" From a7ffff4d2985ff4b0dd313832fade25ef8fed4a5 Mon Sep 17 00:00:00 2001 From: Stanislav Matveyev Date: Thu, 11 Jun 2026 10:21:01 +0300 Subject: [PATCH 2/5] feat: add --headed flag for UI tests to run with visible browser Defaults to headless. Pass --headed to open browser windows during UI test execution. Works with pabot (each worker opens its own window). Co-Authored-By: Claude Sonnet 4.6 --- bin/run-robot-suite-tests.sh | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/bin/run-robot-suite-tests.sh b/bin/run-robot-suite-tests.sh index 6bb54efb6..c0d313bc3 100755 --- a/bin/run-robot-suite-tests.sh +++ b/bin/run-robot-suite-tests.sh @@ -25,16 +25,29 @@ fi VENV_DIR="$PROJECT_ROOT/.robot/.venv" -TEST_TYPE="${1:-api}" -TEST_PATH="${2:-}" +HEADLESS="true" +POSITIONAL=() +for arg in "$@"; do + case "$arg" in + --headed) HEADLESS="false" ;; + *) POSITIONAL+=("$arg") ;; + esac +done + +TEST_TYPE="${POSITIONAL[0]:-api}" +TEST_PATH="${POSITIONAL[1]:-}" if [[ "$TEST_TYPE" == "--help" || "$TEST_TYPE" == "-h" ]]; then - echo "Usage: $0 [api|ui] [optional-test-path]" + echo "Usage: $0 [api|ui] [optional-test-path] [--headed]" + echo "" + echo "Options:" + echo " --headed Run UI tests in headed (visible) browser mode (default: headless)" echo "" echo "Examples:" echo " $0 api # Run all API tests" - echo " $0 api tests/api/mp_b2b/glue # Run specific tests" - echo " $0 api tests/api/mp_b2b/glue/cart_endpoints # Run even more specific tests" + echo " $0 api tests/api/mp_b2b/glue # Run specific API tests" + echo " $0 ui # Run UI tests headless" + echo " $0 ui --headed # Run UI tests with visible browser" exit 0 fi @@ -50,6 +63,9 @@ echo "Test Type: $TEST_TYPE" echo "Project Root: $PROJECT_ROOT" echo "Tests Dir: $TESTS_DIR" echo "Results Dir: $RESULTS_DIR" +if [[ "$TEST_TYPE" == "ui" ]]; then + echo "Headless: $HEADLESS" +fi echo "" # Find Python 3 (prefer python3, fall back to versioned binaries) @@ -189,7 +205,7 @@ case "$TEST_TYPE" in --listener resources/libraries/failure_detail_listener.py \ -v env:ui_suite \ -v docker:false \ - -v headless:true \ + -v headless:$HEADLESS \ -v ignore_console:false \ -v dms:true \ -v project_location:$PROJECT_ROOT \ @@ -205,7 +221,7 @@ case "$TEST_TYPE" in --listener resources/libraries/failure_detail_listener.py \ -v env:ui_suite \ -v docker:false \ - -v headless:true \ + -v headless:$HEADLESS \ -v ignore_console:false \ -v dms:true \ -v project_location:$PROJECT_ROOT \ @@ -227,7 +243,7 @@ case "$TEST_TYPE" in -v env:ui_suite \ -v docker:false \ -v dms:true \ - -v headless:true \ + -v headless:$HEADLESS \ -v ignore_console:false \ -v project_location:$PROJECT_ROOT \ -d "$RESULTS_DIR/rerun" \ From e76587f26474bbd8b7134ab7a8a6791120dab983 Mon Sep 17 00:00:00 2001 From: Stanislav Matveyev Date: Thu, 11 Jun 2026 10:42:49 +0300 Subject: [PATCH 3/5] fix: start shared rfbrowser node server for headed mode with pabot When pabot spawns worker processes, each one starts its own Node.js rfbrowser server that has no access to the parent DISPLAY. The documented fix (Browser/browser.py) is to start one shared server in the main process (which has DISPLAY) and point all workers to it via ROBOT_FRAMEWORK_BROWSER_NODE_PORT before pabot runs. Co-Authored-By: Claude Sonnet 4.6 --- bin/run-robot-suite-tests.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/bin/run-robot-suite-tests.sh b/bin/run-robot-suite-tests.sh index c0d313bc3..95262bb34 100755 --- a/bin/run-robot-suite-tests.sh +++ b/bin/run-robot-suite-tests.sh @@ -197,6 +197,21 @@ case "$TEST_TYPE" in # Create subdirectories mkdir -p "$RESULTS_DIR/dynamic_set" "$RESULTS_DIR/dynamic_set/pabot_results" "$RESULTS_DIR/static_set" "$RESULTS_DIR/rerun" + # In headed mode, pabot workers each spawn their own Node.js rfbrowser server + # which has no display access. The documented fix (Browser/browser.py) is to start + # one shared server in the main process (which has DISPLAY) and point all workers + # to it via ROBOT_FRAMEWORK_BROWSER_NODE_PORT. + if [ "$HEADLESS" = "false" ]; then + RF_NODE_PORT=$(python3 -c "import socket; s=socket.socket(); s.bind(('', 0)); p=s.getsockname()[1]; s.close(); print(p)") + echo "๐ŸŒ Starting shared rfbrowser node server on port $RF_NODE_PORT..." + node "$SITE_PACKAGES/Browser/wrapper/index.js" 127.0.0.1 "$RF_NODE_PORT" & + RF_NODE_PID=$! + export ROBOT_FRAMEWORK_BROWSER_NODE_PORT="$RF_NODE_PORT" + trap "kill $RF_NODE_PID 2>/dev/null || true" EXIT + sleep 1 + echo "โœ… rfbrowser node server started (PID $RF_NODE_PID)" + fi + echo "๐Ÿงช Running UI tests..." cd $TESTS_DIR From 9a3f9c0560d3907c0890f6788ef5e5ca96977a40 Mon Sep 17 00:00:00 2001 From: Stanislav Matveyev Date: Thu, 11 Jun 2026 11:20:13 +0300 Subject: [PATCH 4/5] refactor: run headed UI tests with robot instead of pabot pabot worker subprocesses have no DISPLAY access so headed browsers never appear. In headed mode, skip pabot entirely and run all smoke tests sequentially with a single robot process. Co-Authored-By: Claude Sonnet 4.6 --- bin/run-robot-suite-tests.sh | 160 ++++++++++++++++++----------------- 1 file changed, 82 insertions(+), 78 deletions(-) diff --git a/bin/run-robot-suite-tests.sh b/bin/run-robot-suite-tests.sh index 95262bb34..24e1927f1 100755 --- a/bin/run-robot-suite-tests.sh +++ b/bin/run-robot-suite-tests.sh @@ -169,7 +169,6 @@ case "$TEST_TYPE" in BROWSER_PATH="$SITE_PACKAGES/Browser/wrapper/node_modules/playwright-core/.local-browsers" if [ ! -d "$BROWSER_PATH" ] || [ -z "$(find "$BROWSER_PATH" -type d -name "chromium-*" 2>/dev/null)" ]; then - echo "๐Ÿ“ฆ Installing Chromium browser (first time only)..." WRAPPER_DIR="$SITE_PACKAGES/Browser/wrapper" RFBROWSER_LOG=$(rfbrowser init chromium 2>&1) || { if echo "$RFBROWSER_LOG" | grep -q "does not support chromium"; then @@ -194,87 +193,92 @@ case "$TEST_TYPE" in echo "โœ… Chromium already installed at $BROWSER_PATH, skipping download" fi - # Create subdirectories - mkdir -p "$RESULTS_DIR/dynamic_set" "$RESULTS_DIR/dynamic_set/pabot_results" "$RESULTS_DIR/static_set" "$RESULTS_DIR/rerun" - - # In headed mode, pabot workers each spawn their own Node.js rfbrowser server - # which has no display access. The documented fix (Browser/browser.py) is to start - # one shared server in the main process (which has DISPLAY) and point all workers - # to it via ROBOT_FRAMEWORK_BROWSER_NODE_PORT. - if [ "$HEADLESS" = "false" ]; then - RF_NODE_PORT=$(python3 -c "import socket; s=socket.socket(); s.bind(('', 0)); p=s.getsockname()[1]; s.close(); print(p)") - echo "๐ŸŒ Starting shared rfbrowser node server on port $RF_NODE_PORT..." - node "$SITE_PACKAGES/Browser/wrapper/index.js" 127.0.0.1 "$RF_NODE_PORT" & - RF_NODE_PID=$! - export ROBOT_FRAMEWORK_BROWSER_NODE_PORT="$RF_NODE_PORT" - trap "kill $RF_NODE_PID 2>/dev/null || true" EXIT - sleep 1 - echo "โœ… rfbrowser node server started (PID $RF_NODE_PID)" - fi - echo "๐Ÿงช Running UI tests..." cd $TESTS_DIR - echo "Running dynamic smoke tests with $PROCESSES parallel processes (detected $CPU_COUNT CPUs)..." - pabot --processes "$PROCESSES" --testlevelsplit \ - --listener resources/libraries/failure_detail_listener.py \ - -v env:ui_suite \ - -v docker:false \ - -v headless:$HEADLESS \ - -v ignore_console:false \ - -v dms:true \ - -v project_location:$PROJECT_ROOT \ - -d "$RESULTS_DIR/dynamic_set" \ - --exclude skip-due-to-issueORskip-due-to-refactoringORstatic-set \ - --include smoke \ - -s '*'.tests.parallel_ui.suite \ - . || true - - echo "" - echo "Running static smoke tests sequentially..." - robot \ - --listener resources/libraries/failure_detail_listener.py \ - -v env:ui_suite \ - -v docker:false \ - -v headless:$HEADLESS \ - -v ignore_console:false \ - -v dms:true \ - -v project_location:$PROJECT_ROOT \ - -d "$RESULTS_DIR/static_set" \ - --exclude skip-due-to-issueORskip-due-to-refactoring \ - --include static-setANDsmoke \ - -s '*'.tests.parallel_ui.suite \ - . || true - - # Merge results - echo "Merging test results..." - rebot -d "$RESULTS_DIR" --output output.xml --merge \ - "$RESULTS_DIR/dynamic_set/output.xml" \ - "$RESULTS_DIR/static_set/output.xml" || true - - echo "Rerunning failed tests..." - robot \ - --listener resources/libraries/failure_detail_listener.py \ - -v env:ui_suite \ - -v docker:false \ - -v dms:true \ - -v headless:$HEADLESS \ - -v ignore_console:false \ - -v project_location:$PROJECT_ROOT \ - -d "$RESULTS_DIR/rerun" \ - --runemptysuite \ - --rerunfailed "$RESULTS_DIR/output.xml" \ - --output rerun.xml \ - -s '*'.tests.parallel_ui.suite \ - $TESTS_DIR || true - - if [ -f "$RESULTS_DIR/rerun/rerun.xml" ] && [ -s "$RESULTS_DIR/rerun/rerun.xml" ]; then - echo "Merging rerun results..." - rebot -d "$RESULTS_DIR" --merge \ - "$RESULTS_DIR/output.xml" \ - "$RESULTS_DIR/rerun/rerun.xml" || true + if [ "$HEADLESS" = "false" ]; then + # Headed mode: run sequentially with robot so the browser window is visible. + # pabot spawns isolated subprocesses whose Node.js rfbrowser servers have no + # DISPLAY access, so headed mode only works with a single robot process. + echo "Running all smoke tests sequentially (headed mode)..." + robot \ + --listener resources/libraries/failure_detail_listener.py \ + -v env:ui_suite \ + -v docker:false \ + -v headless:false \ + -v ignore_console:false \ + -v dms:true \ + -v project_location:$PROJECT_ROOT \ + -d "$RESULTS_DIR" \ + --exclude skip-due-to-issueORskip-due-to-refactoring \ + --include smoke \ + -s '*'.tests.parallel_ui.suite \ + . else - echo "โœ… All tests passed on first run, no rerun needed" + # Headless mode: run dynamic tests in parallel with pabot, static tests + # sequentially, then merge and rerun failures. + mkdir -p "$RESULTS_DIR/dynamic_set" "$RESULTS_DIR/dynamic_set/pabot_results" "$RESULTS_DIR/static_set" "$RESULTS_DIR/rerun" + + echo "Running dynamic smoke tests with $PROCESSES parallel processes (detected $CPU_COUNT CPUs)..." + pabot --processes "$PROCESSES" --testlevelsplit \ + --listener resources/libraries/failure_detail_listener.py \ + -v env:ui_suite \ + -v docker:false \ + -v headless:true \ + -v ignore_console:false \ + -v dms:true \ + -v project_location:$PROJECT_ROOT \ + -d "$RESULTS_DIR/dynamic_set" \ + --exclude skip-due-to-issueORskip-due-to-refactoringORstatic-set \ + --include smoke \ + -s '*'.tests.parallel_ui.suite \ + . || true + + echo "" + echo "Running static smoke tests sequentially..." + robot \ + --listener resources/libraries/failure_detail_listener.py \ + -v env:ui_suite \ + -v docker:false \ + -v headless:true \ + -v ignore_console:false \ + -v dms:true \ + -v project_location:$PROJECT_ROOT \ + -d "$RESULTS_DIR/static_set" \ + --exclude skip-due-to-issueORskip-due-to-refactoring \ + --include static-setANDsmoke \ + -s '*'.tests.parallel_ui.suite \ + . || true + + echo "Merging test results..." + rebot -d "$RESULTS_DIR" --output output.xml --merge \ + "$RESULTS_DIR/dynamic_set/output.xml" \ + "$RESULTS_DIR/static_set/output.xml" || true + + echo "Rerunning failed tests..." + robot \ + --listener resources/libraries/failure_detail_listener.py \ + -v env:ui_suite \ + -v docker:false \ + -v dms:true \ + -v headless:true \ + -v ignore_console:false \ + -v project_location:$PROJECT_ROOT \ + -d "$RESULTS_DIR/rerun" \ + --runemptysuite \ + --rerunfailed "$RESULTS_DIR/output.xml" \ + --output rerun.xml \ + -s '*'.tests.parallel_ui.suite \ + $TESTS_DIR || true + + if [ -f "$RESULTS_DIR/rerun/rerun.xml" ] && [ -s "$RESULTS_DIR/rerun/rerun.xml" ]; then + echo "Merging rerun results..." + rebot -d "$RESULTS_DIR" --merge \ + "$RESULTS_DIR/output.xml" \ + "$RESULTS_DIR/rerun/rerun.xml" || true + else + echo "โœ… All tests passed on first run, no rerun needed" + fi fi ;; From a0dca9057cb36b80facf82132fd4b40b002c8aa4 Mon Sep 17 00:00:00 2001 From: Stanislav Matveyev Date: Thu, 11 Jun 2026 11:24:21 +0300 Subject: [PATCH 5/5] fix: set PLAYWRIGHT_BROWSERS_PATH=0 when installing chromium via fallback Without this, playwright installs to ~/.cache/ms-playwright/ instead of the rfbrowser wrapper's .local-browsers directory, causing a missing executable error at test runtime. Co-Authored-By: Claude Sonnet 4.6 --- bin/run-robot-suite-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/run-robot-suite-tests.sh b/bin/run-robot-suite-tests.sh index 24e1927f1..1f6837142 100755 --- a/bin/run-robot-suite-tests.sh +++ b/bin/run-robot-suite-tests.sh @@ -181,7 +181,7 @@ case "$TEST_TYPE" in CORE_BUNDLE="$WRAPPER_DIR/node_modules/playwright-core/lib/coreBundle.js" sed -i 's/if (major < 26)/if (major < 28)/g' "$CORE_BUNDLE" cd "$WRAPPER_DIR" - node_modules/.bin/playwright install chromium + PLAYWRIGHT_BROWSERS_PATH=0 node_modules/.bin/playwright install chromium cd - > /dev/null else echo "$RFBROWSER_LOG"