diff --git a/src/Runner.ts b/src/Runner.ts index 7b1071ae..2538d7d3 100644 --- a/src/Runner.ts +++ b/src/Runner.ts @@ -21,6 +21,7 @@ import { UserFlow } from './Schema.js'; export class Runner { #flow: UserFlow; #extension: RunnerExtension; + #aborted: boolean = false; #nextStep = 0; /** @@ -31,40 +32,35 @@ export class Runner { this.#extension = extension; } + abort(): void { + this.#aborted = true; + } + /** - * @param stepIdx - Run the flow up until the step with the `stepIdx` index. + * Run all the steps in the flow + * @returns whether all the steps are run or the execution is aborted */ - async run(stepIdx?: number): Promise { - if (stepIdx === undefined) { - stepIdx = this.#flow.steps.length; - } - if (this.#nextStep === 0) { - await this.#extension.beforeAllSteps?.(this.#flow); - } - while ( - this.#nextStep < stepIdx && - this.#nextStep < this.#flow.steps.length - ) { + async run(): Promise { + this.#aborted = false; + await this.#extension.beforeAllSteps?.(this.#flow); + + let nextStep = 0; + while (nextStep < this.#flow.steps.length && !this.#aborted) { await this.#extension.beforeEachStep?.( - this.#flow.steps[this.#nextStep], - this.#flow - ); - await this.#extension.runStep( - this.#flow.steps[this.#nextStep], + this.#flow.steps[nextStep], this.#flow ); + await this.#extension.runStep(this.#flow.steps[nextStep], this.#flow); await this.#extension.afterEachStep?.( - this.#flow.steps[this.#nextStep], + this.#flow.steps[nextStep], this.#flow ); - this.#nextStep++; - } - if (this.#nextStep >= this.#flow.steps.length) { - await this.#extension.afterAllSteps?.(this.#flow); - return true; + nextStep++; } - return false; + await this.#extension.afterAllSteps?.(this.#flow); + + return nextStep >= this.#flow.steps.length; } } diff --git a/test/runner_test.ts b/test/runner_test.ts index 75798793..97b084b8 100644 --- a/test/runner_test.ts +++ b/test/runner_test.ts @@ -86,6 +86,20 @@ describe('Runner', () => { await runner.run(); }); + it('should run return true when all the steps are run', async () => { + const runner = await createRunner( + { + title: 'test', + steps: [], + }, + new PuppeteerRunnerExtension(browser, page) + ); + + const isFinished = await runner.run(); + + assert.isTrue(isFinished); + }); + it('should navigate to the right URL', async () => { const runner = await createRunner( { @@ -704,42 +718,6 @@ describe('Runner', () => { await runner.run(); }); - it('should run steps partially', async () => { - class DummyExtension implements RunnerExtension { - #log: string[] = []; - - getLog(): string { - return this.#log.join(','); - } - - async runStep(step: Step, flow: UserFlow): Promise { - this.#log.push(flow.steps.indexOf(step).toString(10)); - } - } - const extension = new DummyExtension(); - const runner = await createRunner( - { - title: 'test', - steps: [ - { type: 'customStep', name: 'step1', parameters: {} }, - { type: 'customStep', name: 'step2', parameters: {} }, - { type: 'customStep', name: 'step3', parameters: {} }, - ], - }, - extension - ); - assert.strictEqual(await runner.run(-1), false); - assert.strictEqual(extension.getLog(), ''); - assert.strictEqual(await runner.run(0), false); - assert.strictEqual(extension.getLog(), ''); - assert.strictEqual(await runner.run(1), false); - assert.strictEqual(extension.getLog(), '0'); - assert.strictEqual(await runner.run(3), true); - assert.strictEqual(extension.getLog(), '0,1,2'); - assert.strictEqual(await runner.run(), true); - assert.strictEqual(extension.getLog(), '0,1,2'); - }); - it('should run all extension hooks', async () => { class DummyExtension implements RunnerExtension { #log: string[] = []; @@ -900,4 +878,118 @@ describe('Runner', () => { 'Hovered' ); }); + + describe('abort', () => { + it('should abort execution of remaining steps', async () => { + class AbortAfterFirstStepExtension extends RunnerExtension { + ranSteps = 0; + #abortFn?: Function; + + setAbortFn(abortFn: Function) { + this.#abortFn = abortFn; + } + + async runStep(step: Step, flow: UserFlow) { + if (flow.steps.indexOf(step) === 0) { + this.#abortFn?.(); + } + + await super.runStep(step, flow); + this.ranSteps++; + } + } + const extension = new AbortAfterFirstStepExtension(); + const runner = await createRunner( + { + title: 'test', + steps: [ + { type: 'customStep', name: 'step1', parameters: {} }, + { type: 'customStep', name: 'step2', parameters: {} }, + { type: 'customStep', name: 'step3', parameters: {} }, + ], + }, + extension + ); + extension.setAbortFn(() => runner.abort()); + + await runner.run(); + + assert.strictEqual(extension.ranSteps, 1); + }); + + it('should run afterAllSteps if the execution is aborted', async () => { + class AbortAfterFirstStepExtension extends RunnerExtension { + isAfterAllStepsRan = false; + #abortFn?: Function; + + setAbortFn(abortFn: Function) { + this.#abortFn = abortFn; + } + + async runStep(step: Step, flow: UserFlow) { + if (flow.steps.indexOf(step) === 0) { + this.#abortFn?.(); + } + + await super.runStep(step, flow); + } + + async afterAllSteps() { + this.isAfterAllStepsRan = true; + } + } + const extension = new AbortAfterFirstStepExtension(); + const runner = await createRunner( + { + title: 'test', + steps: [ + { type: 'customStep', name: 'step1', parameters: {} }, + { type: 'customStep', name: 'step2', parameters: {} }, + { type: 'customStep', name: 'step3', parameters: {} }, + ], + }, + extension + ); + extension.setAbortFn(() => runner.abort()); + + await runner.run(); + + assert.isTrue(extension.isAfterAllStepsRan); + }); + + it('should return false when the execution is aborted before all the steps are executed', async () => { + class AbortAfterFirstStepExtension extends RunnerExtension { + #abortFn?: Function; + + setAbortFn(abortFn: Function) { + this.#abortFn = abortFn; + } + + async runStep(step: Step, flow: UserFlow) { + if (flow.steps.indexOf(step) === 0) { + this.#abortFn?.(); + } + + await super.runStep(step, flow); + } + } + const extension = new AbortAfterFirstStepExtension(); + const runner = await createRunner( + { + title: 'test', + steps: [ + { type: 'customStep', name: 'step1', parameters: {} }, + { type: 'customStep', name: 'step2', parameters: {} }, + { type: 'customStep', name: 'step3', parameters: {} }, + ], + }, + extension + ); + extension.setAbortFn(() => runner.abort()); + + const isFinished = await runner.run(); + + assert.isFalse(isFinished); + }); + }); });