Add dut in regression test report

PiperOrigin-RevId: 602556760
diff --git a/ui_automator/test_reporter.py b/ui_automator/test_reporter.py
index 194b26c..eae676d 100644
--- a/ui_automator/test_reporter.py
+++ b/ui_automator/test_reporter.py
@@ -25,7 +25,7 @@
 import logging
 import os
 import time
-from typing import TypedDict, NotRequired
+from typing import NotRequired, TypedDict
 import unittest
 
 from absl.testing import xml_reporter
@@ -53,6 +53,7 @@
 
 class ReportInfo(TypedDict):
   """Type guard for summary of running unit tests."""
+
   gha_version: NotRequired[str | None]
   gms_core_version: NotRequired[str | None]
   hub_version: NotRequired[str | None]
@@ -315,8 +316,21 @@
     ])
 
     f = open(summary_file_path, 'w', encoding='utf-8')
+    data_indents = (
+        # Set indents to the maximum length of values in report info if any.
+        # Add 2 extra spaces to separate data and next header.
+        # If the value is less than default column indents, use the default.
+        max(max(map(len, report_info.values())) + 2, _SUMMARY_COL_INDENTS)
+        if report_info
+        else _SUMMARY_COL_INDENTS
+    )
     for row in rows:
-      f.writelines(element.ljust(_SUMMARY_COL_INDENTS) for element in row)
+      for i, element in enumerate(row):
+        if i % 2 == 0:
+          # Writes header.
+          f.write(element.ljust(_SUMMARY_COL_INDENTS))
+        else:
+          f.write(element.ljust(data_indents))
       f.write('\n')
 
     f.writelines(['\n', '\n'])
diff --git a/ui_automator/test_reporter_test.py b/ui_automator/test_reporter_test.py
index 3cc6678..fdfc214 100644
--- a/ui_automator/test_reporter_test.py
+++ b/ui_automator/test_reporter_test.py
@@ -210,7 +210,7 @@
             duration=test_reporter.duration_formatter(run_time),
             total_runs=3,
             total_successful_runs=2,
-            **fake_report_info,
+            report_info=fake_report_info,
         )
     )
     res_of_test_commission = ['FAIL', 'PASS', 'PASS']
diff --git a/ui_automator/ui_automator.py b/ui_automator/ui_automator.py
index 96801a9..8a3e7c2 100644
--- a/ui_automator/ui_automator.py
+++ b/ui_automator/ui_automator.py
@@ -81,6 +81,14 @@
     default=None,
     help=_REGRESSION_TESTS_FLAG_USAGE_GUIDE,
 )
+_DUT = flags.DEFINE_list(
+    name='dut',
+    default=None,
+    help=(
+        'Use --dut {model},{type},{protocol} to include this field in'
+        ' test report. e.g. `--dut X123123123,LIGHT,Matter`.'
+    ),
+)
 
 
 class RegTestSuiteType(enum.Enum):
@@ -398,6 +406,9 @@
           self._connected_device, 'com.google.android.gms'
       )
 
+    if _DUT.value:
+      temp_info['dut'] = f"<{', '.join(_DUT.value)}>"
+
     report_info: test_reporter.ReportInfo = {**temp_info}
 
     return report_info
diff --git a/ui_automator/ui_automator_test.py b/ui_automator/ui_automator_test.py
index 7706d74..02fb559 100644
--- a/ui_automator/ui_automator_test.py
+++ b/ui_automator/ui_automator_test.py
@@ -1013,7 +1013,7 @@
 
     report_info = self.ui_automator.get_report_info()
 
-    self.assertIsNotNone(report_info)
+    self.assertEqual(len(report_info), 2)
     self.assertEqual(report_info.get('gha_version'), '0.0.0')
     self.assertEqual(report_info.get('gms_core_version'), '0.0.1')
 
@@ -1060,6 +1060,21 @@
     )
     mock_open.assert_called_once()
 
+  @flagsaver.flagsaver((ui_automator._DUT, ['model', 'type', 'protocol']))
+  def test_get_report_info_includes_dut_value_from_flag_input(self):
+    report_info = self.ui_automator.get_report_info()
+
+    self.assertEqual(len(report_info), 1)
+    self.assertEqual(report_info.get('dut'), '<model, type, protocol>')
+
+  @flagsaver.flagsaver((ui_automator._DUT, None))
+  def test_get_report_info_returns_empty_dict_without_dut_flag_input(
+      self,
+  ):
+    report_info = self.ui_automator.get_report_info()
+
+    self.assertDictEqual(report_info, {})
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/ui_automator/unit_test_utils.py b/ui_automator/unit_test_utils.py
index 7bde017..4989738 100644
--- a/ui_automator/unit_test_utils.py
+++ b/ui_automator/unit_test_utils.py
@@ -57,24 +57,36 @@
   ).isoformat()
 
 
-def make_summary(**kwargs) -> str:
+def make_summary(
+    test_date: str | None = None,
+    duration: str | None = None,
+    total_runs: int = 0,
+    total_successful_runs: int = 0,
+    report_info: dict[str, str] | None = None,
+) -> str:
   """Makes test summary produced by `test_reporter` for unit tests.
 
   Args:
-    **kwargs: Fields written in test summary.
+    test_date: Test time for running regression tests.
+    duration: The duration for running regression tests.
+    total_runs: The number of runs for regression tests.
+    total_successful_runs: The number of successful runs for regression tests.
+    report_info: Versions and device info displayed in generated txt report.
+      Versions includes GHA, GMSCore, hub and device firmware. Device info is in
+      <model, type, protocol> format, e.g. <X123123, LIGHT, Matter>.
 
   Returns:
     Test summary.
   """
-  dut = kwargs.get('dut', 'n/a')
-  gha = kwargs.get('gha_version', 'n/a')
-  test_date = kwargs.get('test_date', 'n/a')
-  gms_core = kwargs.get('gms_core_version', 'n/a')
-  duration = kwargs.get('duration', 0)
-  hub = kwargs.get('hub_version', 'n/a')
-  total_runs = kwargs.get('total_runs', 0)
-  device = kwargs.get('device_firmware', 'n/a')
-  total_successful_runs = kwargs.get('total_successful_runs', 0)
+  dut = report_info.get('dut', 'n/a') if report_info else 'n/a'
+  gha = report_info.get('gha_version', 'n/a') if report_info else 'n/a'
+  test_date = test_date or 'n/a'
+  duration = duration or 'n/a'
+  gms_core = (
+      report_info.get('gms_core_version', 'n/a') if report_info else 'n/a'
+  )
+  hub = report_info.get('hub_version', 'n/a') if report_info else 'n/a'
+  device = report_info.get('device_firmware', 'n/a') if report_info else 'n/a'
   success_rate = round(100.0 * float(total_successful_runs) / float(total_runs))
   rows: list[list[str]] = []
   rows.append(['Summary', '', 'Version Info', ''])
@@ -87,8 +99,17 @@
       f'{success_rate}%({total_successful_runs}/{total_runs})',
   ])
   summary = []
+  data_indents = (
+      max(max(map(len, report_info.values())) + 2, 25) if report_info else 25
+  )
   for row in rows:
-    summary.append(''.join(element.ljust(25) for element in row))
+    for i, element in enumerate(row):
+      if i % 2 == 0:
+        # Writes header.
+        summary.append(element.ljust(25))
+      else:
+        # Writes data, add 2 extra spaces to separate data and next header.
+        summary.append(element.ljust(data_indents))
     summary.append('\n')
 
   return ''.join(summary)