Skip to content

Implement StepType.CustomStep #970

@htho

Description

@htho

Currently the implementation for StepType.CustomStep is missing.

Since it is a custom step, the implementation must be provided by the user.

While I can think of some approaches to this, I think the best solution is a callback property on PuppeteerRunnerExtension. We could name it executeCustomStep.

Someone who executes the runner can just provide their implementation by calling: puppeteerRunnerExtension.executeCustomStep = async (opts) => {/* do stuff... */};

Now the interesting question is: "What should opts look like?"

When I provide my implementation of executeCustomStep I should be able to do everything that all existing steps can do. The opts object should contain all parameters used by the other steps.

This leads to this API:

  executeCustomStep: (opts: {
    step: CustomStep;
    mainPage: Page;
    targetPageOrFrame: Page | Frame;
    localFrame: Frame;
    timeout: number;

    startWaitingForEvents: () => void;
    querySelectorsAll: (
      selectors: Selector[]
    ) => Promise<ElementHandle<Element>[]>;
  }) => Promise<void> = async () => {};

The first parameters are exactly the parameters used in runStepInFrame().
startWaitingForEvents is used in the other steps, so I should be able to call it, when I see fit.
querySelectorsAll is a wrapper around the querySelectorsAll function in the PuppeteerRunnerExtension.ts. It collects all returned Element handles. When the users implementation has finished, the Element handles are disposed.

This is what the changed switch/case in runStepInFrame() would look like.

      case StepType.CustomStep: {
        const { dispose, querySelectorsAll } =
          createDisposableQuerySelectorsAll(localFrame);

        try {
          await this.executeCustomStep({
            step,
            mainPage,
            targetPageOrFrame,
            localFrame,
            timeout,
            startWaitingForEvents,
            querySelectorsAll,
          });
        } finally {
          await dispose();
        }

        break;
      }

And this is how the wrapper around querySelectorsAll is implemented:

function createDisposableQuerySelectorsAll(localFrame: Frame): {
  querySelectorsAll: (
    selectors: Selector[]
  ) => Promise<ElementHandle<Element>[]>;
  dispose: () => Promise<void>;
} {
  const toDispose: ElementHandle<Element>[] = [];
  async function _querySelectorsAll(
    selectors: Selector[]
  ): Promise<ElementHandle<Element>[]> {
    const elements = await querySelectorsAll(selectors, localFrame);
    toDispose.push(...elements);
    return elements;
  }
  async function dispose(): Promise<void> {
    await Promise.all(toDispose.map((element) => element.dispose()));
  }
  return { querySelectorsAll: _querySelectorsAll, dispose };
}

What do you think?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions