diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 304b9566a7..9e0ef8acaf 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -66,13 +66,25 @@ jobs: # - name: run unit tests # working-directory: ./packages/nocodb # run: npm run test:unit - playwright-mysql: + # playwright-mysql: + # if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'trigger-CI') || !github.event.pull_request.draft }} + # uses: ./.github/workflows/playwright-test-workflow.yml + # with: + # db: mysql + # playwright-sqlite: + # if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'trigger-CI') || !github.event.pull_request.draft }} + # uses: ./.github/workflows/playwright-test-workflow.yml + # with: + # db: sqlite + playwright-pg-shard-1: if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'trigger-CI') || !github.event.pull_request.draft }} uses: ./.github/workflows/playwright-test-workflow.yml with: - db: mysql - playwright-sqlite: + db: pg + shard: 1 + playwright-pg-shard-2: if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'trigger-CI') || !github.event.pull_request.draft }} uses: ./.github/workflows/playwright-test-workflow.yml with: - db: sqlite \ No newline at end of file + db: pg + shard: 2 \ No newline at end of file diff --git a/.github/workflows/playwright-test-workflow.yml b/.github/workflows/playwright-test-workflow.yml index 4d03de1b0f..1bcd7c2059 100644 --- a/.github/workflows/playwright-test-workflow.yml +++ b/.github/workflows/playwright-test-workflow.yml @@ -6,6 +6,7 @@ on: db: required: true type: string + shard: string jobs: playwright: @@ -57,7 +58,7 @@ jobs: - name: setup pg if: ${{ inputs.db == 'pg' }} working-directory: ./ - run: docker-compose -f ./scripts/playwright/scripts/docker-compose-pg.yml up -d & + run: docker-compose -f ./scripts/playwright/scripts/docker-compose-playwright-pg.yml up -d & - name: run frontend working-directory: ./packages/nc-gui run: npm run ci:run @@ -86,16 +87,16 @@ jobs: done - name: Run Playwright tests working-directory: ./scripts/playwright - run: E2E_DB_TYPE=${{ inputs.db }} npm run ci:test + run: E2E_DB_TYPE=${{ inputs.db }} npm run ci:test:shard:${{ inputs.shard }} - uses: actions/upload-artifact@v3 if: always() with: - name: playwright-report + name: playwright-report-${{ inputs.db }}-${{ inputs.shard }} path: ./scripts/playwright/playwright-report/ retention-days: 2 - uses: actions/upload-artifact@v3 if: always() with: - name: backend logs + name: backend-logs-${{ inputs.db }}-${{ inputs.shard }} path: ./packages/nocodb/mysql_test_backend.log retention-days: 2 \ No newline at end of file diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json index e29a502574..74970c47cf 100644 --- a/packages/nocodb/package.json +++ b/packages/nocodb/package.json @@ -36,7 +36,7 @@ "docker:build": "EE=\"true-xc-test\" webpack --config docker/webpack.config.js", "watch:build": "nodemon -e ts,js -w ./src -x npm run build", "watch:run": "cross-env NC_DISABLE_TELE1=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"", - "watch:run:playwright": "cross-env PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/testDocker --log-error --project tsconfig.json\"", + "watch:run:playwright": "cross-env DATABASE_URL=sqlite:./test_noco.db PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/testDocker --log-error --project tsconfig.json\"", "watch:run:playwright:quick": "rm -f ./test_noco.db; cp ../../scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"", "watch:run:playwright:pg:cyquick": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"", "watch:run:cypress": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"", diff --git a/packages/nocodb/src/lib/services/test/TestResetService/index.ts b/packages/nocodb/src/lib/services/test/TestResetService/index.ts index 3e065fdd48..930e1d5bfc 100644 --- a/packages/nocodb/src/lib/services/test/TestResetService/index.ts +++ b/packages/nocodb/src/lib/services/test/TestResetService/index.ts @@ -9,6 +9,8 @@ import NocoCache from '../../../cache/NocoCache'; import { CacheScope } from '../../../utils/globals'; import ProjectUser from '../../../models/ProjectUser'; +const workerStatus = {}; + const loginRootUser = async () => { const response = await axios.post( 'http://localhost:8080/api/v1/auth/user/signin', @@ -45,6 +47,21 @@ export class TestResetService { async process() { try { + console.log( + `earlier workerStatus: parrelledId: ${this.parallelId}:`, + workerStatus[this.parallelId] + ); + + // wait till previous worker is done + while (workerStatus[this.parallelId] === 'processing') { + console.log( + `waiting for previous worker to finish parrelelId:${this.parallelId}` + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + workerStatus[this.parallelId] = 'processing'; + const token = await loginRootUser(); const { project } = await this.resetProject({ @@ -53,12 +70,18 @@ export class TestResetService { parallelId: this.parallelId, }); - await removeAllProjectCreatedByTheTest(this.parallelId); - await removeAllPrefixedUsersExceptSuper(this.parallelId); + try { + await removeAllProjectCreatedByTheTest(this.parallelId); + await removeAllPrefixedUsersExceptSuper(this.parallelId); + } catch (e) { + console.log(`Error in cleaning up project: ${this.parallelId}`, e); + } + workerStatus[this.parallelId] = 'completed'; return { token, project }; } catch (e) { console.error('TestResetService:process', e); + workerStatus[this.parallelId] = 'errored'; return { error: e }; } } @@ -80,9 +103,11 @@ export class TestResetService { const bases = await project.getBases(); - await Project.delete(project.id); + if (bases.length > 0) { + await NcConnectionMgrv2.deleteAwait(bases[0]); + } - if (bases.length > 0) await NcConnectionMgrv2.deleteAwait(bases[0]); + await Project.delete(project.id); } if (dbType == 'sqlite') { diff --git a/packages/nocodb/src/lib/services/test/TestResetService/resetMysqlSakilaProject.ts b/packages/nocodb/src/lib/services/test/TestResetService/resetMysqlSakilaProject.ts index 8b602acadb..b50438487a 100644 --- a/packages/nocodb/src/lib/services/test/TestResetService/resetMysqlSakilaProject.ts +++ b/packages/nocodb/src/lib/services/test/TestResetService/resetMysqlSakilaProject.ts @@ -45,13 +45,28 @@ const isSakilaMysqlToBeReset = async ( parallelId: string, project?: Project ) => { - const tablesInDb: Array = await knex.raw( + const tablesInDbInfo: Array = await knex.raw( `SELECT table_name FROM information_schema.tables WHERE table_schema = 'test_sakila_${parallelId}'` ); + const nonMetaTablesInDb = tablesInDbInfo[0] + .map((t) => t['TABLE_NAME']) + .filter((table) => table !== 'nc_evolutions'); + + const mysqlSakilaTablesAndViews = [ + ...mysqlSakilaTables, + ...mysqlSakilaSqlViews, + ]; + if ( - tablesInDb.length === 0 || - (tablesInDb.length > 0 && !tablesInDb.includes(`actor`)) + nonMetaTablesInDb.length === 0 || + // If there are sakila tables + !nonMetaTablesInDb.includes(`actor`) || + // If there are no pg sakila tables in tables in db + !( + nonMetaTablesInDb.length === mysqlSakilaTablesAndViews.length && + nonMetaTablesInDb.every((t) => mysqlSakilaTablesAndViews.includes(t)) + ) ) { return true; } @@ -151,4 +166,33 @@ const resetMysqlSakilaProject = async ({ await nc_knex.destroy(); }; +const mysqlSakilaTables = [ + 'actor', + 'address', + 'category', + 'city', + 'country', + 'customer', + 'film', + 'film_text', + 'film_actor', + 'film_category', + 'inventory', + 'language', + 'payment', + 'rental', + 'staff', + 'store', +]; + +const mysqlSakilaSqlViews = [ + 'actor_info', + 'customer_list', + 'film_list', + 'nicer_but_slower_film_list', + 'sales_by_film_category', + 'sales_by_store', + 'staff_list', +]; + export default resetMysqlSakilaProject; diff --git a/packages/nocodb/src/lib/services/test/TestResetService/resetPgSakilaProject.ts b/packages/nocodb/src/lib/services/test/TestResetService/resetPgSakilaProject.ts index 7463281027..b331ecdb7f 100644 --- a/packages/nocodb/src/lib/services/test/TestResetService/resetPgSakilaProject.ts +++ b/packages/nocodb/src/lib/services/test/TestResetService/resetPgSakilaProject.ts @@ -1,9 +1,9 @@ import axios from 'axios'; -import { Knex, knex } from 'knex'; +import { knex } from 'knex'; import { promises as fs } from 'fs'; -const util = require('util'); -const exec = util.promisify(require('child_process').exec); +// const util = require('util'); +// const exec = util.promisify(require('child_process').exec); import Audit from '../../../models/Audit'; import Project from '../../../models/Project'; @@ -19,7 +19,7 @@ const config = { multipleStatements: true, }, searchPath: ['public', 'information_schema'], - pool: { min: 0, max: 5 }, + pool: { min: 0, max: 1 }, }; const extMysqlProject = (title, parallelId) => ({ @@ -45,16 +45,31 @@ const extMysqlProject = (title, parallelId) => ({ external: true, }); -const isSakilaPgToBeReset = async (knex: Knex, project?: Project) => { +const isSakilaPgToBeReset = async (parallelId: string, project?: Project) => { + const sakilaKnex = knex(sakilaKnexConfig(parallelId)); + const tablesInDb: Array = ( - await knex.raw( + await sakilaKnex.raw( `SELECT * FROM information_schema.tables WHERE table_schema = 'public'` ) ).rows.map((row) => row.table_name); + await sakilaKnex.destroy(); + + const nonMetaTablesInDb = tablesInDb.filter( + (table) => table !== 'nc_evolutions' + ); + const pgSakilaTablesAndViews = [...pgSakilaTables, ...pgSakilaSqlViews]; + if ( tablesInDb.length === 0 || - (tablesInDb.length > 0 && !tablesInDb.includes(`actor`)) + // If there are sakila tables + !tablesInDb.includes(`actor`) || + // If there are no pg sakila tables in tables in db + !( + nonMetaTablesInDb.length === pgSakilaTablesAndViews.length && + nonMetaTablesInDb.every((t) => pgSakilaTablesAndViews.includes(t)) + ) ) { return true; } @@ -66,34 +81,33 @@ const isSakilaPgToBeReset = async (knex: Knex, project?: Project) => { return audits?.length > 0; }; -const resetSakilaPg = async ( - pgknex: Knex, - parallelId: string, - isEmptyProject: boolean -) => { +const resetSakilaPg = async (parallelId: string, isEmptyProject: boolean) => { const testsDir = __dirname.replace( '/src/lib/services/test/TestResetService', '/tests' ); - await pgknex.raw(`DROP DATABASE IF EXISTS sakila_${parallelId}`); - await pgknex.raw(`CREATE DATABASE sakila_${parallelId}`); - if (isEmptyProject) return; - const sakilaKnex = knex(sakilaKnexConfig(parallelId)); - - const schemaFile = await fs.readFile( - `${testsDir}/pg-sakila-db/03-postgres-sakila-schema.sql` - ); - await sakilaKnex.raw(schemaFile.toString()); - - const dataFilePath = `${testsDir}/pg-sakila-db/04-postgres-sakila-insert-data.sql`; - await exec( - `export PGPASSWORD='${config.connection.password}';psql sakila_${parallelId} -h localhost -U postgres -w -f ${dataFilePath}` - ); + try { + const sakilaKnex = knex(sakilaKnexConfig(parallelId)); + const schemaFile = await fs.readFile( + `${testsDir}/pg-sakila-db/01-postgres-sakila-schema.sql` + ); + await sakilaKnex.raw(schemaFile.toString()); + + const trx = await sakilaKnex.transaction(); + const dataFile = await fs.readFile( + `${testsDir}/pg-sakila-db/02-postgres-sakila-insert-data.sql` + ); + await trx.raw(dataFile.toString()); + await trx.commit(); - await sakilaKnex.destroy(); + await sakilaKnex.destroy(); + } catch (e) { + console.error(`Error resetting pg sakila db: Worker ${parallelId}`); + throw Error(`Error resetting pg sakila db: Worker ${parallelId}`); + } }; const sakilaKnexConfig = (parallelId: string) => ({ @@ -123,13 +137,12 @@ const resetPgSakilaProject = async ({ await pgknex.raw(`CREATE DATABASE sakila_${parallelId}`); } catch (e) {} - const sakilaKnex = knex(sakilaKnexConfig(parallelId)); + if (isEmptyProject || (await isSakilaPgToBeReset(parallelId, oldProject))) { + await pgknex.raw(`DROP DATABASE IF EXISTS sakila_${parallelId}`); + await pgknex.raw(`CREATE DATABASE sakila_${parallelId}`); + await pgknex.destroy(); - if (isEmptyProject || (await isSakilaPgToBeReset(sakilaKnex, oldProject))) { - await sakilaKnex.destroy(); - await resetSakilaPg(pgknex, parallelId, isEmptyProject); - } else { - await sakilaKnex.destroy(); + await resetSakilaPg(parallelId, isEmptyProject); } const response = await axios.post( @@ -145,8 +158,40 @@ const resetPgSakilaProject = async ({ console.error('Error creating project', response.data); throw new Error('Error creating project', response.data); } - - await pgknex.destroy(); }; +const pgSakilaTables = [ + 'country', + 'city', + 'actor', + 'film_actor', + 'category', + 'film_category', + 'language', + 'film', + 'payment_p2007_01', + 'payment_p2007_02', + 'payment_p2007_03', + 'payment_p2007_04', + 'payment_p2007_05', + 'payment_p2007_06', + 'payment', + 'customer', + 'inventory', + 'rental', + 'address', + 'staff', + 'store', +]; + +const pgSakilaSqlViews = [ + 'actor_info', + 'customer_list', + 'film_list', + 'nicer_but_slower_film_list', + 'sales_by_film_category', + 'sales_by_store', + 'staff_list', +]; + export default resetPgSakilaProject; diff --git a/scripts/playwright/fixtures/expectedBaseDownloadDataPg.txt b/scripts/playwright/fixtures/expectedBaseDownloadDataPg.txt new file mode 100644 index 0000000000..e8777dddae --- /dev/null +++ b/scripts/playwright/fixtures/expectedBaseDownloadDataPg.txt @@ -0,0 +1,110 @@ +Country,LastUpdate,City List +Afghanistan,"""2006-02-14T23:14:00.000Z""",Kabul +Algeria,"""2006-02-14T23:14:00.000Z""","Batna, Bchar, Skikda" +American Samoa,"""2006-02-14T23:14:00.000Z""",Tafuna +Angola,"""2006-02-14T23:14:00.000Z""","Benguela, Namibe" +Anguilla,"""2006-02-14T23:14:00.000Z""",South Hill +Argentina,"""2006-02-14T23:14:00.000Z""","Almirante Brown, Avellaneda, Baha Blanca, Crdoba, Escobar, Ezeiza, La Plata, Merlo, Quilmes, San Miguel de Tucumn, Santa F, Tandil, Vicente Lpez" +Armenia,"""2006-02-14T23:14:00.000Z""",Yerevan +Australia,"""2006-02-14T23:14:00.000Z""",Woodridge +Austria,"""2006-02-14T23:14:00.000Z""","Graz, Linz, Salzburg" +Azerbaijan,"""2006-02-14T23:14:00.000Z""","Baku, Sumqayit" +Bahrain,"""2006-02-14T23:14:00.000Z""",al-Manama +Bangladesh,"""2006-02-14T23:14:00.000Z""","Dhaka, Jamalpur, Tangail" +Belarus,"""2006-02-14T23:14:00.000Z""","Mogiljov, Molodetno" +Bolivia,"""2006-02-14T23:14:00.000Z""","El Alto, Sucre" +Brazil,"""2006-02-14T23:14:00.000Z""","Alvorada, Angra dos Reis, Anpolis, Aparecida de Goinia, Araatuba, Bag, Belm, Blumenau, Boa Vista, Braslia, Goinia, Guaruj, guas Lindas de Gois, Ibirit, Juazeiro do Norte, Juiz de Fora, Luzinia, Maring, Po, Poos de Caldas, Rio Claro, Santa Brbara dOeste, Santo Andr, So Bernardo do Campo, So Leopoldo" +Brunei,"""2006-02-14T23:14:00.000Z""",Bandar Seri Begawan +Bulgaria,"""2006-02-14T23:14:00.000Z""","Ruse, Stara Zagora" +Cambodia,"""2006-02-14T23:14:00.000Z""","Battambang, Phnom Penh" +Cameroon,"""2006-02-14T23:14:00.000Z""","Bamenda, Yaound" +Canada,"""2006-02-14T23:14:00.000Z""","Gatineau, Halifax, Lethbridge, London, Oshawa, Richmond Hill, Vancouver" +Chad,"""2006-02-14T23:14:00.000Z""",NDjamna +Chile,"""2006-02-14T23:14:00.000Z""","Antofagasta, Coquimbo, Rancagua" +China,"""2006-02-14T23:14:00.000Z""","Baicheng, Baiyin, Binzhou, Changzhou, Datong, Daxian, Dongying, Emeishan, Enshi, Ezhou, Fuyu, Fuzhou, Haining, Hami, Hohhot, Huaian, Jinchang, Jining, Jinzhou, Junan, Korla, Laiwu, Laohekou, Lengshuijiang, Leshan" +Colombia,"""2006-02-14T23:14:00.000Z""","Buenaventura, Dos Quebradas, Florencia, Pereira, Sincelejo, Sogamoso" +"Congo, The Democratic Republic of the","""2006-02-14T23:14:00.000Z""","Lubumbashi, Mwene-Ditu" +Czech Republic,"""2006-02-14T23:14:00.000Z""",Olomouc +Dominican Republic,"""2006-02-14T23:14:00.000Z""","La Romana, San Felipe de Puerto Plata, Santiago de los Caballeros" +Ecuador,"""2006-02-14T23:14:00.000Z""","Loja, Portoviejo, Robamba" +Egypt,"""2006-02-14T23:14:00.000Z""","Bilbays, Idfu, Mit Ghamr, Qalyub, Sawhaj, Shubra al-Khayma" +Estonia,"""2006-02-14T23:14:00.000Z""",Tartu +Ethiopia,"""2006-02-14T23:14:00.000Z""",Addis Abeba +Faroe Islands,"""2006-02-14T23:14:00.000Z""",Trshavn +Finland,"""2006-02-14T23:14:00.000Z""",Oulu +France,"""2006-02-14T23:14:00.000Z""","Brest, Le Mans, Toulon, Toulouse" +French Guiana,"""2006-02-14T23:14:00.000Z""",Cayenne +French Polynesia,"""2006-02-14T23:14:00.000Z""","Faaa, Papeete" +Gambia,"""2006-02-14T23:14:00.000Z""",Banjul +Germany,"""2006-02-14T23:14:00.000Z""","Duisburg, Erlangen, Halle/Saale, Mannheim, Saarbrcken, Siegen, Witten" +Greece,"""2006-02-14T23:14:00.000Z""","Athenai, Patras" +Greenland,"""2006-02-14T23:14:00.000Z""",Nuuk +Holy See (Vatican City State),"""2006-02-14T23:14:00.000Z""",Citt del Vaticano +Hong Kong,"""2006-02-14T23:14:00.000Z""",Kowloon and New Kowloon +Hungary,"""2006-02-14T23:14:00.000Z""",Szkesfehrvr +India,"""2006-02-14T23:14:00.000Z""","Adoni, Ahmadnagar, Allappuzha (Alleppey), Ambattur, Amroha, Balurghat, Berhampore (Baharampur), Bhavnagar, Bhilwara, Bhimavaram, Bhopal, Bhusawal, Bijapur, Chandrapur, Chapra, Dhule (Dhulia), Etawah, Firozabad, Gandhinagar, Gulbarga, Haldia, Halisahar, Hoshiarpur, Hubli-Dharwad, Jaipur" +Indonesia,"""2006-02-14T23:14:00.000Z""","Cianjur, Ciomas, Ciparay, Gorontalo, Jakarta, Lhokseumawe, Madiun, Pangkal Pinang, Pemalang, Pontianak, Probolinggo, Purwakarta, Surakarta, Tegal" +Iran,"""2006-02-14T23:14:00.000Z""","Arak, Esfahan, Kermanshah, Najafabad, Qomsheh, Shahr-e Kord, Sirjan, Tabriz" +Iraq,"""2006-02-14T23:14:00.000Z""",Mosul +Israel,"""2006-02-14T23:14:00.000Z""","Ashdod, Ashqelon, Bat Yam, Tel Aviv-Jaffa" +Italy,"""2006-02-14T23:14:00.000Z""","Alessandria, Bergamo, Brescia, Brindisi, Livorno, Syrakusa, Udine" +Japan,"""2006-02-14T23:14:00.000Z""","Akishima, Fukuyama, Higashiosaka, Hino, Hiroshima, Isesaki, Iwaki, Iwakuni, Iwatsuki, Izumisano, Kakamigahara, Kamakura, Kanazawa, Koriyama, Kurashiki, Kuwana, Matsue, Miyakonojo, Nagareyama, Okayama, Okinawa, Omiya, Onomichi, Otsu, Sagamihara" +Kazakstan,"""2006-02-14T23:14:00.000Z""","Pavlodar, Zhezqazghan" +Kenya,"""2006-02-14T23:14:00.000Z""","Kisumu, Nyeri" +Kuwait,"""2006-02-14T23:14:00.000Z""",Jalib al-Shuyukh +Latvia,"""2006-02-14T23:14:00.000Z""","Daugavpils, Liepaja" +Liechtenstein,"""2006-02-14T23:14:00.000Z""",Vaduz +Lithuania,"""2006-02-14T23:14:00.000Z""",Vilnius +Madagascar,"""2006-02-14T23:14:00.000Z""",Mahajanga +Malawi,"""2006-02-14T23:14:00.000Z""",Lilongwe +Malaysia,"""2006-02-14T23:14:00.000Z""","Ipoh, Kuching, Sungai Petani" +Mexico,"""2006-02-14T23:14:00.000Z""","Acua, Allende, Atlixco, Carmen, Celaya, Coacalco de Berriozbal, Coatzacoalcos, Cuauhtmoc, Cuautla, Cuernavaca, El Fuerte, Guadalajara, Hidalgo, Huejutla de Reyes, Huixquilucan, Jos Azueta, Jurez, La Paz, Matamoros, Mexicali, Monclova, Nezahualcyotl, Pachuca de Soto, Salamanca, San Felipe del Progreso" +Moldova,"""2006-02-14T23:14:00.000Z""",Chisinau +Morocco,"""2006-02-14T23:14:00.000Z""","Beni-Mellal, Nador, Sal" +Mozambique,"""2006-02-14T23:14:00.000Z""","Beira, Naala-Porto, Tete" +Myanmar,"""2006-02-14T23:14:00.000Z""","Monywa, Myingyan" +Nauru,"""2006-02-14T23:14:00.000Z""",Yangor +Nepal,"""2006-02-14T23:14:00.000Z""",Birgunj +Netherlands,"""2006-02-14T23:14:00.000Z""","Amersfoort, Apeldoorn, Ede, Emmen, s-Hertogenbosch" +New Zealand,"""2006-02-14T23:14:00.000Z""",Hamilton +Nigeria,"""2006-02-14T23:14:00.000Z""","Benin City, Deba Habe, Effon-Alaiye, Ife, Ikerre, Ilorin, Kaduna, Ogbomosho, Ondo, Owo, Oyo, Sokoto, Zaria" +North Korea,"""2006-02-14T23:14:00.000Z""",Pyongyang +Oman,"""2006-02-14T23:14:00.000Z""","Masqat, Salala" +Pakistan,"""2006-02-14T23:14:00.000Z""","Dadu, Mandi Bahauddin, Mardan, Okara, Shikarpur" +Paraguay,"""2006-02-14T23:14:00.000Z""","Asuncin, Ciudad del Este, San Lorenzo" +Peru,"""2006-02-14T23:14:00.000Z""","Callao, Hunuco, Lima, Sullana" +Philippines,"""2006-02-14T23:14:00.000Z""","Baybay, Bayugan, Bislig, Cabuyao, Cavite, Davao, Gingoog, Hagonoy, Iligan, Imus, Lapu-Lapu, Mandaluyong, Ozamis, Santa Rosa, Taguig, Talavera, Tanauan, Tanza, Tarlac, Tuguegarao" +Poland,"""2006-02-14T23:14:00.000Z""","Bydgoszcz, Czestochowa, Jastrzebie-Zdrj, Kalisz, Lublin, Plock, Tychy, Wroclaw" +Puerto Rico,"""2006-02-14T23:14:00.000Z""","Arecibo, Ponce" +Romania,"""2006-02-14T23:14:00.000Z""","Botosani, Bucuresti" +Runion,"""2006-02-14T23:14:00.000Z""",Saint-Denis +Russian Federation,"""2006-02-14T23:14:00.000Z""","Atinsk, Balaiha, Dzerzinsk, Elista, Ivanovo, Jaroslavl, Jelets, Kaliningrad, Kamyin, Kirovo-Tepetsk, Kolpino, Korolev, Kurgan, Kursk, Lipetsk, Ljubertsy, Maikop, Moscow, Nabereznyje Telny, Niznekamsk, Novoterkassk, Pjatigorsk, Serpuhov, Smolensk, Syktyvkar" +Saint Vincent and the Grenadines,"""2006-02-14T23:14:00.000Z""",Kingstown +Saudi Arabia,"""2006-02-14T23:14:00.000Z""","Abha, al-Hawiya, al-Qatif, Jedda, Tabuk" +Senegal,"""2006-02-14T23:14:00.000Z""",Ziguinchor +Slovakia,"""2006-02-14T23:14:00.000Z""",Bratislava +South Africa,"""2006-02-14T23:14:00.000Z""","Boksburg, Botshabelo, Chatsworth, Johannesburg, Kimberley, Klerksdorp, Newcastle, Paarl, Rustenburg, Soshanguve, Springs" +South Korea,"""2006-02-14T23:14:00.000Z""","Cheju, Kimchon, Naju, Tonghae, Uijongbu" +Spain,"""2006-02-14T23:14:00.000Z""","A Corua (La Corua), Donostia-San Sebastin, Gijn, Ourense (Orense), Santiago de Compostela" +Sri Lanka,"""2006-02-14T23:14:00.000Z""",Jaffna +Sudan,"""2006-02-14T23:14:00.000Z""","al-Qadarif, Omdurman" +Sweden,"""2006-02-14T23:14:00.000Z""",Malm +Switzerland,"""2006-02-14T23:14:00.000Z""","Basel, Bern, Lausanne" +Taiwan,"""2006-02-14T23:14:00.000Z""","Changhwa, Chiayi, Chungho, Fengshan, Hsichuh, Lungtan, Nantou, Tanshui, Touliu, Tsaotun" +Tanzania,"""2006-02-14T23:14:00.000Z""","Mwanza, Tabora, Zanzibar" +Thailand,"""2006-02-14T23:14:00.000Z""","Nakhon Sawan, Pak Kret, Songkhla" +Tonga,"""2006-02-14T23:14:00.000Z""",Nukualofa +Tunisia,"""2006-02-14T23:14:00.000Z""",Sousse +Turkey,"""2006-02-14T23:14:00.000Z""","Adana, Balikesir, Batman, Denizli, Eskisehir, Gaziantep, Inegl, Kilis, Ktahya, Osmaniye, Sivas, Sultanbeyli, Tarsus, Tokat, Usak" +Turkmenistan,"""2006-02-14T23:14:00.000Z""",Ashgabat +Tuvalu,"""2006-02-14T23:14:00.000Z""",Funafuti +Ukraine,"""2006-02-14T23:14:00.000Z""","Kamjanets-Podilskyi, Konotop, Mukateve, ostka, Simferopol, Sumy" +United Arab Emirates,"""2006-02-14T23:14:00.000Z""","Abu Dhabi, al-Ayn, Sharja" +United Kingdom,"""2006-02-14T23:14:00.000Z""","Bradford, Dundee, London, Southampton, Southend-on-Sea, Southport, Stockport, York" +United States,"""2006-02-14T23:14:00.000Z""","Akron, Arlington, Augusta-Richmond County, Aurora, Bellevue, Brockton, Cape Coral, Citrus Heights, Clarksville, Compton, Dallas, Dayton, El Monte, Fontana, Garden Grove, Garland, Grand Prairie, Greensboro, Joliet, Kansas City, Lancaster, Laredo, Lincoln, Manchester, Memphis" +Venezuela,"""2006-02-14T23:14:00.000Z""","Barcelona, Caracas, Cuman, Maracabo, Ocumare del Tuy, Valencia, Valle de la Pascua" +Vietnam,"""2006-02-14T23:14:00.000Z""","Cam Ranh, Haiphong, Hanoi, Nam Dinh, Nha Trang, Vinh" +"Virgin Islands, U.S.","""2006-02-14T23:14:00.000Z""",Charlotte Amalie +Yemen,"""2006-02-14T23:14:00.000Z""","Aden, Hodeida, Sanaa, Taizz" +Yugoslavia,"""2006-02-14T23:14:00.000Z""","Kragujevac, Novi Sad" +Zambia,"""2006-02-14T23:14:00.000Z""",Kitwe \ No newline at end of file diff --git a/scripts/playwright/package-lock.json b/scripts/playwright/package-lock.json index af4778f1d0..18132ac04f 100644 --- a/scripts/playwright/package-lock.json +++ b/scripts/playwright/package-lock.json @@ -29,6 +29,7 @@ "husky": "^8.0.1", "lint-staged": "^13.0.3", "mysql2": "^2.3.3", + "pg": "^8.8.0", "prettier": "^2.7.1", "promised-sqlite3": "^1.2.0" } @@ -841,6 +842,15 @@ "node": ">=8" } }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3628,6 +3638,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3686,6 +3702,87 @@ "node": ">=8" } }, + "node_modules/pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "dev": true, + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==", + "dev": true + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "dev": true, + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==", + "dev": true + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dev": true, + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dev": true, + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3722,6 +3819,45 @@ "node": ">=14" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dev": true, + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4273,6 +4409,15 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4797,6 +4942,15 @@ "node": ">=0.8" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -5394,6 +5548,12 @@ "fill-range": "^7.0.1" } }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "dev": true + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -7459,6 +7619,12 @@ "aggregate-error": "^3.0.0" } }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", + "dev": true + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7502,6 +7668,68 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "dev": true, + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==", + "dev": true + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dev": true + }, + "pg-pool": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "dev": true, + "requires": {} + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==", + "dev": true + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dev": true, + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dev": true, + "requires": { + "split2": "^4.1.0" + } + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -7520,6 +7748,33 @@ "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", "dev": true }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dev": true + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "dev": true + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "dev": true + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dev": true, + "requires": { + "xtend": "^4.0.0" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7917,6 +8172,12 @@ "is-fullwidth-code-point": "^3.0.0" } }, + "split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "dev": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -8320,6 +8581,12 @@ "word": "~0.3.0" } }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/scripts/playwright/package.json b/scripts/playwright/package.json index f5627b0d49..6d1263062f 100644 --- a/scripts/playwright/package.json +++ b/scripts/playwright/package.json @@ -5,11 +5,15 @@ "main": "index.js", "scripts": { "test": "TRACE=true npx playwright test --workers=4", - "test:repeat": "TRACE=true npx playwright test --workers=4 --repeat-each=10", + "test:shard:1": "TRACE=true npx playwright test --workers=4 --shard=1/2", + "test:shard:2": "TRACE=true npx playwright test --workers=4 --shard=2/2", + "test:repeat": "TRACE=true npx playwright test --workers=4 --repeat-each=12", "test:quick": "TRACE=true PW_QUICK_TEST=1 npx playwright test --workers=4", "test:debug": "./startPlayWrightServer.sh; PW_TEST_REUSE_CONTEXT=1 PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:31000/ PWDEBUG=console npx playwright test -c playwright.config.ts --headed --project=chromium --retries 0 --timeout 0 --workers 1 --max-failures=1", "test:debug:quick:sqlite": "./startPlayWrightServer.sh; PW_QUICK_TEST=1 PW_TEST_REUSE_CONTEXT=1 PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:31000/ PWDEBUG=console npx playwright test -c playwright.config.ts --headed --project=chromium --retries 0 --timeout 5 --workers 1 --max-failures=1", "ci:test": "npx playwright test --workers=2", + "ci:test:shard:1": "npx playwright test --workers=2 --shard=1/2", + "ci:test:shard:2": "npx playwright test --workers=2 --shard=2/2", "ci:test:mysql": "E2E_DB_TYPE=mysql npx playwright test --workers=2", "ci:test:pg": "E2E_DB_TYPE=pg npx playwright test --workers=2" }, @@ -27,11 +31,12 @@ "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-functional": "^3.0.2", "eslint-plugin-import": "^2.22.0", - "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-json": "^3.1.0", + "eslint-plugin-prettier": "^4.0.0", "husky": "^8.0.1", "lint-staged": "^13.0.3", "mysql2": "^2.3.3", + "pg": "^8.8.0", "prettier": "^2.7.1", "promised-sqlite3": "^1.2.0" }, diff --git a/scripts/playwright/pages/Dashboard/Settings/Metadata.ts b/scripts/playwright/pages/Dashboard/Settings/Metadata.ts index bb498bed97..93064e9ee9 100644 --- a/scripts/playwright/pages/Dashboard/Settings/Metadata.ts +++ b/scripts/playwright/pages/Dashboard/Settings/Metadata.ts @@ -31,18 +31,17 @@ export class MetaDataPage extends BasePage { } async verifyRow({ index, model, state }: { index: number; model: string; state: string }) { - await expect - .poll(async () => { - return await this.get() - .locator(`tr.ant-table-row`) - .nth(index) - .locator(`td.ant-table-cell`) - .nth(0) - .textContent(); - }) - .toContain(model); - await expect( - await this.get().locator(`tr.ant-table-row`).nth(index).locator(`td.ant-table-cell`).nth(1).textContent() - ).toContain(state); + await expect(this.get().locator(`tr.ant-table-row`).nth(index).locator(`td.ant-table-cell`).nth(0)).toHaveText( + model, + { + ignoreCase: true, + } + ); + await expect(this.get().locator(`tr.ant-table-row`).nth(index).locator(`td.ant-table-cell`).nth(1)).toHaveText( + state, + { + ignoreCase: true, + } + ); } } diff --git a/scripts/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts b/scripts/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts index 96f347d736..ca999fb118 100644 --- a/scripts/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts +++ b/scripts/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts @@ -55,7 +55,8 @@ export class ToolbarViewMenuPage extends BasePage { // Get API Snippet // ERD View - async click({ menu, subMenu }: { menu: string; subMenu?: string }) { + // todo: Move verification out of the click method + async click({ menu, subMenu, verificationInfo }: { menu: string; subMenu?: string; verificationInfo?: any }) { await this.viewsMenuBtn.click(); await this.get().locator(`.ant-dropdown-menu-title-content:has-text("${menu}")`).first().click(); if (subMenu) { @@ -65,7 +66,7 @@ export class ToolbarViewMenuPage extends BasePage { downloadLocator: await this.rootPage .locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`) .last(), - expectedDataFile: './fixtures/expectedBaseDownloadData.txt', + expectedDataFile: verificationInfo?.verificationFile ?? './fixtures/expectedBaseDownloadData.txt', }); } else { await this.rootPage.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`).last().click(); diff --git a/scripts/playwright/quickTests/commonTest.ts b/scripts/playwright/quickTests/commonTest.ts index 6a5f559dc0..3d5d0dc6bf 100644 --- a/scripts/playwright/quickTests/commonTest.ts +++ b/scripts/playwright/quickTests/commonTest.ts @@ -1,7 +1,7 @@ import { DashboardPage } from '../pages/Dashboard'; import { ProjectsPage } from '../pages/ProjectsPage'; import { NcContext } from '../setup'; -import { isMysql } from '../setup/db'; +import { isMysql, isPg } from '../setup/db'; // normal fields const recordCells = { @@ -103,7 +103,7 @@ const quickVerify = async ({ await dashboard.grid.cell.verifyVirtualCell({ index: cellIndex, columnHeader: 'Actor', - value: isMysql(context) ? ['Actor1'] : recordsVirtualCells.Actor, + value: isMysql(context) || isPg(context) ? ['Actor1'] : recordsVirtualCells.Actor, }); // Status (from Actor) diff --git a/scripts/playwright/scripts/docker-compose-playwright-pg.yml b/scripts/playwright/scripts/docker-compose-playwright-pg.yml new file mode 100644 index 0000000000..4998de8d00 --- /dev/null +++ b/scripts/playwright/scripts/docker-compose-playwright-pg.yml @@ -0,0 +1,15 @@ +version: "2.1" + +services: + pg96: + image: postgres:15 + restart: always + environment: + POSTGRES_PASSWORD: password + ports: + - 5432:5432 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 diff --git a/scripts/playwright/setup/db.ts b/scripts/playwright/setup/db.ts index 1bdddd08d3..8b61ccc4c5 100644 --- a/scripts/playwright/setup/db.ts +++ b/scripts/playwright/setup/db.ts @@ -1,5 +1,6 @@ import { NcContext } from '.'; const mysql = require('mysql2'); +const { Client } = require('pg'); import { PromisedDatabase } from 'promised-sqlite3'; const sqliteDb = new PromisedDatabase(); @@ -10,6 +11,23 @@ const isSqlite = (context: NcContext) => context.dbType === 'sqlite'; const isPg = (context: NcContext) => context.dbType === 'pg'; +const pg_credentials = () => ({ + user: 'postgres', + host: 'localhost', + database: `sakila_${process.env.TEST_PARALLEL_INDEX}`, + password: 'password', + port: 5432, +}); + +const pgExec = async (query: string) => { + // open pg client connection + const client = new Client(pg_credentials()); + await client.connect(); + + await client.query(query); + await client.end(); +}; + const mysqlExec = async query => { // creates a new mysql connection using credentials from cypress.json env's const connection = mysql.createConnection({ @@ -42,4 +60,4 @@ async function sqliteExec(query) { await sqliteDb.close(); } -export { sqliteExec, mysqlExec, isMysql, isSqlite, isPg }; +export { sqliteExec, mysqlExec, isMysql, isSqlite, isPg, pgExec }; diff --git a/scripts/playwright/setup/index.ts b/scripts/playwright/setup/index.ts index 018864183d..2c3f48481d 100644 --- a/scripts/playwright/setup/index.ts +++ b/scripts/playwright/setup/index.ts @@ -11,14 +11,21 @@ const setup = async ({ page, isEmptyProject }: { page: Page; isEmptyProject?: bo let dbType = process.env.CI ? process.env.E2E_DB_TYPE : process.env.E2E_DEV_DB_TYPE; dbType = dbType || 'mysql'; - const response = await axios.post(`http://localhost:8080/api/v1/meta/test/reset`, { - parallelId: process.env.TEST_PARALLEL_INDEX, - dbType, - isEmptyProject, - }); - - if (response.status !== 200) { - console.error('Failed to reset test data', response.data); + if (!process.env.CI) console.time(`setup ${process.env.TEST_PARALLEL_INDEX}`); + let response; + try { + response = await axios.post(`http://localhost:8080/api/v1/meta/test/reset`, { + parallelId: process.env.TEST_PARALLEL_INDEX, + dbType, + isEmptyProject, + }); + } catch (e) { + console.error(`Error resetting project: ${process.env.TEST_PARALLEL_INDEX}`, e); + } + if (!process.env.CI) console.timeEnd(`setup ${process.env.TEST_PARALLEL_INDEX}`); + + if (response.status !== 200 || !response.data?.token || !response.data?.project) { + console.error('Failed to reset test data', response.data, response.status); throw new Error('Failed to reset test data'); } const token = response.data.token; diff --git a/scripts/playwright/tests/columnFormula.spec.ts b/scripts/playwright/tests/columnFormula.spec.ts index d07056db8a..d9a1ed5763 100644 --- a/scripts/playwright/tests/columnFormula.spec.ts +++ b/scripts/playwright/tests/columnFormula.spec.ts @@ -1,7 +1,7 @@ import { test } from '@playwright/test'; import { DashboardPage } from '../pages/Dashboard'; -import setup from '../setup'; -import { isSqlite } from '../setup/db'; +import setup, { NcContext } from '../setup'; +import { isPg, isSqlite } from '../setup/db'; // Add formula to be verified here & store expected results for 5 rows // Column data from City table (Sakila DB) @@ -13,7 +13,7 @@ import { isSqlite } from '../setup/db'; * Acua 2006-02-15 04:45:25 1789 Saint-Denis Parkway Mexico * Adana 2006-02-15 04:45:25 663 Baha Blanca Parkway Turkey */ -const formulaData = [ +const formulaDataByDbType = (context: NcContext) => [ { formula: '1 + 1', result: ['2', '2', '2', '2', '2'], @@ -46,7 +46,9 @@ const formulaData = [ }, { formula: `LOG({CityId}) + EXP({CityId}) + POWER({CityId}, 3) + SQRT({CountryId})`, - result: ['13.04566088154786', '25.137588417628013', '58.23402483297667', '127.73041108667896', '284.8714548168068'], + result: isPg(context) + ? ['13.04566088154786', '24.74547123273205', '57.61253379902822', '126.94617671688704', '283.9609869087087'] + : ['13.04566088154786', '25.137588417628013', '58.23402483297667', '127.73041108667896', '284.8714548168068'], }, { formula: `NOW()`, @@ -75,10 +77,10 @@ test.describe('Virtual Columns', () => { test('Formula', async () => { // close 'Team & Auth' tab + const formulaData = formulaDataByDbType(context); await dashboard.closeTab({ title: 'Team & Auth' }); await dashboard.treeView.openTable({ title: 'City' }); - // Create formula column await dashboard.grid.column.create({ title: 'NC_MATH_0', diff --git a/scripts/playwright/tests/columnRelationalExtendedTests.spec.ts b/scripts/playwright/tests/columnRelationalExtendedTests.spec.ts index 69bc153ec9..8ea6fa67d9 100644 --- a/scripts/playwright/tests/columnRelationalExtendedTests.spec.ts +++ b/scripts/playwright/tests/columnRelationalExtendedTests.spec.ts @@ -1,6 +1,7 @@ import { test } from '@playwright/test'; import { DashboardPage } from '../pages/Dashboard'; import setup from '../setup'; +import { isPg } from '../setup/db'; test.describe('Relational Columns', () => { let dashboard: DashboardPage; diff --git a/scripts/playwright/tests/erd.spec.ts b/scripts/playwright/tests/erd.spec.ts index 3e186fc0ef..ac45036d61 100644 --- a/scripts/playwright/tests/erd.spec.ts +++ b/scripts/playwright/tests/erd.spec.ts @@ -61,7 +61,7 @@ test.describe('Erd', () => { await erd.dbClickShowColumnNames(); if (isPg(context)) { - await erd.verifyNodesCount(mysqlSakilaTables.length); + await erd.verifyNodesCount(sakilaTables.length); await erd.verifyEdgesCount({ count: 32, circleCount: 29, diff --git a/scripts/playwright/tests/import.spec.ts b/scripts/playwright/tests/import.spec.ts index fae59761a3..c47e4d3783 100644 --- a/scripts/playwright/tests/import.spec.ts +++ b/scripts/playwright/tests/import.spec.ts @@ -3,7 +3,7 @@ import { airtableApiBase, airtableApiKey } from '../constants'; import { DashboardPage } from '../pages/Dashboard'; import { quickVerify } from '../quickTests/commonTest'; import setup from '../setup'; -import { isSqlite } from '../setup/db'; +import { isPg, isSqlite } from '../setup/db'; test.describe('Import', () => { let dashboard: DashboardPage; @@ -49,7 +49,7 @@ test.describe('Import', () => { result: expected, }); - const recordCells = { Number: '1', Float: isSqlite(context) ? '1.1' : '1.10', Text: 'abc' }; + const recordCells = { Number: '1', Float: isSqlite(context) || isPg(context) ? '1.1' : '1.10', Text: 'abc' }; for (const [key, value] of Object.entries(recordCells)) { await dashboard.grid.cell.verify({ diff --git a/scripts/playwright/tests/metaSync.spec.ts b/scripts/playwright/tests/metaSync.spec.ts index 10ea0146e9..086d2bcb10 100644 --- a/scripts/playwright/tests/metaSync.spec.ts +++ b/scripts/playwright/tests/metaSync.spec.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { DashboardPage } from '../pages/Dashboard'; import { SettingsPage, SettingTab } from '../pages/Dashboard/Settings'; import setup, { NcContext } from '../setup'; -import { isSqlite, mysqlExec, sqliteExec } from '../setup/db'; +import { isMysql, isPg, isSqlite, mysqlExec, pgExec, sqliteExec } from '../setup/db'; // todo: Enable when view bug is fixed test.describe('Meta sync', () => { @@ -23,6 +23,9 @@ test.describe('Meta sync', () => { case 'mysql': dbExec = mysqlExec; break; + case 'pg': + dbExec = pgExec; + break; } }); @@ -37,37 +40,41 @@ test.describe('Meta sync', () => { await settings.metaData.clickReload(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: `table1`, state: 'New table', }); await settings.metaData.verifyRow({ - index: 17, + index: isPg(context) ? 22 : 17, model: `table2`, state: 'New table', }); await settings.metaData.sync(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: 'Table1', state: 'No change identified', }); await settings.metaData.verifyRow({ - index: 17, + index: isPg(context) ? 22 : 17, model: 'Table2', state: 'No change identified', }); if (!isSqlite(context)) { // Add relation - await dbExec(`ALTER TABLE table1 ADD INDEX fk1_idx (col1 ASC) VISIBLE`); - await dbExec( - `ALTER TABLE table1 ADD CONSTRAINT fk1 FOREIGN KEY (col1) REFERENCES table2 (id) ON DELETE NO ACTION ON UPDATE NO ACTION` - ); + if (isPg(context)) { + await dbExec(`ALTER TABLE table1 ADD CONSTRAINT fk_idx FOREIGN KEY (id) REFERENCES table2 (id);`); + } else { + await dbExec(`ALTER TABLE table1 ADD INDEX fk1_idx (col1 ASC) VISIBLE`); + await dbExec( + `ALTER TABLE table1 ADD CONSTRAINT fk1 FOREIGN KEY (col1) REFERENCES table2 (id) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + } await settings.metaData.clickReload(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: 'Table1', state: 'New relation added', }); @@ -75,17 +82,21 @@ test.describe('Meta sync', () => { //verify after sync await settings.metaData.sync(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: 'Table1', state: 'No change identified', }); // Remove relation - await dbExec(`ALTER TABLE table1 DROP FOREIGN KEY fk1`); - await dbExec(`ALTER TABLE table1 DROP INDEX fk1_idx`); + if (isPg(context)) { + await dbExec(`ALTER TABLE table1 DROP CONSTRAINT fk_idx`); + } else { + await dbExec(`ALTER TABLE table1 DROP FOREIGN KEY fk1`); + await dbExec(`ALTER TABLE table1 DROP INDEX fk1_idx`); + } await settings.metaData.clickReload(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: 'Table1', state: 'Relation removed', }); @@ -93,21 +104,24 @@ test.describe('Meta sync', () => { //verify after sync await settings.metaData.sync(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: 'Table1', state: 'No change identified', }); } // Add column - await dbExec( - isSqlite(context) - ? `ALTER TABLE table1 ADD COLUMN newCol TEXT NULL` - : `ALTER TABLE table1 ADD COLUMN newCol VARCHAR(45) NULL AFTER id` - ); + if (isSqlite(context)) { + await dbExec(`ALTER TABLE table1 ADD COLUMN newCol TEXT NULL`); + } else if (isMysql(context)) { + await dbExec(`ALTER TABLE table1 ADD COLUMN newCol VARCHAR(45) NULL AFTER id`); + } else if (isPg(context)) { + await dbExec(`ALTER TABLE table1 ADD COLUMN newCol INT`); + } + await settings.metaData.clickReload(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: `Table1`, state: 'New column(newCol)', }); @@ -115,20 +129,23 @@ test.describe('Meta sync', () => { //verify after sync await settings.metaData.sync(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: 'Table1', state: 'No change identified', }); // Edit column - await dbExec( - isSqlite(context) - ? `ALTER TABLE table1 RENAME COLUMN newCol TO newColName` - : `ALTER TABLE table1 CHANGE COLUMN newCol newColName VARCHAR(45) NULL DEFAULT NULL` - ); + if (isSqlite(context)) { + await dbExec(`ALTER TABLE table1 RENAME COLUMN newCol TO newColName`); + } else if (isMysql(context)) { + await dbExec(`ALTER TABLE table1 CHANGE COLUMN newCol newColName VARCHAR(45) NULL DEFAULT NULL`); + } else if (isPg(context)) { + await dbExec(`ALTER TABLE table1 RENAME COLUMN newCol TO newColName`); + } + await settings.metaData.clickReload(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: `Table1`, state: 'New column(newColName), Column removed(newCol)', }); @@ -136,7 +153,7 @@ test.describe('Meta sync', () => { //verify after sync await settings.metaData.sync(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: 'Table1', state: 'No change identified', }); @@ -147,7 +164,7 @@ test.describe('Meta sync', () => { await dbExec(`ALTER TABLE table1 DROP COLUMN newColName`); await settings.metaData.clickReload(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: `Table1`, state: 'Column removed(newColName)', }); @@ -155,7 +172,7 @@ test.describe('Meta sync', () => { //verify after sync await settings.metaData.sync(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: 'Table1', state: 'No change identified', }); @@ -166,12 +183,12 @@ test.describe('Meta sync', () => { await dbExec(`DROP TABLE table2`); await settings.metaData.clickReload(); await settings.metaData.verifyRow({ - index: 16, + index: isPg(context) ? 21 : 16, model: `table1`, state: 'Table removed', }); await settings.metaData.verifyRow({ - index: 17, + index: isPg(context) ? 22 : 17, model: `table2`, state: 'Table removed', }); @@ -190,7 +207,19 @@ test.describe('Meta sync', () => { model: 'FilmList', state: 'No change identified', }); - } else { + } + if (isPg(context)) { + await settings.metaData.verifyRow({ + index: 21, + model: 'ActorInfo', + state: 'No change identified', + }); + await settings.metaData.verifyRow({ + index: 22, + model: 'CustomerList', + state: 'No change identified', + }); + } else if (isMysql(context)) { await settings.metaData.verifyRow({ index: 16, model: 'ActorInfo', diff --git a/scripts/playwright/tests/viewGridShare.spec.ts b/scripts/playwright/tests/viewGridShare.spec.ts index 7d4569b035..948aa11e19 100644 --- a/scripts/playwright/tests/viewGridShare.spec.ts +++ b/scripts/playwright/tests/viewGridShare.spec.ts @@ -1,7 +1,7 @@ import { test } from '@playwright/test'; import { DashboardPage } from '../pages/Dashboard'; import setup from '../setup'; -import { isSqlite } from '../setup/db'; +import { isMysql, isPg, isSqlite } from '../setup/db'; test.describe('Shared view', () => { let dashboard: DashboardPage; @@ -77,13 +77,14 @@ test.describe('Shared view', () => { await sharedPage.grid.column.verify(column); } - const expectedRecordsByDb = isSqlite(context) ? sqliteExpectedRecords : expectedRecords; + const expectedRecordsByDb = isSqlite(context) || isPg(context) ? sqliteExpectedRecords : expectedRecords; // verify order of records (original sort & filter) for (const record of expectedRecordsByDb) { await sharedPage.grid.cell.verify(record); } - const expectedVirtualRecordsByDb = isSqlite(context) ? sqliteExpectedVirtualRecords : expectedVirtualRecords; + const expectedVirtualRecordsByDb = + isSqlite(context) || isPg(context) ? sqliteExpectedVirtualRecords : expectedVirtualRecords; // verify virtual records for (const record of expectedVirtualRecordsByDb) { @@ -104,7 +105,7 @@ test.describe('Shared view', () => { isLocallySaved: true, }); - if (!isSqlite(context)) { + if (isMysql(context)) { await sharedPage.grid.toolbar.filter.addNew({ columnTitle: 'District', value: 'Ta', @@ -120,7 +121,7 @@ test.describe('Shared view', () => { await sharedPage.grid.column.verify(column); } - const expectedRecordsByDb2 = isSqlite(context) ? sqliteExpectedRecords2 : expectedRecords2; + const expectedRecordsByDb2 = isSqlite(context) || isPg(context) ? sqliteExpectedRecords2 : expectedRecords2; // verify order of records (original sort & filter) for (const record of expectedRecordsByDb2) { await sharedPage.grid.cell.verify(record); @@ -134,7 +135,7 @@ test.describe('Shared view', () => { // verify download await sharedPage.grid.toolbar.clickDownload( 'Download as CSV', - isSqlite(context) ? 'expectedDataSqlite.txt' : 'expectedData.txt' + isSqlite(context) || isPg(context) ? 'expectedDataSqlite.txt' : 'expectedData.txt' ); }); diff --git a/scripts/playwright/tests/viewKanban.spec.ts b/scripts/playwright/tests/viewKanban.spec.ts index 231939d8f5..f28c1b94ba 100644 --- a/scripts/playwright/tests/viewKanban.spec.ts +++ b/scripts/playwright/tests/viewKanban.spec.ts @@ -3,7 +3,7 @@ import { DashboardPage } from '../pages/Dashboard'; import { ToolbarPage } from '../pages/Dashboard/common/Toolbar'; import setup from '../setup'; -import { isSqlite } from '../setup/db'; +import { isPg, isSqlite } from '../setup/db'; const filmRatings = ['G', 'PG', 'PG-13', 'R', 'NC-17']; @@ -20,7 +20,13 @@ test.describe('View', () => { await dashboard.closeTab({ title: 'Team & Auth' }); await dashboard.treeView.openTable({ title: 'Film' }); - if (isSqlite(context)) { + if (isPg(context)) { + // Since these view depend on the Ratings column of the Film table + await dashboard.treeView.deleteTable({ title: 'NicerButSlowerFilmList' }); + await dashboard.treeView.deleteTable({ title: 'FilmList' }); + } + + if (isSqlite(context) || isPg(context)) { await dashboard.grid.column.openEdit({ title: 'Rating' }); await dashboard.grid.column.selectType({ type: 'SingleSelect' }); let count = 0; diff --git a/scripts/playwright/tests/viewMenu.spec.ts b/scripts/playwright/tests/viewMenu.spec.ts index 6912a3d2d1..53bc17246c 100644 --- a/scripts/playwright/tests/viewMenu.spec.ts +++ b/scripts/playwright/tests/viewMenu.spec.ts @@ -1,6 +1,7 @@ import { test } from '@playwright/test'; import { DashboardPage } from '../pages/Dashboard'; import setup from '../setup'; +import { isPg } from '../setup/db'; test.describe('Grid view locked', () => { let dashboard: DashboardPage; @@ -44,6 +45,9 @@ test.describe('Grid view locked', () => { await dashboard.grid.toolbar.viewsMenu.click({ menu: 'Download', subMenu: 'Download as CSV', + verificationInfo: { + verificationFile: isPg(context) ? './fixtures/expectedBaseDownloadDataPg.txt' : null, + }, }); }); });