11import { test , expect } from '../test-setup'
22import { createHelpers , LONG_TIMEOUT , log } from '../helpers'
3- import { E2E_USER , PASSWORD , E2E_TEST_PROJECT } from '../config'
3+ import { E2E_USER , PASSWORD } from '../config'
44import Project from '../../common/project'
55
66const PAGE_SIZE = 50
@@ -20,7 +20,6 @@ test.describe('Deep link to feature slideout', () => {
2020 const { login } = createHelpers ( page )
2121 const api = Project . api
2222
23- // Given - an authenticated API session and a project with > PAGE_SIZE features
2423 log ( 'Authenticate against the API' )
2524 const loginRes = await request . post ( `${ api } auth/login/` , {
2625 data : { email : E2E_USER , password : PASSWORD } ,
@@ -29,86 +28,93 @@ test.describe('Deep link to feature slideout', () => {
2928 const { key } = await loginRes . json ( )
3029 const headers = { Authorization : `Token ${ key } ` }
3130
32- const project = unwrap < { id : number ; name : string } > (
33- await ( await request . get ( `${ api } projects/` , { headers } ) ) . json ( ) ,
34- ) . find ( ( p ) => p . name === E2E_TEST_PROJECT ) !
35- expect ( project ) . toBeTruthy ( )
31+ // Unique names per run so the flakiness check (`E2E_REPEAT` / `/e2e N`) does
32+ // not collide with entities created by a previous iteration.
33+ const runId = Date . now ( )
3634
37- const environment = unwrap < { id : number ; name : string ; api_key : string } > (
38- await (
39- await request . get ( `${ api } environments/?project=${ project . id } ` , {
40- headers,
41- } )
42- ) . json ( ) ,
43- ) . find ( ( e ) => e . name === 'Development' ) !
44- expect ( environment ) . toBeTruthy ( )
35+ log ( 'Create a dedicated project and environment' )
36+ // The seeded organisation from e2e_seed_data.py; other orgs may appear
37+ // concurrently (e.g. the versioning test creates one), so match by name.
38+ const organisation = unwrap < { id : number ; name : string } > (
39+ await ( await request . get ( `${ api } organisations/` , { headers } ) ) . json ( ) ,
40+ ) . find ( ( o ) => o . name === 'Bullet Train Ltd' ) !
41+ expect ( organisation ) . toBeTruthy ( )
42+
43+ // The seeded org is at its subscription's project cap; the E2E auth token
44+ // header marks the request as E2E so the cap check is bypassed.
45+ const e2eToken =
46+ process . env . E2E_TEST_TOKEN ??
47+ process . env [ `E2E_TEST_TOKEN_${ Project . env . toUpperCase ( ) } ` ] ??
48+ ''
49+ const projectRes = await request . post ( `${ api } projects/` , {
50+ data : {
51+ name : `Deep Link Project ${ runId } ` ,
52+ organisation : organisation . id ,
53+ } ,
54+ headers : { ...headers , 'X-E2E-Test-Auth-Token' : e2eToken . trim ( ) } ,
55+ } )
56+ expect ( projectRes . ok ( ) ) . toBeTruthy ( )
57+ const project = ( await projectRes . json ( ) ) as { id : number }
58+
59+ const environmentRes = await request . post ( `${ api } environments/` , {
60+ data : { name : 'Development' , project : project . id } ,
61+ headers,
62+ } )
63+ expect ( environmentRes . ok ( ) ) . toBeTruthy ( )
64+ const environment = ( await environmentRes . json ( ) ) as {
65+ id : number
66+ api_key : string
67+ }
4568
4669 log ( `Create ${ FEATURE_COUNT } features` )
47- // Unique names per run so the flakiness check (`E2E_REPEAT` / `/e2e N`) does
48- // not collide with features created by a previous iteration.
49- const runId = Date . now ( )
5070 const featureName = ( i : number ) =>
5171 `${ FEATURE_PREFIX } ${ runId } _${ String ( i ) . padStart ( 3 , '0' ) } `
52- const createdFeatureIds : number [ ] = [ ]
53-
54- try {
55- const created = await Promise . all (
56- Array . from ( { length : FEATURE_COUNT } , ( _ , i ) =>
57- request . post ( `${ api } projects/${ project . id } /features/` , {
58- data : { name : featureName ( i ) } ,
59- headers,
60- } ) ,
61- ) ,
62- )
63- for ( const res of created ) {
64- expect ( res . ok ( ) ) . toBeTruthy ( )
65- createdFeatureIds . push ( ( await res . json ( ) ) . id )
66- }
72+ const created = await Promise . all (
73+ Array . from ( { length : FEATURE_COUNT } , ( _ , i ) =>
74+ request . post ( `${ api } projects/${ project . id } /features/` , {
75+ data : { name : featureName ( i ) } ,
76+ headers,
77+ } ) ,
78+ ) ,
79+ )
80+ for ( const res of created ) {
81+ expect ( res . ok ( ) ) . toBeTruthy ( )
82+ }
6783
68- // The list renders sorted by name ascending, so page 1 holds the first
69- // PAGE_SIZE features and a feature on page 2 never mounts a row on page 1.
70- const listUrl = ( pageNumber : number ) =>
71- `${ api } projects/${ project . id } /features/?environment=${ environment . id } &page=${ pageNumber } &page_size=${ PAGE_SIZE } &sort_field=name&sort_direction=ASC`
72- const page1 = await ( await request . get ( listUrl ( 1 ) , { headers } ) ) . json ( )
73- const page2 = await ( await request . get ( listUrl ( 2 ) , { headers } ) ) . json ( )
74- const onPageFeature = page1 . results [ 0 ] as { id : number ; name : string }
75- const offPageFeature = page2 . results [ 0 ] as { id : number ; name : string }
76- expect ( onPageFeature ) . toBeTruthy ( )
77- expect ( offPageFeature ) . toBeTruthy ( )
78- log ( `On-page: ${ onPageFeature . name } , off-page: ${ offPageFeature . name } ` )
84+ // The list renders sorted by name ascending, so page 1 holds the first
85+ // PAGE_SIZE features and a feature on page 2 never mounts a row on page 1.
86+ const listUrl = ( pageNumber : number ) =>
87+ `${ api } projects/${ project . id } /features/?environment=${ environment . id } &page=${ pageNumber } &page_size=${ PAGE_SIZE } &sort_field=name&sort_direction=ASC`
88+ const page1 = await ( await request . get ( listUrl ( 1 ) , { headers } ) ) . json ( )
89+ const page2 = await ( await request . get ( listUrl ( 2 ) , { headers } ) ) . json ( )
90+ const onPageFeature = page1 . results [ 0 ] as { id : number ; name : string }
91+ const offPageFeature = page2 . results [ 0 ] as { id : number ; name : string }
92+ expect ( onPageFeature ) . toBeTruthy ( )
93+ expect ( offPageFeature ) . toBeTruthy ( )
94+ log ( `On-page: ${ onPageFeature . name } , off-page: ${ offPageFeature . name } ` )
7995
80- await login ( E2E_USER , PASSWORD )
81- const featuresPath = `/project/${ project . id } /environment/${ environment . api_key } /features`
82- const slideout = page . locator ( '.create-feature-modal' )
96+ await login ( E2E_USER , PASSWORD )
97+ const featuresPath = `/project/${ project . id } /environment/${ environment . api_key } /features`
98+ const slideout = page . locator ( '.create-feature-modal' )
8399
84- // When/Then - this is the #7652 regression: a deep link to a feature that
85- // is NOT on the first page previously rendered the list without opening
86- // any modal, because the deep-link handler only fired for mounted rows.
87- await page . goto ( `${ featuresPath } ?feature=${ offPageFeature . id } &tab=value` )
88- await expect ( slideout ) . toBeVisible ( { timeout : LONG_TIMEOUT } )
89- await expect ( slideout ) . toContainText ( offPageFeature . name )
100+ // When/Then - this is the #7652 regression: a deep link to a feature that
101+ // is NOT on the first page previously rendered the list without opening
102+ // any modal, because the deep-link handler only fired for mounted rows.
103+ await page . goto ( `${ featuresPath } ?feature=${ offPageFeature . id } &tab=value` )
104+ await expect ( slideout ) . toBeVisible ( { timeout : LONG_TIMEOUT } )
105+ await expect ( slideout ) . toContainText ( offPageFeature . name )
90106
91- // And - the existing on-page deep link still works (no regression). A
92- // fresh navigation reloads the page, dismissing the previous slideout.
93- await page . goto ( `${ featuresPath } ?feature=${ onPageFeature . id } &tab=value` )
94- await expect ( slideout ) . toBeVisible ( { timeout : LONG_TIMEOUT } )
95- await expect ( slideout ) . toContainText ( onPageFeature . name )
107+ // And - the existing on-page deep link still works (no regression). A
108+ // fresh navigation reloads the page, dismissing the previous slideout.
109+ await page . goto ( `${ featuresPath } ?feature=${ onPageFeature . id } &tab=value` )
110+ await expect ( slideout ) . toBeVisible ( { timeout : LONG_TIMEOUT } )
111+ await expect ( slideout ) . toContainText ( onPageFeature . name )
96112
97- // And - an unknown feature id degrades gracefully (no slideout, no crash).
98- await page . goto ( `${ featuresPath } ?feature=999999999&tab=value` )
99- await expect ( page . locator ( '[data-test="features-page"]' ) ) . toBeVisible ( {
100- timeout : LONG_TIMEOUT ,
101- } )
102- await expect ( slideout ) . toBeHidden ( )
103- } finally {
104- // Clean up so reruns against a persistent project don't accumulate rows.
105- await Promise . all (
106- createdFeatureIds . map ( ( id ) =>
107- request . delete ( `${ api } projects/${ project . id } /features/${ id } /` , {
108- headers,
109- } ) ,
110- ) ,
111- )
112- }
113+ // And - an unknown feature id degrades gracefully (no slideout, no crash).
114+ await page . goto ( `${ featuresPath } ?feature=999999999&tab=value` )
115+ await expect ( page . locator ( '[data-test="features-page"]' ) ) . toBeVisible ( {
116+ timeout : LONG_TIMEOUT ,
117+ } )
118+ await expect ( slideout ) . toBeHidden ( )
113119 } )
114120} )
0 commit comments