@@ -6,6 +6,7 @@ import { openAddMcpServerModal } from "../../../../helpers/mcp/open-add-mcp-serv
66import { zoomOut } from "../../../../helpers/ui/zoom-out" ;
77import { getAuthToken } from "../../../../helpers/auth/get-auth-token" ;
88import { deleteFlow } from "../../../../helpers/flows/delete-flow" ;
9+ import { openFlowById } from "../../../../helpers/flows/open-flow-by-id" ;
910
1011/**
1112 * Add-MCP-Server modal: stdio / HTTP registration, field persistence and tool
@@ -790,7 +791,48 @@ test(
790791 await page . waitForSelector ( '[data-testid="blank-flow"]' , {
791792 timeout : 30000 ,
792793 } ) ;
794+ // The flow under test has to be addressed by id from here on (#1340), and
795+ // the id has to satisfy BOTH sources — neither alone is enough here:
796+ //
797+ // - `page.url()` alone is the documented trap. `awaitBootstrapTest` reaches
798+ // the templates modal through "New Flow", so before the blank-flow
799+ // navigation the URL still carries the bootstrap PLACEHOLDER — the flow
800+ // Langflow deletes the moment the modal navigates elsewhere, and the one
801+ // authoring-conventions Pattern A warns about (#681/#505).
802+ // - the tracked `POST /flows` 201 ids alone do not say which flow the editor
803+ // ended up on: this page creates the placeholder AND the blank flow, so
804+ // picking one means trusting arrival order of two async body reads, and
805+ // the wrong pick is precisely the id that gets deleted.
806+ //
807+ // So: poll until the editor's URL carries an id this page is known to have
808+ // created and that is not the placeholder. A transient or client-only id
809+ // cannot satisfy the membership test, and a blank-flow click that never
810+ // navigates fails HERE, naming the cause, instead of surfacing later as an
811+ // unattributed timeout. Measured on nightly 1.12.0.dev18: the click issues
812+ // its own `POST /flows` 201 and the URL changes every time (5/5) — the
813+ // placeholder is never reused — so this is about attribution, not a defect.
814+ const placeholderId = new URL ( page . url ( ) ) . pathname . match (
815+ / \/ f l o w \/ ( [ 0 - 9 a - f - ] { 36 } ) / ,
816+ ) ?. [ 1 ] ;
793817 await page . getByTestId ( "blank-flow" ) . click ( ) ;
818+ const editorFlowId = ( ) =>
819+ new URL ( page . url ( ) ) . pathname . match ( / \/ f l o w \/ ( [ 0 - 9 a - f - ] { 36 } ) / ) ?. [ 1 ] ;
820+ await expect
821+ . poll (
822+ ( ) => {
823+ const id = editorFlowId ( ) ;
824+ return ! ! id && id !== placeholderId && createdFlowIds . includes ( id ) ;
825+ } ,
826+ {
827+ timeout : 30000 ,
828+ message :
829+ "the blank-flow click never landed the editor on a newly created " +
830+ "flow: the URL still holds the bootstrap placeholder, or its id is " +
831+ "not among this page's POST /api/v1/flows 201 responses" ,
832+ } ,
833+ )
834+ . toBe ( true ) ;
835+ const flowUnderTest = editorFlowId ( ) ! ;
794836 await page . getByTestId ( "sidebar-nav-mcp" ) . click ( ) ;
795837 await page . waitForSelector (
796838 '[data-testid="add-component-button-lf-starter_project"]' ,
@@ -955,17 +997,16 @@ test(
955997
956998 await awaitBootstrapTest ( page , { skipModal : true } ) ;
957999
958- // The /flows a11y refactor (Langflow #13891) makes `flow-name-div`
959- // `pointer-events-none`; open the flow via the card's overlay button.
960- const flowOpenButton = page
961- . getByTestId ( "list-card" )
962- . filter ( {
963- has : page . getByTestId ( "flow-name-div" ) . filter ( { hasText : "New Flow" } ) ,
964- } )
965- . getByTestId ( "list-card-open-button" )
966- . first ( ) ;
967- await flowOpenButton . waitFor ( { state : "visible" , timeout : 10000 } ) ;
968- await flowOpenButton . click ( ) ;
1000+ // By id, never a name-filtered `list-card` + `.first()` (#1340). Langflow
1001+ // names every blank flow "New Flow"/"New Flow (N)", so under `fullyParallel`
1002+ // that filter resolves whichever card the shared project's list puts first.
1003+ // Measured on nightly 1.12.0.dev18: seeding ONE competing "New Flow …" in
1004+ // this project before the list fetch is enough — the click opened the
1005+ // competitor, and the test then died on the `text="MCP Tools"` wait below,
1006+ // blaming the node for a flow it was never in. `openFlowById` also seeds the
1007+ // assistant-onboarding flag and gates on the flow being writable, which the
1008+ // card click never did (#1214/#1005).
1009+ await openFlowById ( page , flowUnderTest ) ;
9691010
9701011 // Wait for the MCP Tools component to be visible on canvas
9711012 await page . waitForSelector ( 'text="MCP Tools"' , {
@@ -1076,16 +1117,8 @@ test(
10761117
10771118 await awaitBootstrapTest ( page , { skipModal : true } ) ;
10781119
1079- // See note above: open the flow via the card's overlay button.
1080- const flowOpenButton2 = page
1081- . getByTestId ( "list-card" )
1082- . filter ( {
1083- has : page . getByTestId ( "flow-name-div" ) . filter ( { hasText : "New Flow" } ) ,
1084- } )
1085- . getByTestId ( "list-card-open-button" )
1086- . first ( ) ;
1087- await flowOpenButton2 . waitFor ( { state : "visible" , timeout : 10000 } ) ;
1088- await flowOpenButton2 . click ( ) ;
1120+ // See note above: by id, not by name.
1121+ await openFlowById ( page , flowUnderTest ) ;
10891122
10901123 // Wait for the MCP Tools component to be visible on canvas
10911124 await page . waitForSelector ( 'text="MCP Tools"' , {
0 commit comments