Repository: beeware/briefcase
Pull request #2327 (2025-06-04 20:11:22)

Change `test_mode` to an app property
In this PR, the `test_mode` Flag becomes a property of the `AppConfig` as suggested in https://github.com/beeware/briefcase/pull/2173#pullrequestreview-2873000047. This simplifies the code, as `test_mode` is only required at a few points and no longer has to be passed through all functions.

This refactoring simplifies adding a new `debug_mode` in #2173.

## PR Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [x] All new features have been tested
- [x] All new features have been documented
- [x] I have read the **CONTRIBUTING.md** file
- [x] I will abide by the code of conduct



Patch:
diff --git a/changes/2327.misc.rst b/changes/2327.misc.rst
new file mode 100644
index 00000000..36236f86
--- /dev/null
+++ b/changes/2327.misc.rst
@@ -0,0 +1,1 @@
+Changed `test_mode` to an app property
diff --git a/src/briefcase/commands/base.py b/src/briefcase/commands/base.py
index 33682e9e..df046d5f 100644
--- a/src/briefcase/commands/base.py
+++ b/src/briefcase/commands/base.py
@@ -143,7 +143,7 @@ class BaseCommand(ABC):
         self,
         console: Console,
         tools: ToolCache = None,
-        apps: dict = None,
+        apps: dict[str, AppConfig] = None,
         base_path: Path = None,
         data_path: Path = None,
         is_clone: bool = False,
@@ -631,7 +631,7 @@ a custom location for Briefcase's tools.
         :param app: The app configuration to finalize.
         """
 
-    def finalize(self, app: AppConfig | None = None):
+    def finalize(self, app: AppConfig | None = None, test_mode: bool = False):
         """Finalize Briefcase configuration.
 
         This will:
@@ -648,13 +648,10 @@ a custom location for Briefcase's tools.
         self.verify_host()
         self.verify_tools()
 
-        if app is None:
-            for app in self.apps.values():
-                if hasattr(app, "__draft__"):
-                    self.finalize_app_config(app)
-                    delattr(app, "__draft__")
-        else:
+        apps = self.apps.values() if app is None else [app]
+        for app in apps:
             if hasattr(app, "__draft__"):
+                app.test_mode = test_mode
                 self.finalize_app_config(app)
                 delattr(app, "__draft__")
 
diff --git a/src/briefcase/commands/build.py b/src/briefcase/commands/build.py
index a649910c..f77b991e 100644
--- a/src/briefcase/commands/build.py
+++ b/src/briefcase/commands/build.py
@@ -40,7 +40,6 @@ class BuildCommand(BaseCommand):
         update_support: bool,
         update_stub: bool,
         no_update: bool,
-        test_mode: bool,
         **options,
     ) -> dict | None:
         """Internal method to invoke a build on a single app. Ensures the app exists,
@@ -56,10 +55,9 @@ class BuildCommand(BaseCommand):
         :param update_support: Should the application support be updated?
         :param update_stub: Should the stub binary be updated?
         :param no_update: Should automated updates be disabled?
-        :param test_mode: Is the app being build in test mode?
         """
         if not self.bundle_path(app).exists():
-            state = self.create_command(app, test_mode=test_mode, **options)
+            state = self.create_command(app, **options)
         elif (
             update  # An explicit update has been requested
             or update_requirements  # An explicit update of requirements has been requested
@@ -67,7 +65,7 @@ class BuildCommand(BaseCommand):
             or update_support  # An explicit update of app support has been requested
             or update_stub  # An explicit update of the stub binary has been requested
             or (
-                test_mode and not no_update
+                app.test_mode and not no_update
             )  # Test mode, but updates have not been disabled
         ):
             state = self.update_command(
@@ -76,7 +74,6 @@ class BuildCommand(BaseCommand):
                 update_resources=update_resources,
                 update_support=update_support,
                 update_stub=update_stub,
-                test_mode=test_mode,
                 **options,
             )
         else:
@@ -84,9 +81,9 @@ class BuildCommand(BaseCommand):
 
         self.verify_app(app)
 
-        state = self.build_app(app, test_mode=test_mode, **full_options(state, options))
+        state = self.build_app(app, **full_options(state, options))
 
-        qualifier = " (test mode)" if test_mode else ""
+        qualifier = " (test mode)" if app.test_mode else ""
         self.console.info(
             f"Built {self.binary_path(app).relative_to(self.base_path)}{qualifier}",
             prefix=app.app_name,
@@ -132,7 +129,7 @@ class BuildCommand(BaseCommand):
 
         # Confirm host compatibility, that all required tools are available,
         # and that the app configuration is finalized.
-        self.finalize(app)
+        self.finalize(app, test_mode)
 
         if app_name:
             try:
@@ -156,7 +153,6 @@ class BuildCommand(BaseCommand):
                 update_support=update_support,
                 update_stub=update_stub,
                 no_update=no_update,
-                test_mode=test_mode,
                 **full_options(state, options),
             )
 
diff --git a/src/briefcase/commands/create.py b/src/briefcase/commands/create.py
index e69aca39..e647e7ca 100644
--- a/src/briefcase/commands/create.py
+++ b/src/briefcase/commands/create.py
@@ -670,7 +670,7 @@ class CreateCommand(BaseCommand):
         else:
             self.console.info("No application requirements.")
 
-    def install_app_requirements(self, app: AppConfig, test_mode: bool):
+    def install_app_requirements(self, app: AppConfig):
         """Handle requirements for the app.
 
         This will result in either (in preferential order):
@@ -679,16 +679,13 @@ class CreateCommand(BaseCommand):
          * requirements being installed with pip into the location specified
            by the ``app_packages_path`` in the template path index.
 
-        If ``test_mode`` is True, the test requirements will also be installed.
-
         If the path index doesn't specify either of the path index entries,
         an error is raised.
 
         :param app: The config object for the app
-        :param test_mode: Should the test requirements be installed?
         """
         requires = app.requires.copy() if app.requires else []
-        if test_mode and app.test_requires:
+        if app.test_mode and app.test_requires:
             requires.extend(app.test_requires)
 
         try:
@@ -717,11 +714,10 @@ class CreateCommand(BaseCommand):
                     "`app_requirements_path` or `app_packages_path`"
                 ) from e
 
-    def install_app_code(self, app: AppConfig, test_mode: bool):
+    def install_app_code(self, app: AppConfig):
         """Install the application code into the bundle.
 
         :param app: The config object for the app
-        :param test_mode: Should the application test code also be installed?
         """
         # Remove existing app folder if it exists
         app_path = self.app_path(app)
@@ -730,7 +726,7 @@ class CreateCommand(BaseCommand):
         self.tools.os.mkdir(app_path)
 
         sources = app.sources.copy() if app.sources else []
-        if test_mode and app.test_sources:
+        if app.test_mode and app.test_sources:
             sources.extend(app.test_sources)
 
         # Install app code.
@@ -912,11 +908,10 @@ class CreateCommand(BaseCommand):
                         self.console.verbose(f"Removing {relative_path}")
                         path.unlink()
 
-    def create_app(self, app: AppConfig, test_mode: bool = False, **options):
+    def create_app(self, app: AppConfig, **options):
         """Create an application bundle.
 
         :param app: The config object for the app
-        :param test_mode: Should the app be updated in test mode? (default: False)
         """
         if not app.supported:
             raise UnsupportedPlatform(self.platform)
@@ -957,10 +952,10 @@ class CreateCommand(BaseCommand):
         self.verify_app(app)
 
         self.console.info("Installing application code...", prefix=app.app_name)
-        self.install_app_code(app=app, test_mode=test_mode)
+        self.install_app_code(app=app)
 
         self.console.info("Installing requirements...", prefix=app.app_name)
-        self.install_app_requirements(app=app, test_mode=test_mode)
+        self.install_app_requirements(app=app)
 
         self.console.info("Installing application resources...", prefix=app.app_name)
         self.install_app_resources(app=app)
diff --git a/src/briefcase/commands/dev.py b/src/briefcase/commands/dev.py
index adcb8fe2..ccd36068 100644
--- a/src/briefcase/commands/dev.py
+++ b/src/briefcase/commands/dev.py
@@ -114,7 +114,6 @@ class DevCommand(RunAppMixin, BaseCommand):
         self,
         app: AppConfig,
         env: dict,
-        test_mode: bool,
         passthrough: list[str],
         **options,
     ):
@@ -122,10 +121,9 @@ class DevCommand(RunAppMixin, BaseCommand):
 
         :param app: The config object for the app
         :param env: environment dictionary for sub command
-        :param test_mode: Run the test suite, rather than the app?
         :param passthrough: A list of arguments to pass to the app
         """
-        main_module = app.main_module(test_mode)
+        main_module = app.main_module()
 
         # Add in the environment settings to get Python in the state we want.
         env.update(self.DEV_ENVIRONMENT)
@@ -145,7 +143,7 @@ class DevCommand(RunAppMixin, BaseCommand):
         # Console apps must operate in non-streaming mode so that console input can
         # be handled correctly. However, if we're in test mode, we *must* stream so
         # that we can see the test exit sentinel
-        if app.console_app and not test_mode:
+        if app.console_app and not app.test_mode:
             self.console.info("=" * 75)
             self.tools.subprocess.run(
                 cmdline,
@@ -170,16 +168,15 @@ class DevCommand(RunAppMixin, BaseCommand):
             self._stream_app_logs(
                 app,
                 popen=app_popen,
-                test_mode=test_mode,
                 clean_output=False,
             )
 
-    def get_environment(self, app, test_mode: bool):
+    def get_environment(self, app: AppConfig):
         # Create a shell environment where PYTHONPATH points to the source
         # directories described by the app config.
         env = {
             "PYTHONPATH": os.pathsep.join(
-                os.fsdecode(Path.cwd() / path) for path in app.PYTHONPATH(test_mode)
+                os.fsdecode(Path.cwd() / path) for path in app.PYTHONPATH()
             )
         }
 
@@ -223,7 +220,7 @@ class DevCommand(RunAppMixin, BaseCommand):
             )
         # Confirm host compatibility, that all required tools are available,
         # and that the app configuration is finalized.
-        self.finalize(app)
+        self.finalize(app, test_mode)
 
         self.verify_app(app)
 
@@ -243,17 +240,16 @@ class DevCommand(RunAppMixin, BaseCommand):
             write_dist_info(app, dist_info_path)
 
         if run_app:
-            if test_mode:
+            if app.test_mode:
                 self.console.info(
                     "Running test suite in dev environment...", prefix=app.app_name
                 )
             else:
                 self.console.info("Starting in dev mode...", prefix=app.app_name)
-            env = self.get_environment(app, test_mode=test_mode)
+            env = self.get_environment(app)
             return self.run_dev_app(
                 app,
                 env,
-                test_mode=test_mode,
                 passthrough=[] if passthrough is None else passthrough,
                 **options,
             )
diff --git a/src/briefcase/commands/run.py b/src/briefcase/commands/run.py
index 9df195b4..42638175 100644
--- a/src/briefcase/commands/run.py
+++ b/src/briefcase/commands/run.py
@@ -130,7 +130,6 @@ class RunAppMixin:
         self,
         app: AppConfig,
         popen,
-        test_mode=False,
         clean_filter=None,
         clean_output=False,
         stop_func=lambda: False,
@@ -143,7 +142,6 @@ class RunAppMixin:
         :param app: The app to be launched
         :param popen: The Popen object for the stream we are monitoring; this Popen
             process will be closed after log streaming completes.
-        :param test_mode: Are we launching in test mode?
         :param clean_filter: The log cleaning filter to use; see ``LogFilter``
             for details.
         :param clean_output: Should the cleaned output be presented to the user?
@@ -179,7 +177,7 @@ class RunAppMixin:
 
             # If we're in test mode, and log streaming ends,
             # check for the status of the test suite.
-            if test_mode:
+            if app.test_mode:
                 if log_filter.returncode == 0:
                     self.console.info("Test suite passed!", prefix=app.app_name)
                 else:
@@ -220,14 +218,13 @@ class RunCommand(RunAppMixin, BaseCommand):
         self._add_update_options(parser, context_label=" before running")
         self._add_test_options(parser, context_label="Run")
 
-    def _prepare_app_kwargs(self, app: AppConfig, test_mode: bool):
+    def _prepare_app_kwargs(self, app: AppConfig):
         """Prepare the kwargs for running an app as a log stream.
 
         This won't be used by every backend; but it's a sufficiently common default that
         it's been factored out.
 
         :param app: The app to be launched
-        :param test_mode: Are we launching in test mode?
         :returns: A dictionary of additional arguments to pass to the Popen
         """
         args = {}
@@ -237,10 +234,10 @@ class RunCommand(RunAppMixin, BaseCommand):
         if self.console.is_debug:
             env["BRIEFCASE_DEBUG"] = "1"
 
-        if test_mode:
+        if app.test_mode:
             # In test mode, set a BRIEFCASE_MAIN_MODULE environment variable
             # to override the module at startup
-            env["BRIEFCASE_MAIN_MODULE"] = app.main_module(test_mode)
+            env["BRIEFCASE_MAIN_MODULE"] = app.main_module()
             self.console.info("Starting test_suite...", prefix=app.app_name)
         else:
             self.console.info("Starting app...", prefix=app.app_name)
@@ -290,7 +287,7 @@ class RunCommand(RunAppMixin, BaseCommand):
 
         # Confirm host compatibility, that all required tools are available,
         # and that the app configuration is finalized.
-        self.finalize(app)
+        self.finalize(app, test_mode)
 
         template_file = self.bundle_path(app)
         exec_file = self.binary_executable_path(app)
@@ -303,7 +300,7 @@ class RunCommand(RunAppMixin, BaseCommand):
             or update_stub  # An explicit update of the stub binary has been requested
             or (not exec_file.exists())  # Executable binary doesn't exist yet
             or (
-                test_mode and not no_update
+                app.test_mode and not no_update
             )  # Test mode, but updates have not been disabled
         ):
             state = self.build_command(
@@ -314,7 +311,6 @@ class RunCommand(RunAppMixin, BaseCommand):
                 update_support=update_support,
                 update_stub=update_stub,
                 no_update=no_update,
-                test_mode=test_mode,
                 **options,
             )
         else:
@@ -324,7 +320,6 @@ class RunCommand(RunAppMixin, BaseCommand):
 
         state = self.run_app(
             app,
-            test_mode=test_mode,
             passthrough=[] if passthrough is None else passthrough,
             **full_options(state, options),
         )
diff --git a/src/briefcase/commands/update.py b/src/briefcase/commands/update.py
index 27673b32..eb84faca 100644
--- a/src/briefcase/commands/update.py
+++ b/src/briefcase/commands/update.py
@@ -32,7 +32,6 @@ class UpdateCommand(CreateCommand):
         update_resources: bool,
         update_support: bool,
         update_stub: bool,
-        test_mode: bool,
         **options,
     ) -> dict | None:
         """Update an existing application bundle.
@@ -42,7 +41,6 @@ class UpdateCommand(CreateCommand):
         :param update_resources: Should extra resources be updated?
         :param update_support: Should app support be updated?
         :param update_stub: Should stub binary be updated?
-        :param test_mode: Should the app be updated in test mode?
         """
 
         if not self.bundle_path(app).exists():
@@ -54,11 +52,11 @@ class UpdateCommand(CreateCommand):
         self.verify_app(app)
 
         self.console.info("Updating application code...", prefix=app.app_name)
-        self.install_app_code(app=app, test_mode=test_mode)
+        self.install_app_code(app=app)
 
         if update_requirements:
             self.console.info("Updating requirements...", prefix=app.app_name)
-            self.install_app_requirements(app=app, test_mode=test_mode)
+            self.install_app_requirements(app=app)
 
         if update_resources:
             self.console.info("Updating application resources...", prefix=app.app_name)
@@ -100,7 +98,7 @@ class UpdateCommand(CreateCommand):
     ) -> dict | None:
         # Confirm host compatibility, that all required tools are available,
         # and that the app configuration is finalized.
-        self.finalize(app)
+        self.finalize(app, test_mode)
 
         if app_name:
             try:
@@ -122,7 +120,6 @@ class UpdateCommand(CreateCommand):
                 update_resources=update_resources,
                 update_support=update_support,
                 update_stub=update_stub,
-                test_mode=test_mode,
                 **full_options(state, options),
             )
 
diff --git a/src/briefcase/config.py b/src/briefcase/config.py
index 06c10f36..e41f25c1 100644
--- a/src/briefcase/config.py
+++ b/src/briefcase/config.py
@@ -343,6 +343,7 @@ class AppConfig(BaseConfig):
         self.requirement_installer_args = (
             [] if requirement_installer_args is None else requirement_installer_args
         )
+        self.test_mode: bool = False
 
         if not is_valid_app_name(self.app_name):
             raise BriefcaseConfigError(
@@ -431,14 +432,11 @@ class AppConfig(BaseConfig):
         `module_name`."""
         return self.bundle.replace("-", "_")
 
-    def PYTHONPATH(self, test_mode):
-        """The PYTHONPATH modifications needed to run this app.
-
-        :param test_mode: Should test_mode sources be included?
-        """
+    def PYTHONPATH(self):
+        """The PYTHONPATH modifications needed to run this app."""
         paths = []
         sources = self.sources
-        if test_mode and self.test_sources:
+        if self.test_mode and self.test_sources:
             sources.extend(self.test_sources)
 
         for source in sources:
@@ -447,15 +445,13 @@ class AppConfig(BaseConfig):
                 paths.append(path)
         return paths
 
