diff --git a/src/cli/callbacks/ci-mode/handler.ts b/src/cli/callbacks/ci-mode/handler.ts index b0cfb57..439ad04 100644 --- a/src/cli/callbacks/ci-mode/handler.ts +++ b/src/cli/callbacks/ci-mode/handler.ts @@ -19,6 +19,8 @@ function formatAction(action: Action): string { return `${action.type} ${chalk.gray(action.selector)}`; case "navigate": return `navigate ${chalk.gray(action.url)}`; + case "waitForNavigation": + return `wait for navigation`; case "screenshot": return `screenshot ${chalk.gray(action.name)}`; default: { diff --git a/src/cli/callbacks/existing-story/handler.ts b/src/cli/callbacks/existing-story/handler.ts index 44af340..522103d 100644 --- a/src/cli/callbacks/existing-story/handler.ts +++ b/src/cli/callbacks/existing-story/handler.ts @@ -18,6 +18,8 @@ function formatAction(action: Action): string { return `${action.type} ${chalk.gray(action.selector)}`; case "navigate": return `navigate ${chalk.gray(action.url)}`; + case "waitForNavigation": + return `wait for navigation`; case "screenshot": return `screenshot ${chalk.gray(action.name)}`; default: { diff --git a/src/cli/callbacks/run-multiple-stories/handler.ts b/src/cli/callbacks/run-multiple-stories/handler.ts index 1d92a34..912d737 100644 --- a/src/cli/callbacks/run-multiple-stories/handler.ts +++ b/src/cli/callbacks/run-multiple-stories/handler.ts @@ -18,6 +18,8 @@ function formatAction(action: Action): string { return `${action.type} ${chalk.gray(action.selector)}`; case "navigate": return `navigate ${chalk.gray(action.url)}`; + case "waitForNavigation": + return `wait for navigation`; case "screenshot": return `screenshot ${chalk.gray(action.name)}`; default: { diff --git a/src/core/types.ts b/src/core/types.ts index fbb74c0..1d9eef2 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -46,6 +46,10 @@ export interface NavigateAction extends BaseAction { url: string; } +export interface WaitForNavigationAction extends BaseAction { + type: "waitForNavigation"; +} + export interface ScreenshotAction extends BaseAction { type: "screenshot"; name: string; @@ -57,6 +61,7 @@ export type Action = | SelectAction | CheckAction | NavigateAction + | WaitForNavigationAction | ScreenshotAction; export interface Story { diff --git a/src/recorder/event-capture.ts b/src/recorder/event-capture.ts index 2ce5547..b9b2aaa 100644 --- a/src/recorder/event-capture.ts +++ b/src/recorder/event-capture.ts @@ -31,6 +31,7 @@ export class EventCapture { private pendingActions: Action[] = []; private inputTracking = new Map(); private lastUrl = ""; + private lastInteractionTime = 0; constructor(page: Page) { this.page = page; @@ -61,10 +62,22 @@ export class EventCapture { if (frame === this.page.mainFrame()) { const newUrl = this.page.url(); if (newUrl !== this.lastUrl) { - this.pendingActions.push({ - type: "navigate", - url: newUrl, - }); + // Check if navigation occurred within 1 second of last interaction + const timeSinceInteraction = Date.now() - this.lastInteractionTime; + + if (timeSinceInteraction <= 1000) { + // Automatic redirect (likely caused by previous action) + this.pendingActions.push({ + type: "waitForNavigation", + }); + } else { + // Manual navigation + this.pendingActions.push({ + type: "navigate", + url: newUrl, + }); + } + this.lastUrl = newUrl; // Re-inject listeners after navigation @@ -89,6 +102,7 @@ export class EventCapture { switch (event.type) { case "click": if (event.selector) { + this.lastInteractionTime = Date.now(); this.pendingActions.push({ type: "click", selector: event.selector, @@ -105,6 +119,7 @@ export class EventCapture { case "change": if (event.selector) { + this.lastInteractionTime = Date.now(); if (event.inputType === "select" && event.value !== undefined) { this.pendingActions.push({ type: "select", diff --git a/src/runner/action-executor.ts b/src/runner/action-executor.ts index 0e8f718..4c44602 100644 --- a/src/runner/action-executor.ts +++ b/src/runner/action-executor.ts @@ -113,6 +113,18 @@ export async function executeAction(page: Page, action: Action): Promise { break; } + case "waitForNavigation": { + // Wait for navigation that was triggered by a previous action + // This is used for automatic redirects (e.g., post-login redirects) + // The previous action (click/input) already triggered the navigation + // We just need to wait for it to complete to preserve cookies + await page.waitForNavigation({ + waitUntil: "networkidle2", + timeout: DEFAULT_TIMEOUT, + }); + break; + } + case "screenshot": { // Screenshot actions are handled externally by the runner // This should not be called directly through executeAction