diff --git a/docs/manpage.rst b/docs/manpage.rst
index 90543a9f..8ee59874 100644
--- a/docs/manpage.rst
+++ b/docs/manpage.rst
@@ -418,6 +418,23 @@ Options controlling ReFrame execution
 
    .. versionadded:: 3.11.0
 
+
+.. option:: --exec-order=ORDER
+
+   Impose an execution order for the independent tests.
+   The ``ORDER`` argument can take one of the following values:
+
+   - ``name``: Order tests by their display name.
+   - ``rname``: Order tests by their display name in reverse order.
+   - ``uid``: Order tests by their unique name.
+   - ``ruid``: Order tests by their unique name in reverse order.
+   - ``random``: Randomize the order of execution.
+
+   If this option is not specified the order of execution of independent tests is implementation defined.
+   This option can be combined with any of the listing options (:option:`-l` or :option:`-L`) to list the tests in the order.
+
+   .. versionadded:: 4.0.0
+
 .. option:: --exec-policy=POLICY
 
    The execution policy to be used for running tests.
diff --git a/reframe/frontend/cli.py b/reframe/frontend/cli.py
index 83bba0fb..0412fd61 100644
--- a/reframe/frontend/cli.py
+++ b/reframe/frontend/cli.py
@@ -7,6 +7,7 @@ import inspect
 import itertools
 import json
 import os
+import random
 import shlex
 import socket
 import sys
@@ -25,6 +26,7 @@ import reframe.frontend.ci as ci
 import reframe.frontend.dependencies as dependencies
 import reframe.frontend.filters as filters
 import reframe.frontend.runreport as runreport
+import reframe.utility as util
 import reframe.utility.jsonext as jsonext
 import reframe.utility.osext as osext
 import reframe.utility.typecheck as typ
@@ -386,6 +388,11 @@ def main():
         help=('Distribute the selected single-node jobs on every node that'
               'is in STATE (default: "idle"')
     )
+    run_options.add_argument(
+        '--exec-order', metavar='ORDER', action='store',
+        choices=['name', 'random', 'rname', 'ruid', 'uid'],
+        help='Impose an execution order for independent tests'
+    )
     run_options.add_argument(
         '--exec-policy', metavar='POLICY', action='store',
         choices=['async', 'serial'], default='async',
@@ -1054,8 +1061,8 @@ def main():
                     "a non-negative integer"
                 ) from None
 
-            testcases = repeat_tests(testcases, num_repeats)
-            testcases_all = testcases
+            testcases_all = repeat_tests(testcases, num_repeats)
+            testcases = testcases_all
 
         if options.distribute:
             node_map = getallnodes(options.distribute, parsed_job_options)
@@ -1070,15 +1077,31 @@ def main():
                 x for x in parsed_job_options
                 if (not x.startswith('-w') and not x.startswith('--nodelist'))
             ]
-            testcases = distribute_tests(testcases, node_map)
-            testcases_all = testcases
+            testcases_all = distribute_tests(testcases, node_map)
+            testcases = testcases_all
+
+        @logging.time_function
+        def _sort_testcases(testcases):
+            if options.exec_order in ('name', 'rname'):
+                testcases.sort(key=lambda c: c.check.display_name,
+                               reverse=(options.exec_order == 'rname'))
+            elif options.exec_order in ('uid', 'ruid'):
+                testcases.sort(key=lambda c: c.check.unique_name,
+                               reverse=(options.exec_order == 'ruid'))
+            elif options.exec_order == 'random':
+                random.shuffle(testcases)
+
+        _sort_testcases(testcases)
+        if testcases_all is not testcases:
+            _sort_testcases(testcases_all)
 
         # Prepare for running
         printer.debug('Building and validating the full test DAG')
         testgraph, skipped_cases = dependencies.build_deps(testcases_all)
         if skipped_cases:
             # Some cases were skipped, so adjust testcases
-            testcases = list(set(testcases) - set(skipped_cases))
+            testcases = list(util.OrderedSet(testcases) -
+                             util.OrderedSet(skipped_cases))
             printer.verbose(
                 f'Filtering test case(s) due to unresolved dependencies: '
                 f'{len(testcases)} remaining'

diff --git a/unittests/test_cli.py b/unittests/test_cli.py
index 0136b66f..12ce3ad0 100644
--- a/unittests/test_cli.py
+++ b/unittests/test_cli.py
@@ -834,7 +834,7 @@ def test_repeat_invalid_option(run_reframe):
 
 def test_repeat_negative(run_reframe):
     returncode, stdout, stderr = run_reframe(
-        more_options=['--repeat', 'foo'],
+        more_options=['--repeat', '-1'],
         checkpath=['unittests/resources/checks/hellocheck.py']
     )
     errmsg = "argument to '--repeat' option must be a non-negative integer"
@@ -844,6 +844,44 @@ def test_repeat_negative(run_reframe):
     assert returncode == 1
 
 
+@pytest.fixture(params=['name', 'rname', 'uid', 'ruid', 'random'])
+def exec_order(request):
+    return request.param
+
+
+def test_exec_order(run_reframe, exec_order):
+    import reframe.utility.sanity as sn
+
+    returncode, stdout, stderr = run_reframe(
+        more_options=['--repeat', '11', '-n', 'HelloTest',
+                      f'--exec-order={exec_order}'],
+        checkpath=['unittests/resources/checks/hellocheck.py'],
+        action='list_detailed',
+    )
+    assert 'Traceback' not in stdout
+    assert 'Traceback' not in stderr
+    assert 'Found 11 check(s)' in stdout
+    assert returncode == 0
+
+    # Verify the order
+    if exec_order == 'name':
+        repeat_no = sn.extractsingle_s(r'- HelloTest.*repeat_no=(\d+)',
+                                       stdout, 1, int, 2).evaluate()
+        assert repeat_no == 10
+    elif exec_order == 'rname':
+        repeat_no = sn.extractsingle_s(r'- HelloTest.*repeat_no=(\d+)',
+                                       stdout, 1, int, -3).evaluate()
+        assert repeat_no == 10
+    elif exec_order == 'uid':
+        repeat_no = sn.extractsingle_s(r'- HelloTest.*repeat_no=(\d+)',
+                                       stdout, 1, int, -1).evaluate()
+        assert repeat_no == 10
+    elif exec_order == 'ruid':
+        repeat_no = sn.extractsingle_s(r'- HelloTest.*repeat_no=(\d+)',
+                                       stdout, 1, int, 0).evaluate()
+        assert repeat_no == 10
+
+
 def test_detect_host_topology(run_reframe):
     from reframe.utility.cpuinfo import cpuinfo
 

RFE: randomize test execution order
I don't think this is possible right now. It would be useful to be able to add a CLI argument to randomize the execution order of all the tests. 

Ideally it would randomize the tests across all files passed to Reframe, e.g. with `-c test1.py -c test2.py -c test3.py` it would randomize and interleave tests between all 3 files, not just within each file. To replay particular sequence of execution, perhaps the argument could accept an optional seed for the RNG.

The rationale is that some test A might change the state of the system like making the system very hot and throttling clocks, that could cause a failure in a later test C only if you run A -> C -> B instead of always executing A -> B -> C, for example if test B is long but does not stress the system (giving the system the time to cool down).
Similarly, if the same Reframe test starts in parallel on 10 machines, they might all reach a network test at the same time, creating heavy congestion (which is useful in itself, but might not be the target of the test).

If possible, it would also be great if the performance report printed would always stay in the same order to make it easier to compare runs despite the randomized execution order.

unittests/test_cli.py::test_exec_order[name] unittests/test_cli.py::test_exec_order[rname] unittests/test_cli.py::test_exec_order[uid] unittests/test_cli.py::test_exec_order[ruid] unittests/test_cli.py::test_exec_order[random]
unittests/test_cli.py::test_check_success unittests/test_cli.py::test_check_restore_session_failed unittests/test_cli.py::test_check_restore_session_succeeded_test unittests/test_cli.py::test_check_restore_session_check_search_path unittests/test_cli.py::test_check_success_force_local unittests/test_cli.py::test_report_file_with_sessionid unittests/test_cli.py::test_report_ends_with_newline unittests/test_cli.py::test_check_failure unittests/test_cli.py::test_check_setup_failure unittests/test_cli.py::test_check_kbd_interrupt unittests/test_cli.py::test_check_sanity_failure unittests/test_cli.py::test_dont_restage unittests/test_cli.py::test_checkpath_symlink unittests/test_cli.py::test_performance_check_failure unittests/test_cli.py::test_perflogdir_from_env unittests/test_cli.py::test_performance_report unittests/test_cli.py::test_skip_system_check_option unittests/test_cli.py::test_skip_prgenv_check_option unittests/test_cli.py::test_sanity_of_checks unittests/test_cli.py::test_unknown_system unittests/test_cli.py::test_sanity_of_optconfig unittests/test_cli.py::test_checkpath_recursion unittests/test_cli.py::test_same_output_stage_dir unittests/test_cli.py::test_execution_modes unittests/test_cli.py::test_timestamp_option unittests/test_cli.py::test_timestamp_option_default unittests/test_cli.py::test_list_empty_prgenvs_check_and_options unittests/test_cli.py::test_list_check_with_empty_prgenvs unittests/test_cli.py::test_list_empty_prgenvs_in_check_and_options unittests/test_cli.py::test_list_with_details unittests/test_cli.py::test_list_concretized unittests/test_cli.py::test_list_tags unittests/test_cli.py::test_filtering_multiple_criteria unittests/test_cli.py::test_show_config_all unittests/test_cli.py::test_show_config_param unittests/test_cli.py::test_show_config_unknown_param unittests/test_cli.py::test_show_config_null_param unittests/test_cli.py::test_verbosity unittests/test_cli.py::test_verbosity_with_check unittests/test_cli.py::test_quiesce_with_check unittests/test_cli.py::test_failure_stats unittests/test_cli.py::test_maxfail_option unittests/test_cli.py::test_maxfail_invalid_option unittests/test_cli.py::test_maxfail_negative unittests/test_cli.py::test_repeat_option unittests/test_cli.py::test_repeat_invalid_option unittests/test_cli.py::test_repeat_negative unittests/test_cli.py::test_detect_host_topology unittests/test_cli.py::test_detect_host_topology_file unittests/test_cli.py::test_external_vars unittests/test_cli.py::test_external_vars_invalid_expr unittests/test_cli.py::test_fixture_registry_env_sys unittests/test_cli.py::test_fixture_resolution unittests/test_cli.py::test_dynamic_tests unittests/test_cli.py::test_dynamic_tests_filtering