diff --git a/tests/behat/app_behat_runtime.js b/tests/behat/app_behat_runtime.js
index 1567ac68a..03e647250 100644
--- a/tests/behat/app_behat_runtime.js
+++ b/tests/behat/app_behat_runtime.js
@@ -345,6 +345,26 @@
         return element.parentElement || (element.getRootNode() && element.getRootNode().host) || null;
     };
 
+    /**
+     * Get closest element matching a selector, without traversing up a given container.
+     *
+     * @param {HTMLElement} element Element.
+     * @param {string} selector Selector.
+     * @param {HTMLElement} container Topmost container to search within.
+     * @return {HTMLElement} Closest matching element.
+     */
+    const getClosestMatching = function(element, selector, container) {
+        if (element.matches(selector)) {
+            return element;
+        }
+
+        if (element === container || !element.parentElement) {
+            return null;
+        }
+
+        return getClosestMatching(element.parentElement, selector, container);
+    };
+
     /**
      * Function to find elements based on their text or Aria label.
      *
@@ -361,6 +381,24 @@
 
         let container = topContainer;
 
+        if (locator.within) {
+            const withinElements = findElementsBasedOnText(locator.within);
+
+            if (withinElements.length === 0) {
+                throw new Error('There was no match for within text')
+            } else if (withinElements.length > 1) {
+                const withinElementsAncestors = getTopAncestors(withinElements);
+
+                if (withinElementsAncestors.length > 1) {
+                    throw new Error('Too many matches for within text');
+                }
+
+                topContainer = container = withinElementsAncestors[0];
+            } else {
+                topContainer = container = withinElements[0];
+            }
+        }
+
         if (topContainer && locator.near) {
             const nearElements = findElementsBasedOnText(locator.near);
 
@@ -382,7 +420,7 @@
         do {
             const elements = findElementsBasedOnTextWithin(container, locator.text);
             const filteredElements = locator.selector
-                ? elements.filter(element => element.matches(locator.selector))
+                ? elements.map(element => getClosestMatching(element, locator.selector, container)).filter(element => !!element)
                 : elements;
 
             if (filteredElements.length > 0) {
diff --git a/tests/behat/behat_app.php b/tests/behat/behat_app.php
index d36f1f405..3bd63c9f2 100644
--- a/tests/behat/behat_app.php
+++ b/tests/behat/behat_app.php
@@ -1020,7 +1020,7 @@ class behat_app extends behat_base {
      * @return object
      */
     public function parse_element_locator(string $text): object {
-        preg_match('/^"((?:[^"]|\\")*?)"(?: "([^"]*?)")?(?: near "((?:[^"]|\\")*?)"(?: "([^"]*?)")?)?$/', $text, $matches);
+        preg_match('/^"((?:[^"]|\\")*?)"(?: "([^"]*?)")?(?: (near|within) "((?:[^"]|\\")*?)"(?: "([^"]*?)")?)?$/', $text, $matches);
 
         $locator = [
             'text' => str_replace('\\"', '"', $matches[1]),
@@ -1028,9 +1028,9 @@ class behat_app extends behat_base {
         ];
 
         if (!empty($matches[3])) {
-            $locator['near'] = (object) [
-                'text' => str_replace('\\"', '"', $matches[3]),
-                'selector' => $matches[4] ?? null,
+            $locator[$matches[3]] = (object) [
+                'text' => str_replace('\\"', '"', $matches[4]),
+                'selector' => $matches[5] ?? null,
             ];
         }