Merge Android 14 QPR3 to AOSP main

Bug: 346855327
Merged-In: Ieaafba65d89342b3b45f3b7b74e04aa3861923e9
Change-Id: I606b02a2f3d33bd96563e92c69e75ef33d0fa2b1
diff --git a/acts_tests/acts_contrib/test_utils/cellular/keysight_5g_testapp.py b/acts_tests/acts_contrib/test_utils/cellular/keysight_5g_testapp.py
index f4ba74f..967cace 100644
--- a/acts_tests/acts_contrib/test_utils/cellular/keysight_5g_testapp.py
+++ b/acts_tests/acts_contrib/test_utils/cellular/keysight_5g_testapp.py
@@ -16,13 +16,16 @@
 
 import collections
 import itertools
+
 import pyvisa
 import time
 from acts import logger
+from acts import asserts as acts_asserts
 from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
 
 SHORT_SLEEP = 1
 VERY_SHORT_SLEEP = 0.1
+MEDIUM_SLEEP = 5
 SUBFRAME_DURATION = 0.001
 VISA_QUERY_DELAY = 0.01
 
@@ -129,12 +132,20 @@
                     if 'No error' not in error:
                         self.log.warning("Command: {}. Error: {}".format(
                             command, error))
-                self.send_cmd('*OPC?', 1)
+                self.send_cmd('*OPC?', 1, check_errors)
                 time.sleep(VISA_QUERY_DELAY)
             except:
                 raise RuntimeError('Lost connection to test app.')
             return None
 
+    def check_error(self):
+        error = self.test_app.query('SYSTem:ERRor?')
+        if 'No error' not in error:
+            self.log.warning("Error: {}".format(error))
+            return True
+        else:
+            return False
+
     def import_scpi_file(self, file_name, check_last_loaded=0):
         """Function to import SCPI file specified in file_name.
 
@@ -438,11 +449,12 @@
                      or subcarrier
         """
         if full_bw:
-            self.send_cmd('BSE:CONFIG:{}:{}:DL:POWer:CHANnel {}'.format(
+            self.send_cmd('BSE:CONFig:{}:{}:DL:POWer:CHANnel {}'.format(
                 cell_type, Keysight5GTestApp._format_cells(cell), power))
         else:
-            self.send_cmd('BSE:CONFIG:{}:{}:DL:POWer:EPRE {}'.format(
+            self.send_cmd('BSE:CONFig:{}:{}:DL:POWer:EPRE {}'.format(
                 cell_type, Keysight5GTestApp._format_cells(cell), power))
+        time.sleep(VERY_SHORT_SLEEP)
         self.send_cmd('BSE:CONFig:{}:APPLY'.format(cell_type))
 
     def set_cell_ul_power_control(self, cell_type, cell, mode, target_power=0):
@@ -475,8 +487,22 @@
             cell: cell/carrier number
             power: expected input power
         """
-        self.send_cmd('BSE:CONFIG:{}:{}:MANual:POWer {}'.format(
-            cell_type, Keysight5GTestApp._format_cells(cell), power))
+        if power == "AUTO" and cell_type == "LTE":
+            self.send_cmd('BSE:CONFig:{}:{}:CONTrol:POWer:AUTO ON'.format(
+                cell_type, Keysight5GTestApp._format_cells(cell)))
+        elif cell_type == "LTE":
+            self.send_cmd('BSE:CONFig:{}:{}:CONTrol:POWer:AUTO OFF'.format(
+                cell_type, Keysight5GTestApp._format_cells(cell)))
+            self.send_cmd('BSE:CONFig:{}:{}:MANual:POWer {}'.format(
+                cell_type, Keysight5GTestApp._format_cells(cell), power))
+        if power == "AUTO" and cell_type == "NR5G":
+            self.send_cmd('BSE:CONFig:{}:UL:EIP:AUTO ON'.format(
+                cell_type))
+        elif cell_type == "NR5G":
+            self.send_cmd('BSE:CONFig:{}:UL:EIP:AUTO OFF'.format(
+                cell_type))
+            self.send_cmd('BSE:CONFig:{}:{}:MANual:POWer {}'.format(
+                cell_type, Keysight5GTestApp._format_cells(cell), power))
         self.send_cmd('BSE:CONFig:{}:APPLY'.format(cell_type))
 
     def set_cell_duplex_mode(self, cell_type, cell, duplex_mode):
@@ -688,6 +714,20 @@
                 'BSE:CONFig:NR5G:UL:{}:CLPControl:TARGet:POWer:ALL {}'.format(
                     channel, target))
 
+    def configure_channel_emulator(self, cell_type, cell, fading_model):
+        if cell_type == 'LTE':
+            self.send_cmd('BSE:CONFig:{}:{}:CMODel {}'.format(cell_type, Keysight5GTestApp._format_cells(cell), fading_model['channel_model']))
+            self.send_cmd('BSE:CONFig:{}:{}:CMATrix {}'.format(cell_type, Keysight5GTestApp._format_cells(cell), fading_model['correlation_matrix']))
+            self.send_cmd('BSE:CONFig:{}:{}:MDSHift {}'.format(cell_type, Keysight5GTestApp._format_cells(cell), fading_model['max_doppler']))
+        elif cell_type == 'NR5G':
+            #TODO: check that this is FR1
+            self.send_cmd('BSE:CONFig:{}:{}:FRANge1:CMODel {}'.format(cell_type, Keysight5GTestApp._format_cells(cell), fading_model['channel_model']))
+            self.send_cmd('BSE:CONFig:{}:{}:FRANge1:CMATrix {}'.format(cell_type, Keysight5GTestApp._format_cells(cell), fading_model['correlation_matrix']))
+            self.send_cmd('BSE:CONFig:{}:{}:FRANge1:MDSHift {}'.format(cell_type, Keysight5GTestApp._format_cells(cell), fading_model['max_doppler']))
+
+    def set_channel_emulator_state(self, state):
+        self.send_cmd('BSE:CONFig:FADing:ENABle {}'.format(int(state)))
+
     def apply_lte_carrier_agg(self, cells):
         """Function to start LTE carrier aggregation on already configured cells"""
         if self.wait_for_cell_status('LTE', 'CELL1', 'CONN', 60):
@@ -700,11 +740,16 @@
 
     def apply_carrier_agg(self):
         """Function to start carrier aggregation on already configured cells"""
-        if self.wait_for_cell_status('LTE', 'CELL1', 'CONN', 60):
-            self.send_cmd(
-                'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:APPly')
-        else:
+        if not self.wait_for_cell_status('LTE', 'CELL1', 'CONN', 60):
             raise RuntimeError('LTE must be connected to start aggregation.')
+        # Continue if LTE connected
+        self.send_cmd(
+            'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:APPly', 0, 0)
+        time.sleep(MEDIUM_SLEEP)
+        error = self.check_error()
+        if error:
+            acts_asserts.fail('Failed to apply NR carrier aggregation.')
+
 
     def get_ip_throughput(self, cell_type):
         """Function to query IP layer throughput on LTE or NR
@@ -728,8 +773,8 @@
         """Helper function to get PHY layer throughput on single cell"""
         if cell_type == 'LTE':
             tput_response = self.send_cmd(
-                'BSE:MEASure:LTE:{}:BTHRoughput:{}:THRoughput:OTA:{}?'.format(
-                    Keysight5GTestApp._format_cells(cell), link,
+                'BSE:MEASure:LTE:BTHRoughput:{}:THRoughput:OTA:{}?'.format(
+                    link,
                     Keysight5GTestApp._format_cells(cell)), 1)
         elif cell_type == 'NR5G':
             # Tester reply format
@@ -747,7 +792,7 @@
         }
         return tput_result
 
-    def get_throughput(self, cell_type, cells):
+    def get_throughput(self, cell_type, dl_cells, ul_cells):
         """Function to get PHY layer throughput on on or more cells
 
         This function returns the throughput data on the requested cells
@@ -760,15 +805,18 @@
         Returns:
             tput_result: dict containing all throughput statistics in Mbps
         """
-        if not isinstance(cells, list):
-            cells = [cells]
+        if not isinstance(dl_cells, list):
+            dl_cells = [dl_cells]
+        if not isinstance(ul_cells, list):
+            ul_cells = [ul_cells]
         tput_result = collections.OrderedDict()
-        for cell in cells:
-            tput_result[cell] = {
-                'DL': self._get_throughput(cell_type, 'DL', cell),
-                'UL': self._get_throughput(cell_type, 'UL', cell)
-            }
+        for cell in dl_cells:
+            tput_result.setdefault(cell, {})
+            tput_result[cell]['DL'] = self._get_throughput(cell_type, 'DL', cell)
             frame_count = tput_result[cell]['DL']['frame_count']
+        for cell in ul_cells:
+            tput_result.setdefault(cell, {})
+            tput_result[cell]['UL'] = self._get_throughput(cell_type, 'UL', cell)
         agg_tput = {
             'DL': {
                 'frame_count': frame_count,
@@ -841,33 +889,35 @@
         self._configure_bler_measurement(cell_type, 0, length)
         self._set_bler_measurement_state(cell_type, 1)
         time.sleep(0.1)
-        bler_check = self.get_bler_result(cell_type, cells, length, 0)
-        if bler_check['total']['DL']['frame_count'] == 0:
-            self.log.warning('BLER measurement did not start. Retrying')
-            self.start_bler_measurement(cell_type, cells, length)
+        #bler_check = self.get_bler_result(cell_type, cells, length, 0)
+        #if bler_check['total']['DL']['frame_count'] == 0:
+        #    self.log.warning('BLER measurement did not start. Retrying')
+        #    self.start_bler_measurement(cell_type, cells, length)
 
     def _get_bler(self, cell_type, link, cell):
         """Helper function to get single-cell BLER measurement results."""
         if cell_type == 'LTE':
             bler_response = self.send_cmd(
-                'BSE:MEASure:LTE:CELL1:BTHRoughput:{}:BLER:CELL1?'.format(
-                    link), 1)
+                'BSE:MEASure:LTE:BTHRoughput:{}:BLER:{}?'.format(
+                    link, Keysight5GTestApp._format_cells(cell)), 1)
+            bler_items = ['frame_count','ack_count','ack_ratio','nack_count','nack_ratio',
+                           'statDtx_count','statDtx_ratio','nackStatDtx_count','nackStatDtx_ratio',
+                           'pdschBler_count','pdschBler_ratio','any_count','any_ratio']
+            bler_result = {bler_items[x] : bler_response[x] for x in range(len(bler_response))}
         elif cell_type == 'NR5G':
             bler_response = self.send_cmd(
                 'BSE:MEASure:NR5G:BTHRoughput:{}:BLER:{}?'.format(
                     link, Keysight5GTestApp._format_cells(cell)), 1)
-        bler_result = {
-            'frame_count': bler_response[0],
-            'ack_count': bler_response[1],
-            'ack_ratio': bler_response[2],
-            'nack_count': bler_response[3],
-            'nack_ratio': bler_response[4]
-        }
+            bler_items = ['frame_count','ack_count','ack_ratio','nack_count','nack_ratio',
+                           'statDtx_count','statDtx_ratio', 'pdschBler_count','pdschBler_ratio','pdschTputRatio']
+
+            bler_result = {bler_items[x]: bler_response[x] for x in range(len(bler_response))}
         return bler_result
 
     def get_bler_result(self,
                         cell_type,
-                        cells,
+                        dl_cells,
+                        ul_cells,
                         length,
                         wait_for_length=1,
                         polling_interval=SHORT_SLEEP):
@@ -888,22 +938,24 @@
         Returns:
             bler_result: dict containing per-cell and aggregate BLER results
         """
-
-        if not isinstance(cells, list):
-            cells = [cells]
+        if not isinstance(dl_cells, list):
+            dl_cells = [dl_cells]
+        if not isinstance(ul_cells, list):
+            ul_cells = [ul_cells]
         while wait_for_length:
-            dl_bler = self._get_bler(cell_type, 'DL', cells[0])
+            dl_bler = self._get_bler(cell_type, 'DL', dl_cells[0])
             if dl_bler['frame_count'] < length:
                 time.sleep(polling_interval)
             else:
                 break
 
         bler_result = collections.OrderedDict()
-        for cell in cells:
-            bler_result[cell] = {
-                'DL': self._get_bler(cell_type, 'DL', cell),
-                'UL': self._get_bler(cell_type, 'UL', cell)
-            }
+        for cell in dl_cells:
+            bler_result.setdefault(cell, {})
+            bler_result[cell]['DL'] = self._get_bler(cell_type, 'DL', cell)
+        for cell in ul_cells:
+            bler_result.setdefault(cell, {})
+            bler_result[cell]['UL'] = self._get_bler(cell_type, 'UL', cell)
         agg_bler = {
             'DL': {
                 'frame_count': length,
diff --git a/acts_tests/acts_contrib/test_utils/cellular/keysight_catr_chamber.py b/acts_tests/acts_contrib/test_utils/cellular/keysight_chamber.py
similarity index 91%
rename from acts_tests/acts_contrib/test_utils/cellular/keysight_catr_chamber.py
rename to acts_tests/acts_contrib/test_utils/cellular/keysight_chamber.py
index bd7540d..079db99 100644
--- a/acts_tests/acts_contrib/test_utils/cellular/keysight_catr_chamber.py
+++ b/acts_tests/acts_contrib/test_utils/cellular/keysight_chamber.py
@@ -4,10 +4,8 @@
 import pyvisa
 import time
 from acts import logger
-from ota_chamber import Chamber
 
-
-class Chamber(Chamber):
+class KeysightChamber(object):
     """Base class implementation for signal generators.
 
     Base class provides functions whose implementation is shared by all
@@ -15,14 +13,15 @@
     """
     CHAMBER_SLEEP = 10
 
+    VISA_LOCATION = '/opt/keysight/iolibs/libktvisa32.so'
+
     def __init__(self, config):
         self.config = config
         self.log = logger.create_tagged_trace_logger("{}{}".format(
             self.config['brand'], self.config['model']))
-        self.chamber_resource = pyvisa.ResourceManager()
+        self.chamber_resource = pyvisa.ResourceManager(self.VISA_LOCATION)
         self.chamber_inst = self.chamber_resource.open_resource(
-            '{}::{}::{}::INSTR'.format(self.config['network_id'],
-                                       self.config['ip_address'],
+            'TCPIP0::{}::{}::INSTR'.format(self.config['ip_address'],
                                        self.config['hislip_interface']))
         self.chamber_inst.timeout = 200000
         self.chamber_inst.write_termination = '\n'
@@ -41,6 +40,7 @@
             self.log.warning(
                 'Reset home set to false. Assumed [0,0]. Chamber angles may not be as expected.'
             )
+        self.preset_orientations = self.config['preset_orientations']
 
     def id_check(self, config):
         """ Checks Chamber ID."""
@@ -87,6 +87,10 @@
         self.move_to_azim_roll(self.current_azim, theta)
 
     def move_theta_phi_abs(self, theta, phi):
+        self.log.info("Moving to Theta={}, Phi={}".format(theta, phi))
+        self.move_to_azim_roll(phi, theta)
+
+    def move_theta_phi_abs(self, theta, phi):
         self.log.info("Moving chamber to [{}, {}]".format(theta, phi))
         self.move_to_azim_roll(phi, theta)
 
@@ -159,7 +163,6 @@
         self.chamber_inst.write("POS:SWE:CONT 1")
 
     def sweep_init(self):
-
         def query_float_list(inst, scpi):
             resp = inst.query(scpi)
             return list(map(float, resp.split(',')))
@@ -170,12 +173,4 @@
         rolls = query_float_list(self.chamber_inst, "FETC:ROLL?")
         phis = query_float_list(self.chamber_inst, "FETC:DUT:PHI?")
         thetas = query_float_list(self.chamber_inst, "FETC:DUT:THET?")
-        return zip(azims, rolls, phis, thetas)
-
-    def configure_positioner(self, pos_name, pos_visa_addr):
-        select = "True"
-        simulate = "False"
-        options = ""
-        data = f"'{pos_name}~{select}~{simulate}~{pos_visa_addr}~{options}'"
-        self.chamber_inst.write(f"EQU:CONF {data}")
-        self.chamber_inst.write("EQU:UPD")
+        return zip(azims, rolls, phis, thetas)
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/cellular/performance/CellularThroughputBaseTest.py b/acts_tests/acts_contrib/test_utils/cellular/performance/CellularThroughputBaseTest.py
index fd2d794..2ebcb25 100644
--- a/acts_tests/acts_contrib/test_utils/cellular/performance/CellularThroughputBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/cellular/performance/CellularThroughputBaseTest.py
@@ -32,6 +32,7 @@
 from acts.controllers.android_lib.tel import tel_utils
 from acts.controllers import iperf_server as ipf
 from acts_contrib.test_utils.cellular.keysight_5g_testapp import Keysight5GTestApp
+from acts_contrib.test_utils.cellular.keysight_chamber import KeysightChamber
 from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
 from functools import partial
@@ -40,6 +41,7 @@
 MEDIUM_SLEEP = 2
 IPERF_TIMEOUT = 10
 SHORT_SLEEP = 1
+VERY_SHORT_SLEEP = 0.1
 SUBFRAME_LENGTH = 0.001
 STOP_COUNTER_LIMIT = 3
 
@@ -70,6 +72,9 @@
         self.dut = self.android_devices[-1]
         self.keysight_test_app = Keysight5GTestApp(
             self.user_params['Keysight5GTestApp'])
+        if 'KeysightChamber' in self.user_params:
+            self.keysight_chamber = KeysightChamber(
+                self.user_params['KeysightChamber'])
         self.iperf_server = self.iperf_servers[0]
         self.iperf_client = self.iperf_clients[0]
         self.remote_server = ssh.connection.SshConnection(
@@ -245,7 +250,7 @@
                 testcase_params['bler_measurement_length'])
         if testcase_params['endc_combo_config']['lte_cell_count']:
             self.keysight_test_app.start_bler_measurement(
-                'LTE', testcase_params['endc_combo_config']['lte_carriers'][0],
+                'LTE', testcase_params['endc_combo_config']['lte_dl_carriers'][0],
                 testcase_params['bler_measurement_length'])
 
         if self.testclass_params['traffic_type'] != 'PHY':
@@ -255,64 +260,59 @@
         if testcase_params['endc_combo_config']['nr_cell_count']:
             result['nr_bler_result'] = self.keysight_test_app.get_bler_result(
                 'NR5G', testcase_params['endc_combo_config']['nr_dl_carriers'],
+                testcase_params['endc_combo_config']['nr_ul_carriers'],
                 testcase_params['bler_measurement_length'])
             result['nr_tput_result'] = self.keysight_test_app.get_throughput(
-                'NR5G', testcase_params['endc_combo_config']['nr_dl_carriers'])
+                'NR5G', testcase_params['endc_combo_config']['nr_dl_carriers'],
+                testcase_params['endc_combo_config']['nr_ul_carriers'])
         if testcase_params['endc_combo_config']['lte_cell_count']:
             result['lte_bler_result'] = self.keysight_test_app.get_bler_result(
-                'LTE', testcase_params['endc_combo_config']['lte_carriers'],
-                testcase_params['bler_measurement_length'])
+                cell_type='LTE', dl_cells=testcase_params['endc_combo_config']['lte_dl_carriers'],
+                ul_cells=testcase_params['endc_combo_config']['lte_ul_carriers'],
+                length=testcase_params['bler_measurement_length'])
             result['lte_tput_result'] = self.keysight_test_app.get_throughput(
-                'LTE', testcase_params['endc_combo_config']['lte_carriers'])
+                'LTE', testcase_params['endc_combo_config']['lte_dl_carriers'],
+                testcase_params['endc_combo_config']['lte_ul_carriers'])
         return result
 
     def print_throughput_result(self, result):
         # Print Test Summary
         if 'nr_tput_result' in result:
+
             self.log.info(
-                "----NR5G STATS-------NR5G STATS-------NR5G STATS---")
-            self.log.info(
-                "DL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+                "NR5G DL PHY Tput (Mbps) (Min/Avg/Max/Th): {:.2f} / {:.2f} / {:.2f} / {:.2f}\tBLER: {:.2f}"
                 .format(
                     result['nr_tput_result']['total']['DL']['min_tput'],
                     result['nr_tput_result']['total']['DL']['average_tput'],
                     result['nr_tput_result']['total']['DL']['max_tput'],
-                    result['nr_tput_result']['total']['DL']
-                    ['theoretical_tput']))
+                    result['nr_tput_result']['total']['DL']['theoretical_tput'],
+                    result['nr_bler_result']['total']['DL']['nack_ratio'] * 100))
             self.log.info(
-                "UL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+                "NR5G UL PHY Tput (Mbps) (Min/Avg/Max/Th): {:.2f} / {:.2f} / {:.2f} / {:.2f}\tBLER: {:.2f}"
                 .format(
                     result['nr_tput_result']['total']['UL']['min_tput'],
                     result['nr_tput_result']['total']['UL']['average_tput'],
                     result['nr_tput_result']['total']['UL']['max_tput'],
-                    result['nr_tput_result']['total']['UL']
-                    ['theoretical_tput']))
-            self.log.info("DL BLER: {:.2f}%\tUL BLER: {:.2f}%".format(
-                result['nr_bler_result']['total']['DL']['nack_ratio'] * 100,
-                result['nr_bler_result']['total']['UL']['nack_ratio'] * 100))
+                    result['nr_tput_result']['total']['UL']['theoretical_tput'],
+                    result['nr_bler_result']['total']['UL']['nack_ratio'] * 100))
         if 'lte_tput_result' in result:
-            self.log.info("----LTE STATS-------LTE STATS-------LTE STATS---")
             self.log.info(
-                "DL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+                "LTE DL PHY Tput (Mbps) (Min/Avg/Max/Th): {:.2f} / {:.2f} / {:.2f} / {:.2f}\tBLER: {:.2f}"
                 .format(
                     result['lte_tput_result']['total']['DL']['min_tput'],
                     result['lte_tput_result']['total']['DL']['average_tput'],
                     result['lte_tput_result']['total']['DL']['max_tput'],
-                    result['lte_tput_result']['total']['DL']
-                    ['theoretical_tput']))
+                    result['lte_tput_result']['total']['DL']['theoretical_tput'],
+                    result['lte_bler_result']['total']['DL']['nack_ratio'] * 100))
             if self.testclass_params['lte_ul_mac_padding']:
                 self.log.info(
-                    "UL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+                    "LTE UL PHY Tput (Mbps) (Min/Avg/Max/Th): {:.2f} / {:.2f} / {:.2f} / {:.2f}\tBLER: {:.2f}"
                     .format(
                         result['lte_tput_result']['total']['UL']['min_tput'],
-                        result['lte_tput_result']['total']['UL']
-                        ['average_tput'],
+                        result['lte_tput_result']['total']['UL']['average_tput'],
                         result['lte_tput_result']['total']['UL']['max_tput'],
-                        result['lte_tput_result']['total']['UL']
-                        ['theoretical_tput']))
-            self.log.info("DL BLER: {:.2f}%\tUL BLER: {:.2f}%".format(
-                result['lte_bler_result']['total']['DL']['nack_ratio'] * 100,
-                result['lte_bler_result']['total']['UL']['nack_ratio'] * 100))
+                        result['lte_tput_result']['total']['UL']['theoretical_tput'],
+                        result['lte_bler_result']['total']['UL']['nack_ratio'] * 100))
             if self.testclass_params['traffic_type'] != 'PHY':
                 self.log.info("{} Tput: {:.2f} Mbps".format(
                     self.testclass_params['traffic_type'],
@@ -337,15 +337,18 @@
             self.keysight_test_app.set_cell_input_power(
                 cell['cell_type'], cell['cell_number'],
                self.testclass_params['input_power'][cell['cell_type']])
-            self.keysight_test_app.set_cell_ul_power_control(
-                cell['cell_type'], cell['cell_number'],
-                self.testclass_params['ul_power_control_mode'],
-                self.testclass_params.get('ul_power_control_target',0)
-            )
+            if cell['cell_type'] == 'LTE' and cell['pcc'] == 0:
+                pass
+            else:
+                self.keysight_test_app.set_cell_ul_power_control(
+                    cell['cell_type'], cell['cell_number'],
+                    self.testclass_params['ul_power_control_mode'],
+                    self.testclass_params.get('ul_power_control_target',0)
+                )
             if cell['cell_type'] == 'NR5G':
                 self.keysight_test_app.set_nr_subcarrier_spacing(
                     cell['cell_number'], cell['subcarrier_spacing'])
-            if 'channel' in cell:
+            if 'channel' in cell and cell['channel'] is not None:
                 self.keysight_test_app.set_cell_channel(
                     cell['cell_type'], cell['cell_number'], cell['channel'])
             self.keysight_test_app.set_cell_bandwidth(cell['cell_type'],
@@ -363,6 +366,10 @@
                 self.keysight_test_app.set_cell_mimo_config(
                     cell['cell_type'], cell['cell_number'], 'UL',
                     cell['ul_mimo_config'])
+            if 'fading_scenario' in self.testclass_params:
+                self.keysight_test_app.configure_channel_emulator(
+                    cell['cell_type'], cell['cell_number'],
+                    self.testclass_params['fading_scenario'][cell['cell_type']])
 
         if testcase_params.get('force_contiguous_nr_channel', False):
             self.keysight_test_app.toggle_contiguous_nr_channels(1)
@@ -377,7 +384,6 @@
                 self.testclass_params['lte_ul_mac_padding'])
 
         if testcase_params['endc_combo_config']['nr_cell_count']:
-
             if 'schedule_scenario' in testcase_params:
                 self.keysight_test_app.set_nr_cell_schedule_scenario(
                     'CELL1',
@@ -406,18 +412,12 @@
                         cell['cell_number']))
                     self.keysight_test_app.set_cell_state(cell['cell_type'],
                                                           cell['cell_number'], 1)
-            # Activate LTE aggregation if applicable
-            if testcase_params['endc_combo_config']['lte_scc_list']:
-                self.keysight_test_app.apply_lte_carrier_agg(
-                    testcase_params['endc_combo_config']['lte_scc_list'])
             self.log.info('Waiting for LTE connections')
             # Turn airplane mode off
             num_apm_toggles = 10
             for idx in range(num_apm_toggles):
                 self.log.info('Turning off airplane mode')
-                #asserts.assert_true(utils.force_airplane_mode(self.dut, False),
-                #                    'Can not turn off airplane mode.')
-                tel_utils.toggle_airplane_mode(self.log, self.dut, False)
+                cputils.toggle_airplane_mode(self.log, self.dut, False, False, idx)
                 if self.keysight_test_app.wait_for_cell_status(
                         'LTE', 'CELL1', 'CONN', 10*(idx+1)):
                     self.log.info('Connected! Waiting for {} seconds.'.format(LONG_SLEEP))
@@ -425,12 +425,14 @@
                     break
                 elif idx < num_apm_toggles - 1:
                     self.log.info('Turning on airplane mode')
-                #    asserts.assert_true(utils.force_airplane_mode(self.dut, True),
-                #                        'Can not turn on airplane mode.')
-                    tel_utils.toggle_airplane_mode(self.log, self.dut, True)
+                    cputils.toggle_airplane_mode(self.log, self.dut, True, False, idx)
                     time.sleep(MEDIUM_SLEEP)
                 else:
                     asserts.fail('DUT did not connect to LTE.')
+            # Activate LTE aggregation if applicable
+            if testcase_params['endc_combo_config']['lte_scc_list']:
+                self.keysight_test_app.apply_lte_carrier_agg(
+                    testcase_params['endc_combo_config']['lte_scc_list'])
 
             if testcase_params['endc_combo_config']['nr_cell_count']:
                 self.keysight_test_app.apply_carrier_agg()
@@ -454,9 +456,7 @@
             num_apm_toggles = 10
             for idx in range(num_apm_toggles):
                 self.log.info('Turning off airplane mode now.')
-                #asserts.assert_true(utils.force_airplane_mode(self.dut, False),
-                #                    'Can not turn off airplane mode.')
-                tel_utils.toggle_airplane_mode(self.log, self.dut, False)
+                cputils.toggle_airplane_mode(self.log, self.dut, False, False, idx)
                 if self.keysight_test_app.wait_for_cell_status(
                         'NR5G', 'CELL1', 'CONN', 10*(idx+1)):
                     self.log.info('Connected! Waiting for {} seconds.'.format(LONG_SLEEP))
@@ -464,13 +464,17 @@
                     break
                 elif idx < num_apm_toggles - 1:
                     self.log.info('Turning on airplane mode now.')
-                #    asserts.assert_true(utils.force_airplane_mode(self.dut, True),
-                #                        'Can not turn on airplane mode.')
-                    tel_utils.toggle_airplane_mode(self.log, self.dut, True)
+                    cputils.toggle_airplane_mode(self.log, self.dut, True, False, idx)
                     time.sleep(MEDIUM_SLEEP)
                 else:
                     asserts.fail('DUT did not connect to NR.')
 
+        if 'fading_scenario' in self.testclass_params and self.testclass_params['fading_scenario']['enable']:
+            self.log.info('Enabling fading.')
+            self.keysight_test_app.set_channel_emulator_state(self.testclass_params['fading_scenario']['enable'])
+        else:
+            self.keysight_test_app.set_channel_emulator_state(0)
+
     def _test_throughput_bler(self, testcase_params):
         """Test function to run cellular throughput and BLER measurements.
 
@@ -489,6 +493,12 @@
         testcase_results['testcase_params'] = testcase_params
         testcase_results['results'] = []
 
+        # Setup ota chamber if needed
+        if hasattr(self, 'keysight_chamber') and 'orientation' in testcase_params:
+            self.keysight_chamber.move_theta_phi_abs(
+                self.keysight_chamber.preset_orientations[testcase_params['orientation']]['theta'],
+                self.keysight_chamber.preset_orientations[testcase_params['orientation']]['phi'])
+
         # Setup tester and wait for DUT to connect
         self.setup_tester(testcase_params)
 
@@ -502,6 +512,16 @@
                                                       'OTAGRAPH')
         for power_idx in range(len(testcase_params['cell_power_sweep'][0])):
             result = collections.OrderedDict()
+            # Check that cells are still connected
+            connected = 1
+            for cell in testcase_params['endc_combo_config']['cell_list']:
+                if not self.keysight_test_app.wait_for_cell_status(
+                    cell['cell_type'], cell['cell_number'],
+                        ['ACT', 'CONN'], VERY_SHORT_SLEEP,VERY_SHORT_SLEEP):
+                    connected = 0
+            if not connected:
+                self.log.info('DUT lost connection to cells. Ending test.')
+                break
             # Set DL cell power
             for cell_idx, cell in enumerate(
                     testcase_params['endc_combo_config']['cell_list']):
diff --git a/acts_tests/acts_contrib/test_utils/cellular/performance/cellular_performance_test_utils.py b/acts_tests/acts_contrib/test_utils/cellular/performance/cellular_performance_test_utils.py
index 2a72590..0132693 100644
--- a/acts_tests/acts_contrib/test_utils/cellular/performance/cellular_performance_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/cellular/performance/cellular_performance_test_utils.py
@@ -19,6 +19,8 @@
 import os
 import re
 import time
+from queue import Empty
+from acts.controllers.android_lib.tel import tel_utils
 
 PCC_PRESET_MAPPING = {
     'N257': {
@@ -66,6 +68,7 @@
     },
 }
 
+LONG_SLEEP = 10
 
 def extract_test_id(testcase_params, id_fields):
     test_id = collections.OrderedDict(
@@ -249,3 +252,293 @@
     sinr_values = [float(x) for x in re.findall(sinr_regex, rx_meas)]
     return {'rsrp': rsrp_values, 'rsrq': rsrq_values, 'rssi': rssi_values, 'sinr': sinr_values}
 
+def toggle_airplane_mode(log, ad, new_state=None, strict_checking=True, try_index=0):
+    """ Toggle the state of airplane mode.
+
+    Args:
+        log: log handler.
+        ad: android_device object.
+        new_state: Airplane mode state to set to.
+            If None, opposite of the current state.
+        strict_checking: Whether to turn on strict checking that checks all features.
+        try_index: index of apm toggle
+
+    Returns:
+        result: True if operation succeed. False if error happens.
+    """
+    if try_index % 2 == 0:
+        log.info('Toggling airplane mode {} by adb.'.format(new_state))
+        return tel_utils.toggle_airplane_mode_by_adb(log, ad, new_state)
+    else:
+        log.info('Toggling airplane mode {} by msim.'.format(new_state))
+        return toggle_airplane_mode_msim(
+            log, ad, new_state, strict_checking=strict_checking)
+
+def toggle_airplane_mode_msim(log, ad, new_state=None, strict_checking=True):
+    """ Toggle the state of airplane mode.
+
+    Args:
+        log: log handler.
+        ad: android_device object.
+        new_state: Airplane mode state to set to.
+            If None, opposite of the current state.
+        strict_checking: Whether to turn on strict checking that checks all features.
+
+    Returns:
+        result: True if operation succeed. False if error happens.
+    """
+
+    cur_state = ad.droid.connectivityCheckAirplaneMode()
+    if cur_state == new_state:
+        ad.log.info("Airplane mode already in %s", new_state)
+        return True
+    elif new_state is None:
+        new_state = not cur_state
+        ad.log.info("Toggle APM mode, from current tate %s to %s", cur_state,
+                    new_state)
+    sub_id_list = []
+    active_sub_info = ad.droid.subscriptionGetAllSubInfoList()
+    if active_sub_info:
+        for info in active_sub_info:
+            sub_id_list.append(info['subscriptionId'])
+
+    ad.ed.clear_all_events()
+    time.sleep(0.1)
+    service_state_list = []
+    if new_state:
+        service_state_list.append(tel_utils.SERVICE_STATE_POWER_OFF)
+        ad.log.info("Turn on airplane mode")
+
+    else:
+        # If either one of these 3 events show up, it should be OK.
+        # Normal SIM, phone in service
+        service_state_list.append(tel_utils.SERVICE_STATE_IN_SERVICE)
+        # NO SIM, or Dead SIM, or no Roaming coverage.
+        service_state_list.append(tel_utils.SERVICE_STATE_OUT_OF_SERVICE)
+        service_state_list.append(tel_utils.SERVICE_STATE_EMERGENCY_ONLY)
+        ad.log.info("Turn off airplane mode")
+
+    for sub_id in sub_id_list:
+        ad.droid.telephonyStartTrackingServiceStateChangeForSubscription(
+            sub_id)
+
+    timeout_time = time.time() + LONG_SLEEP
+    ad.droid.connectivityToggleAirplaneMode(new_state)
+
+    try:
+        try:
+            event = ad.ed.wait_for_event(
+                tel_utils.EVENT_SERVICE_STATE_CHANGED,
+                tel_utils.is_event_match_for_list,
+                timeout= LONG_SLEEP,
+                field=tel_utils.ServiceStateContainer.SERVICE_STATE,
+                value_list=service_state_list)
+            ad.log.info("Got event %s", event)
+        except Empty:
+            ad.log.warning("Did not get expected service state change to %s",
+                           service_state_list)
+        finally:
+            for sub_id in sub_id_list:
+                ad.droid.telephonyStopTrackingServiceStateChangeForSubscription(
+                    sub_id)
+    except Exception as e:
+        ad.log.error(e)
+
+    # APM on (new_state=True) will turn off bluetooth but may not turn it on
+    try:
+        if new_state and not tel_utils._wait_for_bluetooth_in_state(
+                log, ad, False, timeout_time - time.time()):
+            ad.log.error(
+                "Failed waiting for bluetooth during airplane mode toggle")
+            if strict_checking: return False
+    except Exception as e:
+        ad.log.error("Failed to check bluetooth state due to %s", e)
+        if strict_checking:
+            raise
+
+    # APM on (new_state=True) will turn off wifi but may not turn it on
+    if new_state and not tel_utils._wait_for_wifi_in_state(log, ad, False,
+                                                 timeout_time - time.time()):
+        ad.log.error("Failed waiting for wifi during airplane mode toggle on")
+        if strict_checking: return False
+
+    if ad.droid.connectivityCheckAirplaneMode() != new_state:
+        ad.log.error("Set airplane mode to %s failed", new_state)
+        return False
+    return True
+
+def generate_endc_combo_config_from_string(endc_combo_str):
+    """Function to generate ENDC combo config from combo string
+
+    Args:
+        endc_combo_str: ENDC combo descriptor (e.g. B48A[4];A[1]+N5A[2];A[1])
+    Returns:
+        endc_combo_config: dictionary with all ENDC combo settings
+    """
+    endc_combo_config = collections.OrderedDict()
+    endc_combo_config['endc_combo_name']=endc_combo_str
+    endc_combo_str = endc_combo_str.replace(' ', '')
+    endc_combo_list = endc_combo_str.split('+')
+    cell_config_list = list()
+    lte_cell_count = 0
+    nr_cell_count = 0
+    lte_scc_list = []
+    nr_dl_carriers = []
+    nr_ul_carriers = []
+    lte_dl_carriers = []
+    lte_ul_carriers = []
+
+    cell_config_regex = re.compile(
+        r'(?P<cell_type>[B,N])(?P<band>[0-9]+)(?P<bandwidth_class>[A-Z])\[bw=(?P<dl_bandwidth>[0-9]+)\]'
+        r'(\[ch=)?(?P<channel>[0-9]+)?\]?\[ant=(?P<dl_mimo_config>[0-9]+),?(?P<transmission_mode>[TM0-9]+)?\];?'
+        r'(?P<ul_bandwidth_class>[A-Z])?(\[ant=)?(?P<ul_mimo_config>[0-9])?(\])?'
+    )
+    for cell_string in endc_combo_list:
+        cell_config = re.match(cell_config_regex, cell_string).groupdict()
+        if cell_config['cell_type'] == 'B':
+            # Configure LTE specific parameters
+            cell_config['cell_type'] = 'LTE'
+            lte_cell_count = lte_cell_count + 1
+            cell_config['cell_number'] = lte_cell_count
+            if cell_config['cell_number'] == 1:
+                cell_config['pcc'] = 1
+                endc_combo_config['lte_pcc'] = cell_config['cell_number']
+            else:
+                cell_config['pcc'] = 0
+                lte_scc_list.append(cell_config['cell_number'])
+            cell_config['duplex_mode'] = 'FDD' if int(
+                cell_config['band']
+            ) in DUPLEX_MODE_TO_BAND_MAPPING['LTE'][
+                'FDD'] else 'TDD'
+            cell_config['dl_mimo_config'] = 'D{nss}U{nss}'.format(
+                nss=cell_config['dl_mimo_config'])
+            lte_dl_carriers.append(cell_config['cell_number'])
+        else:
+            # Configure NR specific parameters
+            cell_config['cell_type'] = 'NR5G'
+            nr_cell_count = nr_cell_count + 1
+            cell_config['cell_number'] = nr_cell_count
+            nr_dl_carriers.append(cell_config['cell_number'])
+            #TODO: fix NSA/SA indicator
+            cell_config['nr_cell_type'] = 'NSA'
+            cell_config['band'] = 'N' + cell_config['band']
+            cell_config['duplex_mode'] = 'FDD' if cell_config[
+                'band'] in DUPLEX_MODE_TO_BAND_MAPPING['NR5G'][
+                    'FDD'] else 'TDD'
+            cell_config['subcarrier_spacing'] = 'MU0' if cell_config[
+                'duplex_mode'] == 'FDD' else 'MU1'
+            cell_config['dl_mimo_config'] = 'N{nss}X{nss}'.format(
+                nss=cell_config['dl_mimo_config'])
+
+        cell_config['dl_bandwidth_class'] = cell_config['bandwidth_class']
+        cell_config['dl_bandwidth'] = 'BW'+ cell_config['dl_bandwidth']
+        cell_config['ul_enabled'] = 1 if cell_config['ul_bandwidth_class'] else 0
+        if cell_config['ul_enabled']:
+            cell_config['ul_mimo_config'] = 'N{nss}X{nss}'.format(
+                nss=cell_config['ul_mimo_config'])
+            if cell_config['cell_type'] == 'LTE':
+                lte_ul_carriers.append(cell_config['cell_number'])
+            elif cell_config['cell_type'] == 'NR5G':
+                nr_ul_carriers.append(cell_config['cell_number'])
+        cell_config_list.append(cell_config)
+    endc_combo_config['lte_cell_count'] = lte_cell_count
+    endc_combo_config['nr_cell_count'] = nr_cell_count
+    endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
+    endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
+    endc_combo_config['cell_list'] = cell_config_list
+    endc_combo_config['lte_scc_list'] = lte_scc_list
+    endc_combo_config['lte_dl_carriers'] = lte_dl_carriers
+    endc_combo_config['lte_ul_carriers'] = lte_ul_carriers
+    return endc_combo_config
+
+def generate_endc_combo_config_from_csv_row(test_config):
+    """Function to generate ENDC combo config from CSV test config
+
+    Args:
+        test_config: dict containing ENDC combo config from CSV
+    Returns:
+        endc_combo_config: dictionary with all ENDC combo settings
+    """
+    endc_combo_config = collections.OrderedDict()
+    lte_cell_count = 0
+    nr_cell_count = 0
+    lte_scc_list = []
+    nr_dl_carriers = []
+    nr_ul_carriers = []
+    lte_dl_carriers = []
+    lte_ul_carriers = []
+
+    cell_config_list = []
+    if test_config['lte_band']:
+        lte_cell = {
+            'cell_type':
+            'LTE',
+            'cell_number':
+            1,
+            'pcc':
+            1,
+            'band':
+            test_config['lte_band'],
+            'dl_bandwidth':
+            test_config['lte_bandwidth'],
+            'ul_enabled':
+            1,
+            'duplex_mode':
+            test_config['lte_duplex_mode'],
+            'dl_mimo_config':
+            'D{nss}U{nss}'.format(nss=test_config['lte_dl_mimo_config']),
+            'ul_mimo_config':
+            'D{nss}U{nss}'.format(nss=test_config['lte_ul_mimo_config'])
+        }
+        if int(test_config['lte_dl_mimo_config']) == 1:
+            lte_cell['transmission_mode'] = 'TM1'
+        elif int(test_config['lte_dl_mimo_config']) == 2:
+            lte_cell['transmission_mode'] = 'TM2'
+        else:
+            lte_cell['transmission_mode'] = 'TM3'
+        cell_config_list.append(lte_cell)
+        endc_combo_config['lte_pcc'] = 1
+        lte_cell_count = 1
+        lte_dl_carriers = [1]
+        lte_ul_carriers = [1]
+
+    if test_config['nr_band']:
+        nr_cell = {
+            'cell_type':
+            'NR5G',
+            'cell_number':
+            1,
+            'band':
+            test_config['nr_band'],
+            'nr_cell_type': test_config['nr_cell_type'],
+            'duplex_mode':
+            test_config['nr_duplex_mode'],
+            'dl_mimo_config':
+            'N{nss}X{nss}'.format(nss=test_config['nr_dl_mimo_config']),
+            'dl_bandwidth_class':
+            'A',
+            'dl_bandwidth':
+            test_config['nr_bandwidth'],
+            'ul_enabled':
+            1,
+            'ul_bandwidth_class':
+            'A',
+            'ul_mimo_config':
+            'N{nss}X{nss}'.format(nss=test_config['nr_ul_mimo_config']),
+            'subcarrier_spacing':
+            'MU0' if test_config['nr_scs'] == '15' else 'MU1'
+        }
+        cell_config_list.append(nr_cell)
+        nr_cell_count = 1
+        nr_dl_carriers = [1]
+        nr_ul_carriers = [1]
+
+    endc_combo_config['lte_cell_count'] = lte_cell_count
+    endc_combo_config['nr_cell_count'] = nr_cell_count
+    endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
+    endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
+    endc_combo_config['cell_list'] = cell_config_list
+    endc_combo_config['lte_scc_list'] = lte_scc_list
+    endc_combo_config['lte_dl_carriers'] = lte_dl_carriers
+    endc_combo_config['lte_ul_carriers'] = lte_ul_carriers
+    return endc_combo_config
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py
index d944db5..b9e6d28 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py
@@ -269,7 +269,7 @@
     for key, val in connected_rssi.copy().items():
         if 'data' not in val:
             continue
-        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        filtered_rssi_values = [x for x in val['data'] if not (math.isnan(x) or math.isinf(x))]
         if len(filtered_rssi_values) > ignore_samples:
             filtered_rssi_values = filtered_rssi_values[ignore_samples:]
         if filtered_rssi_values:
diff --git a/acts_tests/tests/google/cellular/performance/Cellular5GFR2SensitivityTest.py b/acts_tests/tests/google/cellular/performance/Cellular5GFR2SensitivityTest.py
deleted file mode 100644
index f3e49b8..0000000
--- a/acts_tests/tests/google/cellular/performance/Cellular5GFR2SensitivityTest.py
+++ /dev/null
@@ -1,235 +0,0 @@
-#!/usr/bin/env python3.4
-#
-#   Copyright 2022 - The Android Open Source Project
-#
-#   Licensed under the Apache License, Version 2.0 (the 'License');
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an 'AS IS' BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-import collections
-import itertools
-import json
-import numpy
-import os
-from functools import partial
-from acts import asserts
-from acts import context
-from acts import base_test
-from acts import utils
-from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.controllers.utils_lib import ssh
-from acts_contrib.test_utils.cellular.keysight_5g_testapp import Keysight5GTestApp
-from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
-from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
-from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
-from Cellular5GFR2ThroughputTest import Cellular5GFR2ThroughputTest
-
-
-class Cellular5GFR2SensitivityTest(Cellular5GFR2ThroughputTest):
-    """Class to test cellular throughput
-
-    This class implements cellular throughput tests on a lab/callbox setup.
-    The class setups up the callbox in the desired configurations, configures
-    and connects the phone, and runs traffic/iperf throughput.
-    """
-
-    def __init__(self, controllers):
-        base_test.BaseTestClass.__init__(self, controllers)
-        self.testcase_metric_logger = (
-            BlackboxMappedMetricLogger.for_test_case())
-        self.testclass_metric_logger = (
-            BlackboxMappedMetricLogger.for_test_class())
-        self.publish_testcase_metrics = True
-
-    def setup_class(self):
-        """Initializes common test hardware and parameters.
-
-        This function initializes hardwares and compiles parameters that are
-        common to all tests in this class.
-        """
-        self.dut = self.android_devices[-1]
-        self.testclass_params = self.user_params['sensitivity_test_params']
-        self.keysight_test_app = Keysight5GTestApp(
-            self.user_params['Keysight5GTestApp'])
-        self.testclass_results = collections.OrderedDict()
-        self.iperf_server = self.iperf_servers[0]
-        self.iperf_client = self.iperf_clients[0]
-        self.remote_server = ssh.connection.SshConnection(
-            ssh.settings.from_config(
-                self.user_params['RemoteServer']['ssh_config']))
-        if self.testclass_params.get('reload_scpi', 1):
-            self.keysight_test_app.import_scpi_file(
-                self.testclass_params['scpi_file'])
-        # Configure test retries
-        self.user_params['retry_tests'] = [self.__class__.__name__]
-
-        # Turn Airplane mode on
-        asserts.assert_true(utils.force_airplane_mode(self.dut, True),
-                            'Can not turn on airplane mode.')
-
-    def process_testcase_results(self):
-        if self.current_test_name not in self.testclass_results:
-            return
-        testcase_results = self.testclass_results[self.current_test_name]
-        cell_power_list = [
-            result['cell_power'] for result in testcase_results['results']
-        ]
-        dl_bler_list = [
-            result['bler_result']['total']['DL']['nack_ratio']
-            for result in testcase_results['results']
-        ]
-        bler_above_threshold = [
-            x > self.testclass_params['bler_threshold'] for x in dl_bler_list
-        ]
-        for idx in range(len(bler_above_threshold)):
-            if all(bler_above_threshold[idx:]):
-                sensitivity_index = max(idx, 1) - 1
-                cell_power_at_sensitivity = cell_power_list[sensitivity_index]
-                break
-        else:
-            sensitivity_index = -1
-            cell_power_at_sensitivity = float('nan')
-        if min(dl_bler_list) < 0.05:
-            testcase_results['sensitivity'] = cell_power_at_sensitivity
-        else:
-            testcase_results['sensitivity'] = float('nan')
-
-        testcase_results['cell_power_list'] = cell_power_list
-        testcase_results['dl_bler_list'] = dl_bler_list
-
-        results_file_path = os.path.join(
-            context.get_current_context().get_full_output_path(),
-            '{}.json'.format(self.current_test_name))
-        with open(results_file_path, 'w') as results_file:
-            json.dump(wputils.serialize_dict(testcase_results),
-                      results_file,
-                      indent=4)
-
-        result_string = ('DL {}CC MCS {} Sensitivity = {}dBm.'.format(
-            testcase_results['testcase_params']['num_dl_cells'],
-            testcase_results['testcase_params']['dl_mcs'],
-            testcase_results['sensitivity']))
-        if min(dl_bler_list) < 0.05:
-            self.log.info('Test Passed. {}'.format(result_string))
-        else:
-            self.log.info('Result unreliable. {}'.format(result_string))
-
-    def process_testclass_results(self):
-        Cellular5GFR2ThroughputTest.process_testclass_results(self)
-
-        plots = collections.OrderedDict()
-        id_fields = ['band', 'num_dl_cells']
-        for testcase, testcase_data in self.testclass_results.items():
-            testcase_params = testcase_data['testcase_params']
-            plot_id = cputils.extract_test_id(testcase_params, id_fields)
-            plot_id = tuple(plot_id.items())
-            if plot_id not in plots:
-                plots[plot_id] = BokehFigure(title='{} {}CC'.format(
-                    testcase_params['band'], testcase_params['num_dl_cells']),
-                                             x_label='Cell Power (dBm)',
-                                             primary_y_label='BLER (%)')
-            plots[plot_id].add_line(
-                testcase_data['cell_power_list'],
-                testcase_data['dl_bler_list'],
-                'Channel {}, MCS {}'.format(testcase_params['channel'],
-                                            testcase_params['dl_mcs']))
-        figure_list = []
-        for plot_id, plot in plots.items():
-            plot.generate_figure()
-            figure_list.append(plot)
-        output_file_path = os.path.join(self.log_path, 'results.html')
-        BokehFigure.save_figures(figure_list, output_file_path)
-
-    def generate_test_cases(self, bands, channels, mcs_pair_list,
-                            num_dl_cells_list, num_ul_cells_list, **kwargs):
-        """Function that auto-generates test cases for a test class."""
-        test_cases = []
-
-        for band, channel, num_ul_cells, num_dl_cells, mcs_pair in itertools.product(
-                bands, channels, num_ul_cells_list, num_dl_cells_list,
-                mcs_pair_list):
-            if num_ul_cells > num_dl_cells:
-                continue
-            test_name = 'test_nr_sensitivity_{}_{}_DL_{}CC_mcs{}'.format(
-                band, channel, num_dl_cells, mcs_pair[0])
-            test_params = collections.OrderedDict(
-                band=band,
-                channel=channel,
-                dl_mcs=mcs_pair[0],
-                ul_mcs=mcs_pair[1],
-                num_dl_cells=num_dl_cells,
-                num_ul_cells=num_ul_cells,
-                dl_cell_list=list(range(1, num_dl_cells + 1)),
-                ul_cell_list=list(range(1, num_ul_cells + 1)),
-                **kwargs)
-            setattr(self, test_name,
-                    partial(self._test_nr_throughput_bler, test_params))
-            test_cases.append(test_name)
-        return test_cases
-
-
-class Cellular5GFR2_AllBands_SensitivityTest(Cellular5GFR2SensitivityTest):
-
-    def __init__(self, controllers):
-        super().__init__(controllers)
-        self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                              ['low', 'mid', 'high'],
-                                              [(16, 4), (27, 4)],
-                                              list(range(1, 9)), [1],
-                                              schedule_scenario="FULL_TPUT",
-                                              traffic_direction='DL',
-                                              transform_precoding=0)
-
-
-class Cellular5GFR2_FrequencySweep_SensitivityTest(Cellular5GFR2SensitivityTest
-                                                   ):
-
-    def __init__(self, controllers):
-        super().__init__(controllers)
-        frequency_sweep_params = self.user_params['sensitivity_test_params'][
-            'frequency_sweep']
-        self.tests = self.generate_test_cases(frequency_sweep_params,
-                                              [(16, 4), (27, 4)],
-                                              schedule_scenario="FULL_TPUT",
-                                              traffic_direction='DL',
-                                              transform_precoding=0)
-
-    def generate_test_cases(self, dl_frequency_sweep_params, mcs_pair_list,
-                            **kwargs):
-        """Function that auto-generates test cases for a test class."""
-        test_cases = ['test_load_scpi']
-
-        for band, band_config in dl_frequency_sweep_params.items():
-            for num_dl_cells_str, sweep_config in band_config.items():
-                num_dl_cells = int(num_dl_cells_str[0])
-                num_ul_cells = 1
-                freq_vector = numpy.arange(sweep_config[0], sweep_config[1],
-                                           sweep_config[2])
-                for freq in freq_vector:
-                    for mcs_pair in mcs_pair_list:
-                        test_name = 'test_nr_sensitivity_{}_{}_DL_{}CC_mcs{}'.format(
-                            band, freq, num_dl_cells, mcs_pair[0])
-                        test_params = collections.OrderedDict(
-                            band=band,
-                            channel=freq,
-                            dl_mcs=mcs_pair[0],
-                            ul_mcs=mcs_pair[1],
-                            num_dl_cells=num_dl_cells,
-                            num_ul_cells=num_ul_cells,
-                            dl_cell_list=list(range(1, num_dl_cells + 1)),
-                            ul_cell_list=list(range(1, num_ul_cells + 1)),
-                            **kwargs)
-                        setattr(
-                            self, test_name,
-                            partial(self._test_nr_throughput_bler,
-                                    test_params))
-                        test_cases.append(test_name)
-        return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/Cellular5GFR2ThroughputTest.py b/acts_tests/tests/google/cellular/performance/Cellular5GFR2ThroughputTest.py
deleted file mode 100644
index 9e848a3..0000000
--- a/acts_tests/tests/google/cellular/performance/Cellular5GFR2ThroughputTest.py
+++ /dev/null
@@ -1,664 +0,0 @@
-#!/usr/bin/env python3.4
-#
-#   Copyright 2022 - The Android Open Source Project
-#
-#   Licensed under the Apache License, Version 2.0 (the 'License');
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an 'AS IS' BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-import collections
-import csv
-import itertools
-import json
-import numpy
-import os
-import time
-from acts import asserts
-from acts import context
-from acts import base_test
-from acts import utils
-from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.controllers.utils_lib import ssh
-from acts.controllers import iperf_server as ipf
-from acts_contrib.test_utils.cellular.keysight_5g_testapp import Keysight5GTestApp
-from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
-from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
-
-from functools import partial
-
-LONG_SLEEP = 10
-MEDIUM_SLEEP = 2
-IPERF_TIMEOUT = 10
-SHORT_SLEEP = 1
-SUBFRAME_LENGTH = 0.001
-STOP_COUNTER_LIMIT = 3
-
-
-class Cellular5GFR2ThroughputTest(base_test.BaseTestClass):
-    """Class to test cellular throughput
-
-    This class implements cellular throughput tests on a lab/callbox setup.
-    The class setups up the callbox in the desired configurations, configures
-    and connects the phone, and runs traffic/iperf throughput.
-    """
-
-    def __init__(self, controllers):
-        base_test.BaseTestClass.__init__(self, controllers)
-        self.testcase_metric_logger = (
-            BlackboxMappedMetricLogger.for_test_case())
-        self.testclass_metric_logger = (
-            BlackboxMappedMetricLogger.for_test_class())
-        self.publish_testcase_metrics = True
-
-    def setup_class(self):
-        """Initializes common test hardware and parameters.
-
-        This function initializes hardwares and compiles parameters that are
-        common to all tests in this class.
-        """
-        self.dut = self.android_devices[-1]
-        self.testclass_params = self.user_params['throughput_test_params']
-        self.keysight_test_app = Keysight5GTestApp(
-            self.user_params['Keysight5GTestApp'])
-        self.testclass_results = collections.OrderedDict()
-        self.iperf_server = self.iperf_servers[0]
-        self.iperf_client = self.iperf_clients[0]
-        self.remote_server = ssh.connection.SshConnection(
-            ssh.settings.from_config(
-                self.user_params['RemoteServer']['ssh_config']))
-        if self.testclass_params.get('reload_scpi', 1):
-            self.keysight_test_app.import_scpi_file(
-                self.testclass_params['scpi_file'])
-        # Configure test retries
-        self.user_params['retry_tests'] = [self.__class__.__name__]
-
-        # Turn Airplane mode on
-        asserts.assert_true(utils.force_airplane_mode(self.dut, True),
-                            'Can not turn on airplane mode.')
-
-    def teardown_class(self):
-        self.log.info('Turning airplane mode on')
-        try:
-            asserts.assert_true(utils.force_airplane_mode(self.dut, True),
-                                'Can not turn on airplane mode.')
-        except:
-            self.log.warning('Cannot perform teardown operations on DUT.')
-        try:
-            self.keysight_test_app.set_cell_state('LTE', 1, 0)
-            self.keysight_test_app.destroy()
-        except:
-            self.log.warning('Cannot perform teardown operations on tester.')
-        self.process_testclass_results()
-
-    def setup_test(self):
-        if self.testclass_params['enable_pixel_logs']:
-            cputils.start_pixel_logger(self.dut)
-
-    def on_retry(self):
-        """Function to control test logic on retried tests.
-
-        This function is automatically executed on tests that are being
-        retried. In this case the function resets wifi, toggles it off and on
-        and sets a retry_flag to enable further tweaking the test logic on
-        second attempts.
-        """
-        asserts.assert_true(utils.force_airplane_mode(self.dut, True),
-                            'Can not turn on airplane mode.')
-        if self.keysight_test_app.get_cell_state('LTE', 'CELL1'):
-            self.log.info('Turning LTE off.')
-            self.keysight_test_app.set_cell_state('LTE', 'CELL1', 0)
-
-    def teardown_test(self):
-        self.log.info('Turing airplane mode on')
-        asserts.assert_true(utils.force_airplane_mode(self.dut, True),
-                            'Can not turn on airplane mode.')
-        log_path = os.path.join(
-            context.get_current_context().get_full_output_path(), 'pixel_logs')
-        os.makedirs(self.log_path, exist_ok=True)
-        if self.testclass_params['enable_pixel_logs']:
-            cputils.stop_pixel_logger(self.dut, log_path)
-        self.process_testcase_results()
-        self.pass_fail_check()
-
-    def process_testcase_results(self):
-        if self.current_test_name not in self.testclass_results:
-            return
-        testcase_data = self.testclass_results[self.current_test_name]
-        results_file_path = os.path.join(
-            context.get_current_context().get_full_output_path(),
-            '{}.json'.format(self.current_test_name))
-        with open(results_file_path, 'w') as results_file:
-            json.dump(wputils.serialize_dict(testcase_data),
-                      results_file,
-                      indent=4)
-        testcase_result = testcase_data['results'][0]
-        metric_map = {
-            'min_dl_tput':
-            testcase_result['tput_result']['total']['DL']['min_tput'],
-            'max_dl_tput':
-            testcase_result['tput_result']['total']['DL']['max_tput'],
-            'avg_dl_tput':
-            testcase_result['tput_result']['total']['DL']['average_tput'],
-            'theoretical_dl_tput':
-            testcase_result['tput_result']['total']['DL']['theoretical_tput'],
-            'dl_bler':
-            testcase_result['bler_result']['total']['DL']['nack_ratio'] * 100,
-            'min_dl_tput':
-            testcase_result['tput_result']['total']['UL']['min_tput'],
-            'max_dl_tput':
-            testcase_result['tput_result']['total']['UL']['max_tput'],
-            'avg_dl_tput':
-            testcase_result['tput_result']['total']['UL']['average_tput'],
-            'theoretical_dl_tput':
-            testcase_result['tput_result']['total']['UL']['theoretical_tput'],
-            'ul_bler':
-            testcase_result['bler_result']['total']['UL']['nack_ratio'] * 100,
-            'tcp_udp_tput':
-            testcase_result.get('iperf_throughput', float('nan'))
-        }
-        if self.publish_testcase_metrics:
-            for metric_name, metric_value in metric_map.items():
-                self.testcase_metric_logger.add_metric(metric_name,
-                                                       metric_value)
-
-    def pass_fail_check(self):
-        pass
-
-    def process_testclass_results(self):
-        """Saves CSV with all test results to enable comparison."""
-        results_file_path = os.path.join(
-            context.get_current_context().get_full_output_path(),
-            'results.csv')
-        with open(results_file_path, 'w', newline='') as csvfile:
-            field_names = [
-                'Band', 'Channel', 'DL Carriers', 'UL Carriers', 'DL MCS',
-                'DL MIMO', 'UL MCS', 'UL MIMO', 'Cell Power',
-                'DL Min. Throughput', 'DL Max. Throughput',
-                'DL Avg. Throughput', 'DL Theoretical Throughput',
-                'UL Min. Throughput', 'UL Max. Throughput',
-                'UL Avg. Throughput', 'UL Theoretical Throughput',
-                'DL BLER (%)', 'UL BLER (%)', 'TCP/UDP Throughput'
-            ]
-            writer = csv.DictWriter(csvfile, fieldnames=field_names)
-            writer.writeheader()
-
-            for testcase_name, testcase_results in self.testclass_results.items(
-            ):
-                for result in testcase_results['results']:
-                    writer.writerow({
-                        'Band':
-                        testcase_results['testcase_params']['band'],
-                        'Channel':
-                        testcase_results['testcase_params']['channel'],
-                        'DL Carriers':
-                        testcase_results['testcase_params']['num_dl_cells'],
-                        'UL Carriers':
-                        testcase_results['testcase_params']['num_ul_cells'],
-                        'DL MCS':
-                        testcase_results['testcase_params']['dl_mcs'],
-                        'DL MIMO':
-                        testcase_results['testcase_params']['dl_mimo_config'],
-                        'UL MCS':
-                        testcase_results['testcase_params']['ul_mcs'],
-                        'UL MIMO':
-                        testcase_results['testcase_params']['ul_mimo_config'],
-                        'Cell Power':
-                        result['cell_power'],
-                        'DL Min. Throughput':
-                        result['tput_result']['total']['DL']['min_tput'],
-                        'DL Max. Throughput':
-                        result['tput_result']['total']['DL']['max_tput'],
-                        'DL Avg. Throughput':
-                        result['tput_result']['total']['DL']['average_tput'],
-                        'DL Theoretical Throughput':
-                        result['tput_result']['total']['DL']
-                        ['theoretical_tput'],
-                        'UL Min. Throughput':
-                        result['tput_result']['total']['UL']['min_tput'],
-                        'UL Max. Throughput':
-                        result['tput_result']['total']['UL']['max_tput'],
-                        'UL Avg. Throughput':
-                        result['tput_result']['total']['UL']['average_tput'],
-                        'UL Theoretical Throughput':
-                        result['tput_result']['total']['UL']
-                        ['theoretical_tput'],
-                        'DL BLER (%)':
-                        result['bler_result']['total']['DL']['nack_ratio'] *
-                        100,
-                        'UL BLER (%)':
-                        result['bler_result']['total']['UL']['nack_ratio'] *
-                        100,
-                        'TCP/UDP Throughput':
-                        result.get('iperf_throughput', 0)
-                    })
-
-    def setup_tester(self, testcase_params):
-        if not self.keysight_test_app.get_cell_state('LTE', 'CELL1'):
-            self.log.info('Turning LTE on.')
-            self.keysight_test_app.set_cell_state('LTE', 'CELL1', 1)
-        self.log.info('Turning off airplane mode')
-        asserts.assert_true(utils.force_airplane_mode(self.dut, False),
-                            'Can not turn on airplane mode.')
-        for cell in testcase_params['dl_cell_list']:
-            self.keysight_test_app.set_cell_band('NR5G', cell,
-                                                 testcase_params['band'])
-            self.keysight_test_app.set_cell_mimo_config(
-                'NR5G', cell, 'DL', testcase_params['dl_mimo_config'])
-            self.keysight_test_app.set_cell_dl_power(
-                'NR5G', cell, testcase_params['cell_power_list'][0], 1)
-        for cell in testcase_params['ul_cell_list']:
-            self.keysight_test_app.set_cell_mimo_config(
-                'NR5G', cell, 'UL', testcase_params['ul_mimo_config'])
-        self.keysight_test_app.configure_contiguous_nr_channels(
-            testcase_params['dl_cell_list'][0], testcase_params['band'],
-            testcase_params['channel'])
-        # Consider configuring schedule quick config
-        self.keysight_test_app.set_nr_cell_schedule_scenario(
-            testcase_params['dl_cell_list'][0],
-            testcase_params['schedule_scenario'])
-        self.keysight_test_app.set_nr_ul_dft_precoding(
-            testcase_params['dl_cell_list'][0],
-            testcase_params['transform_precoding'])
-        self.keysight_test_app.set_nr_cell_mcs(
-            testcase_params['dl_cell_list'][0], testcase_params['dl_mcs'],
-            testcase_params['ul_mcs'])
-        self.keysight_test_app.set_dl_carriers(testcase_params['dl_cell_list'])
-        self.keysight_test_app.set_ul_carriers(testcase_params['ul_cell_list'])
-        self.log.info('Waiting for LTE and applying aggregation')
-        if not self.keysight_test_app.wait_for_cell_status(
-                'LTE', 'CELL1', 'CONN', 60):
-            asserts.fail('DUT did not connect to LTE.')
-        self.keysight_test_app.apply_carrier_agg()
-        self.log.info('Waiting for 5G connection')
-        connected = self.keysight_test_app.wait_for_cell_status(
-            'NR5G', testcase_params['dl_cell_list'][-1], ['ACT', 'CONN'], 60)
-        if not connected:
-            asserts.fail('DUT did not connect to NR.')
-        time.sleep(SHORT_SLEEP)
-
-    def run_iperf_traffic(self, testcase_params):
-        self.iperf_server.start(tag=0)
-        dut_ip = self.dut.droid.connectivityGetIPv4Addresses('rmnet0')[0]
-        if 'iperf_server_address' in self.testclass_params:
-            iperf_server_address = self.testclass_params[
-                'iperf_server_address']
-        elif isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
-            iperf_server_address = dut_ip
-        else:
-            iperf_server_address = wputils.get_server_address(
-                self.remote_server, dut_ip, '255.255.255.0')
-        client_output_path = self.iperf_client.start(
-            iperf_server_address, testcase_params['iperf_args'], 0,
-            self.testclass_params['traffic_duration'] + IPERF_TIMEOUT)
-        server_output_path = self.iperf_server.stop()
-        # Parse and log result
-        if testcase_params['use_client_output']:
-            iperf_file = client_output_path
-        else:
-            iperf_file = server_output_path
-        try:
-            iperf_result = ipf.IPerfResult(iperf_file)
-            current_throughput = numpy.mean(iperf_result.instantaneous_rates[
-                self.testclass_params['iperf_ignored_interval']:-1]) * 8 * (
-                    1.024**2)
-        except:
-            self.log.warning(
-                'ValueError: Cannot get iperf result. Setting to 0')
-            current_throughput = 0
-        return current_throughput
-
-    def _test_nr_throughput_bler(self, testcase_params):
-        """Test function to run cellular throughput and BLER measurements.
-
-        The function runs BLER/throughput measurement after configuring the
-        callbox and DUT. The test supports running PHY or TCP/UDP layer traffic
-        in a variety of band/carrier/mcs/etc configurations.
-
-        Args:
-            testcase_params: dict containing test-specific parameters
-        Returns:
-            result: dict containing throughput results and meta data
-        """
-        testcase_params = self.compile_test_params(testcase_params)
-        testcase_results = collections.OrderedDict()
-        testcase_results['testcase_params'] = testcase_params
-        testcase_results['results'] = []
-        # Setup tester and wait for DUT to connect
-        self.setup_tester(testcase_params)
-        # Run test
-        stop_counter = 0
-        for cell_power in testcase_params['cell_power_list']:
-            result = collections.OrderedDict()
-            result['cell_power'] = cell_power
-            # Set DL cell power
-            for cell in testcase_params['dl_cell_list']:
-                self.keysight_test_app.set_cell_dl_power(
-                    'NR5G', cell, result['cell_power'], 1)
-            self.keysight_test_app.select_display_tab(
-                'NR5G', testcase_params['dl_cell_list'][0], 'BTHR', 'OTAGRAPH')
-            time.sleep(SHORT_SLEEP)
-            # Start BLER and throughput measurements
-            self.keysight_test_app.start_bler_measurement(
-                'NR5G', testcase_params['dl_cell_list'],
-                testcase_params['bler_measurement_length'])
-            if self.testclass_params['traffic_type'] != 'PHY':
-                result['iperf_throughput'] = self.run_iperf_traffic(
-                    testcase_params)
-            if self.testclass_params['log_power_metrics']:
-                if testcase_params[
-                        'bler_measurement_length'] >= 5000 and self.testclass_params[
-                            'traffic_type'] == 'PHY':
-                    time.sleep(testcase_params['bler_measurement_length'] /
-                               1000 - 5)
-                    cputils.log_system_power_metrics(self.dut, verbose=0)
-                else:
-                    self.log.warning('Test too short to log metrics')
-
-            result['bler_result'] = self.keysight_test_app.get_bler_result(
-                'NR5G', testcase_params['dl_cell_list'],
-                testcase_params['bler_measurement_length'])
-            result['tput_result'] = self.keysight_test_app.get_throughput(
-                'NR5G', testcase_params['dl_cell_list'])
-
-            # Print Test Summary
-            self.log.info("Cell Power: {}dBm".format(cell_power))
-            self.log.info(
-                "DL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
-                .format(
-                    result['tput_result']['total']['DL']['min_tput'],
-                    result['tput_result']['total']['DL']['average_tput'],
-                    result['tput_result']['total']['DL']['max_tput'],
-                    result['tput_result']['total']['DL']['theoretical_tput']))
-            self.log.info(
-                "UL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
-                .format(
-                    result['tput_result']['total']['UL']['min_tput'],
-                    result['tput_result']['total']['UL']['average_tput'],
-                    result['tput_result']['total']['UL']['max_tput'],
-                    result['tput_result']['total']['UL']['theoretical_tput']))
-            self.log.info("DL BLER: {:.2f}%\tUL BLER: {:.2f}%".format(
-                result['bler_result']['total']['DL']['nack_ratio'] * 100,
-                result['bler_result']['total']['UL']['nack_ratio'] * 100))
-            testcase_results['results'].append(result)
-            if self.testclass_params['traffic_type'] != 'PHY':
-                self.log.info("{} {} Tput: {:.2f} Mbps".format(
-                    self.testclass_params['traffic_type'],
-                    testcase_params['traffic_direction'],
-                    result['iperf_throughput']))
-
-            if result['bler_result']['total']['DL']['nack_ratio'] * 100 > 99:
-                stop_counter = stop_counter + 1
-            else:
-                stop_counter = 0
-            if stop_counter == STOP_COUNTER_LIMIT:
-                break
-        # Turn off NR cells
-        for cell in testcase_params['dl_cell_list'][::-1]:
-            self.keysight_test_app.set_cell_state('NR5G', cell, 0)
-        asserts.assert_true(utils.force_airplane_mode(self.dut, True),
-                            'Can not turn on airplane mode.')
-
-        # Save results
-        self.testclass_results[self.current_test_name] = testcase_results
-
-    def compile_test_params(self, testcase_params):
-        """Function that completes all test params based on the test name.
-
-        Args:
-            testcase_params: dict containing test-specific parameters
-        """
-        testcase_params['bler_measurement_length'] = int(
-            self.testclass_params['traffic_duration'] / SUBFRAME_LENGTH)
-        testcase_params['cell_power_list'] = numpy.arange(
-            self.testclass_params['cell_power_start'],
-            self.testclass_params['cell_power_stop'],
-            self.testclass_params['cell_power_step'])
-        if self.testclass_params['traffic_type'] == 'PHY':
-            return testcase_params
-        if self.testclass_params['traffic_type'] == 'TCP':
-            testcase_params['iperf_socket_size'] = self.testclass_params.get(
-                'tcp_socket_size', None)
-            testcase_params['iperf_processes'] = self.testclass_params.get(
-                'tcp_processes', 1)
-        elif self.testclass_params['traffic_type'] == 'UDP':
-            testcase_params['iperf_socket_size'] = self.testclass_params.get(
-                'udp_socket_size', None)
-            testcase_params['iperf_processes'] = self.testclass_params.get(
-                'udp_processes', 1)
-        if (testcase_params['traffic_direction'] == 'DL'
-                and not isinstance(self.iperf_server, ipf.IPerfServerOverAdb)
-            ) or (testcase_params['traffic_direction'] == 'UL'
-                  and isinstance(self.iperf_server, ipf.IPerfServerOverAdb)):
-            testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
-                duration=self.testclass_params['traffic_duration'],
-                reverse_direction=1,
-                traffic_type=self.testclass_params['traffic_type'],
-                socket_size=testcase_params['iperf_socket_size'],
-                num_processes=testcase_params['iperf_processes'],
-                udp_throughput=self.testclass_params['UDP_rates'].get(
-                    testcase_params['num_dl_cells'],
-                    self.testclass_params['UDP_rates']["default"]),
-                udp_length=1440)
-            testcase_params['use_client_output'] = True
-        elif (testcase_params['traffic_direction'] == 'UL'
-              and not isinstance(self.iperf_server, ipf.IPerfServerOverAdb)
-              ) or (testcase_params['traffic_direction'] == 'DL'
-                    and isinstance(self.iperf_server, ipf.IPerfServerOverAdb)):
-            testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
-                duration=self.testclass_params['traffic_duration'],
-                reverse_direction=0,
-                traffic_type=self.testclass_params['traffic_type'],
-                socket_size=testcase_params['iperf_socket_size'],
-                num_processes=testcase_params['iperf_processes'],
-                udp_throughput=self.testclass_params['UDP_rates'].get(
-                    testcase_params['num_dl_cells'],
-                    self.testclass_params['UDP_rates']["default"]),
-                udp_length=1440)
-            testcase_params['use_client_output'] = False
-        return testcase_params
-
-    def generate_test_cases(self, bands, channels, mcs_pair_list,
-                            num_dl_cells_list, num_ul_cells_list,
-                            dl_mimo_config, ul_mimo_config, **kwargs):
-        """Function that auto-generates test cases for a test class."""
-        test_cases = ['test_load_scpi']
-
-        for band, channel, num_ul_cells, num_dl_cells, mcs_pair in itertools.product(
-                bands, channels, num_ul_cells_list, num_dl_cells_list,
-                mcs_pair_list):
-            if num_ul_cells > num_dl_cells:
-                continue
-            if channel not in cputils.PCC_PRESET_MAPPING[band]:
-                continue
-            test_name = 'test_nr_throughput_bler_{}_{}_DL_{}CC_mcs{}_{}_UL_{}CC_mcs{}_{}'.format(
-                band, channel, num_dl_cells, mcs_pair[0], dl_mimo_config,
-                num_ul_cells, mcs_pair[1], ul_mimo_config)
-            test_params = collections.OrderedDict(
-                band=band,
-                channel=channel,
-                dl_mcs=mcs_pair[0],
-                ul_mcs=mcs_pair[1],
-                num_dl_cells=num_dl_cells,
-                num_ul_cells=num_ul_cells,
-                dl_mimo_config=dl_mimo_config,
-                ul_mimo_config=ul_mimo_config,
-                dl_cell_list=list(range(1, num_dl_cells + 1)),
-                ul_cell_list=list(range(1, num_ul_cells + 1)),
-                **kwargs)
-            setattr(self, test_name,
-                    partial(self._test_nr_throughput_bler, test_params))
-            test_cases.append(test_name)
-        return test_cases
-
-
-class Cellular5GFR2_DL_ThroughputTest(Cellular5GFR2ThroughputTest):
-
-    def __init__(self, controllers):
-        super().__init__(controllers)
-        self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                              ['low', 'mid', 'high'],
-                                              [(16, 4), (27, 4)],
-                                              list(range(1, 9)),
-                                              list(range(1, 3)),
-                                              dl_mimo_config='N2X2',
-                                              ul_mimo_config='N1X1',
-                                              schedule_scenario="FULL_TPUT",
-                                              traffic_direction='DL',
-                                              transform_precoding=0)
-
-
-class Cellular5GFR2_CP_UL_ThroughputTest(Cellular5GFR2ThroughputTest):
-
-    def __init__(self, controllers):
-        super().__init__(controllers)
-        self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                              ['low', 'mid', 'high'],
-                                              [(4, 16), (4, 27)], [1], [1],
-                                              dl_mimo_config='N2X2',
-                                              ul_mimo_config='N1X1',
-                                              schedule_scenario="FULL_TPUT",
-                                              traffic_direction='UL',
-                                              transform_precoding=0)
-        self.tests.extend(
-            self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                     ['low', 'mid', 'high'],
-                                     [(4, 16), (4, 27)], [1], [1],
-                                     dl_mimo_config='N2X2',
-                                     ul_mimo_config='N2X2',
-                                     schedule_scenario="FULL_TPUT",
-                                     traffic_direction='UL',
-                                     transform_precoding=0))
-        self.tests.extend(
-            self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                     ['low', 'mid', 'high'],
-                                     [(4, 16), (4, 27)], [2], [2],
-                                     dl_mimo_config='N2X2',
-                                     ul_mimo_config='N2X2',
-                                     schedule_scenario="FULL_TPUT",
-                                     traffic_direction='UL',
-                                     transform_precoding=0))
-        self.tests.extend(
-            self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                     ['low', 'mid', 'high'],
-                                     [(4, 16), (4, 27)], [3], [3],
-                                     dl_mimo_config='N2X2',
-                                     ul_mimo_config='N2X2',
-                                     schedule_scenario="UL_RMC",
-                                     traffic_direction='UL',
-                                     transform_precoding=0))
-        self.tests.extend(
-            self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                     ['low', 'mid', 'high'],
-                                     [(4, 16), (4, 27)], [4], [4],
-                                     dl_mimo_config='N2X2',
-                                     ul_mimo_config='N2X2',
-                                     schedule_scenario="FULL_TPUT",
-                                     traffic_direction='UL',
-                                     transform_precoding=0))
-
-
-class Cellular5GFR2_DFTS_UL_ThroughputTest(Cellular5GFR2ThroughputTest):
-
-    def __init__(self, controllers):
-        super().__init__(controllers)
-        self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                              ['low', 'mid', 'high'],
-                                              [(4, 16), (4, 27)], [1], [1],
-                                              dl_mimo_config='N2X2',
-                                              ul_mimo_config='N1X1',
-                                              schedule_scenario="FULL_TPUT",
-                                              traffic_direction='UL',
-                                              transform_precoding=1)
-        self.tests.extend(
-            self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                     ['low', 'mid', 'high'],
-                                     [(4, 16), (4, 27)], [1], [1],
-                                     dl_mimo_config='N2X2',
-                                     ul_mimo_config='N2X2',
-                                     schedule_scenario="FULL_TPUT",
-                                     traffic_direction='UL',
-                                     transform_precoding=1))
-        self.tests.extend(
-            self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                     ['low', 'mid', 'high'],
-                                     [(4, 16), (4, 27)], [2], [2],
-                                     dl_mimo_config='N2X2',
-                                     ul_mimo_config='N2X2',
-                                     schedule_scenario="FULL_TPUT",
-                                     traffic_direction='UL',
-                                     transform_precoding=1))
-        self.tests.extend(
-            self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                     ['low', 'mid', 'high'],
-                                     [(4, 16), (4, 27)], [3], [3],
-                                     dl_mimo_config='N2X2',
-                                     ul_mimo_config='N2X2',
-                                     schedule_scenario="FULL_TPUT",
-                                     traffic_direction='UL',
-                                     transform_precoding=1))
-        self.tests.extend(
-            self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
-                                     ['low', 'mid', 'high'],
-                                     [(4, 16), (4, 27)], [4], [4],
-                                     dl_mimo_config='N2X2',
-                                     ul_mimo_config='N2X2',
-                                     schedule_scenario="FULL_TPUT",
-                                     traffic_direction='UL',
-                                     transform_precoding=1))
-
-
-class Cellular5GFR2_DL_FrequecySweep_ThroughputTest(Cellular5GFR2ThroughputTest
-                                                    ):
-
-    def __init__(self, controllers):
-        super().__init__(controllers)
-        dl_frequency_sweep_params = self.user_params['throughput_test_params'][
-            'dl_frequency_sweep']
-        self.tests = self.generate_test_cases(dl_frequency_sweep_params,
-                                              [(16, 4), (27, 4)],
-                                              schedule_scenario="FULL_TPUT",
-                                              traffic_direction='DL',
-                                              transform_precoding=0,
-                                              dl_mimo_config='N2X2',
-                                              ul_mimo_config='N1X1')
-
-    def generate_test_cases(self, dl_frequency_sweep_params, mcs_pair_list,
-                            **kwargs):
-        """Function that auto-generates test cases for a test class."""
-        test_cases = ['test_load_scpi']
-
-        for band, band_config in dl_frequency_sweep_params.items():
-            for num_dl_cells_str, sweep_config in band_config.items():
-                num_dl_cells = int(num_dl_cells_str[0])
-                num_ul_cells = 1
-                freq_vector = numpy.arange(sweep_config[0], sweep_config[1],
-                                           sweep_config[2])
-                for freq in freq_vector:
-                    for mcs_pair in mcs_pair_list:
-                        test_name = 'test_nr_throughput_bler_{}_{}MHz_DL_{}CC_mcs{}_UL_{}CC_mcs{}'.format(
-                            band, freq, num_dl_cells, mcs_pair[0],
-                            num_ul_cells, mcs_pair[1])
-                        test_params = collections.OrderedDict(
-                            band=band,
-                            channel=freq,
-                            dl_mcs=mcs_pair[0],
-                            ul_mcs=mcs_pair[1],
-                            num_dl_cells=num_dl_cells,
-                            num_ul_cells=num_ul_cells,
-                            dl_cell_list=list(range(1, num_dl_cells + 1)),
-                            ul_cell_list=list(range(1, num_ul_cells + 1)),
-                            **kwargs)
-                        setattr(
-                            self, test_name,
-                            partial(self._test_nr_throughput_bler,
-                                    test_params))
-                        test_cases.append(test_name)
-        return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/CellularFr1RvRTest.py b/acts_tests/tests/google/cellular/performance/CellularFr1RvRTest.py
index 96b2aa0..605580d 100644
--- a/acts_tests/tests/google/cellular/performance/CellularFr1RvRTest.py
+++ b/acts_tests/tests/google/cellular/performance/CellularFr1RvRTest.py
@@ -23,14 +23,15 @@
 from acts import context
 from acts import base_test
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
 from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
