chore: Route composable navigation & URL reads through NavigationHelper (clear no-page-in-flow drift) (#34982)

Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
n8n-cat-bot[bot]
2026-07-27 04:50:30 +01:00
committed by GitHub
parent d93d2dea5b
commit 4e43644bba
6 changed files with 30 additions and 45 deletions
@@ -1,7 +1,7 @@
{
"version": 1,
"generated": "2026-07-17T15:21:33.743Z",
"totalViolations": 61,
"generated": "2026-07-27T03:21:27.407Z",
"totalViolations": 56,
"violations": {
"pages/AIAssistantPage.ts": [
{
@@ -217,20 +217,6 @@
"hash": "ceda398a2fc4"
}
],
"composables/CanvasComposer.ts": [
{
"rule": "no-page-in-flow",
"line": 138,
"message": "Direct page access in composable: this.n8n.page.url",
"hash": "c978979c3eac"
},
{
"rule": "no-page-in-flow",
"line": 148,
"message": "Direct page access in composable: this.n8n.page.url",
"hash": "c978979c3eac"
}
],
"composables/ExecutionsComposer.ts": [
{
"rule": "no-page-in-flow",
@@ -245,27 +231,7 @@
"hash": "ff02b5de4505"
}
],
"composables/ProjectComposer.ts": [
{
"rule": "no-page-in-flow",
"line": 51,
"message": "Direct page access in composable: this.n8n.page.url",
"hash": "378ec8b3ce94"
}
],
"composables/TestEntryComposer.ts": [
{
"rule": "no-page-in-flow",
"line": 45,
"message": "Direct page access in composable: this.n8n.page.goto",
"hash": "bdb7602d2cb2"
},
{
"rule": "no-page-in-flow",
"line": 63,
"message": "Direct page access in composable: this.n8n.page.goto",
"hash": "bdb7602d2cb2"
},
{
"rule": "no-page-in-flow",
"line": 90,
@@ -135,7 +135,7 @@ export class CanvasComposer {
* @returns The workflow URL after save
*/
async waitForWorkflowSaveAndUrl(): Promise<string> {
const isNewWorkflow = this.n8n.page.url().includes('/workflow/new');
const isNewWorkflow = this.n8n.navigate.currentUrl().includes('/workflow/new');
if (isNewWorkflow) {
await this.n8n.canvas.waitForSaveWorkflowCompleted();
@@ -145,6 +145,6 @@ export class CanvasComposer {
await this.n8n.canvas.waitForSaveWorkflowCompleted();
}
return this.n8n.page.url();
return this.n8n.navigate.currentUrl();
}
}
@@ -48,6 +48,6 @@ export class ProjectComposer {
}
extractProjectIdFromPage(beforeWord: string, afterWord: string): string {
return this.extractIdFromUrl(this.n8n.page.url(), beforeWord, afterWord);
return this.extractIdFromUrl(this.n8n.navigate.currentUrl(), beforeWord, afterWord);
}
}
@@ -42,7 +42,7 @@ export class TestEntryComposer {
const response = await this.n8n.api.projects.createProject();
const projectId = response.id;
await this.n8n.page.goto(`workflow/new?projectId=${projectId}`);
await this.n8n.navigate.toWorkflow('new', { projectId });
await this.n8n.canvas.waitForBlankCanvasReady();
return projectId;
}
@@ -60,7 +60,7 @@ export class TestEntryComposer {
*/
async fromImportedWorkflow(workflowFile: string) {
const workflowImportResult = await this.n8n.api.workflows.importWorkflowFromFile(workflowFile);
await this.n8n.page.goto(`workflow/${workflowImportResult.workflowId}`);
await this.n8n.navigate.toWorkflow(workflowImportResult.workflowId);
// Wait for the canvas loading overlay to clear and the imported nodes to
// render before returning, so tests don't interact with a canvas that is
// still covered by the full-screen loader.
@@ -88,7 +88,7 @@ export async function viewExecutionsListAsAdmin(
(r) => r.url().includes('/rest/executions') && r.status() === 200,
{ timeout: 120_000 },
);
await n8n.page.goto(`/projects/${ctx.project.id}/executions`, {
await n8n.navigate.toProjectExecutions(ctx.project.id, {
waitUntil: 'commit',
timeout: 120_000,
});
@@ -98,13 +98,27 @@ export class NavigationHelper {
* URLs:
* - New workflow: /workflow/new
* - Existing workflow: /workflow/{workflowId}
* - Project workflow: /projects/{projectId}/workflow/{workflowId}
* - New workflow in a project: /workflow/new?projectId={projectId}
*/
async toWorkflow(workflowId: string = 'new'): Promise<void> {
const url = `/workflow/${workflowId}`;
async toWorkflow(workflowId: string = 'new', options?: { projectId?: string }): Promise<void> {
let url = `/workflow/${workflowId}`;
if (options?.projectId) {
url += `?projectId=${options.projectId}`;
}
await this.page.goto(url);
}
/**
* Navigate to a project's executions list
* URL: /projects/{projectId}/executions
*/
async toProjectExecutions(
projectId: string,
options?: Parameters<Page['goto']>[1],
): Promise<void> {
await this.page.goto(`/projects/${projectId}/executions`, options);
}
/**
* Navigate to a specific execution within a workflow
* URLs:
@@ -268,4 +282,9 @@ export class NavigationHelper {
async toExternalSecrets(): Promise<void> {
await this.secretsProviderSettings.goto();
}
/** Current page URL — use instead of reaching into n8n.page.url() from flows. */
currentUrl(): string {
return this.page.url();
}
}