@@ -200,3 +200,46 @@ Then('the newly added profile should still appear in the list', async ({ page })
200200 const card = page . locator ( '[data-testid="profile-card"]' ) . filter ( { hasText : newProfileName } ) ;
201201 await expect ( card ) . toBeVisible ( { timeout : testConfig . timeouts . element } ) ;
202202} ) ;
203+
204+ // The edit dialog is the tallest in the app, so it is where a dialog that
205+ // cannot scroll first hides its own Save and Cancel buttons (refs #322).
206+ function dialogBody ( page : import ( '@playwright/test' ) . Page ) {
207+ return page . getByTestId ( 'profile-edit-dialog' ) . getByTestId ( 'dialog-body' ) ;
208+ }
209+
210+ async function isWithinViewport ( locator : import ( '@playwright/test' ) . Locator ) {
211+ const box = await locator . boundingBox ( ) ;
212+ if ( ! box ) return false ;
213+ const viewport = locator . page ( ) . viewportSize ( ) ;
214+ if ( ! viewport ) throw new Error ( 'E2E: no viewport size set' ) ;
215+ return box . y >= 0 && box . y + box . height <= viewport . height ;
216+ }
217+
218+ Then ( 'the profile edit dialog should fit within the viewport' , async ( { page } ) => {
219+ expect ( await isWithinViewport ( page . getByTestId ( 'profile-edit-dialog' ) ) ) . toBe ( true ) ;
220+ } ) ;
221+
222+ Then ( 'the profile edit dialog body should be scrollable' , async ( { page } ) => {
223+ const overflow = await dialogBody ( page ) . evaluate ( ( el ) => el . scrollHeight - el . clientHeight ) ;
224+ // The fixture profile's fields are what make this dialog overflow a 375px
225+ // tall screen; if they ever stop doing so the scenario is vacuous, so this
226+ // is an assertion rather than a skip.
227+ expect ( overflow ) . toBeGreaterThan ( 0 ) ;
228+ } ) ;
229+
230+ When ( 'I scroll the profile edit dialog to the bottom' , async ( { page } ) => {
231+ await dialogBody ( page ) . evaluate ( ( el ) => {
232+ el . scrollTop = el . scrollHeight ;
233+ } ) ;
234+ } ) ;
235+
236+ Then ( 'the profile edit save button should be within the viewport' , async ( { page } ) => {
237+ await expect ( page . getByTestId ( 'profile-edit-save' ) ) . toBeVisible ( ) ;
238+ expect ( await isWithinViewport ( page . getByTestId ( 'profile-edit-save' ) ) ) . toBe ( true ) ;
239+ } ) ;
240+
241+ Then ( 'the dialog close button should be within the viewport' , async ( { page } ) => {
242+ const close = page . getByTestId ( 'profile-edit-dialog' ) . getByTestId ( 'dialog-close-button' ) ;
243+ await expect ( close ) . toBeVisible ( ) ;
244+ expect ( await isWithinViewport ( close ) ) . toBe ( true ) ;
245+ } ) ;
0 commit comments