Browse Source

Merge pull request #6499 from nocodb/fix/test-fixes

Test fix
pull/6508/head
Muhammed Mustafa 1 year ago committed by GitHub
parent
commit
0bc59c53f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      packages/nc-gui/components.d.ts
  2. 9
      packages/nc-gui/components/general/DeleteModal.vue
  3. 51
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  4. 2
      packages/nocodb/package.json
  5. 27
      pnpm-lock.yaml
  6. 3
      tests/playwright/package.json
  7. 7
      tests/playwright/pages/Account/Users.ts
  8. 32
      tests/playwright/pages/Dashboard/ExpandedForm/index.ts
  9. 42
      tests/playwright/pages/Dashboard/Form/index.ts
  10. 11
      tests/playwright/pages/Dashboard/Grid/Group.ts
  11. 11
      tests/playwright/pages/Dashboard/Grid/index.ts
  12. 17
      tests/playwright/pages/Dashboard/TreeView.ts
  13. 6
      tests/playwright/pages/Dashboard/ViewSidebar/index.ts
  14. 21
      tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
  15. 3
      tests/playwright/pages/Dashboard/common/Cell/CheckboxCell.ts
  16. 3
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  17. 3
      tests/playwright/pages/Dashboard/index.ts
  18. 2
      tests/playwright/tests/db/features/expandedFormUrl.spec.ts
  19. 3
      tests/playwright/tests/db/features/metaLTAR.spec.ts
  20. 6
      tests/playwright/tests/db/features/verticalFillHandle.spec.ts
  21. 2
      tests/playwright/tests/db/general/projectOperations.spec.ts
  22. 9
      tests/playwright/tests/db/usersAccounts/accountUserManagement.spec.ts

10
packages/nc-gui/components.d.ts vendored

@ -52,7 +52,6 @@ declare module '@vue/runtime-core' {
APagination: typeof import('ant-design-vue/es')['Pagination'] APagination: typeof import('ant-design-vue/es')['Pagination']
APopover: typeof import('ant-design-vue/es')['Popover'] APopover: typeof import('ant-design-vue/es')['Popover']
ARadio: typeof import('ant-design-vue/es')['Radio'] ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARate: typeof import('ant-design-vue/es')['Rate'] ARate: typeof import('ant-design-vue/es')['Rate']
ARow: typeof import('ant-design-vue/es')['Row'] ARow: typeof import('ant-design-vue/es')['Row']
@ -81,7 +80,6 @@ declare module '@vue/runtime-core' {
CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default'] CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default']
ClaritySuccessLine: typeof import('~icons/clarity/success-line')['default'] ClaritySuccessLine: typeof import('~icons/clarity/success-line')['default']
IcBaselineMoreVert: typeof import('~icons/ic/baseline-more-vert')['default'] IcBaselineMoreVert: typeof import('~icons/ic/baseline-more-vert')['default']
Icon: typeof import('~icons/ic/on')['default']
IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default'] IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default']
IcRoundEdit: typeof import('~icons/ic/round-edit')['default'] IcRoundEdit: typeof import('~icons/ic/round-edit')['default']
IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default'] IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default']
@ -105,12 +103,9 @@ declare module '@vue/runtime-core' {
MaterialSymbolsVisibility: typeof import('~icons/material-symbols/visibility')['default'] MaterialSymbolsVisibility: typeof import('~icons/material-symbols/visibility')['default']
MaterialSymbolsVisibilityOff: typeof import('~icons/material-symbols/visibility-off')['default'] MaterialSymbolsVisibilityOff: typeof import('~icons/material-symbols/visibility-off')['default']
MaterialSymbolsWarning: typeof import('~icons/material-symbols/warning')['default'] MaterialSymbolsWarning: typeof import('~icons/material-symbols/warning')['default']
MdiAccordionUp: typeof import('~icons/mdi/accordion-up')['default']
MdiAccount: typeof import('~icons/mdi/account')['default'] MdiAccount: typeof import('~icons/mdi/account')['default']
MdiAccountCircleOutline: typeof import('~icons/mdi/account-circle-outline')['default'] MdiAccountCircleOutline: typeof import('~icons/mdi/account-circle-outline')['default']
MdiAccountCircleOutlines: typeof import('~icons/mdi/account-circle-outlines')['default']
MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default'] MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default']
MdiAlpha: typeof import('~icons/mdi/alpha')['default']
MdiAppleKeyboardShift: typeof import('~icons/mdi/apple-keyboard-shift')['default'] MdiAppleKeyboardShift: typeof import('~icons/mdi/apple-keyboard-shift')['default']
MdiArrowDownDropCircle: typeof import('~icons/mdi/arrow-down-drop-circle')['default'] MdiArrowDownDropCircle: typeof import('~icons/mdi/arrow-down-drop-circle')['default']
MdiArrowDownDropCircleOutline: typeof import('~icons/mdi/arrow-down-drop-circle-outline')['default'] MdiArrowDownDropCircleOutline: typeof import('~icons/mdi/arrow-down-drop-circle-outline')['default']
@ -121,9 +116,7 @@ declare module '@vue/runtime-core' {
MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default'] MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default']
MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default'] MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default']
MdiChat: typeof import('~icons/mdi/chat')['default'] MdiChat: typeof import('~icons/mdi/chat')['default']
MdiChatProcessingOutline: typeof import('~icons/mdi/chat-processing-outline')['default']
MdiCheck: typeof import('~icons/mdi/check')['default'] MdiCheck: typeof import('~icons/mdi/check')['default']
MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
MdiChevronLeft: typeof import('~icons/mdi/chevron-left')['default'] MdiChevronLeft: typeof import('~icons/mdi/chevron-left')['default']
MdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] MdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
MdiChevronUp: typeof import('~icons/mdi/chevron-up')['default'] MdiChevronUp: typeof import('~icons/mdi/chevron-up')['default']
@ -151,7 +144,6 @@ declare module '@vue/runtime-core' {
MdiMessageOutline: typeof import('~icons/mdi/message-outline')['default'] MdiMessageOutline: typeof import('~icons/mdi/message-outline')['default']
MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default'] MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default']
MdiMoonFull: typeof import('~icons/mdi/moon-full')['default'] MdiMoonFull: typeof import('~icons/mdi/moon-full')['default']
MdiMoreVert: typeof import('~icons/mdi/more-vert')['default']
MdiPlus: typeof import('~icons/mdi/plus')['default'] MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiReload: typeof import('~icons/mdi/reload')['default'] MdiReload: typeof import('~icons/mdi/reload')['default']
MdiRocketLaunchOutline: typeof import('~icons/mdi/rocket-launch-outline')['default'] MdiRocketLaunchOutline: typeof import('~icons/mdi/rocket-launch-outline')['default']
@ -168,9 +160,7 @@ declare module '@vue/runtime-core' {
MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default'] MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default']
MiCircleWarning: typeof import('~icons/mi/circle-warning')['default'] MiCircleWarning: typeof import('~icons/mi/circle-warning')['default']
NcIconsInbox: typeof import('~icons/nc-icons/inbox')['default'] NcIconsInbox: typeof import('~icons/nc-icons/inbox')['default']
PhLink: typeof import('~icons/ph/link')['default']
PhMagnifyingGlassBold: typeof import('~icons/ph/magnifying-glass-bold')['default'] PhMagnifyingGlassBold: typeof import('~icons/ph/magnifying-glass-bold')['default']
PhTriangleFill: typeof import('~icons/ph/triangle-fill')['default']
RiExternalLinkLine: typeof import('~icons/ri/external-link-line')['default'] RiExternalLinkLine: typeof import('~icons/ri/external-link-line')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']

