Skip to content

Enhancement: Support Strongly-Typed Step Outcomes and Locators in Playbook Steps #47

Description

@rickcedwhat-ai

Problem Description

Currently, when sharing selectors/locators between adjacent steps (e.g. from .detect() to .prep()), the downstream step receives a loosely-typed lastOutcome object. Developers must manually cast the locator (e.g., lastOutcome.locator as Locator) and do not get autocomplete or type-safety on the outcome names:

.detect((page) => [
  { name: 'found', isSuccess: true, locator: page.getByRole('textbox', { name: 'Name' }) }
])
.prep('fill details', async (page, ctx, lastOutcome) => {
  if (lastOutcome?.name === 'found') { // No type safety or autocomplete on 'found'
    const input = lastOutcome.locator as Locator; // Requires manual cast
    await input.fill('value');
  }
})

Proposed Solution

Refactor the generic types of the Play builder class so that the list of outcomes provided in .detect() or .attempt() maps to a strongly-typed union. Downstream steps like .prep(), .attempt(), or .cleanup() should automatically infer:

  1. lastOutcome.name (restricted to the literal string names defined in the prior step).
  2. lastOutcome.locator (typed properly as Locator instead of any or unknown).

Example hypothetical signature:

class Play<
  TContext = any,
  TOutcomes extends { name: string; locator?: any } = any
> {
  detect<const TNewOutcomes extends ReadonlyArray<OutcomeDefinition>>(
    detector: (page: Page) => TNewOutcomes,
    options?: DetectOptions
  ): Play<TContext, TNewOutcomes[number]>;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    blockedBlocked by another issueplaybook-frameworkBelongs to the future RBAC/playbook library

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions