Browse Source

fix(test): Integrated postgres to CI with sharding and removed other tests from CI for the timebeing

pull/4278/head
Muhammed Mustafa 2 years ago
parent
commit
3e1f7f22e4
  1. 20
      .github/workflows/ci-cd.yml
  2. 9
      .github/workflows/playwright-test-workflow.yml
  3. 2
      packages/nocodb/package.json
  4. 33
      packages/nocodb/src/lib/services/test/TestResetService/index.ts
  5. 50
      packages/nocodb/src/lib/services/test/TestResetService/resetMysqlSakilaProject.ts
  6. 115
      packages/nocodb/src/lib/services/test/TestResetService/resetPgSakilaProject.ts
  7. 110
      scripts/playwright/fixtures/expectedBaseDownloadDataPg.txt
  8. 267
      scripts/playwright/package-lock.json
  9. 9
      scripts/playwright/package.json
  10. 25
      scripts/playwright/pages/Dashboard/Settings/Metadata.ts
  11. 5
      scripts/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts
  12. 4
      scripts/playwright/quickTests/commonTest.ts
  13. 15
      scripts/playwright/scripts/docker-compose-playwright-pg.yml
  14. 20
      scripts/playwright/setup/db.ts
  15. 23
      scripts/playwright/setup/index.ts
  16. 12
      scripts/playwright/tests/columnFormula.spec.ts
  17. 1
      scripts/playwright/tests/columnRelationalExtendedTests.spec.ts
  18. 2
      scripts/playwright/tests/erd.spec.ts
  19. 4
      scripts/playwright/tests/import.spec.ts
  20. 97
      scripts/playwright/tests/metaSync.spec.ts
  21. 13
      scripts/playwright/tests/viewGridShare.spec.ts
  22. 10
      scripts/playwright/tests/viewKanban.spec.ts
  23. 4
      scripts/playwright/tests/viewMenu.spec.ts

20
.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
db: pg
shard: 2

9
.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

2
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\"",

33
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') {

50
packages/nocodb/src/lib/services/test/TestResetService/resetMysqlSakilaProject.ts

@ -45,13 +45,28 @@ const isSakilaMysqlToBeReset = async (
parallelId: string,
project?: Project
) => {
const tablesInDb: Array<string> = await knex.raw(
const tablesInDbInfo: Array<any> = 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;

115
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<string> = (
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;

110
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

267
scripts/playwright/package-lock.json generated

@ -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",

9
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"
},

25
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,
}
);
}
}

5
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();

4
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)

15
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

20
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 };

23
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;

12
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',

1
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;

2
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,

4
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({

97
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',

13
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'
);
});

10
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;

4
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,
},
});
});
});

Loading…
Cancel
Save