diff --git a/bridges/zombienet/helpers/chains/rococo-at-westend.js b/bridges/zombienet/helpers/chains/rococo-at-westend.js
new file mode 100644
index 0000000000000000000000000000000000000000..eb9510e46f0b7ba94e55968816accac185373c7c
--- /dev/null
+++ b/bridges/zombienet/helpers/chains/rococo-at-westend.js
@@ -0,0 +1,6 @@
+module.exports = {
+	grandpaPalletName: "bridgeRococoGrandpa",
+	parachainsPalletName: "bridgeRococoParachains",
+	messagesPalletName: "bridgeRococoMessages",
+    bridgedBridgeHubParaId: 1013,
+}
diff --git a/bridges/zombienet/helpers/chains/westend-at-rococo.js b/bridges/zombienet/helpers/chains/westend-at-rococo.js
new file mode 100644
index 0000000000000000000000000000000000000000..771a0778cb098c4e1e3a5c124c81cc38e2aac695
--- /dev/null
+++ b/bridges/zombienet/helpers/chains/westend-at-rococo.js
@@ -0,0 +1,6 @@
+module.exports = {
+	grandpaPalletName: "bridgeWestendGrandpa",
+	parachainsPalletName: "bridgeWestendParachains",
+	messagesPalletName: "bridgeWestendMessages",
+    bridgedBridgeHubParaId: 1002,
+}
diff --git a/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js b/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js
new file mode 100644
index 0000000000000000000000000000000000000000..3a3432cfaf38da93f3ea0e65657f266b66f84d74
--- /dev/null
+++ b/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js
@@ -0,0 +1,44 @@
+const utils = require("./utils");
+
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // parse arguments
+    const exitAfterSeconds = Number(args[0]);
+    const bridgedChain = require("./chains/" + args[1]);
+
+    // start listening to new blocks
+    let totalGrandpaHeaders = 0;
+    let totalParachainHeaders = 0;
+    api.rpc.chain.subscribeNewHeads(async function (header) {
+        const apiAtParent = await api.at(header.parentHash);
+        const apiAtCurrent = await api.at(header.hash);
+        const currentEvents = await apiAtCurrent.query.system.events();
+
+        totalGrandpaHeaders += await utils.ensureOnlyMandatoryGrandpaHeadersImported(
+            bridgedChain,
+            apiAtParent,
+            apiAtCurrent,
+            currentEvents,
+        );
+        totalParachainHeaders += await utils.ensureOnlyInitialParachainHeaderImported(
+            bridgedChain,
+            apiAtParent,
+            apiAtCurrent,
+            currentEvents,
+        );
+    });
+
+    // wait given time
+    await new Promise(resolve => setTimeout(resolve, exitAfterSeconds * 1000));
+    // if we haven't seen any new GRANDPA or parachain headers => fail
+    if (totalGrandpaHeaders == 0) {
+        throw new Error("No bridged relay chain headers imported");
+    }
+    if (totalParachainHeaders == 0) {
+        throw new Error("No bridged parachain headers imported");
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/zombienet/helpers/only-required-headers-synced-when-idle.js b/bridges/zombienet/helpers/only-required-headers-synced-when-idle.js
new file mode 100644
index 0000000000000000000000000000000000000000..8c3130e4fd960601d377dde5101520c95531cdf6
--- /dev/null
+++ b/bridges/zombienet/helpers/only-required-headers-synced-when-idle.js
@@ -0,0 +1,81 @@
+const utils = require("./utils");
+
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // parse arguments
+    const exitAfterSeconds = Number(args[0]);
+    const bridgedChain = require("./chains/" + args[1]);
+
+    // start listening to new blocks
+    let atLeastOneMessageReceived = false;
+    let atLeastOneMessageDelivered = false;
+    const unsubscribe = await api.rpc.chain.subscribeNewHeads(async function (header) {
+        const apiAtParent = await api.at(header.parentHash);
+        const apiAtCurrent = await api.at(header.hash);
+        const currentEvents = await apiAtCurrent.query.system.events();
+
+        const messagesReceived = currentEvents.find((record) => {
+            return record.event.section == bridgedChain.messagesPalletName
+                && record.event.method == "MessagesReceived";
+        }) != undefined;
+        const messagesDelivered = currentEvents.find((record) => {
+            return record.event.section == bridgedChain.messagesPalletName &&
+                record.event.method == "MessagesDelivered";
+        }) != undefined;
+        const hasMessageUpdates = messagesReceived || messagesDelivered;
+        atLeastOneMessageReceived = atLeastOneMessageReceived || messagesReceived;
+        atLeastOneMessageDelivered = atLeastOneMessageDelivered || messagesDelivered;
+
+        if (!hasMessageUpdates) {
+            // if there are no any message update transactions, we only expect mandatory GRANDPA
+            // headers and initial parachain headers
+            await utils.ensureOnlyMandatoryGrandpaHeadersImported(
+                bridgedChain,
+                apiAtParent,
+                apiAtCurrent,
+                currentEvents,
+            );
+            await utils.ensureOnlyInitialParachainHeaderImported(
+                bridgedChain,
+                apiAtParent,
+                apiAtCurrent,
+                currentEvents,
+            );
+        } else {
+            const messageTransactions = (messagesReceived ? 1 : 0) + (messagesDelivered ? 1 : 0);
+
+            // otherwise we only accept at most one GRANDPA header
+            const newGrandpaHeaders = utils.countGrandpaHeaderImports(bridgedChain, currentEvents);
+            if (newGrandpaHeaders > 1) {
+                utils.logEvents(currentEvents);
+                throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + messageTransactions);
+            }
+
+            // ...and at most one parachain header
+            const newParachainHeaders = utils.countParachainHeaderImports(bridgedChain, currentEvents);
+            if (newParachainHeaders > 1) {
+                utils.logEvents(currentEvents);
+                throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + messageTransactions);
+            }
+        }
+    });
+
+    // wait until we have received + delivered messages OR until timeout
+    await utils.pollUntil(
+        exitAfterSeconds,
+        () => { return atLeastOneMessageReceived && atLeastOneMessageDelivered; },
+        () => { unsubscribe(); },
+        () => {
+            if (!atLeastOneMessageReceived) {
+                throw new Error("No messages received from bridged chain");
+            }
+            if (!atLeastOneMessageDelivered) {
+                throw new Error("No messages delivered to bridged chain");
+            }
+        },
+    );
+}
+
+module.exports = { run }
diff --git a/bridges/zombienet/helpers/utils.js b/bridges/zombienet/helpers/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a5542b56dfc215a082fc6fbb8c1b9aa018de83e
--- /dev/null
+++ b/bridges/zombienet/helpers/utils.js
@@ -0,0 +1,103 @@
+module.exports = {
+    logEvents: function(events) {
+        let stringifiedEvents = "";
+        events.forEach((record) => {
+            if (stringifiedEvents != "") {
+                stringifiedEvents += ", ";
+            }
+            stringifiedEvents += record.event.section + "::" + record.event.method;
+        });
+        console.log("Block events: " + stringifiedEvents);
+    },
+    countGrandpaHeaderImports: function(bridgedChain, events) {
+        return events.reduce(
+            (count, record) => {
+                const { event } = record;
+                if (event.section == bridgedChain.grandpaPalletName && event.method == "UpdatedBestFinalizedHeader") {
+                    count += 1;
+                }
+                return count;
+            },
+            0,
+        );
+    },
+    countParachainHeaderImports: function(bridgedChain, events) {
+        return events.reduce(
+            (count, record) => {
+                const { event } = record;
+                if (event.section == bridgedChain.parachainsPalletName && event.method == "UpdatedParachainHead") {
+                    count += 1;
+                }
+                return count;
+            },
+            0,
+        );
+    },
+    pollUntil: async function(
+        timeoutInSecs,
+        predicate,
+        cleanup,
+        onFailure,
+    )  {
+        const begin = new Date().getTime();
+        const end = begin + timeoutInSecs * 1000;
+        while (new Date().getTime() < end) {
+            if (predicate()) {
+                cleanup();
+                return;
+            }
+            await new Promise(resolve => setTimeout(resolve, 100));
+        }
+
+        cleanup();
+        onFailure();
+    },
+    ensureOnlyMandatoryGrandpaHeadersImported: async function(
+        bridgedChain,
+        apiAtParent,
+        apiAtCurrent,
+        currentEvents,
+    ) {
+        // remember id of bridged relay chain GRANDPA authorities set at parent block
+        const authoritySetAtParent = await apiAtParent.query[bridgedChain.grandpaPalletName].currentAuthoritySet();
+        const authoritySetIdAtParent = authoritySetAtParent["setId"];
+
+        // now read the id of bridged relay chain GRANDPA authorities set at current block
+        const authoritySetAtCurrent = await apiAtCurrent.query[bridgedChain.grandpaPalletName].currentAuthoritySet();
+        const authoritySetIdAtCurrent = authoritySetAtCurrent["setId"];
+
+        // we expect to see no more than `authoritySetIdAtCurrent - authoritySetIdAtParent` new GRANDPA headers
+        const maxNewGrandpaHeaders = authoritySetIdAtCurrent - authoritySetIdAtParent;
+        const newGrandpaHeaders = module.exports.countGrandpaHeaderImports(bridgedChain, currentEvents);
+
+        // check that our assumptions are correct
+        if (newGrandpaHeaders > maxNewGrandpaHeaders) {
+            module.exports.logEvents(currentEvents);
+            throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + maxNewGrandpaHeaders);
+        }
+
+        return newGrandpaHeaders;
+    },
+    ensureOnlyInitialParachainHeaderImported: async function(
+        bridgedChain,
+        apiAtParent,
+        apiAtCurrent,
+        currentEvents,
+    ) {
+        // remember whether we already know bridged parachain header at a parent block
+        const bestBridgedParachainHeader = await apiAtParent.query[bridgedChain.parachainsPalletName].parasInfo(bridgedChain.bridgedBridgeHubParaId);;
+        const hasBestBridgedParachainHeader = bestBridgedParachainHeader.isSome;
+
+        // we expect to see: no more than `1` bridged parachain header if there were no parachain header before.
+        const maxNewParachainHeaders = hasBestBridgedParachainHeader ? 0 : 1;
+        const newParachainHeaders = module.exports.countParachainHeaderImports(bridgedChain, currentEvents);
+
+        // check that our assumptions are correct
+        if (newParachainHeaders > maxNewParachainHeaders) {
+            module.exports.logEvents(currentEvents);
+            throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + maxNewParachainHeaders);
+        }
+
+        return newParachainHeaders;
+    },
+}
diff --git a/bridges/zombienet/run-tests.sh b/bridges/zombienet/run-tests.sh
index 34487e13261f375d0072817d667212d21b31f498..cf3b529e6a9d9823f875938d8603b363c6079136 100755
--- a/bridges/zombienet/run-tests.sh
+++ b/bridges/zombienet/run-tests.sh
@@ -1,10 +1,12 @@
 #!/bin/bash
