Skip to main content
FixturFab

Examples

Test case recipes and patterns from the pytest-f3ts Knowledge Base for hardware functional testing.

This section provides example test cases to support development of pytest-f3ts test plans. These recipes demonstrate common patterns for PCBA functional testing.

General PCBA Functional Test Flow#

Pre-Test Setup#

  • Serial Number Entry: Scan the serial number or identifying information from the device under test (DUT)
  • Fixture Interlock or Closed State: Verify the fixture is properly closed or interlock engaged

Test Plan#

  • Continuity Tests: Verify power and ground connections to detect shorts before powering the DUT
  • Pre-Programming Voltage Tests: Check voltage rails enabled by default
  • Programming: Program processors, MCUs, or programmable logic (EEPROM, FLASH, etc.)
  • Post-Programming Voltage Tests: Check additional voltage rails controlled by the processor or MCU
  • Functional Tests: Verify the DUT operates correctly through communication and response validation
  • Calibration: Perform required calibration for proper DUT operation

Post-Test Cleanup#

  • Production Firmware Programming: Program final production firmware or reset variables as needed

Serial Number Entry#

The pytest-f3ts framework delegates serial number entry handling to individual test cases, allowing flexibility in implementation. This approach accommodates both the FixturFab Test Runner (with UI dialogs) and CLI environments.

Example Implementation#

from pytest_f3ts.schemas.msg import Dialog, Notif
from pytest_f3ts.utils import log_vars
 
def test_serial_number(
    status_banner,
    user_dialog,
    serial_number,
    record_property,
):
    """Handles DUT Serial Number Scanning at start of every run.
 
    Args:
        status_banner: Status banner pytest-f3ts fixture.
        user_dialog: User dialog pytest-f3ts fixture.
        serial_number: Serial number pytest-f3ts fixture.
        record_property: Record property pytest fixture.
    """

Key Features#

  • Pattern Validation: Uses regex to enforce specific serial number formats (example: ^\d{2}[A-Z]{2}\d{6}-\d+$)
  • Retry Logic: Implements configurable maximum retry attempts before failure
  • User Feedback: Displays status banners and dialog messages through the test framework
  • Error Handling: Captures invalid formats with descriptive error messages and logs variables for troubleshooting
  • Backend Storage: Persists validated serial numbers using the serial_number.set() method

Workflow#

  1. Override status banner to indicate action required
  2. Send dialog prompting user input
  3. Validate input against defined pattern
  4. Retry on failure up to maximum attempts
  5. Store validated serial number in backend
  6. Clear status banner upon completion

Fixture Interlock or Closed State#

This test case addresses scenarios requiring the fixture to be closed before testing begins. This is particularly useful when operators need to scan device information or insert components before proceeding with tests.

Implementation Details#

The implementation uses a FixturCtrl API accessed through the fixture_api fixture. The get_fixture_close() method verifies the fixture's closed status. If not closed, a status banner displays and the system waits for closure. You can substitute this with your own closure-checking method via your test_fixture fixture.

Code Example#

def test_fixture_close(test_fixture, status_banner, fixture_api):
    """Handles testing start when fixture is closed.
 
    Args:
        test_fixture: Test fixture pytest-f3ts fixture.
        status_banner: Status banner pytest-f3ts fixture.
        fixture_api: FixturCtrl API pytest-f3ts fixture.
    """
    try:
        timeout = 10
 
        # Get fixture close status
        closed = fixture_api.get_fixture_close()
 
        # If fixture is not closed, display status banner and wait
        if not closed:
            status_banner.override(True, status=f"Close Fixture...",
                                 color="#E6B700")
 
            # Wait for fixture to be closed or timeout
            start_time = time.time()
            while not closed and (time.time() - start_time) < timeout:
                closed = fixture_api.get_fixture_close()
                time.sleep(1)
 
            assert closed, "Fixture did not close within timeout."
            status_banner.override(False)
 
    except Exception as e:
        status_banner.override(False)
        raise e

This pattern implements a polling mechanism with visual feedback and timeout protection for reliable fixture closure verification.


Continuity Test#

The continuity test case demonstrates how to detect shorts on power rails before powering up the device under test (DUT). This implementation leverages pytest's parametrization feature to test multiple power rails using a single test function.

It's best to test for shorts on all power rails prior to powering up the DUT. This preventative approach protects equipment and ensures safe testing conditions.

Code Implementation#

@pytest.mark.parametrize(
    "test_id, error_code, channel, limit_flag",
    [
        ('1.1', '110', "VBATT_SOURCE", "4V5"),
        ('1.2', '120', "5V_SOURCE", "5V0"),
    ]
)
def test_continuity(test_id, error_code, channel, limit_flag, test_fixture, test_config, record_property):
    """ 1.1: Continuity Test
 
    Args:
        test_id: Test ID.
        error_code: Error code.
        channel: Channel to test.
        limit_flag: Limit flag.
        test_fixture: Test interface pytest fixture.
        test_config: Test configuration dictionary.
        record_property: Record property pytest fixture.
    """
 
    # Disconnect the power supply from the DUT. This would cause the continuity test to fail.
    test_fixture.set_power_mux(False)
 
    # Get the continuity voltage for the specified channel
    meas = test_fixture.get_continuity_voltage(channel)
 
    # Log the measured voltage and limits
    log_vars(record_property)
 
    # Verify the measured voltage is greater than the minimum limit
    assert meas > float(test_config.user_vars[f"MIN_LIMIT_{limit_flag}"])

