diff --git a/BUILD.gn b/BUILD.gn
index d91ef0419d702..917aef8a752b7 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1132,6 +1132,8 @@ if (use_blink && !is_cronet_build) {
   _common_web_test_options = [
     "--no-show-results",
     "--zero-tests-executed-ok",
+    "--build-directory",
+    "@WrappedPath(.)",
   ]
   if (is_debug) {
     _common_web_test_options += [ "--debug" ]
@@ -1161,15 +1163,6 @@ if (use_blink && !is_cronet_build) {
       "--out-dir",
       "@WrappedPath(.)",
     ]
-  } else {
-    base_out_dir = rebase_path(get_path_info(root_build_dir, "dir"), ".")
-    out_dir = get_path_info(root_build_dir, "name")
-    _common_web_test_options += [
-      "--build-directory",
-      base_out_dir,
-      "--target",
-      out_dir,
-    ]
   }
 
   if (!is_chromeos_ash && !is_ios && !is_fuchsia && !is_android && !is_castos) {
@@ -1381,6 +1374,8 @@ if (use_blink && !is_cronet_build) {
     "4",
     "--debug-rwt-logging",
     "--clobber-old-results",
+    "--build-directory",
+    "@WrappedPath(.)",
   ]
 
   # https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index d11658b82c436..362409f8bf3af 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -99,20 +99,14 @@ script_test("system_webview_crx_smoke_tests") {
 _common_web_test_options = [
   "--no-show-results",
   "--zero-tests-executed-ok",
+  "--build-directory",
+  "@WrappedPath(.)",
 ]
 if (is_debug) {
   _common_web_test_options += [ "--debug" ]
 } else {
   _common_web_test_options += [ "--release" ]
 }
-base_out_dir = rebase_path(get_path_info(root_build_dir, "dir"), "../../.")
-out_dir = get_path_info(root_build_dir, "name")
-_common_web_test_options += [
-  "--build-directory",
-  base_out_dir,
-  "--target",
-  out_dir,
-]
 
 script_test("system_webview_wpt") {
   script = "//third_party/blink/tools/run_wpt_tests.py"
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index dd4098bda76bb..75a72e886964f 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -3362,20 +3362,14 @@ if (current_toolchain == default_toolchain) {
   _common_web_test_options = [
     "--no-show-results",
     "--zero-tests-executed-ok",
+    "--build-directory",
+    "@WrappedPath(.)",
   ]
   if (is_debug) {
     _common_web_test_options += [ "--debug" ]
   } else {
     _common_web_test_options += [ "--release" ]
   }
-  base_out_dir = rebase_path(get_path_info(root_build_dir, "dir"), "../../.")
-  out_dir = get_path_info(root_build_dir, "name")
-  _common_web_test_options += [
-    "--build-directory",
-    base_out_dir,
-    "--target",
-    out_dir,
-  ]
 
   script_test("chrome_public_wpt") {
     script = "//third_party/blink/tools/run_wpt_tests.py"
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index b47c0ce9bacab..dd339945677f6 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -2950,10 +2950,12 @@ class Port(object):
 
     def build_path(self, *comps: str, target: Optional[str] = None):
         """Returns a path from the build directory."""
-        return self._filesystem.join(
-            self._path_from_chromium_base(),
-            self.get_option('build_directory') or 'out', target
-            or self._options.target, *comps)
+        build_path = self.get_option('build_directory')
+        if build_path:
+            return self._filesystem.join(self._filesystem.abspath(build_path),
+                                         *comps)
+        return self._filesystem.join(self._path_from_chromium_base(), 'out',
+                                     target or self._options.target, *comps)
 
     def _check_driver_build_up_to_date(self, target):
         # FIXME: We should probably get rid of this check altogether as it has
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py
index 178d5b3aa0600..2213c167e23ce 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py
@@ -1727,8 +1727,10 @@ class PortTest(LoggingTestCase):
         # Test for a protected method - pylint: disable=protected-access
         # Test that optional paths are used regardless of whether they exist.
         options = optparse.Values({
-            'configuration': 'Release',
-            'build_directory': 'xcodebuild'
+            'configuration':
+            'Release',
+            'build_directory':
+            '/mock-checkout/xcodebuild/Release'
         })
         self.assertEqual(
             self.make_port(options=options).build_path(),
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/factory.py b/third_party/blink/tools/blinkpy/web_tests/port/factory.py
index 8adb1966c8d75..2c1de7bafe82f 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/factory.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/factory.py
@@ -272,9 +272,10 @@ def add_results_options_group(parser: argparse.ArgumentParser,
         '--build-directory',
         metavar='PATH',
         default='out',
-        help=(
-            'Path to the directory where build files are kept, not including '
-            'configuration. In general this will be "out".'))
+        help=('Full path to the directory where build files are generated. '
+              'Likely similar to "out/some-dir-name/". If not specified, will '
+              'look for a dir under out/ of the same name as the value passed '
+              'to --target.'))
     results_group.add_argument(
         '--clobber-old-results',
         action='store_true',
@@ -830,13 +831,14 @@ def _update_configuration_and_target(host, options):
 
 def _read_configuration_from_gn(fs, options):
     """Returns the configuration to used based on args.gn, if possible."""
-    build_directory = getattr(options, 'build_directory', 'out')
-    target = options.target
+    build_directory = getattr(options, 'build_directory', None)
     finder = PathFinder(fs)
-    path = fs.join(finder.chromium_base(), build_directory, target, 'args.gn')
+    if not build_directory:
+        build_directory = fs.join(finder.chromium_base(), 'out',
+                                  options.target)
+    path = fs.join(build_directory, 'args.gn')
     if not fs.exists(path):
-        path = fs.join(finder.chromium_base(), build_directory, target,
-                       'toolchain.ninja')
+        path = fs.join(build_directory, 'toolchain.ninja')
         if not fs.exists(path):
             # This does not appear to be a GN-based build directory, so we don't know
             # how to interpret it.
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/factory_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/factory_unittest.py
index 4f8ea3daf5d37..178c1f6339f0f 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/factory_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/factory_unittest.py
@@ -106,7 +106,11 @@ class FactoryTest(unittest.TestCase):
         factory.PortFactory(host).get(options=options)
         self.assertEqual(options, optparse.Values())
 
-    def get_port(self, target=None, configuration=None, files=None):
+    def get_port(self,
+                 target=None,
+                 configuration=None,
+                 build_directory=None,
+                 files=None):
         host = MockHost()
         finder = PathFinder(host.filesystem)
         files = files or {}
@@ -114,8 +118,13 @@ class FactoryTest(unittest.TestCase):
             host.filesystem.write_text_file(
                 finder.path_from_chromium_base(path), contents)
         options = optparse.Values({
-            'target': target,
-            'configuration': configuration
+            'target':
+            target,
+            'configuration':
+            configuration,
+            'build_directory':
+            finder.path_from_chromium_base(build_directory)
+            if build_directory else None,
         })
         return factory.PortFactory(host).get(options=options)
 
@@ -150,38 +159,45 @@ class FactoryTest(unittest.TestCase):
         self.assertEqual(port._options.target, 'Release_x64')
 
     def test_release_args_gn(self):
-        port = self.get_port(
-            target='foo', files={'out/foo/args.gn': 'is_debug = false'})
+        port = self.get_port(target='foo',
+                             files={'out/foo/args.gn': 'is_debug = false'},
+                             build_directory='out/foo')
         self.assertEqual(port._options.configuration, 'Release')
         self.assertEqual(port._options.target, 'foo')
 
         # Also test that we handle multi-line args files properly.
         port = self.get_port(
             target='foo',
-            files={'out/foo/args.gn': 'is_debug = false\nfoo = bar\n'})
+            files={'out/foo/args.gn': 'is_debug = false\nfoo = bar\n'},
+            build_directory='out/foo')
         self.assertEqual(port._options.configuration, 'Release')
         self.assertEqual(port._options.target, 'foo')
 
         port = self.get_port(
             target='foo',
-            files={'out/foo/args.gn': 'foo=bar\nis_debug=false\n'})
+            files={'out/foo/args.gn': 'foo=bar\nis_debug=false\n'},
+            build_directory='out/foo')
         self.assertEqual(port._options.configuration, 'Release')
         self.assertEqual(port._options.target, 'foo')
 
     def test_debug_args_gn(self):
-        port = self.get_port(
-            target='foo', files={'out/foo/args.gn': 'is_debug = true'})
+        port = self.get_port(target='foo',
+                             files={'out/foo/args.gn': 'is_debug = true'},
+                             build_directory='out/foo')
         self.assertEqual(port._options.configuration, 'Debug')
         self.assertEqual(port._options.target, 'foo')
 
     def test_default_gn_build(self):
-        port = self.get_port(
-            target='Default', files={'out/Default/toolchain.ninja': ''})
+        port = self.get_port(target='Default',
+                             files={'out/Default/toolchain.ninja': ''},
+                             build_directory='out/Default')
         self.assertEqual(port._options.configuration, 'Debug')
         self.assertEqual(port._options.target, 'Default')
 
     def test_empty_args_gn(self):
-        port = self.get_port(target='foo', files={'out/foo/args.gn': ''})
+        port = self.get_port(target='foo',
+                             files={'out/foo/args.gn': ''},
+                             build_directory='out/foo')
         self.assertEqual(port._options.configuration, 'Debug')
         self.assertEqual(port._options.target, 'foo')
 
@@ -197,11 +213,12 @@ class FactoryTest(unittest.TestCase):
 
     def test_both_configuration_and_target_is_an_error(self):
         with self.assertRaises(ValueError):
-            self.get_port(
-                target='Debug',
-                configuration='Release',
-                files={'out/Debug/toolchain.ninja': ''})
+            self.get_port(target='Debug',
+                          configuration='Release',
+                          files={'out/Debug/toolchain.ninja': ''},
+                          build_directory='out/Debug')
 
     def test_no_target_has_correct_config(self):
-        port = self.get_port(files={'out/Release/args.gn': 'is_debug = true'})
+        port = self.get_port(files={'out/Release/args.gn': 'is_debug = true'},
+                             build_directory='out/Release')
         self.assertEqual(port._options.configuration, 'Debug')
diff --git a/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter_unittest.py b/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter_unittest.py
index 3d1cee2a1b1b6..25ba1c59996e7 100644
--- a/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter_unittest.py
+++ b/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter_unittest.py
@@ -397,8 +397,10 @@ class WPTAdapterTest(unittest.TestCase):
             self.assertEqual(os.environ['NEW_ENV_VAR'], 'new_env_var_value')
 
     def test_show_results(self):
-        adapter = WPTAdapter.from_args(
-            self.host, ['--product=headless_shell', '--no-manifest-update'])
+        adapter = WPTAdapter.from_args(self.host, [
+            '--product=headless_shell', '--no-manifest-update',
+            '--build-directory=/mock-checkout/out/Release'
+        ])
         post_run_tasks = mock.Mock()
         self._mocks.enter_context(
             mock.patch('blinkpy.web_tests.port.base.Port.clean_up_test_run',