-#set -eu
 set -x
 shopt -s nullglob
 
-trap "trap - SIGINT SIGTERM EXIT && kill -- -$$" SIGINT SIGTERM EXIT
+trap "trap - SIGINT SIGTERM EXIT && killall -q -9 substrate-relay && kill -- -$$" SIGINT SIGTERM EXIT
 
+# run tests in range [TESTS_BEGIN; TESTS_END)
+TESTS_BEGIN=1
+TESTS_END=1000
 # whether to use paths for zombienet+bridges tests container or for local testing
 ZOMBIENET_DOCKER_PATHS=0
 while [ $# -ne 0 ]
@@ -14,6 +16,11 @@ do
         --docker)
             ZOMBIENET_DOCKER_PATHS=1
             ;;
+        --test)
+            shift
+            TESTS_BEGIN="$1"
+            TESTS_END="$1"
+            ;;
     esac
     shift
 done
@@ -57,7 +64,8 @@ ALL_TESTS_FOLDER=`mktemp -d /tmp/bridges-zombienet-tests.XXXXX`
 function start_coproc() {
     local command=$1
     local name=$2
-    local coproc_log=`mktemp -p $TEST_FOLDER`
+    local logname=`basename $name`
+    local coproc_log=`mktemp -p $TEST_FOLDER $logname.XXXXX`
     coproc COPROC {
         # otherwise zombienet uses some hardcoded paths
         unset RUN_IN_CONTAINER
@@ -73,7 +81,7 @@ function start_coproc() {
 }
 
 # execute every test from tests folder
-TEST_INDEX=1
+TEST_INDEX=$TESTS_BEGIN
 while true
 do
     declare -A TEST_COPROCS
@@ -81,7 +89,7 @@ do
     TEST_PREFIX=$(printf "%04d" $TEST_INDEX)
 
     # it'll be used by the `sync-exit.sh` script
-    export TEST_FOLDER=`mktemp -d -p $ALL_TESTS_FOLDER`
+    export TEST_FOLDER=`mktemp -d -p $ALL_TESTS_FOLDER test-$TEST_PREFIX.XXXXX`
 
     # check if there are no more tests
     zndsl_files=($BRIDGE_TESTS_FOLDER/$TEST_PREFIX-*.zndsl)
@@ -89,12 +97,6 @@ do
         break
     fi
 
-    # start relay
-    if [ -f $BRIDGE_TESTS_FOLDER/$TEST_PREFIX-start-relay.sh ]; then
-        start_coproc "${BRIDGE_TESTS_FOLDER}/${TEST_PREFIX}-start-relay.sh" "relay"
-        RELAY_COPROC=$COPROC_PID
-        ((TEST_COPROCS_COUNT++))
-    fi
     # start tests
     for zndsl_file in "${zndsl_files[@]}"; do
         start_coproc "$ZOMBIENET_BINARY_PATH --provider native test $zndsl_file" "$zndsl_file"
@@ -102,7 +104,6 @@ do
         ((TEST_COPROCS_COUNT++))
     done
     # wait until all tests are completed
-    relay_exited=0
     for n in `seq 1 $TEST_COPROCS_COUNT`; do
         if [ "$IS_BASH_5_1" -eq 1 ]; then
             wait -n -p COPROC_PID
@@ -110,7 +111,6 @@ do
             coproc_name=${TEST_COPROCS[$COPROC_PID, 0]}
             coproc_log=${TEST_COPROCS[$COPROC_PID, 1]}
             coproc_stdout=$(cat $coproc_log)
-            relay_exited=$(expr "${coproc_name}" == "relay")
         else
             wait -n
             exit_code=$?
@@ -124,18 +124,20 @@ do
             echo "====================================================================="
             echo "=== Shutting down. Log of failed process below                    ==="
             echo "====================================================================="
-            echo $coproc_stdout
+            echo "$coproc_stdout"
 
             exit 1
         fi
-
-        # if last test has exited, exit relay too
-        if [ $n -eq $(($TEST_COPROCS_COUNT - 1)) ] && [ $relay_exited -eq 0 ]; then
-            kill $RELAY_COPROC
-            break
-        fi
     done
+
+    # proceed to next index
     ((TEST_INDEX++))
+    if [ "$TEST_INDEX" -ge "$TESTS_END" ]; then
+        break
+    fi
+
+    # kill relay here - it is started manually by tests
+    killall substrate-relay
 done
 
 echo "====================================================================="
diff --git a/bridges/zombienet/scripts/invoke-script.sh b/bridges/zombienet/scripts/invoke-script.sh
index 6a3754a8824017e18409cde031be9a09e9392a75..835b4fe500f01ea2968bcb8bff538491ec7149bc 100755
--- a/bridges/zombienet/scripts/invoke-script.sh
+++ b/bridges/zombienet/scripts/invoke-script.sh
@@ -1,5 +1,7 @@
 #!/bin/bash
 
+INVOKE_LOG=`mktemp -p $TEST_FOLDER invoke.XXXXX`
+
 pushd $POLKADOT_SDK_FOLDER/cumulus/scripts
-./bridges_rococo_westend.sh $1
+./bridges_rococo_westend.sh $1 >$INVOKE_LOG 2>&1
 popd
diff --git a/bridges/zombienet/scripts/start-relayer.sh b/bridges/zombienet/scripts/start-relayer.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2f72b5ee556bcc8a89b2de4c5d3c53db8ac072b1
--- /dev/null
+++ b/bridges/zombienet/scripts/start-relayer.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+RELAY_LOG=`mktemp -p $TEST_FOLDER relay.XXXXX`
+
+pushd $POLKADOT_SDK_FOLDER/cumulus/scripts
+./bridges_rococo_westend.sh run-relay >$RELAY_LOG 2>&1&
+popd
diff --git a/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl b/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl
index fe7dc26b001125342f449911c8808b9b9dcf5775..82d1eee2f45cc12b60a85b829d4a4c17588fa9e7 100644
--- a/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl
+++ b/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl
@@ -2,31 +2,36 @@ Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset
 Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml
 Creds: config
 
+# step 0: start relayer
+# (started by sibling 0001-asset-transfer-works-westend-to-rococo.zndsl test)
+
 # step 1: initialize Westend AH
-asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 240 seconds
-asset-hub-westend-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1002" within 400 seconds
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 60 seconds
 
 # step 2: initialize Westend bridge hub
-bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 120 seconds
+bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds
+
+# step 3: ensure that initialization has completed
+asset-hub-westend-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds
 
-# step 3: relay is started elsewhere - let's wait until with-Rococo GRANPDA pallet is initialized at Westend
+# step 4: relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend
 bridge-hub-westend-collator1: js-script ../helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds
 
-# step 4: send WND to //Alice on Rococo AH
+# step 5: send WND to //Alice on Rococo AH
 # (that's a required part of a sibling 0001-asset-transfer-works-westend-to-rococo.zndsl test)
-asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 120 seconds
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds
 
-# step 5: elsewhere Rococo has sent ROC to //Alice - let's wait for it
+# step 6: elsewhere Rococo has sent ROC to //Alice - let's wait for it
 asset-hub-westend-collator1: js-script ../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 600 seconds
 
-# step 6: check that the relayer //Charlie is rewarded by both our AH and target AH
+# step 7: check that the relayer //Charlie is rewarded by both our AH and target AH
 bridge-hub-westend-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726f,BridgedChain,0" within 300 seconds
 bridge-hub-westend-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 300 seconds
 
-# step 7: send wROC back to Alice at Rococo AH
-asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 120 seconds
+# step 8: send wROC back to Alice at Rococo AH
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 60 seconds
 
-# step 8: elsewhere Rococo has sent wWND to //Alice - let's wait for it
+# step 9: elsewhere Rococo has sent wWND to //Alice - let's wait for it
 # (we wait until //Alice account increases here - there are no other transactionc that may increase it)
 asset-hub-westend-collator1: js-script ../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 600 seconds
 
diff --git a/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl b/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl
index 610b4ca7acdc372323f8781478722c0b9613d162..acfe0df03d26779abf0dd3c2aa3dfc8f37c0e3aa 100644
--- a/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl
+++ b/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl
@@ -2,31 +2,36 @@ Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset
 Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml
 Creds: config
 
+# step 0: start relayer
+bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds
+
 # step 1: initialize Rococo AH
-asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 240 seconds
-asset-hub-rococo-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1013" within 400 seconds
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 60 seconds
 
 # step 2: initialize Rococo bridge hub
-bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 120 seconds
+bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds
+
+# step 3: ensure that initialization has completed
+asset-hub-rococo-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds
 
-# step 3: relay is started elsewhere - let's wait until with-Westend GRANPDA pallet is initialized at Rococo
+# step 4: relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo
 bridge-hub-rococo-collator1: js-script ../helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds
 
-# step 4: send ROC to //Alice on Westend AH
+# step 5: send ROC to //Alice on Westend AH
 # (that's a required part of a sibling 0001-asset-transfer-works-rococo-to-westend.zndsl test)
-asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 120 seconds
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds
 
-# step 5: elsewhere Westend has sent WND to //Alice - let's wait for it
+# step 6: elsewhere Westend has sent WND to //Alice - let's wait for it
 asset-hub-rococo-collator1: js-script ../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 600 seconds
 
-# step 6: check that the relayer //Charlie is rewarded by both our AH and target AH
+# step 7: check that the relayer //Charlie is rewarded by both our AH and target AH
 bridge-hub-rococo-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,BridgedChain,0" within 300 seconds
 bridge-hub-rococo-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 300 seconds
 
-# step 7: send wWND back to Alice at Westend AH
-asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 120 seconds
+# step 8: send wWND back to Alice at Westend AH
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 60 seconds
 
-# step 8: elsewhere Westend has sent wROC to //Alice - let's wait for it
+# step 9: elsewhere Westend has sent wROC to //Alice - let's wait for it
 # (we wait until //Alice account increases here - there are no other transactionc that may increase it)
 asset-hub-rococo-collator1: js-script ../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 600 seconds
 
diff --git a/bridges/zombienet/tests/0001-start-relay.sh b/bridges/zombienet/tests/0001-start-relay.sh
deleted file mode 100755
index 7be2cf4d5938797b98b86e8abf08ae43a5cee449..0000000000000000000000000000000000000000
--- a/bridges/zombienet/tests/0001-start-relay.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-
-pushd $POLKADOT_SDK_FOLDER/cumulus/scripts
-./bridges_rococo_westend.sh run-relay
-popd
diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..eb6a75c373c7add04f895c01e332d40195150370
--- /dev/null
+++ b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl
@@ -0,0 +1,26 @@
+Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH.
+Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml
+Creds: config
+
+# step 1: initialize Westend bridge hub
+bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds
+
+# step 2: sleep some time before starting relayer. We want to sleep for at least 1 session, which is expected to
+# be 60 seconds for test environment.
+sleep 120 seconds
+
+# step 3: start relayer
+# (it is started by the sibling 0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl test file)
+
+# it also takes some time for relayer to initialize bridge, so let's sleep for 5 minutes to be sure that parachain
+# header has been synced
+
+# step 4: ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were
+# born while relay was offline and those in the next 100 seconds while script is active.
+bridge-hub-westend-collator1: js-script ../helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds
diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..728d54d586a9b46625e3db70251b68c6501db922
--- /dev/null
+++ b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl
@@ -0,0 +1,26 @@
+Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH.
+Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml
+Creds: config
+
+# step 1: initialize Rococo bridge hub
+bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds
+
+# step 2: sleep some time before starting relayer. We want to sleep for at least 1 session, which is expected to
+# be 60 seconds for test environment.
+sleep 120 seconds
+
+# step 3: start relayer
+bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds
+
+# it also takes some time for relayer to initialize bridge, so let's sleep for 5 minutes to be sure that parachain
+# header has been synced
+
+# step 4: ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were
+# born while relay was offline and those in the next 100 seconds while script is active.
+bridge-hub-rococo-collator1: js-script ../helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds
diff --git a/bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl b/bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..a4960344f0a03265d2accfa52cd9a4ab1d7117d6
--- /dev/null
+++ b/bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl
@@ -0,0 +1,26 @@
+Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH.
+Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml
+Creds: config
+
+# step 1: initialize Westend AH
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 60 seconds
+
+# step 2: initialize Westend bridge hub
+bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds
+
+# step 3: ensure that initialization has completed
+asset-hub-westend-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds
+
+# step 4: send message from Westend to Rococo
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds
+
+# step 5: start relayer
+# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script
+# will be started at step 6)
+# (it is started by sibling 0003-required-headers-synced-while-active-westend-to-rococo.zndsl)
+
+# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations
+bridge-hub-westend-collator1: js-script ../helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds
diff --git a/bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl b/bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..33c3ceebcf844cc6029d41deb289b1a1d8103132
--- /dev/null
+++ b/bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl
@@ -0,0 +1,26 @@
+Description: While relayer is active, we only sync mandatory and required Westend (and Westend BH) headers to Rococo BH.
+Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml
+Creds: config
+
+# step 1: initialize Rococo AH
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 60 seconds
+
+# step 2: initialize Rococo bridge hub
+bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds
+
+# step 3: ensure that initialization has completed
+asset-hub-rococo-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds
+
+# step 4: send message from Rococo to Westend
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds
+
+# step 5: start relayer
+# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script
+# will be started at step 6)
+bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds
+
+# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations
+bridge-hub-rococo-collator1: js-script ../helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds
diff --git a/cumulus/scripts/bridges_common.sh b/cumulus/scripts/bridges_common.sh
index 5d5b7b7a482a1ec5dbe6a9db99e333c8b9029ebb..029d4cd4ff74a5c88165913a48b2b369c0f185b8 100755
--- a/cumulus/scripts/bridges_common.sh
+++ b/cumulus/scripts/bridges_common.sh
@@ -48,6 +48,14 @@ function ensure_polkadot_js_api() {
     fi
 }
 
+function call_polkadot_js_api() {
+    # --noWait: without that argument `polkadot-js-api` waits until transaction is included into the block.
+    #           With it, it just submits it to the tx pool and exits.
+    # --nonce -1: means to compute transaction nonce using `system_accountNextIndex` RPC, which includes all
+    #             transaction that are in the tx pool.
+    polkadot-js-api --noWait --nonce -1 "$@"
+}
+
 function generate_hex_encoded_call_data() {
     local type=$1
     local endpoint=$2
@@ -80,7 +88,7 @@ function transfer_balance() {
     echo "      amount: ${amount}"
     echo "--------------------------------------------------"
 
-    polkadot-js-api \
+    call_polkadot_js_api \
         --ws "${runtime_para_endpoint}" \
         --seed "${seed?}" \
         tx.balances.transferAllowDeath \
@@ -145,7 +153,7 @@ function send_governance_transact() {
     echo ""
     echo "--------------------------------------------------"
 
-    polkadot-js-api \
+    call_polkadot_js_api \
         --ws "${relay_url?}" \
         --seed "${relay_chain_seed?}" \
         --sudo \
@@ -170,7 +178,7 @@ function open_hrmp_channels() {
     echo "      max_message_size: ${max_message_size}"
     echo "      params:"
     echo "--------------------------------------------------"
-    polkadot-js-api \
+    call_polkadot_js_api \
         --ws "${relay_url?}" \
         --seed "${relay_chain_seed?}" \
         --sudo \
@@ -254,7 +262,7 @@ function limited_reserve_transfer_assets() {
     echo ""
     echo "--------------------------------------------------"
 
-    polkadot-js-api \
+    call_polkadot_js_api \
         --ws "${url?}" \
         --seed "${seed?}" \
         tx.polkadotXcm.limitedReserveTransferAssets \
@@ -293,7 +301,7 @@ function claim_rewards() {
     echo "${rewards_account_params}"
     echo "--------------------------------------------------"
 
-    polkadot-js-api \
+    call_polkadot_js_api \
         --ws "${runtime_para_endpoint}" \
         --seed "${seed?}" \
         tx.bridgeRelayers.claimRewards \