Test Flow#

The test isolates power, measures voltage across specified channels, logs results, and validates measurements against configured minimum thresholds.


Voltage Test#

The voltage test demonstrates how to test voltage rails on a Device Under Test (DUT) by applying power and verifying the voltage measurements against specified limits.

Process#

The implementation involves two main functions:

  • Power Setup: A test_power_DUT function establishes power delivery to the device with a specified voltage (4.5V in the example) and includes an appropriate delay for stabilization.
  • Voltage Verification: A parameterized test_voltage_rail function performs measurements across multiple voltage rails, comparing each against configured minimum and maximum limits.

Key Implementation Details#

The test retrieves limit values from test configuration using flag-based keys like "MIN_LIMIT_4V5" and "MAX_LIMIT_5V0". Measurements are obtained through test_fixture.get_daq_voltage(channel), and assertions verify that values fall within the acceptable range.

Error handling logs variables for debugging when power application or measurements fail. The parametrized approach allows testing multiple rails (VBATT_SOURCE, 5V_SOURCE, 3V3_SOURCE, 3V3_REF) with different error codes and limits in a single test function.

The assertion message provides clear failure information: measured value, expected range, and channel identification for troubleshooting voltage discrepancies.


LED Measurement#

Testing Light Emitting Diodes (LEDs) can be accomplished through voltage or optical measurement approaches.

Feasa LED Analyzers

  • Product: Feasa LED Analyzer
  • Operation: Optical fibers positioned adjacent to LEDs measure luminous intensity
  • Advantages: High accuracy, repeatability, capable of measuring multiple LEDs simultaneously
  • Disadvantages: Higher cost, fragile optical fibers requiring potential replacement

I2C Optical Sensor Breakout Boards

  • Example: ISL29125 Breakout Board
  • Operation: Sensor positioned near LED; I2C interface controls sensor and reads luminous intensity
  • Advantages: Low cost, simple setup
  • Disadvantages: Lower accuracy than Feasa analyzers, sensor fixturing challenges

USB Camera Approach

  • Equipment: Any USB camera
  • Operation: Camera captures LED images; OpenCV processes images to calculate luminous intensity
  • Advantages: Suitable for automated testing of multiple LEDs/screens, relatively inexpensive
  • Disadvantages: Lower accuracy than Feasa, requires software development, ambient lighting interference

Feasa LED Analyzer Test Case Implementation#

The test utilizes Feasa hardware from the f3ts-hardware repository. The process involves enabling LEDs, capturing readings, disabling LEDs, then verifying measurements fall within acceptable ranges.

from f3ts_hardware_utils.feasa import Feasa
 
class TestInterface:
    def __init__(self, feasa: Feasa):
        self.feasa = Feasa(FEASA_COM_PORT)
 
    def set_dut_led(self, led_en, state):
        """Enable / Disable DUT LED driver FETs"""
        mtm_exec(self.usb_stem.digital[led_en].setState, state)
 
    def capture_led_reading(self):
        """Initiate an LED reading on the Feasa Analyzer (all channels)"""
        self.feasa.capture()
 
    def get_led_rgbi(self, indicator):
        """Get LED reading results from Feasa Analyzer (particular channel)"""
        return self.feasa.get_rgbi(indicator)
 
def test_camera_active_leds(test_config, test_fixture, f3ts_assert, record_property):
    """
    3.1 Camera Active LED Tests (D1 & D2)
 
    Read the indicator reading and verify Red, Green, Blue, and Intensity readings.
    """
    logger.info("3.1 -> Camera Active LED Tests (D1 & D2)")
    error_msg = "An error occurred while testing the Camera Active LEDs"
 
    min_intensity = {"r": 250, "g": 0, "b": 0, "i": 20000}
 
    test_fixture.set_dut_led(led_en=test_fixture.CAM_LED_EN_OUT, state=1)
    test_fixture.capture_led_reading()
    test_fixture.set_dut_led(led_en=test_fixture.CAM_LED_EN_OUT, state=0)
 
    log_vars(record_property)
 
    for led_chan, led_name, error_code in zip(
        (TestInterface.CAM1, TestInterface.CAM2), ("CAM1", "CAM2"), (301, 302)
    ):
        intensity = test_fixture.get_led_rgbi(led_chan)
 
        try:
            logger.info(
                "%s -> measured led=%d (intensity) %d (red) %d (grn) %d (blu)",
                test_config.test_id,
                intensity["i"],
                intensity["r"],
                intensity["g"],
                intensity["b"],
            )
            for name, key in zip(
                ("INTENSITY", "RED", "GREEN", "BLUE"), ("i", "r", "g", "b")
            ):
                this_meas = int(intensity[key])
                this_limit = int(min_intensity[key])
                error_msg = (
                    f"Indicator {led_name}'s {name} reading not in spec, "
                    f"meas={this_meas}, min={this_limit}"
                )
 
                assert this_limit <= this_meas, error_msg
            passed = True
 
        except Exception:
            passed = False
 
        f3ts_assert(
            passed,
            test_id=test_config.test_id,
            test_name=f"test_camera_active_leds_{led_name}",
            description=f"Checking {led_name} RGB Measurement",
            min_limit=str(min_intensity),
            meas=str(intensity),
            error_msg=error_msg,
            error_code=error_code,
        )

Next Steps#

Last updated:January 25, 2025