-    def main_module(self, test_mode: bool):
+    def main_module(self):
         """The path to the main module for the app.
 
         In normal operation, this is ``app.module_name``; however,
         in test mode, it is prefixed with ``tests.``.
-
-        :param test_mode: Are we running in test mode?
         """
-        if test_mode:
+        if self.test_mode:
             return f"tests.{self.module_name}"
         else:
             return self.module_name
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index 9a041edc..d32f30a1 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -297,7 +297,7 @@ class GradleBuildCommand(GradleMixin, BuildCommand):
     def metadata_resource_path(self, app: AppConfig):
         return self.bundle_path(app) / self.path_index(app, "metadata_resource_path")
 
-    def update_app_metadata(self, app: AppConfig, test_mode: bool):
+    def update_app_metadata(self, app: AppConfig):
         with self.console.wait_bar("Setting main module..."):
             with self.metadata_resource_path(app).open("w", encoding="utf-8") as f:
                 # Set the name of the app's main module; this will depend
@@ -305,19 +305,18 @@ class GradleBuildCommand(GradleMixin, BuildCommand):
                 f.write(
                     f"""\
 <resources>
-    <string name="main_module">{app.main_module(test_mode)}</string>
+    <string name="main_module">{app.main_module()}</string>
 </resources>
 """
                 )
 
-    def build_app(self, app: AppConfig, test_mode: bool, **kwargs):
+    def build_app(self, app: AppConfig, **kwargs):
         """Build an application.
 
         :param app: The application to build
-        :param test_mode: Should the app be updated in test mode? (default: False)
         """
         self.console.info("Updating app metadata...", prefix=app.app_name)
-        self.update_app_metadata(app=app, test_mode=test_mode)
+        self.update_app_metadata(app=app)
 
         self.console.info("Building Android APK...", prefix=app.app_name)
         with self.console.wait_bar("Building..."):
@@ -363,7 +362,6 @@ class GradleRunCommand(GradleMixin, RunCommand):
     def run_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         device_or_avd=None,
         extra_emulator_args=None,
@@ -373,7 +371,6 @@ class GradleRunCommand(GradleMixin, RunCommand):
         """Start the application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         :param device_or_avd: The device to target. If ``None``, the user will
             be asked to re-run the command selecting a specific device.
@@ -405,7 +402,7 @@ class GradleRunCommand(GradleMixin, RunCommand):
             )
 
         try:
-            label = "test suite" if test_mode else "app"
+            label = "test suite" if app.test_mode else "app"
 
             self.console.info(
                 f"Starting {label} on {name} (device ID {device})", prefix=app.app_name
@@ -456,7 +453,6 @@ class GradleRunCommand(GradleMixin, RunCommand):
                 self._stream_app_logs(
                     app,
                     popen=log_popen,
-                    test_mode=test_mode,
                     clean_filter=android_log_clean_filter,
                     clean_output=False,
                     # Check for the PID in quiet mode so logs aren't corrupted.
diff --git a/src/briefcase/platforms/iOS/xcode.py b/src/briefcase/platforms/iOS/xcode.py
index 6a0f348f..c7ad4f31 100644
--- a/src/briefcase/platforms/iOS/xcode.py
+++ b/src/briefcase/platforms/iOS/xcode.py
@@ -428,7 +428,7 @@ class iOSXcodeBuildCommand(iOSXcodePassiveMixin, BuildCommand):
         """
         return self.bundle_path(app) / self.path_index(app, "info_plist_path")
 
-    def update_app_metadata(self, app: AppConfig, test_mode: bool):
+    def update_app_metadata(self, app: AppConfig):
         with self.console.wait_bar("Setting main module..."):
             # Load the original plist
             with self.info_plist_path(app).open("rb") as f:
@@ -436,20 +436,19 @@ class iOSXcodeBuildCommand(iOSXcodePassiveMixin, BuildCommand):
 
             # Set the name of the app's main module; this will depend
             # on whether we're in test mode.
-            info_plist["MainModule"] = app.main_module(test_mode)
+            info_plist["MainModule"] = app.main_module()
 
             # Write the modified plist
             with self.info_plist_path(app).open("wb") as f:
                 plistlib.dump(info_plist, f)
 
-    def build_app(self, app: AppConfig, test_mode: bool = False, **kwargs):
+    def build_app(self, app: AppConfig, **kwargs):
         """Build the Xcode project for the application.
 
         :param app: The application to build
-        :param test_mode: Should the app be updated in test mode? (default: False)
         """
         self.console.info("Updating app metadata...", prefix=app.app_name)
-        self.update_app_metadata(app=app, test_mode=test_mode)
+        self.update_app_metadata(app=app)
 
         self.console.info("Building Xcode project...", prefix=app.app_name)
         with self.console.wait_bar("Building..."):
@@ -492,7 +491,6 @@ class iOSXcodeRunCommand(iOSXcodeMixin, RunCommand):
     def run_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         udid=None,
         **kwargs,
@@ -500,7 +498,6 @@ class iOSXcodeRunCommand(iOSXcodeMixin, RunCommand):
         """Start the application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         :param udid: The device UDID to target. If ``None``, the user will
             be asked to select a device at runtime.
@@ -512,7 +509,7 @@ class iOSXcodeRunCommand(iOSXcodeMixin, RunCommand):
                 "Input has been disabled; can't select a device to target."
             ) from e
 
-        if test_mode:
+        if app.test_mode:
             label = "test suite"
         else:
             label = "app"
@@ -546,7 +543,7 @@ class iOSXcodeRunCommand(iOSXcodeMixin, RunCommand):
                     f"Unable to boot {device} simulator running {iOS_version}"
                 ) from e
 
-        if not test_mode:
+        if not app.test_mode:
             # We now know the simulator is *running*, so we can open it.
             # We don't need to open the simulator to run the test suite.
             try:
@@ -665,7 +662,6 @@ class iOSXcodeRunCommand(iOSXcodeMixin, RunCommand):
             self._stream_app_logs(
                 app,
                 popen=simulator_log_popen,
-                test_mode=test_mode,
                 clean_filter=macOS_log_clean_filter,
                 clean_output=True,
                 stop_func=lambda: is_process_dead(app_pid),
diff --git a/src/briefcase/platforms/linux/appimage.py b/src/briefcase/platforms/linux/appimage.py
index 745efae8..b6aa93d3 100644
--- a/src/briefcase/platforms/linux/appimage.py
+++ b/src/briefcase/platforms/linux/appimage.py
@@ -369,23 +369,21 @@ class LinuxAppImageRunCommand(LinuxAppImagePassiveMixin, RunCommand):
     def run_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         **kwargs,
     ):
         """Start the application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         """
         # Set up the log stream
-        kwargs = self._prepare_app_kwargs(app=app, test_mode=test_mode)
+        kwargs = self._prepare_app_kwargs(app=app)
 
         # Console apps must operate in non-streaming mode so that console input can
         # be handled correctly. However, if we're in test mode, we *must* stream so
         # that we can see the test exit sentinel
-        if app.console_app and not test_mode:
+        if app.console_app and not app.test_mode:
             self.console.info("=" * 75)
             self.tools.subprocess.run(
                 [self.binary_path(app)] + passthrough,
@@ -409,7 +407,6 @@ class LinuxAppImageRunCommand(LinuxAppImagePassiveMixin, RunCommand):
             self._stream_app_logs(
                 app,
                 popen=app_popen,
-                test_mode=test_mode,
                 clean_output=False,
             )
 
diff --git a/src/briefcase/platforms/linux/flatpak.py b/src/briefcase/platforms/linux/flatpak.py
index 3e16dd3f..c4b2c3a4 100644
--- a/src/briefcase/platforms/linux/flatpak.py
+++ b/src/briefcase/platforms/linux/flatpak.py
@@ -201,23 +201,21 @@ class LinuxFlatpakRunCommand(LinuxFlatpakMixin, RunCommand):
     def run_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         **kwargs,
     ):
         """Start the application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         """
         # Set up the log stream
-        kwargs = self._prepare_app_kwargs(app=app, test_mode=test_mode)
+        kwargs = self._prepare_app_kwargs(app=app)
 
         # Console apps must operate in non-streaming mode so that console input can
         # be handled correctly. However, if we're in test mode, we *must* stream so
         # that we can see the test exit sentinel
-        if app.console_app and not test_mode:
+        if app.console_app and not app.test_mode:
             self.console.info("=" * 75)
             self.tools.flatpak.run(
                 bundle_identifier=app.bundle_identifier,
@@ -238,7 +236,6 @@ class LinuxFlatpakRunCommand(LinuxFlatpakMixin, RunCommand):
             self._stream_app_logs(
                 app,
                 popen=app_popen,
-                test_mode=test_mode,
                 clean_output=False,
             )
 
diff --git a/src/briefcase/platforms/linux/system.py b/src/briefcase/platforms/linux/system.py
index 88234572..e637fff4 100644
--- a/src/briefcase/platforms/linux/system.py
+++ b/src/briefcase/platforms/linux/system.py
@@ -838,24 +838,22 @@ class LinuxSystemRunCommand(LinuxSystemMixin, RunCommand):
     def run_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         **kwargs,
     ):
         """Start the application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         """
         # Set up the log stream
-        kwargs = self._prepare_app_kwargs(app=app, test_mode=test_mode)
+        kwargs = self._prepare_app_kwargs(app=app)
 
         with self.tools[app].app_context.run_app_context(kwargs) as kwargs:
             # Console apps must operate in non-streaming mode so that console input can
             # be handled correctly. However, if we're in test mode, we *must* stream so
             # that we can see the test exit sentinel
-            if app.console_app and not test_mode:
+            if app.console_app and not app.test_mode:
                 self.console.info("=" * 75)
                 self.tools[app].app_context.run(
                     [self.binary_path(app)] + passthrough,
@@ -879,7 +877,6 @@ class LinuxSystemRunCommand(LinuxSystemMixin, RunCommand):
                 self._stream_app_logs(
                     app,
                     popen=app_popen,
-                    test_mode=test_mode,
                     clean_output=False,
                 )
 
diff --git a/src/briefcase/platforms/macOS/__init__.py b/src/briefcase/platforms/macOS/__init__.py
index e0e0b9e5..5ec52e66 100644
--- a/src/briefcase/platforms/macOS/__init__.py
+++ b/src/briefcase/platforms/macOS/__init__.py
@@ -301,14 +301,12 @@ class macOSRunMixin:
     def run_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         **kwargs,
     ):
         """Start the application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         """
         # Console apps must operate in non-streaming mode so that console input can
@@ -317,14 +315,12 @@ class macOSRunMixin:
         if app.console_app:
             self.run_console_app(
                 app,
-                test_mode=test_mode,
                 passthrough=passthrough,
                 **kwargs,
             )
         else:
             self.run_gui_app(
                 app,
-                test_mode=test_mode,
                 passthrough=passthrough,
                 **kwargs,
             )
@@ -332,21 +328,19 @@ class macOSRunMixin:
     def run_console_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         **kwargs,
     ):
         """Start the console application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         """
-        sub_kwargs = self._prepare_app_kwargs(app=app, test_mode=test_mode)
+        sub_kwargs = self._prepare_app_kwargs(app=app)
         cmdline = [self.binary_path(app) / f"Contents/MacOS/{app.formal_name}"]
         cmdline.extend(passthrough)
 
-        if test_mode:
+        if app.test_mode:
             # Stream the app's output for testing.
             # When a console app runs normally, its stdout should be connected
             # directly to the terminal to properly display the app. When its test
@@ -360,7 +354,7 @@ class macOSRunMixin:
                 bufsize=1,
                 **sub_kwargs,
             )
-            self._stream_app_logs(app, popen=app_popen, test_mode=test_mode)
+            self._stream_app_logs(app, popen=app_popen)
 
         else:
             try:
@@ -381,14 +375,12 @@ class macOSRunMixin:
     def run_gui_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         **kwargs,
     ):
         """Start the GUI application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         """
         # Start log stream for the app.
@@ -430,7 +422,7 @@ class macOSRunMixin:
         app_pid = None
         try:
             # Set up the log stream
-            sub_kwargs = self._prepare_app_kwargs(app=app, test_mode=test_mode)
+            sub_kwargs = self._prepare_app_kwargs(app=app)
 
             # Start the app in a way that lets us stream the logs
             self.tools.subprocess.run(
@@ -457,7 +449,6 @@ class macOSRunMixin:
             self._stream_app_logs(
                 app,
                 popen=log_popen,
-                test_mode=test_mode,
                 clean_filter=macOS_log_clean_filter,
                 clean_output=True,
                 stop_func=lambda: is_process_dead(app_pid),
diff --git a/src/briefcase/platforms/web/static.py b/src/briefcase/platforms/web/static.py
index 15909ba6..90e44d1c 100644
--- a/src/briefcase/platforms/web/static.py
+++ b/src/briefcase/platforms/web/static.py
@@ -310,7 +310,6 @@ class StaticWebRunCommand(StaticWebMixin, RunCommand):
     def run_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         host,
         port,
@@ -320,13 +319,12 @@ class StaticWebRunCommand(StaticWebMixin, RunCommand):
         """Start the application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         :param host: The host on which to run the server
         :param port: The port on which to run the server
         :param open_browser: Should a browser be opened on the newly started server.
         """
-        if test_mode:
+        if app.test_mode:
             raise BriefcaseCommandError("Briefcase can't run web apps in test mode.")
 
         self.console.info("Starting web server...", prefix=app.app_name)
diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py
index b110782f..41f5802d 100644
--- a/src/briefcase/platforms/windows/__init__.py
+++ b/src/briefcase/platforms/windows/__init__.py
@@ -129,23 +129,21 @@ class WindowsRunCommand(RunCommand):
     def run_app(
         self,
         app: AppConfig,
-        test_mode: bool,
         passthrough: list[str],
         **kwargs,
     ):
         """Start the application.
 
         :param app: The config object for the app