-from CellularLtePlusFr1PeakThroughputTest import CellularFr1SingleCellPeakThroughputTest
 
 from functools import partial
 
 
-class CellularFr1RvrTest(CellularFr1SingleCellPeakThroughputTest):
+class CellularFr1RvrTest(CellularThroughputBaseTest):
     """Class to test single cell FR1 NSA sensitivity"""
 
     def __init__(self, controllers):
@@ -129,7 +130,7 @@
                     test_configs, channel_list):
                 if int(test_config['skip_test']):
                     continue
-                endc_combo_config = self.generate_endc_combo_config(
+                endc_combo_config = cputils.generate_endc_combo_config_from_csv_row(
                     test_config)
                 test_name = 'test_fr1_{}_{}'.format(
                     test_config['nr_band'], channel.lower())
diff --git a/acts_tests/tests/google/cellular/performance/CellularFr1SensitivityTest.py b/acts_tests/tests/google/cellular/performance/CellularFr1SensitivityTest.py
index 2e515cb..3057cfb 100644
--- a/acts_tests/tests/google/cellular/performance/CellularFr1SensitivityTest.py
+++ b/acts_tests/tests/google/cellular/performance/CellularFr1SensitivityTest.py
@@ -23,14 +23,15 @@
 from acts import context
 from acts import base_test
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
 from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
-from CellularLtePlusFr1PeakThroughputTest import CellularFr1SingleCellPeakThroughputTest
+from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest
 
 from functools import partial
 
 
-class CellularFr1SensitivityTest(CellularFr1SingleCellPeakThroughputTest):
+class CellularFr1SensitivityTest(CellularThroughputBaseTest):
     """Class to test single cell FR1 NSA sensitivity"""
 
     def __init__(self, controllers):
@@ -49,7 +50,10 @@
             lte_dl_mcs=4,
             lte_ul_mcs_table='QAM256',
             lte_ul_mcs=4,
-            transform_precoding=0)
+            transform_precoding=0,
+            schedule_scenario='FULL_TPUT',
+            schedule_slot_ratio=80
+        )
 
     def process_testclass_results(self):
         # Plot individual test id results raw data and compile metrics
@@ -98,7 +102,6 @@
                 width=1,
                 style='dashed')
 
-        # Compute average RvRs and compute metrics over orientations
         for test_id, test_data in compiled_data.items():
             test_id_rvr = test_id + tuple('RvR')
             cell_power_interp = sorted(set(sum(test_data['cell_power'], [])))
@@ -121,17 +124,29 @@
         output_file_path = os.path.join(self.log_path, 'results.html')
         BokehFigure.save_figures(figure_list, output_file_path)
 
+        """Saves CSV with all test results to enable comparison."""
+        results_file_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            'results.csv')
+        with open(results_file_path, 'w', newline='') as csvfile:
+            field_names = [
+                'Test Name', 'Sensitivity'
+            ]
+            writer = csv.DictWriter(csvfile, fieldnames=field_names)
+            writer.writeheader()
+
+            for testcase_name, testcase_results in self.testclass_results.items(
+            ):
+                row_dict = {
+                    'Test Name': testcase_name,
+                    'Sensitivity': testcase_results['sensitivity']
+                }
+                writer.writerow(row_dict)
+
     def process_testcase_results(self):
         if self.current_test_name not in self.testclass_results:
             return
         testcase_data = self.testclass_results[self.current_test_name]
-        results_file_path = os.path.join(
-            context.get_current_context().get_full_output_path(),
-            '{}.json'.format(self.current_test_name))
-        with open(results_file_path, 'w') as results_file:
-            json.dump(wputils.serialize_dict(testcase_data),
-                      results_file,
-                      indent=4)
 
         bler_list = []
         average_throughput_list = []
@@ -176,6 +191,14 @@
         testcase_data['cell_power_list'] = cell_power_list
         testcase_data['sensitivity'] = sensitivity
 
+        results_file_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            '{}.json'.format(self.current_test_name))
+        with open(results_file_path, 'w') as results_file:
+            json.dump(wputils.serialize_dict(testcase_data),
+                      results_file,
+                      indent=4)
+
     def get_per_cell_power_sweeps(self, testcase_params):
         # get reference test
         nr_cell_index = testcase_params['endc_combo_config']['lte_cell_count']
@@ -221,7 +244,7 @@
                     test_configs, channel_list, dl_mcs_list):
                 if int(test_config['skip_test']):
                     continue
-                endc_combo_config = self.generate_endc_combo_config(
+                endc_combo_config = cputils.generate_endc_combo_config_from_csv_row(
                     test_config)
                 test_name = 'test_fr1_{}_{}_dl_mcs{}'.format(
                     test_config['nr_band'], channel.lower(), nr_dl_mcs)
@@ -233,3 +256,27 @@
                         partial(self._test_throughput_bler, test_params))
                 test_cases.append(test_name)
         return test_cases
+
+class CellularFr1Sensitivity_SampleMCS_Test(CellularFr1SensitivityTest):
+    """Class to test single cell FR1 NSA sensitivity"""
+
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+        self.testclass_params = self.user_params['nr_sensitivity_test_params']
+        self.tests = self.generate_test_cases(
+            channel_list=['LOW'],
+            dl_mcs_list=[27, 25, 16, 9],
+            nr_ul_mcs=4,
+            lte_dl_mcs_table='QAM256',
+            lte_dl_mcs=4,
+            lte_ul_mcs_table='QAM256',
+            lte_ul_mcs=4,
+            transform_precoding=0,
+            schedule_scenario='FULL_TPUT',
+            schedule_slot_ratio=80
+        )
\ No newline at end of file
diff --git a/acts_tests/tests/google/cellular/performance/CellularFr2PeakThroughputTest.py b/acts_tests/tests/google/cellular/performance/CellularFr2PeakThroughputTest.py
index 848832e..6fbb4d9 100644
--- a/acts_tests/tests/google/cellular/performance/CellularFr2PeakThroughputTest.py
+++ b/acts_tests/tests/google/cellular/performance/CellularFr2PeakThroughputTest.py
@@ -76,60 +76,60 @@
                 'nr_cell_count']:
             metric_map.update({
                 'nr_min_dl_tput':
-                testcase_result['nr_tput_result']['total']['DL']['min_tput'],
+                testcase_result['throughput_measurements']['nr_tput_result']['total']['DL']['min_tput'],
                 'nr_max_dl_tput':
-                testcase_result['nr_tput_result']['total']['DL']['max_tput'],
+                testcase_result['throughput_measurements']['nr_tput_result']['total']['DL']['max_tput'],
                 'nr_avg_dl_tput':
-                testcase_result['nr_tput_result']['total']['DL']
+                testcase_result['throughput_measurements']['nr_tput_result']['total']['DL']
                 ['average_tput'],
                 'nr_theoretical_dl_tput':
-                testcase_result['nr_tput_result']['total']['DL']
+                testcase_result['throughput_measurements']['nr_tput_result']['total']['DL']
                 ['theoretical_tput'],
                 'nr_dl_bler':
-                testcase_result['nr_bler_result']['total']['DL']['nack_ratio']
+                testcase_result['throughput_measurements']['nr_bler_result']['total']['DL']['nack_ratio']
                 * 100,
                 'nr_min_dl_tput':
-                testcase_result['nr_tput_result']['total']['UL']['min_tput'],
+                testcase_result['throughput_measurements']['nr_tput_result']['total']['UL']['min_tput'],
                 'nr_max_dl_tput':
-                testcase_result['nr_tput_result']['total']['UL']['max_tput'],
+                testcase_result['throughput_measurements']['nr_tput_result']['total']['UL']['max_tput'],
                 'nr_avg_dl_tput':
-                testcase_result['nr_tput_result']['total']['UL']
+                testcase_result['throughput_measurements']['nr_tput_result']['total']['UL']
                 ['average_tput'],
                 'nr_theoretical_dl_tput':
-                testcase_result['nr_tput_result']['total']['UL']
+                testcase_result['throughput_measurements']['nr_tput_result']['total']['UL']
                 ['theoretical_tput'],
                 'nr_ul_bler':
-                testcase_result['nr_bler_result']['total']['UL']['nack_ratio']
+                testcase_result['throughput_measurements']['nr_bler_result']['total']['UL']['nack_ratio']
                 * 100
             })
         if testcase_data['testcase_params']['endc_combo_config'][
                 'lte_cell_count']:
             metric_map.update({
                 'lte_min_dl_tput':
-                testcase_result['lte_tput_result']['total']['DL']['min_tput'],
+                testcase_result['throughput_measurements']['lte_tput_result']['total']['DL']['min_tput'],
                 'lte_max_dl_tput':
-                testcase_result['lte_tput_result']['total']['DL']['max_tput'],
+                testcase_result['throughput_measurements']['lte_tput_result']['total']['DL']['max_tput'],
                 'lte_avg_dl_tput':
-                testcase_result['lte_tput_result']['total']['DL']
+                testcase_result['throughput_measurements']['lte_tput_result']['total']['DL']
                 ['average_tput'],
                 'lte_theoretical_dl_tput':
-                testcase_result['lte_tput_result']['total']['DL']
+                testcase_result['throughput_measurements']['lte_tput_result']['total']['DL']
                 ['theoretical_tput'],
                 'lte_dl_bler':
-                testcase_result['lte_bler_result']['total']['DL']['nack_ratio']
+                testcase_result['throughput_measurements']['lte_bler_result']['total']['DL']['nack_ratio']
                 * 100,
                 'lte_min_dl_tput':
-                testcase_result['lte_tput_result']['total']['UL']['min_tput'],
+                testcase_result['throughput_measurements']['lte_tput_result']['total']['UL']['min_tput'],
                 'lte_max_dl_tput':
-                testcase_result['lte_tput_result']['total']['UL']['max_tput'],
+                testcase_result['throughput_measurements']['lte_tput_result']['total']['UL']['max_tput'],
                 'lte_avg_dl_tput':
-                testcase_result['lte_tput_result']['total']['UL']
+                testcase_result['throughput_measurements']['lte_tput_result']['total']['UL']
                 ['average_tput'],
                 'lte_theoretical_dl_tput':
-                testcase_result['lte_tput_result']['total']['UL']
+                testcase_result['throughput_measurements']['lte_tput_result']['total']['UL']
                 ['theoretical_tput'],
                 'lte_ul_bler':
-                testcase_result['lte_bler_result']['total']['UL']['nack_ratio']
+                testcase_result['throughput_measurements']['lte_bler_result']['total']['UL']['nack_ratio']
                 * 100
             })
         if self.publish_testcase_metrics:
@@ -170,68 +170,68 @@
                             'endc_combo_config']['nr_cell_count']:
                         row_dict.update({
                             'NR DL Min. Throughput':
-                            result['nr_tput_result']['total']['DL']
+                            result['throughput_measurements']['nr_tput_result']['total']['DL']
                             ['min_tput'],
                             'NR DL Max. Throughput':
-                            result['nr_tput_result']['total']['DL']
+                            result['throughput_measurements']['nr_tput_result']['total']['DL']
                             ['max_tput'],
                             'NR DL Avg. Throughput':
-                            result['nr_tput_result']['total']['DL']
+                            result['throughput_measurements']['nr_tput_result']['total']['DL']
                             ['average_tput'],
                             'NR DL Theoretical Throughput':
-                            result['nr_tput_result']['total']['DL']
+                            result['throughput_measurements']['nr_tput_result']['total']['DL']
                             ['theoretical_tput'],
                             'NR UL Min. Throughput':
-                            result['nr_tput_result']['total']['UL']
+                            result['throughput_measurements']['nr_tput_result']['total']['UL']
                             ['min_tput'],
                             'NR UL Max. Throughput':
-                            result['nr_tput_result']['total']['UL']
+                            result['throughput_measurements']['nr_tput_result']['total']['UL']
                             ['max_tput'],
                             'NR UL Avg. Throughput':
-                            result['nr_tput_result']['total']['UL']
+                            result['throughput_measurements']['nr_tput_result']['total']['UL']
                             ['average_tput'],
                             'NR UL Theoretical Throughput':
-                            result['nr_tput_result']['total']['UL']
+                            result['throughput_measurements']['nr_tput_result']['total']['UL']
                             ['theoretical_tput'],
                             'NR DL BLER (%)':
-                            result['nr_bler_result']['total']['DL']
+                            result['throughput_measurements']['nr_bler_result']['total']['DL']
                             ['nack_ratio'] * 100,
                             'NR UL BLER (%)':
-                            result['nr_bler_result']['total']['UL']
+                            result['throughput_measurements']['nr_bler_result']['total']['UL']
                             ['nack_ratio'] * 100
                         })
                     if testcase_results['testcase_params'][
                             'endc_combo_config']['lte_cell_count']:
                         row_dict.update({
                             'LTE DL Min. Throughput':
-                            result['lte_tput_result']['total']['DL']
+                            result['throughput_measurements']['lte_tput_result']['total']['DL']
                             ['min_tput'],
                             'LTE DL Max. Throughput':
-                            result['lte_tput_result']['total']['DL']
+                            result['throughput_measurements']['lte_tput_result']['total']['DL']
                             ['max_tput'],
                             'LTE DL Avg. Throughput':
-                            result['lte_tput_result']['total']['DL']
+                            result['throughput_measurements']['lte_tput_result']['total']['DL']
                             ['average_tput'],
                             'LTE DL Theoretical Throughput':
-                            result['lte_tput_result']['total']['DL']
+                            result['throughput_measurements']['lte_tput_result']['total']['DL']
                             ['theoretical_tput'],
                             'LTE UL Min. Throughput':
-                            result['lte_tput_result']['total']['UL']
+                            result['throughput_measurements']['lte_tput_result']['total']['UL']
                             ['min_tput'],
                             'LTE UL Max. Throughput':
-                            result['lte_tput_result']['total']['UL']
+                            result['throughput_measurements']['lte_tput_result']['total']['UL']
                             ['max_tput'],
                             'LTE UL Avg. Throughput':
-                            result['lte_tput_result']['total']['UL']
+                            result['throughput_measurements']['lte_tput_result']['total']['UL']
                             ['average_tput'],
                             'LTE UL Theoretical Throughput':
-                            result['lte_tput_result']['total']['UL']
+                            result['throughput_measurements']['lte_tput_result']['total']['UL']
                             ['theoretical_tput'],
                             'LTE DL BLER (%)':
-                            result['lte_bler_result']['total']['DL']
+                            result['throughput_measurements']['lte_bler_result']['total']['DL']
                             ['nack_ratio'] * 100,
                             'LTE UL BLER (%)':
-                            result['lte_bler_result']['total']['UL']
+                            result['throughput_measurements']['lte_bler_result']['total']['UL']
                             ['nack_ratio'] * 100
                         })
                     writer.writerow(row_dict)
@@ -301,6 +301,7 @@
                 'NR5G',
                 'cell_number':
                 nr_cell_idx,
+                'nr_cell_type': 'NSA',
                 'band':
                 test_config['nr_band'],
                 'duplex_mode':
@@ -334,16 +335,17 @@
         endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
         endc_combo_config['cell_list'] = cell_config_list
         endc_combo_config['lte_scc_list'] = lte_scc_list
-        endc_combo_config['lte_carriers'] = lte_carriers
+        endc_combo_config['lte_dl_carriers'] = lte_carriers
+        endc_combo_config['lte_ul_carriers'] = lte_carriers
         return endc_combo_config
 
     def generate_test_cases(self, bands, channels, nr_mcs_pair_list,
-                            num_dl_cells_list, num_ul_cells_list,
+                            num_dl_cells_list, num_ul_cells_list, orientation_list,
                             dl_mimo_config, ul_mimo_config, **kwargs):
         """Function that auto-generates test cases for a test class."""
         test_cases = []
-        for band, channel, num_ul_cells, num_dl_cells, nr_mcs_pair in itertools.product(
-                bands, channels, num_ul_cells_list, num_dl_cells_list,
+        for orientation, band, channel, num_ul_cells, num_dl_cells, nr_mcs_pair in itertools.product(
+                orientation_list, bands, channels, num_ul_cells_list, num_dl_cells_list,
                 nr_mcs_pair_list):
             if num_ul_cells > num_dl_cells:
                 continue
@@ -358,6 +360,7 @@
                 'nr_band': band,
                 'nr_bandwidth': 'BW100',
                 'nr_duplex_mode': 'TDD',
+                'nr_cell_type': 'NSA',
                 'nr_channel': channel,
                 'num_dl_cells': num_dl_cells,
                 'num_ul_cells': num_ul_cells,
@@ -365,14 +368,15 @@
                 'nr_ul_mimo_config': ul_mimo_config
             }
             endc_combo_config = self.generate_endc_combo_config(test_config)
-            test_name = 'test_fr2_{}_{}_DL_{}CC_mcs{}_{}x{}_UL_{}CC_mcs{}_{}x{}'.format(
-                band, channel, num_dl_cells, nr_mcs_pair[0], dl_mimo_config,
+            test_name = 'test_fr2_{}_{}_{}_DL_{}CC_mcs{}_{}x{}_UL_{}CC_mcs{}_{}x{}'.format(
+                orientation, band, channel, num_dl_cells, nr_mcs_pair[0], dl_mimo_config,
                 dl_mimo_config, num_ul_cells, nr_mcs_pair[1], ul_mimo_config,
                 ul_mimo_config)
             test_params = collections.OrderedDict(
                 endc_combo_config=endc_combo_config,
                 nr_dl_mcs=nr_mcs_pair[0],
                 nr_ul_mcs=nr_mcs_pair[1],
+                orientation=orientation,
                 **kwargs)
             setattr(self, test_name,
                     partial(self._test_throughput_bler, test_params))
@@ -390,16 +394,18 @@
 
     def __init__(self, controllers):
         super().__init__(controllers)
-        self.testclass_params = self.user_params['throughput_test_params']
+        self.testclass_params = self.user_params['fr2_throughput_test_params']
         self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                               ['low', 'mid', 'high'],
                                               [(16, 4), (27, 4)],
                                               list(range(1, 9)),
                                               list(range(1, 3)),
+                                              ['A_Plane', 'B_Plane'],
                                               force_contiguous_nr_channel=True,
                                               dl_mimo_config=2,
                                               ul_mimo_config=1,
                                               schedule_scenario="FULL_TPUT",
+                                              schedule_slot_ratio=80,
                                               traffic_direction='DL',
                                               transform_precoding=0,
                                               lte_dl_mcs=4,
@@ -416,10 +422,12 @@
         self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                               ['low', 'mid', 'high'],
                                               [(4, 16), (4, 27)], [1], [1],
+                                              ['A_Plane', 'B_Plane'],
                                               force_contiguous_nr_channel=True,
                                               dl_mimo_config=2,
                                               ul_mimo_config=1,
                                               schedule_scenario="FULL_TPUT",
+                                              schedule_slot_ratio=80,
                                               traffic_direction='UL',
                                               transform_precoding=0,
                                               lte_dl_mcs=4,
@@ -430,10 +438,12 @@
             self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                      ['low', 'mid', 'high'],
                                      [(4, 16), (4, 27)], [1], [1],
+                                     ['A_Plane', 'B_Plane'],
                                      force_contiguous_nr_channel=True,
                                      dl_mimo_config=2,
                                      ul_mimo_config=2,
                                      schedule_scenario="FULL_TPUT",
+                                     schedule_slot_ratio=80,
                                      traffic_direction='UL',
                                      transform_precoding=0,
                                      lte_dl_mcs=4,
@@ -444,10 +454,12 @@
             self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                      ['low', 'mid', 'high'],
                                      [(4, 16), (4, 27)], [2], [2],
+                                     ['A_Plane', 'B_Plane'],
                                      force_contiguous_nr_channel=True,
                                      dl_mimo_config=2,
                                      ul_mimo_config=2,
                                      schedule_scenario="FULL_TPUT",
+                                     schedule_slot_ratio=80,
                                      traffic_direction='UL',
                                      transform_precoding=0,
                                      lte_dl_mcs=4,
@@ -458,10 +470,12 @@
             self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                      ['low', 'mid', 'high'],
                                      [(4, 16), (4, 27)], [4], [4],
+                                     ['A_Plane', 'B_Plane'],
                                      force_contiguous_nr_channel=True,
                                      dl_mimo_config=2,
                                      ul_mimo_config=2,
                                      schedule_scenario="FULL_TPUT",
+                                     schedule_slot_ratio=80,
                                      traffic_direction='UL',
                                      transform_precoding=0,
                                      lte_dl_mcs=4,
@@ -478,10 +492,12 @@
         self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                               ['low', 'mid', 'high'],
                                               [(4, 16), (4, 27)], [1], [1],
+                                              ['A_Plane', 'B_Plane'],
                                               force_contiguous_nr_channel=True,
                                               dl_mimo_config=2,
                                               ul_mimo_config=1,
                                               schedule_scenario="FULL_TPUT",
+                                              schedule_slot_ratio=80,
                                               traffic_direction='UL',
                                               transform_precoding=1,
                                               lte_dl_mcs=4,
@@ -492,10 +508,12 @@
             self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                      ['low', 'mid', 'high'],
                                      [(4, 16), (4, 27)], [1], [1],
+                                     ['A_Plane', 'B_Plane'],
                                      force_contiguous_nr_channel=True,
                                      dl_mimo_config=2,
                                      ul_mimo_config=2,
                                      schedule_scenario="FULL_TPUT",
+                                     schedule_slot_ratio=80,
                                      traffic_direction='UL',
                                      transform_precoding=1,
                                      lte_dl_mcs=4,
@@ -506,10 +524,12 @@
             self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                      ['low', 'mid', 'high'],
                                      [(4, 16), (4, 27)], [2], [2],
+                                     ['A_Plane', 'B_Plane'],
                                      force_contiguous_nr_channel=True,
                                      dl_mimo_config=2,
                                      ul_mimo_config=2,
                                      schedule_scenario="FULL_TPUT",
+                                     schedule_slot_ratio=80,
                                      traffic_direction='UL',
                                      transform_precoding=1,
                                      lte_dl_mcs=4,
@@ -520,10 +540,12 @@
             self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
                                      ['low', 'mid', 'high'],
                                      [(4, 16), (4, 27)], [4], [4],
+                                     ['A_Plane', 'B_Plane'],
                                      force_contiguous_nr_channel=True,
                                      dl_mimo_config=2,
                                      ul_mimo_config=2,
                                      schedule_scenario="FULL_TPUT",
+                                     schedule_slot_ratio=80,
                                      traffic_direction='UL',
                                      transform_precoding=1,
                                      lte_dl_mcs=4,
@@ -548,10 +570,12 @@
             ['N257', 'N258', 'N260', 'N261'],
             self.user_params['throughput_test_params']['frequency_sweep'],
             [(16, 4), (27, 4)],
+            ['A_Plane', 'B_Plane'],
             force_contiguous_nr_channel=False,
             dl_mimo_config=2,
             ul_mimo_config=1,
             schedule_scenario="FULL_TPUT",
+            schedule_slot_ratio=80,
             traffic_direction='DL',
             transform_precoding=0,
             lte_dl_mcs=4,
diff --git a/acts_tests/tests/google/cellular/performance/CellularFr2SensitivityTest.py b/acts_tests/tests/google/cellular/performance/CellularFr2SensitivityTest.py
index 4176a62..8f92667 100644
--- a/acts_tests/tests/google/cellular/performance/CellularFr2SensitivityTest.py
+++ b/acts_tests/tests/google/cellular/performance/CellularFr2SensitivityTest.py
@@ -26,12 +26,12 @@
 from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
 from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
-from CellularFr2PeakThroughputTest import CellularFr2PeakThroughputTest
+from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest
 
 from functools import partial
 
 
-class CellularFr2SensitivityTest(CellularFr2PeakThroughputTest):
+class CellularFr2SensitivityTest(CellularThroughputBaseTest):
     """Class to test single cell FR1 NSA sensitivity"""
 
     def __init__(self, controllers):
@@ -41,13 +41,14 @@
         self.testclass_metric_logger = (
             BlackboxMappedMetricLogger.for_test_class())
         self.publish_testcase_metrics = True
-        self.testclass_params = self.user_params['nr_sensitivity_test_params']
+        self.testclass_params = self.user_params['fr2_sensitivity_test_params']
         self.log.info('Hello')
         self.tests = self.generate_test_cases(
             band_list=['N257', 'N258', 'N260', 'N261'],
             channel_list=['low', 'mid', 'high'],
             dl_mcs_list=list(numpy.arange(27, -1, -1)),
             num_dl_cells_list=[1, 2, 4, 8],
+            orientation_list=['A_Plane', 'B_Plane'],
             dl_mimo_config=2,
             nr_ul_mcs=4,
             lte_dl_mcs_table='QAM256',
@@ -55,6 +56,7 @@
             lte_ul_mcs_table='QAM256',
             lte_ul_mcs=4,
             schedule_scenario="FULL_TPUT",
+            schedule_slot_ratio= 80,
             force_contiguous_nr_channel=True,
             transform_precoding=0)
 
@@ -63,8 +65,10 @@
         plots = collections.OrderedDict()
         compiled_data = collections.OrderedDict()
         for testcase_name, testcase_data in self.testclass_results.items():
+            nr_cell_index = testcase_data['testcase_params'][
+                'endc_combo_config']['lte_cell_count']
             cell_config = testcase_data['testcase_params'][
-                'endc_combo_config']['cell_list'][1]
+                'endc_combo_config']['cell_list'][nr_cell_index]
             test_id = tuple(('band', cell_config['band']))
             if test_id not in plots:
                 # Initialize test id data when not present
@@ -103,7 +107,6 @@
                 width=1,
                 style='dashed')
 
-        # Compute average RvRs and compute metrics over orientations
         for test_id, test_data in compiled_data.items():
             test_id_rvr = test_id + tuple('RvR')
             cell_power_interp = sorted(set(sum(test_data['cell_power'], [])))
@@ -126,30 +129,46 @@
         output_file_path = os.path.join(self.log_path, 'results.html')
         BokehFigure.save_figures(figure_list, output_file_path)
 
+        """Saves CSV with all test results to enable comparison."""
+        results_file_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            'results.csv')
+        with open(results_file_path, 'w', newline='') as csvfile:
+            field_names = [
+                'Test Name', 'Sensitivity'
+            ]
+            writer = csv.DictWriter(csvfile, fieldnames=field_names)
+            writer.writeheader()
+
+            for testcase_name, testcase_results in self.testclass_results.items(
+            ):
+                row_dict = {
+                    'Test Name': testcase_name,
+                    'Sensitivity': testcase_results['sensitivity']
+                }
+                writer.writerow(row_dict)
+
     def process_testcase_results(self):
         if self.current_test_name not in self.testclass_results:
             return
         testcase_data = self.testclass_results[self.current_test_name]
-        results_file_path = os.path.join(
-            context.get_current_context().get_full_output_path(),
-            '{}.json'.format(self.current_test_name))
-        with open(results_file_path, 'w') as results_file:
-            json.dump(wputils.serialize_dict(testcase_data),
-                      results_file,
-                      indent=4)
 
         bler_list = []
         average_throughput_list = []
         theoretical_throughput_list = []
+        nr_cell_index = testcase_data['testcase_params']['endc_combo_config'][
+            'lte_cell_count']
         cell_power_list = testcase_data['testcase_params']['cell_power_sweep'][
-            1]
+            nr_cell_index]
         for result in testcase_data['results']:
-            bler_list.append(
-                result['nr_bler_result']['total']['DL']['nack_ratio'])
+            bler_list.append(result['throughput_measurements']
+                             ['nr_bler_result']['total']['DL']['nack_ratio'])
             average_throughput_list.append(
-                result['nr_tput_result']['total']['DL']['average_tput'])
+                result['throughput_measurements']['nr_tput_result']['total']
+                ['DL']['average_tput'])
             theoretical_throughput_list.append(
-                result['nr_tput_result']['total']['DL']['theoretical_tput'])
+                result['throughput_measurements']['nr_tput_result']['total']
+                ['DL']['theoretical_tput'])
         padding_len = len(cell_power_list) - len(average_throughput_list)
         average_throughput_list.extend([0] * padding_len)
         theoretical_throughput_list.extend([0] * padding_len)
@@ -167,8 +186,8 @@
         sensitivity = cell_power_list[sensitivity_idx]
         self.log.info('NR Band {} MCS {} Sensitivity = {}dBm'.format(
             testcase_data['testcase_params']['endc_combo_config']['cell_list']
-            [1]['band'], testcase_data['testcase_params']['nr_dl_mcs'],
-            sensitivity))
+            [nr_cell_index]['band'],
+            testcase_data['testcase_params']['nr_dl_mcs'], sensitivity))
 
         testcase_data['bler_list'] = bler_list
         testcase_data['average_throughput_list'] = average_throughput_list
@@ -177,6 +196,14 @@
         testcase_data['cell_power_list'] = cell_power_list
         testcase_data['sensitivity'] = sensitivity
 
+        results_file_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            '{}.json'.format(self.current_test_name))
+        with open(results_file_path, 'w') as results_file:
+            json.dump(wputils.serialize_dict(testcase_data),
+                      results_file,
+                      indent=4)
+
     def get_per_cell_power_sweeps(self, testcase_params):
         # get reference test
         current_band = testcase_params['endc_combo_config']['cell_list'][1][
@@ -211,13 +238,98 @@
             [nr_cell_sweep] *
             testcase_params['endc_combo_config']['nr_cell_count'])
         return cell_power_sweeps
+    def generate_endc_combo_config(self, test_config):
+        """Function to generate ENDC combo config from CSV test config
+
+        Args:
+            test_config: dict containing ENDC combo config from CSV
+        Returns:
+            endc_combo_config: dictionary with all ENDC combo settings
+        """
+        endc_combo_config = collections.OrderedDict()
+        cell_config_list = []
+
+        lte_cell_count = 1
+        lte_carriers = [1]
+        lte_scc_list = []
+        endc_combo_config['lte_pcc'] = 1
+        lte_cell = {
+            'cell_type':
+            'LTE',
+            'cell_number':
+            1,
+            'pcc':
+            1,
+            'band':
+            test_config['lte_band'],
+            'dl_bandwidth':
+            test_config['lte_bandwidth'],
+            'ul_enabled':
+            1,
+            'duplex_mode':
+            test_config['lte_duplex_mode'],
+            'dl_mimo_config':
+            'D{nss}U{nss}'.format(nss=test_config['lte_dl_mimo_config']),
+            'ul_mimo_config':
+            'D{nss}U{nss}'.format(nss=test_config['lte_ul_mimo_config']),
+            'transmission_mode':
+            'TM1'
+        }
+        cell_config_list.append(lte_cell)
+
+        nr_cell_count = 0
+        nr_dl_carriers = []
+        nr_ul_carriers = []
+        for nr_cell_idx in range(1, test_config['num_dl_cells'] + 1):
+            nr_cell = {
+                'cell_type':
+                'NR5G',
+                'cell_number':
+                nr_cell_idx,
+                'nr_cell_type': 'NSA',
+                'band':
+                test_config['nr_band'],
+                'duplex_mode':
+                test_config['nr_duplex_mode'],
+                'channel':
+                test_config['nr_channel'],
+                'dl_mimo_config':
+                'N{nss}X{nss}'.format(nss=test_config['nr_dl_mimo_config']),
+                'dl_bandwidth_class':
+                'A',
+                'dl_bandwidth':
+                test_config['nr_bandwidth'],
+                'ul_enabled':
+                1 if nr_cell_idx <= test_config['num_ul_cells'] else 0,
+                'ul_bandwidth_class':
+                'A',
+                'ul_mimo_config':
+                'N{nss}X{nss}'.format(nss=test_config['nr_ul_mimo_config']),
+                'subcarrier_spacing':
+                'MU3'
+            }
+            cell_config_list.append(nr_cell)
+            nr_cell_count = nr_cell_count + 1
+            nr_dl_carriers.append(nr_cell_idx)
+            if nr_cell_idx <= test_config['num_ul_cells']:
+                nr_ul_carriers.append(nr_cell_idx)
+
+        endc_combo_config['lte_cell_count'] = lte_cell_count
+        endc_combo_config['nr_cell_count'] = nr_cell_count
+        endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
+        endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
+        endc_combo_config['cell_list'] = cell_config_list
+        endc_combo_config['lte_scc_list'] = lte_scc_list
+        endc_combo_config['lte_dl_carriers'] = lte_carriers
+        endc_combo_config['lte_ul_carriers'] = lte_carriers
+        return endc_combo_config
 
     def generate_test_cases(self, band_list, channel_list, dl_mcs_list,
-                            num_dl_cells_list, dl_mimo_config, **kwargs):
+                            num_dl_cells_list, dl_mimo_config, orientation_list, **kwargs):
         """Function that auto-generates test cases for a test class."""
         test_cases = []
-        for band, channel, num_dl_cells, nr_dl_mcs in itertools.product(
-                band_list, channel_list, num_dl_cells_list, dl_mcs_list):
+        for orientation, band, channel, num_dl_cells, nr_dl_mcs in itertools.product(
+                orientation_list, band_list, channel_list, num_dl_cells_list, dl_mcs_list):
             if channel not in cputils.PCC_PRESET_MAPPING[band]:
                 continue
             test_config = {
@@ -242,9 +354,9 @@
             test_params = collections.OrderedDict(
                 endc_combo_config=endc_combo_config,
                 nr_dl_mcs=nr_dl_mcs,
+                orientation=orientation,
                 **kwargs)
             setattr(self, test_name,
                     partial(self._test_throughput_bler, test_params))
             test_cases.append(test_name)
-        self.log.info(test_cases)
         return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/CellularLteFr1EndcSensitivityTest.py b/acts_tests/tests/google/cellular/performance/CellularLteFr1EndcSensitivityTest.py
new file mode 100644
index 0000000..3d347c0
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/CellularLteFr1EndcSensitivityTest.py
@@ -0,0 +1,249 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2022 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import csv
+import itertools
+import numpy
+import json
+import re
+import os
+from acts import context
+from acts import base_test
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
+from functools import partial
+
+
+class CellularLteFr1EndcSensitivityTest(CellularThroughputBaseTest):
+    """Class to test ENDC sensitivity"""
+
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+        self.testclass_params = self.user_params['endc_sensitivity_test_params']
+        self.tests = self.generate_test_cases(lte_dl_mcs_list=list(numpy.arange(27,0,-1)),
+                                              lte_dl_mcs_table='QAM256',
+                                              lte_ul_mcs_table='QAM256',
+                                              lte_ul_mcs=4,
+                                              nr_dl_mcs_list=list(numpy.arange(27,0,-1)),
+                                              nr_ul_mcs=4,
+                                              transform_precoding=0,
+                                              schedule_scenario='FULL_TPUT',
+                                              schedule_slot_ratio=80)
+
+    def process_testclass_results(self):
+        """Saves CSV with all test results to enable comparison."""
+        results_file_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            'results.csv')
+        with open(results_file_path, 'w', newline='') as csvfile:
+            field_names = [
+                'Test Name', 'Sensitivity'
+            ]
+            writer = csv.DictWriter(csvfile, fieldnames=field_names)
+            writer.writeheader()
+
+            for testcase_name, testcase_results in self.testclass_results.items(
+            ):
+                row_dict = {
+                    'Test Name': testcase_name,
+                    'Sensitivity': testcase_results['sensitivity']
+                }
+                writer.writerow(row_dict)
+
+    def process_testcase_results(self):
+        if self.current_test_name not in self.testclass_results:
+            return
+        testcase_data = self.testclass_results[self.current_test_name]
+
+        bler_list = []
+        average_throughput_list = []
+        theoretical_throughput_list = []
+        test_cell_idx = testcase_data['testcase_params']['test_cell_idx']
+        test_cell_config = testcase_data['testcase_params']['endc_combo_config']['cell_list'][test_cell_idx]
+        cell_power_list = testcase_data['testcase_params']['cell_power_sweep'][
+            test_cell_idx]
+
+        for result in testcase_data['results']:
+            if test_cell_config['cell_type'] == 'LTE':
+                bler_list.append(1-result['throughput_measurements']
+                                 ['lte_bler_result'][test_cell_config['cell_number']]['DL']['ack_ratio'])
+                average_throughput_list.append(
+                    result['throughput_measurements']['lte_tput_result'][test_cell_config['cell_number']]
+                    ['DL']['average_tput'])
+                theoretical_throughput_list.append(
+                    result['throughput_measurements']['lte_tput_result'][test_cell_config['cell_number']]
+                    ['DL']['theoretical_tput'])
+            else:
+                bler_list.append(1-result['throughput_measurements']
+                                 ['nr_bler_result'][test_cell_config['cell_number']]['DL']['ack_ratio'])
+                average_throughput_list.append(
+                    result['throughput_measurements']['nr_tput_result'][test_cell_config['cell_number']]
+                    ['DL']['average_tput'])
+                theoretical_throughput_list.append(
+                    result['throughput_measurements']['nr_tput_result'][test_cell_config['cell_number']]
+                    ['DL']['theoretical_tput'])
+        padding_len = len(cell_power_list) - len(average_throughput_list)
+        average_throughput_list.extend([0] * padding_len)
+        theoretical_throughput_list.extend([0] * padding_len)
+
+        bler_above_threshold = [
+            bler > self.testclass_params['bler_threshold']
+            for bler in bler_list
+        ]
+
+        for idx in range(len(bler_above_threshold)):
+            if all(bler_above_threshold[idx:]):
+                sensitivity_idx = max(idx, 1) - 1
+                sensitivity = cell_power_list[sensitivity_idx]
+                break
+        else:
+            sensitivity = float('nan')
+
+
+        if test_cell_config['cell_type'] == 'LTE':
+            test_mcs = testcase_data['testcase_params']['lte_dl_mcs']
+        else:
+            test_mcs = testcase_data['testcase_params']['nr_dl_mcs']
+        self.log.info('{} Band {} MCS {} Sensitivity = {}dBm'.format(
+            test_cell_config['cell_type'],
+            test_cell_config['band'],
+            test_mcs,
+            sensitivity))
+
+        testcase_data['bler_list'] = bler_list
+        testcase_data['average_throughput_list'] = average_throughput_list
+        testcase_data[
+            'theoretical_throughput_list'] = theoretical_throughput_list
+        testcase_data['cell_power_list'] = cell_power_list
+        testcase_data['sensitivity'] = sensitivity
+
+        results_file_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            '{}.json'.format(self.current_test_name))
+        with open(results_file_path, 'w') as results_file:
+            json.dump(wputils.serialize_dict(testcase_data),
+                      results_file,
+                      indent=4)
+
+    def get_per_cell_power_sweeps(self, testcase_params):
+        cell_power_sweeps = []
+        # Construct test cell sweep
+        test_cell = testcase_params['endc_combo_config']['cell_list'][testcase_params['test_cell_idx']]
+        if test_cell['cell_type'] == 'LTE':
+            test_cell_sweep = list(
+                numpy.arange(self.testclass_params['lte_cell_power_start'],
+                             self.testclass_params['lte_cell_power_stop'],
+                             self.testclass_params['lte_cell_power_step']))
+        else:
+            test_cell_sweep = list(
+                numpy.arange(self.testclass_params['nr_cell_power_start'],
+                             self.testclass_params['nr_cell_power_stop'],
+                             self.testclass_params['nr_cell_power_step']))
+
+        for cell_idx, cell_config in enumerate(testcase_params['endc_combo_config']['cell_list']):
+            if cell_idx == testcase_params['test_cell_idx']:
+                cell_power_sweeps.append(test_cell_sweep)
+            elif cell_config['cell_type'] == 'LTE':
+                lte_sweep = [self.testclass_params['lte_cell_power_start']
+                             ] * len(test_cell_sweep)
+                cell_power_sweeps.append(lte_sweep)
+            elif cell_config['cell_type'] == 'NR5G':
+                nr_sweep = [self.testclass_params['nr_cell_power_start']
+                             ] * len(test_cell_sweep)
+                cell_power_sweeps.append(nr_sweep)
+        return cell_power_sweeps
+
+    def generate_test_cases(self, lte_dl_mcs_list, lte_dl_mcs_table,
+                            lte_ul_mcs_table, lte_ul_mcs, nr_dl_mcs_list,
+                            nr_ul_mcs, **kwargs):
+        test_cases = []
+        with open(self.testclass_params['endc_combo_file'],
+                  'r') as endc_combos:
+            for endc_combo_str in endc_combos:
+                if endc_combo_str[0] == '#':
+                    continue
+                endc_combo_config = cputils.generate_endc_combo_config_from_string(
+                    endc_combo_str)
+                special_chars = '+[]=;,\n'
+                for char in special_chars:
+                    endc_combo_str = endc_combo_str.replace(char, '_')
+                endc_combo_str = endc_combo_str.replace('__', '_')
+                endc_combo_str = endc_combo_str.strip('_')
+                for cell_idx, cell_config in enumerate(endc_combo_config['cell_list']):
+                    if cell_config['cell_type'] == 'LTE':
+                        dl_mcs_list = lte_dl_mcs_list
+                    else:
+                        dl_mcs_list = nr_dl_mcs_list
+                    for dl_mcs in dl_mcs_list:
+                        test_name = 'test_sensitivity_{}_cell_{}_mcs{}'.format(
+                            endc_combo_str, cell_idx, dl_mcs)
+                        if cell_config['cell_type'] == 'LTE':
+                            test_params = collections.OrderedDict(
+                                endc_combo_config=endc_combo_config,
+                                test_cell_idx=cell_idx,
+                                lte_dl_mcs_table=lte_dl_mcs_table,
+                                lte_dl_mcs=dl_mcs,
+                                lte_ul_mcs_table=lte_ul_mcs_table,
+                                lte_ul_mcs=lte_ul_mcs,
+                                nr_dl_mcs=4,
+                                nr_ul_mcs=nr_ul_mcs,
+                                **kwargs)
+                        else:
+                            test_params = collections.OrderedDict(
+                                endc_combo_config=endc_combo_config,
+                                test_cell_idx=cell_idx,
+                                lte_dl_mcs_table=lte_dl_mcs_table,
+                                lte_dl_mcs=4,
+                                lte_ul_mcs_table=lte_ul_mcs_table,
+                                lte_ul_mcs=lte_ul_mcs,
+                                nr_dl_mcs=dl_mcs,
+                                nr_ul_mcs=nr_ul_mcs,
+                                **kwargs)
+                        setattr(self, test_name,
+                                partial(self._test_throughput_bler, test_params))
+                        test_cases.append(test_name)
+        return test_cases
+
+
+class CellularLteFr1EndcSensitivity_SampleMCS_Test(CellularLteFr1EndcSensitivityTest):
+    """Class to test single cell LTE sensitivity"""
+
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+        self.testclass_params = self.user_params['endc_sensitivity_test_params']
+        self.tests = self.generate_test_cases(lte_dl_mcs_list=[27,25,16,9],
+                                              lte_dl_mcs_table='QAM256',
+                                              lte_ul_mcs_table='QAM256',
+                                              lte_ul_mcs=4,
+                                              nr_dl_mcs_list=[27,25,16,9],
+                                              nr_ul_mcs=4,
+                                              transform_precoding=0,
+                                              schedule_scenario='FULL_TPUT',
+                                              schedule_slot_ratio=80)
\ No newline at end of file
diff --git a/acts_tests/tests/google/cellular/performance/CellularLtePlusFr1PeakThroughputTest.py b/acts_tests/tests/google/cellular/performance/CellularLtePlusFr1PeakThroughputTest.py
index 65fef93..1a90e9e 100644
--- a/acts_tests/tests/google/cellular/performance/CellularLtePlusFr1PeakThroughputTest.py
+++ b/acts_tests/tests/google/cellular/performance/CellularLtePlusFr1PeakThroughputTest.py
@@ -259,98 +259,9 @@
         self.tests = self.generate_test_cases([(27, 4), (4, 27)],
                                               lte_dl_mcs_table='QAM256',
                                               lte_ul_mcs_table='QAM256',
-                                              transform_precoding=0)
-
-    def generate_endc_combo_config(self, endc_combo_str):
-        """Function to generate ENDC combo config from combo string
-
-        Args:
-            endc_combo_str: ENDC combo descriptor (e.g. B48A[4];A[1]+N5A[2];A[1])
-        Returns:
-            endc_combo_config: dictionary with all ENDC combo settings
-        """
-        endc_combo_str = endc_combo_str.replace(' ', '')
-        endc_combo_list = endc_combo_str.split('+')
-        endc_combo_list = [combo.split(';') for combo in endc_combo_list]
-        endc_combo_config = collections.OrderedDict()
-        cell_config_list = list()
-        lte_cell_count = 0
-        nr_cell_count = 0
-        lte_scc_list = []
-        nr_dl_carriers = []
-        nr_ul_carriers = []
-        lte_carriers = []
-
-        for cell in endc_combo_list:
-            cell_config = {}
-            dl_config_str = cell[0]
-            dl_config_regex = re.compile(
-                r'(?P<cell_type>[B,N])(?P<band>[0-9]+)(?P<bandwidth_class>[A-Z])\[(?P<mimo_config>[0-9])\]'
-            )
-            dl_config_match = re.match(dl_config_regex, dl_config_str)
-            if dl_config_match.group('cell_type') == 'B':
-                cell_config['cell_type'] = 'LTE'
-                lte_cell_count = lte_cell_count + 1
-                cell_config['cell_number'] = lte_cell_count
-                if cell_config['cell_number'] == 1:
-                    cell_config['pcc'] = 1
-                    endc_combo_config['lte_pcc'] = cell_config['cell_number']
-                else:
-                    cell_config['pcc'] = 0
-                    lte_scc_list.append(cell_config['cell_number'])
-                cell_config['band'] = dl_config_match.group('band')
-                cell_config['duplex_mode'] = 'FDD' if int(
-                    cell_config['band']
-                ) in cputils.DUPLEX_MODE_TO_BAND_MAPPING['LTE'][
-                    'FDD'] else 'TDD'
-                cell_config['dl_mimo_config'] = 'D{nss}U{nss}'.format(
-                    nss=dl_config_match.group('mimo_config'))
-                if int(dl_config_match.group('mimo_config')) == 1:
-                    cell_config['transmission_mode'] = 'TM1'
-                elif int(dl_config_match.group('mimo_config')) == 2:
-                    cell_config['transmission_mode'] = 'TM2'
-                else:
-                    cell_config['transmission_mode'] = 'TM3'
-                lte_carriers.append(cell_config['cell_number'])
-            else:
-                cell_config['cell_type'] = 'NR5G'
-                nr_cell_count = nr_cell_count + 1
-                cell_config['cell_number'] = nr_cell_count
-                nr_dl_carriers.append(cell_config['cell_number']),
-                cell_config['nr_cell_type'] = 'NSA',
-                cell_config['band'] = 'N' + dl_config_match.group('band')
-                cell_config['duplex_mode'] = 'FDD' if cell_config[
-                    'band'] in cputils.DUPLEX_MODE_TO_BAND_MAPPING['NR5G'][
-                        'FDD'] else 'TDD'
-                cell_config['subcarrier_spacing'] = 'MU0' if cell_config[
-                    'duplex_mode'] == 'FDD' else 'MU1'
-                cell_config['dl_mimo_config'] = 'N{nss}X{nss}'.format(
-                    nss=dl_config_match.group('mimo_config'))
-
-            cell_config['dl_bandwidth_class'] = dl_config_match.group(
-                'bandwidth_class')
-            cell_config['dl_bandwidth'] = 'BW20'
-            cell_config['ul_enabled'] = len(cell) > 1
-            if cell_config['ul_enabled']:
-                ul_config_str = cell[1]
-                ul_config_regex = re.compile(
-                    r'(?P<bandwidth_class>[A-Z])\[(?P<mimo_config>[0-9])\]')
-                ul_config_match = re.match(ul_config_regex, ul_config_str)
-                cell_config['ul_bandwidth_class'] = ul_config_match.group(
-                    'bandwidth_class')
-                cell_config['ul_mimo_config'] = 'N{nss}X{nss}'.format(
-                    nss=ul_config_match.group('mimo_config'))
-                if cell_config['cell_type'] == 'NR5G':
-                    nr_ul_carriers.append(cell_config['cell_number'])
-            cell_config_list.append(cell_config)
-        endc_combo_config['lte_cell_count'] = lte_cell_count
-        endc_combo_config['nr_cell_count'] = nr_cell_count
-        endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
-        endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
-        endc_combo_config['cell_list'] = cell_config_list
-        endc_combo_config['lte_scc_list'] = lte_scc_list
-        endc_combo_config['lte_carriers'] = lte_carriers
-        return endc_combo_config
+                                              transform_precoding=0,
+                                              schedule_scenario='FULL_TPUT',
+                                              schedule_slot_ratio=80)
 
     def generate_test_cases(self, mcs_pair_list, **kwargs):
         test_cases = []
