diff --git a/packages/testing/playwright/composables/WorkflowComposer.ts b/packages/testing/playwright/composables/WorkflowComposer.ts index 728fb84918e..0f60b285b7d 100644 --- a/packages/testing/playwright/composables/WorkflowComposer.ts +++ b/packages/testing/playwright/composables/WorkflowComposer.ts @@ -108,7 +108,15 @@ export class WorkflowComposer { await this.selectFolderInMoveModal(folder); } + // The list only refetches after the transfer has completed. Waiting for the + // request keeps the next navigation from reading the pre-move state. + const transferred = this.n8n.page.waitForResponse( + (response) => + response.request().method() === 'PUT' && + /\/rest\/workflows\/[^/]+\/transfer$/.test(response.url()), + ); await this.n8n.resourceMoveModal.clickConfirmMoveButton(); + expect((await transferred).ok()).toBe(true); } private async selectProjectInMoveModal(projectNameOrEmail: string): Promise { diff --git a/packages/testing/playwright/pages/SidebarPage.ts b/packages/testing/playwright/pages/SidebarPage.ts index e2444dfd706..05bc6d5c767 100644 --- a/packages/testing/playwright/pages/SidebarPage.ts +++ b/packages/testing/playwright/pages/SidebarPage.ts @@ -2,6 +2,9 @@ import { expect, type Locator } from '@playwright/test'; import { BasePage } from './BasePage'; +/** Any page of a project: /projects//workflows, /projects//credentials, ... */ +const PROJECT_ROUTE = /\/projects\/[^/]+\//; + export class SidebarPage extends BasePage { get container() { return this.page.locator('#side-menu'); @@ -36,8 +39,15 @@ export class SidebarPage extends BasePage { await this.container.getByTestId('project-home-menu-item').click(); } + /** + * Both project clicks wait for the project route. A click issued while a list + * view is still initializing can be cancelled by that view's own + * `router.replace` of its query string; then the page stays where it was and + * the caller's assertions run against the wrong list. + */ async clickPersonalMenuItem() { await this.container.getByTestId('project-personal-menu-item').click(); + await this.page.waitForURL(PROJECT_ROUTE); } async clickWorkflowsLink(): Promise { @@ -92,6 +102,7 @@ export class SidebarPage extends BasePage { async clickProjectMenuItem(projectName: string) { await this.expand(); await this.getProjectMenuItems().filter({ hasText: projectName }).click(); + await this.page.waitForURL(PROJECT_ROUTE); } getSettings(): Locator { diff --git a/packages/testing/playwright/tests/e2e/projects/projects-move-resources.spec.ts b/packages/testing/playwright/tests/e2e/projects/projects-move-resources.spec.ts index 466e62f9525..df0e3e44e5c 100644 --- a/packages/testing/playwright/tests/e2e/projects/projects-move-resources.spec.ts +++ b/packages/testing/playwright/tests/e2e/projects/projects-move-resources.spec.ts @@ -63,6 +63,11 @@ test.describe( // Navigate to home to load sidebar with new projects await n8n.goHome(); + // The Overview lists the three workflows created above. Waiting for them + // lets the view finish its first initialization, which ends with a + // `router.replace` of the query string. A sidebar click that lands during + // that replace is cancelled and the page stays on the Overview. + await expect(n8n.workflows.cards.getWorkflows()).toHaveCount(3); }); test('should move the workflow to expected projects @auth:owner', async ({ n8n }) => {