-        :param test_mode: Boolean; Is the app running in test mode?
         :param passthrough: The list of arguments to pass to the app
         """
         # Set up the log stream
-        kwargs = self._prepare_app_kwargs(app=app, test_mode=test_mode)
+        kwargs = self._prepare_app_kwargs(app=app)
 
         # Console apps must operate in non-streaming mode so that console input can
         # be handled correctly. However, if we're in test mode, we *must* stream so
         # that we can see the test exit sentinel
-        if app.console_app and not test_mode:
+        if app.console_app and not app.test_mode:
             self.console.info("=" * 75)
             self.tools.subprocess.run(
                 [self.binary_path(app)] + passthrough,
@@ -171,7 +169,6 @@ class WindowsRunCommand(RunCommand):
             self._stream_app_logs(
                 app,
                 popen=app_popen,
-                test_mode=test_mode,
                 clean_output=False,
             )
 


Test patch:
diff --git a/tests/commands/build/conftest.py b/tests/commands/build/conftest.py
index 32390ecb..edf6a45d 100644
--- a/tests/commands/build/conftest.py
+++ b/tests/commands/build/conftest.py
@@ -52,7 +52,7 @@ class DummyBuildCommand(BuildCommand):
         self.actions.append(("verify-app-tools", app.app_name))
 
     def build_app(self, app, **kwargs):
-        self.actions.append(("build", app.app_name, kwargs.copy()))
+        self.actions.append(("build", app.app_name, app.test_mode, kwargs.copy()))
         # Remove arguments consumed by the underlying call to build_app()
         kwargs.pop("update", None)
         kwargs.pop("update_requirements", None)
@@ -60,26 +60,23 @@ class DummyBuildCommand(BuildCommand):
         kwargs.pop("update_support", None)
         kwargs.pop("update_stub", None)
         kwargs.pop("no_update", None)
-        kwargs.pop("test_mode", None)
         return full_options({"build_state": app.app_name}, kwargs)
 
     # These commands override the default behavior, simply tracking that
     # they were invoked, rather than instantiating a Create/Update command.
     # This is for testing purposes.
     def create_command(self, app, **kwargs):
-        self.actions.append(("create", app.app_name, kwargs.copy()))
+        self.actions.append(("create", app.app_name, app.test_mode, kwargs.copy()))
         # Remove arguments consumed by the underlying call to create_app()
-        kwargs.pop("test_mode", None)
         return full_options({"create_state": app.app_name}, kwargs)
 
     def update_command(self, app, **kwargs):
-        self.actions.append(("update", app.app_name, kwargs.copy()))
+        self.actions.append(("update", app.app_name, app.test_mode, kwargs.copy()))
         # Remove arguments consumed by the underlying call to update_app()
         kwargs.pop("update_requirements", None)
         kwargs.pop("update_resources", None)
         kwargs.pop("update_support", None)
         kwargs.pop("update_stub", None)
-        kwargs.pop("test_mode", None)
         return full_options({"update_state": app.app_name}, kwargs)
 
 
diff --git a/tests/commands/build/test_call.py b/tests/commands/build/test_call.py
index f70be9f2..5dcfa3e9 100644
--- a/tests/commands/build/test_call.py
+++ b/tests/commands/build/test_call.py
@@ -30,7 +30,7 @@ def test_specific_app(build_command, first_app, second_app):
         # App tools are verified for app
         ("verify-app-tools", "first"),
         # Build the first app; no state
-        ("build", "first", {"test_mode": False}),
+        ("build", "first", False, {}),
     ]
 
 
@@ -62,13 +62,13 @@ def test_multiple_apps(build_command, first_app, second_app):
         # App tools are verified for first app
         ("verify-app-tools", "first"),
         # Build the first app; no state
-        ("build", "first", {"test_mode": False}),
+        ("build", "first", False, {}),
         # App template is verified for second app
         ("verify-app-template", "second"),
         # App tools are verified for second app
         ("verify-app-tools", "second"),
         # Build the second apps; state from previous build.
-        ("build", "second", {"build_state": "first", "test_mode": False}),
+        ("build", "second", False, {"build_state": "first"}),
     ]
 
 
@@ -96,12 +96,12 @@ def test_non_existent(build_command, first_app_config, second_app):
         ("finalize-app-config", "first"),
         ("finalize-app-config", "second"),
         # First App doesn't exist, so it will be created, then built
-        ("create", "first", {"test_mode": False}),
+        ("create", "first", False, {}),
         # App template is verified for first app
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"create_state": "first", "test_mode": False}),
+        ("build", "first", False, {"create_state": "first"}),
         # App template is verified for second app
         ("verify-app-template", "second"),
         # App tools are verified for second app
@@ -110,7 +110,8 @@ def test_non_existent(build_command, first_app_config, second_app):
         (
             "build",
             "second",
-            {"create_state": "first", "build_state": "first", "test_mode": False},
+            False,
+            {"create_state": "first", "build_state": "first"},
         ),
     ]
 
@@ -144,13 +145,13 @@ def test_unbuilt(build_command, first_app_unbuilt, second_app):
         # App tools are verified for first app
         ("verify-app-tools", "first"),
         # First App exists, but hasn't been built; it will be built.
-        ("build", "first", {"test_mode": False}),
+        ("build", "first", False, {}),
         # App template is verified for second app
         ("verify-app-template", "second"),
         # App tools are verified for second app
         ("verify-app-tools", "second"),
         # Second app has been built before; it will be built again.
-        ("build", "second", {"build_state": "first", "test_mode": False}),
+        ("build", "second", False, {"build_state": "first"}),
     ]
 
 
@@ -181,8 +182,8 @@ def test_update_app(build_command, first_app, second_app):
         (
             "update",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -193,15 +194,15 @@ def test_update_app(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": False}),
+        ("build", "first", False, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            False,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -215,7 +216,8 @@ def test_update_app(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": False},
+            False,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -247,8 +249,8 @@ def test_update_app_requirements(build_command, first_app, second_app):
         (
             "update",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update_requirements": True,
                 "update_resources": False,
                 "update_support": False,
@@ -259,15 +261,15 @@ def test_update_app_requirements(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": False}),
+        ("build", "first", False, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            False,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": False,
                 "update_requirements": True,
                 "update_resources": False,
                 "update_support": False,
@@ -281,7 +283,8 @@ def test_update_app_requirements(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": False},
+            False,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -313,8 +316,8 @@ def test_update_app_support(build_command, first_app, second_app):
         (
             "update",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": True,
@@ -325,15 +328,15 @@ def test_update_app_support(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": False}),
+        ("build", "first", False, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            False,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": True,
@@ -347,7 +350,8 @@ def test_update_app_support(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": False},
+            False,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -379,8 +383,8 @@ def test_update_app_stub(build_command, first_app, second_app):
         (
             "update",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -391,15 +395,15 @@ def test_update_app_stub(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": False}),
+        ("build", "first", False, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            False,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -413,7 +417,8 @@ def test_update_app_stub(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": False},
+            False,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -445,8 +450,8 @@ def test_update_app_resources(build_command, first_app, second_app):
         (
             "update",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": True,
                 "update_support": False,
@@ -457,15 +462,15 @@ def test_update_app_resources(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": False}),
+        ("build", "first", False, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            False,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": True,
                 "update_support": False,
@@ -479,7 +484,8 @@ def test_update_app_resources(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": False},
+            False,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -508,20 +514,20 @@ def test_update_non_existent(build_command, first_app_config, second_app):
         ("finalize-app-config", "first"),
         ("finalize-app-config", "second"),
         # First App doesn't exist, so it will be created, then built
-        ("create", "first", {"test_mode": False}),
+        ("create", "first", False, {}),
         # App template is verified for first app
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"create_state": "first", "test_mode": False}),
+        ("build", "first", False, {"create_state": "first"}),
         # Second app *does* exist, so it will be updated, then built
         (
             "update",
             "second",
+            False,
             {
                 "create_state": "first",
                 "build_state": "first",
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -535,11 +541,11 @@ def test_update_non_existent(build_command, first_app_config, second_app):
         (
             "build",
             "second",
+            False,
             {
                 "create_state": "first",
                 "build_state": "first",
                 "update_state": "second",
-                "test_mode": False,
             },
         ),
     ]
@@ -572,8 +578,8 @@ def test_update_unbuilt(build_command, first_app_unbuilt, second_app):
         (
             "update",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -584,15 +590,15 @@ def test_update_unbuilt(build_command, first_app_unbuilt, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": False}),
+        ("build", "first", False, {"update_state": "first"}),
         # Second app has been built before; it will be built again.
         (
             "update",
             "second",
+            False,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": False,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -606,7 +612,8 @@ def test_update_unbuilt(build_command, first_app_unbuilt, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": False},
+            False,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -638,8 +645,8 @@ def test_build_test(build_command, first_app, second_app):
         (
             "update",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -650,15 +657,15 @@ def test_build_test(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": True}),
+        ("build", "first", True, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            True,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -672,7 +679,8 @@ def test_build_test(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": True},
+            True,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -706,7 +714,7 @@ def test_build_test_no_update(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"test_mode": True}),
+        ("build", "first", True, {}),
         # No update of the second app
         # App template is verified for second app
         ("verify-app-template", "second"),
@@ -715,7 +723,8 @@ def test_build_test_no_update(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"build_state": "first", "test_mode": True},
+            True,
+            {"build_state": "first"},
         ),
     ]
 
@@ -748,8 +757,8 @@ def test_build_test_update_dependencies(build_command, first_app, second_app):
         (
             "update",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update_requirements": True,
                 "update_resources": False,
                 "update_support": False,
@@ -760,15 +769,15 @@ def test_build_test_update_dependencies(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": True}),
+        ("build", "first", True, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            True,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": True,
                 "update_requirements": True,
                 "update_resources": False,
                 "update_support": False,
@@ -782,7 +791,8 @@ def test_build_test_update_dependencies(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": True},
+            True,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -815,8 +825,8 @@ def test_build_test_update_resources(build_command, first_app, second_app):
         (
             "update",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": True,
                 "update_support": False,
@@ -827,15 +837,15 @@ def test_build_test_update_resources(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": True}),
+        ("build", "first", True, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            True,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": True,
                 "update_support": False,
@@ -849,7 +859,8 @@ def test_build_test_update_resources(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": True},
+            True,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -882,8 +893,8 @@ def test_build_test_update_support(build_command, first_app, second_app):
         (
             "update",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": True,
@@ -894,15 +905,15 @@ def test_build_test_update_support(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": True}),
+        ("build", "first", True, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            True,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": True,
@@ -916,7 +927,8 @@ def test_build_test_update_support(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": True},
+            True,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -949,8 +961,8 @@ def test_build_test_update_stub(build_command, first_app, second_app):
         (
             "update",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -961,15 +973,15 @@ def test_build_test_update_stub(build_command, first_app, second_app):
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"update_state": "first", "test_mode": True}),
+        ("build", "first", True, {"update_state": "first"}),
         # Update then build the second app
         (
             "update",
             "second",
+            True,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -983,7 +995,8 @@ def test_build_test_update_stub(build_command, first_app, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": True},
+            True,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -1111,20 +1124,20 @@ def test_test_app_non_existent(build_command, first_app_config, second_app):
         ("finalize-app-config", "first"),
         ("finalize-app-config", "second"),
         # First App doesn't exist, so it will be created, then built
-        ("create", "first", {"test_mode": True}),
+        ("create", "first", True, {}),
         # App template is verified for first app
         ("verify-app-template", "first"),
         # App tools are verified for first app
         ("verify-app-tools", "first"),
-        ("build", "first", {"create_state": "first", "test_mode": True}),
+        ("build", "first", True, {"create_state": "first"}),
         # Second app *does* exist, so it will be updated, then built
         (
             "update",
             "second",
+            True,
             {
                 "create_state": "first",
                 "build_state": "first",
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -1138,11 +1151,11 @@ def test_test_app_non_existent(build_command, first_app_config, second_app):
         (
             "build",
             "second",
+            True,
             {
                 "create_state": "first",
                 "build_state": "first",
                 "update_state": "second",
-                "test_mode": True,
             },
         ),
     ]
@@ -1176,8 +1189,8 @@ def test_test_app_unbuilt(build_command, first_app_unbuilt, second_app):
         (
             "update",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -1191,16 +1204,17 @@ def test_test_app_unbuilt(build_command, first_app_unbuilt, second_app):
         (
             "build",
             "first",
-            {"update_state": "first", "test_mode": True},
+            True,
+            {"update_state": "first"},
         ),
         # Second app has been built before; it will be built again.
         (
             "update",
             "second",
+            True,
             {
                 "update_state": "first",
                 "build_state": "first",
-                "test_mode": True,
                 "update_requirements": False,
                 "update_resources": False,
                 "update_support": False,
@@ -1214,7 +1228,8 @@ def test_test_app_unbuilt(build_command, first_app_unbuilt, second_app):
         (
             "build",
             "second",
-            {"update_state": "second", "build_state": "first", "test_mode": True},
+            True,
+            {"update_state": "second", "build_state": "first"},
         ),
     ]
 
@@ -1249,7 +1264,7 @@ def test_build_app_single(build_command, first_app, second_app, app_flags):
         # App tools are verified for first app
         ("verify-app-tools", "first"),
         # Build the first app
-        ("build", "first", {"test_mode": False}),
+        ("build", "first", False, {}),
     ]
 
 
@@ -1326,8 +1341,8 @@ def test_build_app_all_flags(build_command, first_app, second_app):
         (
             "update",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update_requirements": True,
                 "update_resources": True,
                 "update_support": True,
@@ -1339,5 +1354,5 @@ def test_build_app_all_flags(build_command, first_app, second_app):
         # App tools are verified for first app
         ("verify-app-tools", "first"),
         # First app is built in test mode
-        ("build", "first", {"update_state": "first", "test_mode": True}),
+        ("build", "first", True, {"update_state": "first"}),
     ]
diff --git a/tests/commands/create/conftest.py b/tests/commands/create/conftest.py
index 2e08b3d1..b89a0194 100644
--- a/tests/commands/create/conftest.py
+++ b/tests/commands/create/conftest.py
@@ -168,11 +168,11 @@ class TrackingCreateCommand(DummyCreateCommand):
     def install_app_support_package(self, app):
         self.actions.append(("support", app.app_name))
 
-    def install_app_requirements(self, app, test_mode):
-        self.actions.append(("requirements", app.app_name, test_mode))
+    def install_app_requirements(self, app):
+        self.actions.append(("requirements", app.app_name, app.test_mode))
 
-    def install_app_code(self, app, test_mode):
-        self.actions.append(("code", app.app_name, test_mode))
+    def install_app_code(self, app):
+        self.actions.append(("code", app.app_name, app.test_mode))
 
     def install_app_resources(self, app):
         self.actions.append(("resources", app.app_name))
diff --git a/tests/commands/create/test_generate_app_template.py b/tests/commands/create/test_generate_app_template.py
index 7b2abc6f..74629836 100644
--- a/tests/commands/create/test_generate_app_template.py
+++ b/tests/commands/create/test_generate_app_template.py
@@ -44,6 +44,7 @@ def full_context():
         "document_types": {},
         "license": {"file": "LICENSE"},
         "requirement_installer_args": [],
+        "test_mode": False,
         # Properties of the generating environment
         "python_version": platform.python_version(),
         "host_arch": "gothic",
diff --git a/tests/commands/create/test_install_app_code.py b/tests/commands/create/test_install_app_code.py
index ab0a754c..31a8a78e 100644
--- a/tests/commands/create/test_install_app_code.py
+++ b/tests/commands/create/test_install_app_code.py
@@ -52,7 +52,7 @@ def test_no_code(
 
     myapp.sources = None
 
-    create_command.install_app_code(myapp, test_mode=False)
+    create_command.install_app_code(myapp)
 
     # No request was made to install requirements
     create_command.tools.shutil.rmtree.assert_called_once_with(app_path)
@@ -78,7 +78,7 @@ def test_empty_code(
 
     myapp.sources = []
 
-    create_command.install_app_code(myapp, test_mode=False)
+    create_command.install_app_code(myapp)
 
     # No request was made to install requirements
     create_command.tools.shutil.rmtree.assert_called_once_with(app_path)
@@ -101,7 +101,7 @@ def test_source_missing(
     myapp.sources = ["missing"]
 
     with pytest.raises(MissingAppSources):
-        create_command.install_app_code(myapp, test_mode=False)
+        create_command.install_app_code(myapp)
 
     # Distinfo won't be created.
     dist_info_path = app_path / "myapp-1.2.3.dist-info"
@@ -140,7 +140,7 @@ def test_source_dir(
     # Set the app definition, and install sources
     myapp.sources = ["src/first", "src/second"]
 
-    create_command.install_app_code(myapp, test_mode=False)
+    create_command.install_app_code(myapp)
 
     # All the sources exist.
     assert (app_path / "first").exists()
@@ -184,7 +184,7 @@ def test_source_file(
     # Set the app definition, and install sources
     myapp.sources = ["src/demo.py", "other.py"]
 
-    create_command.install_app_code(myapp, test_mode=False)
+    create_command.install_app_code(myapp)
 
     # All the sources exist.
     assert (app_path / "demo.py").exists()
@@ -233,7 +233,7 @@ def test_no_existing_app_folder(
     # Set the app definition, and install sources
     myapp.sources = ["src/first/demo.py", "src/second"]
 
-    create_command.install_app_code(myapp, test_mode=False)
+    create_command.install_app_code(myapp)
 
     # All the new sources exist, and contain the new content.
     assert (app_path / "demo.py").exists()
@@ -336,7 +336,7 @@ def test_replace_sources(
     # Set the app definition, and install sources
     myapp.sources = ["src/first/demo.py", "src/second"]
 
-    create_command.install_app_code(myapp, test_mode=False)
+    create_command.install_app_code(myapp)
 
     # All the new sources exist, and contain the new content.
     assert (app_path / "demo.py").exists()
@@ -387,7 +387,7 @@ def test_non_latin_metadata(
 
     myapp.sources = []
 
-    create_command.install_app_code(myapp, test_mode=False)
+    create_command.install_app_code(myapp)
 
     # No request was made to install requirements
     create_command.tools.shutil.rmtree.assert_called_once_with(app_path)
@@ -474,7 +474,7 @@ def test_test_sources(
     myapp.sources = ["src/first", "src/second"]
     myapp.test_sources = ["tests", "othertests"]
 
-    create_command.install_app_code(myapp, test_mode=False)
+    create_command.install_app_code(myapp)
 
     # App sources exist.
     assert (app_path / "first").exists()
@@ -545,8 +545,9 @@ def test_test_sources_test_mode(
     # Set the app definition, and install sources
     myapp.sources = ["src/first", "src/second"]
     myapp.test_sources = ["tests", "othertests"]
+    myapp.test_mode = True
 
-    create_command.install_app_code(myapp, test_mode=True)
+    create_command.install_app_code(myapp)
 
     # App sources exist.
     assert (app_path / "first").exists()
@@ -616,8 +617,9 @@ def test_only_test_sources_test_mode(
     # Set the app definition, and install sources
     myapp.sources = None
     myapp.test_sources = ["tests", "othertests"]
+    myapp.test_mode = True
 
-    create_command.install_app_code(myapp, test_mode=True)
+    create_command.install_app_code(myapp)
 
     # App sources do not exist.
     assert not (app_path / "first").exists()
diff --git a/tests/commands/create/test_install_app_requirements.py b/tests/commands/create/test_install_app_requirements.py
index aa439b1c..b33a0a15 100644
--- a/tests/commands/create/test_install_app_requirements.py
+++ b/tests/commands/create/test_install_app_requirements.py
@@ -80,7 +80,7 @@ def test_bad_path_index(create_command, myapp, bundle_path, app_requirements_pat
         BriefcaseCommandError,
         match=r"Application path index file does not define `app_requirements_path` or `app_packages_path`",
     ):
-        create_command.install_app_requirements(myapp, test_mode=False)
+        create_command.install_app_requirements(myapp)
 
     # pip wasn't invoked
     create_command.tools[myapp].app_context.run.assert_not_called()
@@ -102,7 +102,7 @@ def test_app_packages_no_requires(
     """If an app has no requirements, install_app_requirements is a no-op."""
     myapp.requires = None
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # No request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_not_called()
@@ -117,7 +117,7 @@ def test_app_packages_empty_requires(
     """If an app has an empty requirements list, install_app_requirements is a no-op."""
     myapp.requires = []
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # No request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_not_called()
@@ -132,7 +132,7 @@ def test_app_packages_valid_requires(
     """If an app has a valid list of requirements, pip is invoked."""
     myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"]
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -173,7 +173,7 @@ def test_app_packages_requirement_installer_args_no_paths(
     myapp.requirement_installer_args = ["--no-cache"]
     myapp.requires = ["package"]
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -210,7 +210,7 @@ def test_app_packages_requirement_installer_args_path_transformed(
     myapp.requirement_installer_args = ["--extra-index-url", "./packages"]
     myapp.requires = ["package"]
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -248,7 +248,7 @@ def test_app_packages_requirement_installer_args_coincidental_path_not_transform
     myapp.requirement_installer_args = ["-f./wheels"]
     myapp.requires = ["package"]
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -285,7 +285,7 @@ def test_app_packages_requirement_installer_args_path_not_transformed(
     myapp.requirement_installer_args = ["--extra-index-url", "./packages"]
     myapp.requires = ["package"]
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -323,7 +323,7 @@ def test_app_packages_requirement_installer_args_combined_argument_not_transform
     myapp.requirement_installer_args = ["--extra-index-url=./packages"]
     myapp.requires = ["package"]
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -362,7 +362,7 @@ def test_app_packages_valid_requires_no_support_package(
         "paths": {"app_packages_path": "path/to/app_packages"}
     }
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -410,7 +410,7 @@ def test_app_packages_invalid_requires(
     )
 
     with pytest.raises(RequirementsInstallError):
-        create_command.install_app_requirements(myapp, test_mode=False)
+        create_command.install_app_requirements(myapp)
 
     # But the request to install was still made
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -456,7 +456,7 @@ def test_app_packages_offline(
     )
 
     with pytest.raises(RequirementsInstallError):
-        create_command.install_app_requirements(myapp, test_mode=False)
+        create_command.install_app_requirements(myapp)
 
     # But the request to install was still made
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -508,7 +508,7 @@ def test_app_packages_install_requirements(
     )
 
     # Install the requirements
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # The request to install was made
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -565,7 +565,7 @@ def test_app_packages_replace_existing_requirements(
     )
 
     # Install the requirements
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # The request to install was still made
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -617,7 +617,7 @@ def test_app_requirements_no_requires(
     myapp.requires = None
 
     # Install requirements into the bundle
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # requirements.txt doesn't exist either
     assert app_requirements_path.exists()
@@ -641,7 +641,7 @@ def test_app_requirements_empty_requires(
     myapp.requires = []
 
     # Install requirements into the bundle
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # requirements.txt doesn't exist either
     assert app_requirements_path.exists()
@@ -665,7 +665,7 @@ def test_app_requirements_requires(
     myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"]
 
     # Install requirements into the bundle
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # requirements.txt doesn't exist either
     assert app_requirements_path.exists()
@@ -692,7 +692,7 @@ def test_app_requirements_requirement_installer_args_no_template_support(
     myapp.requires = ["my-favourite-package"]
 
     # Install requirements into the bundle
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # requirements.txt exists either
     assert app_requirements_path.exists()
@@ -719,7 +719,7 @@ def test_app_requirements_requirement_installer_args_with_template_support(
     myapp.requires = ["my-favourite-package"]
 
     # Install requirements into the bundle
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # requirements.txt exists either
     assert app_requirements_path.exists()
@@ -753,7 +753,7 @@ def test_app_requirements_requirement_installer_args_without_requires_no_templat
     myapp.requires = []
 
     # Install requirements into the bundle
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # requirements.txt exists either
     assert app_requirements_path.exists()
@@ -783,7 +783,7 @@ def test_app_requirements_requirement_installer_args_without_requires_with_templ
     myapp.requires = []
 
     # Install requirements into the bundle
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # requirements.txt exists either
     assert app_requirements_path.exists()
@@ -837,7 +837,7 @@ def _test_app_requirements_paths(
         converted = requirement
     myapp.requires = ["first", requirement, "third"]
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
     with app_requirements_path.open(encoding="utf-8") as f:
         assert f.read() == (
             "\n".join(
@@ -976,7 +976,7 @@ def test_app_packages_test_requires(
     myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"]
     myapp.test_requires = ["pytest", "pytest-tldr"]
 
-    create_command.install_app_requirements(myapp, test_mode=False)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -1014,8 +1014,9 @@ def test_app_packages_test_requires_test_mode(
     """If an app has test requirements and we're in test mode, they are installed."""
     myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"]
     myapp.test_requires = ["pytest", "pytest-tldr"]
