Fix GHA and GMSCore version not shown in test report

PiperOrigin-RevId: 604087771
diff --git a/ui_automator/test_reporter.py b/ui_automator/test_reporter.py
index eae676d..d5d0852 100644
--- a/ui_automator/test_reporter.py
+++ b/ui_automator/test_reporter.py
@@ -37,7 +37,7 @@
     'Removing from GHA': 'test_decommission',
 })
 _NA = 'n/a'
-_SUMMARY_COL_INDENTS = 25
+_MINIMUM_SUMMARY_COL_INDENTS = 25
 _TEST_CASE_TITLE_INDENTS = 25
 _TEST_RESULT_INDENTS = 8
 
@@ -54,11 +54,11 @@
 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]
-  device_firmware: NotRequired[str | None]
-  dut: NotRequired[str | None]
+  gha_version: NotRequired[str]
+  gms_core_version: NotRequired[str]
+  hub_version: NotRequired[str]
+  device_firmware: NotRequired[str]
+  dut: NotRequired[str]
 
 
 # pylint: disable=protected-access
@@ -283,7 +283,12 @@
         100.0 * float(total_successful_runs) / float(total_runs)
     )
     report_info = report_info or {}
-    # TODO(b/317837867): Replace all placeholders with real values.
+    data_indents = _MINIMUM_SUMMARY_COL_INDENTS
+    for value in report_info.values():
+      # Set indents to the maximum length of values in report info.
+      # Add 2 extra spaces to separate data and next header.
+      # If the value is less than minimum column indents, use the minimum.
+      data_indents = max(data_indents, len(value) + 2)
     rows: list[list[str]] = []
     rows.append(['Summary', '', 'Version Info', ''])
     rows.append([
@@ -316,19 +321,11 @@
     ])
 
     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:
       for i, element in enumerate(row):
         if i % 2 == 0:
           # Writes header.
-          f.write(element.ljust(_SUMMARY_COL_INDENTS))
+          f.write(element.ljust(_MINIMUM_SUMMARY_COL_INDENTS))
         else:
           f.write(element.ljust(data_indents))
       f.write('\n')
diff --git a/ui_automator/ui_automator.py b/ui_automator/ui_automator.py
index edb17ca..380ab31 100644
--- a/ui_automator/ui_automator.py
+++ b/ui_automator/ui_automator.py
@@ -407,6 +407,7 @@
       self._is_reg_test_finished = True
       executor.shutdown(wait=False)
 
+  @get_android_device_ready
   def get_report_info(self) -> test_reporter.ReportInfo:
     """Gets report info for regression tests.
 
@@ -417,12 +418,16 @@
     """
     temp_info = collections.defaultdict()
     if self._connected_device:
-      temp_info['gha_version'] = ad.get_apk_version(
+      gha_version = ad.get_apk_version(
           self._connected_device, 'com.google.android.apps.chromecast.app'
       )
-      temp_info['gms_core_version'] = ad.get_apk_version(
+      if gha_version:
+        temp_info['gha_version'] = gha_version
+      gms_core_version = ad.get_apk_version(
           self._connected_device, 'com.google.android.gms'
       )
+      if gms_core_version:
+        temp_info['gms_core_version'] = gms_core_version
 
     if _DUT.value:
       temp_info['dut'] = f"<{', '.join(_DUT.value)}>"
diff --git a/ui_automator/ui_automator_test.py b/ui_automator/ui_automator_test.py
index d8813ba..2b2a191 100644
--- a/ui_automator/ui_automator_test.py
+++ b/ui_automator/ui_automator_test.py
@@ -1000,29 +1000,35 @@
     mock_exit.assert_called_once()
 
   @mock.patch.object(android_device, 'get_all_instances', autospec=True)
+  @mock.patch.object(ui_automator.UIAutomator, 'load_snippet', autospec=True)
   def test_get_report_info_includes_apk_versions_with_device_connected(
       self,
+      mock_load_snippet,
       mock_get_all_instances,
   ):
+    mock_get_all_instances.return_value = [self.mock_android_device]
     self.mock_android_device.adb.shell.side_effect = [
         b'versionName=0.0.0\n',
         b'versionName=0.0.1\n',
     ]
-    mock_get_all_instances.return_value = [self.mock_android_device]
-    self.ui_automator.load_device()
 
     report_info = self.ui_automator.get_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')
+    mock_load_snippet.assert_called_once()
+    self.assertDictEqual(
+        report_info, {'gha_version': '0.0.0', 'gms_core_version': '0.0.1'}
+    )
 
-  def test_get_report_info_returns_empty_dict_when_no_device_connected(
-      self,
+  @mock.patch.object(android_device, 'get_all_instances', autospec=True)
+  def test_get_report_info_raises_an_error_when_no_device_connected(
+      self, mock_get_all_instances
   ):
-    report_info = self.ui_automator.get_report_info()
+    mock_get_all_instances.return_value = []
 
-    self.assertDictEqual(report_info, {})
+    with self.assertRaisesRegex(
+        errors.AndroidDeviceNotReadyError, 'get_report_info failed.'
+    ):
+      self.ui_automator.get_report_info()
 
   @mock.patch.object(time, 'sleep', autospec=True)
   @mock.patch('builtins.open', autospec=True)
@@ -1064,48 +1070,95 @@
     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,
+  @mock.patch.object(ui_automator.UIAutomator, 'load_device', autospec=True)
+  @mock.patch.object(ui_automator.UIAutomator, 'load_snippet', autospec=True)
+  def test_get_report_info_includes_dut_value_from_flag_input(
+      self, mock_load_snippet, mock_load_device
   ):
     report_info = self.ui_automator.get_report_info()
 
+    mock_load_device.assert_called_once()
+    mock_load_snippet.assert_called_once()
+    self.assertDictEqual(report_info, {'dut': '<model, type, protocol>'})
+
+  @flagsaver.flagsaver((ui_automator._DUT, None))
+  @mock.patch.object(ui_automator.UIAutomator, 'load_device', autospec=True)
+  @mock.patch.object(ui_automator.UIAutomator, 'load_snippet', autospec=True)
+  def test_get_report_info_returns_empty_dict_without_dut_flag_input(
+      self, mock_load_snippet, mock_load_device
+  ):
+    report_info = self.ui_automator.get_report_info()
+
+    mock_load_device.assert_called_once()
+    mock_load_snippet.assert_called_once()
     self.assertDictEqual(report_info, {})
 
   @flagsaver.flagsaver((ui_automator._HUB, '10.1.3'))
-  def test_get_report_info_includes_hub_value_from_flag_input(self):
-    report_info = self.ui_automator.get_report_info()
-
-    self.assertEqual(len(report_info), 1)
-    self.assertEqual(report_info.get('hub_version'), '10.1.3')
-
-  @flagsaver.flagsaver((ui_automator._HUB, None))
-  def test_get_report_info_returns_empty_dict_without_hub_flag_input(
-      self,
+  @mock.patch.object(ui_automator.UIAutomator, 'load_device', autospec=True)
+  @mock.patch.object(ui_automator.UIAutomator, 'load_snippet', autospec=True)
+  def test_get_report_info_includes_hub_value_from_flag_input(
+      self, mock_load_snippet, mock_load_device
   ):
     report_info = self.ui_automator.get_report_info()
 
+    mock_load_device.assert_called_once()
+    mock_load_snippet.assert_called_once()
+    self.assertDictEqual(report_info, {'hub_version': '10.1.3'})
+
+  @flagsaver.flagsaver((ui_automator._HUB, None))
+  @mock.patch.object(ui_automator.UIAutomator, 'load_device', autospec=True)
+  @mock.patch.object(ui_automator.UIAutomator, 'load_snippet', autospec=True)
+  def test_get_report_info_returns_empty_dict_without_hub_flag_input(
+      self, mock_load_snippet, mock_load_device
+  ):
+    report_info = self.ui_automator.get_report_info()
+
+    mock_load_device.assert_called_once()
+    mock_load_snippet.assert_called_once()
     self.assertDictEqual(report_info, {})
 
   @flagsaver.flagsaver((ui_automator._DEVICE_FIRMWARE, '10.20.12'))
-  def test_get_report_info_includes_device_firmware_from_flag_input(self):
-    report_info = self.ui_automator.get_report_info()
-
-    self.assertEqual(len(report_info), 1)
-    self.assertEqual(report_info.get('device_firmware'), '10.20.12')
-
-  @flagsaver.flagsaver((ui_automator._DEVICE_FIRMWARE, None))
-  def test_get_report_info_returns_empty_dict_without_device_firmware_flag(
-      self,
+  @mock.patch.object(ui_automator.UIAutomator, 'load_device', autospec=True)
+  @mock.patch.object(ui_automator.UIAutomator, 'load_snippet', autospec=True)
+  def test_get_report_info_includes_device_firmware_from_flag_input(
+      self, mock_load_snippet, mock_load_device
   ):
     report_info = self.ui_automator.get_report_info()
 
+    mock_load_device.assert_called_once()
+    mock_load_snippet.assert_called_once()
+    self.assertDictEqual(report_info, {'device_firmware': '10.20.12'})
+
+  @flagsaver.flagsaver((ui_automator._DEVICE_FIRMWARE, None))
+  @mock.patch.object(ui_automator.UIAutomator, 'load_device', autospec=True)
+  @mock.patch.object(ui_automator.UIAutomator, 'load_snippet', autospec=True)
+  def test_get_report_info_returns_empty_dict_without_device_firmware_flag(
+      self, mock_load_snippet, mock_load_device
+  ):
+    report_info = self.ui_automator.get_report_info()
+
+    mock_load_device.assert_called_once()
+    mock_load_snippet.assert_called_once()
+    self.assertDictEqual(report_info, {})
+
+  @mock.patch.object(android_device, 'get_all_instances', autospec=True)
+  @mock.patch.object(ui_automator.UIAutomator, 'load_snippet', autospec=True)
+  def test_get_report_info_should_not_write_none_in_report_info(
+      self,
+      mock_load_snippet,
+      mock_get_all_instances,
+  ):
+    mock_get_all_instances.return_value = [self.mock_android_device]
+    self.mock_android_device.adb.shell.side_effect = [
+        # Response for getting gha version.
+        b'Unable to find package\n',
+        # Response for getting gms core version.
+        b'Unable to find package\n',
+    ]
+
+    report_info = self.ui_automator.get_report_info()
+
+    mock_load_snippet.assert_called_once()
     self.assertDictEqual(report_info, {})
 
 if __name__ == '__main__':