Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose runner APIs for running individual steps #215

Merged
merged 2 commits into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/PuppeteerRunnerExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ export class PuppeteerRunnerExtension extends RunnerExtension {
}
}

#getTimeoutForStep(step: Step, flow: UserFlow): number {
return step.timeout || flow.timeout || this.timeout;
#getTimeoutForStep(step: Step, flow?: UserFlow): number {
return step.timeout || flow?.timeout || this.timeout;
}

override async runStep(step: Step, flow: UserFlow): Promise<void> {
override async runStep(step: Step, flow?: UserFlow): Promise<void> {
const timeout = this.#getTimeoutForStep(step, flow);
const page = this.page;
const browser = this.browser;
Expand Down
58 changes: 40 additions & 18 deletions src/Runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@

import { PuppeteerRunnerOwningBrowserExtension } from './PuppeteerRunnerExtension.js';
import { RunnerExtension } from './RunnerExtension.js';
import { UserFlow } from './Schema.js';
import { UserFlow, Step } from './Schema.js';

async function _runStepWithHooks(
extension: RunnerExtension,
step: Step,
flow?: UserFlow
) {
await extension.beforeEachStep?.(step, flow);
await extension.runStep(step, flow);
await extension.afterEachStep?.(step, flow);
}

export class Runner {
#flow: UserFlow;
Expand All @@ -35,40 +45,52 @@ export class Runner {
this.#aborted = true;
}

async runStep(step: Step): Promise<void> {
await _runStepWithHooks(this.#extension, step);
}

/**
* Run all the steps in the flow
* @returns whether all the steps are run or the execution is aborted
*/
async run(): Promise<boolean> {
this.#aborted = false;

await this.#extension.beforeAllSteps?.(this.#flow);

let nextStepIndex = 0;
while (nextStepIndex < this.#flow.steps.length && !this.#aborted) {
const nextStep = this.#flow.steps[nextStepIndex]!;
await this.#extension.beforeEachStep?.(nextStep, this.#flow);
await this.#extension.runStep(nextStep, this.#flow);
await this.#extension.afterEachStep?.(nextStep, this.#flow);
nextStepIndex++;
if (this.#aborted) {
return false;
}

for (const step of this.#flow.steps) {
if (this.#aborted) {
await this.#extension.afterAllSteps?.(this.#flow);
return false;
}
await _runStepWithHooks(this.#extension, step, this.#flow);
}

await this.#extension.afterAllSteps?.(this.#flow);

return nextStepIndex >= this.#flow.steps.length;
return true;
}
}

export async function createRunner(
flow: UserFlow,
extension?: RunnerExtension
) {
if (!extension) {
const { default: puppeteer } = await import('puppeteer');
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();
extension = new PuppeteerRunnerOwningBrowserExtension(browser, page);
}
return new Runner(flow, extension);
return new Runner(
flow,
extension ?? (await createPuppeteerRunnerOwningBrowserExtension())
);
}

async function createPuppeteerRunnerOwningBrowserExtension() {
const { default: puppeteer } = await import('puppeteer');
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();
return new PuppeteerRunnerOwningBrowserExtension(browser, page);
}
10 changes: 5 additions & 5 deletions src/RunnerExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import { UserFlow, Step } from './Schema.js';

export class RunnerExtension {
async beforeAllSteps?(flow: UserFlow): Promise<void> {}
async afterAllSteps?(flow: UserFlow): Promise<void> {}
async beforeEachStep?(step: Step, flow: UserFlow): Promise<void> {}
async runStep(step: Step, flow: UserFlow): Promise<void> {}
async afterEachStep?(step: Step, flow: UserFlow): Promise<void> {}
async beforeAllSteps?(flow?: UserFlow): Promise<void> {}
async afterAllSteps?(flow?: UserFlow): Promise<void> {}
async beforeEachStep?(step: Step, flow?: UserFlow): Promise<void> {}
async runStep(step: Step, flow?: UserFlow): Promise<void> {}
async afterEachStep?(step: Step, flow?: UserFlow): Promise<void> {}
}
32 changes: 32 additions & 0 deletions test/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,38 @@ describe('Runner', () => {
);
});

it('should replay individual steps', async () => {
const runner = await createRunner(
{
title: 'Test Recording',
timeout: 3000,
steps: [],
},
new PuppeteerRunnerExtension(browser, page)
);
await runner.runStep({
type: StepType.Navigate as const,
url: `${HTTP_PREFIX}/main.html`,
assertedEvents: [
{
title: '',
type: AssertedEventType.Navigation,
url: `${HTTP_PREFIX}/main.html`,
},
],
});
await runner.runStep({
type: StepType.Hover as const,
selectors: [['#hover-button']],
});
assert.ok(
await page.evaluate(
() => document.getElementById('hover-button')?.textContent
),
'Hovered'
);
});

describe('abort', () => {
it('should abort execution of remaining steps', async () => {
class AbortAfterFirstStepExtension extends RunnerExtension {
Expand Down