+    myapp.test_mode = True
 
-    create_command.install_app_requirements(myapp, test_mode=True)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
@@ -1056,8 +1057,9 @@ def test_app_packages_only_test_requires_test_mode(
     installed."""
     myapp.requires = None
     myapp.test_requires = ["pytest", "pytest-tldr"]
+    myapp.test_mode = True
 
-    create_command.install_app_requirements(myapp, test_mode=True)
+    create_command.install_app_requirements(myapp)
 
     # A request was made to install requirements
     create_command.tools[myapp].app_context.run.assert_called_with(
diff --git a/tests/commands/dev/test_call.py b/tests/commands/dev/test_call.py
index ed170409..8f759e1b 100644
--- a/tests/commands/dev/test_call.py
+++ b/tests/commands/dev/test_call.py
@@ -43,11 +43,11 @@ class DummyDevCommand(DevCommand):
     def install_dev_requirements(self, app, **kwargs):
         self.actions.append(("dev_requirements", app.app_name, kwargs))
 
-    def get_environment(self, app, test_mode):
+    def get_environment(self, app):
         return self.env
 
     def run_dev_app(self, app, env, **kwargs):
-        self.actions.append(("run_dev", app.app_name, kwargs, env))
+        self.actions.append(("run_dev", app.app_name, app.test_mode, kwargs, env))
         return full_options({"run_dev_state": app.app_name, "env": env}, kwargs)
 
 
@@ -80,7 +80,7 @@ def test_no_args_one_app(dev_command, first_app):
         # App tools are verified for app
         ("verify-app-tools", "first"),
         # Run the first app devly
-        ("run_dev", "first", {"test_mode": False, "passthrough": []}, dev_command.env),
+        ("run_dev", "first", False, {"passthrough": []}, dev_command.env),
     ]
 
 
@@ -127,7 +127,7 @@ def test_with_arg_one_app(dev_command, first_app):
         # App tools are verified for app
         ("verify-app-tools", "first"),
         # Run the first app devly
-        ("run_dev", "first", {"test_mode": False, "passthrough": []}, dev_command.env),
+        ("run_dev", "first", False, {"passthrough": []}, dev_command.env),
     ]
 
 
@@ -156,7 +156,7 @@ def test_with_arg_two_apps(dev_command, first_app, second_app):
         # App tools are verified for app
         ("verify-app-tools", "second"),
         # Run the second app devly
-        ("run_dev", "second", {"test_mode": False, "passthrough": []}, dev_command.env),
+        ("run_dev", "second", False, {"passthrough": []}, dev_command.env),
     ]
 
 
@@ -206,7 +206,7 @@ def test_update_requirements(dev_command, first_app):
         # An update was requested
         ("dev_requirements", "first", {}),
         # Then, it will be started
-        ("run_dev", "first", {"test_mode": False, "passthrough": []}, dev_command.env),
+        ("run_dev", "first", False, {"passthrough": []}, dev_command.env),
     ]
 
 
@@ -236,7 +236,7 @@ def test_run_uninstalled(dev_command, first_app_uninstalled):
         # The app will be installed
         ("dev_requirements", "first", {}),
         # Then, it will be started
-        ("run_dev", "first", {"test_mode": False, "passthrough": []}, dev_command.env),
+        ("run_dev", "first", False, {"passthrough": []}, dev_command.env),
     ]
 
 
@@ -267,7 +267,7 @@ def test_update_uninstalled(dev_command, first_app_uninstalled):
         # An update was requested
         ("dev_requirements", "first", {}),
         # Then, it will be started
-        ("run_dev", "first", {"test_mode": False, "passthrough": []}, dev_command.env),
+        ("run_dev", "first", False, {"passthrough": []}, dev_command.env),
     ]
 
 
@@ -323,7 +323,7 @@ def test_run_test(dev_command, first_app):
         # App tools are verified for app
         ("verify-app-tools", "first"),
         # Then, it will be started
-        ("run_dev", "first", {"test_mode": True, "passthrough": []}, dev_command.env),
+        ("run_dev", "first", True, {"passthrough": []}, dev_command.env),
     ]
 
 
@@ -353,5 +353,5 @@ def test_run_test_uninstalled(dev_command, first_app_uninstalled):
         # Development requirements will be installed
         ("dev_requirements", "first", {}),
         # Then, it will be started
-        ("run_dev", "first", {"test_mode": True, "passthrough": []}, dev_command.env),
+        ("run_dev", "first", True, {"passthrough": []}, dev_command.env),
     ]
diff --git a/tests/commands/dev/test_get_environment.py b/tests/commands/dev/test_get_environment.py
index 2f507d4c..a46f5e9b 100644
--- a/tests/commands/dev/test_get_environment.py
+++ b/tests/commands/dev/test_get_environment.py
@@ -12,7 +12,7 @@ PYTHONMALLOC = "PYTHONMALLOC"
 @pytest.mark.skipif(sys.platform != "win32", reason="Relevant only for windows")
 def test_pythonpath_with_one_source_in_windows(dev_command, first_app):
     """Test get environment with one source."""
-    env = dev_command.get_environment(first_app, test_mode=False)
+    env = dev_command.get_environment(first_app)
     assert env[PYTHONPATH] == f"{Path.cwd() / 'src'}"
     assert env[PYTHONMALLOC] == "default"
 
@@ -20,7 +20,8 @@ def test_pythonpath_with_one_source_in_windows(dev_command, first_app):
 @pytest.mark.skipif(sys.platform != "win32", reason="Relevant only for windows")
 def test_pythonpath_with_one_source_test_mode_in_windows(dev_command, first_app):
     """Test get environment with one source, no tests sources, in test mode."""
-    env = dev_command.get_environment(first_app, test_mode=True)
+    first_app.test_mode = True
+    env = dev_command.get_environment(first_app)
     assert env[PYTHONPATH] == f"{Path.cwd() / 'src'}"
     assert env[PYTHONMALLOC] == "default"
 
@@ -28,7 +29,7 @@ def test_pythonpath_with_one_source_test_mode_in_windows(dev_command, first_app)
 @pytest.mark.skipif(sys.platform != "win32", reason="Relevant only for windows")
 def test_pythonpath_with_two_sources_in_windows(dev_command, third_app):
     """Test get environment with two sources in windows."""
-    env = dev_command.get_environment(third_app, test_mode=False)
+    env = dev_command.get_environment(third_app)
     assert env[PYTHONPATH] == f"{Path.cwd() / 'src'};{Path.cwd()}"
     assert env[PYTHONMALLOC] == "default"
 
@@ -37,7 +38,8 @@ def test_pythonpath_with_two_sources_in_windows(dev_command, third_app):
 def test_pythonpath_with_two_sources_and_tests_in_windows(dev_command, third_app):
     """Test get environment with two sources and test sources in windows."""
     third_app.test_sources = ["tests", "path/to/other"]
-    env = dev_command.get_environment(third_app, test_mode=True)
+    third_app.test_mode = True
+    env = dev_command.get_environment(third_app)
     assert (
         env[PYTHONPATH]
         == f"{Path.cwd() / 'src'};{Path.cwd()};{Path.cwd() / 'path' / 'to'}"
@@ -48,7 +50,7 @@ def test_pythonpath_with_two_sources_and_tests_in_windows(dev_command, third_app
 @pytest.mark.skipif(sys.platform == "win32", reason="Relevant only for non-windows")
 def test_pythonpath_with_one_source(dev_command, first_app):
     """Test get environment with one source."""
-    env = dev_command.get_environment(first_app, test_mode=False)
+    env = dev_command.get_environment(first_app)
     assert env[PYTHONPATH] == f"{Path.cwd() / 'src'}"
     assert PYTHONMALLOC not in env
 
@@ -56,7 +58,8 @@ def test_pythonpath_with_one_source(dev_command, first_app):
 @pytest.mark.skipif(sys.platform == "win32", reason="Relevant only for non-windows")
 def test_pythonpath_with_one_source_test_mode(dev_command, first_app):
     """Test get environment with one source, no tests sources, in test mode."""
-    env = dev_command.get_environment(first_app, test_mode=True)
+    first_app.test_mode = True
+    env = dev_command.get_environment(first_app)
     assert env[PYTHONPATH] == f"{Path.cwd() / 'src'}"
     assert PYTHONMALLOC not in env
 
@@ -64,7 +67,7 @@ def test_pythonpath_with_one_source_test_mode(dev_command, first_app):
 @pytest.mark.skipif(sys.platform == "win32", reason="Relevant only for non-windows")
 def test_pythonpath_with_two_sources_in_linux(dev_command, third_app):
     """Test get environment with two sources in linux."""
-    env = dev_command.get_environment(third_app, test_mode=False)
+    env = dev_command.get_environment(third_app)
     assert env[PYTHONPATH] == f"{Path.cwd() / 'src'}:{Path.cwd()}"
     assert PYTHONMALLOC not in env
 
@@ -72,7 +75,8 @@ def test_pythonpath_with_two_sources_in_linux(dev_command, third_app):
 @pytest.mark.skipif(sys.platform == "win32", reason="Relevant only for non-windows")
 def test_pythonpath_with_two_sources_and_tests_in_linux(dev_command, third_app):
     """Test get environment with two sources and test sources in linux."""
-    env = dev_command.get_environment(third_app, test_mode=True)
+    third_app.test_mode = True
+    env = dev_command.get_environment(third_app)
     assert (
         env[PYTHONPATH]
         == f"{Path.cwd() / 'src'}:{Path.cwd()}:{Path.cwd() / 'path' / 'to'}"
@@ -83,12 +87,12 @@ def test_pythonpath_with_two_sources_and_tests_in_linux(dev_command, third_app):
 def test_non_verbose_mode(dev_command, first_app):
     """Non-verbose mode doesn't include BRIEFCASE_DEBUG in the dev environment."""
     dev_command.console.verbosity = LogLevel.INFO
-    env = dev_command.get_environment(first_app, test_mode=False)
+    env = dev_command.get_environment(first_app)
     assert "BRIEFCASE_DEBUG" not in env
 
 
 def test_verbose_mode(dev_command, first_app):
     """Verbose mode adds BRIEFCASE_DEBUG to the dev environment."""
     dev_command.console.verbosity = LogLevel.DEBUG
-    env = dev_command.get_environment(first_app, test_mode=False)
+    env = dev_command.get_environment(first_app)
     assert env["BRIEFCASE_DEBUG"] == "1"
diff --git a/tests/commands/dev/test_run_dev_app.py b/tests/commands/dev/test_run_dev_app.py
index 4f49cf84..0e0bd4bd 100644
--- a/tests/commands/dev/test_run_dev_app.py
+++ b/tests/commands/dev/test_run_dev_app.py
@@ -14,7 +14,6 @@ def test_dev_run(dev_command, first_app, tmp_path):
     dev_command.run_dev_app(
         first_app,
         env={"a": 1, "b": 2, "c": 3},
-        test_mode=False,
         passthrough=[],
     )
 
@@ -46,7 +45,6 @@ def test_dev_run(dev_command, first_app, tmp_path):
     dev_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=app_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -60,7 +58,6 @@ def test_dev_run_with_args(dev_command, first_app, tmp_path):
     dev_command.run_dev_app(
         first_app,
         env={"a": 1, "b": 2, "c": 3},
-        test_mode=False,
         passthrough=["foo", "bar", "--whiz"],
     )
 
@@ -92,7 +89,6 @@ def test_dev_run_with_args(dev_command, first_app, tmp_path):
     dev_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=app_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -102,6 +98,7 @@ def test_dev_test_mode(dev_command, first_app, is_console_app, tmp_path):
     """The test suite can be run in development mode."""
     # Test mode is the same regardless of whether it's a console app or not.
     first_app.console_app = is_console_app
+    first_app.test_mode = True
 
     dev_command._stream_app_logs = mock.MagicMock()
     app_popen = mock.MagicMock()
@@ -110,7 +107,6 @@ def test_dev_test_mode(dev_command, first_app, is_console_app, tmp_path):
     dev_command.run_dev_app(
         first_app,
         env={"a": 1, "b": 2, "c": 3},
-        test_mode=True,
         passthrough=[],
     )
 
@@ -142,7 +138,6 @@ def test_dev_test_mode(dev_command, first_app, is_console_app, tmp_path):
     dev_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=app_popen,
-        test_mode=True,
         clean_output=False,
     )
 
