11import { expect , Locator , Page } from '@playwright/test'
22import fs from 'fs'
33import path from 'path'
4+ import { isCLI } from '../utils/is-cli'
5+ import { resetLocalStorage } from '../utils/reset-local-storage'
46import { test } from '../utils/test'
57import { toUrl } from '../utils/to-url'
68import {
79 waitForApiResponse ,
810 waitForGridDataToLoad ,
911 waitForTableToLoad ,
1012} from '../utils/wait-for-response'
11- import { resetLocalStorage } from '../utils/reset-local-storage'
12- import { isCLI } from '../utils/is-cli'
1313import { waitForApiResponseWithTimeout } from '../utils/wait-for-response-with-timeout'
1414
1515const tableNamePrefix = 'pw_table'
@@ -45,37 +45,46 @@ const createTable = async (page: Page, ref: string, tableName: string) => {
4545 const nameInput = page . getByTestId ( 'table-name-input' )
4646 await expect ( nameInput ) . toBeVisible ( )
4747 await nameInput . fill ( tableName )
48+ await expect ( nameInput ) . toHaveValue ( tableName )
4849 await page . getByTestId ( 'created_at-extra-options' ) . click ( )
49- await page . getByText ( ' Is Nullable') . click ( )
50+ await page . getByRole ( 'checkbox' , { name : ' Is Nullable' } ) . click ( )
5051 await page . getByTestId ( 'created_at-extra-options' ) . click ( { force : true } )
5152 await page . getByRole ( 'button' , { name : 'Add column' } ) . click ( )
5253 await page . getByRole ( 'textbox' , { name : 'column_name' } ) . fill ( columnName )
5354 await page . getByText ( 'Choose a column type...' ) . click ( )
5455 await page . getByRole ( 'option' , { name : 'text Variable-length' } ) . click ( )
55- await page . getByRole ( 'button' , { name : 'Save' } ) . click ( )
56+ const createTablePromise = waitForApiResponseWithTimeout ( page , ( response ) =>
57+ response . url ( ) . includes ( 'query?key=table-create' )
58+ )
5659 // Wait specifically for tables list refresh instead of generic networkidle
57- await waitForApiResponseWithTimeout ( page , ( response ) =>
60+ const tablesPromise = waitForApiResponseWithTimeout ( page , ( response ) =>
5861 response . url ( ) . includes ( 'tables?include_columns=true&included_schemas=public' )
5962 )
6063 // wait for tables to load, we don't need to wait here cause this response may complete before the table creation.
61- await waitForApiResponseWithTimeout ( page , ( response ) =>
64+ const entitiesPromise = waitForApiResponseWithTimeout ( page , ( response ) =>
6265 response . url ( ) . includes ( 'query?key=entity-types-public-' )
6366 )
67+ await page . getByRole ( 'button' , { name : 'Save' } ) . click ( )
68+ await Promise . all ( [ createTablePromise , tablesPromise , entitiesPromise ] )
6469 await expect (
6570 page . getByRole ( 'button' , { name : `View ${ tableName } ` , exact : true } ) ,
6671 'Table should be visible after creation'
67- ) . toBeVisible ( { timeout : 10000 } )
72+ ) . toBeVisible ( { timeout : 15_000 } )
6873}
6974
7075const deleteTable = async ( page : Page , ref : string , tableName : string ) => {
7176 const viewLocator = page . getByLabel ( `View ${ tableName } ` )
7277 if ( ( await viewLocator . count ( ) ) === 0 ) return
7378 await viewLocator . nth ( 0 ) . click ( )
74- await viewLocator . getByRole ( 'button' ) . nth ( 1 ) . click ( { force : true } )
79+ await viewLocator . locator ( 'button[aria-haspopup="menu"]' ) . click ( { force : true } )
7580 await page . getByText ( 'Delete table' ) . click ( )
7681 await page . getByRole ( 'checkbox' , { name : 'Drop table with cascade?' } ) . click ( )
82+ const apiPromise = waitForApiResponse ( page , 'pg-meta' , ref , 'query?key=table-delete-' , {
83+ method : 'POST' ,
84+ } )
85+ const revalidatePromise = waitForApiResponse ( page , 'pg-meta' , ref , `query?key=entity-types-` )
7786 await page . getByRole ( 'button' , { name : 'Delete' } ) . click ( )
78- await waitForApiResponse ( page , 'pg-meta' , ref , 'query?key=table-delete-' , { method : 'POST' } )
87+ await Promise . all ( [ apiPromise , revalidatePromise ] )
7988}
8089
8190const deleteEnumIfExist = async ( page : Page , ref : string , enumName : string ) => {
@@ -96,49 +105,36 @@ const deleteEnumIfExist = async (page: Page, ref: string, enumName: string) => {
96105}
97106
98107test . describe . serial ( 'table editor' , ( ) => {
99- let page : Page
100-
101- test . beforeEach ( async ( { ref } ) => {
102- await resetLocalStorage ( page , ref )
103- await page . goto ( toUrl ( `/project/${ ref } /editor?schema=public` ) )
104- await waitForTableToLoad ( page , ref )
105- } )
106-
107108 test . beforeAll ( async ( { browser, ref } ) => {
108- page = await browser . newPage ( )
109+ const ctx = await browser . newContext ( )
110+ const page = await ctx . newPage ( )
111+
112+ const loadPromise = waitForTableToLoad ( page , ref )
109113 await page . goto ( toUrl ( `/project/${ ref } /editor?schema=public` ) )
110- await waitForTableToLoad ( page , ref )
114+ await loadPromise
111115
112- // Delete all tables with prefix pw_table (ensure page is stable first)
113116 const viewButtons = page . getByRole ( 'button' , { name : / ^ V i e w / } )
114- const names = ( await viewButtons . allTextContents ( ) ) . map ( ( t ) => t . replace ( / ^ V i e w \s + / , '' ) . trim ( ) )
117+ const names = await Promise . all (
118+ ( await viewButtons . all ( ) ) . map ( async ( btn ) => {
119+ const ariaLabel = await btn . getAttribute ( 'aria-label' )
120+ const name = ariaLabel ? ariaLabel . replace ( / ^ V i e w \s + / , '' ) . trim ( ) : ''
121+ return name
122+ } )
123+ )
115124 const tablesToDelete = names . filter ( ( tableName ) => tableName . startsWith ( tableNamePrefix ) )
116125
117126 for ( const tableName of tablesToDelete ) {
118127 await deleteTable ( page , ref , tableName )
119- await waitForTableToLoad ( page , ref ) // wait for table data to update
120128 }
121129 } )
122130
123- test . afterAll ( async ( { ref } ) => {
124- await resetLocalStorage ( page , ref )
125-
126- // Always navigate explicitly to editor and wait for tables to be loaded
131+ test . beforeEach ( async ( { page, ref } ) => {
132+ const loadPromise = waitForTableToLoad ( page , ref )
127133 await page . goto ( toUrl ( `/project/${ ref } /editor?schema=public` ) )
128- await waitForTableToLoad ( page , ref )
129-
130- // Delete all tables with prefix pw_table
131- const viewButtons = page . getByRole ( 'button' , { name : / ^ V i e w / } )
132- const names = ( await viewButtons . allTextContents ( ) ) . map ( ( t ) => t . replace ( / ^ V i e w \s + / , '' ) . trim ( ) )
133- const tablesToDelete = names . filter ( ( tableName ) => tableName . startsWith ( tableNamePrefix ) )
134-
135- for ( const tableName of tablesToDelete ) {
136- await deleteTable ( page , ref , tableName )
137- await waitForTableToLoad ( page , ref ) // wait for table data to update
138- }
134+ await loadPromise
139135 } )
140136
141- test ( 'sidebar actions works as expected' , async ( { ref } ) => {
137+ test ( 'sidebar actions works as expected' , async ( { page , ref } ) => {
142138 const tableNameActions = 'pw_table_actions'
143139 const tableNameActionsDuplicate = 'pw_table_actions_duplicate'
144140
@@ -193,7 +189,7 @@ test.describe.serial('table editor', () => {
193189 await deleteTable ( page , ref , tableNameActions )
194190 } )
195191
196- test ( 'switching schemas work as expected' , async ( { ref } ) => {
192+ test ( 'switching schemas work as expected' , async ( { page , ref } ) => {
197193 const authTableSso = 'identities'
198194 const authTableMfa = 'mfa_factors'
199195
@@ -212,7 +208,7 @@ test.describe.serial('table editor', () => {
212208 await expect ( page . getByLabel ( `View ${ authTableMfa } ` ) ) . toBeVisible ( )
213209 } )
214210
215- test ( 'should show rls accordingly' , async ( { ref } ) => {
211+ test ( 'should show rls accordingly' , async ( { page , ref } ) => {
216212 const tableNameRlsEnabled = 'pw_table_rls_enabled'
217213 const tableNameRlsDisabled = 'pw_table_rls_disabled'
218214
@@ -226,22 +222,19 @@ test.describe.serial('table editor', () => {
226222 await page . getByTestId ( 'table-name-input' ) . fill ( tableNameRlsDisabled )
227223 await page . getByLabel ( 'Enable Row Level Security (' ) . click ( )
228224 await page . getByRole ( 'button' , { name : 'Confirm' } ) . click ( )
229- await page . getByRole ( 'button' , { name : 'Save' } ) . click ( )
230- await waitForApiResponse (
225+ const apiPromise = waitForApiResponse (
231226 page ,
232227 'pg-meta' ,
233228 ref ,
234229 'tables?include_columns=false&included_schemas=public'
235230 ) // wait for table creation
231+ await page . getByRole ( 'button' , { name : 'Save' } ) . click ( )
232+ await apiPromise
236233 await page . getByRole ( 'button' , { name : `View ${ tableNameRlsDisabled } ` } ) . click ( )
237234 await expect ( page . getByRole ( 'button' , { name : 'RLS disabled' } ) ) . toBeVisible ( )
238-
239- // clear all tables
240- await deleteTable ( page , ref , tableNameRlsEnabled )
241- await deleteTable ( page , ref , tableNameRlsDisabled )
242235 } )
243236
244- test ( 'add enums and show enums on table' , async ( { ref } ) => {
237+ test ( 'add enums and show enums on table' , async ( { page , ref } ) => {
245238 const tableNameEnum = 'pw_table_enum'
246239 const columnNameEnum = 'pw_column_enum'
247240 const enum_name = 'pw_enum'
@@ -317,7 +310,7 @@ test.describe.serial('table editor', () => {
317310 await resetLocalStorage ( page , ref )
318311 } )
319312
320- test ( 'Grid editor exporting works as expected' , async ( { ref } ) => {
313+ test ( 'Grid editor exporting works as expected' , async ( { page , ref } ) => {
321314 const tableNameGridEditor = ' pw_table_grid_editor'
322315 const tableNameUpdated = 'pw_table_updated'
323316 const columnNameUpdated = 'pw_column_updated'
@@ -449,14 +442,9 @@ test.describe.serial('table editor', () => {
449442 await page . getByRole ( 'menuitem' , { name : 'Export table via CLI' } ) . click ( )
450443 await expect ( page . getByRole ( 'heading' , { name : 'Export table data via CLI' } ) ) . toBeVisible ( )
451444 await page . getByRole ( 'button' , { name : 'Close' } ) . first ( ) . click ( )
452-
453- // Ensure all menus/dialogs are closed before continuing
454- await page . keyboard . press ( 'Escape' )
455- await page . keyboard . press ( 'Escape' )
456- await page . waitForTimeout ( 500 )
457445 } )
458446
459- test ( 'filtering rows works as expected' , async ( { ref } ) => {
447+ test ( 'filtering rows works as expected' , async ( { page , ref } ) => {
460448 const tableName = 'pw_table_filtering'
461449 const colName = 'pw_column'
462450
@@ -473,13 +461,14 @@ test.describe.serial('table editor', () => {
473461 await page . getByTestId ( 'table-editor-insert-new-row' ) . click ( )
474462 await page . getByRole ( 'menuitem' , { name : 'Insert row Insert a new row' } ) . click ( )
475463 await page . getByTestId ( `${ colName } -input` ) . fill ( value )
464+ const apiPromise = waitForApiResponse ( page , 'pg-meta' , ref , 'query?key=' , { method : 'POST' } )
476465 await page . getByTestId ( 'action-bar-save-row' ) . click ( )
477- await waitForApiResponse ( page , 'pg-meta' , ref , 'query?key=' , { method : 'POST' } )
466+ await apiPromise
478467 }
479468
480469 await page . getByRole ( 'button' , { name : 'Filter' , exact : true } ) . click ( )
481470 await page . getByRole ( 'button' , { name : 'Add filter' } ) . click ( )
482- await page . getByRole ( 'button' , { name : 'id' } ) . click ( )
471+ await page . getByRole ( 'button' , { name : 'id' , exact : true } ) . click ( )
483472 await page . getByRole ( 'menuitem' , { name : colName } ) . click ( )
484473 await page . getByRole ( 'textbox' , { name : 'Enter a value' } ) . fill ( '789' )
485474 await page . getByRole ( 'button' , { name : 'Apply filter' } ) . click ( )
@@ -493,25 +482,27 @@ test.describe.serial('table editor', () => {
493482 await deleteTable ( page , ref , tableName )
494483 } )
495484
496- test ( 'view table definition works as expected' , async ( { ref } ) => {
485+ test ( 'view table definition works as expected' , async ( { page , ref } ) => {
497486 const tableName = 'pw_table_definition'
498487 const colName = 'pw_column'
499488 if ( ! page . url ( ) . includes ( '/editor' ) ) {
489+ const tableLoadPromise = waitForTableToLoad ( page , ref )
500490 await page . goto ( toUrl ( `/project/${ ref } /editor?schema=public` ) )
501- await waitForTableToLoad ( page , ref )
491+ await tableLoadPromise
502492 }
503493 await createTable ( page , ref , tableName )
504494 await page . getByRole ( 'button' , { name : `View ${ tableName } ` , exact : true } ) . click ( )
505495 await page . waitForURL ( / \/ e d i t o r \/ \d + \? s c h e m a = p u b l i c $ / )
496+ const apiPromise = waitForApiResponse ( page , 'pg-meta' , ref , 'query?key=table-definition-' )
506497 await page . getByText ( 'definition' , { exact : true } ) . click ( )
507- await waitForApiResponse ( page , 'pg-meta' , ref , 'query?key=table-definition-' )
498+ await apiPromise
508499 await expect ( page . locator ( '.view-lines' ) ) . toContainText (
509500 `create table public.${ tableName } ( id bigint generated by default as identity not null, created_at timestamp with time zone null default now(), ${ colName } text null, constraint ${ tableName } _pkey primary key (id)) TABLESPACE pg_default;`
510501 )
511502 await deleteTable ( page , ref , tableName )
512503 } )
513504
514- test ( 'sorting rows works as expected' , async ( { ref } ) => {
505+ test ( 'sorting rows works as expected' , async ( { page , ref } ) => {
515506 const tableName = 'pw_table_sorting'
516507 const colName = 'pw_column'
517508
@@ -552,7 +543,7 @@ test.describe.serial('table editor', () => {
552543 await deleteTable ( page , ref , tableName )
553544 } )
554545
555- test ( 'importing, pagination and large data actions works as expected' , async ( { ref } ) => {
546+ test ( 'importing, pagination and large data actions works as expected' , async ( { page , ref } ) => {
556547 await page . goto ( toUrl ( `/project/${ ref } /editor?schema=public` ) )
557548 const tableNameDataActions = 'pw_table_data'
558549
@@ -575,7 +566,6 @@ test.describe.serial('table editor', () => {
575566 // importing 51 data via paste text
576567 const filePath = path . join ( __dirname , 'files' , 'table-editor-import-paste.txt' )
577568 const fileContent = fs . readFileSync ( filePath , 'utf-8' )
578- await page . getByRole ( 'button' , { name : 'Close toast' } ) . first ( ) . click ( ) // close toast, as paste text is behind toast
579569 await page . getByTestId ( 'table-editor-insert-new-row' ) . click ( )
580570 await page . getByRole ( 'menuitem' , { name : 'Import data from CSV' } ) . click ( )
581571 await page . getByRole ( 'tab' , { name : 'Paste text' } ) . click ( )
0 commit comments