@@ -360,9 +271,9 @@
             for endc_combo_str in endc_combos:
                 if endc_combo_str[0] == '#':
                     continue
-                endc_combo_config = self.generate_endc_combo_config(
+                endc_combo_config = cputils.generate_endc_combo_config_from_string(
                     endc_combo_str)
-                special_chars = '+[];\n'
+                special_chars = '+[]=;,\n'
                 for char in special_chars:
                     endc_combo_str = endc_combo_str.replace(char, '_')
                 endc_combo_str = endc_combo_str.replace('__', '_')
@@ -383,100 +294,7 @@
         return test_cases
 
 
-class CellularSingleCellThroughputTest(CellularLtePlusFr1PeakThroughputTest):
-    """Base Class to test single cell LTE or LTE/FR1"""
-
-    def generate_endc_combo_config(self, test_config):
-        """Function to generate ENDC combo config from CSV test config
-
-        Args:
-            test_config: dict containing ENDC combo config from CSV
-        Returns:
-            endc_combo_config: dictionary with all ENDC combo settings
-        """
-        endc_combo_config = collections.OrderedDict()
-        lte_cell_count = 0
-        nr_cell_count = 0
-        lte_scc_list = []
-        nr_dl_carriers = []
-        nr_ul_carriers = []
-        lte_carriers = []
-
-        cell_config_list = []
-        if test_config['lte_band']:
-            lte_cell = {
-                'cell_type':
-                'LTE',
-                'cell_number':
-                1,
-                'pcc':
-                1,
-                'band':
-                test_config['lte_band'],
-                'dl_bandwidth':
-                test_config['lte_bandwidth'],
-                'ul_enabled':
-                1,
-                'duplex_mode':
-                test_config['lte_duplex_mode'],
-                'dl_mimo_config':
-                'D{nss}U{nss}'.format(nss=test_config['lte_dl_mimo_config']),
-                'ul_mimo_config':
-                'D{nss}U{nss}'.format(nss=test_config['lte_ul_mimo_config'])
-            }
-            if int(test_config['lte_dl_mimo_config']) == 1:
-                lte_cell['transmission_mode'] = 'TM1'
-            elif int(test_config['lte_dl_mimo_config']) == 2:
-                lte_cell['transmission_mode'] = 'TM2'
-            else:
-                lte_cell['transmission_mode'] = 'TM3'
-            cell_config_list.append(lte_cell)
-            endc_combo_config['lte_pcc'] = 1
-            lte_cell_count = 1
-            lte_carriers = [1]
-
-        if test_config['nr_band']:
-            nr_cell = {
-                'cell_type':
-                'NR5G',
-                'cell_number':
-                1,
-                'band':
-                test_config['nr_band'],
-                'nr_cell_type': test_config['nr_cell_type'],
-                'duplex_mode':
-                test_config['nr_duplex_mode'],
-                'dl_mimo_config':
-                'N{nss}X{nss}'.format(nss=test_config['nr_dl_mimo_config']),
-                'dl_bandwidth_class':
-                'A',
-                'dl_bandwidth':
-                test_config['nr_bandwidth'],
-                'ul_enabled':
-                1,
-                'ul_bandwidth_class':
-                'A',
-                'ul_mimo_config':
-                'N{nss}X{nss}'.format(nss=test_config['nr_ul_mimo_config']),
-                'subcarrier_spacing':
-                'MU0' if test_config['nr_scs'] == '15' else 'MU1'
-            }
-            cell_config_list.append(nr_cell)
-            nr_cell_count = 1
-            nr_dl_carriers = [1]
-            nr_ul_carriers = [1]
-
-        endc_combo_config['lte_cell_count'] = lte_cell_count
-        endc_combo_config['nr_cell_count'] = nr_cell_count
-        endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
-        endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
-        endc_combo_config['cell_list'] = cell_config_list
-        endc_combo_config['lte_scc_list'] = lte_scc_list
-        endc_combo_config['lte_carriers'] = lte_carriers
-        return endc_combo_config
-
-
-class CellularFr1SingleCellPeakThroughputTest(CellularSingleCellThroughputTest
+class CellularFr1SingleCellPeakThroughputTest(CellularLtePlusFr1PeakThroughputTest
                                               ):
     """Class to test single cell FR1 NSA mode"""
 
@@ -509,7 +327,7 @@
                     test_configs, nr_channel_list, nr_mcs_pair_list):
                 if int(test_config['skip_test']):
                     continue
-                endc_combo_config = self.generate_endc_combo_config(
+                endc_combo_config = cputils.generate_endc_combo_config_from_csv_row(
                     test_config)
                 endc_combo_config['cell_list'][endc_combo_config['lte_cell_count']]['channel'] = nr_channel
                 test_name = 'test_fr1_{}_{}_dl_mcs{}_ul_mcs{}'.format(
@@ -526,7 +344,7 @@
         return test_cases
 
 
-class CellularLteSingleCellPeakThroughputTest(CellularSingleCellThroughputTest
+class CellularLteSingleCellPeakThroughputTest(CellularLtePlusFr1PeakThroughputTest
                                               ):
     """Class to test single cell LTE"""
 
@@ -552,7 +370,7 @@
                     test_configs, lte_mcs_pair_list):
                 if int(test_config['skip_test']):
                     continue
-                endc_combo_config = self.generate_endc_combo_config(
+                endc_combo_config = cputils.generate_endc_combo_config_from_csv_row(
                     test_config)
                 test_name = 'test_lte_B{}_dl_{}_mcs{}_ul_{}_mcs{}'.format(
                     test_config['lte_band'], lte_mcs_pair[0][0],
diff --git a/acts_tests/tests/google/cellular/performance/CellularLteRvrTest.py b/acts_tests/tests/google/cellular/performance/CellularLteRvrTest.py
index d444bca..16e6e95 100644
--- a/acts_tests/tests/google/cellular/performance/CellularLteRvrTest.py
+++ b/acts_tests/tests/google/cellular/performance/CellularLteRvrTest.py
@@ -24,14 +24,15 @@
 from acts import context
 from acts import base_test
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
 from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
-from CellularLtePlusFr1PeakThroughputTest import CellularLteSingleCellPeakThroughputTest
 
 from functools import partial
 
 
-class CellularLteRvrTest(CellularLteSingleCellPeakThroughputTest):
+class CellularLteRvrTest(CellularThroughputBaseTest):
     """Class to test single cell LTE sensitivity"""
 
     def __init__(self, controllers):
@@ -41,7 +42,7 @@
         self.testclass_metric_logger = (
             BlackboxMappedMetricLogger.for_test_class())
         self.publish_testcase_metrics = True
-        self.testclass_params = self.user_params['lte_sensitivity_test_params']
+        self.testclass_params = self.user_params['lte_rvr_test_params']
         self.tests = self.generate_test_cases(lte_dl_mcs_table='QAM256',
                                               lte_ul_mcs_table='QAM256',
                                               lte_ul_mcs=4,
@@ -194,7 +195,7 @@
             for test_config in test_configs:
                 if int(test_config['skip_test']):
                     continue
-                endc_combo_config = self.generate_endc_combo_config(
+                endc_combo_config = cputils.generate_endc_combo_config_from_csv_row(
                     test_config)
                 test_name = 'test_lte_B{}_dl_{}'.format(
                     test_config['lte_band'], lte_dl_mcs_table)
diff --git a/acts_tests/tests/google/cellular/performance/CellularLteSensitivityTest.py b/acts_tests/tests/google/cellular/performance/CellularLteSensitivityTest.py
index 22e436b..f3b5afe 100644
--- a/acts_tests/tests/google/cellular/performance/CellularLteSensitivityTest.py
+++ b/acts_tests/tests/google/cellular/performance/CellularLteSensitivityTest.py
@@ -24,14 +24,14 @@
 from acts import context
 from acts import base_test
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
 from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
-from CellularLtePlusFr1PeakThroughputTest import CellularLteSingleCellPeakThroughputTest
-
 from functools import partial
 
 
-class CellularLteSensitivityTest(CellularLteSingleCellPeakThroughputTest):
+class CellularLteSensitivityTest(CellularThroughputBaseTest):
     """Class to test single cell LTE sensitivity"""
 
     def __init__(self, controllers):
@@ -100,7 +100,6 @@
                 width=1,
                 style='dashed')
 
-        # Compute average RvRs and compute metrics over orientations
         for test_id, test_data in compiled_data.items():
             test_id_rvr = test_id + tuple('RvR')
             cell_power_interp = sorted(set(sum(test_data['cell_power'], [])))
@@ -123,17 +122,29 @@
         output_file_path = os.path.join(self.log_path, 'results.html')
         BokehFigure.save_figures(figure_list, output_file_path)
 
+        """Saves CSV with all test results to enable comparison."""
+        results_file_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            'results.csv')
+        with open(results_file_path, 'w', newline='') as csvfile:
+            field_names = [
+                'Test Name', 'Sensitivity'
+            ]
+            writer = csv.DictWriter(csvfile, fieldnames=field_names)
+            writer.writeheader()
+
+            for testcase_name, testcase_results in self.testclass_results.items(
+            ):
+                row_dict = {
+                    'Test Name': testcase_name,
+                    'Sensitivity': testcase_results['sensitivity']
+                }
+                writer.writerow(row_dict)
+
     def process_testcase_results(self):
         if self.current_test_name not in self.testclass_results:
             return
         testcase_data = self.testclass_results[self.current_test_name]
-        results_file_path = os.path.join(
-            context.get_current_context().get_full_output_path(),
-            '{}.json'.format(self.current_test_name))
-        with open(results_file_path, 'w') as results_file:
-            json.dump(wputils.serialize_dict(testcase_data),
-                      results_file,
-                      indent=4)
 
         bler_list = []
         average_throughput_list = []
@@ -176,6 +187,14 @@
         testcase_data['cell_power_list'] = cell_power_list
         testcase_data['sensitivity'] = sensitivity
 
+        results_file_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            '{}.json'.format(self.current_test_name))
+        with open(results_file_path, 'w') as results_file:
+            json.dump(wputils.serialize_dict(testcase_data),
+                      results_file,
+                      indent=4)
+
     def get_per_cell_power_sweeps(self, testcase_params):
         # get reference test
         current_band = testcase_params['endc_combo_config']['cell_list'][0][
@@ -217,7 +236,7 @@
                     test_configs, dl_mcs_list):
                 if int(test_config['skip_test']):
                     continue
-                endc_combo_config = self.generate_endc_combo_config(
+                endc_combo_config = cputils.generate_endc_combo_config_from_csv_row(
                     test_config)
                 test_name = 'test_lte_B{}_dl_{}_mcs{}'.format(
                     test_config['lte_band'], lte_dl_mcs_table, lte_dl_mcs)
@@ -232,3 +251,21 @@
                         partial(self._test_throughput_bler, test_params))
                 test_cases.append(test_name)
         return test_cases
+
+
+class CellularLteSensitivity_SampleMCS_Test(CellularLteSensitivityTest):
+    """Class to test single cell LTE sensitivity"""
+
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+        self.testclass_params = self.user_params['lte_sensitivity_test_params']
+        self.tests = self.generate_test_cases(dl_mcs_list=[27,25,16,9],
+                                              lte_dl_mcs_table='QAM256',
+                                              lte_ul_mcs_table='QAM256',
+                                              lte_ul_mcs=4,
+                                              transform_precoding=0)
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiRvrTest.py b/acts_tests/tests/google/wifi/WifiRvrTest.py
index cb188fd..731807c 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTest.py
@@ -1103,13 +1103,13 @@
         WifiOtaRvrTest.__init__(self, controllers)
         self.tests = self.generate_test_cases([6], ['bw20'],
                                               list(range(0, 360, 45)), ['UDP'],
-                                              ['DL'])
+                                              ['DL', 'UL'])
         self.tests.extend(
             self.generate_test_cases([36, 149], ['bw80', 'bw160'],
-                                     list(range(0, 360, 45)), ['UDP'], ['DL']))
+                                     list(range(0, 360, 45)), ['UDP'], ['DL', 'UL']))
         self.tests.extend(
             self.generate_test_cases(['6g37'], ['bw160'],
-                                     list(range(0, 360, 45)), ['UDP'], ['DL']))
+                                     list(range(0, 360, 45)), ['UDP'], ['DL', 'UL']))
 
 class WifiOtaRvr_SingleOrientation_Test(WifiOtaRvrTest):