@@ -152,6 +147,7 @@ def test_dev_test_mode_with_args(dev_command, first_app, is_console_app, tmp_pat
     """The test suite can be run in development mode with args."""
     # Test mode is the same regardless of whether it's a console app or not.
     first_app.console_app = is_console_app
+    first_app.test_mode = True
 
     dev_command._stream_app_logs = mock.MagicMock()
     app_popen = mock.MagicMock()
@@ -160,7 +156,6 @@ def test_dev_test_mode_with_args(dev_command, first_app, is_console_app, tmp_pat
     dev_command.run_dev_app(
         first_app,
         env={"a": 1, "b": 2, "c": 3},
-        test_mode=True,
         passthrough=["foo", "bar", "--whiz"],
     )
 
@@ -192,7 +187,6 @@ def test_dev_test_mode_with_args(dev_command, first_app, is_console_app, tmp_pat
     dev_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=app_popen,
-        test_mode=True,
         clean_output=False,
     )
 
@@ -207,7 +201,6 @@ def test_dev_run_console(dev_command, first_app, tmp_path):
     dev_command.run_dev_app(
         first_app,
         env={"a": 1, "b": 2, "c": 3},
-        test_mode=False,
         passthrough=[],
     )
 
@@ -250,7 +243,6 @@ def test_dev_run_console_with_args(dev_command, first_app, tmp_path):
     dev_command.run_dev_app(
         first_app,
         env={"a": 1, "b": 2, "c": 3},
-        test_mode=False,
         passthrough=["foo", "bar", "--whiz"],
     )
 
diff --git a/tests/commands/package/conftest.py b/tests/commands/package/conftest.py
index 1abb69a8..1bffc5ec 100644
--- a/tests/commands/package/conftest.py
+++ b/tests/commands/package/conftest.py
@@ -92,7 +92,6 @@ class DummyPackageCommand(PackageCommand):
     def create_command(self, app, **kwargs):
         self.actions.append(("create", app.app_name, kwargs.copy()))
         # Remove arguments consumed by the underlying call to create_app()
-        kwargs.pop("test_mode", None)
         return full_options({"create_state": app.app_name}, kwargs)
 
     def update_command(self, app, **kwargs):
@@ -102,7 +101,6 @@ class DummyPackageCommand(PackageCommand):
         kwargs.pop("update_requirements", None)
         kwargs.pop("update_resources", None)
         kwargs.pop("update_support", None)
-        kwargs.pop("test_mode", None)
         return full_options({"update_state": app.app_name}, kwargs)
 
     def build_command(self, app, **kwargs):
@@ -112,7 +110,6 @@ class DummyPackageCommand(PackageCommand):
         kwargs.pop("update_requirements", None)
         kwargs.pop("update_resources", None)
         kwargs.pop("update_support", None)
-        kwargs.pop("test_mode", None)
         return full_options({"build_state": app.app_name}, kwargs)
 
 
diff --git a/tests/commands/publish/conftest.py b/tests/commands/publish/conftest.py
index 81271763..c8233224 100644
--- a/tests/commands/publish/conftest.py
+++ b/tests/commands/publish/conftest.py
@@ -69,13 +69,11 @@ class DummyPublishCommand(PublishCommand):
     def create_command(self, app, **kwargs):
         self.actions.append(("create", app.app_name, kwargs.copy()))
         # Remove arguments consumed by the underlying call to create_app()
-        kwargs.pop("test_mode", None)
         return full_options({"create_state": app.app_name}, kwargs)
 
     def update_command(self, app, **kwargs):
         self.actions.append(("update", app.app_name, kwargs.copy()))
         # Remove arguments consumed by the underlying call to update_app()
-        kwargs.pop("test_mode", None)
         kwargs.pop("update_requirements", None)
         kwargs.pop("update_resources", None)
         kwargs.pop("update_support", None)
@@ -84,7 +82,6 @@ class DummyPublishCommand(PublishCommand):
     def build_command(self, app, **kwargs):
         self.actions.append(("build", app.app_name, kwargs.copy()))
         # Remove arguments consumed by the underlying call to build_app()
-        kwargs.pop("test_mode", None)
         kwargs.pop("update", None)
         return full_options({"build_state": app.app_name}, kwargs)
 
diff --git a/tests/commands/run/conftest.py b/tests/commands/run/conftest.py
index f08fe84a..62d8c117 100644
--- a/tests/commands/run/conftest.py
+++ b/tests/commands/run/conftest.py
@@ -52,7 +52,7 @@ class DummyRunCommand(RunCommand):
         self.actions.append(("verify-app-tools", app.app_name))
 
     def run_app(self, app, **kwargs):
-        self.actions.append(("run", app.app_name, kwargs.copy()))
+        self.actions.append(("run", app.app_name, app.test_mode, kwargs.copy()))
         # Remove arguments consumed by the underlying call to run_app()
         kwargs.pop("update", None)
         kwargs.pop("update_requirements", None)
@@ -60,7 +60,6 @@ class DummyRunCommand(RunCommand):
         kwargs.pop("update_support", None)
         kwargs.pop("update_stub", None)
         kwargs.pop("no_update", None)
-        kwargs.pop("test_mode", None)
         kwargs.pop("passthrough", None)
         return full_options({"run_state": app.app_name}, kwargs)
 
@@ -68,23 +67,21 @@ class DummyRunCommand(RunCommand):
     # they were invoked, rather than instantiating a Create/Update/Build command.
     # This is for testing purposes.
     def create_command(self, app, **kwargs):
-        self.actions.append(("create", app.app_name, kwargs.copy()))
+        self.actions.append(("create", app.app_name, app.test_mode, kwargs.copy()))
         # Remove arguments consumed by the underlying call to create_app()
-        kwargs.pop("test_mode", None)
         return full_options({"create_state": app.app_name}, kwargs)
 
     def update_command(self, app, **kwargs):
-        self.actions.append(("update", app.app_name, kwargs.copy()))
+        self.actions.append(("update", app.app_name, app.test_mode, kwargs.copy()))
         # Remove arguments consumed by the underlying call to update_app()
         kwargs.pop("update_requirements", None)
         kwargs.pop("update_resources", None)
         kwargs.pop("update_support", None)
         kwargs.pop("update_stub", None)
-        kwargs.pop("test_mode", None)
         return full_options({"update_state": app.app_name}, kwargs)
 
     def build_command(self, app, **kwargs):
-        self.actions.append(("build", app.app_name, kwargs.copy()))
+        self.actions.append(("build", app.app_name, app.test_mode, kwargs.copy()))
         # Remove arguments consumed by the underlying call to build_app()
         kwargs.pop("update", None)
         kwargs.pop("update_requirements", None)
@@ -92,7 +89,6 @@ class DummyRunCommand(RunCommand):
         kwargs.pop("update_support", None)
         kwargs.pop("update_stub", None)
         kwargs.pop("no_update", None)
-        kwargs.pop("test_mode", None)
         return full_options({"build_state": app.app_name}, kwargs)
 
 
diff --git a/tests/commands/run/test__stream_app_logs.py b/tests/commands/run/test__stream_app_logs.py
index 74c1287b..e7471072 100644
--- a/tests/commands/run/test__stream_app_logs.py
+++ b/tests/commands/run/test__stream_app_logs.py
@@ -22,7 +22,6 @@ def test_run_app(run_command, first_app):
     run_command._stream_app_logs(
         first_app,
         popen=popen,
-        test_mode=False,
         clean_filter=clean_filter,
         clean_output=False,
     )
@@ -56,7 +55,6 @@ def test_run_app_custom_stop_func(run_command, first_app):
     run_command._stream_app_logs(
         first_app,
         popen=popen,
-        test_mode=False,
         clean_filter=clean_filter,
         clean_output=False,
         stop_func=stop_func,
@@ -81,6 +79,8 @@ def test_run_app_custom_stop_func(run_command, first_app):
 
 def test_test_mode_success(run_command, first_app):
     """An app can be streamed in test mode."""
+    first_app.test_mode = True
+
     popen = mock.MagicMock()
     popen.returncode = 0
     clean_filter = mock.MagicMock()
@@ -97,7 +97,6 @@ def test_test_mode_success(run_command, first_app):
     run_command._stream_app_logs(
         first_app,
         popen=popen,
-        test_mode=True,
         clean_filter=clean_filter,
         clean_output=False,
         stop_func=stop_func,
@@ -122,6 +121,8 @@ def test_test_mode_success(run_command, first_app):
 
 def test_test_mode_failure(run_command, first_app):
     """An app can be streamed in test mode, resulting in test failure."""
+    first_app.test_mode = True
+
     popen = mock.MagicMock()
     popen.returncode = 0
     clean_filter = mock.MagicMock()
@@ -139,7 +140,6 @@ def test_test_mode_failure(run_command, first_app):
         run_command._stream_app_logs(
             first_app,
             popen=popen,
-            test_mode=True,
             clean_filter=clean_filter,
             clean_output=False,
             stop_func=stop_func,
@@ -164,6 +164,8 @@ def test_test_mode_failure(run_command, first_app):
 
 def test_test_mode_no_result(run_command, first_app):
     """An app can be streamed in test mode, but with no test result being found."""
+    first_app.test_mode = True
+
     popen = mock.MagicMock()
     popen.returncode = 0
     clean_filter = mock.MagicMock()
@@ -184,7 +186,6 @@ def test_test_mode_no_result(run_command, first_app):
         run_command._stream_app_logs(
             first_app,
             popen=popen,
-            test_mode=True,
             clean_filter=clean_filter,
             clean_output=False,
             stop_func=stop_func,
@@ -209,6 +210,8 @@ def test_test_mode_no_result(run_command, first_app):
 
 def test_test_mode_custom_filters(run_command, first_app):
     """An app can define custom success/failure regexes."""
+    first_app.test_mode = True
+
     popen = mock.MagicMock()
     popen.returncode = 0
     clean_filter = mock.MagicMock()
@@ -227,7 +230,6 @@ def test_test_mode_custom_filters(run_command, first_app):
     run_command._stream_app_logs(
         first_app,
         popen=popen,
-        test_mode=True,
         clean_filter=clean_filter,
         clean_output=False,
         stop_func=stop_func,
@@ -266,7 +268,6 @@ def test_run_app_failure(run_command, first_app):
         run_command._stream_app_logs(
             first_app,
             popen=popen,
-            test_mode=False,
             clean_filter=clean_filter,
             clean_output=False,
             stop_func=stop_func,
@@ -301,7 +302,6 @@ def test_run_app_log_stream_stream_failure(run_command, first_app):
     run_command._stream_app_logs(
         first_app,
         popen=popen,
-        test_mode=False,
         clean_filter=clean_filter,
         clean_output=False,
         stop_func=stop_func,
@@ -343,7 +343,6 @@ def test_run_app_log_stream_success(run_command, first_app):
     run_command._stream_app_logs(
         first_app,
         popen=popen,
-        test_mode=False,
         clean_filter=clean_filter,
         clean_output=False,
         stop_func=stop_func,
@@ -389,7 +388,6 @@ def test_run_app_log_stream_failure(run_command, first_app):
         run_command._stream_app_logs(
             first_app,
             popen=popen,
-            test_mode=False,
             clean_filter=clean_filter,
             clean_output=False,
             stop_func=stop_func,
@@ -431,7 +429,6 @@ def test_run_app_log_stream_no_result(run_command, first_app):
     run_command._stream_app_logs(
         first_app,
         popen=popen,
-        test_mode=False,
         clean_filter=clean_filter,
         clean_output=False,
         stop_func=stop_func,
@@ -470,7 +467,6 @@ def test_run_app_ctrl_c(run_command, first_app):
     run_command._stream_app_logs(
         first_app,
         popen=popen,
-        test_mode=False,
         clean_filter=clean_filter,
         clean_output=False,
         stop_func=stop_func,
diff --git a/tests/commands/run/test_call.py b/tests/commands/run/test_call.py
index fce9130e..eb733d5b 100644
--- a/tests/commands/run/test_call.py
+++ b/tests/commands/run/test_call.py
@@ -29,7 +29,7 @@ def test_no_args_one_app(run_command, first_app):
         # App tools are verified
         ("verify-app-tools", "first"),
         # Run the first app
-        ("run", "first", {"test_mode": False, "passthrough": []}),
+        ("run", "first", False, {"passthrough": []}),
     ]
 
 
@@ -60,7 +60,7 @@ def test_no_args_one_app_with_passthrough(run_command, first_app):
         # App tools have been verified
         ("verify-app-tools", "first"),
         # Run the first app
-        ("run", "first", {"test_mode": False, "passthrough": ["foo", "--bar"]}),
+        ("run", "first", False, {"passthrough": ["foo", "--bar"]}),
     ]
 
 
@@ -109,7 +109,7 @@ def test_with_arg_one_app(run_command, first_app):
         # App tools are verified
         ("verify-app-tools", "first"),
         # Run the first app
-        ("run", "first", {"test_mode": False, "passthrough": []}),
+        ("run", "first", False, {"passthrough": []}),
     ]
 
 
@@ -140,7 +140,7 @@ def test_with_arg_two_apps(run_command, first_app, second_app):
         # App tools have been verified
         ("verify-app-tools", "second"),
         # Run the second app
-        ("run", "second", {"test_mode": False, "passthrough": []}),
+        ("run", "second", False, {"passthrough": []}),
     ]
 
 
@@ -190,8 +190,8 @@ def test_create_app_before_start(run_command, first_app_config):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": False,
@@ -208,7 +208,8 @@ def test_create_app_before_start(run_command, first_app_config):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -238,8 +239,8 @@ def test_build_app_before_start(run_command, first_app_unbuilt):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": False,
@@ -256,7 +257,8 @@ def test_build_app_before_start(run_command, first_app_unbuilt):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -286,8 +288,8 @@ def test_update_app(run_command, first_app):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": True,
                 "update_requirements": False,
                 "update_resources": False,
@@ -304,7 +306,8 @@ def test_update_app(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -334,8 +337,8 @@ def test_update_app_requirements(run_command, first_app):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": False,
                 "update_requirements": True,
                 "update_resources": False,
@@ -352,7 +355,8 @@ def test_update_app_requirements(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -382,8 +386,8 @@ def test_update_app_resources(run_command, first_app):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": True,
@@ -400,7 +404,8 @@ def test_update_app_resources(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -430,8 +435,8 @@ def test_update_app_support(run_command, first_app):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": False,
@@ -448,7 +453,8 @@ def test_update_app_support(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -478,8 +484,8 @@ def test_update_app_stub(run_command, first_app):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": False,
@@ -496,7 +502,8 @@ def test_update_app_stub(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -527,8 +534,8 @@ def test_update_unbuilt_app(run_command, first_app_unbuilt):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": True,
                 "update_requirements": False,
                 "update_resources": False,
@@ -545,7 +552,8 @@ def test_update_unbuilt_app(run_command, first_app_unbuilt):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -576,8 +584,8 @@ def test_update_non_existent(run_command, first_app_config):
         (
             "build",
             "first",
+            False,
             {
-                "test_mode": False,
                 "update": True,
                 "update_requirements": False,
                 "update_resources": False,
@@ -594,7 +602,8 @@ def test_update_non_existent(run_command, first_app_config):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": False, "passthrough": []},
+            False,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -624,8 +633,8 @@ def test_test_mode_existing_app(run_command, first_app):
         (
             "build",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": False,
@@ -642,7 +651,8 @@ def test_test_mode_existing_app(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": True, "passthrough": []},
+            True,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -672,8 +682,8 @@ def test_test_mode_existing_app_with_passthrough(run_command, first_app):
         (
             "build",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": False,
@@ -690,9 +700,9 @@ def test_test_mode_existing_app_with_passthrough(run_command, first_app):
         (
             "run",
             "first",
+            True,
             {
                 "build_state": "first",
-                "test_mode": True,
                 "passthrough": ["foo", "--bar"],
             },
         ),
@@ -729,7 +739,8 @@ def test_test_mode_existing_app_no_update(run_command, first_app):
         (
             "run",
             "first",
-            {"test_mode": True, "passthrough": []},
+            True,
+            {"passthrough": []},
         ),
     ]
 
@@ -759,8 +770,8 @@ def test_test_mode_existing_app_update_requirements(run_command, first_app):
         (
             "build",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update": False,
                 "update_requirements": True,
                 "update_resources": False,
@@ -777,7 +788,8 @@ def test_test_mode_existing_app_update_requirements(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": True, "passthrough": []},
+            True,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -807,8 +819,8 @@ def test_test_mode_existing_app_update_resources(run_command, first_app):
         (
             "build",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": True,
@@ -825,7 +837,8 @@ def test_test_mode_existing_app_update_resources(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": True, "passthrough": []},
+            True,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -855,8 +868,8 @@ def test_test_mode_update_existing_app(run_command, first_app):
         (
             "build",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update": True,
                 "update_requirements": False,
                 "update_resources": False,
@@ -873,7 +886,8 @@ def test_test_mode_update_existing_app(run_command, first_app):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": True, "passthrough": []},
+            True,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
 
@@ -903,8 +917,8 @@ def test_test_mode_non_existent(run_command, first_app_config):
         (
             "build",
             "first",
+            True,
             {
-                "test_mode": True,
                 "update": False,
                 "update_requirements": False,
                 "update_resources": False,
@@ -921,6 +935,7 @@ def test_test_mode_non_existent(run_command, first_app_config):
         (
             "run",
             "first",
-            {"build_state": "first", "test_mode": True, "passthrough": []},
+            True,
+            {"build_state": "first", "passthrough": []},
         ),
     ]
diff --git a/tests/commands/update/conftest.py b/tests/commands/update/conftest.py
index aa6c5b76..014d9e16 100644
--- a/tests/commands/update/conftest.py
+++ b/tests/commands/update/conftest.py
@@ -52,12 +52,12 @@ class DummyUpdateCommand(UpdateCommand):
 
     # Override all the body methods of a UpdateCommand
     # with versions that we can use to track actions performed.
-    def install_app_requirements(self, app, test_mode):
-        self.actions.append(("requirements", app.app_name, test_mode))
+    def install_app_requirements(self, app):
+        self.actions.append(("requirements", app.app_name, app.test_mode))
         create_file(self.bundle_path(app) / "requirements", "app requirements")
 
-    def install_app_code(self, app, test_mode):
-        self.actions.append(("code", app.app_name, test_mode))
+    def install_app_code(self, app):
+        self.actions.append(("code", app.app_name, app.test_mode))
         create_file(self.bundle_path(app) / "code.py", "print('app')")
 
     def install_app_resources(self, app):
diff --git a/tests/commands/update/test_update_app.py b/tests/commands/update/test_update_app.py
index 11113714..c5720a8b 100644
--- a/tests/commands/update/test_update_app.py
+++ b/tests/commands/update/test_update_app.py
@@ -6,7 +6,6 @@ def test_update_app(update_command, first_app, tmp_path):
         update_resources=False,
         update_support=False,
         update_stub=False,
-        test_mode=False,
     )
 
     # The right sequence of things will be done
@@ -39,7 +38,6 @@ def test_update_non_existing_app(update_command, tmp_path):
         update_resources=False,
         update_support=False,
         update_stub=False,
-        test_mode=False,
     )
 
     # No app creation actions will be performed
@@ -58,7 +56,6 @@ def test_update_app_with_requirements(update_command, first_app, tmp_path):
         update_resources=False,
         update_support=False,
         update_stub=False,
-        test_mode=False,
     )
 
     # The right sequence of things will be done
@@ -91,7 +88,6 @@ def test_update_app_with_resources(update_command, first_app, tmp_path):
         update_resources=True,
         update_support=False,
         update_stub=False,
-        test_mode=False,
     )
 
     # The right sequence of things will be done
@@ -124,7 +120,6 @@ def test_update_app_with_support_package(update_command, first_app, tmp_path):
         update_resources=False,
         update_support=True,
         update_stub=False,
-        test_mode=False,
     )
 
     # The right sequence of things will be done
@@ -163,7 +158,6 @@ def test_update_app_with_stub(update_command, first_app, tmp_path):
         update_resources=False,
         update_support=False,
         update_stub=True,
-        test_mode=False,
     )
 
     # The right sequence of things will be done
@@ -198,7 +192,6 @@ def test_update_app_stub_without_stub(update_command, first_app, tmp_path):
         update_resources=False,
         update_support=False,
         update_stub=True,
-        test_mode=False,
     )
 
     # The right sequence of things will be done
@@ -224,10 +217,11 @@ def test_update_app_stub_without_stub(update_command, first_app, tmp_path):
 
 def test_update_app_test_mode(update_command, first_app, tmp_path):
     """Update app in test mode."""
+    update_command.apps["first"].test_mode = True
+
     # Pass in the defaults for the update flags
     update_command.update_app(
         update_command.apps["first"],
-        test_mode=True,
         update_requirements=False,
         update_resources=False,
         update_support=False,
@@ -257,10 +251,11 @@ def test_update_app_test_mode(update_command, first_app, tmp_path):
 
 def test_update_app_test_mode_requirements(update_command, first_app, tmp_path):
     """Update app in test mode, but with requirements."""
+    update_command.apps["first"].test_mode = True
+
     # Pass in the defaults for the update flags
     update_command.update_app(
         update_command.apps["first"],
-        test_mode=True,
         update_requirements=True,
         update_resources=False,
         update_support=False,
@@ -291,10 +286,11 @@ def test_update_app_test_mode_requirements(update_command, first_app, tmp_path):
 
 def test_update_app_test_mode_resources(update_command, first_app, tmp_path):
     """Update app in test mode, but with resources."""
+    update_command.apps["first"].test_mode = True
+
     # Pass in the defaults for the update flags
     update_command.update_app(
         update_command.apps["first"],
-        test_mode=True,
         update_requirements=False,
         update_resources=True,
         update_support=False,
diff --git a/tests/config/test_AppConfig.py b/tests/config/test_AppConfig.py
index 6fa8b8e7..e77e1946 100644
--- a/tests/config/test_AppConfig.py
+++ b/tests/config/test_AppConfig.py
@@ -33,9 +33,11 @@ def test_minimal_AppConfig():
     assert config.icon is None
 
     # The PYTHONPATH is derived correctly
-    assert config.PYTHONPATH(False) == ["src", "somewhere/else", ""]
+    config.test_mode = False
+    assert config.PYTHONPATH() == ["src", "somewhere/else", ""]
     # The test mode PYTHONPATH is the same
-    assert config.PYTHONPATH(True) == ["src", "somewhere/else", ""]
+    config.test_mode = True
+    assert config.PYTHONPATH() == ["src", "somewhere/else", ""]
 
     # The object has a meaningful REPL
     assert repr(config) == "<org.beeware.myapp v1.2.3 AppConfig>"
diff --git a/tests/platforms/android/gradle/test_build.py b/tests/platforms/android/gradle/test_build.py
index ec5d7fdf..aeea1325 100644
--- a/tests/platforms/android/gradle/test_build.py
+++ b/tests/platforms/android/gradle/test_build.py
@@ -75,7 +75,7 @@ def test_build_app(
     # Create mock environment with `key`, which we expect to be preserved, and
     # `ANDROID_SDK_ROOT`, which we expect to be overwritten.
     build_command.tools.os.environ = {"ANDROID_SDK_ROOT": "somewhere", "key": "value"}
-    build_command.build_app(first_app_generated, test_mode=False)
+    build_command.build_app(first_app_generated)
     build_command.tools.android_sdk.verify_emulator.assert_called_once_with()
     build_command.tools.subprocess.run.assert_called_once_with(
         [
@@ -134,6 +134,8 @@ def test_build_app_test_mode(
     tmp_path,
 ):
     """The app can be built in test mode, invoking gradle and rewriting app metadata."""
+    first_app_generated.test_mode = True
+
     # Mock out `host_os` so we can validate which name is used for gradlew.
     build_command.tools.host_os = host_os
     # Enable verbose tool logging
@@ -142,7 +144,7 @@ def test_build_app_test_mode(
     # Create mock environment with `key`, which we expect to be preserved, and
     # `ANDROID_SDK_ROOT`, which we expect to be overwritten.
     build_command.tools.os.environ = {"ANDROID_SDK_ROOT": "somewhere", "key": "value"}
-    build_command.build_app(first_app_generated, test_mode=True)
+    build_command.build_app(first_app_generated)
     build_command.tools.android_sdk.verify_emulator.assert_called_once_with()
     build_command.tools.subprocess.run.assert_called_once_with(
         [
@@ -192,4 +194,4 @@ def test_print_gradle_errors(build_command, first_app_generated):
         cmd=["ignored"],
     )
     with pytest.raises(BriefcaseCommandError):
-        build_command.build_app(first_app_generated, test_mode=False)
+        build_command.build_app(first_app_generated)
diff --git a/tests/platforms/android/gradle/test_run.py b/tests/platforms/android/gradle/test_run.py
index 9b534630..2f6fb648 100644
--- a/tests/platforms/android/gradle/test_run.py
+++ b/tests/platforms/android/gradle/test_run.py
@@ -192,7 +192,6 @@ def test_run_existing_device(run_command, first_app_config):
     run_command.run_app(
         first_app_config,
         device_or_avd="exampleDevice",
-        test_mode=False,
         passthrough=[],
     )
 
@@ -227,7 +226,6 @@ def test_run_existing_device(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_filter=android_log_clean_filter,
         clean_output=False,
         stop_func=mock.ANY,
@@ -264,7 +262,6 @@ def test_run_with_passthrough(run_command, first_app_config):
     run_command.run_app(
         first_app_config,
         device_or_avd="exampleDevice",
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -299,7 +296,6 @@ def test_run_with_passthrough(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_filter=android_log_clean_filter,
         clean_output=False,
         stop_func=mock.ANY,
@@ -327,7 +323,6 @@ def test_run_slow_start(run_command, first_app_config, monkeypatch):
     run_command.run_app(
         first_app_config,
         device_or_avd="exampleDevice",
-        test_mode=False,
         passthrough=[],
     )
 
@@ -341,7 +336,6 @@ def test_run_slow_start(run_command, first_app_config, monkeypatch):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_filter=android_log_clean_filter,
         clean_output=False,
         stop_func=mock.ANY,
@@ -381,7 +375,6 @@ def test_run_crash_at_start(run_command, first_app_config, monkeypatch):
         run_command.run_app(
             first_app_config,
             device_or_avd="exampleDevice",
-            test_mode=False,
             passthrough=[],
         )
 
@@ -419,7 +412,7 @@ def test_run_created_emulator(run_command, first_app_config):
     run_command.tools.mock_adb.logcat.return_value = log_popen
 
     # Invoke run_app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # A new emulator was created
     run_command.tools.android_sdk.create_emulator.assert_called_once_with()
@@ -455,7 +448,6 @@ def test_run_created_emulator(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_filter=android_log_clean_filter,
         clean_output=False,
         stop_func=mock.ANY,
@@ -482,7 +474,7 @@ def test_run_idle_device(run_command, first_app_config):
     run_command.tools.mock_adb.logcat.return_value = log_popen
 
     # Invoke run_app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # No attempt was made to create a new emulator
     run_command.tools.android_sdk.create_emulator.assert_not_called()
@@ -517,7 +509,6 @@ def test_run_idle_device(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_filter=android_log_clean_filter,
         clean_output=False,
         stop_func=mock.ANY,
@@ -562,6 +553,8 @@ def test_log_file_extra(run_command, monkeypatch):
 
 def test_run_test_mode(run_command, first_app_config):
     """An app can be run in test mode."""
+    first_app_config.test_mode = True
+
     # Set up device selection to return a running physical device.
     run_command.tools.android_sdk.select_target_device = mock.MagicMock(
         return_value=("exampleDevice", "ExampleDevice", None)
@@ -586,7 +579,6 @@ def test_run_test_mode(run_command, first_app_config):
     run_command.run_app(
         first_app_config,
         device_or_avd="exampleDevice",
-        test_mode=True,
         passthrough=[],
         shutdown_on_exit=True,
     )
@@ -622,7 +614,6 @@ def test_run_test_mode(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_filter=android_log_clean_filter,
         clean_output=False,
         stop_func=mock.ANY,
@@ -635,6 +626,8 @@ def test_run_test_mode(run_command, first_app_config):
 
 def test_run_test_mode_with_passthrough(run_command, first_app_config):
     """An app can be run in test mode with passthrough args."""
+    first_app_config.test_mode = True
+
     # Set up device selection to return a running physical device.
     run_command.tools.android_sdk.select_target_device = mock.MagicMock(
         return_value=("exampleDevice", "ExampleDevice", None)
@@ -659,7 +652,6 @@ def test_run_test_mode_with_passthrough(run_command, first_app_config):
     run_command.run_app(
         first_app_config,
         device_or_avd="exampleDevice",
-        test_mode=True,
         passthrough=["foo", "--bar"],
         shutdown_on_exit=True,
     )
@@ -695,7 +687,6 @@ def test_run_test_mode_with_passthrough(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_filter=android_log_clean_filter,
         clean_output=False,
         stop_func=mock.ANY,
@@ -708,6 +699,8 @@ def test_run_test_mode_with_passthrough(run_command, first_app_config):
 
 def test_run_test_mode_created_emulator(run_command, first_app_config):
     """The user can choose to run in test mode on a newly created emulator."""
+    first_app_config.test_mode = True
+
     # Set up device selection to return a completely new emulator
     run_command.tools.android_sdk.select_target_device = mock.MagicMock(
         return_value=(None, None, None)
@@ -727,7 +720,6 @@ def test_run_test_mode_created_emulator(run_command, first_app_config):
     # Invoke run_app
     run_command.run_app(
         first_app_config,
-        test_mode=True,
         passthrough=[],
         extra_emulator_args=["-no-window", "-no-audio"],
         shutdown_on_exit=True,
@@ -768,7 +760,6 @@ def test_run_test_mode_created_emulator(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_filter=android_log_clean_filter,
         clean_output=False,
         stop_func=mock.ANY,
diff --git a/tests/platforms/iOS/xcode/test_build.py b/tests/platforms/iOS/xcode/test_build.py
index eb287926..98047b42 100644
--- a/tests/platforms/iOS/xcode/test_build.py
+++ b/tests/platforms/iOS/xcode/test_build.py
@@ -65,12 +65,14 @@ def test_build_app(build_command, first_app_generated, tool_debug_mode, tmp_path
 
 def test_build_app_test_mode(build_command, first_app_generated, tmp_path):
     """An iOS App can be built in test mode."""
+    first_app_generated.test_mode = True
+
     build_command.tools.subprocess = mock.MagicMock(spec_set=Subprocess)
 
     # Mock the host's CPU architecture to ensure it's reflected in the Xcode call
     build_command.tools.host_arch = "weird"
 
-    build_command.build_app(first_app_generated, test_mode=True)
+    build_command.build_app(first_app_generated)
 
     build_command.tools.subprocess.run.assert_called_with(
         [
diff --git a/tests/platforms/iOS/xcode/test_create.py b/tests/platforms/iOS/xcode/test_create.py
index 1b176083..54bdc56f 100644
--- a/tests/platforms/iOS/xcode/test_create.py
+++ b/tests/platforms/iOS/xcode/test_create.py
@@ -78,7 +78,7 @@ def test_extra_pip_args(
         spec_set=Subprocess
     )
 
-    create_command.install_app_requirements(first_app_generated, test_mode=False)
+    create_command.install_app_requirements(first_app_generated)
 
     bundle_path = tmp_path / "base_path/build/first-app/ios/xcode"
     assert create_command.tools[first_app_generated].app_context.run.mock_calls == [
@@ -163,7 +163,7 @@ def test_min_os_version(create_command, first_app_generated, tmp_path):
         spec_set=Subprocess
     )
 
-    create_command.install_app_requirements(first_app_generated, test_mode=False)
+    create_command.install_app_requirements(first_app_generated)
 
     bundle_path = tmp_path / "base_path/build/first-app/ios/xcode"
     assert create_command.tools[first_app_generated].app_context.run.mock_calls == [
@@ -254,7 +254,7 @@ def test_incompatible_min_os_version(create_command, first_app_generated, tmp_pa
             r"but the support package only supports 12.0"
         ),
     ):
-        create_command.install_app_requirements(first_app_generated, test_mode=False)
+        create_command.install_app_requirements(first_app_generated)
 
     create_command.tools[first_app_generated].app_context.run.assert_not_called()
 
diff --git a/tests/platforms/iOS/xcode/test_run.py b/tests/platforms/iOS/xcode/test_run.py
index 1fd150be..755613e7 100644
--- a/tests/platforms/iOS/xcode/test_run.py
+++ b/tests/platforms/iOS/xcode/test_run.py
@@ -72,7 +72,7 @@ def test_run_multiple_devices_input_disabled(run_command, first_app_config):
         BriefcaseCommandError,
         match=r"Input has been disabled; can't select a device to target.",
     ):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
 
 @pytest.mark.usefixtures("sleep_zero")
@@ -106,7 +106,7 @@ def test_run_app_simulator_booted(run_command, first_app_config, tmp_path):
     ]
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # The correct sequence of commands was issued.
     run_command.tools.subprocess.run.assert_has_calls(
@@ -191,7 +191,6 @@ def test_run_app_simulator_booted(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -238,7 +237,7 @@ def test_run_app_simulator_booted_underscore(
     ]
 
     # Run the app
-    run_command.run_app(underscore_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(underscore_app_config, passthrough=[])
 
     # slept 4 times for uninstall/install and 1 time for log stream start
     assert time.sleep.call_count == 4 + 1
@@ -323,7 +322,6 @@ def test_run_app_simulator_booted_underscore(
     run_command._stream_app_logs.assert_called_with(
         underscore_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -364,7 +362,6 @@ def test_run_app_with_passthrough(run_command, first_app_config, tmp_path):
     # Run the app with passthrough args.
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -453,7 +450,6 @@ def test_run_app_with_passthrough(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -498,7 +494,7 @@ def test_run_app_simulator_shut_down(
     ]
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # slept 4 times for uninstall/install and 1 time for log stream start
     assert time.sleep.call_count == 4 + 1
@@ -589,7 +585,6 @@ def test_run_app_simulator_shut_down(
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -640,7 +635,7 @@ def test_run_app_simulator_shutting_down(run_command, first_app_config, tmp_path
     ]
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # We should have slept 4 times for shutting down and 4 time for uninstall/install
     assert time.sleep.call_count == 4 + 4
@@ -731,7 +726,6 @@ def test_run_app_simulator_shutting_down(run_command, first_app_config, tmp_path
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -757,7 +751,7 @@ def test_run_app_simulator_boot_failure(run_command, first_app_config):
 
     # Run the app
     with pytest.raises(BriefcaseCommandError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # No sleeps
     assert time.sleep.call_count == 0
@@ -801,7 +795,7 @@ def test_run_app_simulator_open_failure(run_command, first_app_config):
 
     # Run the app
     with pytest.raises(BriefcaseCommandError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # No sleeps
     assert time.sleep.call_count == 0
@@ -852,7 +846,7 @@ def test_run_app_simulator_uninstall_failure(run_command, first_app_config):
 
     # Run the app
     with pytest.raises(BriefcaseCommandError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Sleep twice for uninstall failure
     assert time.sleep.call_count == 2
@@ -924,7 +918,7 @@ def test_run_app_simulator_install_failure(run_command, first_app_config, tmp_pa
 
     # Run the app
     with pytest.raises(BriefcaseCommandError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Sleep twice for uninstall and twice for install failure
     assert time.sleep.call_count == 4
@@ -1017,7 +1011,7 @@ def test_run_app_simulator_launch_failure(run_command, first_app_config, tmp_pat
 
     # Run the app
     with pytest.raises(BriefcaseCommandError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Sleep four times for uninstall/install and once for log stream start
     assert time.sleep.call_count == 4 + 1
@@ -1138,7 +1132,7 @@ def test_run_app_simulator_no_pid(run_command, first_app_config, tmp_path):
 
     # Run the app
     with pytest.raises(BriefcaseCommandError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Sleep four times for uninstall/install and once for log stream start
     assert time.sleep.call_count == 4 + 1
@@ -1261,7 +1255,7 @@ def test_run_app_simulator_non_integer_pid(run_command, first_app_config, tmp_pa
 
     # Run the app
     with pytest.raises(BriefcaseCommandError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Sleep four times for uninstall/install and once for log stream start
     assert time.sleep.call_count == 4 + 1
@@ -1355,6 +1349,8 @@ def test_run_app_simulator_non_integer_pid(run_command, first_app_config, tmp_pa
 @pytest.mark.usefixtures("sleep_zero")
 def test_run_app_test_mode(run_command, first_app_config, tmp_path):
     """An iOS App can be started in test mode."""
+    first_app_config.test_mode = True
+
     # A valid target device will be selected.
     run_command.select_target_device = mock.MagicMock(
         return_value=("2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D", "13.2", "iPhone 11")
@@ -1383,7 +1379,7 @@ def test_run_app_test_mode(run_command, first_app_config, tmp_path):
     ]
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=True, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Sleep four times for uninstall/install and once for log stream start
     assert time.sleep.call_count == 4 + 1
@@ -1454,7 +1450,6 @@ def test_run_app_test_mode(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=True,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -1465,6 +1460,8 @@ def test_run_app_test_mode(run_command, first_app_config, tmp_path):
 @pytest.mark.usefixtures("sleep_zero")
 def test_run_app_test_mode_with_passthrough(run_command, first_app_config, tmp_path):
     """An iOS App can be started in test mode with passthrough args."""
+    first_app_config.test_mode = True
+
     # A valid target device will be selected.
     run_command.select_target_device = mock.MagicMock(
         return_value=("2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D", "13.2", "iPhone 11")
@@ -1495,7 +1492,6 @@ def test_run_app_test_mode_with_passthrough(run_command, first_app_config, tmp_p
     # Run the app with args.
     run_command.run_app(
         first_app_config,
-        test_mode=True,
         passthrough=["foo", "--bar"],
     )
 
@@ -1570,7 +1566,6 @@ def test_run_app_test_mode_with_passthrough(run_command, first_app_config, tmp_p
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=True,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
diff --git a/tests/platforms/iOS/xcode/test_update.py b/tests/platforms/iOS/xcode/test_update.py
index 2b917298..06abbb4c 100644
--- a/tests/platforms/iOS/xcode/test_update.py
+++ b/tests/platforms/iOS/xcode/test_update.py
@@ -59,7 +59,7 @@ def test_extra_pip_args(
         spec_set=Subprocess
     )
 
-    update_command.install_app_requirements(first_app_generated, test_mode=False)
+    update_command.install_app_requirements(first_app_generated)
 
     bundle_path = tmp_path / "base_path/build/first-app/ios/xcode"
     assert update_command.tools[first_app_generated].app_context.run.mock_calls == [
diff --git a/tests/platforms/linux/appimage/test_run.py b/tests/platforms/linux/appimage/test_run.py
index e4e1eb45..e208686d 100644
--- a/tests/platforms/linux/appimage/test_run.py
+++ b/tests/platforms/linux/appimage/test_run.py
@@ -49,7 +49,7 @@ def test_run_gui_app(run_command, first_app_config, tmp_path):
     run_command.tools.subprocess.Popen.return_value = log_popen
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess.Popen.assert_called_with(
@@ -67,7 +67,6 @@ def test_run_gui_app(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -83,7 +82,6 @@ def test_run_gui_app_with_passthrough(run_command, first_app_config, tmp_path):
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -106,7 +104,6 @@ def test_run_gui_app_with_passthrough(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -116,7 +113,7 @@ def test_run_gui_app_failed(run_command, first_app_config, tmp_path):
     run_command.tools.subprocess.Popen.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # The run command was still invoked
     run_command.tools.subprocess.Popen.assert_called_with(
@@ -139,7 +136,7 @@ def test_run_console_app(run_command, first_app_config, tmp_path):
     first_app_config.console_app = True
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess.run.assert_called_with(
@@ -165,7 +162,6 @@ def test_run_console_app_with_passthrough(run_command, first_app_config, tmp_pat
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -194,7 +190,7 @@ def test_run_console_app_failed(run_command, first_app_config, tmp_path):
     run_command.tools.subprocess.run.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # The run command was still invoked
     run_command.tools.subprocess.run.assert_called_with(
@@ -216,13 +212,14 @@ def test_run_app_test_mode(run_command, first_app_config, is_console_app, tmp_pa
     """A linux App can be started in test mode."""
     # Test mode apps are always streamed
     first_app_config.console_app = is_console_app
+    first_app_config.test_mode = True
 
     # Set up the log streamer to return a known stream
     log_popen = mock.MagicMock()
     run_command.tools.subprocess.Popen.return_value = log_popen
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=True, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess.Popen.assert_called_with(
@@ -241,7 +238,6 @@ def test_run_app_test_mode(run_command, first_app_config, is_console_app, tmp_pa
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
 
@@ -256,6 +252,7 @@ def test_run_app_test_mode_with_args(
     """A linux App can be started in test mode with args."""
     # Test mode apps are always streamed
     first_app_config.console_app = is_console_app
+    first_app_config.test_mode = True
 
     # Set up the log streamer to return a known stream
     log_popen = mock.MagicMock()
@@ -264,7 +261,6 @@ def test_run_app_test_mode_with_args(
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=True,
         passthrough=["foo", "--bar"],
     )
 
@@ -287,6 +283,5 @@ def test_run_app_test_mode_with_args(
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
diff --git a/tests/platforms/linux/flatpak/test_run.py b/tests/platforms/linux/flatpak/test_run.py
index e91002da..bfb5a0e1 100644
--- a/tests/platforms/linux/flatpak/test_run.py
+++ b/tests/platforms/linux/flatpak/test_run.py
@@ -30,7 +30,7 @@ def test_run_gui_app(run_command, first_app_config):
     run_command.tools.flatpak.run.return_value = log_popen
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # App is executed
     run_command.tools.flatpak.run.assert_called_once_with(
@@ -43,7 +43,6 @@ def test_run_gui_app(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -59,7 +58,6 @@ def test_run_gui_app_with_passthrough(run_command, first_app_config):
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -75,7 +73,6 @@ def test_run_gui_app_with_passthrough(run_command, first_app_config):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -85,7 +82,7 @@ def test_run_gui_app_failed(run_command, first_app_config, tmp_path):
     run_command.tools.flatpak.run.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # The run command was still invoked
     run_command.tools.flatpak.run.assert_called_once_with(
@@ -103,7 +100,7 @@ def test_run_console_app(run_command, first_app_config):
     first_app_config.console_app = True
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # App is executed
     run_command.tools.flatpak.run.assert_called_once_with(
@@ -124,7 +121,6 @@ def test_run_console_app_with_passthrough(run_command, first_app_config):
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -147,7 +143,7 @@ def test_run_console_app_failed(run_command, first_app_config, tmp_path):
     run_command.tools.flatpak.run.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # The run command was still invoked
     run_command.tools.flatpak.run.assert_called_once_with(
@@ -165,13 +161,14 @@ def test_run_test_mode(run_command, first_app_config, is_console_app):
     """A flatpak can be executed in test mode."""
     # Test mode apps are always streamed
     first_app_config.console_app = is_console_app
+    first_app_config.test_mode = True
 
     # Set up the log streamer to return a known stream and a good return code
     log_popen = mock.MagicMock()
     run_command.tools.flatpak.run.return_value = log_popen
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=True, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # App is executed
     run_command.tools.flatpak.run.assert_called_once_with(
@@ -185,7 +182,6 @@ def test_run_test_mode(run_command, first_app_config, is_console_app):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
 
@@ -195,6 +191,7 @@ def test_run_test_mode_with_args(run_command, first_app_config, is_console_app):
     """A flatpak can be executed in test mode with args."""
     # Test mode apps are always streamed
     first_app_config.console_app = is_console_app
+    first_app_config.test_mode = True
 
     # Set up the log streamer to return a known stream and a good return code
     log_popen = mock.MagicMock()
@@ -203,7 +200,6 @@ def test_run_test_mode_with_args(run_command, first_app_config, is_console_app):
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=True,
         passthrough=["foo", "--bar"],
     )
 
@@ -219,6 +215,5 @@ def test_run_test_mode_with_args(run_command, first_app_config, is_console_app):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
diff --git a/tests/platforms/linux/system/test_run.py b/tests/platforms/linux/system/test_run.py
index 289d384a..a8f6f87a 100644
--- a/tests/platforms/linux/system/test_run.py
+++ b/tests/platforms/linux/system/test_run.py
@@ -152,7 +152,6 @@ def test_supported_host_os(run_command, first_app, sub_kw, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -227,7 +226,6 @@ def test_supported_host_os_docker(
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -245,7 +243,7 @@ def test_run_gui_app(run_command, first_app, sub_kw, tmp_path):
     )
 
     # Run the app
-    run_command.run_app(first_app, test_mode=False, passthrough=[])
+    run_command.run_app(first_app, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess._subprocess.Popen.assert_called_with(
@@ -266,7 +264,6 @@ def test_run_gui_app(run_command, first_app, sub_kw, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -285,7 +282,7 @@ def test_run_gui_app_passthrough(run_command, first_app, sub_kw, tmp_path):
     )
 
     # Run the app
-    run_command.run_app(first_app, test_mode=False, passthrough=["foo", "--bar"])
+    run_command.run_app(first_app, passthrough=["foo", "--bar"])
 
     # The process was started
     run_command.tools.subprocess._subprocess.Popen.assert_called_with(
@@ -313,7 +310,6 @@ def test_run_gui_app_passthrough(run_command, first_app, sub_kw, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -327,7 +323,7 @@ def test_run_gui_app_failed(run_command, first_app, sub_kw, tmp_path):
     run_command.tools.subprocess._subprocess.Popen.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app, test_mode=False, passthrough=[])
+        run_command.run_app(first_app, passthrough=[])
 
     # The run command was still invoked
     run_command.tools.subprocess._subprocess.Popen.assert_called_with(
@@ -356,7 +352,7 @@ def test_run_console_app(run_command, first_app, tmp_path):
     run_command.verify_app_tools(app=first_app)
 
     # Run the app
-    run_command.run_app(first_app, test_mode=False, passthrough=[])
+    run_command.run_app(first_app, passthrough=[])
 
     # The process was started
     assert run_command.tools.subprocess.run.mock_calls == [
@@ -385,7 +381,7 @@ def test_run_console_app_passthrough(run_command, first_app, tmp_path):
     run_command.verify_app_tools(app=first_app)
 
     # Run the app
-    run_command.run_app(first_app, test_mode=False, passthrough=["foo", "--bar"])
+    run_command.run_app(first_app, passthrough=["foo", "--bar"])
 
     # The process was started
     assert run_command.tools.subprocess.run.mock_calls == [
@@ -417,7 +413,7 @@ def test_run_console_app_failed(run_command, first_app, sub_kw, tmp_path):
     run_command.tools.subprocess.run.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app, test_mode=False, passthrough=[])
+        run_command.run_app(first_app, passthrough=[])
 
     # The run command was still invoked
     assert run_command.tools.subprocess.run.mock_calls == [
@@ -459,7 +455,7 @@ def test_run_app_docker(run_command, first_app, sub_kw, tmp_path, monkeypatch):
     )
 
     # Run the app
-    run_command.run_app(first_app, test_mode=False, passthrough=[])
+    run_command.run_app(first_app, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess._subprocess.Popen.assert_called_with(
@@ -495,7 +491,6 @@ def test_run_app_docker(run_command, first_app, sub_kw, tmp_path, monkeypatch):
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -520,7 +515,7 @@ def test_run_app_failed_docker(run_command, first_app, sub_kw, tmp_path, monkeyp
     run_command.tools.subprocess._subprocess.Popen.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app, test_mode=False, passthrough=[])
+        run_command.run_app(first_app, passthrough=[])
 
     # The run command was still invoked
     run_command.tools.subprocess._subprocess.Popen.assert_called_with(
@@ -568,6 +563,7 @@ def test_run_app_test_mode(
     """A linux App can be started in test mode."""
     # Test mode apps are always streamed
     first_app.console_app = is_console_app
+    first_app.test_mode = True
 
     # Set up tool cache
     run_command.verify_app_tools(app=first_app)
@@ -580,7 +576,7 @@ def test_run_app_test_mode(
     monkeypatch.setattr(run_command.tools.os, "environ", {"ENVVAR": "Value"})
 
     # Run the app
-    run_command.run_app(first_app, test_mode=True, passthrough=[])
+    run_command.run_app(first_app, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess._subprocess.Popen.assert_called_with(
@@ -602,7 +598,6 @@ def test_run_app_test_mode(
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
 
@@ -620,6 +615,7 @@ def test_run_app_test_mode_docker(
     """A linux App can be started in Docker in test mode."""
     # Test mode apps are always streamed
     first_app.console_app = is_console_app
+    first_app.test_mode = True
 
     # Trigger to run in Docker
     run_command.target_image = first_app.target_image = "best/distro"
@@ -639,7 +635,7 @@ def test_run_app_test_mode_docker(
     )
 
     # Run the app
-    run_command.run_app(first_app, test_mode=True, passthrough=[])
+    run_command.run_app(first_app, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess._subprocess.Popen.assert_called_with(
@@ -677,7 +673,6 @@ def test_run_app_test_mode_docker(
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
 
@@ -694,6 +689,7 @@ def test_run_app_test_mode_with_args(
     """A linux App can be started in test mode with args."""
     # Test mode apps are always streamed
     first_app.console_app = is_console_app
+    first_app.test_mode = True
 
     # Set up tool cache
     run_command.verify_app_tools(app=first_app)
@@ -708,7 +704,6 @@ def test_run_app_test_mode_with_args(
     # Run the app with args
     run_command.run_app(
         first_app,
-        test_mode=True,
         passthrough=["foo", "--bar"],
     )
 
@@ -734,7 +729,6 @@ def test_run_app_test_mode_with_args(
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
 
@@ -752,6 +746,7 @@ def test_run_app_test_mode_with_args_docker(
     """A linux App can be started in Docker in test mode with args."""
     # Test mode apps are always streamed
     first_app.console_app = is_console_app
+    first_app.test_mode = True
 
     # Trigger to run in Docker
     run_command.target_image = first_app.target_image = "best/distro"
@@ -773,7 +768,6 @@ def test_run_app_test_mode_with_args_docker(
     # Run the app with args
     run_command.run_app(
         first_app,
-        test_mode=True,
         passthrough=["foo", "--bar"],
     )
 
@@ -815,6 +809,5 @@ def test_run_app_test_mode_with_args_docker(
     run_command._stream_app_logs.assert_called_once_with(
         first_app,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
diff --git a/tests/platforms/linux/test_LocalRequirementsMixin.py b/tests/platforms/linux/test_LocalRequirementsMixin.py
index ee6633c7..c6596b15 100644
--- a/tests/platforms/linux/test_LocalRequirementsMixin.py
+++ b/tests/platforms/linux/test_LocalRequirementsMixin.py
@@ -152,7 +152,7 @@ def test_install_app_requirements_in_docker(create_command, first_app_config, tm
     """If Docker is in use, a docker context is used to invoke pip."""
 
     # Install requirements
-    create_command.install_app_requirements(first_app_config, test_mode=False)
+    create_command.install_app_requirements(first_app_config)
 
     # pip was invoked inside docker.
     create_command.tools.subprocess.run.assert_called_once_with(
@@ -204,7 +204,7 @@ def test_install_app_requirements_no_docker(
     no_docker_create_command.verify_app_tools(first_app_config)
 
     # Install requirements
-    no_docker_create_command.install_app_requirements(first_app_config, test_mode=False)
+    no_docker_create_command.install_app_requirements(first_app_config)
 
     # Docker is not verified.
     assert not hasattr(no_docker_create_command.tools, "docker")
@@ -282,7 +282,7 @@ def test_install_app_requirements_with_locals(
     create_command.tools.subprocess.check_output.side_effect = build_sdist
 
     # Install requirements
-    create_command.install_app_requirements(first_app_config, test_mode=False)
+    create_command.install_app_requirements(first_app_config)
 
     # An sdist was built for the local package
     create_command.tools.subprocess.check_output.assert_called_once_with(
@@ -383,7 +383,7 @@ def test_install_app_requirements_with_bad_local(
         BriefcaseCommandError,
         match=r"Unable to build sdist for .*/local/first",
     ):
-        create_command.install_app_requirements(first_app_config, test_mode=False)
+        create_command.install_app_requirements(first_app_config)
 
     # An attempt to build the sdist was made
     create_command.tools.subprocess.check_output.assert_called_once_with(
@@ -428,7 +428,7 @@ def test_install_app_requirements_with_missing_local_build(
         BriefcaseCommandError,
         match=r"Unable to find local requirement .*/local/first",
     ):
-        create_command.install_app_requirements(first_app_config, test_mode=False)
+        create_command.install_app_requirements(first_app_config)
 
     # No attempt to build the sdist was made
     create_command.tools.subprocess.check_output.assert_not_called()
@@ -460,7 +460,7 @@ def test_install_app_requirements_with_bad_local_file(
         BriefcaseCommandError,
         match=r"Unable to find local requirement .*/local/missing-2.3.4.tar.gz",
     ):
-        create_command.install_app_requirements(first_app_config, test_mode=False)
+        create_command.install_app_requirements(first_app_config)
 
     # An attempt was made to copy the package
     create_command.tools.shutil.copy.assert_called_once_with(
diff --git a/tests/platforms/macOS/app/test_create.py b/tests/platforms/macOS/app/test_create.py
index 2d602d50..3cf597e9 100644
--- a/tests/platforms/macOS/app/test_create.py
+++ b/tests/platforms/macOS/app/test_create.py
@@ -351,7 +351,7 @@ def test_install_app_packages(
     # Mock the merge command so we can confirm it was invoked.
     create_command.merge_app_packages = mock.Mock()
 
-    create_command.install_app_requirements(first_app_templated, test_mode=False)
+    create_command.install_app_requirements(first_app_templated)
 
     # We looked for binary packages in the host app_packages
     create_command.find_binary_packages.assert_called_once_with(
@@ -463,7 +463,7 @@ def test_min_os_version(create_command, first_app_templated, tmp_path):
     # Mock the merge command so we can confirm it was invoked.
     create_command.merge_app_packages = mock.Mock()
 
-    create_command.install_app_requirements(first_app_templated, test_mode=False)
+    create_command.install_app_requirements(first_app_templated)
 
     # We looked for binary packages in the host app_packages
     create_command.find_binary_packages.assert_called_once_with(
@@ -564,7 +564,7 @@ def test_invalid_min_os_version(create_command, first_app_templated):
             r"but the support package only supports 10.12"
         ),
     ):
-        create_command.install_app_requirements(first_app_templated, test_mode=False)
+        create_command.install_app_requirements(first_app_templated)
 
     # No request was made to install requirements
     create_command.tools[first_app_templated].app_context.run.assert_not_called()
@@ -602,7 +602,7 @@ def test_install_app_packages_no_binary(
     # Mock the merge command so we can confirm it was invoked.
     create_command.merge_app_packages = mock.Mock()
 
-    create_command.install_app_requirements(first_app_templated, test_mode=False)
+    create_command.install_app_requirements(first_app_templated)
 
     # We looked for binary packages in the host app_packages
     create_command.find_binary_packages.assert_called_once_with(
@@ -702,7 +702,7 @@ def test_install_app_packages_failure(create_command, first_app_templated, tmp_p
             r"to the PyPI server.\n"
         ),
     ):
-        create_command.install_app_requirements(first_app_templated, test_mode=False)
+        create_command.install_app_requirements(first_app_templated)
 
     # We looked for binary packages in the host app_packages
     create_command.find_binary_packages.assert_called_once_with(
@@ -806,7 +806,7 @@ def test_install_app_packages_non_universal(
     # Mock the merge command so we can confirm it wasn't invoked.
     create_command.merge_app_packages = mock.Mock()
 
-    create_command.install_app_requirements(first_app_templated, test_mode=False)
+    create_command.install_app_requirements(first_app_templated)
 
     # We didn't search for binary packages
     create_command.find_binary_packages.assert_not_called()
diff --git a/tests/platforms/macOS/app/test_run.py b/tests/platforms/macOS/app/test_run.py
index dcb69dac..7ae0413e 100644
--- a/tests/platforms/macOS/app/test_run.py
+++ b/tests/platforms/macOS/app/test_run.py
@@ -44,7 +44,7 @@ def test_run_gui_app(run_command, first_app_config, sleep_zero, tmp_path, monkey
         "briefcase.platforms.macOS.get_process_id_by_command", lambda *a, **kw: 100
     )
 
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -74,7 +74,6 @@ def test_run_gui_app(run_command, first_app_config, sleep_zero, tmp_path, monkey
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -107,7 +106,6 @@ def test_run_gui_app_with_passthrough(
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -140,7 +138,6 @@ def test_run_gui_app_with_passthrough(
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -160,7 +157,7 @@ def test_run_gui_app_failed(run_command, first_app_config, sleep_zero, tmp_path)
     )
 
     with pytest.raises(BriefcaseCommandError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -207,7 +204,7 @@ def test_run_gui_app_find_pid_failed(
     )
 
     with pytest.raises(BriefcaseCommandError) as exc_info:
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -249,6 +246,8 @@ def test_run_gui_app_test_mode(
     monkeypatch,
 ):
     """A macOS GUI app can be started in test mode."""
+    first_app_config.test_mode = True
+
     # Mock a popen object that represents the log stream
     log_stream_process = mock.MagicMock(spec_set=subprocess.Popen)
     run_command.tools.subprocess.Popen.return_value = log_stream_process
@@ -258,7 +257,7 @@ def test_run_gui_app_test_mode(
         "briefcase.platforms.macOS.get_process_id_by_command", lambda *a, **kw: 100
     )
 
-    run_command.run_app(first_app_config, test_mode=True, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -289,7 +288,6 @@ def test_run_gui_app_test_mode(
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=True,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -305,7 +303,7 @@ def test_run_console_app(run_command, first_app_config, tmp_path):
     # Set the app to be a console app
     first_app_config.console_app = True
 
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -334,7 +332,6 @@ def test_run_console_app_with_passthrough(
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -355,12 +352,13 @@ def test_run_console_app_with_passthrough(
 def test_run_console_app_test_mode(run_command, first_app_config, sleep_zero, tmp_path):
     """A macOS console app can be started in test mode."""
     first_app_config.console_app = True
+    first_app_config.test_mode = True
 
     # Mock a popen object that represents the app
     app_process = mock.MagicMock(spec_set=subprocess.Popen)
     run_command.tools.subprocess.Popen.return_value = app_process
 
-    run_command.run_app(first_app_config, test_mode=True, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -377,7 +375,6 @@ def test_run_console_app_test_mode(run_command, first_app_config, sleep_zero, tm
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=app_process,
-        test_mode=True,
     )
 
 
@@ -392,12 +389,13 @@ def test_run_console_app_test_mode_with_passthrough(
     run_command.console.verbosity = LogLevel.DEBUG
 
     first_app_config.console_app = True
+    first_app_config.test_mode = True
 
     # Mock a popen object that represents the app
     app_process = mock.MagicMock(spec_set=subprocess.Popen)
     run_command.tools.subprocess.Popen.return_value = app_process
 
-    run_command.run_app(first_app_config, test_mode=True, passthrough=["foo", "--bar"])
+    run_command.run_app(first_app_config, passthrough=["foo", "--bar"])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -414,7 +412,6 @@ def test_run_console_app_test_mode_with_passthrough(
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=app_process,
-        test_mode=True,
     )
 
 
@@ -431,7 +428,7 @@ def test_run_console_app_failed(run_command, first_app_config, sleep_zero, tmp_p
 
     # Although the command raises an error, this could be because the script itself
     # raised an error.
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
diff --git a/tests/platforms/macOS/xcode/test_build.py b/tests/platforms/macOS/xcode/test_build.py
index e0c97bd9..769ffc05 100644
--- a/tests/platforms/macOS/xcode/test_build.py
+++ b/tests/platforms/macOS/xcode/test_build.py
@@ -26,7 +26,7 @@ def test_build_app(build_command, first_app_generated, tool_debug_mode, tmp_path
         build_command.tools.console.verbosity = LogLevel.DEEP_DEBUG
 
     build_command.tools.subprocess = MagicMock(spec_set=Subprocess)
-    build_command.build_app(first_app_generated, test_mode=False)
+    build_command.build_app(first_app_generated)
 
     build_command.tools.subprocess.run.assert_called_with(
         [
@@ -59,7 +59,7 @@ def test_build_app_failed(build_command, first_app_generated, tmp_path):
     )
 
     with pytest.raises(BriefcaseCommandError):
-        build_command.build_app(first_app_generated, test_mode=False)
+        build_command.build_app(first_app_generated)
 
     build_command.tools.subprocess.run.assert_called_with(
         [
diff --git a/tests/platforms/macOS/xcode/test_run.py b/tests/platforms/macOS/xcode/test_run.py
index cba4ccbd..fc5e10b9 100644
--- a/tests/platforms/macOS/xcode/test_run.py
+++ b/tests/platforms/macOS/xcode/test_run.py
@@ -46,7 +46,7 @@ def test_run_app(run_command, first_app_config, sleep_zero, tmp_path, monkeypatc
         "briefcase.platforms.macOS.get_process_id_by_command", lambda *a, **kw: 100
     )
 
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -76,7 +76,6 @@ def test_run_app(run_command, first_app_config, sleep_zero, tmp_path, monkeypatc
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -107,7 +106,6 @@ def test_run_app_with_passthrough(
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -139,7 +137,6 @@ def test_run_app_with_passthrough(
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=False,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -158,6 +155,8 @@ def test_run_app_test_mode(
     monkeypatch,
 ):
     """A macOS Xcode app can be started in test mode."""
+    first_app_config.test_mode = True
+
     # Mock a popen object that represents the log stream
     log_stream_process = mock.MagicMock(spec_set=subprocess.Popen)
     run_command.tools.subprocess.Popen.return_value = log_stream_process
@@ -167,7 +166,7 @@ def test_run_app_test_mode(
         "briefcase.platforms.macOS.get_process_id_by_command", lambda *a, **kw: 100
     )
 
-    run_command.run_app(first_app_config, test_mode=True, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Calls were made to start the app and to start a log stream.
     bin_path = run_command.binary_path(first_app_config)
@@ -198,7 +197,6 @@ def test_run_app_test_mode(
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=True,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
@@ -217,6 +215,8 @@ def test_run_app_test_mode_with_passthrough(
     monkeypatch,
 ):
     """A macOS Xcode app can be started in test mode with args."""
+    first_app_config.test_mode = True
+
     # Mock a popen object that represents the log stream
     log_stream_process = mock.MagicMock(spec_set=subprocess.Popen)
     run_command.tools.subprocess.Popen.return_value = log_stream_process
@@ -229,7 +229,6 @@ def test_run_app_test_mode_with_passthrough(
     # Run app in test mode with args
     run_command.run_app(
         first_app_config,
-        test_mode=True,
         passthrough=["foo", "--bar"],
     )
 
@@ -262,7 +261,6 @@ def test_run_app_test_mode_with_passthrough(
     run_command._stream_app_logs.assert_called_with(
         first_app_config,
         popen=log_stream_process,
-        test_mode=True,
         clean_filter=macOS_log_clean_filter,
         clean_output=True,
         stop_func=mock.ANY,
diff --git a/tests/platforms/web/static/test_run.py b/tests/platforms/web/static/test_run.py
index 3b897c00..aec75138 100644
--- a/tests/platforms/web/static/test_run.py
+++ b/tests/platforms/web/static/test_run.py
@@ -107,7 +107,6 @@ def test_run(monkeypatch, run_command, first_app_built):
     # Run the app
     run_command.run_app(
         first_app_built,
-        test_mode=False,
         passthrough=[],
         host="localhost",
         port=8080,
@@ -172,7 +171,6 @@ def test_run_with_fallback_port(
     # Run the app
     run_command.run_app(
         first_app_built,
-        test_mode=False,
         passthrough=[],
         host="localhost",
         port=8080,
@@ -226,7 +224,6 @@ def test_run_with_args(monkeypatch, run_command, first_app_built):
     # Run the app
     run_command.run_app(
         first_app_built,
-        test_mode=False,
         passthrough=["foo", "--bar"],
         host="localhost",
         port=8080,
@@ -321,7 +318,6 @@ def test_cleanup_server_error(
     with pytest.raises(BriefcaseCommandError, match=message):
         run_command.run_app(
             first_app_built,
-            test_mode=False,
             passthrough=[],
             host=host,
             port=port,
@@ -370,7 +366,6 @@ def test_cleanup_runtime_server_error(monkeypatch, run_command, first_app_built)
     with pytest.raises(ValueError):
         run_command.run_app(
             first_app_built,
-            test_mode=False,
             passthrough=[],
             host="localhost",
             port=8080,
@@ -420,7 +415,6 @@ def test_run_without_browser(monkeypatch, run_command, first_app_built):
     # Run the app
     run_command.run_app(
         first_app_built,
-        test_mode=False,
         passthrough=[],
         host="localhost",
         port=8080,
@@ -471,7 +465,6 @@ def test_run_autoselect_port(monkeypatch, run_command, first_app_built):
     # Run the app on an autoselected port
     run_command.run_app(
         first_app_built,
-        test_mode=False,
         passthrough=[],
         host="localhost",
         port=0,
@@ -561,6 +554,8 @@ def test_log_requests_to_logger(monkeypatch):
 
 def test_test_mode(run_command, first_app_built):
     """Test mode raises an error (at least for now)."""
+    first_app_built.test_mode = True
+
     # Run the app
     with pytest.raises(
         BriefcaseCommandError,
@@ -568,7 +563,6 @@ def test_test_mode(run_command, first_app_built):
     ):
         run_command.run_app(
             first_app_built,
-            test_mode=True,
             passthrough=[],
             host="localhost",
             port=8080,
diff --git a/tests/platforms/windows/app/test_run.py b/tests/platforms/windows/app/test_run.py
index 8fea5137..94ef24e9 100644
--- a/tests/platforms/windows/app/test_run.py
+++ b/tests/platforms/windows/app/test_run.py
@@ -30,7 +30,7 @@ def test_run_gui_app(run_command, first_app_config, tmp_path):
     run_command.tools.subprocess.Popen.return_value = log_popen
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess.Popen.assert_called_with(
@@ -46,7 +46,6 @@ def test_run_gui_app(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -62,7 +61,6 @@ def test_run_gui_app_with_passthrough(run_command, first_app_config, tmp_path):
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -85,7 +83,6 @@ def test_run_gui_app_with_passthrough(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -96,7 +93,7 @@ def test_run_gui_app_failed(run_command, first_app_config, tmp_path):
     run_command.tools.subprocess.Popen.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Popen was still invoked, though
     run_command.tools.subprocess.Popen.assert_called_with(
@@ -121,7 +118,7 @@ def test_run_console_app(run_command, first_app_config, tmp_path):
     run_command.tools.subprocess.Popen.return_value = log_popen
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # The process was started
     run_command.tools.subprocess.run.assert_called_with(
@@ -145,7 +142,6 @@ def test_run_console_app_with_passthrough(run_command, first_app_config, tmp_pat
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -174,7 +170,7 @@ def test_run_console_app_failed(run_command, first_app_config, tmp_path):
     run_command.tools.subprocess.run.side_effect = OSError
 
     with pytest.raises(OSError):
-        run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+        run_command.run_app(first_app_config, passthrough=[])
 
     # Popen was still invoked, though
     run_command.tools.subprocess.run.assert_called_with(
@@ -194,13 +190,14 @@ def test_run_app_test_mode(run_command, first_app_config, is_console_app, tmp_pa
     """A Windows app can be started in test mode."""
     # Test mode apps are always streamed
     first_app_config.console_app = is_console_app
+    first_app_config.test_mode = True
 
     # Set up the log streamer to return a known stream
     log_popen = mock.MagicMock()
     run_command.tools.subprocess.Popen.return_value = log_popen
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=True, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # The process was started
     exe_name = "first-app" if is_console_app else "First App"
@@ -218,7 +215,6 @@ def test_run_app_test_mode(run_command, first_app_config, is_console_app, tmp_pa
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
 
@@ -233,6 +229,7 @@ def test_run_app_test_mode_with_passthrough(
     """A Windows app can be started in test mode with args."""
     # Test mode apps are always streamed
     first_app_config.console_app = is_console_app
+    first_app_config.test_mode = True
 
     # Set up the log streamer to return a known stream
     log_popen = mock.MagicMock()
@@ -241,7 +238,6 @@ def test_run_app_test_mode_with_passthrough(
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=True,
         passthrough=["foo", "--bar"],
     )
 
@@ -265,6 +261,5 @@ def test_run_app_test_mode_with_passthrough(
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
diff --git a/tests/platforms/windows/visualstudio/test_run.py b/tests/platforms/windows/visualstudio/test_run.py
index 9e3950e9..2700e919 100644
--- a/tests/platforms/windows/visualstudio/test_run.py
+++ b/tests/platforms/windows/visualstudio/test_run.py
@@ -34,7 +34,7 @@ def test_run_app(run_command, first_app_config, tmp_path):
     run_command.tools.subprocess.Popen.return_value = log_popen
 
     # Run the app
-    run_command.run_app(first_app_config, test_mode=False, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Popen was called
     run_command.tools.subprocess.Popen.assert_called_with(
@@ -53,7 +53,6 @@ def test_run_app(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
@@ -68,7 +67,6 @@ def test_run_app_with_args(run_command, first_app_config, tmp_path):
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=False,
         passthrough=["foo", "--bar"],
     )
 
@@ -91,20 +89,20 @@ def test_run_app_with_args(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=False,
         clean_output=False,
     )
 
 
 def test_run_app_test_mode(run_command, first_app_config, tmp_path):
     """A windows Visual Studio project app can be started in test mode."""
+    first_app_config.test_mode = True
 
     # Set up the log streamer to return a known stream with a good returncode
     log_popen = mock.MagicMock()
     run_command.tools.subprocess.Popen.return_value = log_popen
 
     # Run the app in test mode
-    run_command.run_app(first_app_config, test_mode=True, passthrough=[])
+    run_command.run_app(first_app_config, passthrough=[])
 
     # Popen was called
     run_command.tools.subprocess.Popen.assert_called_with(
@@ -124,13 +122,13 @@ def test_run_app_test_mode(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )
 
 
 def test_run_app_test_mode_with_args(run_command, first_app_config, tmp_path):
     """A windows Visual Studio project app can be started in test mode with args."""
+    first_app_config.test_mode = True
 
     # Set up the log streamer to return a known stream with a good returncode
     log_popen = mock.MagicMock()
@@ -139,7 +137,6 @@ def test_run_app_test_mode_with_args(run_command, first_app_config, tmp_path):
     # Run the app with args
     run_command.run_app(
         first_app_config,
-        test_mode=True,
         passthrough=["foo", "--bar"],
     )
 
@@ -163,6 +160,5 @@ def test_run_app_test_mode_with_args(run_command, first_app_config, tmp_path):
     run_command._stream_app_logs.assert_called_once_with(
         first_app_config,
         popen=log_popen,
-        test_mode=True,
         clean_output=False,
     )