Skip to content

fix: substitute {{viewMode}} placeholder in setup-page-script#601

Draft
MIreland wants to merge 1 commit into
storybookjs:nextfrom
MIreland:fix/viewmode-placeholder-substitution
Draft

fix: substitute {{viewMode}} placeholder in setup-page-script#601
MIreland wants to merge 1 commit into
storybookjs:nextfrom
MIreland:fix/viewmode-placeholder-substitution

Conversation

@MIreland

@MIreland MIreland commented May 12, 2026

Copy link
Copy Markdown

(LLM-powered WIP, I will clean up the PR description shortly)

Problem

setup-page-script.ts declares:

const TEST_RUNNER_VIEW_MODE: string = '{{viewMode}}';

and emits it on every story navigation:

channel.emit('setCurrentStory', { storyId, viewMode: TEST_RUNNER_VIEW_MODE });

But setupPage in setup-page.ts never replaces {{viewMode}} before injecting the script. All other placeholders ({{storybookUrl}}, {{failOnConsole}}, {{renderedEvent}}, etc.) are substituted — {{viewMode}} was simply missing from the chain.

Effect

The browser receives the literal string "{{viewMode}}" as the viewMode argument to setCurrentStory. Storybook 8+ syncs this into the iframe URL as ?viewMode=%7B%7BviewMode%7D%7D. When an unknown viewMode is set, Storybook can trigger a hard iframe navigation mid-test, which destroys Playwright's execution context and produces:

page.evaluate: Execution context was destroyed, most likely because of a navigation

This failure is intermittent (depends on timing of the navigation vs. the script evaluation) and has no obvious stack trace pointing at the root cause, making it appear as general test flakiness.

Diagnostic evidence: every setCurrentStory NAV event carried viewMode=%7B%7BviewMode%7D%7D literally; the runner's catch → resetPage() retry branch fired 2 seconds later — matching the exact retry signature in @playwright/test's jestPlaywright integration.

Fix

Add .replaceAll('{{viewMode}}', viewMode) to the substitution chain. viewMode is already computed two lines above from process.env.VIEW_MODE ?? 'story'.

-    .replaceAll('{{debugPrintLimit}}', debugPrintLimit.toString());
+    .replaceAll('{{debugPrintLimit}}', debugPrintLimit.toString())
+    .replaceAll('{{viewMode}}', viewMode);

Testing

Verified by running the test-runner against a Storybook 8 instance with VIEW_MODE=story (default). The setCurrentStory events now carry viewMode: "story" and the intermittent navigation-destroy failures no longer occur.

`setup-page-script.ts` declares `TEST_RUNNER_VIEW_MODE = '{{viewMode}}'`
and passes it to `setCurrentStory` on every navigation, but `setupPage`
in `setup-page.ts` never replaced the placeholder before injecting the
script — leaving the browser with the literal string `"{{viewMode}}"`.

When Storybook 8+ receives an unknown `viewMode` value it syncs it into
the iframe URL (`?viewMode={{viewMode}}`), which can trigger a hard
navigation that destroys Playwright's execution context mid-test,
producing intermittent "page.evaluate: Execution context was destroyed"
failures with no obvious cause.

Fix: add `.replaceAll('{{viewMode}}', viewMode)` to the substitution
chain alongside the other placeholders. `viewMode` is already computed
on the line above from `process.env.VIEW_MODE ?? 'story'`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant