diff --git a/api/tests/opentrons/protocol_api_integration/test_transfer_with_liquid_classes.py b/api/tests/opentrons/protocol_api_integration/test_transfer_with_liquid_classes.py index 6fe2474cfba..4cbe5463b63 100644 --- a/api/tests/opentrons/protocol_api_integration/test_transfer_with_liquid_classes.py +++ b/api/tests/opentrons/protocol_api_integration/test_transfer_with_liquid_classes.py @@ -355,3 +355,393 @@ def test_order_of_water_transfer_steps_with_no_new_tips( ] assert len(mock_manager.mock_calls) == len(expected_calls) assert mock_manager.mock_calls[2] == expected_calls[2] + + +@pytest.mark.ot3_only +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.23", "Flex")], indirect=True +) +def test_order_of_water_consolidate_steps( + simulated_protocol_context: ProtocolContext, +) -> None: + """It should run the consolidate steps without any errors. + + This test only checks that various supported configurations for a consolidation + analyze successfully. It doesn't check whether the steps are as expected. + That will be covered in analysis snapshot tests. + """ + trash = simulated_protocol_context.load_trash_bin("A3") + tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "D1" + ) + pipette_50 = simulated_protocol_context.load_instrument( + "flex_1channel_50", mount="left", tip_racks=[tiprack] + ) + nest_plate = simulated_protocol_context.load_labware( + "nest_96_wellplate_200ul_flat", "C3" + ) + arma_plate = simulated_protocol_context.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "C2" + ) + + water = simulated_protocol_context.define_liquid_class("water") + with ( + mock.patch.object( + InstrumentCore, + "load_liquid_class", + side_effect=InstrumentCore.load_liquid_class, + autospec=True, + ) as patched_load_liquid_class, + mock.patch.object( + InstrumentCore, + "pick_up_tip", + side_effect=InstrumentCore.pick_up_tip, + autospec=True, + ) as patched_pick_up_tip, + mock.patch.object( + InstrumentCore, + "aspirate_liquid_class", + side_effect=InstrumentCore.aspirate_liquid_class, + autospec=True, + ) as patched_aspirate, + mock.patch.object( + InstrumentCore, + "dispense_liquid_class", + side_effect=InstrumentCore.dispense_liquid_class, + autospec=True, + ) as patched_dispense, + mock.patch.object( + InstrumentCore, + "drop_tip_in_disposal_location", + side_effect=InstrumentCore.drop_tip_in_disposal_location, + autospec=True, + ) as patched_drop_tip, + ): + mock_manager = mock.Mock() + mock_manager.attach_mock(patched_pick_up_tip, "pick_up_tip") + mock_manager.attach_mock(patched_load_liquid_class, "load_liquid_class") + mock_manager.attach_mock(patched_aspirate, "aspirate_liquid_class") + mock_manager.attach_mock(patched_dispense, "dispense_liquid_class") + mock_manager.attach_mock(patched_drop_tip, "drop_tip_in_disposal_location") + pipette_50.consolidate_liquid( + liquid_class=water, + volume=25, + source=nest_plate.rows()[0][:2], + dest=arma_plate.wells()[0], + new_tip="once", + trash_location=trash, + ) + expected_calls = [ + mock.call.load_liquid_class( + mock.ANY, + name="water", + transfer_properties=mock.ANY, + tiprack_uri="opentrons/opentrons_flex_96_tiprack_50ul/1", + ), + mock.call.pick_up_tip( + mock.ANY, + location=mock.ANY, + well_core=mock.ANY, + presses=mock.ANY, + increment=mock.ANY, + ), + mock.call.aspirate_liquid_class( + mock.ANY, + volume=25, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=0, air_gap=0)], + ), + mock.call.aspirate_liquid_class( + mock.ANY, + volume=25, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=25, air_gap=0.1)], + ), + mock.call.dispense_liquid_class( + mock.ANY, + volume=50, + dest=mock.ANY, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=50, air_gap=0)], + add_final_air_gap=True, + trash_location=mock.ANY, + ), + mock.call.drop_tip_in_disposal_location( + mock.ANY, + disposal_location=trash, + home_after=False, + alternate_tip_drop=True, + ), + ] + assert len(mock_manager.mock_calls) == 6 + assert mock_manager.mock_calls == expected_calls + + +@pytest.mark.ot3_only +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.23", "Flex")], indirect=True +) +def test_order_of_water_consolidate_steps_larger_volume_then_tip( + simulated_protocol_context: ProtocolContext, +) -> None: + """It should run the consolidate steps without any errors. + + This test only checks that various supported configurations for a consolidation + analyze successfully. It doesn't check whether the steps are as expected. + That will be covered in analysis snapshot tests. + """ + trash = simulated_protocol_context.load_trash_bin("A3") + tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "D1" + ) + pipette_50 = simulated_protocol_context.load_instrument( + "flex_1channel_50", mount="left", tip_racks=[tiprack] + ) + nest_plate = simulated_protocol_context.load_labware( + "nest_96_wellplate_200ul_flat", "C3" + ) + arma_plate = simulated_protocol_context.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "C2" + ) + + water = simulated_protocol_context.define_liquid_class("water") + with ( + mock.patch.object( + InstrumentCore, + "load_liquid_class", + side_effect=InstrumentCore.load_liquid_class, + autospec=True, + ) as patched_load_liquid_class, + mock.patch.object( + InstrumentCore, + "pick_up_tip", + side_effect=InstrumentCore.pick_up_tip, + autospec=True, + ) as patched_pick_up_tip, + mock.patch.object( + InstrumentCore, + "aspirate_liquid_class", + side_effect=InstrumentCore.aspirate_liquid_class, + autospec=True, + ) as patched_aspirate, + mock.patch.object( + InstrumentCore, + "dispense_liquid_class", + side_effect=InstrumentCore.dispense_liquid_class, + autospec=True, + ) as patched_dispense, + mock.patch.object( + InstrumentCore, + "drop_tip_in_disposal_location", + side_effect=InstrumentCore.drop_tip_in_disposal_location, + autospec=True, + ) as patched_drop_tip, + ): + mock_manager = mock.Mock() + mock_manager.attach_mock(patched_pick_up_tip, "pick_up_tip") + mock_manager.attach_mock(patched_load_liquid_class, "load_liquid_class") + mock_manager.attach_mock(patched_aspirate, "aspirate_liquid_class") + mock_manager.attach_mock(patched_dispense, "dispense_liquid_class") + mock_manager.attach_mock(patched_drop_tip, "drop_tip_in_disposal_location") + pipette_50.consolidate_liquid( + liquid_class=water, + volume=30, + source=nest_plate.rows()[0][:2], + dest=arma_plate.wells()[0], + new_tip="always", + trash_location=trash, + ) + expected_calls = [ + mock.call.load_liquid_class( + mock.ANY, + name="water", + transfer_properties=mock.ANY, + tiprack_uri="opentrons/opentrons_flex_96_tiprack_50ul/1", + ), + mock.call.pick_up_tip( + mock.ANY, + location=mock.ANY, + well_core=mock.ANY, + presses=mock.ANY, + increment=mock.ANY, + ), + mock.call.aspirate_liquid_class( + mock.ANY, + volume=30, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=0, air_gap=0)], + ), + mock.call.dispense_liquid_class( + mock.ANY, + volume=30, + dest=mock.ANY, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=30, air_gap=0.1)], + add_final_air_gap=True, + trash_location=mock.ANY, + ), + mock.call.drop_tip_in_disposal_location( + mock.ANY, + disposal_location=trash, + home_after=False, + alternate_tip_drop=True, + ), + mock.call.pick_up_tip( + mock.ANY, + location=mock.ANY, + well_core=mock.ANY, + presses=mock.ANY, + increment=mock.ANY, + ), + mock.call.aspirate_liquid_class( + mock.ANY, + volume=30, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=0, air_gap=0)], + ), + mock.call.dispense_liquid_class( + mock.ANY, + volume=30, + dest=mock.ANY, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=30, air_gap=0.1)], + add_final_air_gap=True, + trash_location=mock.ANY, + ), + mock.call.drop_tip_in_disposal_location( + mock.ANY, + disposal_location=trash, + home_after=False, + alternate_tip_drop=True, + ), + ] + assert len(mock_manager.mock_calls) == 9 + assert mock_manager.mock_calls == expected_calls + + +@pytest.mark.ot3_only +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.23", "Flex")], indirect=True +) +def test_order_of_water_consolidate_steps_with_no_new_tips( + simulated_protocol_context: ProtocolContext, +) -> None: + """It should run the consolidate steps without any errors. + + This test only checks that various supported configurations for a consolidation + analyze successfully. It doesn't check whether the steps are as expected. + That will be covered in analysis snapshot tests. + """ + trash = simulated_protocol_context.load_trash_bin("A3") + tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "D1" + ) + pipette_50 = simulated_protocol_context.load_instrument( + "flex_1channel_50", mount="left", tip_racks=[tiprack] + ) + nest_plate = simulated_protocol_context.load_labware( + "nest_96_wellplate_200ul_flat", "C3" + ) + arma_plate = simulated_protocol_context.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "C2" + ) + + water = simulated_protocol_context.define_liquid_class("water") + pipette_50.pick_up_tip() + with ( + mock.patch.object( + InstrumentCore, + "load_liquid_class", + side_effect=InstrumentCore.load_liquid_class, + autospec=True, + ) as patched_load_liquid_class, + mock.patch.object( + InstrumentCore, + "pick_up_tip", + side_effect=InstrumentCore.pick_up_tip, + autospec=True, + ) as patched_pick_up_tip, + mock.patch.object( + InstrumentCore, + "aspirate_liquid_class", + side_effect=InstrumentCore.aspirate_liquid_class, + autospec=True, + ) as patched_aspirate, + mock.patch.object( + InstrumentCore, + "dispense_liquid_class", + side_effect=InstrumentCore.dispense_liquid_class, + autospec=True, + ) as patched_dispense, + mock.patch.object( + InstrumentCore, + "drop_tip_in_disposal_location", + side_effect=InstrumentCore.drop_tip_in_disposal_location, + autospec=True, + ) as patched_drop_tip, + ): + mock_manager = mock.Mock() + mock_manager.attach_mock(patched_pick_up_tip, "pick_up_tip") + mock_manager.attach_mock(patched_load_liquid_class, "load_liquid_class") + mock_manager.attach_mock(patched_aspirate, "aspirate_liquid_class") + mock_manager.attach_mock(patched_dispense, "dispense_liquid_class") + mock_manager.attach_mock(patched_drop_tip, "drop_tip_in_disposal_location") + pipette_50.consolidate_liquid( + liquid_class=water, + volume=25, + source=nest_plate.rows()[0][:2], + dest=arma_plate.wells()[0], + new_tip="never", + trash_location=trash, + ) + expected_calls = [ + mock.call.load_liquid_class( + mock.ANY, + name="water", + transfer_properties=mock.ANY, + tiprack_uri="opentrons/opentrons_flex_96_tiprack_50ul/1", + ), + mock.call.aspirate_liquid_class( + mock.ANY, + volume=25, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=0, air_gap=0)], + ), + mock.call.aspirate_liquid_class( + mock.ANY, + volume=25, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=25, air_gap=0.1)], + ), + mock.call.dispense_liquid_class( + mock.ANY, + volume=50, + dest=mock.ANY, + source=mock.ANY, + transfer_properties=mock.ANY, + transfer_type=TransferType.MANY_TO_ONE, + tip_contents=[LiquidAndAirGapPair(liquid=50, air_gap=0)], + add_final_air_gap=False, + trash_location=mock.ANY, + ), + ] + assert len(mock_manager.mock_calls) == 4 + assert mock_manager.mock_calls == expected_calls