class SoftlyExtension implements AfterTestExecutionCallback, TestInstancePostProcessor {

  private static final Namespace SOFTLY_EXTENSION_NAMESPACE = Namespace.create(SoftlyExtension.class);

  @Override
  public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) throws Exception {
    if (isPerClassLifeCycle(extensionContext)) {
      throw new IllegalStateException("A SoftAssertions field is not permitted in test classes with PER_CLASS life cycle as the instance would be collecting all class tests errors (instead of per test errors). "
                                      + "Consider using {@link SoftAssertionsExtension} instead which does not have such limitation.");
    }
    // this is called multiple times depending on the test hierarchy
    // we store the field against the ExtensionContext where we found it
    initSoftAssertionsField(testInstance).ifPresent(softAssertions -> getStore(extensionContext).put(SoftlyExtension.class,
                                                                                                     softAssertions));
  }

  @Override
  public void afterTestExecution(ExtensionContext extensionContext) throws Exception {<FILL_FUNCTION_BODY>}

  private static Optional<ExtensionContext> getParent(Optional<ExtensionContext> currentContext) {
    return currentContext.flatMap(ExtensionContext::getParent);
  }

  private static boolean isPerClassLifeCycle(ExtensionContext methodExtensionContext) {
    return methodExtensionContext.getTestInstanceLifecycle()
                                 .map(lifecycle -> lifecycle == PER_CLASS)
                                 .orElse(false);
  }

  private static Optional<SoftAssertions> initSoftAssertionsField(Object testInstance) throws IllegalAccessException {
    // find SoftAssertions fields in the test class hierarchy
    Collection<Field> softAssertionsFields = findFields(testInstance.getClass(),
                                                        field -> field.getType() == SoftAssertions.class,
                                                        HierarchyTraversalMode.BOTTOM_UP);
    if (softAssertionsFields.isEmpty()) return Optional.empty();
    checkTooManySoftAssertionsFields(softAssertionsFields);

    Field softAssertionsField = softAssertionsFields.iterator().next();
    softAssertionsField.setAccessible(true);
    SoftAssertions softAssertions = new SoftAssertions();
    softAssertionsField.set(testInstance, softAssertions);
    return Optional.of(softAssertions);
  }

  private static void checkTooManySoftAssertionsFields(Collection<Field> softAssertionsFields) {
    if (softAssertionsFields.size() > 1)
      throw new IllegalStateException("Only one field of type " + SoftAssertions.class.getName() + " should be defined but found "
                                      + softAssertionsFields.size() + " : " + softAssertionsFields);
  }

  private static Store getStore(ExtensionContext extensionContext) {
    return extensionContext.getStore(SOFTLY_EXTENSION_NAMESPACE);
  }
}

    SoftAssertions softAssertions = getStore(extensionContext).remove(SoftlyExtension.class, SoftAssertions.class);
    Optional<ExtensionContext> currentContext = Optional.of(extensionContext);
    // try to find SoftAssertions in the hierarchy of ExtensionContexts starting with the current one.
    while (softAssertions == null && getParent(currentContext).isPresent()) {
      softAssertions = getParent(currentContext).map(context -> getStore(context).remove(SoftlyExtension.class,
                                                                                         SoftAssertions.class))
                                                .orElse(null);
      currentContext = getParent(currentContext);
    }
    if (softAssertions == null) throw new IllegalStateException("No SoftlyExtension field found");
    softAssertions.assertAll();
  
<no_super_class>