@Override
    public void run() {
        try { // make sure we call afterRun() even on crashes
            // and operate countdown latches, else we may hang the parallel runner
            if (steps == null) {
                beforeRun();
            }
            if (skipped) {
                return;
            }
            int count = steps.size();
            int index = 0;
            while ((index = nextStepIndex()) < count) {
                currentStep = steps.get(index);
                execute(currentStep);
                if (currentStepResult != null) { // can be null if debug step-back or hook skip
                    result.addStepResult(currentStepResult);
                }
            }
        } catch (Exception e) {
            if (currentStepResult != null) {
                result.addStepResult(currentStepResult);
            }
            logError("scenario [run] failed\n" + StringUtils.throwableToString(e));
            currentStepResult = result.addFakeStepResult("scenario [run] failed", e);
        } finally {
            if (!skipped) {
                afterRun();
                if (isFailed() && engine.getConfig().isAbortSuiteOnFailure()) {
                    featureRuntime.suite.abort();
                }
            }
            if (caller.isNone()) {
                logAppender.close(); // reclaim memory
            }
        }
    }
@Test
    void testJavaInteropBase64() {
        run(
                "def Base64 = Java.type('java.util.Base64')",
                "def res = Base64.encoder.encodeToString('hello'.getBytes())"
        );
        matchVar("res", java.util.Base64.getEncoder().encodeToString("hello".getBytes()));
    }