9
packages/nc-gui/components/general/DeleteModal.vue

@ -57,7 +57,14 @@ onKeyStroke('Enter', () => {
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</NcButton> </NcButton>
<NcButton key="submit" type="danger" html-type="submit" :loading="isLoading" @click="onDelete"> <NcButton
key="submit"
type="danger"
html-type="submit"
:loading="isLoading"
data-testid="nc-delete-modal-delete-btn"
@click="onDelete"
>
{{ `${$t('general.delete')} ${props.entityName}` }} {{ `${$t('general.delete')} ${props.entityName}` }}
<template #loading> <template #loading>
{{ $t('general.deleting') }} {{ $t('general.deleting') }}

51
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -325,13 +325,14 @@ const onConfirmDeleteRowClick = async () => {
showDeleteRowModal.value = false showDeleteRowModal.value = false
await deleteRowById(primaryKey.value) await deleteRowById(primaryKey.value)
message.success('Row deleted') message.success('Row deleted')
if (!props.lastRow) { // if (!props.lastRow) {
await onNext() // await onNext()
} else if (!props.firstRow) { // } else if (!props.firstRow) {
emits('prev') // emits('prev')
} else { // } else {
onClose() // }
} reloadTrigger.trigger()
onClose()
} }
watch( watch(
@ -398,7 +399,7 @@ export default {
<template #overlay> <template #overlay>
<NcMenu> <NcMenu>
<NcMenuItem v-if="!isNew" class="text-gray-700" @click="_loadRow()"> <NcMenuItem v-if="!isNew" class="text-gray-700" @click="_loadRow()">
<div v-e="['c:row-expand:reload']" class="flex gap-2 items-center"> <div v-e="['c:row-expand:reload']" class="flex gap-2 items-center" data-testid="nc-expanded-form-reload">
<component :is="iconMap.reload" class="cursor-pointer" /> <component :is="iconMap.reload" class="cursor-pointer" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>
@ -408,25 +409,34 @@ export default {
class="text-gray-700" class="text-gray-700"
@click="!isNew ? onDuplicateRow() : () => {}" @click="!isNew ? onDuplicateRow() : () => {}"
> >
<div v-e="['c:row-expand:duplicate']" class="flex gap-2 items-center"> <div
v-e="['c:row-expand:duplicate']"
data-testid="nc-expanded-form-duplicate"
class="flex gap-2 items-center"
>
<component :is="iconMap.copy" class="cursor-pointer nc-duplicate-row" /> <component :is="iconMap.copy" class="cursor-pointer nc-duplicate-row" />
Duplicate record Duplicate record
</div> </div>
</NcMenuItem> </NcMenuItem>
<NcDivider /> <NcDivider v-if="isUIAllowed('dataEdit') && !isNew" />
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew" v-if="isUIAllowed('dataEdit') && !isNew"
v-e="['c:row-expand:delete']" v-e="['c:row-expand:delete']"
class="!text-red-500" class="!text-red-500"
@click="!isNew && onDeleteRowClick()" @click="!isNew && onDeleteRowClick()"
> >
<component :is="iconMap.delete" class="cursor-pointer nc-delete-row" /> <component :is="iconMap.delete" data-testid="nc-expanded-form-delete" class="cursor-pointer nc-delete-row" />
Delete record Delete record
</NcMenuItem> </NcMenuItem>
</NcMenu> </NcMenu>
</template> </template>
</NcDropdown> </NcDropdown>
<NcButton type="secondary" class="nc-expand-form-close-btn w-10" @click="onClose"> <NcButton
type="secondary"
class="nc-expand-form-close-btn w-10"
data-testid="nc-expanded-form-close"
@click="onClose"
>
<GeneralIcon icon="close" class="text-md text-gray-700" /> <GeneralIcon icon="close" class="text-md text-gray-700" />
</NcButton> </NcButton>
</div> </div>
@ -542,7 +552,7 @@ export default {
<template #overlay> <template #overlay>
<NcMenu> <NcMenu>
<NcMenuItem v-if="!isNew" class="text-gray-700" @click="_loadRow()"> <NcMenuItem v-if="!isNew" class="text-gray-700" @click="_loadRow()">
<div v-e="['c:row-expand:reload']" class="flex gap-2 items-center"> <div v-e="['c:row-expand:reload']" class="flex gap-2 items-center" data-testid="nc-expanded-form-reload">
<component :is="iconMap.reload" class="cursor-pointer" /> <component :is="iconMap.reload" class="cursor-pointer" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>
@ -554,8 +564,10 @@ export default {
class="!text-red-500" class="!text-red-500"
@click="!isNew && onDeleteRowClick()" @click="!isNew && onDeleteRowClick()"
> >
<component :is="iconMap.delete" class="cursor-pointer nc-delete-row" /> <div data-testid="nc-expanded-form-delete">
Delete record <component :is="iconMap.delete" class="cursor-pointer nc-delete-row" />
Delete record
</div>
</NcMenuItem> </NcMenuItem>
</NcMenu> </NcMenu>
</template> </template>
@ -566,12 +578,19 @@ export default {
v-if="isMobileMode" v-if="isMobileMode"
type="secondary" type="secondary"
size="medium" size="medium"
data-testid="nc-expanded-form-save"
class="nc-expand-form-save-btn !xs:(text-base)" class="nc-expand-form-save-btn !xs:(text-base)"
@click="onClose" @click="onClose"
> >
<div class="px-1">Close</div> <div class="px-1">Close</div>
</NcButton> </NcButton>
<NcButton type="primary" size="medium" class="nc-expand-form-save-btn !xs:(text-base)" @click="save"> <NcButton
data-testid="nc-expanded-form-save"
type="primary"
size="medium"
class="nc-expand-form-save-btn !xs:(text-base)"
@click="save"
>
<div class="xs:px-1">Save</div> <div class="xs:px-1">Save</div>
</NcButton> </NcButton>
</div> </div>

2
packages/nocodb/package.json

@ -37,7 +37,7 @@
"watch:run:pg": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG --log-error --project tsconfig.json\"", "watch:run:pg": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG --log-error --project tsconfig.json\"",
"watch:run:playwright:mysql": "rm -f ./test_noco.db; cross-env DB_TYPE=mysql NC_DB=\"mysql2://localhost:3306?u=root&p=password&d=pw_ncdb\" 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:mysql": "rm -f ./test_noco.db; cross-env DB_TYPE=mysql NC_DB=\"mysql2://localhost:3306?u=root&p=password&d=pw_ncdb\" 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:pg": "rm -f ./test_noco.db; cross-env DB_TYPE=pg NC_DB=\"pg://localhost:5432?u=postgres&p=password&d=pw_ncdb\" 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:pg": "rm -f ./test_noco.db; cross-env DB_TYPE=pg NC_DB=\"pg://localhost:5432?u=postgres&p=password&d=pw_ncdb\" 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": "rm -f ./test_noco.db; cross-env DB_TYPE=sqlite DATABASE_URL=sqlite:./test_noco.db PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true NC_SNAPSHOT_WINDOW_SEC=3 nodemon -e ts,js -w ./src -x \"ts-node src/run/testDocker --log-error --project tsconfig.json\"", "watch:run:playwright": "rm -f ./test_*.db; cross-env DB_TYPE=sqlite DATABASE_URL=sqlite:./test_noco.db PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true NC_SNAPSHOT_WINDOW_SEC=3 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 ../../tests/playwright/fixtures/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:quick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/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": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"", "watch:run:playwright:pg:cyquick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"",
"test:unit": "cross-env EE=false TS_NODE_PROJECT=./tests/unit/tsconfig.json mocha -r ts-node/register tests/unit/index.test.ts --recursive --timeout 300000 --exit --delay", "test:unit": "cross-env EE=false TS_NODE_PROJECT=./tests/unit/tsconfig.json mocha -r ts-node/register tests/unit/index.test.ts --recursive --timeout 300000 --exit --delay",

27
pnpm-lock.yaml

@ -932,8 +932,8 @@ importers:
version: 0.18.5 version: 0.18.5
devDependencies: devDependencies:
'@playwright/test': '@playwright/test':
specifier: 1.36.1 specifier: 1.38.0
version: 1.36.1 version: 1.38.0
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^6.1.0 specifier: ^6.1.0
version: 6.1.0(@typescript-eslint/parser@6.1.0)(eslint@8.33.0)(typescript@5.2.2) version: 6.1.0(@typescript-eslint/parser@6.1.0)(eslint@8.33.0)(typescript@5.2.2)
@ -5610,15 +5610,12 @@ packages:
dev: true dev: true
optional: true optional: true
/@playwright/test@1.36.1: /@playwright/test@1.38.0:
resolution: {integrity: sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==} resolution: {integrity: sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==}
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
dependencies: dependencies:
'@types/node': 20.3.1 playwright: 1.38.0
playwright-core: 1.36.1
optionalDependencies:
fsevents: 2.3.2
dev: true dev: true
/@polka/url@1.0.0-next.21: /@polka/url@1.0.0-next.21:
@ -18477,10 +18474,20 @@ packages:
mlly: 1.4.1 mlly: 1.4.1
pathe: 1.1.1 pathe: 1.1.1
/playwright-core@1.36.1: /playwright-core@1.38.0:
resolution: {integrity: sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==} resolution: {integrity: sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==}
engines: {node: '>=16'}
hasBin: true
dev: true
/playwright@1.38.0:
resolution: {integrity: sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==}
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
dependencies:
playwright-core: 1.38.0
optionalDependencies:
fsevents: 2.3.2
dev: true dev: true
/plimit-lit@1.5.0: /plimit-lit@1.5.0:

3
tests/playwright/package.json

@ -34,6 +34,7 @@
"ci:test:shard:2": "pnpm exec playwright test --workers=2 --shard=2/4", "ci:test:shard:2": "pnpm exec playwright test --workers=2 --shard=2/4",
"ci:test:shard:3": "pnpm exec playwright test --workers=2 --shard=3/4", "ci:test:shard:3": "pnpm exec playwright test --workers=2 --shard=3/4",
"ci:test:shard:4": "pnpm exec playwright test --workers=2 --shard=4/4", "ci:test:shard:4": "pnpm exec playwright test --workers=2 --shard=4/4",
"ci:test:flaky:repeat": "pnpm exec playwright test --workers=4 --grep @flaky --repeat-each=3",
"ci:test:mysql": "E2E_DB_TYPE=mysql pnpm exec playwright test --workers=2", "ci:test:mysql": "E2E_DB_TYPE=mysql pnpm exec playwright test --workers=2",
"ci:test:pg": "E2E_DB_TYPE=pg pnpm exec playwright test --workers=2", "ci:test:pg": "E2E_DB_TYPE=pg pnpm exec playwright test --workers=2",
"preinstall": "npx only-allow pnpm" "preinstall": "npx only-allow pnpm"
@ -48,7 +49,7 @@
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.36.1", "@playwright/test": "1.38.0",
"@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0", "@typescript-eslint/parser": "^6.1.0",
"axios": "^0.24.0", "axios": "^0.24.0",

7
tests/playwright/pages/Account/Users.ts

@ -39,6 +39,8 @@ export class AccountUsersPage extends BasePage {
} }
async invite({ email, role }: { email: string; role: string }) { async invite({ email, role }: { email: string; role: string }) {
email = this.prefixEmail(email);
await this.inviteUserBtn.click(); await this.inviteUserBtn.click();
await this.inviteUserModal.locator(`input[placeholder="E-mail"]`).fill(email); await this.inviteUserModal.locator(`input[placeholder="E-mail"]`).fill(email);
await this.inviteUserModal.locator(`.nc-user-roles`).click(); await this.inviteUserModal.locator(`.nc-user-roles`).click();
@ -47,6 +49,9 @@ export class AccountUsersPage extends BasePage {
await this.inviteUserModal.locator(`button:has-text("Invite")`).click(); await this.inviteUserModal.locator(`button:has-text("Invite")`).click();
await this.verifyToast({ message: 'Successfully added user' }); await this.verifyToast({ message: 'Successfully added user' });
// TODO: Wait on the invite api and get the invite url a better way as we are not waiting if the url is reflected in the UI
await this.rootPage.waitForTimeout(1000);
// http://localhost:3000/#/signup/a5e7bf3a-cbb0-46bc-87f7-c2ae21796707 // http://localhost:3000/#/signup/a5e7bf3a-cbb0-46bc-87f7-c2ae21796707
return (await this.inviteUserModal.locator(`.ant-alert-message`).innerText()).split('\n')[0]; return (await this.inviteUserModal.locator(`.ant-alert-message`).innerText()).split('\n')[0];
} }
@ -63,6 +68,8 @@ export class AccountUsersPage extends BasePage {
async getUserRow({ email }: { email: string }) { async getUserRow({ email }: { email: string }) {
// ensure page is loaded // ensure page is loaded
email = this.prefixEmail(email);
await this.get().waitFor(); await this.get().waitFor();
return this.get().locator(`tr:has-text("${email}")`); return this.get().locator(`tr:has-text("${email}")`);
} }

32
tests/playwright/pages/Dashboard/ExpandedForm/index.ts

@ -141,6 +141,10 @@ export class ExpandedFormPage extends BasePage {
async escape() { async escape() {
await this.rootPage.keyboard.press('Escape'); await this.rootPage.keyboard.press('Escape');
await this.get().locator('.nc-drawer-expanded-form').waitFor({ state: 'hidden' }); await this.get().locator('.nc-drawer-expanded-form').waitFor({ state: 'hidden' });
await this.rootPage.waitForLoadState('networkidle');
await this.rootPage.waitForLoadState('domcontentloaded');
await this.rootPage.waitForTimeout(500);
} }
async close() { async close() {
@ -165,29 +169,27 @@ export class ExpandedFormPage extends BasePage {
// expect(await this.btn_moreActions.count()).toBe(1); // expect(await this.btn_moreActions.count()).toBe(1);
await this.btn_moreActions.click(); await this.btn_moreActions.click();
const menu = this.rootPage.locator('.ant-dropdown:visible');
await menu.waitFor({ state: 'visible' }); if (role === 'owner' || role === 'editor' || role === 'creator') {
const menuItems = menu.locator('.ant-dropdown-menu-item'); await expect(this.rootPage.getByTestId('nc-expanded-form-reload')).toBeVisible();
for (let i = 0; i < (await menuItems.count()); i++) { await expect(this.rootPage.getByTestId('nc-expanded-form-duplicate')).toBeVisible();
if (role === 'owner' || role === 'editor' || role === 'creator') { await expect(this.rootPage.getByTestId('nc-expanded-form-delete')).toBeVisible();
const menuText = ['Reload', 'Duplicate record', 'Delete record']; } else {
expect(await getTextExcludeIconText(menuItems.nth(i))).toBe(menuText[i]); await expect(this.rootPage.getByTestId('nc-expanded-form-reload')).toBeVisible();
} else { await expect(this.rootPage.getByTestId('nc-expanded-form-duplicate')).toHaveCount(0);
const menuText = ['Reload', 'Close']; await expect(this.rootPage.getByTestId('nc-expanded-form-delete')).toHaveCount(0);
expect(await menuItems.nth(i).innerText()).toBe(menuText[i]);
}
} }
if (role === 'owner' || role === 'editor' || role === 'creator') { if (role === 'owner' || role === 'editor' || role === 'creator') {
expect(await this.btn_save.count()).toBe(1); await expect(this.rootPage.getByTestId('nc-expanded-form-save')).toHaveCount(1);
} else { } else {
expect(await this.btn_save.count()).toBe(0); await expect(this.rootPage.getByTestId('nc-expanded-form-save')).toHaveCount(0);
} }
if (role === 'viewer') { if (role === 'viewer') {
expect(await this.get().locator('.nc-comments-drawer').count()).toBe(0); await expect(this.get().locator('.nc-comments-drawer')).toHaveCount(0);
} else { } else {
expect(await this.get().locator('.nc-comments-drawer').count()).toBe(1); await expect(this.get().locator('.nc-comments-drawer')).toHaveCount(1);
} }
// press escape to close the expanded form // press escape to close the expanded form

42
tests/playwright/pages/Dashboard/Form/index.ts

@ -146,8 +146,24 @@ export class FormPage extends BasePage {
} }
async configureHeader(param: { subtitle: string; title: string }) { async configureHeader(param: { subtitle: string; title: string }) {
await this.formHeading.fill(param.title); await this.waitForResponse({
await this.formSubHeading.fill(param.subtitle); uiAction: async () => {
await this.formHeading.click();
await this.formHeading.fill(param.title);
await this.formSubHeading.click();
},
requestUrlPathToMatch: 'api/v1/db/meta/forms',
httpMethodsToMatch: ['PATCH'],
});
await this.waitForResponse({
uiAction: async () => {
await this.formSubHeading.click();
await this.formSubHeading.fill(param.subtitle);
await this.formHeading.click();
},
requestUrlPathToMatch: 'api/v1/db/meta/forms',
httpMethodsToMatch: ['PATCH'],
});
} }
async verifyHeader(param: { subtitle: string; title: string }) { async verifyHeader(param: { subtitle: string; title: string }) {
@ -177,14 +193,21 @@ export class FormPage extends BasePage {
label: string; label: string;
helpText: string; helpText: string;
}) { }) {
const waitForResponse = async (action: () => Promise<any>) =>
await this.waitForResponse({
uiAction: action,
requestUrlPathToMatch: 'api/v1/db/meta/form-columns',
httpMethodsToMatch: ['PATCH'],
});
await this.get() await this.get()
.locator(`.nc-form-drag-${field.replace(' ', '')}`) .locator(`.nc-form-drag-${field.replace(' ', '')}`)
.locator('div[data-testid="nc-form-input-label"]') .locator('div[data-testid="nc-form-input-label"]')
.click(); .click();
await this.getFormFieldsInputLabel().fill(label); await waitForResponse(() => this.getFormFieldsInputLabel().fill(label));
await this.getFormFieldsInputHelpText().fill(helpText); await waitForResponse(() => this.getFormFieldsInputHelpText().fill(helpText));
if (required) { if (required) {
await this.getFormFieldsRequired().click(); await waitForResponse(() => this.getFormFieldsRequired().click());
} }
await this.formHeading.click(); await this.formHeading.click();
} }
@ -241,7 +264,14 @@ export class FormPage extends BasePage {
} }
async configureSubmitMessage(param: { message: string }) { async configureSubmitMessage(param: { message: string }) {
await this.afterSubmitMsg.fill(param.message); await this.waitForResponse({
uiAction: async () => {
await this.afterSubmitMsg.click();
await this.afterSubmitMsg.fill(param.message);
},
requestUrlPathToMatch: 'api/v1/db/meta/forms',
httpMethodsToMatch: ['PATCH'],
});
} }
submitAnotherForm() { submitAnotherForm() {

11
tests/playwright/pages/Dashboard/Grid/Group.ts

@ -100,15 +100,16 @@ export class GroupPageObject extends BasePage {
const addNewRowBtn = this.get({ indexMap }).locator('.nc-grid-add-new-row'); const addNewRowBtn = this.get({ indexMap }).locator('.nc-grid-add-new-row');
await addNewRowBtn.scrollIntoViewIfNeeded(); await addNewRowBtn.scrollIntoViewIfNeeded();
await (await addNewRowBtn.elementHandle()).waitForElementState('stable'); await (await addNewRowBtn.elementHandle()).waitForElementState('stable');
await this.rootPage.waitForTimeout(100);
const rowCount = await this.get({ indexMap }).locator('.nc-grid-row').count(); await this.rootPage.waitForTimeout(200);
await this.rootPage.waitForLoadState('networkidle');
await this.rootPage.waitForTimeout(200);
await this.rootPage.waitForLoadState('domcontentloaded');
await this.get({ indexMap }).locator('.nc-grid-add-new-row').click(); await this.get({ indexMap }).locator('.nc-grid-add-new-row').click();
// add delay for UI to render (can wait for count to stabilize by reading it multiple times) const rowCount = index + 1;
await this.rootPage.waitForTimeout(100); await expect(this.get({ indexMap }).locator('.nc-grid-row')).toHaveCount(rowCount);
expect(await this.get({ indexMap }).locator('.nc-grid-row').count()).toBe(rowCount + 1);
await this._fillRow({ indexMap, index, columnHeader, value: rowValue }); await this._fillRow({ indexMap, index, columnHeader, value: rowValue });

11
tests/playwright/pages/Dashboard/Grid/index.ts

@ -115,15 +115,16 @@ export class GridPage extends BasePage {
if (index !== 0) await this.get().locator('.nc-grid-row').nth(0).waitFor({ state: 'attached' }); if (index !== 0) await this.get().locator('.nc-grid-row').nth(0).waitFor({ state: 'attached' });
await (await this.get().locator('.nc-grid-add-new-cell').elementHandle())?.waitForElementState('stable'); await (await this.get().locator('.nc-grid-add-new-cell').elementHandle())?.waitForElementState('stable');
await this.rootPage.waitForTimeout(100);
const rowCount = await this.get().locator('.nc-grid-row').count(); await this.rootPage.waitForTimeout(200);
await this.rootPage.waitForLoadState('networkidle');
await this.rootPage.waitForTimeout(200);
await this.rootPage.waitForLoadState('domcontentloaded');
await this.get().locator('.nc-grid-add-new-cell').click(); await this.get().locator('.nc-grid-add-new-cell').click();
// add delay for UI to render (can wait for count to stabilize by reading it multiple times) const rowCount = index + 1;
await this.rootPage.waitForTimeout(100); await expect(this.get().locator('.nc-grid-row')).toHaveCount(rowCount);
await expect(this.get().locator('.nc-grid-row')).toHaveCount(rowCount + 1);
await this._fillRow({ index, columnHeader, value: rowValue }); await this._fillRow({ index, columnHeader, value: rowValue });

17
tests/playwright/pages/Dashboard/TreeView.ts

@ -185,17 +185,6 @@ export class TreeViewPage extends BasePage {
requestUrlPathToMatch: `/api/v1/db/meta/tables/`, requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
}); });
await expect
.poll(
async () =>
await this.dashboard.tabBar
.locator('.ant-tabs-tab', {
hasText: title,
})
.isVisible()
)
.toBe(false);
await (await this.rootPage.locator('.nc-container').last().elementHandle())?.waitForElementState('stable'); await (await this.rootPage.locator('.nc-container').last().elementHandle())?.waitForElementState('stable');
} }
@ -238,7 +227,7 @@ export class TreeViewPage extends BasePage {
async changeTableIcon({ title, icon, iconDisplay }: { title: string; icon: string; iconDisplay?: string }) { async changeTableIcon({ title, icon, iconDisplay }: { title: string; icon: string; iconDisplay?: string }) {
await this.get().locator(`.nc-project-tree-tbl-${title} .nc-table-icon`).click(); await this.get().locator(`.nc-project-tree-tbl-${title} .nc-table-icon`).click();
await this.rootPage.locator('.emoji-mart-search').type(icon); await this.rootPage.locator('.emoji-mart-search > input').fill(icon);
const emojiList = this.rootPage.locator('[id="emoji-mart-list"]'); const emojiList = this.rootPage.locator('[id="emoji-mart-list"]');
await emojiList.locator('button').first().click(); await emojiList.locator('button').first().click();
await expect( await expect(
@ -302,9 +291,11 @@ export class TreeViewPage extends BasePage {
async openProject({ title, context }: { title: string; context: NcContext }) { async openProject({ title, context }: { title: string; context: NcContext }) {
title = this.scopedProjectTitle({ title, context }); title = this.scopedProjectTitle({ title, context });
// loop through nodes.count() to find the node with title
await this.get().getByTestId(`nc-sidebar-project-title-${title}`).click(); await this.get().getByTestId(`nc-sidebar-project-title-${title}`).click();
await this.rootPage.waitForTimeout(1000);
// TODO: FIx why project click is not always registering
await this.get().getByTestId(`nc-sidebar-project-title-${title}`).click();
await this.rootPage.waitForTimeout(1000); await this.rootPage.waitForTimeout(1000);
} }

6
tests/playwright/pages/Dashboard/ViewSidebar/index.ts

@ -127,14 +127,16 @@ export class ViewSidebarPage extends BasePage {
.locator('.nc-sidebar-view-node-context-btn') .locator('.nc-sidebar-view-node-context-btn')
.click(); .click();
await this.rootPage.waitForTimeout(750);
await this.rootPage await this.rootPage
.locator(`[data-testid="view-sidebar-view-actions-${title}"]`) .locator(`[data-testid="view-sidebar-view-actions-${title}"]`)
.locator('.nc-view-delete-icon') .locator('.ant-dropdown-menu-title-content:has-text("Delete")')
.click({ .click({
force: true, force: true,
}); });
await this.rootPage.locator('button:has-text("Delete View"):visible').click(); await this.rootPage.getByTestId('nc-delete-modal-delete-btn').click();
} }
async renameView({ title, newTitle }: { title: string; newTitle: string }) { async renameView({ title, newTitle }: { title: string; newTitle: string }) {

21
tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts

@ -27,7 +27,10 @@ export class AttachmentCellPageObject extends BasePage {
const attachFileAction = this.get({ index, columnHeader }) const attachFileAction = this.get({ index, columnHeader })
.locator('[data-testid="attachment-cell-file-picker-button"]') .locator('[data-testid="attachment-cell-file-picker-button"]')
.click(); .click();
return await this.attachFile({ filePickUIAction: attachFileAction, filePath }); await this.attachFile({ filePickUIAction: attachFileAction, filePath });
// wait for file to be uploaded
await this.rootPage.waitForTimeout(750);
} }
async expandModalAddFile({ filePath }: { filePath: string[] }) { async expandModalAddFile({ filePath }: { filePath: string[] }) {
@ -51,20 +54,8 @@ export class AttachmentCellPageObject extends BasePage {
async verifyFileCount({ index, columnHeader, count }: { index: number; columnHeader: string; count: number }) { async verifyFileCount({ index, columnHeader, count }: { index: number; columnHeader: string; count: number }) {
// retry below logic for 5 times, with 1 second delay // retry below logic for 5 times, with 1 second delay
let retryCount = 0; const attachments = this.get({ index, columnHeader }).locator('.nc-attachment');
while (retryCount < 5) { await expect(attachments).toHaveCount(count);
const attachments = this.get({ index, columnHeader }).locator('.nc-attachment');
// console.log(await attachments.count());
if ((await attachments.count()) === count) {
break;
}
retryCount++;
await this.rootPage.waitForTimeout(1000);
if (retryCount === 5) {
expect(await attachments.count()).toBe(count);
}
}
} }
async expandModalClose() { async expandModalClose() {

3
tests/playwright/pages/Dashboard/common/Cell/CheckboxCell.ts

@ -15,7 +15,8 @@ export class CheckboxCellPageObject extends BasePage {
} }
async click({ index, columnHeader }: { index?: number; columnHeader: string }) { async click({ index, columnHeader }: { index?: number; columnHeader: string }) {
return await this.get({ index, columnHeader }).locator('.nc-cell').click(); await this.get({ index, columnHeader }).locator('.nc-cell').click();
await this.rootPage.waitForTimeout(500);
} }
// async isChecked({ index, columnHeader }: { index?: number; columnHeader: string }) { // async isChecked({ index, columnHeader }: { index?: number; columnHeader: string }) {

3
tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -356,6 +356,9 @@ export class ToolbarFilterPage extends BasePage {
} else { } else {
await this.get().locator('.nc-filter-item-remove-btn').click(); await this.get().locator('.nc-filter-item-remove-btn').click();
} }
// TODO: Filter reset await not working all the time
await this.rootPage.waitForTimeout(650);
await this.toolbar.clickFilter(); await this.toolbar.clickFilter();
} }

3
tests/playwright/pages/Dashboard/index.ts

@ -192,9 +192,6 @@ export class DashboardPage extends BasePage {
await this.rootPage.getByTestId('nc-sidebar-user-logout').waitFor({ state: 'visible' }); await this.rootPage.getByTestId('nc-sidebar-user-logout').waitFor({ state: 'visible' });
await this.sidebar.userMenu.clickLogout(); await this.sidebar.userMenu.clickLogout();
// TODO: Remove this
await this.rootPage.reload();
await this.rootPage.locator('[data-testid="nc-form-signin"]:visible').waitFor(); await this.rootPage.locator('[data-testid="nc-form-signin"]:visible').waitFor();
await new Promise(resolve => setTimeout(resolve, 150)); await new Promise(resolve => setTimeout(resolve, 150));
} }

2
tests/playwright/tests/db/features/expandedFormUrl.spec.ts

@ -42,7 +42,7 @@ test.describe('Expanded form URL', () => {
// expand row & verify URL // expand row & verify URL
// New Expanded Modal don't have functionality to copy URL. Hence gettting URL from root page // New Expanded Modal don't have functionality to copy URL. Hence gettting URL from root page
await viewObj.openExpandedRow({ index: 0 }); await viewObj.openExpandedRow({ index: 0 });
const url = await dashboard.rootPage.url(); const url = await dashboard.rootPage.url();
await dashboard.expandedForm.escape(); await dashboard.expandedForm.escape();

3
tests/playwright/tests/db/features/metaLTAR.spec.ts

@ -56,7 +56,10 @@ test.describe.serial('Test table', () => {
grid = dashboard.grid; grid = dashboard.grid;
// create a new xcdb project // create a new xcdb project
await dashboard.rootPage.waitForTimeout(650);
const xcdb = await createXcdb(context); const xcdb = await createXcdb(context);
await dashboard.rootPage.waitForTimeout(650);
await dashboard.rootPage.reload(); await dashboard.rootPage.reload();
await dashboard.treeView.openProject({ title: 'xcdb', context }); await dashboard.treeView.openProject({ title: 'xcdb', context });

6
tests/playwright/tests/db/features/verticalFillHandle.spec.ts

@ -162,7 +162,7 @@ test.describe('Fill Handle', () => {
await unsetup(p.context); await unsetup(p.context);
}); });
test('Select based', async () => { test('Select based', async ({ page }) => {
const fields = [ const fields = [
{ title: 'SingleSelect', value: 'jan', type: 'singleSelect' }, { title: 'SingleSelect', value: 'jan', type: 'singleSelect' },
{ title: 'MultiSelect', value: 'jan,feb,mar', type: 'multiSelect' }, { title: 'MultiSelect', value: 'jan,feb,mar', type: 'multiSelect' },
@ -170,7 +170,7 @@ test.describe('Fill Handle', () => {
await dragDrop({ firstColumn: 'SingleSelect', lastColumn: 'MultiSelect', params: p }); await dragDrop({ firstColumn: 'SingleSelect', lastColumn: 'MultiSelect', params: p });
await new Promise((r) => setTimeout(r, 500)); await page.waitForTimeout(1000);
// verify data on grid // verify data on grid
const displayOptions = ['jan', 'feb', 'mar']; const displayOptions = ['jan', 'feb', 'mar'];
@ -212,7 +212,7 @@ test.describe('Fill Handle', () => {
await unsetup(p.context); await unsetup(p.context);
}); });
test('Miscellaneous (Checkbox, attachment)', async () => { test('Miscellaneous (Checkbox, attachment) @flaky', async () => {
const fields = [ const fields = [
{ title: 'Checkbox', value: 'true', type: 'checkbox' }, { title: 'Checkbox', value: 'true', type: 'checkbox' },
{ title: 'Attachment', value: `${process.cwd()}/fixtures/sampleFiles/1.json`, type: 'attachment' }, { title: 'Attachment', value: `${process.cwd()}/fixtures/sampleFiles/1.json`, type: 'attachment' },

2
tests/playwright/tests/db/general/projectOperations.spec.ts

@ -11,7 +11,7 @@ test.describe('Project operations', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;
let context: NcContext; let context: NcContext;
let api: Api<any>; let api: Api<any>;
test.setTimeout(100000); test.setTimeout(150000);
async function getProjectList(workspaceId?: string) { async function getProjectList(workspaceId?: string) {
let projectList: ProjectListType; let projectList: ProjectListType;

9
tests/playwright/tests/db/usersAccounts/accountUserManagement.spec.ts

@ -11,8 +11,8 @@ import { isEE } from '../../../setup/db';
let api: Api<any>; let api: Api<any>;
const roleDb = [ const roleDb = [
{ email: 'org_creator@nocodb.com', role: 'Organization Level Creator', url: '' }, { email: `org_creator_@nocodb.com`, role: 'Organization Level Creator', url: '' },
{ email: 'org_viewer@nocodb.com', role: 'Organization Level Viewer', url: '' }, { email: `org_viewer_@nocodb.com`, role: 'Organization Level Viewer', url: '' },
]; ];
test.describe('User roles', () => { test.describe('User roles', () => {
@ -113,16 +113,15 @@ test.describe('User roles', () => {
await signupPage.signUp({ await signupPage.signUp({
email: roleDb[roleIdx].email, email: roleDb[roleIdx].email,
password: getDefaultPwd(), password: getDefaultPwd(),
withoutPrefix: true,
}); });
// wait for page rendering to complete after sign up // wait for page rendering to complete after sign up
await dashboard.rootPage.waitForTimeout(1000); await dashboard.rootPage.waitForTimeout(1000);
if (roleDb[roleIdx].role === 'Organization Level Creator') { if (roleDb[roleIdx].role === 'Organization Level Creator') {
expect(await dashboard.leftSidebar.btn_newProject.isVisible()).toBeTruthy(); await expect(dashboard.leftSidebar.btn_newProject).toBeVisible();
} else { } else {
expect(await dashboard.leftSidebar.btn_newProject.isVisible()).toBeFalsy(); await expect(dashboard.leftSidebar.btn_newProject).toHaveCount(0);
} }
} }
}); });

Loading…
Cancel
Save