Browse Source

feat(format): Formated all playwright code

pull/3848/head
Muhammed Mustafa 2 years ago
parent
commit
27345c6d2d
  1. 2
      packages/nocodb/tests/unit/tsconfig.json
  2. 7
      scripts/playwright/.eslintrc.json
  3. 6
      scripts/playwright/constants/index.ts
  4. 16
      scripts/playwright/fixtures/template.spec.ts
  5. 25
      scripts/playwright/pages/Base.ts
  6. 83
      scripts/playwright/pages/Dashboard/ExpandedForm/index.ts
  7. 189
      scripts/playwright/pages/Dashboard/Form/index.ts
  8. 16
      scripts/playwright/pages/Dashboard/Gallery/index.ts
  9. 54
      scripts/playwright/pages/Dashboard/Grid/Column/LTAR/ChildList.ts
  10. 30
      scripts/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts
  11. 58
      scripts/playwright/pages/Dashboard/Grid/Column/SelectOptionColumn.ts
  12. 164
      scripts/playwright/pages/Dashboard/Grid/Column/index.ts
  13. 176
      scripts/playwright/pages/Dashboard/Grid/index.ts
  14. 8
      scripts/playwright/pages/Dashboard/Import/Airtable.ts
  15. 41
      scripts/playwright/pages/Dashboard/Import/ImportTemplate.ts
  16. 63
      scripts/playwright/pages/Dashboard/Kanban/index.ts
  17. 18
      scripts/playwright/pages/Dashboard/Settings/Acl.ts
  18. 32
      scripts/playwright/pages/Dashboard/Settings/AppStore.ts
  19. 71
      scripts/playwright/pages/Dashboard/Settings/Audit.ts
  20. 5
      scripts/playwright/pages/Dashboard/Settings/Erd.ts
  21. 38
      scripts/playwright/pages/Dashboard/Settings/Metadata.ts
  22. 4
      scripts/playwright/pages/Dashboard/Settings/Miscellaneous.ts
  23. 76
      scripts/playwright/pages/Dashboard/Settings/Teams.ts
  24. 47
      scripts/playwright/pages/Dashboard/Settings/index.ts
  25. 77
      scripts/playwright/pages/Dashboard/SurveyForm/index.ts
  26. 145
      scripts/playwright/pages/Dashboard/TreeView.ts
  27. 113
      scripts/playwright/pages/Dashboard/ViewSidebar/index.ts
  28. 129
      scripts/playwright/pages/Dashboard/WebhookForm/index.ts
  29. 28
      scripts/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
  30. 28
      scripts/playwright/pages/Dashboard/common/Cell/CheckboxCell.ts
  31. 19
      scripts/playwright/pages/Dashboard/common/Cell/RatingCell.ts
  32. 97
      scripts/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts
  33. 143
      scripts/playwright/pages/Dashboard/common/Cell/index.ts
  34. 24
      scripts/playwright/pages/Dashboard/common/ProjectMenu/index.ts
  35. 4
      scripts/playwright/pages/Dashboard/common/Toolbar/Actions/Erd.ts
  36. 6
      scripts/playwright/pages/Dashboard/common/Toolbar/Actions/index.ts
  37. 11
      scripts/playwright/pages/Dashboard/common/Toolbar/AddEditKanbanStack.ts
  38. 31
      scripts/playwright/pages/Dashboard/common/Toolbar/Fields.ts
  39. 54
      scripts/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  40. 34
      scripts/playwright/pages/Dashboard/common/Toolbar/ShareView.ts
  41. 49
      scripts/playwright/pages/Dashboard/common/Toolbar/Sort.ts
  42. 9
      scripts/playwright/pages/Dashboard/common/Toolbar/StackBy.ts
  43. 82
      scripts/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts
  44. 107
      scripts/playwright/pages/Dashboard/common/Toolbar/index.ts
  45. 38
      scripts/playwright/pages/Dashboard/commonBase/Erd.ts
  46. 183
      scripts/playwright/pages/Dashboard/index.ts
  47. 26
      scripts/playwright/pages/LoginPage/index.ts
  48. 144
      scripts/playwright/pages/ProjectsPage/index.ts
  49. 22
      scripts/playwright/pages/SharedForm/index.ts
  50. 26
      scripts/playwright/pages/SignupPage/index.ts
  51. 297
      scripts/playwright/quickTests/commonTest.ts
  52. 31
      scripts/playwright/quickTests/quickTests.spec.ts
  53. 23
      scripts/playwright/setup/db.ts
  54. 48
      scripts/playwright/setup/index.ts
  55. 25
      scripts/playwright/setup/mysqlExec.ts
  56. 21
      scripts/playwright/setup/server.ts
  57. 8
      scripts/playwright/setup/sqliteExec.ts
  58. 45
      scripts/playwright/tests-examples/demo-todo-app.spec.ts
  59. 190
      scripts/playwright/tests/01-webhook.spec.ts
  60. 66
      scripts/playwright/tests/authChangePassword.spec.ts
  61. 40
      scripts/playwright/tests/baseShare.spec.ts
  62. 56
      scripts/playwright/tests/columnAttachments.spec.ts
  63. 128
      scripts/playwright/tests/columnDuration.spec.ts
  64. 69
      scripts/playwright/tests/columnFormula.spec.ts
  65. 216
      scripts/playwright/tests/columnLinkToAnotherRecord.spec.ts
  66. 44
      scripts/playwright/tests/columnLookupRollup.spec.ts
  67. 149
      scripts/playwright/tests/columnMultiSelect.spec.ts
  68. 102
      scripts/playwright/tests/columnRelationalExtendedTests.spec.ts
  69. 74
      scripts/playwright/tests/columnSingleSelect.spec.ts
  70. 312
      scripts/playwright/tests/erd.spec.ts
  71. 75
      scripts/playwright/tests/expandedFormUrl.spec.ts
  72. 44
      scripts/playwright/tests/import.spec.ts
  73. 78
      scripts/playwright/tests/language.spec.ts
  74. 110
      scripts/playwright/tests/metaSync.spec.ts
  75. 24
      scripts/playwright/tests/pagination.spec.ts
  76. 24
      scripts/playwright/tests/projectOperations.spec.ts
  77. 74
      scripts/playwright/tests/rolesCreate.spec.ts
  78. 54
      scripts/playwright/tests/rolesPreview.spec.ts
  79. 52
      scripts/playwright/tests/rolesSuperUser.spec.ts
  80. 46
      scripts/playwright/tests/tableColumnOperation.spec.ts
  81. 46
      scripts/playwright/tests/tableOperations.spec.ts
  82. 51
      scripts/playwright/tests/toolbarOperations.spec.ts
  83. 61
      scripts/playwright/tests/utils/sakila.ts
  84. 124
      scripts/playwright/tests/viewForm.spec.ts
  85. 60
      scripts/playwright/tests/viewFormShareSurvey.spec.ts
  86. 159
      scripts/playwright/tests/viewGridShare.spec.ts
  87. 118
      scripts/playwright/tests/viewKanban.spec.ts
  88. 32
      scripts/playwright/tests/viewMenu.spec.ts
  89. 50
      scripts/playwright/tests/views.spec.ts
  90. 2
      scripts/playwright/tsconfig.json

2
packages/nocodb/tests/unit/tsconfig.json

@ -45,7 +45,7 @@
// "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
"lib": [
"es2017"
"es2017", "DOM"
],
"types": [
"mocha", "node"

7
scripts/playwright/.eslintrc.json

@ -11,6 +11,8 @@
"node_modules",
"build",
"coverage",
"playwright-report",
"output",
"dist",
"nc"
],
@ -29,7 +31,10 @@
"globals": {
"BigInt": true,
"console": true,
"WebAssembly": true
"WebAssembly": true,
"window": true,
"document": true,
"localStorage": true
},
"rules": {
"require-await": "error",

6
scripts/playwright/constants/index.ts

@ -1,4 +1,4 @@
const airtableApiKey = "keyn1MR87qgyUsYg4";
const airtableApiBase = "https://airtable.com/shr4z0qmh6dg5s3eB";
const airtableApiKey = 'keyn1MR87qgyUsYg4';
const airtableApiBase = 'https://airtable.com/shr4z0qmh6dg5s3eB';
export { airtableApiKey, airtableApiBase };
export { airtableApiKey, airtableApiBase };

16
scripts/playwright/fixtures/template.spec.ts

@ -1,9 +1,9 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
test.describe.only("Test block name", () => {
test.describe.only('Test block name', () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any;
@ -14,9 +14,9 @@ test.describe.only("Test block name", () => {
toolbar = dashboard.grid.toolbar;
});
test("Test case name", async () => {
test('Test case name', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' });
});
});

25
scripts/playwright/pages/Base.ts

@ -1,4 +1,4 @@
import { Page, Locator } from "@playwright/test";
import { Locator, Page } from '@playwright/test';
type ResponseSelector = (json: any) => boolean;
@ -12,10 +12,7 @@ export default abstract class BasePage {
}
async verifyToast({ message }: { message: string }) {
await this.rootPage
.locator(".ant-message .ant-message-notice-content", { hasText: message })
.last()
.isVisible();
await this.rootPage.locator('.ant-message .ant-message-notice-content', { hasText: message }).last().isVisible();
}
async waitForResponse({
@ -30,9 +27,9 @@ export default abstract class BasePage {
responseJsonMatcher?: ResponseSelector;
}) {
await Promise.all([
this.rootPage.waitForResponse(async (res) => {
this.rootPage.waitForResponse(async res => {
let isResJsonMatched = true;
if(responseJsonMatcher){
if (responseJsonMatcher) {
try {
isResJsonMatched = responseJsonMatcher(await res.json());
} catch (e) {
@ -44,13 +41,13 @@ export default abstract class BasePage {
res.request().url().includes(requestUrlPathToMatch) &&
httpMethodsToMatch.includes(res.request().method()) &&
isResJsonMatched
);
);
}),
uiAction,
]);
}
async attachFile({filePickUIAction, filePath}:{ filePickUIAction: Promise<any>, filePath: string}) {
async attachFile({ filePickUIAction, filePath }: { filePickUIAction: Promise<any>; filePath: string }) {
const [fileChooser] = await Promise.all([
// It is important to call waitForEvent before click to set up waiting.
this.rootPage.waitForEvent('filechooser'),
@ -60,22 +57,22 @@ export default abstract class BasePage {
await fileChooser.setFiles(filePath);
}
async downloadAndGetFile({downloadUIAction}:{ downloadUIAction: Promise<any>,}) {
const [ download ] = await Promise.all([
async downloadAndGetFile({ downloadUIAction }: { downloadUIAction: Promise<any> }) {
const [download] = await Promise.all([
// It is important to call waitForEvent before click to set up waiting.
this.rootPage.waitForEvent('download'),
// Triggers the download.
downloadUIAction,
]);
// wait for download to complete
if(await download.failure()) {
if (await download.failure()) {
throw new Error('Download failed');
}
const file = await download.createReadStream();
const data = await new Promise((resolve, reject) => {
let data = '';
file?.on('data', chunk => data += chunk);
file?.on('data', chunk => (data += chunk));
file?.on('end', () => resolve(data));
file?.on('error', reject);
});

83
scripts/playwright/pages/Dashboard/ExpandedForm/index.ts

@ -1,7 +1,7 @@
// playwright-dev-page.ts
import { expect, Locator } from "@playwright/test";
import BasePage from "../../Base";
import { DashboardPage } from "..";
import { expect, Locator } from '@playwright/test';
import BasePage from '../../Base';
import { DashboardPage } from '..';
// import clipboard from "clipboardy";
export class ExpandedFormPage extends BasePage {
@ -13,13 +13,9 @@ export class ExpandedFormPage extends BasePage {
constructor(dashboard: DashboardPage) {
super(dashboard.rootPage);
this.dashboard = dashboard;
this.addNewTableButton = this.dashboard.get().locator(".nc-add-new-table");
this.copyUrlButton = this.dashboard
.get()
.locator(".nc-copy-row-url:visible");
this.toggleCommentsButton = this.dashboard
.get()
.locator(".nc-toggle-comments:visible");
this.addNewTableButton = this.dashboard.get().locator('.nc-add-new-table');
this.copyUrlButton = this.dashboard.get().locator('.nc-copy-row-url:visible');
this.toggleCommentsButton = this.dashboard.get().locator('.nc-toggle-comments:visible');
}
get() {
@ -28,36 +24,24 @@ export class ExpandedFormPage extends BasePage {
async gotoUsingUrlAndRowId({ rowId }: { rowId: string }) {
const url = await this.dashboard.rootPage.url();
const expandedFormUrl = "/" + url.split("/").slice(3).join("/").split("?")[0] + `?rowId=${rowId}`
await this.rootPage.goto(
expandedFormUrl
);
const expandedFormUrl = '/' + url.split('/').slice(3).join('/').split('?')[0] + `?rowId=${rowId}`;
await this.rootPage.goto(expandedFormUrl);
await this.dashboard.waitForLoaderToDisappear();
}
async fillField({
columnTitle,
value,
type = "text",
}: {
columnTitle: string;
value: string;
type?: string;
}) {
const field = this.get().locator(
`[pw-data="nc-expand-col-${columnTitle}"]`
);
async fillField({ columnTitle, value, type = 'text' }: { columnTitle: string; value: string; type?: string }) {
const field = this.get().locator(`[pw-data="nc-expand-col-${columnTitle}"]`);
await field.hover();
switch (type) {
case "text":
await field.locator("input").fill(value);
case 'text':
await field.locator('input').fill(value);
break;
case "belongsTo":
await field.locator(".nc-action-icon").click();
case 'belongsTo':
await field.locator('.nc-action-icon').click();
await this.dashboard.linkRecord.select(value);
break;
case "hasMany":
case "manyToMany":
case 'hasMany':
case 'manyToMany':
await field.locator(`[data-cy="nc-child-list-button-link-to"]`).click();
await this.dashboard.linkRecord.select(value);
break;
@ -70,12 +54,12 @@ export class ExpandedFormPage extends BasePage {
waitForRowsData?: boolean;
} = {}) {
const saveRowAction = this.get().locator('button:has-text("Save Row")').click();
if(waitForRowsData) {
if (waitForRowsData) {
await this.waitForResponse({
uiAction: saveRowAction,
requestUrlPathToMatch: 'api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
responseJsonMatcher: (json) => json['pageInfo'],
responseJsonMatcher: json => json['pageInfo'],
});
} else {
await this.waitForResponse({
@ -85,12 +69,10 @@ export class ExpandedFormPage extends BasePage {
});
}
await this.get().press("Escape");
await this.get().waitFor({ state: "hidden" });
await this.get().press('Escape');
await this.get().waitFor({ state: 'hidden' });
await this.verifyToast({ message: `updated successfully.` });
await this.rootPage
.locator('[pw-data="grid-load-spinner"]')
.waitFor({ state: "hidden" });
await this.rootPage.locator('[pw-data="grid-load-spinner"]').waitFor({ state: 'hidden' });
}
async verify({ header, url }: { header: string; url: string }) {
@ -99,7 +81,7 @@ export class ExpandedFormPage extends BasePage {
}
async close() {
await this.rootPage.keyboard.press("Escape");
await this.rootPage.keyboard.press('Escape');
}
async cancel() {
@ -114,33 +96,26 @@ export class ExpandedFormPage extends BasePage {
// }
async openChildCard(param: { column: string; title: string }) {
let childList = await this.get().locator(
`[pw-data="nc-expand-col-${param.column}"]`
);
const childList = await this.get().locator(`[pw-data="nc-expand-col-${param.column}"]`);
await childList.locator(`.ant-card:has-text("${param.title}")`).click();
}
async verifyCount({ count }: { count: number }) {
return await expect(this.rootPage
.locator(`.nc-drawer-expanded-form .ant-drawer-content`)).toHaveCount(count);
return await expect(this.rootPage.locator(`.nc-drawer-expanded-form .ant-drawer-content`)).toHaveCount(count);
}
async validateRoleAccess(param: { role: string }) {
if (param.role === "commenter" || param.role === "viewer") {
await expect(
await this.get().locator('button:has-text("Save Row")')
).toBeDisabled();
if (param.role === 'commenter' || param.role === 'viewer') {
await expect(await this.get().locator('button:has-text("Save Row")')).toBeDisabled();
} else {
await expect(
await this.get().locator('button:has-text("Save Row")')
).toBeEnabled();
await expect(await this.get().locator('button:has-text("Save Row")')).toBeEnabled();
}
if (param.role === "viewer") {
if (param.role === 'viewer') {
await expect(await this.toggleCommentsButton).toHaveCount(0);
} else {
await expect(await this.toggleCommentsButton).toHaveCount(1);
}
// press escape to close the expanded form
await this.rootPage.keyboard.press("Escape");
await this.rootPage.keyboard.press('Escape');
}
}

189
scripts/playwright/pages/Dashboard/Form/index.ts

@ -1,8 +1,8 @@
// playwright-dev-page.ts
import { Locator, expect } from "@playwright/test";
import { DashboardPage } from "..";
import BasePage from "../../Base";
import { ToolbarPage } from "../common/Toolbar";
import { expect, Locator } from '@playwright/test';
import { DashboardPage } from '..';
import BasePage from '../../Base';
import { ToolbarPage } from '../common/Toolbar';
export class FormPage extends BasePage {
readonly dashboard: DashboardPage;
@ -26,27 +26,15 @@ export class FormPage extends BasePage {
this.toolbar = new ToolbarPage(this);
this.addAllButton = dashboard.get().locator('[data-pw="nc-form-add-all"]');
this.removeAllButton = dashboard
.get()
.locator('[data-pw="nc-form-remove-all"]');
this.removeAllButton = dashboard.get().locator('[data-pw="nc-form-remove-all"]');
this.submitButton = dashboard.get().locator('[data-pw="nc-form-submit"]');
this.showAnotherFormRadioButton = dashboard
.get()
.locator('[data-pw="nc-form-checkbox-submit-another-form"]');
this.showAnotherFormAfter5SecRadioButton = dashboard
.get()
.locator('[data-pw="nc-form-checkbox-show-blank-form"]');
this.emailMeRadioButton = dashboard
.get()
.locator('[data-pw="nc-form-checkbox-send-email"]');
this.showAnotherFormRadioButton = dashboard.get().locator('[data-pw="nc-form-checkbox-submit-another-form"]');
this.showAnotherFormAfter5SecRadioButton = dashboard.get().locator('[data-pw="nc-form-checkbox-show-blank-form"]');
this.emailMeRadioButton = dashboard.get().locator('[data-pw="nc-form-checkbox-send-email"]');
this.formHeading = dashboard.get().locator('[data-pw="nc-form-heading"]');
this.formSubHeading = dashboard
.get()
.locator('[data-pw="nc-form-sub-heading"]');
this.afterSubmitMsg = dashboard
.get()
.locator('[data-pw="nc-form-after-submit-msg"]');
this.formSubHeading = dashboard.get().locator('[data-pw="nc-form-sub-heading"]');
this.afterSubmitMsg = dashboard.get().locator('[data-pw="nc-form-after-submit-msg"]');
}
/*
@ -108,112 +96,64 @@ export class FormPage extends BasePage {
}
getFormFieldsInputHelpText() {
return this.get().locator(
'input[data-pw="nc-form-input-help-text"]:visible'
);
return this.get().locator('input[data-pw="nc-form-input-help-text"]:visible');
}
///////////////////////////
// Form Actions
async verifyFormFieldLabel({
index,
label,
}: {
index: number;
label: string;
}) {
await expect(
await this.getFormFields()
.nth(index)
.locator('[data-pw="nc-form-input-label"]')
).toContainText(label);
async verifyFormFieldLabel({ index, label }: { index: number; label: string }) {
await expect(await this.getFormFields().nth(index).locator('[data-pw="nc-form-input-label"]')).toContainText(label);
}
async verifyFormFieldHelpText({
index,
helpText,
}: {
index: number;
helpText: string;
}) {
async verifyFormFieldHelpText({ index, helpText }: { index: number; helpText: string }) {
await expect(
await this.getFormFields()
.nth(index)
.locator('[pw-data="nc-form-input-help-text-label"]')
await this.getFormFields().nth(index).locator('[pw-data="nc-form-input-help-text-label"]')
).toContainText(helpText);
}
async verifyFieldsIsEditable({ index }: { index: number }) {
await expect(await this.getFormFields().nth(index)).toHaveClass(
/nc-editable/
);
await expect(await this.getFormFields().nth(index)).toHaveClass(/nc-editable/);
}
async verifyAfterSubmitMsg({ msg }: { msg: string }) {
await expect(
(await this.afterSubmitMsg.inputValue()).includes(msg)
).toBeTruthy();
await expect((await this.afterSubmitMsg.inputValue()).includes(msg)).toBeTruthy();
}
async verifyFormViewFieldsOrder({ fields }: { fields: string[] }) {
let fieldLabels = await this.get().locator(
'[data-pw="nc-form-input-label"]'
);
const fieldLabels = await this.get().locator('[data-pw="nc-form-input-label"]');
await expect(await fieldLabels).toHaveCount(fields.length);
for (let i = 0; i < fields.length; i++) {
await expect(await fieldLabels.nth(i)).toContainText(fields[i]);
}
}
async reorderFields({
sourceField,
destinationField,
}: {
sourceField: string;
destinationField: string;
}) {
await expect(
await this.get().locator(`.nc-form-drag-${sourceField}`)
).toBeVisible();
await expect(
await this.get().locator(`.nc-form-drag-${destinationField}`)
).toBeVisible();
let src = await this.get().locator(
`.nc-form-drag-${sourceField.replace(" ", "")}`
);
let dst = await this.get().locator(
`.nc-form-drag-${destinationField.replace(" ", "")}`
);
async reorderFields({ sourceField, destinationField }: { sourceField: string; destinationField: string }) {
await expect(await this.get().locator(`.nc-form-drag-${sourceField}`)).toBeVisible();
await expect(await this.get().locator(`.nc-form-drag-${destinationField}`)).toBeVisible();
const src = await this.get().locator(`.nc-form-drag-${sourceField.replace(' ', '')}`);
const dst = await this.get().locator(`.nc-form-drag-${destinationField.replace(' ', '')}`);
await src.dragTo(dst);
}
async removeField({ field, mode }: { mode: string; field: string }) {
if (mode === "dragDrop") {
let src = await this.get().locator(
`.nc-form-drag-${field.replace(" ", "")}`
);
let dst = await this.get().locator(`[data-pw="nc-drag-n-drop-to-hide"]`);
if (mode === 'dragDrop') {
const src = await this.get().locator(`.nc-form-drag-${field.replace(' ', '')}`);
const dst = await this.get().locator(`[data-pw="nc-drag-n-drop-to-hide"]`);
await src.dragTo(dst);
} else if (mode === "hideField") {
let src = await this.get().locator(
`.nc-form-drag-${field.replace(" ", "")}`
);
} else if (mode === 'hideField') {
const src = await this.get().locator(`.nc-form-drag-${field.replace(' ', '')}`);
await src.locator(`[data-pw="nc-field-remove-icon"]`).click();
}
}
async addField({ field, mode }: { mode: string; field: string }) {
if (mode === "dragDrop") {
let src = await this.get().locator(
`[data-pw="nc-form-hidden-column-${field}"]`
);
let dst = await this.get().locator(`.nc-form-drag-Country`);
if (mode === 'dragDrop') {
const src = await this.get().locator(`[data-pw="nc-form-hidden-column-${field}"]`);
const dst = await this.get().locator(`.nc-form-drag-Country`);
await src.dragTo(dst);
} else if (mode === "clickField") {
let src = await this.get().locator(
`[data-pw="nc-form-hidden-column-${field}"]`
);
} else if (mode === 'clickField') {
const src = await this.get().locator(`[data-pw="nc-form-hidden-column-${field}"]`);
await src.click();
}
}
@ -232,19 +172,14 @@ export class FormPage extends BasePage {
}
async verifyHeader(param: { subtitle: string; title: string }) {
await expect.poll(async() => await this.formHeading.inputValue()).toBe(param.title);
await expect.poll(async() => await this.formSubHeading.inputValue()).toBe(param.subtitle);
await expect.poll(async () => await this.formHeading.inputValue()).toBe(param.title);
await expect.poll(async () => await this.formSubHeading.inputValue()).toBe(param.subtitle);
}
async fillForm(param: { field: string; value: string }[]) {
for (let i = 0; i < param.length; i++) {
await this.get()
.locator(
`[data-pw="nc-form-input-${param[i].field.replace(
" ",
""
)}"] >> input`
)
.locator(`[data-pw="nc-form-input-${param[i].field.replace(' ', '')}"] >> input`)
.fill(param[i].value);
}
}
@ -261,14 +196,14 @@ export class FormPage extends BasePage {
helpText: string;
}) {
await this.get()
.locator(`.nc-form-drag-${field.replace(" ", "")}`)
.locator(`.nc-form-drag-${field.replace(' ', '')}`)
.locator('div[data-pw="nc-form-input-label"]')
.click();
await this.getFormFieldsInputLabel().fill(label);
await this.getFormFieldsInputHelpText().fill(helpText);
if (required) {
await this.get()
.locator(`.nc-form-drag-${field.replace(" ", "")}`)
.locator(`.nc-form-drag-${field.replace(' ', '')}`)
.click();
}
await this.formHeading.click();
@ -285,19 +220,19 @@ export class FormPage extends BasePage {
label: string;
helpText: string;
}) {
let expectText = "";
if (required) expectText = label + " *";
let expectText = '';
if (required) expectText = label + ' *';
else expectText = label;
// data-pw="nc-form-input-label"
// data-pw="nc-form-input-help-text-label"
let fieldLabel = await this.get()
.locator(`.nc-form-drag-${field.replace(" ", "")}`)
const fieldLabel = await this.get()
.locator(`.nc-form-drag-${field.replace(' ', '')}`)
.locator('div[data-pw="nc-form-input-label"]');
await expect(fieldLabel).toHaveText(expectText);
let fieldHelpText = await this.get()
.locator(`.nc-form-drag-${field.replace(" ", "")}`)
const fieldHelpText = await this.get()
.locator(`.nc-form-drag-${field.replace(' ', '')}`)
.locator('div[data-pw="nc-form-input-help-text-label"]');
await expect(fieldHelpText).toHaveText(helpText);
}
@ -306,20 +241,12 @@ export class FormPage extends BasePage {
await this.submitButton.click();
}
async verifyStatePostSubmit(param: {
message?: string;
submitAnotherForm?: boolean;
showBlankForm?: boolean;
}) {
async verifyStatePostSubmit(param: { message?: string; submitAnotherForm?: boolean; showBlankForm?: boolean }) {
if (undefined !== param.message) {
await expect(await this.getFormAfterSubmit()).toContainText(param.message);
}
if (true === param.submitAnotherForm) {
await expect(
await this.getFormAfterSubmit().locator(
'button:has-text("Submit Another Form")'
)
).toBeVisible();
await expect(await this.getFormAfterSubmit().locator('button:has-text("Submit Another Form")')).toBeVisible();
}
if (true === param.showBlankForm) {
await this.get().waitFor();
@ -331,9 +258,7 @@ export class FormPage extends BasePage {
}
submitAnotherForm() {
return this.getFormAfterSubmit().locator(
'button:has-text("Submit Another Form")'
);
return this.getFormAfterSubmit().locator('button:has-text("Submit Another Form")');
}
// todo: Wait for render to complete
@ -341,31 +266,19 @@ export class FormPage extends BasePage {
await this.rootPage.waitForTimeout(1000);
}
async verifyAfterSubmitMenuState(param: {
showBlankForm?: boolean;
submitAnotherForm?: boolean;
emailMe?: boolean;
}) {
async verifyAfterSubmitMenuState(param: { showBlankForm?: boolean; submitAnotherForm?: boolean; emailMe?: boolean }) {
if (true === param.showBlankForm) {
await expect(
this.get().locator(
'[data-pw="nc-form-checkbox-show-blank-form"][aria-checked="true"]'
)
this.get().locator('[data-pw="nc-form-checkbox-show-blank-form"][aria-checked="true"]')
).toBeVisible();
}
if (true === param.submitAnotherForm) {
await expect(
this.get().locator(
'[data-pw="nc-form-checkbox-submit-another-form"][aria-checked="true"]'
)
this.get().locator('[data-pw="nc-form-checkbox-submit-another-form"][aria-checked="true"]')
).toBeVisible();
}
if (true === param.emailMe) {
await expect(
this.get().locator(
'[data-pw="nc-form-checkbox-send-email"][aria-checked="true"]'
)
).toBeVisible();
await expect(this.get().locator('[data-pw="nc-form-checkbox-send-email"][aria-checked="true"]')).toBeVisible();
}
}
}

16
scripts/playwright/pages/Dashboard/Gallery/index.ts

@ -1,8 +1,8 @@
// playwright-dev-page.ts
import { Locator, expect } from "@playwright/test";
import { DashboardPage } from "..";
import BasePage from "../../Base";
import { ToolbarPage } from "../common/Toolbar";
import { expect, Locator } from '@playwright/test';
import { DashboardPage } from '..';
import BasePage from '../../Base';
import { ToolbarPage } from '../common/Toolbar';
export class GalleryPage extends BasePage {
readonly dashboard: DashboardPage;
@ -24,13 +24,7 @@ export class GalleryPage extends BasePage {
async openExpandedRow({ index }: { index: number }) {
await this.card(index).click();
await (
await this.rootPage.locator(".ant-drawer-body").elementHandle()
)?.waitForElementState("stable");
}
async verifyRowCount(param: { count: number }) {
return;
await (await this.rootPage.locator('.ant-drawer-body').elementHandle())?.waitForElementState('stable');
}
// todo: Wait for render to complete

54
scripts/playwright/pages/Dashboard/Grid/Column/LTAR/ChildList.ts

@ -1,6 +1,6 @@
import BasePage from "../../../../Base";
import { DashboardPage } from "../../../index";
import { expect } from "@playwright/test";
import BasePage from '../../../../Base';
import { DashboardPage } from '../../../index';
import { expect } from '@playwright/test';
export class ChildList extends BasePage {
readonly dashboard: DashboardPage;
@ -14,69 +14,43 @@ export class ChildList extends BasePage {
return this.dashboard.get().locator(`.nc-modal-child-list`);
}
async verify({
cardTitle,
linkField,
}: {
cardTitle: string[];
linkField: string;
}) {
async verify({ cardTitle, linkField }: { cardTitle: string[]; linkField: string }) {
// DOM element validation
// title: Child list
// button: Link to 'City'
// icon: reload
await expect(this.get().locator(`.ant-modal-title`)).toHaveText(
`Child list`
);
await expect(
await this.get()
.locator(`button:has-text("Link to '${linkField}'")`)
.isVisible()
).toBeTruthy();
await expect(
await this.get().locator(`[data-cy="nc-child-list-reload"]`).isVisible()
).toBeTruthy();
await expect(this.get().locator(`.ant-modal-title`)).toHaveText(`Child list`);
await expect(await this.get().locator(`button:has-text("Link to '${linkField}'")`).isVisible()).toBeTruthy();
await expect(await this.get().locator(`[data-cy="nc-child-list-reload"]`).isVisible()).toBeTruthy();
// child list body validation (card count, card title)
const cardCount = cardTitle.length;
await this.get().locator(".ant-modal-content").waitFor();
await this.get().locator('.ant-modal-content').waitFor();
{
let childList = this.get().locator(`.ant-card`);
const childList = this.get().locator(`.ant-card`);
const childCards = await childList.count();
await expect(childCards).toEqual(cardCount);
for (let i = 0; i < cardCount; i++) {
await expect(await childList.nth(i).textContent()).toContain(cardTitle[i]);
// icon: unlink
// icon: delete
await expect(
await childList
.nth(i)
.locator(`[data-cy="nc-child-list-icon-unlink"]`)
.isVisible()
).toBeTruthy();
await expect(
await childList
.nth(i)
.locator(`[data-cy="nc-child-list-icon-delete"]`)
.isVisible()
).toBeTruthy();
await expect(await childList.nth(i).locator(`[data-cy="nc-child-list-icon-unlink"]`).isVisible()).toBeTruthy();
await expect(await childList.nth(i).locator(`[data-cy="nc-child-list-icon-delete"]`).isVisible()).toBeTruthy();
}
}
}
async close() {
await this.get().locator(`.ant-modal-close-x`).click();
await this.get().waitFor({ state: "hidden" });
await this.get().waitFor({ state: 'hidden' });
}
async openLinkRecord({ linkTableTitle }: { linkTableTitle: string }) {
const openActions = this.get()
.locator(`button:has-text("Link to '${linkTableTitle}'")`)
.click();
const openActions = this.get().locator(`button:has-text("Link to '${linkTableTitle}'")`).click();
await this.waitForResponse({
requestUrlPathToMatch: '/exclude',
httpMethodsToMatch: ['GET'],
uiAction: openActions
uiAction: openActions,
});
}
}

30
scripts/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts

@ -1,6 +1,6 @@
import BasePage from "../../../../Base";
import { DashboardPage } from "../../../index";
import { expect } from "@playwright/test";
import BasePage from '../../../../Base';
import { DashboardPage } from '../../../index';
import { expect } from '@playwright/test';
export class LinkRecord extends BasePage {
readonly dashboard: DashboardPage;
@ -11,27 +11,21 @@ export class LinkRecord extends BasePage {
}
async verify(cardTitle?: string[]) {
await this.dashboard.get().locator(".nc-modal-link-record").waitFor();
let linkRecord = await this.get();
await this.dashboard.get().locator('.nc-modal-link-record').waitFor();
const linkRecord = await this.get();
// DOM element validation
// title: Link Record
// button: Add new record
// icon: reload
await expect(this.get().locator(`.ant-modal-title`)).toHaveText(
`Link record`
);
await expect(
await linkRecord.locator(`button:has-text("Add new record")`).isVisible()
).toBeTruthy();
await expect(this.get().locator(`.ant-modal-title`)).toHaveText(`Link record`);
await expect(await linkRecord.locator(`button:has-text("Add new record")`).isVisible()).toBeTruthy();
await expect(await linkRecord.locator(`.nc-reload`).isVisible()).toBeTruthy();
// placeholder: Filter query
await expect(
await linkRecord.locator(`[placeholder="Filter query"]`).isVisible()
).toBeTruthy();
await expect(await linkRecord.locator(`[placeholder="Filter query"]`).isVisible()).toBeTruthy();
{
let childList = linkRecord.locator(`.ant-card`);
const childList = linkRecord.locator(`.ant-card`);
const childCards = await childList.count();
await expect(childCards).toEqual(cardTitle.length);
for (let i = 0; i < cardTitle.length; i++) {
@ -41,14 +35,12 @@ export class LinkRecord extends BasePage {
}
async select(cardTitle: string) {
await this.get()
.locator(`.ant-card:has-text("${cardTitle}"):visible`)
.click();
await this.get().locator(`.ant-card:has-text("${cardTitle}"):visible`).click();
}
async close() {
await this.get().locator(`.ant-modal-close-x`).click();
await this.get().waitFor({ state: "hidden" });
await this.get().waitFor({ state: 'hidden' });
}
get() {

58
scripts/playwright/pages/Dashboard/Grid/Column/SelectOptionColumn.ts

@ -1,5 +1,5 @@
import { ColumnPageObject } from ".";
import BasePage from "../../../Base";
import { ColumnPageObject } from '.';
import BasePage from '../../../Base';
export class SelectOptionColumnPageObject extends BasePage {
readonly column: ColumnPageObject;
@ -13,8 +13,18 @@ export class SelectOptionColumnPageObject extends BasePage {
return this.column.get();
}
async addOption({index, columnTitle,option, skipColumnModal}: {index: number, option: string, skipColumnModal?: boolean, columnTitle?: string}) {
if(!skipColumnModal && columnTitle) await this.column.openEdit({title: columnTitle});
async addOption({
index,
columnTitle,
option,
skipColumnModal,
}: {
index: number;
option: string;
skipColumnModal?: boolean;
columnTitle?: string;
}) {
if (!skipColumnModal && columnTitle) await this.column.openEdit({ title: columnTitle });
await this.column.get().locator('button:has-text("Add option")').click();
@ -22,35 +32,47 @@ export class SelectOptionColumnPageObject extends BasePage {
await this.column.get().locator(`[data-pw="select-column-option-input-${index}"]`).click();
await this.column.get().locator(`[data-pw="select-column-option-input-${index}"]`).fill(option);
if(!skipColumnModal && columnTitle) await this.column.save({isUpdated: true});
if (!skipColumnModal && columnTitle) await this.column.save({ isUpdated: true });
}
async editOption({columnTitle, index, newOption}: {index: number, columnTitle: string, newOption: string}) {
await this.column.openEdit({title: columnTitle});
async editOption({ columnTitle, index, newOption }: { index: number; columnTitle: string; newOption: string }) {
await this.column.openEdit({ title: columnTitle });
await this.column.get().locator(`[data-pw="select-column-option-input-${index}"]`).click();
await this.column.get().locator(`[data-pw="select-column-option-input-${index}"]`).fill(newOption);
await this.column.save({isUpdated: true});
await this.column.save({ isUpdated: true });
}
async deleteOption({columnTitle, index}: {index: number, columnTitle: string}) {
await this.column.openEdit({title: columnTitle});
async deleteOption({ columnTitle, index }: { index: number; columnTitle: string }) {
await this.column.openEdit({ title: columnTitle });
await this.column.get().locator(`svg[data-pw="select-column-option-remove-${index}"]`).click();
await this.column.save({isUpdated: true});
await this.column.save({ isUpdated: true });
}
async reorderOption({columnTitle, sourceOption, destinationOption}: {columnTitle: string, sourceOption: string, destinationOption: string}) {
await this.column.openEdit({title: columnTitle});
async reorderOption({
columnTitle,
sourceOption,
destinationOption,
}: {
columnTitle: string;
sourceOption: string;
destinationOption: string;
}) {
await this.column.openEdit({ title: columnTitle });
await this.column.rootPage.waitForTimeout(150);
await this.column.rootPage.dragAndDrop(`svg[data-pw="select-option-column-handle-icon-${sourceOption}"]`, `svg[data-pw="select-option-column-handle-icon-${destinationOption}"]`, {
force: true,
});
await this.column.rootPage.dragAndDrop(
`svg[data-pw="select-option-column-handle-icon-${sourceOption}"]`,
`svg[data-pw="select-option-column-handle-icon-${destinationOption}"]`,
{
force: true,
}
);
await this.column.save({isUpdated: true});
await this.column.save({ isUpdated: true });
}
}
}

164
scripts/playwright/pages/Dashboard/Grid/Column/index.ts

@ -1,7 +1,7 @@
import { Page, expect } from "@playwright/test";
import { GridPage } from "..";
import BasePage from "../../../Base";
import { SelectOptionColumnPageObject } from "./SelectOptionColumn";
import { expect, Page } from '@playwright/test';
import { GridPage } from '..';
import BasePage from '../../../Base';
import { SelectOptionColumnPageObject } from './SelectOptionColumn';
export class ColumnPageObject extends BasePage {
readonly grid: GridPage;
@ -19,13 +19,13 @@ export class ColumnPageObject extends BasePage {
async create({
title,
type = "SingleLineText",
formula = "",
childTable = "",
childColumn = "",
relationType = "",
rollupType = "",
format = "",
type = 'SingleLineText',
formula = '',
childTable = '',
childColumn = '',
relationType = '',
rollupType = '',
format = '',
}: {
title: string;
type?: string;
@ -36,7 +36,7 @@ export class ColumnPageObject extends BasePage {
rollupType?: string;
format?: string;
}) {
await this.grid.get().locator(".nc-column-add").click();
await this.grid.get().locator('.nc-column-add').click();
await this.rootPage.waitForTimeout(500);
await this.fillTitle({ title });
await this.rootPage.waitForTimeout(500);
@ -44,60 +44,60 @@ export class ColumnPageObject extends BasePage {
await this.rootPage.waitForTimeout(500);
switch (type) {
case "SingleTextLine":
case 'SingleTextLine':
break;
case "SingleSelect":
case "MultiSelect":
case 'SingleSelect':
case 'MultiSelect':
await this.selectOption.addOption({
index: 0,
option: "Option 1",
option: 'Option 1',
skipColumnModal: true,
});
await this.selectOption.addOption({
index: 1,
option: "Option 2",
option: 'Option 2',
skipColumnModal: true,
});
break;
case "Duration":
await this.get().locator(".ant-select-single").nth(1).click();
case 'Duration':
await this.get().locator('.ant-select-single').nth(1).click();
await this.rootPage
.locator(`.ant-select-item`, {
hasText: format,
})
.click();
break;
case "Formula":
await this.get().locator(".nc-formula-input").fill(formula);
case 'Formula':
await this.get().locator('.nc-formula-input').fill(formula);
break;
case "Lookup":
await this.get().locator(".ant-select-single").nth(1).click();
case 'Lookup':
await this.get().locator('.ant-select-single').nth(1).click();
await this.rootPage
.locator(`.ant-select-item`, {
hasText: childTable,
})
.click();
await this.get().locator(".ant-select-single").nth(2).click();
await this.get().locator('.ant-select-single').nth(2).click();
await this.rootPage
.locator(`.ant-select-item`, {
hasText: childColumn,
})
.click();
break;
case "Rollup":
await this.get().locator(".ant-select-single").nth(1).click();
case 'Rollup':
await this.get().locator('.ant-select-single').nth(1).click();
await this.rootPage
.locator(`.ant-select-item`, {
hasText: childTable,
})
.click();
await this.get().locator(".ant-select-single").nth(2).click();
await this.get().locator('.ant-select-single').nth(2).click();
await this.rootPage
.locator(`.nc-dropdown-relation-column >> .ant-select-item`, {
hasText: childColumn,
})
.click();
await this.get().locator(".ant-select-single").nth(3).click();
await this.get().locator('.ant-select-single').nth(3).click();
await this.rootPage
.locator(`.nc-dropdown-rollup-function >> .ant-select-item`, {
hasText: rollupType,
@ -105,15 +105,13 @@ export class ColumnPageObject extends BasePage {
.nth(0)
.click();
break;
case "LinkToAnotherRecord":
case 'LinkToAnotherRecord':
await this.get()
.locator(".nc-ltar-relation-type >> .ant-radio")
.nth(relationType === "Has Many" ? 0 : 1)
.locator('.nc-ltar-relation-type >> .ant-radio')
.nth(relationType === 'Has Many' ? 0 : 1)
.click();
await this.get().locator(".ant-select-single").nth(1).click();
await this.rootPage
.locator(`.nc-ltar-child-table >> input[type="search"]`)
.fill(childTable);
await this.get().locator('.ant-select-single').nth(1).click();
await this.rootPage.locator(`.nc-ltar-child-table >> input[type="search"]`).fill(childTable);
await this.rootPage
.locator(`.nc-dropdown-ltar-child-table >> .ant-select-item`, {
hasText: childTable,
@ -129,47 +127,34 @@ export class ColumnPageObject extends BasePage {
}
async fillTitle({ title }: { title: string }) {
await this.get().locator(".nc-column-name-input").fill(title);
await this.get().locator('.nc-column-name-input').fill(title);
}
async selectType({ type }: { type: string }) {
await this.get()
.locator(".ant-select-selector > .ant-select-selection-item")
.click();
await this.get().locator('.ant-select-selector > .ant-select-selection-item').click();
await this.get()
.locator('.ant-select-selection-search-input[aria-expanded="true"]')
.waitFor();
await this.get()
.locator('.ant-select-selection-search-input[aria-expanded="true"]')
.fill(type);
await this.get().locator('.ant-select-selection-search-input[aria-expanded="true"]').waitFor();
await this.get().locator('.ant-select-selection-search-input[aria-expanded="true"]').fill(type);
// Select column type
await this.rootPage.locator(`text=${type}`).nth(1).click();
}
async delete({ title }: { title: string }) {
await this.grid
.get()
.locator(`th[data-title="${title}"] >> svg.ant-dropdown-trigger`)
.click();
await this.grid.get().locator(`th[data-title="${title}"] >> svg.ant-dropdown-trigger`).click();
// await this.rootPage.locator('li[role="menuitem"]:has-text("Delete")').waitFor();
await this.rootPage
.locator('li[role="menuitem"]:has-text("Delete")')
.click();
await this.rootPage.locator('li[role="menuitem"]:has-text("Delete")').click();
await this.rootPage.locator('button:has-text("Delete")').click();
// wait till modal is closed
await this.rootPage
.locator(".nc-modal-column-delete")
.waitFor({ state: "hidden" });
await this.rootPage.locator('.nc-modal-column-delete').waitFor({ state: 'hidden' });
}
async openEdit({
title,
type = "SingleLineText",
formula = "",
type = 'SingleLineText',
formula = '',
format,
}: {
title: string;
@ -177,20 +162,17 @@ export class ColumnPageObject extends BasePage {
formula?: string;
format?: string;
}) {
await this.grid
.get()
.locator(`th[data-title="${title}"] .nc-ui-dt-dropdown`)
.click();
await this.grid.get().locator(`th[data-title="${title}"] .nc-ui-dt-dropdown`).click();
await this.rootPage.locator('li[role="menuitem"]:has-text("Edit")').click();
await this.get().waitFor({ state: "visible" });
await this.get().waitFor({ state: 'visible' });
switch (type) {
case "Formula":
await this.get().locator(".nc-formula-input").fill(formula);
case 'Formula':
await this.get().locator('.nc-formula-input').fill(formula);
break;
case "Duration":
await this.get().locator(".ant-select-single").nth(1).click();
case 'Duration':
await this.get().locator('.ant-select-single').nth(1).click();
await this.rootPage
.locator(`.ant-select-item`, {
hasText: format,
@ -207,55 +189,31 @@ export class ColumnPageObject extends BasePage {
uiAction: this.get().locator('button:has-text("Save")').click(),
requestUrlPathToMatch: 'api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
responseJsonMatcher: (json) => json['pageInfo'],
responseJsonMatcher: json => json['pageInfo'],
});
await this.verifyToast({
message: isUpdated ? "Column updated" : "Column created",
message: isUpdated ? 'Column updated' : 'Column created',
});
await this.get().waitFor({ state: "hidden" });
await this.get().waitFor({ state: 'hidden' });
await this.rootPage.waitForTimeout(200);
}
async verify({
title,
isVisible = true,
}: {
title: string;
isVisible?: boolean;
}) {
async verify({ title, isVisible = true }: { title: string; isVisible?: boolean }) {
if (!isVisible) {
return await expect(
await this.rootPage.locator(`th[data-title="${title}"]`)
).not.toBeVisible();
return await expect(await this.rootPage.locator(`th[data-title="${title}"]`)).not.toBeVisible();
}
await await expect(
this.rootPage.locator(`th[data-title="${title}"]`)
).toContainText(title);
await await expect(this.rootPage.locator(`th[data-title="${title}"]`)).toContainText(title);
}
async verifyRoleAccess(param: { role: string }) {
await expect(
this.grid.get().locator(".nc-column-add:visible")
).toHaveCount(param.role === "creator" ? 1 : 0);
await expect(
this.grid.get().locator(".nc-ui-dt-dropdown:visible")
).toHaveCount(param.role === "creator" ? 3 : 0);
await expect(this.grid.get().locator('.nc-column-add:visible')).toHaveCount(param.role === 'creator' ? 1 : 0);
await expect(this.grid.get().locator('.nc-ui-dt-dropdown:visible')).toHaveCount(param.role === 'creator' ? 3 : 0);
if (param.role === "creator") {
await this.grid
.get()
.locator(".nc-ui-dt-dropdown:visible")
.first()
.click();
await expect(
this.rootPage.locator(".nc-dropdown-column-operations")
).toHaveCount(1);
await this.grid
.get()
.locator(".nc-ui-dt-dropdown:visible")
.first()
.click();
if (param.role === 'creator') {
await this.grid.get().locator('.nc-ui-dt-dropdown:visible').first().click();
await expect(this.rootPage.locator('.nc-dropdown-column-operations')).toHaveCount(1);
await this.grid.get().locator('.nc-ui-dt-dropdown:visible').first().click();
}
}
}
}

176
scripts/playwright/pages/Dashboard/Grid/index.ts

@ -1,11 +1,11 @@
// playwright-dev-page.ts
import { Locator, expect } from "@playwright/test";
import { DashboardPage } from "..";
import BasePage from "../../Base";
import { CellPageObject } from "../common/Cell";
import { ColumnPageObject } from "./Column";
import { ToolbarPage } from "../common/Toolbar";
import { ProjectMenuObject } from "../common/ProjectMenu";
import { expect, Locator } from '@playwright/test';
import { DashboardPage } from '..';
import BasePage from '../../Base';
import { CellPageObject } from '../common/Cell';
import { ColumnPageObject } from './Column';
import { ToolbarPage } from '../common/Toolbar';
import { ProjectMenuObject } from '../common/ProjectMenu';
export class GridPage extends BasePage {
readonly dashboard: DashboardPage;
@ -19,7 +19,7 @@ export class GridPage extends BasePage {
constructor(dashboardPage: DashboardPage) {
super(dashboardPage.rootPage);
this.dashboard = dashboardPage;
this.addNewTableButton = dashboardPage.get().locator(".nc-add-new-table");
this.addNewTableButton = dashboardPage.get().locator('.nc-add-new-table');
this.column = new ColumnPageObject(this);
this.cell = new CellPageObject(this);
this.toolbar = new ToolbarPage(this);
@ -35,34 +35,26 @@ export class GridPage extends BasePage {
}
async rowCount() {
return await this.get().locator(".nc-grid-row").count();
return await this.get().locator('.nc-grid-row').count();
}
async verifyRowCount({ count }: { count: number }) {
return await expect(this.get().locator(".nc-grid-row")).toHaveCount(count);
return await expect(this.get().locator('.nc-grid-row')).toHaveCount(count);
}
private async _fillRow({
index,
columnHeader,
value,
}: {
index: number;
columnHeader: string;
value: string;
}) {
private async _fillRow({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) {
const cell = this.cell.get({ index, columnHeader });
await this.cell.dblclick({
index,
columnHeader,
});
await cell.locator("input").fill(value);
await cell.locator('input').fill(value);
}
async addNewRow({
index = 0,
columnHeader = "Title",
columnHeader = 'Title',
value,
networkValidation = true,
}: {
@ -72,10 +64,10 @@ export class GridPage extends BasePage {
networkValidation?: boolean;
} = {}) {
const rowValue = value ?? `Row ${index}`;
const rowCount = await this.get().locator(".nc-grid-row").count();
await this.get().locator(".nc-grid-add-new-cell").click();
const rowCount = await this.get().locator('.nc-grid-row').count();
await this.get().locator('.nc-grid-add-new-cell').click();
await expect(this.get().locator(".nc-grid-row")).toHaveCount(rowCount + 1);
await expect(this.get().locator('.nc-grid-row')).toHaveCount(rowCount + 1);
await this._fillRow({ index, columnHeader, value: rowValue });
@ -87,9 +79,9 @@ export class GridPage extends BasePage {
if (networkValidation) {
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: "api/v1/db/data/noco",
httpMethodsToMatch: ["POST"],
responseJsonMatcher: (resJson) => resJson?.[columnHeader] === value,
requestUrlPathToMatch: 'api/v1/db/data/noco',
httpMethodsToMatch: ['POST'],
responseJsonMatcher: resJson => resJson?.[columnHeader] === value,
});
} else {
await this.rootPage.waitForTimeout(300);
@ -100,7 +92,7 @@ export class GridPage extends BasePage {
async editRow({
index = 0,
columnHeader = "Title",
columnHeader = 'Title',
value,
networkValidation = true,
}: {
@ -119,9 +111,9 @@ export class GridPage extends BasePage {
if (networkValidation) {
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: "api/v1/db/data/noco",
httpMethodsToMatch: ["PATCH"],
responseJsonMatcher: (resJson) => resJson?.[columnHeader] === value,
requestUrlPathToMatch: 'api/v1/db/data/noco',
httpMethodsToMatch: ['PATCH'],
responseJsonMatcher: resJson => resJson?.[columnHeader] === value,
});
} else {
await this.rootPage.waitForTimeout(300);
@ -131,68 +123,57 @@ export class GridPage extends BasePage {
}
async verifyRow({ index }: { index: number }) {
await this.get()
.locator(`td[data-pw="cell-Title-${index}"]`)
.waitFor({ state: "visible" });
await this.get().locator(`td[data-pw="cell-Title-${index}"]`).waitFor({ state: 'visible' });
await expect(this.get().locator(`td[data-pw="cell-Title-${index}"]`)).toHaveCount(1);
}
async verifyRowDoesNotExist({ index }: { index: number }) {
await this.get()
.locator(`td[data-pw="cell-Title-${index}"]`)
.waitFor({ state: "hidden" });
await this.get().locator(`td[data-pw="cell-Title-${index}"]`).waitFor({ state: 'hidden' });
return await expect(this.get().locator(`td[data-pw="cell-Title-${index}"]`)).toHaveCount(0);
}
async deleteRow(index: number) {
await this.get().locator(`td[data-pw="cell-Title-${index}"]`).click({
button: "right",
button: 'right',
});
// Click text=Delete Row
await this.rootPage.locator("text=Delete Row").click();
await this.rootPage.locator('text=Delete Row').click();
// todo: improve selector
await this.rootPage
.locator("span.ant-dropdown-menu-title-content > nc-project-menu-item")
.waitFor({ state: "hidden" });
.locator('span.ant-dropdown-menu-title-content > nc-project-menu-item')
.waitFor({ state: 'hidden' });
await this.rootPage.waitForTimeout(300);
await this.dashboard.waitForLoaderToDisappear();
}
async addRowRightClickMenu(index: number) {
const rowCount = await this.get().locator(".nc-grid-row").count();
const rowCount = await this.get().locator('.nc-grid-row').count();
await this.get().locator(`td[data-pw="cell-Title-${index}"]`).click({
button: "right",
button: 'right',
});
// Click text=Insert New Row
await this.rootPage.locator("text=Insert New Row").click();
await expect(await this.get().locator(".nc-grid-row")).toHaveCount(rowCount + 1);
await this.rootPage.locator('text=Insert New Row').click();
await expect(await this.get().locator('.nc-grid-row')).toHaveCount(rowCount + 1);
}
async openExpandedRow({ index }: { index: number }) {
await this.row(index).locator(`td[pw-data="cell-Id-${index}"]`).hover();
await this.row(index).locator(`div[pw-data="nc-expand-${index}"]`).click();
await (
await this.rootPage.locator(".ant-drawer-body").elementHandle()
)?.waitForElementState("stable");
await (await this.rootPage.locator('.ant-drawer-body').elementHandle())?.waitForElementState('stable');
}
async selectAll() {
await this.get().locator('[pw-data="nc-check-all"]').hover();
await this.get()
.locator('[pw-data="nc-check-all"]')
.locator('input[type="checkbox"]')
.check({
force: true,
});
await this.get().locator('[pw-data="nc-check-all"]').locator('input[type="checkbox"]').check({
force: true,
});
const rowCount = await this.rowCount();
for (let i = 0; i < rowCount; i++) {
await expect(
this.row(i).locator(`[pw-data="cell-Id-${i}"]`).locator("span.ant-checkbox-checked")
).toHaveCount(1);
await expect(this.row(i).locator(`[pw-data="cell-Id-${i}"]`).locator('span.ant-checkbox-checked')).toHaveCount(1);
}
await this.rootPage.waitForTimeout(300);
}
@ -200,124 +181,101 @@ export class GridPage extends BasePage {
async deleteAll() {
await this.selectAll();
await this.get().locator('[pw-data="nc-check-all"]').nth(0).click({
button: "right",
button: 'right',
});
await this.rootPage.locator("text=Delete Selected Rows").click();
await this.rootPage.locator('text=Delete Selected Rows').click();
await this.dashboard.waitForLoaderToDisappear();
}
private async pagination({ page }: { page: string }) {
await this.get().locator(`.nc-pagination`).waitFor();
if (page === "<")
return this.get().locator(".nc-pagination > .ant-pagination-prev");
if (page === ">")
return this.get().locator(".nc-pagination > .ant-pagination-next");
if (page === '<') return this.get().locator('.nc-pagination > .ant-pagination-prev');
if (page === '>') return this.get().locator('.nc-pagination > .ant-pagination-next');
return this.get().locator(
`.nc-pagination > .ant-pagination-item.ant-pagination-item-${page}`
);
return this.get().locator(`.nc-pagination > .ant-pagination-item.ant-pagination-item-${page}`);
}
async clickPagination({ page }: { page: string }) {
await this.waitForResponse({
uiAction: (await this.pagination({ page })).click(),
httpMethodsToMatch: ["GET"],
requestUrlPathToMatch: "/views/",
responseJsonMatcher: (resJson) => resJson?.pageInfo,
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: '/views/',
responseJsonMatcher: resJson => resJson?.pageInfo,
});
await this.waitLoading();
}
async verifyActivePage({ page }: { page: string }) {
await expect(await this.pagination({ page })).toHaveClass(
/ant-pagination-item-active/
);
await expect(await this.pagination({ page })).toHaveClass(/ant-pagination-item-active/);
}
async waitLoading() {
await this.dashboard
.get()
.locator('[pw-data="grid-load-spinner"]')
.waitFor({ state: "hidden" });
await this.dashboard.get().locator('[pw-data="grid-load-spinner"]').waitFor({ state: 'hidden' });
}
async verifyEditDisabled({
columnHeader = "Title",
}: { columnHeader?: string } = {}) {
async verifyEditDisabled({ columnHeader = 'Title' }: { columnHeader?: string } = {}) {
// double click to toggle to edit mode
const cell = this.cell.get({ index: 0, columnHeader: columnHeader });
await this.cell.dblclick({
index: 0,
columnHeader: columnHeader,
});
await expect(await cell.locator("input")).not.toBeVisible();
await expect(await cell.locator('input')).not.toBeVisible();
// right click menu
await this.get().locator(`td[data-pw="cell-${columnHeader}-0"]`).click({
button: "right",
button: 'right',
});
await expect(
await this.rootPage.locator("text=Insert New Row")
).not.toBeVisible();
await expect(await this.rootPage.locator('text=Insert New Row')).not.toBeVisible();
// in cell-add
await this.cell.get({ index: 0, columnHeader: "City List" }).hover();
await this.cell.get({ index: 0, columnHeader: 'City List' }).hover();
await expect(
await this.cell
.get({ index: 0, columnHeader: "City List" })
.locator(".nc-action-icon.nc-plus")
await this.cell.get({ index: 0, columnHeader: 'City List' }).locator('.nc-action-icon.nc-plus')
).not.toBeVisible();
// expand row
await this.cell.get({ index: 0, columnHeader: "City List" }).hover();
await this.cell.get({ index: 0, columnHeader: 'City List' }).hover();
await expect(
await this.cell
.get({ index: 0, columnHeader: "City List" })
.locator(".nc-action-icon >> nth=0")
await this.cell.get({ index: 0, columnHeader: 'City List' }).locator('.nc-action-icon >> nth=0')
).not.toBeVisible();
}
async verifyEditEnabled({
columnHeader = "Title",
}: { columnHeader?: string } = {}) {
async verifyEditEnabled({ columnHeader = 'Title' }: { columnHeader?: string } = {}) {
// double click to toggle to edit mode
const cell = this.cell.get({ index: 0, columnHeader: columnHeader });
await this.cell.dblclick({
index: 0,
columnHeader: columnHeader,
});
await expect(await cell.locator("input")).toBeVisible();
await expect(await cell.locator('input')).toBeVisible();
// right click menu
await this.get().locator(`td[data-pw="cell-${columnHeader}-0"]`).click({
button: "right",
button: 'right',
});
await expect(await this.rootPage.locator("text=Insert New Row")).toBeVisible();
await expect(await this.rootPage.locator('text=Insert New Row')).toBeVisible();
// in cell-add
await this.cell.get({ index: 0, columnHeader: "City List" }).hover();
await this.cell.get({ index: 0, columnHeader: 'City List' }).hover();
await expect(
await this.cell
.get({ index: 0, columnHeader: "City List" })
.locator(".nc-action-icon.nc-plus")
await this.cell.get({ index: 0, columnHeader: 'City List' }).locator('.nc-action-icon.nc-plus')
).toBeVisible();
// expand row
await this.cell.get({ index: 0, columnHeader: "City List" }).hover();
await this.cell.get({ index: 0, columnHeader: 'City List' }).hover();
await expect(
await this.cell
.get({ index: 0, columnHeader: "City List" })
.locator(".nc-action-icon.nc-arrow-expand")
await this.cell.get({ index: 0, columnHeader: 'City List' }).locator('.nc-action-icon.nc-arrow-expand')
).toBeVisible();
}
async validateRoleAccess(param: { role: string }) {
await this.column.verifyRoleAccess(param);
await this.cell.verifyRoleAccess(param);
await expect(this.get().locator(".nc-grid-add-new-cell")).toHaveCount(
param.role === "creator" || param.role === "editor" ? 1 : 0
await expect(this.get().locator('.nc-grid-add-new-cell')).toHaveCount(
param.role === 'creator' || param.role === 'editor' ? 1 : 0
);
}
}
}

8
scripts/playwright/pages/Dashboard/Import/Airtable.ts

@ -1,7 +1,7 @@
// playwright-dev-page.ts
import { expect, Locator } from "@playwright/test";
import BasePage from "../../Base";
import { DashboardPage } from "..";
import { expect, Locator } from '@playwright/test';
import BasePage from '../../Base';
import { DashboardPage } from '..';
export class ImportAirtablePage extends BasePage {
readonly dashboard: DashboardPage;
@ -10,7 +10,7 @@ export class ImportAirtablePage extends BasePage {
constructor(dashboard: DashboardPage) {
super(dashboard.rootPage);
this.dashboard = dashboard;
this.importButton = dashboard.get().locator(".nc-btn-airtable-import");
this.importButton = dashboard.get().locator('.nc-btn-airtable-import');
}
get() {

41
scripts/playwright/pages/Dashboard/Import/ImportTemplate.ts

@ -1,7 +1,7 @@
// playwright-dev-page.ts
import { expect, Locator } from "@playwright/test";
import BasePage from "../../Base";
import { DashboardPage } from "..";
import { expect, Locator } from '@playwright/test';
import BasePage from '../../Base';
import { DashboardPage } from '..';
export class ImportTemplatePage extends BasePage {
readonly dashboard: DashboardPage;
@ -10,7 +10,7 @@ export class ImportTemplatePage extends BasePage {
constructor(dashboard: DashboardPage) {
super(dashboard.rootPage);
this.dashboard = dashboard;
this.importButton = dashboard.get().locator(".nc-btn-import");
this.importButton = dashboard.get().locator('.nc-btn-import');
}
get() {
@ -19,11 +19,11 @@ export class ImportTemplatePage extends BasePage {
async getImportTableList() {
await this.get().locator(`.ant-collapse-header`).nth(0).waitFor();
let tr = await this.get().locator(`.ant-collapse-header`);
let rowCount = await tr.count();
let tableList: string[] = [];
const tr = await this.get().locator(`.ant-collapse-header`);
const rowCount = await tr.count();
const tableList: string[] = [];
for (let i = 0; i < rowCount; i++) {
let tableName = await tr.nth(i).innerText();
const tableName = await tr.nth(i).innerText();
tableList.push(tableName);
}
return tableList;
@ -31,19 +31,16 @@ export class ImportTemplatePage extends BasePage {
async getImportColumnList() {
// return an array
let columnList: {type: string; name: string}[] = [];
let tr = await this.get().locator(`tr.ant-table-row-level-0:visible`);
let rowCount = await tr.count();
const columnList: { type: string; name: string }[] = [];
const tr = await this.get().locator(`tr.ant-table-row-level-0:visible`);
const rowCount = await tr.count();
for (let i = 0; i < rowCount; i++) {
// replace \n and \t from innerText
let columnType = await tr
const columnType = await tr
.nth(i)
.innerText()
.then((text) => text.replace(/\n|\t/g, ""));
let columnName = await tr
.nth(i)
.locator(`input[type="text"]`)
.inputValue();
.then(text => text.replace(/\n|\t/g, ''));
const columnName = await tr.nth(i).locator(`input[type="text"]`).inputValue();
columnList.push({ type: columnType, name: columnName });
}
return columnList;
@ -51,14 +48,14 @@ export class ImportTemplatePage extends BasePage {
// todo: Add polling logic to assertions
async import({ file, result }: { file: string; result: any }) {
let importFile = this.get().locator(`input[type="file"]`);
const importFile = this.get().locator(`input[type="file"]`);
await importFile.setInputFiles(file);
await this.importButton.click();
let tblList = await this.getImportTableList();
const tblList = await this.getImportTableList();
for (let i = 0; i < result.length; i++) {
await expect(tblList[i]).toBe(result[i].name);
let columnList = await this.getImportColumnList();
const columnList = await this.getImportColumnList();
await expect(columnList).toEqual(result[i].columns);
if (i < result.length - 1) {
await this.expandTableList({ index: i + 1 });
@ -69,11 +66,11 @@ export class ImportTemplatePage extends BasePage {
await this.waitForResponse({
requestUrlPathToMatch: '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
uiAction: this.get().locator('button:has-text("Import"):visible').click()
uiAction: this.get().locator('button:has-text("Import"):visible').click(),
});
await this.dashboard.waitForTabRender({
title: tblList[0],
})
});
}
private async expandTableList(param: { index: number }) {

63
scripts/playwright/pages/Dashboard/Kanban/index.ts

@ -1,8 +1,8 @@
// playwright-dev-page.ts
import { Locator, expect } from "@playwright/test";
import { DashboardPage } from "..";
import BasePage from "../../Base";
import { ToolbarPage } from "../common/Toolbar";
import { expect, Locator } from '@playwright/test';
import { DashboardPage } from '..';
import BasePage from '../../Base';
import { ToolbarPage } from '../common/Toolbar';
export class KanbanPage extends BasePage {
readonly dashboard: DashboardPage;
@ -24,9 +24,7 @@ export class KanbanPage extends BasePage {
async openExpandedRow({ index }: { index: number }) {
await this.card(index).click();
await (
await this.rootPage.locator(".ant-drawer-body").elementHandle()
)?.waitForElementState("stable");
await (await this.rootPage.locator('.ant-drawer-body').elementHandle())?.waitForElementState('stable');
}
async addOption() {}
@ -66,8 +64,7 @@ export class KanbanPage extends BasePage {
for (let i = 0; i < stacks; i++) {
const stack = await this.get().locator(`.nc-kanban-stack`).nth(i);
// Since otherwise stack title will be repeated as title is in two divs, with one having hidden class
const stackTitle = await stack
.locator(`.nc-kanban-stack-head >> [nc-data="truncate-label"]`);
const stackTitle = await stack.locator(`.nc-kanban-stack-head >> [nc-data="truncate-label"]`);
await expect(stackTitle).toHaveText(order[i], { ignoreCase: true });
}
}
@ -77,12 +74,8 @@ export class KanbanPage extends BasePage {
const stacks = await this.get().locator(`.nc-kanban-stack`).count();
for (let i = 0; i < stacks; i++) {
const stack = await this.get().locator(`.nc-kanban-stack`).nth(i);
const stackFooter = await stack
.locator(`.nc-kanban-data-count`)
.innerText();
await expect(stackFooter).toContain(
`${count[i]} record${count[i] !== 1 ? "s" : ""}`
);
const stackFooter = await stack.locator(`.nc-kanban-data-count`).innerText();
await expect(stackFooter).toContain(`${count[i]} record${count[i] !== 1 ? 's' : ''}`);
}
}
@ -118,49 +111,29 @@ export class KanbanPage extends BasePage {
async collapseStack(param: { index: number }) {
await this.get().locator(`.nc-kanban-stack-head`).nth(param.index).click();
const modal = await this.rootPage.locator(
`.nc-dropdown-kanban-stack-context-menu`
);
await modal
.locator('.ant-dropdown-menu-item:has-text("Collapse Stack")')
.click();
const modal = await this.rootPage.locator(`.nc-dropdown-kanban-stack-context-menu`);
await modal.locator('.ant-dropdown-menu-item:has-text("Collapse Stack")').click();
}
async expandStack(param: { index: number }) {
await this.rootPage
.locator(`.nc-kanban-collapsed-stack`)
.nth(param.index)
.click();
await this.rootPage.locator(`.nc-kanban-collapsed-stack`).nth(param.index).click();
}
async verifyCollapseStackCount(param: { count: number }) {
await expect(this.rootPage.locator(".nc-kanban-collapsed-stack")).toHaveCount(param.count);
await expect(this.rootPage.locator('.nc-kanban-collapsed-stack')).toHaveCount(param.count);
}
async addCard(param: { stackIndex: number }) {
await this.get()
.locator(`.nc-kanban-stack-head`)
.nth(param.stackIndex)
.click();
const modal = await this.rootPage.locator(
`.nc-dropdown-kanban-stack-context-menu`
);
await modal
.locator('.ant-dropdown-menu-item:has-text("Add new record")')
.click();
await this.get().locator(`.nc-kanban-stack-head`).nth(param.stackIndex).click();
const modal = await this.rootPage.locator(`.nc-dropdown-kanban-stack-context-menu`);
await modal.locator('.ant-dropdown-menu-item:has-text("Add new record")').click();
}
async deleteStack(param: { index: number }) {
await this.get().locator(`.nc-kanban-stack-head`).nth(param.index).click();
const modal = await this.rootPage.locator(
`.nc-dropdown-kanban-stack-context-menu`
);
await modal
.locator('.ant-dropdown-menu-item:has-text("Delete Stack")')
.click();
const confirmationModal = await this.rootPage.locator(
`.nc-modal-kanban-delete-stack`
);
const modal = await this.rootPage.locator(`.nc-dropdown-kanban-stack-context-menu`);
await modal.locator('.ant-dropdown-menu-item:has-text("Delete Stack")').click();
const confirmationModal = await this.rootPage.locator(`.nc-modal-kanban-delete-stack`);
await confirmationModal.locator(`button:has-text("Delete")`).click();
}
}

18
scripts/playwright/pages/Dashboard/Settings/Acl.ts

@ -1,6 +1,6 @@
import { Locator, expect } from "@playwright/test";
import { SettingsPage } from ".";
import BasePage from "../../Base";
import { expect, Locator } from '@playwright/test';
import { SettingsPage } from '.';
import BasePage from '../../Base';
export class AclPage extends BasePage {
private readonly settings: SettingsPage;
@ -11,9 +11,7 @@ export class AclPage extends BasePage {
}
get() {
return this.settings
.get()
.locator(`[pw-data="nc-settings-subtab-UI Access Control"]`);
return this.settings.get().locator(`[pw-data="nc-settings-subtab-UI Access Control"]`);
}
async toggle({ table, role }: { table: string; role: string }) {
@ -23,9 +21,9 @@ export class AclPage extends BasePage {
async save() {
await this.waitForResponse({
uiAction: this.get().locator(`button:has-text("Save")`).click(),
httpMethodsToMatch: ["POST"],
requestUrlPathToMatch: '/visibility-rules'
})
await this.verifyToast({ message: "Updated UI ACL for tables successfully" });
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/visibility-rules',
});
await this.verifyToast({ message: 'Updated UI ACL for tables successfully' });
}
}

32
scripts/playwright/pages/Dashboard/Settings/AppStore.ts

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import { SettingsPage } from ".";
import BasePage from "../../Base";
import { expect } from '@playwright/test';
import { SettingsPage } from '.';
import BasePage from '../../Base';
export class AppStoreSettingsPage extends BasePage {
private readonly settings: SettingsPage;
@ -11,29 +11,19 @@ export class AppStoreSettingsPage extends BasePage {
}
get() {
return this.settings
.get()
.locator(`[pw-data="nc-settings-subtab-appStore"]`);
return this.settings.get().locator(`[pw-data="nc-settings-subtab-appStore"]`);
}
async install({ name }: { name: string }) {
let card = await this.settings.get().locator(`.nc-app-store-card-${name}`);
const card = await this.settings.get().locator(`.nc-app-store-card-${name}`);
await card.click();
await card.locator(".nc-app-store-card-install").click();
await card.locator('.nc-app-store-card-install').click();
}
async configureSlack() {}
async configureSMTP({
email,
host,
port,
}: {
email: string;
host: string;
port: string;
}) {
let appStoreCard = this.rootPage.locator(".nc-modal-plugin-install");
async configureSMTP({ email, host, port }: { email: string; host: string; port: string }) {
const appStoreCard = this.rootPage.locator('.nc-modal-plugin-install');
await appStoreCard.locator('[id="form_item_from"]').fill(email);
await appStoreCard.locator('[id="form_item_host"]').fill(host);
@ -43,11 +33,11 @@ export class AppStoreSettingsPage extends BasePage {
}
async uninstall(param: { name: string }) {
let card = this.settings.get().locator(`.nc-app-store-card-${param.name}`);
const card = this.settings.get().locator(`.nc-app-store-card-${param.name}`);
// await card.scrollIntoViewIfNeeded();
await card.click();
await card.locator(".nc-app-store-card-reset").click();
await this.rootPage.locator("button.ant-btn-dangerous").click();
await card.locator('.nc-app-store-card-reset').click();
await this.rootPage.locator('button.ant-btn-dangerous').click();
}
}

71
scripts/playwright/pages/Dashboard/Settings/Audit.ts

@ -1,4 +1,4 @@
import { expect } from '@playwright/test';
import { expect } from '@playwright/test';
import { SettingsPage } from '.';
import BasePage from '../../Base';
@ -14,37 +14,62 @@ export class AuditSettingsPage extends BasePage {
return this.settings.get().locator(`[pw-data="nc-settings-subtab-Audit"]`);
}
async verifyRow(
{index, opType, opSubtype, description, user, created}:
{index: number,opType?: string, opSubtype?: string, description?: string, user?: string, created?: string}
) {
async verifyRow({
index,
opType,
opSubtype,
description,
user,
created,
}: {
index: number;
opType?: string;
opSubtype?: string;
description?: string;
user?: string;
created?: string;
}) {
const table = await this.get();
const row = table.locator(`tr.ant-table-row`).nth(index);
if(opType) {
await row.locator(`td.ant-table-cell`).nth(0).textContent()
.then(async (text) =>await expect(text).toContain(opType));
if (opType) {
await row
.locator(`td.ant-table-cell`)
.nth(0)
.textContent()
.then(async text => await expect(text).toContain(opType));
}
if(opSubtype) {
await row.locator(`td.ant-table-cell`).nth(1).textContent()
.then(async (text) => await expect(text).toContain(opSubtype));
if (opSubtype) {
await row
.locator(`td.ant-table-cell`)
.nth(1)
.textContent()
.then(async text => await expect(text).toContain(opSubtype));
}
if(description) {
await row.locator(`td.ant-table-cell`).nth(2).textContent()
.then(async (text) => await expect(text).toContain(description));
if (description) {
await row
.locator(`td.ant-table-cell`)
.nth(2)
.textContent()
.then(async text => await expect(text).toContain(description));
}
if(user) {
await row.locator(`td.ant-table-cell`).nth(3).textContent()
.then(async (text) => await expect(text).toContain(user));
if (user) {
await row
.locator(`td.ant-table-cell`)
.nth(3)
.textContent()
.then(async text => await expect(text).toContain(user));
}
if(created) {
await row.locator(`td.ant-table-cell`).nth(4).textContent()
.then(async (text) => await expect(text).toContain(created));
if (created) {
await row
.locator(`td.ant-table-cell`)
.nth(4)
.textContent()
.then(async text => await expect(text).toContain(created));
}
}
}
}

5
scripts/playwright/pages/Dashboard/Settings/Erd.ts

@ -1,6 +1,5 @@
import { SettingsPage } from ".";
import { ErdBasePage } from "../commonBase/Erd";
import { SettingsPage } from '.';
import { ErdBasePage } from '../commonBase/Erd';
export class SettingsErdPage extends ErdBasePage {
readonly settings: SettingsPage;

38
scripts/playwright/pages/Dashboard/Settings/Metadata.ts

@ -1,4 +1,4 @@
import { expect } from '@playwright/test';
import { expect } from '@playwright/test';
import { SettingsPage } from '.';
import BasePage from '../../Base';
@ -14,29 +14,35 @@ export class MetaDataPage extends BasePage {
return this.settings.get().locator(`[pw-data="nc-settings-subtab-Metadata"]`);
}
async clickReload(){
async clickReload() {
await this.get().locator(`button:has-text("Reload")`).click();
// todo: Remove this wait
await this.rootPage.waitForTimeout(100);
// await this.get().locator(`.animate-spin`).waitFor({state: 'visible'});
await this.get().locator(`.animate-spin`).waitFor({state: 'detached'});
await this.get().locator(`.animate-spin`).waitFor({ state: 'detached' });
}
async sync(){
async sync() {
await this.get().locator(`button:has-text("Sync Now")`).click();
await this.verifyToast({message: 'Table metadata recreated successfully'});
await this.get().locator(`.animate-spin`).waitFor({state: 'visible'});
await this.get().locator(`.animate-spin`).waitFor({state: 'detached'});
await this.verifyToast({ message: 'Table metadata recreated successfully' });
await this.get().locator(`.animate-spin`).waitFor({ state: 'visible' });
await this.get().locator(`.animate-spin`).waitFor({ state: 'detached' });
}
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);
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);
}
}
}

4
scripts/playwright/pages/Dashboard/Settings/Miscellaneous.ts

@ -1,4 +1,4 @@
import { expect } from '@playwright/test';
import { expect } from '@playwright/test';
import { SettingsPage } from '.';
import BasePage from '../../Base';
@ -17,4 +17,4 @@ export class MiscSettingsPage extends BasePage {
async clickShowM2MTables() {
await this.get().locator('input[type="checkbox"]').click();
}
}
}

76
scripts/playwright/pages/Dashboard/Settings/Teams.ts

@ -1,8 +1,8 @@
import { Locator, expect } from "@playwright/test";
import { SettingsPage } from ".";
import BasePage from "../../Base";
import { writeFileAsync } from "xlsx";
import { ToolbarPage } from "../common/Toolbar";
import { expect, Locator } from '@playwright/test';
import { SettingsPage } from '.';
import BasePage from '../../Base';
import { writeFileAsync } from 'xlsx';
import { ToolbarPage } from '../common/Toolbar';
export class TeamsPage extends BasePage {
private readonly settings: SettingsPage;
@ -13,19 +13,15 @@ export class TeamsPage extends BasePage {
super(settings.rootPage);
this.settings = settings;
this.inviteTeamBtn = this.get().locator(`button:has-text("Invite Team")`);
this.inviteTeamModal = this.rootPage.locator(
`.nc-modal-invite-user-and-share-base`
);
this.inviteTeamModal = this.rootPage.locator(`.nc-modal-invite-user-and-share-base`);
}
get() {
return this.settings
.get()
.locator(`[pw-data="nc-settings-subtab-Users Management"]`);
return this.settings.get().locator(`[pw-data="nc-settings-subtab-Users Management"]`);
}
prefixEmail(email: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0'
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0';
return `nc_test_${parallelId}_${email}`;
}
@ -35,78 +31,60 @@ export class TeamsPage extends BasePage {
async invite({ email, role }: { email: string; role: string }) {
email = this.prefixEmail(email);
await this.inviteTeamBtn.click();
await this.inviteTeamModal
.locator(`input[placeholder="E-mail"]`)
.fill(email);
await this.inviteTeamModal.locator(`input[placeholder="E-mail"]`).fill(email);
await this.inviteTeamModal.locator(`.nc-user-roles`).click();
const userRoleModal = this.rootPage.locator(`.nc-dropdown-user-role`);
await userRoleModal.locator(`.nc-role-option:has-text("${role}")`).click();
await this.inviteTeamModal.locator(`button:has-text("Invite")`).click();
await this.verifyToast({ message: "Successfully updated the user details" });
await this.verifyToast({ message: 'Successfully updated the user details' });
return await this.inviteTeamModal.locator(`.ant-alert-message`).innerText();
}
async closeInvite() {
// two btn-icon-only in invite modal: close & copy url
await this.inviteTeamModal
.locator(`button.ant-btn-icon-only:visible`)
.first()
.click();
await this.inviteTeamModal.locator(`button.ant-btn-icon-only:visible`).first().click();
}
async inviteMore() {
await this.inviteTeamModal
.locator(`button:has-text("Invite More")`)
.click();
await this.inviteTeamModal.locator(`button:has-text("Invite More")`).click();
}
async toggleSharedBase({ toggle }: { toggle: boolean }) {
const toggleBtn = await this.getSharedBaseSubModal().locator(
`.nc-disable-shared-base`
);
const toggleBtn = await this.getSharedBaseSubModal().locator(`.nc-disable-shared-base`);
const toggleBtnText = await toggleBtn.first().innerText();
const disabledBase = toggleBtnText.includes("Disable");
const disabledBase = toggleBtnText.includes('Disable');
if (disabledBase) {
if (toggle) {
// if share base was disabled && request was to enable
await toggleBtn.click();
const modal = await this.rootPage.locator(
`.nc-dropdown-shared-base-toggle`
);
const modal = await this.rootPage.locator(`.nc-dropdown-shared-base-toggle`);
await modal.locator(`.ant-dropdown-menu-title-content`).click();
}
} else {
if (!toggle) {
// if share base was enabled && request was to disable
await toggleBtn.click();
const modal = await this.rootPage.locator(
`.nc-dropdown-shared-base-toggle`
);
const modal = await this.rootPage.locator(`.nc-dropdown-shared-base-toggle`);
await modal.locator(`.ant-dropdown-menu-title-content`).click();
}
}
}
async getSharedBaseUrl() {
const url = await this.getSharedBaseSubModal()
.locator(`.nc-url:visible`)
.innerText();
const url = await this.getSharedBaseSubModal().locator(`.nc-url:visible`).innerText();
return url;
}
async sharedBaseActions({ action }: { action: string }) {
let actionMenu = ["reload", "copy url", "open tab", "copy embed code"];
let index = actionMenu.indexOf(action);
const actionMenu = ['reload', 'copy url', 'open tab', 'copy embed code'];
const index = actionMenu.indexOf(action);
await this.getSharedBaseSubModal()
.locator(`button.ant-btn-icon-only`)
.nth(index)
.click();
await this.getSharedBaseSubModal().locator(`button.ant-btn-icon-only`).nth(index).click();
}
async sharedBaseRole({ role }: { role: string }) {
@ -114,14 +92,8 @@ export class TeamsPage extends BasePage {
// await this.getSharedBaseSubModal()
// .locator(`.nc-shared-base-role`)
// .waitFor();
await this.getSharedBaseSubModal()
.locator(`.nc-shared-base-role:visible`)
.click();
const userRoleModal = await this.rootPage.locator(
`.nc-dropdown-share-base-role:visible`
);
await userRoleModal
.locator(`.ant-select-item-option-content:has-text("${role}"):visible`)
.click();
await this.getSharedBaseSubModal().locator(`.nc-shared-base-role:visible`).click();
const userRoleModal = await this.rootPage.locator(`.nc-dropdown-share-base-role:visible`);
await userRoleModal.locator(`.ant-select-item-option-content:has-text("${role}"):visible`).click();
}
}

47
scripts/playwright/pages/Dashboard/Settings/index.ts

@ -1,24 +1,24 @@
import { DashboardPage } from "..";
import BasePage from "../../Base";
import { AuditSettingsPage } from "./Audit";
import { SettingsErdPage } from "./Erd";
import { MetaDataPage } from "./Metadata";
import { AppStoreSettingsPage } from "./AppStore";
import { MiscSettingsPage } from "./Miscellaneous";
import { TeamsPage } from "./Teams";
import { AclPage } from "./Acl";
import { DashboardPage } from '..';
import BasePage from '../../Base';
import { AuditSettingsPage } from './Audit';
import { SettingsErdPage } from './Erd';
import { MetaDataPage } from './Metadata';
import { AppStoreSettingsPage } from './AppStore';
import { MiscSettingsPage } from './Miscellaneous';
import { TeamsPage } from './Teams';
import { AclPage } from './Acl';
export enum SettingTab {
TeamAuth = "teamAndAuth",
AppStore = "appStore",
ProjectMetadata = "projMetaData",
Audit = "audit",
TeamAuth = 'teamAndAuth',
AppStore = 'appStore',
ProjectMetadata = 'projMetaData',
Audit = 'audit',
}
export enum SettingsSubTab {
ERD = "erd",
Miscellaneous = "misc",
ACL = "acl",
ERD = 'erd',
Miscellaneous = 'misc',
ACL = 'acl',
}
export class SettingsPage extends BasePage {
@ -44,19 +44,12 @@ export class SettingsPage extends BasePage {
}
get() {
return this.rootPage.locator(".nc-modal-settings");
return this.rootPage.locator('.nc-modal-settings');
}
async selectTab({
tab,
subTab,
}: {
tab: SettingTab;
subTab?: SettingsSubTab;
}) {
async selectTab({ tab, subTab }: { tab: SettingTab; subTab?: SettingsSubTab }) {
await this.get().locator(`li[data-menu-id="${tab}"]`).click();
if (subTab)
await this.get().locator(`li[data-menu-id="${subTab}"]`).click();
if (subTab) await this.get().locator(`li[data-menu-id="${subTab}"]`).click();
}
async selectSubTab({ subTab }: { subTab: SettingsSubTab }) {
@ -65,6 +58,6 @@ export class SettingsPage extends BasePage {
async close() {
await this.get().locator('[pw-data="settings-modal-close-button"]').click();
await this.get().waitFor({ state: "hidden" });
await this.get().waitFor({ state: 'hidden' });
}
}

77
scripts/playwright/pages/Dashboard/SurveyForm/index.ts

@ -1,6 +1,6 @@
// playwright-dev-page.ts
import { Page, expect, Locator } from "@playwright/test";
import BasePage from "../../Base";
import { expect, Locator, Page } from '@playwright/test';
import BasePage from '../../Base';
export class SurveyFormPage extends BasePage {
readonly formHeading: Locator;
@ -14,30 +14,18 @@ export class SurveyFormPage extends BasePage {
constructor(rootPage: Page) {
super(rootPage);
this.formHeading = this.get().locator(
'[data-pw="nc-survey-form__heading"]'
);
this.formSubHeading = this.get().locator(
'[data-pw="nc-survey-form__sub-heading"]'
);
this.submitButton = this.get().locator(
'[data-pw="nc-survey-form__btn-submit"]'
);
this.nextButton = this.get().locator(
'[data-pw="nc-survey-form__btn-next"]'
);
this.nextSlideButton = this.get().locator(
'[data-pw="nc-survey-form__icon-next"]'
);
this.prevSlideButton = this.get().locator(
'[data-pw="nc-survey-form__icon-prev"]'
);
this.formHeading = this.get().locator('[data-pw="nc-survey-form__heading"]');
this.formSubHeading = this.get().locator('[data-pw="nc-survey-form__sub-heading"]');
this.submitButton = this.get().locator('[data-pw="nc-survey-form__btn-submit"]');
this.nextButton = this.get().locator('[data-pw="nc-survey-form__btn-next"]');
this.nextSlideButton = this.get().locator('[data-pw="nc-survey-form__icon-next"]');
this.prevSlideButton = this.get().locator('[data-pw="nc-survey-form__icon-prev"]');
this.darkModeButton = this.get().locator('[data-pw="nc-form-dark-mode"]');
this.formFooter = this.get().locator('[data-pw="nc-survey-form__footer"]');
}
get() {
return this.rootPage.locator("html >> .nc-form-view");
return this.rootPage.locator('html >> .nc-form-view');
}
async validate({
@ -55,15 +43,13 @@ export class SurveyFormPage extends BasePage {
await expect(this.formHeading).toHaveText(heading);
await expect(this.formSubHeading).toHaveText(subHeading);
await expect(this.formFooter).toHaveText(footer);
await expect(
this.get().locator(`[data-pw="nc-form-column-label"]`)
).toHaveText(fieldLabel);
await expect(this.get().locator(`[data-pw="nc-form-column-label"]`)).toHaveText(fieldLabel);
// parse footer text ("1 / 3") to identify if last slide
let isLastSlide = false;
const footerText = await this.formFooter.innerText();
const slideNumber = footerText.split(" / ")[0];
const totalSlides = footerText.split(" / ")[1];
const slideNumber = footerText.split(' / ')[0];
const totalSlides = footerText.split(' / ')[1];
if (slideNumber === totalSlides) {
isLastSlide = true;
}
@ -75,44 +61,27 @@ export class SurveyFormPage extends BasePage {
}
async fill(param: { fieldLabel: string; type?: string; value?: string }) {
await this.get()
.locator(`[data-pw="nc-survey-form__input-${param.fieldLabel}"]`)
.click();
if (param.type === "SingleLineText") {
await this.get()
.locator(
`[data-pw="nc-survey-form__input-${param.fieldLabel}"] >> input`
)
.fill(param.value);
await this.get().locator(`[data-pw="nc-survey-form__input-${param.fieldLabel}"]`).click();
if (param.type === 'SingleLineText') {
await this.get().locator(`[data-pw="nc-survey-form__input-${param.fieldLabel}"] >> input`).fill(param.value);
// press enter key
await this.get()
.locator(
`[data-pw="nc-survey-form__input-${param.fieldLabel}"] >> input`
)
.press("Enter");
} else if (param.type === "DateTime") {
const modal = await this.rootPage.locator(".nc-picker-datetime");
await this.get().locator(`[data-pw="nc-survey-form__input-${param.fieldLabel}"] >> input`).press('Enter');
} else if (param.type === 'DateTime') {
const modal = await this.rootPage.locator('.nc-picker-datetime');
await expect(modal).toBeVisible();
await modal.locator(".ant-picker-now-btn").click();
await modal.locator(".ant-picker-ok").click();
await modal.locator('.ant-picker-now-btn').click();
await modal.locator('.ant-picker-ok').click();
await this.nextButton.click();
}
}
async validateSuccessMessage(param: {
message: string;
showAnotherForm?: boolean;
}) {
async validateSuccessMessage(param: { message: string; showAnotherForm?: boolean }) {
await expect(
this.get().locator(
`[data-pw="nc-survey-form__success-msg"]:has-text("${param.message}")`
)
this.get().locator(`[data-pw="nc-survey-form__success-msg"]:has-text("${param.message}")`)
).toBeVisible();
if (param.showAnotherForm) {
await expect(
this.get().locator(`button:has-text("Submit Another Form")`)
).toBeVisible();
await expect(this.get().locator(`button:has-text("Submit Another Form")`)).toBeVisible();
}
}
}

145
scripts/playwright/pages/Dashboard/TreeView.ts

@ -1,6 +1,6 @@
import { Locator, expect } from "@playwright/test";
import { DashboardPage } from ".";
import BasePage from "../Base";
import { expect, Locator } from '@playwright/test';
import { DashboardPage } from '.';
import BasePage from '../Base';
export class TreeViewPage extends BasePage {
readonly dashboard: DashboardPage;
@ -12,12 +12,12 @@ export class TreeViewPage extends BasePage {
super(dashboard.rootPage);
this.dashboard = dashboard;
this.project = project;
this.quickImportButton = dashboard.get().locator(".nc-import-menu");
this.inviteTeamButton = dashboard.get().locator(".nc-share-base");
this.quickImportButton = dashboard.get().locator('.nc-import-menu');
this.inviteTeamButton = dashboard.get().locator('.nc-share-base');
}
get() {
return this.dashboard.get().locator(".nc-treeview-container");
return this.dashboard.get().locator('.nc-treeview-container');
}
async focusTable({ title }: { title: string }) {
@ -26,19 +26,9 @@ export class TreeViewPage extends BasePage {
// assumption: first view rendered is always GRID
//
async openTable({
title,
mode = "standard",
}: {
title: string;
mode?: string;
}) {
if ((await this.get().locator(".active.nc-project-tree-tbl").count()) > 0) {
if (
(await this.get()
.locator(".active.nc-project-tree-tbl")
.innerText()) === title
) {
async openTable({ title, mode = 'standard' }: { title: string; mode?: string }) {
if ((await this.get().locator('.active.nc-project-tree-tbl').count()) > 0) {
if ((await this.get().locator('.active.nc-project-tree-tbl').innerText()) === title) {
// table already open
return;
}
@ -46,59 +36,36 @@ export class TreeViewPage extends BasePage {
await this.waitForResponse({
uiAction: this.get().locator(`.nc-project-tree-tbl-${title}`).click(),
httpMethodsToMatch: ["GET"],
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: `/api/v1/db/data/noco/`,
responseJsonMatcher: (json) => json.pageInfo,
responseJsonMatcher: json => json.pageInfo,
});
await this.dashboard.waitForTabRender({ title, mode });
}
async createTable({ title }: { title: string }) {
await this.get().locator(".nc-add-new-table").click();
await this.get().locator('.nc-add-new-table').click();
await this.dashboard
.get()
.locator(".nc-modal-table-create")
.locator(".ant-modal-body")
.waitFor();
await this.dashboard.get().locator('.nc-modal-table-create').locator('.ant-modal-body').waitFor();
await this.dashboard
.get()
.locator('[placeholder="Enter table name"]')
.fill(title);
await this.dashboard.get().locator('[placeholder="Enter table name"]').fill(title);
await this.waitForResponse({
uiAction: this.dashboard
.get()
.locator('button:has-text("Submit")')
.click(),
httpMethodsToMatch: ["POST"],
uiAction: this.dashboard.get().locator('button:has-text("Submit")').click(),
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: `/api/v1/db/meta/projects/`,
responseJsonMatcher: (json) =>
json.title === title && json.type === "table",
responseJsonMatcher: json => json.title === title && json.type === 'table',
});
await this.dashboard.waitForTabRender({ title });
}
async verifyTable({
title,
index,
exists = true,
}: {
title: string;
index?: number;
exists?: boolean;
}) {
async verifyTable({ title, index, exists = true }: { title: string; index?: number; exists?: boolean }) {
if (exists) {
await expect(
this.get().locator(`.nc-project-tree-tbl-${title}`)
).toBeVisible();
await expect(this.get().locator(`.nc-project-tree-tbl-${title}`)).toBeVisible();
if (index) {
await expect(await this.get().locator(".nc-tbl-title").nth(index)).toHaveText(
title
);
await expect(await this.get().locator('.nc-tbl-title').nth(index)).toHaveText(title);
}
} else {
await expect(this.get().locator(`.nc-project-tree-tbl-${title}`)).toHaveCount(0);
@ -106,17 +73,12 @@ export class TreeViewPage extends BasePage {
}
async deleteTable({ title }: { title: string }) {
await this.get()
.locator(`.nc-project-tree-tbl-${title}`)
.click({ button: "right" });
await this.dashboard
.get()
.locator('div.nc-project-menu-item:has-text("Delete")')
.click();
await this.get().locator(`.nc-project-tree-tbl-${title}`).click({ button: 'right' });
await this.dashboard.get().locator('div.nc-project-menu-item:has-text("Delete")').click();
await this.waitForResponse({
uiAction: this.dashboard.get().locator('button:has-text("Yes")').click(),
httpMethodsToMatch: ["DELETE"],
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
});
@ -124,76 +86,51 @@ export class TreeViewPage extends BasePage {
.poll(
async () =>
await this.dashboard.tabBar
.locator(".ant-tabs-tab", {
.locator('.ant-tabs-tab', {
hasText: title,
})
.isVisible()
)
.toBe(false);
(
await this.rootPage.locator(".nc-container").last().elementHandle()
)?.waitForElementState("stable");
(await this.rootPage.locator('.nc-container').last().elementHandle())?.waitForElementState('stable');
}
async renameTable({ title, newTitle }: { title: string; newTitle: string }) {
await this.get()
.locator(`.nc-project-tree-tbl-${title}`)
.click({ button: "right" });
await this.dashboard
.get()
.locator('div.nc-project-menu-item:has-text("Rename")')
.click();
await this.dashboard
.get()
.locator('[placeholder="Enter table name"]')
.fill(newTitle);
await this.get().locator(`.nc-project-tree-tbl-${title}`).click({ button: 'right' });
await this.dashboard.get().locator('div.nc-project-menu-item:has-text("Rename")').click();
await this.dashboard.get().locator('[placeholder="Enter table name"]').fill(newTitle);
await this.dashboard.get().locator('button:has-text("Submit")').click();
await this.verifyToast({ message: "Table renamed successfully" });
await this.verifyToast({ message: 'Table renamed successfully' });
}
async reorderTables({
sourceTable,
destinationTable,
}: {
sourceTable: string;
destinationTable: string;
}) {
async reorderTables({ sourceTable, destinationTable }: { sourceTable: string; destinationTable: string }) {
await this.dashboard
.get()
.locator(`[pw-data="tree-view-table-draggable-handle-${sourceTable}"]`)
.dragTo(
this.get().locator(`[pw-data="tree-view-table-${destinationTable}"]`)
);
.dragTo(this.get().locator(`[pw-data="tree-view-table-${destinationTable}"]`));
}
async quickImport({ title }: { title: string }) {
await this.get().locator(".nc-add-new-table").hover();
await this.get().locator('.nc-add-new-table').hover();
await this.quickImportButton.click();
const importMenu = this.dashboard.get().locator(".nc-dropdown-import-menu");
await importMenu
.locator(`.ant-dropdown-menu-title-content:has-text("${title}")`)
.click();
const importMenu = this.dashboard.get().locator('.nc-dropdown-import-menu');
await importMenu.locator(`.ant-dropdown-menu-title-content:has-text("${title}")`).click();
}
async validateRoleAccess(param: { role: string }) {
// Add new table button
await expect(this.get().locator(`.nc-add-new-table`)).toHaveCount(
param.role === "creator" ? 1 : 0
);
await expect(this.get().locator(`.nc-add-new-table`)).toHaveCount(param.role === 'creator' ? 1 : 0);
// Import menu
await expect(this.get().locator(`.nc-import-menu`)).toHaveCount(
param.role === "creator" ? 1 : 0
);
await expect(this.get().locator(`.nc-import-menu`)).toHaveCount(param.role === 'creator' ? 1 : 0);
// Invite Team button
await expect(this.get().locator(`.nc-share-base`)).toHaveCount(
param.role === "creator" ? 1 : 0
);
await expect(this.get().locator(`.nc-share-base`)).toHaveCount(param.role === 'creator' ? 1 : 0);
// Right click context menu
await this.get().locator(`.nc-project-tree-tbl-Country`).click({
button: "right",
button: 'right',
});
await expect(this.rootPage.locator(`.nc-dropdown-tree-view-context-menu:visible`)
).toHaveCount(param.role === "creator" ? 1 : 0);
await expect(this.rootPage.locator(`.nc-dropdown-tree-view-context-menu:visible`)).toHaveCount(
param.role === 'creator' ? 1 : 0
);
}
}

113
scripts/playwright/pages/Dashboard/ViewSidebar/index.ts

@ -1,6 +1,6 @@
import { expect, Locator } from "@playwright/test";
import { DashboardPage } from "../";
import BasePage from "../../Base";
import { expect, Locator } from '@playwright/test';
import { DashboardPage } from '../';
import BasePage from '../../Base';
export class ViewSidebarPage extends BasePage {
readonly project: any;
@ -13,42 +13,30 @@ export class ViewSidebarPage extends BasePage {
constructor(dashboard: DashboardPage) {
super(dashboard.rootPage);
this.dashboard = dashboard;
this.createGalleryButton = this.get().locator(
".nc-create-gallery-view:visible"
);
this.createGridButton = this.get().locator(".nc-create-grid-view:visible");
this.createFormButton = this.get().locator(".nc-create-form-view:visible");
this.createKanbanButton = this.get().locator(
".nc-create-kanban-view:visible"
);
this.createGalleryButton = this.get().locator('.nc-create-gallery-view:visible');
this.createGridButton = this.get().locator('.nc-create-grid-view:visible');
this.createFormButton = this.get().locator('.nc-create-form-view:visible');
this.createKanbanButton = this.get().locator('.nc-create-kanban-view:visible');
}
get() {
return this.dashboard.get().locator(".nc-view-sidebar");
return this.dashboard.get().locator('.nc-view-sidebar');
}
private async createView({
title,
locator,
}: {
title: string;
locator: Locator;
}) {
private async createView({ title, locator }: { title: string; locator: Locator }) {
await locator.click();
await this.rootPage
.locator('input[id="form_item_title"]:visible')
.fill(title);
await this.rootPage.locator('input[id="form_item_title"]:visible').fill(title);
const submitAction = this.rootPage
.locator(".ant-modal-content")
.locator('.ant-modal-content')
.locator('button:has-text("Submit"):visible')
.click();
await this.waitForResponse({
httpMethodsToMatch: ["POST"],
requestUrlPathToMatch: "/api/v1/db/meta/tables/",
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/api/v1/db/meta/tables/',
uiAction: submitAction,
responseJsonMatcher: (json) => json.title === title,
responseJsonMatcher: json => json.title === title,
});
await this.verifyToast({ message: "View created successfully" });
await this.verifyToast({ message: 'View created successfully' });
// Todo: Wait for view to be rendered
await this.rootPage.waitForTimeout(1000);
}
@ -77,96 +65,65 @@ export class ViewSidebarPage extends BasePage {
async verifyView({ title, index }: { title: string; index: number }) {
await expect(
this.get().locator('[nc-data="view-item"]').nth(index).locator('[nc-data="truncate-label"]')
).toHaveText(title, { ignoreCase: true});
).toHaveText(title, { ignoreCase: true });
}
async verifyViewNotPresent({
title,
index,
}: {
title: string;
index: number;
}) {
const viewList = this.get()
.locator(`.nc-views-menu`)
.locator(".ant-menu-title-content");
async verifyViewNotPresent({ title, index }: { title: string; index: number }) {
const viewList = this.get().locator(`.nc-views-menu`).locator('.ant-menu-title-content');
if ((await viewList.count()) <= index) {
return true;
}
return await expect(
this.get().locator(`.nc-views-menu`).locator(".ant-menu-title-content").nth(index)
)
.not.toHaveText(title);
this.get().locator(`.nc-views-menu`).locator('.ant-menu-title-content').nth(index)
).not.toHaveText(title);
}
async reorderViews({
sourceView,
destinationView,
}: {
sourceView: string;
destinationView: string;
}) {
async reorderViews({ sourceView, destinationView }: { sourceView: string; destinationView: string }) {
await this.dashboard
.get()
.locator(`[pw-data="view-sidebar-drag-handle-${sourceView}"]`)
.dragTo(
this.get().locator(`[pw-data="view-sidebar-view-${destinationView}"]`)
);
.dragTo(this.get().locator(`[pw-data="view-sidebar-view-${destinationView}"]`));
}
async deleteView({ title }: { title: string }) {
await this.get().locator(`[pw-data="view-sidebar-view-${title}"]`).hover();
await this.get()
.locator(`[pw-data="view-sidebar-view-actions-${title}"]`)
.locator(".nc-view-delete-icon")
.click();
await this.get().locator(`[pw-data="view-sidebar-view-actions-${title}"]`).locator('.nc-view-delete-icon').click();
await this.rootPage
.locator(".nc-modal-view-delete")
.locator('button:has-text("Submit"):visible')
.click();
await this.rootPage.locator('.nc-modal-view-delete').locator('button:has-text("Submit"):visible').click();
// waiting for button to get detached, we will miss toast
// await this.rootPage
// .locator(".nc-modal-view-delete")
// .locator('button:has-text("Submit")')
// .waitFor({ state: "detached" });
await this.verifyToast({ message: "View deleted successfully" });
await this.verifyToast({ message: 'View deleted successfully' });
}
async renameView({ title, newTitle }: { title: string; newTitle: string }) {
await this.get()
.locator(`[pw-data="view-sidebar-view-${title}"]`)
.dblclick();
await this.get()
.locator(`[pw-data="view-sidebar-view-${title}"]`)
.locator("input")
.fill(newTitle);
await this.get().press("Enter");
await this.verifyToast({ message: "View renamed successfully" });
await this.get().locator(`[pw-data="view-sidebar-view-${title}"]`).dblclick();
await this.get().locator(`[pw-data="view-sidebar-view-${title}"]`).locator('input').fill(newTitle);
await this.get().press('Enter');
await this.verifyToast({ message: 'View renamed successfully' });
}
async copyView({ title }: { title: string }) {
await this.get().locator(`[pw-data="view-sidebar-view-${title}"]`).hover();
await this.get()
.locator(`[pw-data="view-sidebar-view-actions-${title}"]`)
.locator(".nc-view-copy-icon")
.click();
await this.get().locator(`[pw-data="view-sidebar-view-actions-${title}"]`).locator('.nc-view-copy-icon').click();
const submitAction = this.rootPage
.locator(".ant-modal-content")
.locator('.ant-modal-content')
.locator('button:has-text("Submit"):visible')
.click();
await this.waitForResponse({
httpMethodsToMatch: ["POST"],
requestUrlPathToMatch: "/api/v1/db/meta/tables/",
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/api/v1/db/meta/tables/',
uiAction: submitAction,
});
await this.verifyToast({ message: "View created successfully" });
await this.verifyToast({ message: 'View created successfully' });
}
async validateRoleAccess(param: { role: string }) {
let count = param.role === "creator" ? 1 : 0;
const count = param.role === 'creator' ? 1 : 0;
await expect(this.createGridButton).toHaveCount(count);
await expect(this.createGalleryButton).toHaveCount(count);
await expect(this.createFormButton).toHaveCount(count);

129
scripts/playwright/pages/Dashboard/WebhookForm/index.ts

@ -1,8 +1,8 @@
// playwright-dev-page.ts
import { expect, Locator } from "@playwright/test";
import BasePage from "../../Base";
import { DashboardPage } from "..";
import { ToolbarPage } from "../common/Toolbar";
import { expect, Locator } from '@playwright/test';
import BasePage from '../../Base';
import { DashboardPage } from '..';
import { ToolbarPage } from '../common/Toolbar';
// import clipboard from "clipboardy";
export class WebhookFormPage extends BasePage {
@ -16,7 +16,7 @@ export class WebhookFormPage extends BasePage {
super(dashboard.rootPage);
this.dashboard = dashboard;
this.toolbar = dashboard.grid.toolbar;
this.addNewButton = this.dashboard.get().locator(".nc-btn-create-webhook");
this.addNewButton = this.dashboard.get().locator('.nc-btn-create-webhook');
this.saveButton = this.get().locator('button:has-text("Save")');
this.testButton = this.get().locator('button:has-text("Test Webhook")');
}
@ -26,39 +26,23 @@ export class WebhookFormPage extends BasePage {
}
// todo: Removing opening webhook drawer logic as it belongs to `Toolbar` page
async create({
title,
event,
url = "http://localhost:9090/hook",
}: {
title: string;
event: string;
url?: string;
}) {
async create({ title, event, url = 'http://localhost:9090/hook' }: { title: string; event: string; url?: string }) {
await this.toolbar.clickActions();
await this.toolbar.actions.click("Webhooks");
await this.toolbar.actions.click('Webhooks');
await this.addNewButton.click();
await this.get().waitFor({ state: "visible" });
await this.get().waitFor({ state: 'visible' });
await this.configureHeader({
key: "Content-Type",
value: "application/json",
key: 'Content-Type',
value: 'application/json',
});
await this.configureWebhook({ title, event, url });
await this.save();
await this.close();
}
async configureWebhook({
title,
event,
url,
}: {
title?: string;
event?: string;
url?: string;
}) {
async configureWebhook({ title, event, url }: { title?: string; event?: string; url?: string }) {
if (title) {
await this.get().locator(`.nc-text-field-hook-title`).fill(title);
}
@ -93,24 +77,20 @@ export class WebhookFormPage extends BasePage {
await this.rootPage.waitForTimeout(1500);
await modal.locator(".nc-filter-field-select").click();
const modalField = await this.dashboard.rootPage.locator(
".nc-dropdown-toolbar-field-list:visible"
);
await modal.locator('.nc-filter-field-select').click();
const modalField = await this.dashboard.rootPage.locator('.nc-dropdown-toolbar-field-list:visible');
await modalField.locator(`.ant-select-item:has-text("${column}")`).click();
await this.rootPage.waitForTimeout(1500);
await modal.locator(".nc-filter-operation-select").click();
const modalOp = await this.dashboard.rootPage.locator(
".nc-dropdown-filter-comp-op:visible"
);
await modal.locator('.nc-filter-operation-select').click();
const modalOp = await this.dashboard.rootPage.locator('.nc-dropdown-filter-comp-op:visible');
await modalOp.locator(`.ant-select-item:has-text("${operator}")`).click();
await this.rootPage.waitForTimeout(1500);
if (operator != "is null" && operator != "is not null") {
await modal.locator("input.nc-filter-value-select").fill(value);
if (operator != 'is null' && operator != 'is not null') {
await modal.locator('input.nc-filter-value-select').fill(value);
}
if (save) {
@ -131,36 +111,36 @@ export class WebhookFormPage extends BasePage {
const saveAction = this.saveButton.click();
await this.waitForResponse({
uiAction: saveAction,
requestUrlPathToMatch: "/hooks",
httpMethodsToMatch: ["POST", "PATCH"],
})
await this.verifyToast({ message: 'Webhook details updated successfully'});
requestUrlPathToMatch: '/hooks',
httpMethodsToMatch: ['POST', 'PATCH'],
});
await this.verifyToast({ message: 'Webhook details updated successfully' });
}
async test() {
await this.testButton.click();
await this.verifyToast({ message: "Webhook tested successfully" });
await this.verifyToast({ message: 'Webhook tested successfully' });
}
async delete({ index }: { index: number }) {
await this.toolbar.clickActions();
await this.toolbar.actions.click("Webhooks");
await this.toolbar.actions.click('Webhooks');
await this.get().locator(`.nc-hook-delete-icon`).nth(index).click();
await this.verifyToast({ message: "Hook deleted successfully" });
await this.verifyToast({ message: 'Hook deleted successfully' });
// click escape to close the drawer
await this.get().press("Escape");
await this.get().press('Escape');
}
async close() {
// type esc key
await this.get().press("Escape");
await this.get().press('Escape');
}
async open({ index }: { index: number }) {
await this.toolbar.clickActions();
await this.toolbar.actions.click("Webhooks");
await this.toolbar.actions.click('Webhooks');
await this.dashboard.get().locator(`.nc-hook`).nth(index).click();
}
@ -176,16 +156,13 @@ export class WebhookFormPage extends BasePage {
// hardcode "Content-type: application/json"
await this.get().locator(`.ant-tabs-tab-btn:has-text("Headers")`).click();
await this.get().locator(".nc-input-hook-header-key >> input").fill(key);
await this.get().locator('.nc-input-hook-header-key >> input').fill(key);
await this.rootPage.locator(`.ant-select-item:has-text("${key}")`).click();
await this.get().locator(".nc-input-hook-header-value").type(value);
await this.get().press("Enter");
await this.get().locator('.nc-input-hook-header-value').type(value);
await this.get().press('Enter');
await this.get()
.locator(".nc-hook-header-tab-checkbox")
.locator("input.ant-checkbox-input")
.click();
await this.get().locator('.nc-hook-header-tab-checkbox').locator('input.ant-checkbox-input').click();
}
async verifyForm({
@ -203,41 +180,15 @@ export class WebhookFormPage extends BasePage {
urlMethod: string;
condition: boolean;
}) {
await expect
.poll(
async () =>
await this.get()
.locator("input.nc-text-field-hook-title")
.inputValue()
)
.toBe(title);
await expect(
this.get().locator(
".nc-text-field-hook-event >> .ant-select-selection-item"
)
).toHaveText(hookEvent);
await expect(
this.get().locator(
".nc-select-hook-notification-type >> .ant-select-selection-item"
)
).toHaveText(notificationType);
await expect(
this.get().locator(
".nc-select-hook-url-method >> .ant-select-selection-item"
)
).toHaveText(urlMethod);
await expect
.poll(
async () =>
await this.get()
.locator("input.nc-text-field-hook-url-path")
.inputValue()
)
.toBe(url);
const conditionCheckbox = this.get().locator(
'label.nc-check-box-hook-condition >> input[type="checkbox"]'
await expect.poll(async () => await this.get().locator('input.nc-text-field-hook-title').inputValue()).toBe(title);
await expect(this.get().locator('.nc-text-field-hook-event >> .ant-select-selection-item')).toHaveText(hookEvent);
await expect(this.get().locator('.nc-select-hook-notification-type >> .ant-select-selection-item')).toHaveText(
notificationType
);
await expect(this.get().locator('.nc-select-hook-url-method >> .ant-select-selection-item')).toHaveText(urlMethod);
await expect.poll(async () => await this.get().locator('input.nc-text-field-hook-url-path').inputValue()).toBe(url);
const conditionCheckbox = this.get().locator('label.nc-check-box-hook-condition >> input[type="checkbox"]');
if (condition) {
await expect(conditionCheckbox).toBeChecked();
} else {
@ -246,6 +197,6 @@ export class WebhookFormPage extends BasePage {
}
async goBackFromForm() {
await this.get().locator("svg.nc-icon-hook-navigate-left").click();
await this.get().locator('svg.nc-icon-hook-navigate-left').click();
}
}

28
scripts/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import { CellPageObject } from ".";
import BasePage from "../../../Base";
import { expect } from '@playwright/test';
import { CellPageObject } from '.';
import BasePage from '../../../Base';
export class AttachmentCellPageObject extends BasePage {
readonly cell: CellPageObject;
@ -10,20 +10,22 @@ export class AttachmentCellPageObject extends BasePage {
this.cell = cell;
}
get({index, columnHeader}: {index?: number, columnHeader: string}) {
return this.cell.get({index, columnHeader});
get({ index, columnHeader }: { index?: number; columnHeader: string }) {
return this.cell.get({ index, columnHeader });
}
clickFilePicker({ index, columnHeader }: { index?: number, columnHeader: string }) {
return this.get({index, columnHeader}).locator('[pw-data="attachment-cell-file-picker-button"]').click();
clickFilePicker({ index, columnHeader }: { index?: number; columnHeader: string }) {
return this.get({ index, columnHeader }).locator('[pw-data="attachment-cell-file-picker-button"]').click();
}
async addFile({ index, columnHeader, filePath }: { index?: number, columnHeader: string, filePath: string; }) {
const attachFileAction = this.get({index, columnHeader}).locator('[pw-data="attachment-cell-file-picker-button"]').click();
return this.attachFile({ filePickUIAction: attachFileAction, filePath });
async addFile({ index, columnHeader, filePath }: { index?: number; columnHeader: string; filePath: string }) {
const attachFileAction = this.get({ index, columnHeader })
.locator('[pw-data="attachment-cell-file-picker-button"]')
.click();
return await this.attachFile({ filePickUIAction: attachFileAction, filePath });
}
async verifyFile({ index, columnHeader }: { index: number, columnHeader: string }) {
await expect(await this.get({index, columnHeader}).locator('.nc-attachment')).toBeVisible();
async verifyFile({ index, columnHeader }: { index: number; columnHeader: string }) {
await expect(await this.get({ index, columnHeader }).locator('.nc-attachment')).toBeVisible();
}
}
}

28
scripts/playwright/pages/Dashboard/common/Cell/CheckboxCell.ts

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import { CellPageObject } from ".";
import BasePage from "../../../Base";
import { expect } from '@playwright/test';
import { CellPageObject } from '.';
import BasePage from '../../../Base';
export class CheckboxCellPageObject extends BasePage {
readonly cell: CellPageObject;
@ -10,23 +10,23 @@ export class CheckboxCellPageObject extends BasePage {
this.cell = cell;
}
get({index, columnHeader}: {index?: number, columnHeader: string}) {
return this.cell.get({index, columnHeader});
get({ index, columnHeader }: { index?: number; columnHeader: string }) {
return this.cell.get({ index, columnHeader });
}
async click({ index, columnHeader }: { index?: number, columnHeader: string }) {
return this.get({index, columnHeader}).locator('.nc-cell').click();
async click({ index, columnHeader }: { index?: number; columnHeader: string }) {
return await this.get({ index, columnHeader }).locator('.nc-cell').click();
}
async isChecked({ index, columnHeader }: { index?: number, columnHeader: string }) {
return this.get({index, columnHeader}).locator('.nc-cell-hover-show').isVisible();
async isChecked({ index, columnHeader }: { index?: number; columnHeader: string }) {
return await this.get({ index, columnHeader }).locator('.nc-cell-hover-show').isVisible();
}
async verifyChecked({ index, columnHeader }: { index?: number, columnHeader: string }) {
await expect(this.get({index, columnHeader}).locator('.nc-cell-hover-show')).not.toBeVisible();
async verifyChecked({ index, columnHeader }: { index?: number; columnHeader: string }) {
await expect(this.get({ index, columnHeader }).locator('.nc-cell-hover-show')).not.toBeVisible();
}
async verifyUnchecked({ index, columnHeader }: { index?: number, columnHeader: string }) {
await expect(this.get({index, columnHeader}).locator('.nc-cell-hover-show')).toBeVisible();
async verifyUnchecked({ index, columnHeader }: { index?: number; columnHeader: string }) {
await expect(this.get({ index, columnHeader }).locator('.nc-cell-hover-show')).toBeVisible();
}
}
}

19
scripts/playwright/pages/Dashboard/common/Cell/RatingCell.ts

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import { CellPageObject } from ".";
import BasePage from "../../../Base";
import { expect } from '@playwright/test';
import { CellPageObject } from '.';
import BasePage from '../../../Base';
export class RatingCellPageObject extends BasePage {
readonly cell: CellPageObject;
@ -10,12 +10,13 @@ export class RatingCellPageObject extends BasePage {
this.cell = cell;
}
get({index, columnHeader}: {index?: number, columnHeader: string}) {
return this.cell.get({index, columnHeader});
get({ index, columnHeader }: { index?: number; columnHeader: string }) {
return this.cell.get({ index, columnHeader });
}
async verify({index, columnHeader, rating}: {index?: number, columnHeader: string, rating: number}) {
await expect(await this.get({index, columnHeader}).locator(`div[role="radio"][aria-checked="true"]`)).toHaveCount(rating);
async verify({ index, columnHeader, rating }: { index?: number; columnHeader: string; rating: number }) {
await expect(await this.get({ index, columnHeader }).locator(`div[role="radio"][aria-checked="true"]`)).toHaveCount(
rating
);
}
}
}

97
scripts/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import { CellPageObject } from ".";
import BasePage from "../../../Base";
import { expect } from '@playwright/test';
import { CellPageObject } from '.';
import BasePage from '../../../Base';
export class SelectOptionCellPageObject extends BasePage {
readonly cell: CellPageObject;
@ -10,62 +10,91 @@ export class SelectOptionCellPageObject extends BasePage {
this.cell = cell;
}
get({index, columnHeader}: {index: number, columnHeader: string}) {
return this.cell.get({index, columnHeader});
get({ index, columnHeader }: { index: number; columnHeader: string }) {
return this.cell.get({ index, columnHeader });
}
async select({index, columnHeader, option, multiSelect}: {index: number, columnHeader: string, option: string, multiSelect?: boolean}) {
await this.get({index, columnHeader}).click();
async select({
index,
columnHeader,
option,
multiSelect,
}: {
index: number;
columnHeader: string;
option: string;
multiSelect?: boolean;
}) {
await this.get({ index, columnHeader }).click();
await this.rootPage.locator(`[pw-data="select-option-${columnHeader}-${index}"]`, {hasText: option}).click();
await this.rootPage.locator(`[pw-data="select-option-${columnHeader}-${index}"]`, { hasText: option }).click();
if(multiSelect) await this.get({index, columnHeader}).click();
if (multiSelect) await this.get({ index, columnHeader }).click();
await this.rootPage.locator(`[pw-data="select-option-${columnHeader}-${index}"]`, {hasText: option}).waitFor({state: 'hidden'});
await this.rootPage
.locator(`[pw-data="select-option-${columnHeader}-${index}"]`, { hasText: option })
.waitFor({ state: 'hidden' });
}
async clear({index, columnHeader, multiSelect}: {index: number, columnHeader: string, multiSelect?: boolean}) {
if(multiSelect){
await this.cell.get({index, columnHeader}).click();
await this.cell.get({index, columnHeader}).click();
async clear({ index, columnHeader, multiSelect }: { index: number; columnHeader: string; multiSelect?: boolean }) {
if (multiSelect) {
await this.cell.get({ index, columnHeader }).click();
await this.cell.get({ index, columnHeader }).click();
const optionCount = await this.cell.get({index, columnHeader}).locator('.ant-tag').count();
const optionCount = await this.cell.get({ index, columnHeader }).locator('.ant-tag').count();
for(let i = 0; i < optionCount; i++) {
await this.cell.get({index, columnHeader}).locator('.ant-tag > .ant-tag-close-icon').first().click();
for (let i = 0; i < optionCount; i++) {
await this.cell.get({ index, columnHeader }).locator('.ant-tag > .ant-tag-close-icon').first().click();
// wait till number of options is less than before
await this.cell.get({index, columnHeader}).locator('.ant-tag').nth(optionCount - i - 1).waitFor({state: 'hidden'});
await this.cell
.get({ index, columnHeader })
.locator('.ant-tag')
.nth(optionCount - i - 1)
.waitFor({ state: 'hidden' });
}
return
return;
}
await this.get({index, columnHeader}).click();
await this.get({ index, columnHeader }).click();
await this.rootPage.locator('.ant-select-single > .ant-select-clear').click();
await this.cell.get({index, columnHeader}).click();
await this.rootPage.locator(`.nc-dropdown-single-select-cell`).waitFor({state: 'hidden'});
await this.cell.get({ index, columnHeader }).click();
await this.rootPage.locator(`.nc-dropdown-single-select-cell`).waitFor({ state: 'hidden' });
}
async verify({index, columnHeader, option, multiSelect}: {index: number, columnHeader: string, option: string, multiSelect?: boolean}) {
if(multiSelect) {
return await expect(
this.cell.get({index, columnHeader})).toContainText(option, {useInnerText: true});
async verify({
index,
columnHeader,
option,
multiSelect,
}: {
index: number;
columnHeader: string;
option: string;
multiSelect?: boolean;
}) {
if (multiSelect) {
return await expect(this.cell.get({ index, columnHeader })).toContainText(option, { useInnerText: true });
}
return await expect(this.cell.get({index, columnHeader}).locator('.ant-select-selection-item > .ant-tag')).toHaveText(option, {useInnerText: true});
return await expect(
this.cell.get({ index, columnHeader }).locator('.ant-select-selection-item > .ant-tag')
).toHaveText(option, { useInnerText: true });
}
async verifyNoOptionsSelected({index, columnHeader}: {index: number, columnHeader: string}) {
return await expect(this.cell.get({index, columnHeader}).locator('.ant-select-selection-item > .ant-tag')).toBeHidden();
async verifyNoOptionsSelected({ index, columnHeader }: { index: number; columnHeader: string }) {
return await expect(
this.cell.get({ index, columnHeader }).locator('.ant-select-selection-item > .ant-tag')
).toBeHidden();
}
async verifyOptions({index, columnHeader, options}: {index: number, columnHeader: string, options: string[]}) {
await this.get({index, columnHeader}).click();
async verifyOptions({ index, columnHeader, options }: { index: number; columnHeader: string; options: string[] }) {
await this.get({ index, columnHeader }).click();
let counter = 0;
for (const option of options) {
await expect(this.rootPage.locator(`div.ant-select-item-option`).nth(counter)).toHaveText(option);
counter++;
}
await this.get({index, columnHeader}).click();
await this.rootPage.locator(`.nc-dropdown-single-select-cell`).nth(index).waitFor({state: 'hidden'});
await this.get({ index, columnHeader }).click();
await this.rootPage.locator(`.nc-dropdown-single-select-cell`).nth(index).waitFor({ state: 'hidden' });
}
}
}

143
scripts/playwright/pages/Dashboard/common/Cell/index.ts

@ -1,11 +1,11 @@
import { expect, Locator } from "@playwright/test";
import { GridPage } from "../../Grid";
import BasePage from "../../../Base";
import { AttachmentCellPageObject } from "./AttachmentCell";
import { SelectOptionCellPageObject } from "./SelectOptionCell";
import { SharedFormPage } from "../../../SharedForm";
import { CheckboxCellPageObject } from "./CheckboxCell";
import { RatingCellPageObject } from "./RatingCell";
import { expect, Locator } from '@playwright/test';
import { GridPage } from '../../Grid';
import BasePage from '../../../Base';
import { AttachmentCellPageObject } from './AttachmentCell';
import { SelectOptionCellPageObject } from './SelectOptionCell';
import { SharedFormPage } from '../../../SharedForm';
import { CheckboxCellPageObject } from './CheckboxCell';
import { RatingCellPageObject } from './RatingCell';
export class CellPageObject extends BasePage {
readonly parent: GridPage | SharedFormPage;
@ -22,107 +22,53 @@ export class CellPageObject extends BasePage {
this.rating = new RatingCellPageObject(this);
}
get({
index,
columnHeader,
}: {
index?: number;
columnHeader: string;
}): Locator {
get({ index, columnHeader }: { index?: number; columnHeader: string }): Locator {
if (this.parent instanceof SharedFormPage) {
return this.parent
.get()
.locator(`[pw-data="nc-form-input-cell-${columnHeader}"]`);
return this.parent.get().locator(`[pw-data="nc-form-input-cell-${columnHeader}"]`);
} else {
return this.parent
.get()
.locator(`td[data-pw="cell-${columnHeader}-${index}"]`);
return this.parent.get().locator(`td[data-pw="cell-${columnHeader}-${index}"]`);
}
}
async click({
index,
columnHeader,
}: {
index: number;
columnHeader: string;
}) {
async click({ index, columnHeader }: { index: number; columnHeader: string }) {
return await this.get({ index, columnHeader }).click();
}
async dblclick({
index,
columnHeader,
}: {
index?: number;
columnHeader: string;
}) {
async dblclick({ index, columnHeader }: { index?: number; columnHeader: string }) {
return await this.get({ index, columnHeader }).dblclick();
}
async fillText({
index,
columnHeader,
text
}: {
index?: number;
columnHeader: string;
text: string;
}) {
async fillText({ index, columnHeader, text }: { index?: number; columnHeader: string; text: string }) {
await this.dblclick({
index,
columnHeader,
});
await this.get({ index, columnHeader }).locator("input").fill(text);
await this.get({ index, columnHeader }).locator('input').fill(text);
}
async inCellExpand({
index,
columnHeader,
}: {
index: number;
columnHeader: string;
}) {
async inCellExpand({ index, columnHeader }: { index: number; columnHeader: string }) {
await this.get({ index, columnHeader }).hover();
await this.waitForResponse({
uiAction: this.get({ index, columnHeader })
.locator(".nc-action-icon >> nth=0")
.click(),
uiAction: this.get({ index, columnHeader }).locator('.nc-action-icon >> nth=0').click(),
requestUrlPathToMatch: '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
})
});
}
async inCellAdd({
index,
columnHeader,
}: {
index: number;
columnHeader: string;
}) {
async inCellAdd({ index, columnHeader }: { index: number; columnHeader: string }) {
await this.get({ index, columnHeader }).hover();
await this.get({ index, columnHeader })
.locator(".nc-action-icon.nc-plus")
.click();
await this.get({ index, columnHeader }).locator('.nc-action-icon.nc-plus').click();
}
async verify({
index,
columnHeader,
value,
}: {
index: number;
columnHeader: string;
value: string | string[];
}) {
const _verify = async (text) => {
async verify({ index, columnHeader, value }: { index: number; columnHeader: string; value: string | string[] }) {
const _verify = async text => {
await expect
.poll(async () => {
const innerTexts = await this.get({
index,
columnHeader,
}).allInnerTexts();
return typeof innerTexts === "string" ? [innerTexts] : innerTexts;
return typeof innerTexts === 'string' ? [innerTexts] : innerTexts;
})
.toContain(text);
};
@ -154,10 +100,10 @@ export class CellPageObject extends BasePage {
}) {
// const count = value.length;
const cell = this.get({ index, columnHeader });
const chips = cell.locator(".chips > .chip");
const chips = cell.locator('.chips > .chip');
// verify chip count & contents
if(count) expect(chips).toHaveCount(count);
if (count) expect(chips).toHaveCount(count);
// verify only the elements that are passed in
for (let i = 0; i < value.length; ++i) {
@ -165,48 +111,37 @@ export class CellPageObject extends BasePage {
}
}
async unlinkVirtualCell({
index,
columnHeader,
}: {
index: number;
columnHeader: string;
}) {
async unlinkVirtualCell({ index, columnHeader }: { index: number; columnHeader: string }) {
const cell = this.get({ index, columnHeader });
await cell.click();
await cell.locator(".nc-icon.unlink-icon").click();
await cell.locator('.nc-icon.unlink-icon').click();
}
async verifyRoleAccess(param: { role: string }) {
// normal text cell
const cell = await this.get({ index: 0, columnHeader: "Country" });
const cell = await this.get({ index: 0, columnHeader: 'Country' });
// editable cell
await cell.dblclick();
await expect(await cell.locator(`input`)).toHaveCount(
param.role === "creator" || param.role === "editor" ? 1 : 0
);
await expect(await cell.locator(`input`)).toHaveCount(param.role === 'creator' || param.role === 'editor' ? 1 : 0);
// right click context menu
await cell.click({ button: "right" });
await expect(
await this.rootPage
.locator(`.nc-dropdown-grid-context-menu:visible`)
).toHaveCount(param.role === "creator" || param.role === "editor" ? 1 : 0);
await cell.click({ button: 'right' });
await expect(await this.rootPage.locator(`.nc-dropdown-grid-context-menu:visible`)).toHaveCount(
param.role === 'creator' || param.role === 'editor' ? 1 : 0
);
// virtual cell
const vCell = await this.get({ index: 0, columnHeader: "City List" });
const vCell = await this.get({ index: 0, columnHeader: 'City List' });
await vCell.hover();
// in-cell add
await expect(await vCell.locator(".nc-action-icon.nc-plus:visible")).toHaveCount(
param.role === "creator" || param.role === "editor" ? 1 : 0
await expect(await vCell.locator('.nc-action-icon.nc-plus:visible')).toHaveCount(
param.role === 'creator' || param.role === 'editor' ? 1 : 0
);
// in-cell expand (all have access)
await expect(
await vCell.locator(".nc-action-icon.nc-arrow-expand:visible")
).toHaveCount(1);
await expect(await vCell.locator('.nc-action-icon.nc-arrow-expand:visible')).toHaveCount(1);
await vCell.click();
// unlink
await expect(await vCell.locator(".nc-icon.unlink-icon:visible")).toHaveCount(
param.role === "creator" || param.role === "editor" ? 1 : 0
await expect(await vCell.locator('.nc-icon.unlink-icon:visible')).toHaveCount(
param.role === 'creator' || param.role === 'editor' ? 1 : 0
);
}
}

24
scripts/playwright/pages/Dashboard/common/ProjectMenu/index.ts

@ -1,8 +1,8 @@
import BasePage from "../../../Base";
import { GridPage } from "../../Grid";
import { GalleryPage } from "../../Gallery";
import { KanbanPage } from "../../Kanban";
import { FormPage } from "../../Form";
import BasePage from '../../../Base';
import { GridPage } from '../../Grid';
import { GalleryPage } from '../../Gallery';
import { KanbanPage } from '../../Kanban';
import { FormPage } from '../../Form';
export class ProjectMenuObject extends BasePage {
constructor(parent: GridPage | GalleryPage | KanbanPage | FormPage) {
@ -18,25 +18,19 @@ export class ProjectMenuObject extends BasePage {
}
async click({ menu, subMenu }: { menu: string; subMenu: string }) {
let pMenu = await this.rootPage.locator(
`.nc-dropdown-project-menu:visible`
);
await pMenu
.locator(`div.nc-project-menu-item:has-text("${menu}"):visible`)
.click();
const pMenu = await this.rootPage.locator(`.nc-dropdown-project-menu:visible`);
await pMenu.locator(`div.nc-project-menu-item:has-text("${menu}"):visible`).click();
await this.rootPage.waitForTimeout(2000);
if (subMenu) {
await this.rootPage
.locator(`div.nc-project-menu-item:has-text("${subMenu}"):visible`)
.click();
await this.rootPage.locator(`div.nc-project-menu-item:has-text("${subMenu}"):visible`).click();
await this.rootPage.waitForTimeout(1000);
}
}
async clickPreview(role: string) {
await this.click({
menu: "Preview as",
menu: 'Preview as',
subMenu: role,
});
await this.rootPage.waitForTimeout(2500);

4
scripts/playwright/pages/Dashboard/common/Toolbar/Actions/Erd.ts

@ -1,5 +1,5 @@
import { ToolbarActionsPage } from ".";
import { ErdBasePage } from "../../../commonBase/Erd";
import { ToolbarActionsPage } from '.';
import { ErdBasePage } from '../../../commonBase/Erd';
export class ToolbarActionsErdPage extends ErdBasePage {
readonly toolbarActions: ToolbarActionsPage;

6
scripts/playwright/pages/Dashboard/common/Toolbar/Actions/index.ts

@ -1,6 +1,6 @@
import BasePage from "../../../../Base";
import { ToolbarPage } from "..";
import { ToolbarActionsErdPage } from "./Erd";
import BasePage from '../../../../Base';
import { ToolbarPage } from '..';
import { ToolbarActionsErdPage } from './Erd';
export class ToolbarActionsPage extends BasePage {
readonly toolbar: ToolbarPage;

11
scripts/playwright/pages/Dashboard/common/Toolbar/AddEditKanbanStack.ts

@ -1,5 +1,5 @@
import BasePage from "../../../Base";
import { ToolbarPage } from "./index";
import BasePage from '../../../Base';
import { ToolbarPage } from './index';
export class ToolbarAddEditStackPage extends BasePage {
readonly toolbar: ToolbarPage;
@ -16,10 +16,7 @@ export class ToolbarAddEditStackPage extends BasePage {
async addOption({ title }: { title: string }) {
await this.get().locator(`.ant-btn-dashed`).click();
await this.get().locator(`.nc-select-option >> input`).last().fill(title);
await this.get()
.locator(`.nc-select-option >> input`)
.last()
.press("Enter");
await this.verifyToast({ message: "Column updated" });
await this.get().locator(`.nc-select-option >> input`).last().press('Enter');
await this.verifyToast({ message: 'Column updated' });
}
}

31
scripts/playwright/pages/Dashboard/common/Toolbar/Fields.ts

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import BasePage from "../../../Base";
import { ToolbarPage } from "./index";
import { expect } from '@playwright/test';
import BasePage from '../../../Base';
import { ToolbarPage } from './index';
export class ToolbarFieldsPage extends BasePage {
readonly toolbar: ToolbarPage;
@ -18,9 +18,9 @@ export class ToolbarFieldsPage extends BasePage {
async toggle({ title, isLocallySaved }: { title: string; isLocallySaved?: boolean }) {
await this.toolbar.clickFields();
const toggleColumn = this.get()
.locator(`[pw-data="nc-fields-menu-${title}"]`)
.locator('input[type="checkbox"]')
.click();
.locator(`[pw-data="nc-fields-menu-${title}"]`)
.locator('input[type="checkbox"]')
.click();
await this.waitForResponse({
uiAction: toggleColumn,
@ -31,10 +31,8 @@ export class ToolbarFieldsPage extends BasePage {
await this.toolbar.clickFields();
}
async verify({ title, checked }: { title: string, checked: boolean }) {
const checkbox = this.get()
.locator(`[pw-data="nc-fields-menu-${title}"]`)
.locator('input[type="checkbox"]');
async verify({ title, checked }: { title: string; checked: boolean }) {
const checkbox = this.get().locator(`[pw-data="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]');
if (checked) {
await expect(checkbox).toBeChecked();
@ -45,10 +43,7 @@ export class ToolbarFieldsPage extends BasePage {
async click({ title, isLocallySaved }: { title: string; isLocallySaved?: boolean }) {
await this.waitForResponse({
uiAction: this.get()
.locator(`[pw-data="nc-fields-menu-${title}"]`)
.locator('input[type="checkbox"]')
.click(),
uiAction: this.get().locator(`[pw-data="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]').click(),
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
@ -59,17 +54,17 @@ export class ToolbarFieldsPage extends BasePage {
await this.toolbar.clickFields();
await this.waitForResponse({
uiAction: this.get().locator(`button:has-text("Hide all")`).click(),
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
await this.toolbar.clickFields();
}
async showAll({ isLocallySaved }: { isLocallySaved? : boolean } = {}) {
async showAll({ isLocallySaved }: { isLocallySaved?: boolean } = {}) {
await this.toolbar.clickFields();
await this.waitForResponse({
uiAction: this.get().locator(`button:has-text("Show all")`).click(),
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
await this.toolbar.clickFields();
@ -79,7 +74,7 @@ export class ToolbarFieldsPage extends BasePage {
await this.toolbar.clickFields();
await this.waitForResponse({
uiAction: this.get().locator(`.nc-fields-show-system-fields`).click(),
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
await this.toolbar.clickFields();

54
scripts/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import BasePage from "../../../Base";
import { ToolbarPage } from "./index";
import { expect } from '@playwright/test';
import BasePage from '../../../Base';
import { ToolbarPage } from './index';
export class ToolbarFilterPage extends BasePage {
readonly toolbar: ToolbarPage;
@ -14,20 +14,12 @@ export class ToolbarFilterPage extends BasePage {
return this.rootPage.locator(`[pw-data="nc-filter-menu"]`);
}
async verify({
index,
column,
operator,
value,
}: {
index: number;
column: string;
operator: string;
value: string;
}) {
async verify({ index, column, operator, value }: { index: number; column: string; operator: string; value: string }) {
await expect(this.get().locator('.nc-filter-field-select').nth(index)).toHaveText(column);
await expect(this.get().locator('.nc-filter-operation-select').nth(index)).toHaveText(operator);
await expect.poll(async () => this.get().locator('input.nc-filter-value-select').nth(index).inputValue()).toBe(value);
await expect
.poll(async () => await this.get().locator('input.nc-filter-value-select').nth(index).inputValue())
.toBe(value);
}
async verifyFilter({ title }: { title: string }) {
@ -55,57 +47,53 @@ export class ToolbarFilterPage extends BasePage {
await this.get().locator(`button:has-text("Add Filter")`).first().click();
await this.rootPage.locator(".nc-filter-field-select").last().click();
await this.rootPage.locator('.nc-filter-field-select').last().click();
const selectColumn = this.rootPage
.locator("div.ant-select-dropdown.nc-dropdown-toolbar-field-list")
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${columnTitle}"][aria-selected="false"]:visible`)
.click();
await this.waitForResponse({
uiAction: selectColumn,
httpMethodsToMatch: isLocallySaved ? [ "GET" ] : ["POST", "PATCH" ],
httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'],
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/filters`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
const selectedOpType = await this.rootPage.locator('.nc-filter-operation-select').textContent();
if(selectedOpType !== opType) {
await this.rootPage.locator(".nc-filter-operation-select").last().click();
if (selectedOpType !== opType) {
await this.rootPage.locator('.nc-filter-operation-select').last().click();
const selectOpType = this.rootPage
.locator(".nc-dropdown-filter-comp-op")
.locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item:has-text("${opType}")`)
.click();
await this.waitForResponse({
uiAction: selectOpType,
httpMethodsToMatch: isLocallySaved ? [ "GET" ] : ["POST", "PATCH"],
httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'],
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/filters`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
}
const fillFilter = this.rootPage
.locator(".nc-filter-value-select")
.last()
.fill(value);
const fillFilter = this.rootPage.locator('.nc-filter-value-select').last().fill(value);
await this.waitForResponse({
uiAction: fillFilter,
httpMethodsToMatch: ["GET"],
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
await this.toolbar.clickFilter();
await this.toolbar.parent.waitLoading()
await this.toolbar.parent.waitLoading();
}
async resetFilter() {
await this.toolbar.clickFilter();
await this.waitForResponse({
uiAction: this.get().locator(".nc-filter-item-remove-btn").click(),
httpMethodsToMatch: ["DELETE"],
requestUrlPathToMatch: "/api/v1/db/meta/filters/",
uiAction: this.get().locator('.nc-filter-item-remove-btn').click(),
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: '/api/v1/db/meta/filters/',
});
await this.toolbar.clickFilter();
}

34
scripts/playwright/pages/Dashboard/common/Toolbar/ShareView.ts

@ -1,5 +1,5 @@
import BasePage from "../../../Base";
import { ToolbarPage } from "./index";
import BasePage from '../../../Base';
import { ToolbarPage } from './index';
export class ToolbarShareViewPage extends BasePage {
readonly toolbar: ToolbarPage;
@ -14,34 +14,22 @@ export class ToolbarShareViewPage extends BasePage {
}
async enablePassword(pwd: string) {
await this.get()
.locator(`[data-pw="nc-modal-share-view__with-password"]`)
.click();
await this.get()
.locator(`[data-pw="nc-modal-share-view__password"]`)
.fill(pwd);
await this.get()
.locator(`[data-pw="nc-modal-share-view__save-password"]`)
.click();
await this.verifyToast({ message: "Successfully updated" });
await this.get().locator(`[data-pw="nc-modal-share-view__with-password"]`).click();
await this.get().locator(`[data-pw="nc-modal-share-view__password"]`).fill(pwd);
await this.get().locator(`[data-pw="nc-modal-share-view__save-password"]`).click();
await this.verifyToast({ message: 'Successfully updated' });
}
async disablePassword() {
await this.get()
.locator(`[data-pw="nc-modal-share-view__with-password"`)
.click();
await this.get().locator(`[data-pw="nc-modal-share-view__with-password"`).click();
}
async toggleDownload() {
await this.get()
.locator(`[data-pw="nc-modal-share-view__with-csv-download"]`)
.click();
await this.get().locator(`[data-pw="nc-modal-share-view__with-csv-download"]`).click();
}
async getShareLink() {
return this.get()
.locator(`[data-pw="nc-modal-share-view__link"]`)
.innerText();
return await this.get().locator(`[data-pw="nc-modal-share-view__link"]`).innerText();
}
async close() {
@ -49,8 +37,6 @@ export class ToolbarShareViewPage extends BasePage {
}
async toggleSurveyMode() {
await this.get()
.locator(`[data-pw="nc-modal-share-view__survey-mode"]`)
.click();
await this.get().locator(`[data-pw="nc-modal-share-view__survey-mode"]`).click();
}
}

49
scripts/playwright/pages/Dashboard/common/Toolbar/Sort.ts

@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import BasePage from "../../../Base";
import { ToolbarPage } from "./index";
import { expect } from '@playwright/test';
import BasePage from '../../../Base';
import { ToolbarPage } from './index';
export class ToolbarSortPage extends BasePage {
readonly toolbar: ToolbarPage;
@ -14,17 +14,11 @@ export class ToolbarSortPage extends BasePage {
return this.rootPage.locator(`[pw-data="nc-sorts-menu"]`);
}
async verify({
index,
column,
direction,
}: {
index: number;
column: string;
direction: string;
}) {
async verify({ index, column, direction }: { index: number; column: string; direction: string }) {
await expect(this.get().locator('.nc-sort-field-select').nth(index)).toHaveText(column);
await expect(await this.get().locator('.nc-sort-dir-select >> span.ant-select-selection-item').nth(index)).toHaveText(direction);
await expect(
await this.get().locator('.nc-sort-dir-select >> span.ant-select-selection-item').nth(index)
).toHaveText(direction);
}
async addSort({
@ -41,32 +35,30 @@ export class ToolbarSortPage extends BasePage {
await this.get().locator(`button:has-text("Add Sort Option")`).click();
await this.rootPage.locator(".nc-sort-field-select").last().click();
await this.rootPage.locator('.nc-sort-field-select').last().click();
const selectColumn = this.rootPage
.locator("div.ant-select-dropdown.nc-dropdown-toolbar-field-list")
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${columnTitle}"]`)
.last()
.click();
await this.waitForResponse({
uiAction: selectColumn,
httpMethodsToMatch: isLocallySaved ? [ "GET" ] : ["POST", "PATCH" ],
httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'],
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/sorts`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
await this.rootPage.locator(".nc-sort-dir-select").last().click();
const selectSortDirection = this.rootPage
.locator(".nc-dropdown-sort-dir")
.locator(".ant-select-item")
await this.rootPage.locator('.nc-sort-dir-select').last().click();
const selectSortDirection = this.rootPage
.locator('.nc-dropdown-sort-dir')
.locator('.ant-select-item')
.nth(isAscending ? 0 : 1)
.click();
await this.waitForResponse({
uiAction: selectSortDirection,
httpMethodsToMatch: ["GET"],
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/`: `/api/v1/db/data/noco/`,
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
// close sort menu
@ -79,16 +71,13 @@ export class ToolbarSortPage extends BasePage {
// open sort menu
await this.toolbar.clickSort();
await this.get().locator(".nc-sort-item-remove-btn").click();
await this.get().locator('.nc-sort-item-remove-btn').click();
// close sort menu
await this.toolbar.clickSort();
}
click({ title }: { title: string }) {
return this.get()
.locator(`[pw-data="nc-fields-menu-${title}"]`)
.locator('input[type="checkbox"]')
.click();
return this.get().locator(`[pw-data="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]').click();
}
}

9
scripts/playwright/pages/Dashboard/common/Toolbar/StackBy.ts

@ -1,5 +1,5 @@
import BasePage from "../../../Base";
import { ToolbarPage } from "./index";
import BasePage from '../../../Base';
import { ToolbarPage } from './index';
export class ToolbarStackbyPage extends BasePage {
readonly toolbar: ToolbarPage;
@ -15,9 +15,6 @@ export class ToolbarStackbyPage extends BasePage {
async click({ title }: { title: string }) {
await this.get().locator(`.nc-kanban-grouping-field-select`).click();
await this.rootPage
.locator(".ant-select-dropdown:visible")
.locator(`div[title="${title}"]`)
.click();
await this.rootPage.locator('.ant-select-dropdown:visible').locator(`div[title="${title}"]`).click();
}
}

82
scripts/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts

@ -1,9 +1,9 @@
import { Locator, expect } from "@playwright/test";
import BasePage from "../../../Base";
import { GridPage } from "../../Grid";
import { ToolbarPage } from "./index";
import { expect, Locator } from '@playwright/test';
import BasePage from '../../../Base';
import { GridPage } from '../../Grid';
import { ToolbarPage } from './index';
// @ts-ignore
import fs from "fs";
import fs from 'fs';
export class ToolbarViewMenuPage extends BasePage {
readonly toolbar: ToolbarPage;
@ -32,17 +32,17 @@ export class ToolbarViewMenuPage extends BasePage {
}) {
const [download] = await Promise.all([
// Start waiting for the download
this.rootPage.waitForEvent("download"),
this.rootPage.waitForEvent('download'),
// Perform the action that initiates download
downloadLocator.click(),
]);
// Save downloaded file somewhere
await download.saveAs("./output/at.txt");
await download.saveAs('./output/at.txt');
// verify downloaded content against expected content
const expectedData = fs.readFileSync(expectedDataFile, "utf8");
const file = fs.readFileSync("./output/at.txt", "utf8");
const expectedData = fs.readFileSync(expectedDataFile, 'utf8');
const file = fs.readFileSync('./output/at.txt', 'utf8');
await expect(file).toEqual(expectedData);
}
@ -57,40 +57,34 @@ export class ToolbarViewMenuPage extends BasePage {
async click({ menu, subMenu }: { menu: string; subMenu?: string }) {
await this.viewsMenuBtn.click();
await this.get()
.locator(`.ant-dropdown-menu-title-content:has-text("${menu}")`)
.first()
.click();
await this.get().locator(`.ant-dropdown-menu-title-content:has-text("${menu}")`).first().click();
if (subMenu) {
// for CSV download, pass locator instead of clicking it here
if (subMenu === "Download as CSV") {
if (subMenu === 'Download as CSV') {
await this.verifyDownloadAsCSV({
downloadLocator: await this.rootPage
.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`)
.last(),
expectedDataFile: "./fixtures/expectedBaseDownloadData.txt",
expectedDataFile: './fixtures/expectedBaseDownloadData.txt',
});
} else {
await this.rootPage
.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`)
.last()
.click();
await this.rootPage.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`).last().click();
}
switch (subMenu) {
case "Download as CSV":
case 'Download as CSV':
await this.verifyToast({
message: "Successfully exported all table data",
message: 'Successfully exported all table data',
});
break;
case "Locked View":
case 'Locked View':
await this.verifyToast({
message: "Successfully Switched to locked view",
message: 'Successfully Switched to locked view',
});
break;
case "Collaborative View":
case 'Collaborative View':
await this.verifyToast({
message: "Successfully Switched to collaborative view",
message: 'Successfully Switched to collaborative view',
});
break;
default:
@ -101,44 +95,26 @@ export class ToolbarViewMenuPage extends BasePage {
}
async verifyLockMode() {
await expect(await this.toolbar.get().locator(`.nc-fields-menu-btn.nc-toolbar-btn`)).toBeDisabled();
await expect(await this.toolbar.get().locator(`.nc-filter-menu-btn.nc-toolbar-btn`)).toBeDisabled();
await expect(await this.toolbar.get().locator(`.nc-sort-menu-btn.nc-toolbar-btn`)).toBeDisabled();
await expect(
await this.toolbar.get().locator(`.nc-fields-menu-btn.nc-toolbar-btn`)
).toBeDisabled();
await expect(
await this.toolbar.get().locator(`.nc-filter-menu-btn.nc-toolbar-btn`)
).toBeDisabled();
await expect(
await this.toolbar.get().locator(`.nc-sort-menu-btn.nc-toolbar-btn`)
).toBeDisabled();
await expect(
await this.toolbar
.get()
.locator(`.nc-add-new-row-btn.nc-toolbar-btn > .nc-icon.disabled`)
await this.toolbar.get().locator(`.nc-add-new-row-btn.nc-toolbar-btn > .nc-icon.disabled`)
).toBeVisible();
await (this.toolbar.parent as GridPage).verifyEditDisabled({
columnHeader: "Country",
columnHeader: 'Country',
});
}
async verifyCollaborativeMode() {
await expect(
await this.toolbar.get().locator(`.nc-fields-menu-btn.nc-toolbar-btn`)
).toBeEnabled();
await expect(
await this.toolbar.get().locator(`.nc-filter-menu-btn.nc-toolbar-btn`)
).toBeEnabled();
await expect(
await this.toolbar.get().locator(`.nc-sort-menu-btn.nc-toolbar-btn`)
).toBeEnabled();
await expect(
await this.toolbar
.get()
.locator(`.nc-add-new-row-btn.nc-toolbar-btn > .nc-icon`)
).toBeVisible();
await expect(await this.toolbar.get().locator(`.nc-fields-menu-btn.nc-toolbar-btn`)).toBeEnabled();
await expect(await this.toolbar.get().locator(`.nc-filter-menu-btn.nc-toolbar-btn`)).toBeEnabled();
await expect(await this.toolbar.get().locator(`.nc-sort-menu-btn.nc-toolbar-btn`)).toBeEnabled();
await expect(await this.toolbar.get().locator(`.nc-add-new-row-btn.nc-toolbar-btn > .nc-icon`)).toBeVisible();
await (this.toolbar.parent as GridPage).verifyEditEnabled({
columnHeader: "Country",
columnHeader: 'Country',
});
}
}

107
scripts/playwright/pages/Dashboard/common/Toolbar/index.ts

@ -1,18 +1,18 @@
import { expect } from "@playwright/test";
import BasePage from "../../../Base";
import { ToolbarFieldsPage } from "./Fields";
import { ToolbarSortPage } from "./Sort";
import { ToolbarFilterPage } from "./Filter";
import { ToolbarShareViewPage } from "./ShareView";
import { ToolbarViewMenuPage } from "./ViewMenu";
import * as fs from "fs";
import { GridPage } from "../../Grid";
import { ToolbarActionsPage } from "./Actions";
import { GalleryPage } from "../../Gallery";
import { KanbanPage } from "../../Kanban";
import { FormPage } from "../../Form";
import { ToolbarStackbyPage } from "./StackBy";
import { ToolbarAddEditStackPage } from "./AddEditKanbanStack";
import { expect } from '@playwright/test';
import BasePage from '../../../Base';
import { ToolbarFieldsPage } from './Fields';
import { ToolbarSortPage } from './Sort';
import { ToolbarFilterPage } from './Filter';
import { ToolbarShareViewPage } from './ShareView';
import { ToolbarViewMenuPage } from './ViewMenu';
import * as fs from 'fs';
import { GridPage } from '../../Grid';
import { ToolbarActionsPage } from './Actions';
import { GalleryPage } from '../../Gallery';
import { KanbanPage } from '../../Kanban';
import { FormPage } from '../../Form';
import { ToolbarStackbyPage } from './StackBy';
import { ToolbarAddEditStackPage } from './AddEditKanbanStack';
export class ToolbarPage extends BasePage {
readonly parent: GridPage | GalleryPage | FormPage | KanbanPage;
@ -48,7 +48,7 @@ export class ToolbarPage extends BasePage {
await this.get().locator(`button.nc-actions-menu-btn`).click();
// Wait for the menu to close
if (menuOpen) await this.fields.get().waitFor({ state: "hidden" });
if (menuOpen) await this.fields.get().waitFor({ state: 'hidden' });
}
async clickFields() {
@ -57,7 +57,7 @@ export class ToolbarPage extends BasePage {
await this.get().locator(`button.nc-fields-menu-btn`).click();
// Wait for the menu to close
if (menuOpen) await this.fields.get().waitFor({ state: "hidden" });
if (menuOpen) await this.fields.get().waitFor({ state: 'hidden' });
}
async clickSort() {
@ -66,7 +66,7 @@ export class ToolbarPage extends BasePage {
await this.get().locator(`button.nc-sort-menu-btn`).click();
// Wait for the menu to close
if (menuOpen) await this.sort.get().waitFor({ state: "hidden" });
if (menuOpen) await this.sort.get().waitFor({ state: 'hidden' });
}
async clickFilter() {
@ -75,7 +75,7 @@ export class ToolbarPage extends BasePage {
await this.get().locator(`button.nc-filter-menu-btn`).click();
// Wait for the menu to close
if (menuOpen) await this.filter.get().waitFor({ state: "hidden" });
if (menuOpen) await this.filter.get().waitFor({ state: 'hidden' });
}
async clickShareView() {
@ -83,13 +83,11 @@ export class ToolbarPage extends BasePage {
await this.get().locator(`button.nc-btn-share-view `).click();
// Wait for the menu to close
if (menuOpen) await this.shareView.get().waitFor({ state: "hidden" });
if (menuOpen) await this.shareView.get().waitFor({ state: 'hidden' });
}
async clickStackByField() {
await this.get()
.locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`)
.click();
await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`).click();
}
async clickAddNewRow() {
@ -101,7 +99,7 @@ export class ToolbarPage extends BasePage {
const [download] = await Promise.all([
// Start waiting for the download
this.rootPage.waitForEvent("download"),
this.rootPage.waitForEvent('download'),
// Perform the action that initiates download
this.rootPage
.locator(`.nc-dropdown-actions-menu`)
@ -110,29 +108,23 @@ export class ToolbarPage extends BasePage {
]);
// Save downloaded file somewhere
await download.saveAs("./output/at.txt");
await download.saveAs('./output/at.txt');
// verify downloaded content against expected content
const expectedData = fs.readFileSync("./fixtures/expectedData.txt", "utf8");
const file = fs.readFileSync("./output/at.txt", "utf8");
const expectedData = fs.readFileSync('./fixtures/expectedData.txt', 'utf8');
const file = fs.readFileSync('./output/at.txt', 'utf8');
await expect(file).toEqual(expectedData);
}
async verifyStackByButton({ title }: { title: string }) {
await this.get()
.locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`)
.waitFor({ state: "visible" });
await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`).waitFor({ state: 'visible' });
await expect(
await this.get().locator(
`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn:has-text("${title}")`
)
await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn:has-text("${title}")`)
).toBeVisible();
}
async verifyDownloadDisabled() {
await this.get()
.locator(`.nc-toolbar-btn.nc-actions-menu-btn`)
.waitFor({ state: "hidden" });
await this.get().locator(`.nc-toolbar-btn.nc-actions-menu-btn`).waitFor({ state: 'hidden' });
}
async clickAddEditStack() {
@ -141,33 +133,24 @@ export class ToolbarPage extends BasePage {
async validateViewsMenu(param: { role: string; mode?: string }) {
let menuItems = {
creator: [
"Download",
"Upload",
"Shared View List",
"Webhooks",
"Get API Snippet",
"ERD View",
],
editor: ["Download", "Upload", "Get API Snippet", "ERD View"],
commenter: ["Download as CSV", "Download as XLSX"],
viewer: ["Download as CSV", "Download as XLSX"],
creator: ['Download', 'Upload', 'Shared View List', 'Webhooks', 'Get API Snippet', 'ERD View'],
editor: ['Download', 'Upload', 'Get API Snippet', 'ERD View'],
commenter: ['Download as CSV', 'Download as XLSX'],
viewer: ['Download as CSV', 'Download as XLSX'],
};
if (param.mode === "shareBase") {
if (param.mode === 'shareBase') {
menuItems = {
creator: [],
editor: ["Download", "Upload", "ERD View"],
editor: ['Download', 'Upload', 'ERD View'],
commenter: [],
viewer: ["Download as CSV", "Download as XLSX"],
viewer: ['Download as CSV', 'Download as XLSX'],
};
}
let vMenu = await this.rootPage.locator(
".nc-dropdown-actions-menu:visible"
);
const vMenu = await this.rootPage.locator('.nc-dropdown-actions-menu:visible');
for (let item of menuItems[param.role]) {
for (const item of menuItems[param.role]) {
await expect(vMenu).toContainText(item);
}
}
@ -179,19 +162,19 @@ export class ToolbarPage extends BasePage {
mode: param.mode,
});
let menuItems = {
creator: ["Fields", "Filter", "Sort", "Share View"],
editor: ["Fields", "Filter", "Sort"],
commenter: ["Fields", "Filter", "Sort", "Download"],
viewer: ["Fields", "Filter", "Sort", "Download"],
const menuItems = {
creator: ['Fields', 'Filter', 'Sort', 'Share View'],
editor: ['Fields', 'Filter', 'Sort'],
commenter: ['Fields', 'Filter', 'Sort', 'Download'],
viewer: ['Fields', 'Filter', 'Sort', 'Download'],
};
for (let item of menuItems[param.role]) {
for (const item of menuItems[param.role]) {
await expect(this.get()).toContainText(item);
}
await expect(this.get().locator(".nc-add-new-row-btn")).toHaveCount(
param.role === "creator" || param.role === "editor" ? 1 : 0
await expect(this.get().locator('.nc-add-new-row-btn')).toHaveCount(
param.role === 'creator' || param.role === 'editor' ? 1 : 0
);
}
}

38
scripts/playwright/pages/Dashboard/commonBase/Erd.ts

@ -1,4 +1,4 @@
import { expect } from '@playwright/test';
import { expect } from '@playwright/test';
import BasePage from '../../Base';
export abstract class ErdBasePage extends BasePage {
@ -37,26 +37,36 @@ export abstract class ErdBasePage extends BasePage {
}
async verifyEasterEggNotShown() {
await expect(await this.get().locator('.nc-erd-showMMTables-checkbox')).not.toBeVisible()
await expect(await this.get().locator('.nc-erd-showMMTables-checkbox')).not.toBeVisible();
}
async verifyNode({tableName, columnName, columnNameShouldNotExist}: {tableName: string; columnName?: string, columnNameShouldNotExist?: string}) {
await this.get().locator(`.nc-erd-table-node-${tableName}`).waitFor({state: 'visible'});
async verifyNode({
tableName,
columnName,
columnNameShouldNotExist,
}: {
tableName: string;
columnName?: string;
columnNameShouldNotExist?: string;
}) {
await this.get().locator(`.nc-erd-table-node-${tableName}`).waitFor({ state: 'visible' });
if (columnName) {
await this.get().locator(`.nc-erd-table-node-${tableName}-column-${columnName}`).waitFor({state: 'visible'});
await this.get().locator(`.nc-erd-table-node-${tableName}-column-${columnName}`).waitFor({ state: 'visible' });
}
if(columnNameShouldNotExist) {
await this.get().locator(`.nc-erd-table-node-${tableName}-column-${columnNameShouldNotExist}`).waitFor({state: 'hidden'});
if (columnNameShouldNotExist) {
await this.get()
.locator(`.nc-erd-table-node-${tableName}-column-${columnNameShouldNotExist}`)
.waitFor({ state: 'hidden' });
}
}
async verifyNodeDoesNotExist({tableName}: {tableName: string}) {
await this.get().locator(`.nc-erd-table-node-${tableName}`).waitFor({state: 'hidden'});
async verifyNodeDoesNotExist({ tableName }: { tableName: string }) {
await this.get().locator(`.nc-erd-table-node-${tableName}`).waitFor({ state: 'hidden' });
}
async verifyColumns({tableName, columns}: {tableName: string; columns: string[]}) {
async verifyColumns({ tableName, columns }: { tableName: string; columns: string[] }) {
for (const column of columns) {
await this.verifyNode({tableName, columnName: column});
await this.verifyNode({ tableName, columnName: column });
}
}
@ -78,9 +88,9 @@ export abstract class ErdBasePage extends BasePage {
await expect(this.get().locator('.nc-erd-edge-rect')).toHaveCount(rectangleCount);
}
async verifyJunctionTableLabel({tableTitle, tableName}: {tableName: string; tableTitle: string}) {
async verifyJunctionTableLabel({ tableTitle, tableName }: { tableName: string; tableTitle: string }) {
await await this.vueFlow().locator(`.nc-erd-table-label-${tableTitle}-${tableName}`).locator('text').waitFor({
state: 'visible',
})
});
}
}
}

183
scripts/playwright/pages/Dashboard/index.ts

@ -1,20 +1,20 @@
// playwright-dev-page.ts
import { Locator, Page, expect } from "@playwright/test";
import BasePage from "../Base";
import { GridPage } from "./Grid";
import { FormPage } from "./Form";
import { ExpandedFormPage } from "./ExpandedForm";
import { ChildList } from "./Grid/Column/LTAR/ChildList";
import { LinkRecord } from "./Grid/Column/LTAR/LinkRecord";
import { TreeViewPage } from "./TreeView";
import { SettingsPage } from "./Settings";
import { ViewSidebarPage } from "./ViewSidebar";
import { GalleryPage } from "./Gallery";
import { KanbanPage } from "./Kanban";
import { ImportAirtablePage } from "./Import/Airtable";
import { ImportTemplatePage } from "./Import/ImportTemplate";
import { WebhookFormPage } from "./WebhookForm";
import { ProjectsPage } from "../ProjectsPage";
import { expect, Locator, Page } from '@playwright/test';
import BasePage from '../Base';
import { GridPage } from './Grid';
import { FormPage } from './Form';
import { ExpandedFormPage } from './ExpandedForm';
import { ChildList } from './Grid/Column/LTAR/ChildList';
import { LinkRecord } from './Grid/Column/LTAR/LinkRecord';
import { TreeViewPage } from './TreeView';
import { SettingsPage } from './Settings';
import { ViewSidebarPage } from './ViewSidebar';
import { GalleryPage } from './Gallery';
import { KanbanPage } from './Kanban';
import { ImportAirtablePage } from './Import/Airtable';
import { ImportTemplatePage } from './Import/ImportTemplate';
import { WebhookFormPage } from './WebhookForm';
import { ProjectsPage } from '../ProjectsPage';
export class DashboardPage extends BasePage {
readonly project: any;
@ -37,8 +37,8 @@ export class DashboardPage extends BasePage {
constructor(rootPage: Page, project: any) {
super(rootPage);
this.project = project;
this.tablesSideBar = rootPage.locator(".nc-treeview-container");
this.tabBar = rootPage.locator(".nc-tab-bar");
this.tablesSideBar = rootPage.locator('.nc-treeview-container');
this.tabBar = rootPage.locator('.nc-tab-bar');
this.treeView = new TreeViewPage(this, project);
this.grid = new GridPage(this);
this.gallery = new GalleryPage(this);
@ -54,7 +54,7 @@ export class DashboardPage extends BasePage {
}
get() {
return this.rootPage.locator("html");
return this.rootPage.locator('html');
}
async goto() {
@ -63,20 +63,16 @@ export class DashboardPage extends BasePage {
async gotoSettings() {
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
await this.rootPage
.locator('div.nc-project-menu-item:has-text(" Team & Settings")')
.click();
await this.rootPage.locator('div.nc-project-menu-item:has-text(" Team & Settings")').click();
}
async verifyInTabBar({ title }: { title: string }) {
await this.tabBar
.textContent()
.then((text) => expect(text).toContain(title));
await this.tabBar.textContent().then(text => expect(text).toContain(title));
}
async closeTab({ title }: { title: string }) {
let tab = await this.tabBar.locator(`.ant-tabs-tab:has-text("${title}")`);
await tab.locator("button.ant-tabs-tab-remove").click();
const tab = await this.tabBar.locator(`.ant-tabs-tab:has-text("${title}")`);
await tab.locator('button.ant-tabs-tab-remove').click();
// fix me!
// await tab.waitFor({ state: "detached" });
@ -89,47 +85,37 @@ export class DashboardPage extends BasePage {
await projectsPage.waitToBeRendered();
}
// When a tab is opened, it is not always immediately visible.
// When a tab is opened, it is not always immediately visible.
// Hence will wait till contents are visible
async waitForTabRender({
title,
mode = "standard",
}: {
title: string;
mode?: string;
}) {
if(title === 'Team & Auth') {
await this.get().locator('div[role="tab"]', {
hasText: 'Users Management'
}).waitFor({
state: 'visible'
});
}else {
async waitForTabRender({ title, mode = 'standard' }: { title: string; mode?: string }) {
if (title === 'Team & Auth') {
await this.get()
.locator('div[role="tab"]', {
hasText: 'Users Management',
})
.waitFor({
state: 'visible',
});
} else {
await this.get().locator('[pw-data="grid-id-column"]').waitFor({
state: "visible",
state: 'visible',
});
}
await this.tabBar
.locator(`.ant-tabs-tab-active:has-text("${title}")`)
.waitFor();
await this.tabBar.locator(`.ant-tabs-tab-active:has-text("${title}")`).waitFor();
// wait active tab animation to finish
await expect
.poll(async () => {
return await this.tabBar
.locator(`[data-pw="nc-root-tabs-${title}"]`)
.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue("color");
});
return await this.tabBar.locator(`[data-pw="nc-root-tabs-${title}"]`).evaluate(el => {
return window.getComputedStyle(el).getPropertyValue('color');
});
})
.toBe("rgb(67, 81, 232)"); // active tab text color
.toBe('rgb(67, 81, 232)'); // active tab text color
await this.get()
.locator('[pw-data="grid-load-spinner"]')
.waitFor({ state: "hidden" });
await this.get().locator('[pw-data="grid-load-spinner"]').waitFor({ state: 'hidden' });
if (mode === "standard") {
if (mode === 'standard') {
await expect(this.rootPage).toHaveURL(
`/#/nc/${this.project.id}/${title === 'Team & Auth' ? 'auth' : `table/${title}`}`
);
@ -138,98 +124,67 @@ export class DashboardPage extends BasePage {
async openPasswordChangeModal() {
// open change password portal
await this.rootPage.locator(".nc-menu-accounts").click();
await this.rootPage.locator('.nc-menu-accounts').click();
await this.rootPage
.locator(
`.nc-dropdown-user-accounts-menu >> [data-cy="nc-menu-accounts__user-settings"]`
)
.locator(`.nc-dropdown-user-accounts-menu >> [data-cy="nc-menu-accounts__user-settings"]`)
.click();
}
// todo: Move this to a seperate page
async changePassword({
oldPass,
newPass,
repeatPass,
}: {
oldPass: string;
newPass: string;
repeatPass: string;
}) {
async changePassword({ oldPass, newPass, repeatPass }: { oldPass: string; newPass: string; repeatPass: string }) {
// change password
const currentPassword = await this.rootPage.locator(
'input[data-cy="nc-user-settings-form__current-password"]'
);
const newPassword = await this.rootPage.locator(
'input[data-cy="nc-user-settings-form__new-password"]'
);
const confirmPassword = await this.rootPage.locator(
'input[data-cy="nc-user-settings-form__new-password-repeat"]'
);
const currentPassword = await this.rootPage.locator('input[data-cy="nc-user-settings-form__current-password"]');
const newPassword = await this.rootPage.locator('input[data-cy="nc-user-settings-form__new-password"]');
const confirmPassword = await this.rootPage.locator('input[data-cy="nc-user-settings-form__new-password-repeat"]');
await currentPassword.fill(oldPass);
await newPassword.fill(newPass);
await confirmPassword.fill(repeatPass);
await this.rootPage
.locator('button[data-cy="nc-user-settings-form__submit"]')
.click();
await this.rootPage.locator('button[data-cy="nc-user-settings-form__submit"]').click();
}
async signOut() {
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
let projMenu = await this.rootPage.locator(".nc-dropdown-project-menu");
const projMenu = await this.rootPage.locator('.nc-dropdown-project-menu');
await projMenu.locator('[data-menu-id="account"]:visible').click();
await this.rootPage
.locator('div.nc-project-menu-item:has-text("Sign Out"):visible')
.click();
await this.rootPage.locator('div.nc-project-menu-item:has-text("Sign Out"):visible').click();
await this.rootPage.locator('[data-cy="nc-form-signin"]:visible').waitFor();
}
async validateProjectMenu(param: { role: string; mode?: string }) {
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
let pMenu = this.rootPage.locator(`.nc-dropdown-project-menu:visible`);
const pMenu = this.rootPage.locator(`.nc-dropdown-project-menu:visible`);
// menu items
let menuItems = {
creator: [
"Copy Project Info",
"Swagger: REST APIs",
"Copy Auth Token",
"Team & Settings",
"Themes",
"Preview as",
"Language",
"Account",
],
editor: [
"Copy Project Info",
"Swagger: REST APIs",
"Copy Auth Token",
"Language",
"Account",
'Copy Project Info',
'Swagger: REST APIs',
'Copy Auth Token',
'Team & Settings',
'Themes',
'Preview as',
'Language',
'Account',
],
commenter: [
"Copy Project Info",
"Copy Auth Token",
"Language",
"Account",
],
viewer: ["Copy Project Info", "Copy Auth Token", "Language", "Account"],
editor: ['Copy Project Info', 'Swagger: REST APIs', 'Copy Auth Token', 'Language', 'Account'],
commenter: ['Copy Project Info', 'Copy Auth Token', 'Language', 'Account'],
viewer: ['Copy Project Info', 'Copy Auth Token', 'Language', 'Account'],
};
if (param?.mode === "shareBase") {
if (param?.mode === 'shareBase') {
menuItems = {
creator: [],
commenter: [],
editor: ["Language"],
viewer: ["Language"],
editor: ['Language'],
viewer: ['Language'],
};
}
// common items
for (let item of menuItems[param.role]) {
for (const item of menuItems[param.role]) {
await expect(pMenu).toContainText(item);
}
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
@ -237,8 +192,6 @@ export class DashboardPage extends BasePage {
// Wait for the loader i.e the loader than appears when rows are being fetched, saved etc on the top right of dashboard
async waitForLoaderToDisappear() {
await this.rootPage
.locator('[pw-data="nc-loading"]')
.waitFor({ state: "hidden" });
await this.rootPage.locator('[pw-data="nc-loading"]').waitFor({ state: 'hidden' });
}
}

26
scripts/playwright/pages/LoginPage/index.ts

@ -1,6 +1,6 @@
// playwright-dev-page.ts
import { expect, Page } from "@playwright/test";
import BasePage from "../Base";
import { expect, Page } from '@playwright/test';
import BasePage from '../Base';
export class LoginPage extends BasePage {
constructor(rootPage: Page) {
@ -8,46 +8,44 @@ export class LoginPage extends BasePage {
}
prefixEmail(email: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0'
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0';
return `nc_test_${parallelId}_${email}`;
}
goto() {
return this.rootPage.goto("/#/signin");
return this.rootPage.goto('/#/signin');
}
get() {
return this.rootPage.locator("html");
return this.rootPage.locator('html');
}
async fillEmail({email, withoutPrefix}:{email: string, withoutPrefix?: boolean}) {
if(!withoutPrefix) email = this.prefixEmail(email);
async fillEmail({ email, withoutPrefix }: { email: string; withoutPrefix?: boolean }) {
if (!withoutPrefix) email = this.prefixEmail(email);
await this.get().locator(`[pw-data="nc-form-signin__email"]`).waitFor();
await this.get().locator(`[pw-data="nc-form-signin__email"]`).fill(email);
}
async fillPassword(password: string) {
await this.get()
.locator(`[pw-data="nc-form-signin__password"]`)
.fill(password);
await this.get().locator(`[pw-data="nc-form-signin__password"]`).fill(password);
}
async submit() {
await this.get().locator(`[pw-data="nc-form-signin__submit"]`).click();
// todo: Login api can take some time to respond if server is under load
await expect(this.rootPage).toHaveURL("http://localhost:3000/#/", {
await expect(this.rootPage).toHaveURL('http://localhost:3000/#/', {
timeout: 15000,
});
}
async signIn({ email, password, withoutPrefix }: { email: string; password: string, withoutPrefix?: boolean }) {
async signIn({ email, password, withoutPrefix }: { email: string; password: string; withoutPrefix?: boolean }) {
await this.goto();
// todo: Login page is sometimes not loaded. Probably because of lazy loading
await this.rootPage.waitForTimeout(1500);
await this.fillEmail({email, withoutPrefix});
await this.fillEmail({ email, withoutPrefix });
await this.fillPassword(password);
await this.submit();
}

144
scripts/playwright/pages/ProjectsPage/index.ts

@ -1,7 +1,7 @@
// playwright-dev-page.ts
import { expect, Page } from "@playwright/test";
import BasePage from "../Base";
import { DashboardPage } from "../Dashboard";
import { expect, Page } from '@playwright/test';
import BasePage from '../Base';
import { DashboardPage } from '../Dashboard';
export class ProjectsPage extends BasePage {
constructor(rootPage: Page) {
@ -9,7 +9,7 @@ export class ProjectsPage extends BasePage {
}
prefixTitle(title: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0'
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0';
return `${title}${parallelId}`;
}
@ -19,36 +19,28 @@ export class ProjectsPage extends BasePage {
// create project
async createProject({
name = "sample",
type = "xcdb",
name = 'sample',
type = 'xcdb',
withoutPrefix,
}: {
name?: string;
type?: string;
withoutPrefix?: boolean;
}) {
if(!withoutPrefix) name = this.prefixTitle(name);
await this.rootPage.locator(".nc-new-project-menu").click();
const createProjectMenu = await this.rootPage.locator(
".nc-dropdown-create-project"
);
if (type === "xcdb") {
await createProjectMenu
.locator(`.ant-dropdown-menu-title-content`)
.nth(0)
.click();
if (!withoutPrefix) name = this.prefixTitle(name);
await this.rootPage.locator('.nc-new-project-menu').click();
const createProjectMenu = await this.rootPage.locator('.nc-dropdown-create-project');
if (type === 'xcdb') {
await createProjectMenu.locator(`.ant-dropdown-menu-title-content`).nth(0).click();
} else {
await createProjectMenu
.locator(`.ant-dropdown-menu-title-content`)
.nth(1)
.click();
await createProjectMenu.locator(`.ant-dropdown-menu-title-content`).nth(1).click();
}
await this.rootPage.locator(`.nc-metadb-project-name`).waitFor();
await this.rootPage.locator(`input.nc-metadb-project-name`).fill(name);
await this.rootPage.locator(`input.nc-metadb-project-name`).press("Enter");
await this.rootPage.locator(`input.nc-metadb-project-name`).press('Enter');
// fix me! wait for page to be rendered completely
await this.rootPage.waitForTimeout(2000);
@ -56,88 +48,86 @@ export class ProjectsPage extends BasePage {
async reloadProjects() {
const reloadUiAction = this.get().locator('[pw-data="projects-reload-button"]').click();
await this.waitForResponse(
{
uiAction: reloadUiAction,
requestUrlPathToMatch: "/api/v1/db/meta/projects",
httpMethodsToMatch: ["GET"],
}
)
await this.waitForResponse({
uiAction: reloadUiAction,
requestUrlPathToMatch: '/api/v1/db/meta/projects',
httpMethodsToMatch: ['GET'],
});
}
async waitToBeRendered() {
await this.get().waitFor({
state: "visible",
state: 'visible',
});
(await this.get().elementHandle())?.waitForElementState("stable");
(await this.get().elementHandle())?.waitForElementState('stable');
// Wait till the ant table is rendered
await this.get().locator('thead.ant-table-thead >> th').nth(0).waitFor({state: 'visible'});
await this.get().locator('thead.ant-table-thead >> th').nth(0).waitFor({ state: 'visible' });
await expect(this.get().locator('thead.ant-table-thead >> th').nth(0)).toHaveText('Title');
// todo: remove this, all the above asserts are useless.
// todo: remove this, all the above asserts are useless.
// The elements are actually invisible from screenshot but in dom level its visible. Lazy loading issue
await this.rootPage.waitForTimeout(1200);
}
async openProject(
{
title,
withoutPrefix,
waitForAuthTab = true
}:
{
title: string,
withoutPrefix?: boolean
waitForAuthTab?: boolean
}) {
if(!withoutPrefix) title = this.prefixTitle(title);
async openProject({
title,
withoutPrefix,
waitForAuthTab = true,
}: {
title: string;
withoutPrefix?: boolean;
waitForAuthTab?: boolean;
}) {
if (!withoutPrefix) title = this.prefixTitle(title);
let project: any;
const openProjectUiAction = this.get().locator(`.ant-table-cell`,{
hasText: title
}).click();
const openProjectUiAction = this.get()
.locator(`.ant-table-cell`, {
hasText: title,
})
.click();
await Promise.all([
this.rootPage.waitForResponse(async (res) => {
let json:any = {}
try{
json = await res.json()
} catch(e) {
this.rootPage.waitForResponse(async res => {
let json: any = {};
try {
json = await res.json();
} catch (e) {
return false;
}
const isRequiredResponse = res.request().url().includes('/api/v1/db/meta/projects') &&
['GET'].includes(res.request().method()) &&
json?.title === title;
const isRequiredResponse =
res.request().url().includes('/api/v1/db/meta/projects') &&
['GET'].includes(res.request().method()) &&
json?.title === title;
if(isRequiredResponse){
if (isRequiredResponse) {
project = json;
}
return isRequiredResponse;
}),
openProjectUiAction
openProjectUiAction,
]);
const dashboard = new DashboardPage(this.rootPage, project);
if(waitForAuthTab) await dashboard.waitForTabRender({title: 'Team & Auth'});
if (waitForAuthTab) await dashboard.waitForTabRender({ title: 'Team & Auth' });
return project;
}
async deleteProject({title, withoutPrefix}: {title: string, withoutPrefix?: boolean}) {
if(!withoutPrefix) title = this.prefixTitle(title);
async deleteProject({ title, withoutPrefix }: { title: string; withoutPrefix?: boolean }) {
if (!withoutPrefix) title = this.prefixTitle(title);
await this.get().locator(`[pw-data="delete-project-${title}"]`).click();
await this.rootPage.locator(`button:has-text("Yes")`).click();
await this.get().locator('.ant-table-row', {hasText: title}).waitFor({state: 'hidden'});
await this.get().locator('.ant-table-row', { hasText: title }).waitFor({ state: 'hidden' });
}
async renameProject({
title,
newTitle,
@ -147,45 +137,41 @@ export class ProjectsPage extends BasePage {
newTitle: string;
withoutPrefix?: boolean;
}) {
if(!withoutPrefix) title = this.prefixTitle(title);
if(!withoutPrefix) newTitle = this.prefixTitle(newTitle);
if (!withoutPrefix) title = this.prefixTitle(title);
if (!withoutPrefix) newTitle = this.prefixTitle(newTitle);
const project = this.rootPage;
const projRow = await project.locator(`tr`, {
has: project.locator(`td.ant-table-cell:has-text("${title}")`),
});
await projRow.locator(".nc-action-btn").nth(0).click();
await project.locator("input.nc-metadb-project-name").fill(newTitle);
await projRow.locator('.nc-action-btn').nth(0).click();
await project.locator('input.nc-metadb-project-name').fill(newTitle);
// press enter to save
const submitAction = project.locator("input.nc-metadb-project-name").press("Enter");
const submitAction = project.locator('input.nc-metadb-project-name').press('Enter');
await this.waitForResponse({
uiAction: submitAction,
requestUrlPathToMatch: 'api/v1/db/meta/projects/',
httpMethodsToMatch: ['PATCH'],
});
// todo: vue navigation breaks if page changes very quickly
await this.rootPage.waitForTimeout(1000);
}
async openLanguageMenu() {
await this.rootPage.locator(".nc-menu-translate").click();
await this.rootPage.locator('.nc-menu-translate').click();
}
async selectLanguage({ index }: { index: number }) {
let modal = await this.rootPage.locator(".nc-dropdown-menu-translate");
const modal = await this.rootPage.locator('.nc-dropdown-menu-translate');
await modal.locator(`.ant-dropdown-menu-item`).nth(index).click();
}
async verifyLanguage(param: { json: any }) {
const title = await this.rootPage
.locator(`.nc-project-page-title`);
const menu = this.rootPage
.locator(`.nc-new-project-menu`);
const title = await this.rootPage.locator(`.nc-project-page-title`);
const menu = this.rootPage.locator(`.nc-new-project-menu`);
await expect(title).toHaveText(param.json.title.myProject);
await expect(menu).toHaveText(param.json.title.newProj);
await this.rootPage
.locator(`[placeholder="${param.json.activity.searchProject}"]`)
.waitFor();
await this.rootPage.locator(`[placeholder="${param.json.activity.searchProject}"]`).waitFor();
}
}

22
scripts/playwright/pages/SharedForm/index.ts

@ -1,7 +1,7 @@
// playwright-dev-page.ts
import { Locator, Page, expect } from "@playwright/test";
import BasePage from "../Base";
import { CellPageObject } from "../Dashboard/common/Cell";
import { expect, Locator, Page } from '@playwright/test';
import BasePage from '../Base';
import { CellPageObject } from '../Dashboard/common/Cell';
export class SharedFormPage extends BasePage {
readonly cell: CellPageObject;
@ -12,20 +12,22 @@ export class SharedFormPage extends BasePage {
}
get() {
return this.rootPage.locator("html");
return this.rootPage.locator('html');
}
async submit() {
await this.waitForResponse({
uiAction:this.get().locator('[pw-data="shared-form-submit-button"]').click(),
httpMethodsToMatch: ["POST"],
requestUrlPathToMatch: '/rows'
uiAction: this.get().locator('[pw-data="shared-form-submit-button"]').click(),
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/rows',
});
}
async verifySuccessMessage() {
await expect(await this.get().locator('.ant-alert-success', {
hasText: 'Successfully submitted form data'
})).toBeVisible();
await expect(
await this.get().locator('.ant-alert-success', {
hasText: 'Successfully submitted form data',
})
).toBeVisible();
}
}

26
scripts/playwright/pages/SignupPage/index.ts

@ -1,7 +1,7 @@
// playwright-dev-page.ts
import { expect, Page } from "@playwright/test";
import BasePage from "../Base";
import { ProjectsPage } from "../ProjectsPage";
import { expect, Page } from '@playwright/test';
import BasePage from '../Base';
import { ProjectsPage } from '../ProjectsPage';
export class SignupPage extends BasePage {
readonly projectsPage: ProjectsPage;
@ -12,30 +12,26 @@ export class SignupPage extends BasePage {
}
prefixEmail(email: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0'
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0';
return `nc_test_${parallelId}_${email}`;
}
goto() {
return this.rootPage.goto("/#/signup/");
return this.rootPage.goto('/#/signup/');
}
get() {
return this.rootPage.locator("html");
return this.rootPage.locator('html');
}
async signUp({ email, password, withoutPrefix }: { email: string; password: string, withoutPrefix?: boolean }) {
if(!withoutPrefix) email = this.prefixEmail(email);
async signUp({ email, password, withoutPrefix }: { email: string; password: string; withoutPrefix?: boolean }) {
if (!withoutPrefix) email = this.prefixEmail(email);
const signUp = this.rootPage;
await signUp.locator('button:has-text("SIGN UP")').waitFor();
await signUp
.locator(`input[placeholder="Enter your work email"]`)
.fill(email);
await signUp
.locator(`input[placeholder="Enter your password"]`)
.fill(password);
await signUp.locator(`input[placeholder="Enter your work email"]`).fill(email);
await signUp.locator(`input[placeholder="Enter your password"]`).fill(password);
await signUp.locator(`button:has-text("SIGN UP")`).click();
await this.projectsPage.waitToBeRendered();
}

297
scripts/playwright/quickTests/commonTest.ts

@ -1,70 +1,68 @@
import { DashboardPage } from "../pages/Dashboard";
import { ProjectsPage } from "../pages/ProjectsPage";
import { NcContext } from "../setup";
import { isMysql } from "../setup/db";
import { DashboardPage } from '../pages/Dashboard';
import { ProjectsPage } from '../pages/ProjectsPage';
import { NcContext } from '../setup';
import { isMysql } from '../setup/db';
// normal fields
let recordCells = {
Name: "Movie-1",
Notes: "Good",
Status: "Todo",
Tags: "Jan",
Phone: "123123123",
Email: "a@b.com",
URL: "www.a.com",
Number: "1",
Value: "$1.00",
Percent: "0.01",
const recordCells = {
Name: 'Movie-1',
Notes: 'Good',
Status: 'Todo',
Tags: 'Jan',
Phone: '123123123',
Email: 'a@b.com',
URL: 'www.a.com',
Number: '1',
Value: '$1.00',
Percent: '0.01',
};
// links/ computed fields
let recordsVirtualCells = {
Duration: "00:01",
const recordsVirtualCells = {
Duration: '00:01',
Done: true,
Date: "2022-05-31",
Date: '2022-05-31',
Rating: 1,
Actor: ["Actor1", "Actor2"],
"Status (from Actor)": ["Todo", "In progress"],
RollUp: "128",
Computation: "4.04",
Producer: ["P1", "P2"],
Actor: ['Actor1', 'Actor2'],
'Status (from Actor)': ['Todo', 'In progress'],
RollUp: '128',
Computation: '4.04',
Producer: ['P1', 'P2'],
};
let tn = ["Film", "Actor", "Producer"];
let cn = [
"Name",
"Notes",
"Status",
"Tags",
"Done",
"Date",
"Phone",
"Email",
"URL",
"Number",
"Percent",
"Duration",
"Rating",
"Actor",
"Status (from Actor)",
"RollUp",
"Computation",
"Producer",
const tn = ['Film', 'Actor', 'Producer'];
const cn = [
'Name',
'Notes',
'Status',
'Tags',
'Done',
'Date',
'Phone',
'Email',
'URL',
'Number',
'Percent',
'Duration',
'Rating',
'Actor',
'Status (from Actor)',
'RollUp',
'Computation',
'Producer',
];
const quickVerify = async (
{
dashboard,
airtableImport,
context
}:
{
dashboard: DashboardPage,
airtableImport?: boolean,
context: NcContext
}) => {
await dashboard.treeView.openTable({ title: "Film" });
const quickVerify = async ({
dashboard,
airtableImport,
context,
}: {
dashboard: DashboardPage;
airtableImport?: boolean;
context: NcContext;
}) => {
await dashboard.treeView.openTable({ title: 'Film' });
// Verify tables
for (let i = 0; i < tn.length; i++) {
@ -84,166 +82,175 @@ const quickVerify = async (
// Verify cells
// normal cells
for (let [key, value] of Object.entries(recordCells)) {
for (const [key, value] of Object.entries(recordCells)) {
await dashboard.grid.cell.verify({ index: cellIndex, columnHeader: key, value });
}
// checkbox
await dashboard.grid.cell.checkbox.verifyChecked({ index: cellIndex, columnHeader: "Done" });
await dashboard.grid.cell.checkbox.verifyChecked({ index: cellIndex, columnHeader: 'Done' });
// duration
await dashboard.grid.cell.verify({ index: cellIndex, columnHeader: "Duration", value: recordsVirtualCells.Duration });
await dashboard.grid.cell.verify({ index: cellIndex, columnHeader: 'Duration', value: recordsVirtualCells.Duration });
// rating
await dashboard.grid.cell.rating.verify({ index: cellIndex, columnHeader: "Rating", rating: recordsVirtualCells.Rating });
await dashboard.grid.cell.rating.verify({
index: cellIndex,
columnHeader: 'Rating',
rating: recordsVirtualCells.Rating,
});
// LinkToAnotherRecord
await dashboard.grid.cell.verifyVirtualCell({ index: cellIndex, columnHeader: "Actor", value: isMysql(context) ? ['Actor1'] : recordsVirtualCells.Actor });
await dashboard.grid.cell.verifyVirtualCell({
index: cellIndex,
columnHeader: 'Actor',
value: isMysql(context) ? ['Actor1'] : recordsVirtualCells.Actor,
});
// Status (from Actor)
// todo: Find a way to verify only the elements that are passed in
// await dashboard.grid.cell.verify({ index: cellIndex, columnHeader: "Status (from Actor)", value: recordsVirtualCells["Status (from Actor)"][0] });
if(!airtableImport){
if (!airtableImport) {
// RollUp
await dashboard.grid.cell.verify({ index: cellIndex, columnHeader: "RollUp", value: recordsVirtualCells.RollUp });
await dashboard.grid.cell.verify({ index: cellIndex, columnHeader: 'RollUp', value: recordsVirtualCells.RollUp });
// Computation
await dashboard.grid.cell.verify({ index: cellIndex, columnHeader: "Computation", value: recordsVirtualCells.Computation });
await dashboard.grid.cell.verify({
index: cellIndex,
columnHeader: 'Computation',
value: recordsVirtualCells.Computation,
});
// LinkToAnotherRecord
await dashboard.grid.cell.verifyVirtualCell({ index: cellIndex, columnHeader: "Producer", value: recordsVirtualCells.Producer });
await dashboard.grid.cell.verifyVirtualCell({
index: cellIndex,
columnHeader: 'Producer',
value: recordsVirtualCells.Producer,
});
}
// Verify form
await dashboard.viewSidebar.openView({ title: "FormTitle" });
await dashboard.form.verifyHeader({ title: "FormTitle", subtitle: "FormDescription" });
await dashboard.form.verifyFormFieldLabel({ index: 0, label: "DisplayName" });
await dashboard.form.verifyFormFieldHelpText({ index: 0, helpText: "HelpText" });
await dashboard.viewSidebar.openView({ title: 'FormTitle' });
await dashboard.form.verifyHeader({ title: 'FormTitle', subtitle: 'FormDescription' });
await dashboard.form.verifyFormFieldLabel({ index: 0, label: 'DisplayName' });
await dashboard.form.verifyFormFieldHelpText({ index: 0, helpText: 'HelpText' });
await dashboard.form.verifyFieldsIsEditable({ index: 0 });
await dashboard.form.verifyAfterSubmitMsg({ msg: "Thank you for submitting the form!" });
await dashboard.form.verifyAfterSubmitMsg({ msg: 'Thank you for submitting the form!' });
await dashboard.form.verifyAfterSubmitMenuState({
emailMe: false,
showBlankForm: true,
submitAnotherForm: true,
});
await dashboard.treeView.openTable({ title: "Actor" });
await dashboard.treeView.openTable({ title: 'Actor' });
if(!airtableImport){
if (!airtableImport) {
// Verify webhooks
await dashboard.grid.toolbar.clickActions();
await dashboard.grid.toolbar.actions.click("Webhooks");
await dashboard.grid.toolbar.actions.click('Webhooks');
await dashboard.webhookForm.openForm({
index: 0,
});
await dashboard.webhookForm.verifyForm(
{
title: "Webhook-1",
hookEvent: "After Insert",
notificationType: "URL",
urlMethod: "POST",
url: "http://localhost:9090/hook",
condition: false,
}
);
await dashboard.webhookForm.verifyForm({
title: 'Webhook-1',
hookEvent: 'After Insert',
notificationType: 'URL',
urlMethod: 'POST',
url: 'http://localhost:9090/hook',
condition: false,
});
await dashboard.webhookForm.goBackFromForm();
await dashboard.webhookForm.openForm({
index: 1,
});
await dashboard.webhookForm.verifyForm(
{
title: "Webhook-2",
hookEvent: "After Update",
notificationType: "URL",
urlMethod: "POST",
url: "http://localhost:9090/hook",
condition: false,
}
);
await dashboard.webhookForm.verifyForm({
title: 'Webhook-2',
hookEvent: 'After Update',
notificationType: 'URL',
urlMethod: 'POST',
url: 'http://localhost:9090/hook',
condition: false,
});
await dashboard.webhookForm.goBackFromForm();
await dashboard.webhookForm.openForm({
index: 2,
});
await dashboard.webhookForm.verifyForm(
{
title: "Webhook-3",
hookEvent: "After Delete",
notificationType: "URL",
urlMethod: "POST",
url: "http://localhost:9090/hook",
condition: false,
}
);
await dashboard.webhookForm.verifyForm({
title: 'Webhook-3',
hookEvent: 'After Delete',
notificationType: 'URL',
urlMethod: 'POST',
url: 'http://localhost:9090/hook',
condition: false,
});
await dashboard.webhookForm.close();
}
// Verify pagination
await dashboard.grid.verifyActivePage({ page: '1' });
(await dashboard.grid.clickPagination({ page: ">" }));
await dashboard.grid.clickPagination({ page: '>' });
await dashboard.grid.verifyActivePage({ page: '2' });
(await dashboard.grid.clickPagination({ page: "<" }));
await dashboard.grid.clickPagination({ page: '<' });
await dashboard.grid.verifyActivePage({ page: '1' });
await dashboard.viewSidebar.openView({ title: "Filter&Sort" });
await dashboard.viewSidebar.openView({ title: 'Filter&Sort' });
// Verify Fields, Filter & Sort
await dashboard.grid.column.verify({ title: "Name" });
await dashboard.grid.column.verify({ title: "Notes" });
await dashboard.grid.column.verify({ title: "Attachments", isVisible: false });
await dashboard.grid.column.verify({ title: "Status" });
await dashboard.grid.column.verify({ title: "Film" });
await dashboard.grid.column.verify({ title: 'Name' });
await dashboard.grid.column.verify({ title: 'Notes' });
await dashboard.grid.column.verify({ title: 'Attachments', isVisible: false });
await dashboard.grid.column.verify({ title: 'Status' });
await dashboard.grid.column.verify({ title: 'Film' });
// Verify Fields
await dashboard.grid.toolbar.clickFields();
await dashboard.grid.toolbar.fields.verify({ title: "Name", checked: true });
await dashboard.grid.toolbar.fields.verify({ title: "Notes", checked: true });
await dashboard.grid.toolbar.fields.verify({ title: "Attachments", checked: false });
await dashboard.grid.toolbar.fields.verify({ title: "Status", checked: true });
await dashboard.grid.toolbar.fields.verify({ title: "Film", checked: true });
await dashboard.grid.toolbar.fields.verify({ title: 'Name', checked: true });
await dashboard.grid.toolbar.fields.verify({ title: 'Notes', checked: true });
await dashboard.grid.toolbar.fields.verify({ title: 'Attachments', checked: false });
await dashboard.grid.toolbar.fields.verify({ title: 'Status', checked: true });
await dashboard.grid.toolbar.fields.verify({ title: 'Film', checked: true });
// Verify Sort
await dashboard.grid.toolbar.clickSort();
await dashboard.grid.toolbar.sort.verify({ index:0, column: "Name", direction: "A → Z" });
await dashboard.grid.toolbar.sort.verify({ index: 0, column: 'Name', direction: 'A → Z' });
// Verify Filter
await dashboard.grid.toolbar.clickFilter();
await dashboard.grid.toolbar.filter.verify({ index: 0, column: "Name", operator: "is like", value: "1" });
await dashboard.grid.toolbar.filter.verify({ index: 1, column: "Name", operator: "is like", value: "2" });
await dashboard.grid.toolbar.filter.verify({ index: 0, column: 'Name', operator: 'is like', value: '1' });
await dashboard.grid.toolbar.filter.verify({ index: 1, column: 'Name', operator: 'is like', value: '2' });
if(!airtableImport){
if (!airtableImport) {
// Verify views
// todo: Wait for 800ms, issue related to vue router
await dashboard.rootPage.waitForTimeout(800);
await dashboard.treeView.openTable({ title: "Producer" });
await dashboard.viewSidebar.verifyView({ index: 0, title: "Grid view" });
await dashboard.viewSidebar.verifyView({ index: 1, title: "Grid 2" });
await dashboard.viewSidebar.verifyView({ index: 2, title: "Grid 3" });
await dashboard.viewSidebar.verifyView({ index: 3, title: "Grid 4" });
await dashboard.viewSidebar.verifyView({ index: 4, title: "Form" });
await dashboard.viewSidebar.verifyView({ index: 5, title: "Form 2" });
await dashboard.viewSidebar.verifyView({ index: 6, title: "Form 3" });
await dashboard.viewSidebar.verifyView({ index: 7, title: "Form 4" });
await dashboard.viewSidebar.verifyView({ index: 8, title: "Gallery" });
await dashboard.viewSidebar.verifyView({ index: 9, title: "Gallery 2" });
await dashboard.viewSidebar.verifyView({ index: 10, title: "Gallery 3" });
await dashboard.treeView.openTable({ title: 'Producer' });
await dashboard.viewSidebar.verifyView({ index: 0, title: 'Grid view' });
await dashboard.viewSidebar.verifyView({ index: 1, title: 'Grid 2' });
await dashboard.viewSidebar.verifyView({ index: 2, title: 'Grid 3' });
await dashboard.viewSidebar.verifyView({ index: 3, title: 'Grid 4' });
await dashboard.viewSidebar.verifyView({ index: 4, title: 'Form' });
await dashboard.viewSidebar.verifyView({ index: 5, title: 'Form 2' });
await dashboard.viewSidebar.verifyView({ index: 6, title: 'Form 3' });
await dashboard.viewSidebar.verifyView({ index: 7, title: 'Form 4' });
await dashboard.viewSidebar.verifyView({ index: 8, title: 'Gallery' });
await dashboard.viewSidebar.verifyView({ index: 9, title: 'Gallery 2' });
await dashboard.viewSidebar.verifyView({ index: 10, title: 'Gallery 3' });
// verify BT relation
await dashboard.grid.cell.verifyVirtualCell({ index: 0, columnHeader: "FilmRead", value: ['Movie-1'] });
await dashboard.grid.cell.verifyVirtualCell({ index: 0, columnHeader: 'FilmRead', value: ['Movie-1'] });
}
if(airtableImport){
if (airtableImport) {
// Delete project
await dashboard.clickHome();
const projectsPage = new ProjectsPage(dashboard.rootPage);
await projectsPage.deleteProject({ title: context.project.title, withoutPrefix: true });
}
}
};
export { quickVerify };
export { quickVerify };

31
scripts/playwright/quickTests/quickTests.spec.ts

@ -1,30 +1,29 @@
import { expect, test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { LoginPage } from '../pages/LoginPage';
import { ProjectsPage } from '../pages/ProjectsPage';
import { quickVerify } from '../quickTests/commonTest';
import { NcContext } from '../setup';
import { expect, test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { LoginPage } from "../pages/LoginPage";
import { ProjectsPage } from "../pages/ProjectsPage";
import { quickVerify } from "../quickTests/commonTest";
import { NcContext } from "../setup";
test.describe("Quick tests", () => {
test.describe('Quick tests', () => {
let dashboard: DashboardPage;
test("Quick tests test", async ({page}) => {
test('Quick tests test', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.fillEmail({email: "user@nocodb.com", withoutPrefix: true});
await loginPage.fillPassword("Password123.");
await loginPage.fillEmail({ email: 'user@nocodb.com', withoutPrefix: true });
await loginPage.fillPassword('Password123.');
await loginPage.submit();
const projectsPage = new ProjectsPage(page);
const project = await projectsPage.openProject({title: "sample", withoutPrefix: true});
const project = await projectsPage.openProject({ title: 'sample', withoutPrefix: true });
dashboard = new DashboardPage(page, project);
const context: NcContext = {
project,
token: '',
dbType: (process.env.CI ? process.env.E2E_DB_TYPE : process.env.E2E_DEV_DB_TYPE) || 'mysql'
}
await quickVerify({dashboard, context});
dbType: (process.env.CI ? process.env.E2E_DB_TYPE : process.env.E2E_DEV_DB_TYPE) || 'mysql',
};
await quickVerify({ dashboard, context });
});
});

23
scripts/playwright/setup/db.ts

@ -1,6 +1,6 @@
import { NcContext } from ".";
import { NcContext } from '.';
import axios from "axios";
import axios from 'axios';
const isMysql = (context: NcContext) => context.dbType === 'mysql';
@ -8,14 +8,15 @@ const isSqlite = (context: NcContext) => context.dbType === 'sqlite';
const isPg = (context: NcContext) => context.dbType === 'pg';
const mysql = require("mysql2");
const mysqlExec = async (query) => {
const mysql = require('mysql2');
// eslint-disable-next-line require-await
const mysqlExec = async query => {
// creates a new mysql connection using credentials from cypress.json env's
const connection = mysql.createConnection({
"host": "localhost",
"user": "root",
"password": "password",
"database": `test_sakila_${process.env.TEST_PARALLEL_INDEX}`
host: 'localhost',
user: 'root',
password: 'password',
database: `test_sakila_${process.env.TEST_PARALLEL_INDEX}`,
});
// start connection to db
connection.connect();
@ -30,12 +31,12 @@ const mysqlExec = async (query) => {
}
});
});
}
};
async function sqliteExec(query) {
try {
await axios.post('http://localhost:8080/api/v1/meta/test/sqlite_exec', {
"sql": query,
sql: query,
});
} catch (e) {
console.error(e);
@ -43,4 +44,4 @@ async function sqliteExec(query) {
}
}
export { sqliteExec, mysqlExec, isMysql, isSqlite, isPg };
export { sqliteExec, mysqlExec, isMysql, isSqlite, isPg };

48
scripts/playwright/setup/index.ts

@ -7,45 +7,51 @@ export interface NcContext {
dbType?: string;
}
const setup = async ({page, isEmptyProject}: {page: Page, isEmptyProject?: boolean}): Promise<NcContext> => {
const setup = async ({ page, isEmptyProject }: { page: Page; isEmptyProject?: boolean }): Promise<NcContext> => {
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`, {
const response = await axios.post(`http://localhost:8080/api/v1/meta/test/reset`, {
parallelId: process.env.TEST_PARALLEL_INDEX,
dbType,
isEmptyProject
isEmptyProject,
});
if(response.status !== 200) {
if (response.status !== 200) {
console.error('Failed to reset test data', response.data);
throw new Error('Failed to reset test data');
}
const token = response.data.token;
await page.addInitScript(async ({token}) => {
try {
let initialLocalStorage = {};
await page.addInitScript(
// eslint-disable-next-line require-await
async ({ token }) => {
try {
initialLocalStorage = JSON.parse(localStorage.getItem('nocodb-gui-v2') || '{}');
} catch(e) {
console.error('Failed to parse local storage', e);
let initialLocalStorage = {};
try {
initialLocalStorage = JSON.parse(localStorage.getItem('nocodb-gui-v2') || '{}');
} catch (e) {
console.error('Failed to parse local storage', e);
}
window.localStorage.setItem(
'nocodb-gui-v2',
JSON.stringify({
...initialLocalStorage,
token: token,
})
);
} catch (e) {
window.console.log(e);
}
window.localStorage.setItem('nocodb-gui-v2', JSON.stringify({
...initialLocalStorage,
token: token,
}));
} catch (e) {
window.console.log(e);
}
}, { token: token });
},
{ token: token }
);
const project = response.data.project;
await page.goto(`/#/nc/${project.id}/auth`);
return { project, token, dbType } as NcContext;
}
};
export default setup;
export default setup;

25
scripts/playwright/setup/mysqlExec.ts

@ -1,25 +0,0 @@
const mysql = require("mysql2");
const mysqlExec = async (query) => {
// creates a new mysql connection using credentials from cypress.json env's
const connection = mysql.createConnection({
"host": "127.0.0.1",
"user": "root",
"password": "password"
});
// start connection to db
connection.connect();
// exec query + disconnect to db as a Promise
return new Promise((resolve, reject) => {
connection.query(query, (error, results) => {
if (error) reject(error);
else {
connection.end();
// console.log(results)
return resolve(results);
}
});
});
}
export default mysqlExec;

21
scripts/playwright/setup/server.ts

@ -3,54 +3,55 @@
// const { express } = require("express");
// const { bodyParser } = require("body-parser");
import express from "express";
import bodyParser from "body-parser";
import express from 'express';
import bodyParser from 'body-parser';
let request = [];
// eslint-disable-next-line require-await
async function makeServer() {
const app = express();
app.use(bodyParser.json());
app.get("/hook/all", (req, res) => {
app.get('/hook/all', (req, res) => {
// console.log(request)
res.json(request);
});
app.get("/hook/last", (req, res) => {
app.get('/hook/last', (req, res) => {
if (request.length) {
// console.log(request[request.length - 1])
res.json(request[request.length - 1]);
}
});
app.get("/hook/count", (req, res) => {
app.get('/hook/count', (req, res) => {
// console.log(request.length)
res.json(request.length);
});
app.get("/hook/clear", (req, res) => {
app.get('/hook/clear', (req, res) => {
request = [];
res.status(200).end();
});
app.post("/hook", (req, res) => {
app.post('/hook', (req, res) => {
request.push(req.body);
// console.log("/hook :: ", req.body) // Call your action on the request here
res.status(200).end(); // Responding is important
});
app.post("/stop", (req, res) => {
app.post('/stop', (req, res) => {
process.exit();
});
const port = 9090;
return new Promise((resolve) => {
return new Promise(resolve => {
const server = app.listen(port, function () {
const port = server.address().port;
// console.log("Example app listening at port %d", port);
// close the server
const close = () => {
return new Promise((resolve) => {
return new Promise(resolve => {
// console.log("closing server");
server.close(resolve);
});

8
scripts/playwright/setup/sqliteExec.ts

@ -1,11 +1,11 @@
const { PromisedDatabase } = require("promised-sqlite3");
const sqliteDb = new PromisedDatabase();
const { PromisedDatabase } = require('promised-sqlite3');
const sqliteDb = new PromisedDatabase();
async function sqliteExec(query) {
const rootProjectDir = __dirname.replace("/scripts/playwright/setup", "");
const rootProjectDir = __dirname.replace('/scripts/playwright/setup', '');
await sqliteDb.open(`${rootProjectDir}/packages/nocodb/test_noco.db`);
await sqliteDb.run(query);
}
export default sqliteExec;
export default sqliteExec;

45
scripts/playwright/tests-examples/demo-todo-app.spec.ts

@ -1,14 +1,10 @@
import { test, expect, type Page } from '@playwright/test';
import { expect, type Page, test } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('https://demo.playwright.dev/todomvc');
});
const TODO_ITEMS = [
'buy some cheese',
'feed the cat',
'book a doctors appointment'
];
const TODO_ITEMS = ['buy some cheese', 'feed the cat', 'book a doctors appointment'];
test.describe('New Todo', () => {
test('should allow me to add todo items', async ({ page }) => {
@ -17,19 +13,14 @@ test.describe('New Todo', () => {
await page.locator('.new-todo').press('Enter');
// Make sure the list only has one todo item.
await expect(page.locator('.view label')).toHaveText([
TODO_ITEMS[0]
]);
await expect(page.locator('.view label')).toHaveText([TODO_ITEMS[0]]);
// Create 2nd todo.
await page.locator('.new-todo').fill(TODO_ITEMS[1]);
await page.locator('.new-todo').press('Enter');
// Make sure the list now has two todo items.
await expect(page.locator('.view label')).toHaveText([
TODO_ITEMS[0],
TODO_ITEMS[1]
]);
await expect(page.locator('.view label')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
await checkNumberOfTodosInLocalStorage(page, 2);
});
@ -118,7 +109,6 @@ test.describe('Mark all as completed', () => {
});
test.describe('Item', () => {
test('should allow me to mark items as complete', async ({ page }) => {
// Create two items.
for (const item of TODO_ITEMS.slice(0, 2)) {
@ -172,11 +162,7 @@ test.describe('Item', () => {
await secondTodo.locator('.edit').press('Enter');
// Explicitly assert the new text value.
await expect(todoItems).toHaveText([
TODO_ITEMS[0],
'buy some sausages',
TODO_ITEMS[2]
]);
await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]);
await checkTodosInLocalStorage(page, 'buy some sausages');
});
});
@ -201,11 +187,7 @@ test.describe('Editing', () => {
await todoItems.nth(1).locator('.edit').fill('buy some sausages');
await todoItems.nth(1).locator('.edit').dispatchEvent('blur');
await expect(todoItems).toHaveText([
TODO_ITEMS[0],
'buy some sausages',
TODO_ITEMS[2],
]);
await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]);
await checkTodosInLocalStorage(page, 'buy some sausages');
});
@ -215,11 +197,7 @@ test.describe('Editing', () => {
await todoItems.nth(1).locator('.edit').fill(' buy some sausages ');
await todoItems.nth(1).locator('.edit').press('Enter');
await expect(todoItems).toHaveText([
TODO_ITEMS[0],
'buy some sausages',
TODO_ITEMS[2],
]);
await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]);
await checkTodosInLocalStorage(page, 'buy some sausages');
});
@ -229,10 +207,7 @@ test.describe('Editing', () => {
await todoItems.nth(1).locator('.edit').fill('');
await todoItems.nth(1).locator('.edit').press('Enter');
await expect(todoItems).toHaveText([
TODO_ITEMS[0],
TODO_ITEMS[2],
]);
await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
});
test('should cancel edits on escape', async ({ page }) => {
@ -393,6 +368,8 @@ async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: n
async function checkTodosInLocalStorage(page: Page, title: string) {
return await page.waitForFunction(t => {
return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t);
return JSON.parse(localStorage['react-todos'])
.map((todo: any) => todo.title)
.includes(t);
}, title);
}

190
scripts/playwright/tests/01-webhook.spec.ts

@ -1,19 +1,19 @@
import { expect, test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import makeServer from "../setup/server";
import { WebhookFormPage } from "../pages/Dashboard/WebhookForm";
import { expect, test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
import makeServer from '../setup/server';
import { WebhookFormPage } from '../pages/Dashboard/WebhookForm';
let hookPath = "http://localhost:9090/hook";
const hookPath = 'http://localhost:9090/hook';
// clear server data
async function clearServerData({ request }) {
// clear stored data in server
await request.get(hookPath + "/clear");
await request.get(hookPath + '/clear');
// ensure stored message count is 0
const response = await request.get(hookPath + "/count");
const response = await request.get(hookPath + '/count');
await expect(await response.json()).toBe(0);
}
@ -21,28 +21,28 @@ async function verifyHookTrigger(count: number, value: string, request) {
// Retry since there can be lag between the time the hook is triggered and the time the server receives the request
let response;
for (let i = 0; i < 20; i++) {
response = await request.get(hookPath + "/count");
response = await request.get(hookPath + '/count');
if ((await response.json()) === count) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 150));
await new Promise(resolve => setTimeout(resolve, 150));
}
await expect(await response.json()).toBe(count);
if (count) {
let response;
for (let i = 0; i < 20; i++) {
response = await request.get(hookPath + "/last");
if (((await response.json()).Title) === value) {
response = await request.get(hookPath + '/last');
if ((await response.json()).Title === value) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 150));
await new Promise(resolve => setTimeout(resolve, 150));
}
await expect((await response.json()).Title).toBe(value);
}
}
test.describe.serial("Webhook", async () => {
test.describe.serial('Webhook', () => {
// start a server locally for webhook tests
let dashboard: DashboardPage, toolbar: ToolbarPage, webhook: WebhookFormPage;
@ -59,79 +59,79 @@ test.describe.serial("Webhook", async () => {
webhook = dashboard.webhookForm;
});
test("CRUD", async ({ request, page }) => {
test('CRUD', async ({ request, page }) => {
// todo: Waiting for the server to start
await page.waitForTimeout(1000);
// close 'Team & Auth' tab
await clearServerData({ request });
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.createTable({ title: "Test" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.createTable({ title: 'Test' });
// after insert hook
await webhook.create({
title: "hook-1",
event: "After Insert",
title: 'hook-1',
event: 'After Insert',
});
await clearServerData({ request });
await dashboard.grid.addNewRow({
index: 0,
columnHeader: "Title",
value: "Poole",
columnHeader: 'Title',
value: 'Poole',
});
await verifyHookTrigger(1, "Poole", request);
await dashboard.grid.editRow({ index: 0, value: "Delaware" });
await verifyHookTrigger(1, "Poole", request);
await verifyHookTrigger(1, 'Poole', request);
await dashboard.grid.editRow({ index: 0, value: 'Delaware' });
await verifyHookTrigger(1, 'Poole', request);
await dashboard.grid.deleteRow(0);
await verifyHookTrigger(1, "Poole", request);
await verifyHookTrigger(1, 'Poole', request);
// after update hook
await webhook.create({
title: "hook-2",
event: "After Update",
title: 'hook-2',
event: 'After Update',
});
await clearServerData({ request });
await dashboard.grid.addNewRow({
index: 0,
columnHeader: "Title",
value: "Poole",
columnHeader: 'Title',
value: 'Poole',
});
await verifyHookTrigger(1, "Poole", request);
await dashboard.grid.editRow({ index: 0, value: "Delaware" });
await verifyHookTrigger(2, "Delaware", request);
await verifyHookTrigger(1, 'Poole', request);
await dashboard.grid.editRow({ index: 0, value: 'Delaware' });
await verifyHookTrigger(2, 'Delaware', request);
await dashboard.grid.deleteRow(0);
await verifyHookTrigger(2, "Delaware", request);
await verifyHookTrigger(2, 'Delaware', request);
// after delete hook
await webhook.create({
title: "hook-3",
event: "After Delete",
title: 'hook-3',
event: 'After Delete',
});
await clearServerData({ request });
await dashboard.grid.addNewRow({
index: 0,
columnHeader: "Title",
value: "Poole",
columnHeader: 'Title',
value: 'Poole',
});
await verifyHookTrigger(1, "Poole", request);
await dashboard.grid.editRow({ index: 0, value: "Delaware" });
await verifyHookTrigger(2, "Delaware", request);
await verifyHookTrigger(1, 'Poole', request);
await dashboard.grid.editRow({ index: 0, value: 'Delaware' });
await verifyHookTrigger(2, 'Delaware', request);
await dashboard.grid.deleteRow(0);
await verifyHookTrigger(3, "Delaware", request);
await verifyHookTrigger(3, 'Delaware', request);
// modify webhook
await webhook.open({ index: 0 });
await webhook.configureWebhook({
title: "hook-1-modified",
event: "After Delete",
title: 'hook-1-modified',
event: 'After Delete',
});
await webhook.save();
await webhook.close();
await webhook.open({ index: 1 });
await webhook.configureWebhook({
title: "hook-2-modified",
event: "After Delete",
title: 'hook-2-modified',
event: 'After Delete',
});
await webhook.save();
await webhook.close();
@ -139,14 +139,14 @@ test.describe.serial("Webhook", async () => {
await clearServerData({ request });
await dashboard.grid.addNewRow({
index: 0,
columnHeader: "Title",
value: "Poole",
columnHeader: 'Title',
value: 'Poole',
});
await verifyHookTrigger(0, "Poole", request);
await dashboard.grid.editRow({ index: 0, value: "Delaware" });
await verifyHookTrigger(0, "Delaware", request);
await verifyHookTrigger(0, 'Poole', request);
await dashboard.grid.editRow({ index: 0, value: 'Delaware' });
await verifyHookTrigger(0, 'Delaware', request);
await dashboard.grid.deleteRow(0);
await verifyHookTrigger(3, "Delaware", request);
await verifyHookTrigger(3, 'Delaware', request);
// delete webhook
await webhook.delete({ index: 0 });
@ -156,61 +156,61 @@ test.describe.serial("Webhook", async () => {
await clearServerData({ request });
await dashboard.grid.addNewRow({
index: 0,
columnHeader: "Title",
value: "Poole",
columnHeader: 'Title',
value: 'Poole',
});
await verifyHookTrigger(0, "", request);
await dashboard.grid.editRow({ index: 0, value: "Delaware" });
await verifyHookTrigger(0, "", request);
await verifyHookTrigger(0, '', request);
await dashboard.grid.editRow({ index: 0, value: 'Delaware' });
await verifyHookTrigger(0, '', request);
await dashboard.grid.deleteRow(0);
await verifyHookTrigger(0, "", request);
await verifyHookTrigger(0, '', request);
});
test("webhook Conditional webhooks", async ({ request }) => {
test.slow();
test('webhook Conditional webhooks', async ({ request }) => {
test.slow();
await clearServerData({ request });
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.createTable({ title: "Test" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.createTable({ title: 'Test' });
// after insert hook
await webhook.create({
title: "hook-1",
event: "After Insert",
title: 'hook-1',
event: 'After Insert',
});
// after insert hook
await webhook.create({
title: "hook-2",
event: "After Update",
title: 'hook-2',
event: 'After Update',
});
// after insert hook
await webhook.create({
title: "hook-3",
event: "After Delete",
title: 'hook-3',
event: 'After Delete',
});
await webhook.open({ index: 0 });
await webhook.addCondition({
column: "Title",
operator: "is like",
value: "Poole",
column: 'Title',
operator: 'is like',
value: 'Poole',
save: true,
});
await webhook.open({ index: 1 });
await webhook.addCondition({
column: "Title",
operator: "is like",
value: "Poole",
column: 'Title',
operator: 'is like',
value: 'Poole',
save: true,
});
await webhook.open({ index: 2 });
await webhook.addCondition({
column: "Title",
operator: "is like",
value: "Poole",
column: 'Title',
operator: 'is like',
value: 'Poole',
save: true,
});
@ -218,21 +218,21 @@ test.describe.serial("Webhook", async () => {
await clearServerData({ request });
await dashboard.grid.addNewRow({
index: 0,
columnHeader: "Title",
value: "Poole",
columnHeader: 'Title',
value: 'Poole',
});
await dashboard.grid.addNewRow({
index: 1,
columnHeader: "Title",
value: "Delaware",
columnHeader: 'Title',
value: 'Delaware',
});
await verifyHookTrigger(1, "Poole", request);
await dashboard.grid.editRow({ index: 0, value: "Delaware" });
await dashboard.grid.editRow({ index: 1, value: "Poole" });
await verifyHookTrigger(2, "Poole", request);
await verifyHookTrigger(1, 'Poole', request);
await dashboard.grid.editRow({ index: 0, value: 'Delaware' });
await dashboard.grid.editRow({ index: 1, value: 'Poole' });
await verifyHookTrigger(2, 'Poole', request);
await dashboard.grid.deleteRow(1);
await dashboard.grid.deleteRow(0);
await verifyHookTrigger(3, "Poole", request);
await verifyHookTrigger(3, 'Poole', request);
// Delete condition
await webhook.open({ index: 2 });
@ -245,20 +245,20 @@ test.describe.serial("Webhook", async () => {
await clearServerData({ request });
await dashboard.grid.addNewRow({
index: 0,
columnHeader: "Title",
value: "Poole",
columnHeader: 'Title',
value: 'Poole',
});
await dashboard.grid.addNewRow({
index: 1,
columnHeader: "Title",
value: "Delaware",
columnHeader: 'Title',
value: 'Delaware',
});
await verifyHookTrigger(2, "Delaware", request);
await dashboard.grid.editRow({ index: 0, value: "Delaware" });
await dashboard.grid.editRow({ index: 1, value: "Poole" });
await verifyHookTrigger(4, "Poole", request);
await verifyHookTrigger(2, 'Delaware', request);
await dashboard.grid.editRow({ index: 0, value: 'Delaware' });
await dashboard.grid.editRow({ index: 1, value: 'Poole' });
await verifyHookTrigger(4, 'Poole', request);
await dashboard.grid.deleteRow(1);
await dashboard.grid.deleteRow(0);
await verifyHookTrigger(6, "Delaware", request);
await verifyHookTrigger(6, 'Delaware', request);
});
});

66
scripts/playwright/tests/authChangePassword.spec.ts

@ -1,12 +1,12 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import { LoginPage } from "../pages/LoginPage";
import { SettingsPage, SettingTab } from "../pages/Dashboard/Settings";
import { SignupPage } from "../pages/SignupPage";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
import { LoginPage } from '../pages/LoginPage';
import { SettingsPage, SettingTab } from '../pages/Dashboard/Settings';
import { SignupPage } from '../pages/SignupPage';
test.describe("Auth", () => {
test.describe('Auth', () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let settings: SettingsPage;
@ -21,13 +21,13 @@ test.describe("Auth", () => {
settings = dashboard.settings;
});
test("Change password", async ({ page }) => {
await dashboard.closeTab({ title: "Team & Auth" });
test('Change password', async ({ page }) => {
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.gotoSettings();
await settings.selectTab({ tab: SettingTab.TeamAuth });
let url = await settings.teams.invite({
email: "user-1@nocodb.com",
role: "creator",
const url = await settings.teams.invite({
email: 'user-1@nocodb.com',
role: 'creator',
});
await settings.teams.closeInvite();
await settings.close();
@ -36,50 +36,42 @@ test.describe("Auth", () => {
await dashboard.rootPage.goto(url);
await signupPage.signUp({
email: "user-1@nocodb.com",
password: "Password123.",
email: 'user-1@nocodb.com',
password: 'Password123.',
});
await dashboard.openPasswordChangeModal();
// Existing active pass incorrect
await dashboard.changePassword({
oldPass: "123456789",
newPass: "123456789",
repeatPass: "123456789",
oldPass: '123456789',
newPass: '123456789',
repeatPass: '123456789',
});
await dashboard.rootPage
.locator(
'[data-cy="nc-user-settings-form__error"]:has-text("Current password is wrong")'
)
.locator('[data-cy="nc-user-settings-form__error"]:has-text("Current password is wrong")')
.waitFor();
// New pass and repeat pass mismatch
await dashboard.changePassword({
oldPass: "Password123.",
newPass: "123456789",
repeatPass: "987654321",
oldPass: 'Password123.',
newPass: '123456789',
repeatPass: '987654321',
});
await dashboard.rootPage
.locator(
'.ant-form-item-explain-error:has-text("Passwords do not match")'
)
.waitFor();
await dashboard.rootPage.locator('.ant-form-item-explain-error:has-text("Passwords do not match")').waitFor();
// All good
await dashboard.changePassword({
oldPass: "Password123.",
newPass: "NewPasswordConfigured",
repeatPass: "NewPasswordConfigured",
oldPass: 'Password123.',
newPass: 'NewPasswordConfigured',
repeatPass: 'NewPasswordConfigured',
});
const loginPage = new LoginPage(page);
await loginPage.fillEmail({email: "user-1@nocodb.com"});
await loginPage.fillPassword("NewPasswordConfigured");
await loginPage.fillEmail({ email: 'user-1@nocodb.com' });
await loginPage.fillPassword('NewPasswordConfigured');
await loginPage.submit();
await page
.locator('.nc-project-page-title:has-text("My Projects")')
.waitFor();
await page.locator('.nc-project-page-title:has-text("My Projects")').waitFor();
});
});

40
scripts/playwright/tests/baseShare.spec.ts

@ -1,11 +1,11 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import { LoginPage } from "../pages/LoginPage";
import { ProjectsPage } from "../pages/ProjectsPage";
test.describe("Shared base", () => {
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
import { LoginPage } from '../pages/LoginPage';
import { ProjectsPage } from '../pages/ProjectsPage';
test.describe('Shared base', () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any;
@ -18,10 +18,10 @@ test.describe("Shared base", () => {
await dashboard.validateProjectMenu({
role: role.toLowerCase(),
mode: "shareBase",
mode: 'shareBase',
});
await dashboard.treeView.openTable({ title: "Country", mode: "shareBase" });
await dashboard.treeView.openTable({ title: 'Country', mode: 'shareBase' });
await dashboard.viewSidebar.validateRoleAccess({
role: role.toLowerCase(),
@ -29,7 +29,7 @@ test.describe("Shared base", () => {
await toolbar.validateRoleAccess({
role: role.toLowerCase(),
mode: "shareBase",
mode: 'shareBase',
});
await dashboard.treeView.validateRoleAccess({
@ -54,13 +54,13 @@ test.describe("Shared base", () => {
loginPage = new LoginPage(page);
});
test("#1", async () => {
test('#1', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.inviteTeamButton.click();
await dashboard.settings.teams.toggleSharedBase({ toggle: true });
await dashboard.settings.teams.sharedBaseRole({ role: "editor" });
await dashboard.settings.teams.sharedBaseRole({ role: 'editor' });
let url = await dashboard.settings.teams.getSharedBaseUrl();
await dashboard.settings.teams.closeInvite();
@ -70,19 +70,19 @@ test.describe("Shared base", () => {
// todo: Move this to a page object
await dashboard.rootPage.goto(url);
await roleTest("editor");
await roleTest('editor');
await loginPage.signIn({
email: "user@nocodb.com",
password: "Password123.",
email: 'user@nocodb.com',
password: 'Password123.',
withoutPrefix: true,
});
await projectPage.openProject({ title: 'externalREST' });
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.inviteTeamButton.click();
await dashboard.settings.teams.toggleSharedBase({ toggle: true });
await dashboard.settings.teams.sharedBaseRole({ role: "viewer" });
await dashboard.settings.teams.sharedBaseRole({ role: 'viewer' });
url = await dashboard.settings.teams.getSharedBaseUrl();
await dashboard.settings.teams.closeInvite();
@ -92,6 +92,6 @@ test.describe("Shared base", () => {
// todo: Move this to a page object
await dashboard.rootPage.goto(url);
await roleTest("viewer");
await roleTest('viewer');
});
});

56
scripts/playwright/tests/columnAttachments.spec.ts

@ -1,9 +1,9 @@
import { expect, test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { SharedFormPage } from "../pages/SharedForm";
import setup from "../setup";
import { expect, test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { SharedFormPage } from '../pages/SharedForm';
import setup from '../setup';
test.describe("Attachment column", () => {
test.describe('Attachment column', () => {
let dashboard: DashboardPage;
let context: any;
@ -12,36 +12,33 @@ test.describe("Attachment column", () => {
dashboard = new DashboardPage(page, context.project);
});
test("Create and verify atttachent column, verify it in shared form,", async ({
page,
context,
}) => {
await dashboard.treeView.openTable({ title: "Country" });
test('Create and verify atttachent column, verify it in shared form,', async ({ page, context }) => {
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.column.create({
title: "testAttach",
type: "Attachment",
title: 'testAttach',
type: 'Attachment',
});
for (let i = 4; i <= 6; i++) {
const filepath = `${process.cwd()}/fixtures/sampleFiles/${i}.json`;
await dashboard.grid.cell.attachment.addFile({
index: i,
columnHeader: "testAttach",
columnHeader: 'testAttach',
filePath: filepath,
});
await dashboard.grid.cell.attachment.verifyFile({
index: i,
columnHeader: "testAttach",
columnHeader: 'testAttach',
});
}
await dashboard.viewSidebar.createFormView({
title: "Form 1",
title: 'Form 1',
});
await dashboard.form.toolbar.clickShareView();
const sharedFormUrl = await dashboard.form.toolbar.shareView.getShareLink();
await dashboard.form.toolbar.shareView.close();
await dashboard.viewSidebar.openView({ title: "Country" });
await dashboard.viewSidebar.openView({ title: 'Country' });
// Verify attachment in shared form
const newPage = await context.newPage();
@ -49,11 +46,11 @@ test.describe("Attachment column", () => {
const sharedForm = new SharedFormPage(newPage);
await sharedForm.cell.fillText({
index: 0,
columnHeader: "Country",
text: "test",
columnHeader: 'Country',
text: 'test',
});
await sharedForm.cell.attachment.addFile({
columnHeader: "testAttach",
columnHeader: 'testAttach',
filePath: `${process.cwd()}/fixtures/sampleFiles/1.json`,
});
await sharedForm.submit();
@ -62,29 +59,26 @@ test.describe("Attachment column", () => {
// Verify attachment in csv
await dashboard.grid.toolbar.clickFields();
await dashboard.grid.toolbar.fields.click({ title: "LastUpdate" });
await dashboard.grid.toolbar.fields.click({ title: 'LastUpdate' });
await dashboard.grid.toolbar.clickActions();
// Headless mode observation- menu doesn't render in one go
// Download button appears before menu is fully rendered
await dashboard.rootPage.waitForTimeout(500);
await dashboard.grid.toolbar.actions.click("Download");
await dashboard.grid.toolbar.actions.click('Download');
const csvFileData: string = await dashboard.downloadAndGetFile({
downloadUIAction:
dashboard.grid.toolbar.actions.clickDownloadSubmenu("Download as CSV"),
downloadUIAction: dashboard.grid.toolbar.actions.clickDownloadSubmenu('Download as CSV'),
});
const csvArray = csvFileData.split("\r\n");
const csvArray = csvFileData.split('\r\n');
const columns = csvArray[0];
const rows = csvArray.slice(1);
const cells = rows[4].split(",");
const cells = rows[4].split(',');
await expect(columns).toBe("Country,City List,testAttach");
await expect(cells[0]).toBe("Anguilla");
await expect(cells[1]).toBe("South Hill");
await expect(
cells[2].includes("4.json(http://localhost:8080/download/")
).toBe(true);
await expect(columns).toBe('Country,City List,testAttach');
await expect(cells[0]).toBe('Anguilla');
await expect(cells[1]).toBe('South Hill');
await expect(cells[2].includes('4.json(http://localhost:8080/download/')).toBe(true);
});
});

128
scripts/playwright/tests/columnDuration.spec.ts

@ -1,106 +1,46 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
// Storing one additional dummy value "10" at end of every input array
// this will trigger update to previously committed data
const durationData = [
{
format: "h:mm (e.g. 1:23)",
input: ["1:30", "30", "60", "80", "12:34", "15:130", "123123", "10"],
output: ["01:30", "00:30", "01:00", "01:20", "12:34", "17:10", "2052:03"],
format: 'h:mm (e.g. 1:23)',
input: ['1:30', '30', '60', '80', '12:34', '15:130', '123123', '10'],
output: ['01:30', '00:30', '01:00', '01:20', '12:34', '17:10', '2052:03'],
},
{
format: "h:mm:ss (e.g. 3:45, 1:23:40)",
input: [
"11:22:33",
"1234",
"50",
"1:1111",
"1:11:1111",
"15:130",
"123123",
"10",
],
output: [
"11:22:33",
"00:20:34",
"00:00:50",
"00:19:31",
"01:29:31",
"00:17:10",
"34:12:03",
],
format: 'h:mm:ss (e.g. 3:45, 1:23:40)',
input: ['11:22:33', '1234', '50', '1:1111', '1:11:1111', '15:130', '123123', '10'],
output: ['11:22:33', '00:20:34', '00:00:50', '00:19:31', '01:29:31', '00:17:10', '34:12:03'],
},
{
format: "h:mm:ss.s (e.g. 3:34.6, 1:23:40.0)",
input: [
"1234",
"12:34",
"12:34:56",
"12:34:999",
"12:999:56",
"12:34:56.12",
"12:34:56.199",
"10",
],
output: [
"00:20:34.0",
"00:12:34.0",
"12:34:56.0",
"12:50:39.0",
"28:39:56.0",
"12:34:56.1",
"12:34:56.2",
],
format: 'h:mm:ss.s (e.g. 3:34.6, 1:23:40.0)',
input: ['1234', '12:34', '12:34:56', '12:34:999', '12:999:56', '12:34:56.12', '12:34:56.199', '10'],
output: ['00:20:34.0', '00:12:34.0', '12:34:56.0', '12:50:39.0', '28:39:56.0', '12:34:56.1', '12:34:56.2'],
},
{
format: "h:mm:ss.ss (e.g. 3.45.67, 1:23:40.00)",
input: [
"1234",
"12:34",
"12:34:56",
"12:34:999",
"12:999:56",
"12:34:56.12",
"12:34:56.199",
"10",
],
output: [
"00:20:34.00",
"00:12:34.00",
"12:34:56.00",
"12:50:39.00",
"28:39:56.00",
"12:34:56.12",
"12:34:56.20",
],
format: 'h:mm:ss.ss (e.g. 3.45.67, 1:23:40.00)',
input: ['1234', '12:34', '12:34:56', '12:34:999', '12:999:56', '12:34:56.12', '12:34:56.199', '10'],
output: ['00:20:34.00', '00:12:34.00', '12:34:56.00', '12:50:39.00', '28:39:56.00', '12:34:56.12', '12:34:56.20'],
},
{
format: "h:mm:ss.sss (e.g. 3.45.678, 1:23:40.000)",
input: [
"1234",
"12:34",
"12:34:56",
"12:34:999",
"12:999:56",
"12:34:56.12",
"12:34:56.199",
"10",
],
format: 'h:mm:ss.sss (e.g. 3.45.678, 1:23:40.000)',
input: ['1234', '12:34', '12:34:56', '12:34:999', '12:999:56', '12:34:56.12', '12:34:56.199', '10'],
output: [
"00:20:34.000",
"00:12:34.000",
"12:34:56.000",
"12:50:39.000",
"28:39:56.000",
"12:34:56.012",
"12:34:56.199",
'00:20:34.000',
'00:12:34.000',
'12:34:56.000',
'12:50:39.000',
'28:39:56.000',
'12:34:56.012',
'12:34:56.199',
],
},
];
test.describe.skip("Duration column", () => {
test.describe.skip('Duration column', () => {
let dashboard: DashboardPage;
let context: any;
@ -109,18 +49,18 @@ test.describe.skip("Duration column", () => {
dashboard = new DashboardPage(page, context.project);
});
test("Create duration column", async () => {
await dashboard.treeView.createTable({ title: "tablex" });
test('Create duration column', async () => {
await dashboard.treeView.createTable({ title: 'tablex' });
// Create duration column
await dashboard.grid.column.create({
title: "NC_DURATION_0",
type: "Duration",
title: 'NC_DURATION_0',
type: 'Duration',
format: durationData[0].format,
});
for (let i = 0; i < 8; i++) {
await dashboard.grid.addNewRow({
index: i,
columnHeader: "NC_DURATION_0",
columnHeader: 'NC_DURATION_0',
value: i.toString(),
networkValidation: false,
});
@ -129,15 +69,15 @@ test.describe.skip("Duration column", () => {
for (let i = 0; i < durationData.length; i++) {
// Edit duration column
await dashboard.grid.column.openEdit({
title: "NC_DURATION_0",
type: "Duration",
title: 'NC_DURATION_0',
type: 'Duration',
format: durationData[i].format,
});
await dashboard.grid.column.save({ isUpdated: true });
for (let j = 0; j < durationData[i].input.length; j++) {
await dashboard.grid.editRow({
index: j,
columnHeader: "NC_DURATION_0",
columnHeader: 'NC_DURATION_0',
value: durationData[i].input[j],
networkValidation: false,
});
@ -145,7 +85,7 @@ test.describe.skip("Duration column", () => {
for (let j = 0; j < durationData[i].output.length; j++) {
await dashboard.grid.cell.verify({
index: j,
columnHeader: "NC_DURATION_0",
columnHeader: 'NC_DURATION_0',
value: durationData[i].output[j],
});
}

69
scripts/playwright/tests/columnFormula.spec.ts

@ -1,6 +1,6 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
// Add formula to be verified here & store expected results for 5 rows
// Column data from City table (Sakila DB)
@ -14,53 +14,46 @@ import setup from "../setup";
*/
const formulaData = [
{
formula: "1 + 1",
result: ["2", "2", "2", "2", "2"],
formula: '1 + 1',
result: ['2', '2', '2', '2', '2'],
},
{
formula:
"ADD({CityId}, {CountryId}) + AVG({CityId}, {CountryId}) + LEN({City})",
result: ["150", "130", "165", "100", "158"],
formula: 'ADD({CityId}, {CountryId}) + AVG({CityId}, {CountryId}) + LEN({City})',
result: ['150', '130', '165', '100', '158'],
},
{
formula: `WEEKDAY("2022-07-19")`,
result: ["1", "1", "1", "1", "1"],
result: ['1', '1', '1', '1', '1'],
},
{
formula: `WEEKDAY("2022-07-19", "sunday")`,
result: ["2", "2", "2", "2", "2"],
result: ['2', '2', '2', '2', '2'],
},
{
formula: `CONCAT(UPPER({City}), LOWER({City}), TRIM(' trimmed '))`,
result: [
"A CORUA (LA CORUA)a corua (la corua)trimmed",
"ABHAabhatrimmed",
"ABU DHABIabu dhabitrimmed",
"ACUAacuatrimmed",
"ADANAadanatrimmed",
'A CORUA (LA CORUA)a corua (la corua)trimmed',
'ABHAabhatrimmed',
'ABU DHABIabu dhabitrimmed',
'ACUAacuatrimmed',
'ADANAadanatrimmed',
],
},
{
formula: `CEILING(1.4) + FLOOR(1.6) + ROUND(2.5) + MOD({CityId}, 3) + MIN({CityId}, {CountryId}) + MAX({CityId}, {CountryId})`,
result: ["95", "92", "110", "71", "110"],
result: ['95', '92', '110', '71', '110'],
},
{
formula: `LOG({CityId}) + EXP({CityId}) + POWER({CityId}, 3) + SQRT({CountryId})`,
result: [
"13.04566088154786",
"25.137588417628013",
"58.23402483297667",
"127.73041108667896",
"284.8714548168068",
],
result: ['13.04566088154786', '25.137588417628013', '58.23402483297667', '127.73041108667896', '284.8714548168068'],
},
{
formula: `NOW()`,
result: ["1", "1", "1", "1", "1"],
result: ['1', '1', '1', '1', '1'],
},
];
test.describe("Virtual Columns", () => {
test.describe('Virtual Columns', () => {
let dashboard: DashboardPage;
let context: any;
@ -69,13 +62,7 @@ test.describe("Virtual Columns", () => {
dashboard = new DashboardPage(page, context.project);
});
async function formulaResultVerify({
title,
result,
}: {
title: string;
result: string[];
}) {
async function formulaResultVerify({ title, result }: { title: string; result: string[] }) {
for (let i = 0; i < result.length; i++) {
await dashboard.grid.cell.verify({
index: i,
@ -85,35 +72,35 @@ test.describe("Virtual Columns", () => {
}
}
test("Formula", async () => {
test('Formula', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: "City" });
await dashboard.treeView.openTable({ title: 'City' });
// Create formula column
await dashboard.grid.column.create({
title: "NC_MATH_0",
type: "Formula",
title: 'NC_MATH_0',
type: 'Formula',
formula: formulaData[1].formula,
});
// verify different formula's
for (let i = 1; i < formulaData.length; i++) {
await dashboard.grid.column.openEdit({
title: "NC_MATH_0",
type: "Formula",
title: 'NC_MATH_0',
type: 'Formula',
formula: formulaData[i].formula,
});
await dashboard.grid.column.save({ isUpdated: true });
if (formulaData[i].formula !== `NOW()`) {
await formulaResultVerify({
title: "NC_MATH_0",
title: 'NC_MATH_0',
result: formulaData[i].result,
});
}
}
await dashboard.closeTab({ title: "City" });
await dashboard.closeTab({ title: 'City' });
});
});

216
scripts/playwright/tests/columnLinkToAnotherRecord.spec.ts

@ -1,8 +1,8 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
test.describe("LTAR create & update", () => {
test.describe('LTAR create & update', () => {
let dashboard: DashboardPage;
let context: any;
@ -14,41 +14,41 @@ test.describe("LTAR create & update", () => {
dashboard = new DashboardPage(page, context.project);
});
test("LTAR", async () => {
test('LTAR', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.createTable({ title: "Sheet1" });
await dashboard.treeView.createTable({ title: 'Sheet1' });
// subsequent table creation fails; hence delay
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.treeView.createTable({ title: "Sheet2" });
await dashboard.treeView.createTable({ title: 'Sheet2' });
await dashboard.treeView.openTable({ title: "Sheet1" });
await dashboard.grid.addNewRow({ index: 0, value: "1a" });
await dashboard.grid.addNewRow({ index: 1, value: "1b" });
await dashboard.grid.addNewRow({ index: 2, value: "1c" });
await dashboard.treeView.openTable({ title: 'Sheet1' });
await dashboard.grid.addNewRow({ index: 0, value: '1a' });
await dashboard.grid.addNewRow({ index: 1, value: '1b' });
await dashboard.grid.addNewRow({ index: 2, value: '1c' });
// Create LTAR-HM column
await dashboard.grid.column.create({
title: "Link1-2hm",
type: "LinkToAnotherRecord",
childTable: "Sheet2",
relationType: "Has Many",
title: 'Link1-2hm',
type: 'LinkToAnotherRecord',
childTable: 'Sheet2',
relationType: 'Has Many',
});
await dashboard.grid.column.create({
title: "Link1-2mm",
type: "LinkToAnotherRecord",
childTable: "Sheet2",
relationType: "Many To many",
title: 'Link1-2mm',
type: 'LinkToAnotherRecord',
childTable: 'Sheet2',
relationType: 'Many To many',
});
await dashboard.closeTab({ title: "Sheet1" });
await dashboard.closeTab({ title: 'Sheet1' });
await dashboard.treeView.openTable({ title: "Sheet2" });
await dashboard.treeView.openTable({ title: 'Sheet2' });
await dashboard.grid.column.create({
title: "Link2-1hm",
type: "LinkToAnotherRecord",
childTable: "Sheet1",
relationType: "Has Many",
title: 'Link2-1hm',
type: 'LinkToAnotherRecord',
childTable: 'Sheet1',
relationType: 'Has Many',
});
// Sheet2 now has all 3 column categories : HM, BT, MM
@ -58,72 +58,72 @@ test.describe("LTAR create & update", () => {
await dashboard.grid.toolbar.clickAddNewRow();
await dashboard.expandedForm.fillField({
columnTitle: "Title",
value: "2a",
columnTitle: 'Title',
value: '2a',
});
await dashboard.expandedForm.fillField({
columnTitle: "Sheet1",
value: "1a",
type: "belongsTo",
columnTitle: 'Sheet1',
value: '1a',
type: 'belongsTo',
});
await dashboard.expandedForm.fillField({
columnTitle: "Sheet1 List",
value: "1a",
type: "manyToMany",
columnTitle: 'Sheet1 List',
value: '1a',
type: 'manyToMany',
});
await dashboard.expandedForm.fillField({
columnTitle: "Link2-1hm",
value: "1a",
type: "hasMany",
columnTitle: 'Link2-1hm',
value: '1a',
type: 'hasMany',
});
await dashboard.expandedForm.save();
// In cell insert
await dashboard.grid.addNewRow({ index: 1, value: "2b" });
await dashboard.grid.cell.inCellAdd({ index: 1, columnHeader: "Sheet1" });
await dashboard.linkRecord.select("1b");
await dashboard.grid.addNewRow({ index: 1, value: '2b' });
await dashboard.grid.cell.inCellAdd({ index: 1, columnHeader: 'Sheet1' });
await dashboard.linkRecord.select('1b');
await dashboard.grid.cell.inCellAdd({
index: 1,
columnHeader: "Sheet1 List",
columnHeader: 'Sheet1 List',
});
await dashboard.linkRecord.select("1b");
await dashboard.linkRecord.select('1b');
await dashboard.grid.cell.inCellAdd({
index: 1,
columnHeader: "Link2-1hm",
columnHeader: 'Link2-1hm',
});
await dashboard.linkRecord.select("1b");
await dashboard.linkRecord.select('1b');
// Expand record insert
await dashboard.grid.addNewRow({ index: 2, value: "2c-temp" });
await dashboard.grid.addNewRow({ index: 2, value: '2c-temp' });
await dashboard.grid.openExpandedRow({ index: 2 });
await dashboard.expandedForm.fillField({
columnTitle: "Sheet1",
value: "1c",
type: "belongsTo",
columnTitle: 'Sheet1',
value: '1c',
type: 'belongsTo',
});
await dashboard.expandedForm.fillField({
columnTitle: "Sheet1 List",
value: "1c",
type: "manyToMany",
columnTitle: 'Sheet1 List',
value: '1c',
type: 'manyToMany',
});
await dashboard.expandedForm.fillField({
columnTitle: "Link2-1hm",
value: "1c",
type: "hasMany",
columnTitle: 'Link2-1hm',
value: '1c',
type: 'hasMany',
});
await dashboard.expandedForm.fillField({
columnTitle: "Title",
value: "2c",
type: "text",
columnTitle: 'Title',
value: '2c',
type: 'text',
});
await dashboard.expandedForm.save();
const expected = [
[["1a"], ["1b"], ["1c"]],
[["1a"], ["1b"], ["1c"]],
[["1a"], ["1b"], ["1c"]],
[['1a'], ['1b'], ['1c']],
[['1a'], ['1b'], ['1c']],
[['1a'], ['1b'], ['1c']],
];
const colHeaders = ["Sheet1", "Sheet1 List", "Link2-1hm"];
const colHeaders = ['Sheet1', 'Sheet1 List', 'Link2-1hm'];
// verify LTAR cell values
for (let i = 0; i < expected.length; i++) {
@ -137,15 +137,15 @@ test.describe("LTAR create & update", () => {
}
}
await dashboard.closeTab({ title: "Sheet2" });
await dashboard.treeView.openTable({ title: "Sheet1" });
await dashboard.closeTab({ title: 'Sheet2' });
await dashboard.treeView.openTable({ title: 'Sheet1' });
const expected2 = [
[["2a"], ["2b"], ["2c"]],
[["2a"], ["2b"], ["2c"]],
[["2a"], ["2b"], ["2c"]],
[['2a'], ['2b'], ['2c']],
[['2a'], ['2b'], ['2c']],
[['2a'], ['2b'], ['2c']],
];
const colHeaders2 = ["Link1-2hm", "Link1-2mm", "Sheet2"];
const colHeaders2 = ['Link1-2hm', 'Link1-2mm', 'Sheet2'];
// verify LTAR cell values
for (let i = 0; i < expected2.length; i++) {
@ -170,13 +170,13 @@ test.describe("LTAR create & update", () => {
}
// delete columns
await dashboard.grid.column.delete({ title: "Link1-2hm" });
await dashboard.grid.column.delete({ title: "Link1-2mm" });
await dashboard.grid.column.delete({ title: "Sheet2" });
await dashboard.grid.column.delete({ title: 'Link1-2hm' });
await dashboard.grid.column.delete({ title: 'Link1-2mm' });
await dashboard.grid.column.delete({ title: 'Sheet2' });
// delete table
await dashboard.treeView.deleteTable({ title: "Sheet1" });
await dashboard.treeView.deleteTable({ title: "Sheet2" });
await dashboard.treeView.deleteTable({ title: 'Sheet1' });
await dashboard.treeView.deleteTable({ title: 'Sheet2' });
});
async function verifyRow(param: {
@ -185,31 +185,31 @@ test.describe("LTAR create & update", () => {
Country: string;
formula?: string;
SLT?: string;
"City List": string[];
'City List': string[];
};
}) {
await dashboard.grid.cell.verify({
index: param.index,
columnHeader: "Country",
columnHeader: 'Country',
value: param.value.Country,
});
if (param.value.formula) {
await dashboard.grid.cell.verify({
index: param.index,
columnHeader: "formula",
columnHeader: 'formula',
value: param.value.formula,
});
}
await dashboard.grid.cell.verifyVirtualCell({
index: param.index,
columnHeader: "City List",
count: param.value["City List"].length,
value: param.value["City List"],
columnHeader: 'City List',
count: param.value['City List'].length,
value: param.value['City List'],
});
if (param.value.SLT) {
await dashboard.grid.cell.verify({
index: param.index,
columnHeader: "SLT",
columnHeader: 'SLT',
value: param.value.SLT,
});
}
@ -224,83 +224,83 @@ test.describe("LTAR create & update", () => {
* https://github.com/nocodb/nocodb/issues/4220
*
*/
test.skip("Existing LTAR table verification", async () => {
test.skip('Existing LTAR table verification', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
// open table
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.treeView.openTable({ title: 'Country' });
await verifyRow({
index: 0,
value: {
Country: "Afghanistan",
"City List": ["Kabul"],
Country: 'Afghanistan',
'City List': ['Kabul'],
},
});
await verifyRow({
index: 1,
value: {
Country: "Algeria",
"City List": ["Batna", "Bchar", "Skikda"],
Country: 'Algeria',
'City List': ['Batna', 'Bchar', 'Skikda'],
},
});
// create new columns
await dashboard.grid.column.create({
title: "SLT",
type: "SingleLineText",
title: 'SLT',
type: 'SingleLineText',
});
await dashboard.grid.column.create({
title: "formula",
type: "Formula",
title: 'formula',
type: 'Formula',
formula: "CONCAT({Country}, ' ', {SLT})",
});
// insert new content into a cell
await dashboard.grid.editRow({
index: 0,
columnHeader: "SLT",
value: "test",
columnHeader: 'SLT',
value: 'test',
});
await verifyRow({
index: 0,
value: {
Country: "Afghanistan",
"City List": ["Kabul"],
SLT: "test",
formula: "Afghanistan test",
Country: 'Afghanistan',
'City List': ['Kabul'],
SLT: 'test',
formula: 'Afghanistan test',
},
});
// edit record
await dashboard.grid.editRow({
index: 0,
columnHeader: "Country",
value: "Afghanistan2",
columnHeader: 'Country',
value: 'Afghanistan2',
});
await verifyRow({
index: 0,
value: {
Country: "Afghanistan2",
"City List": ["Kabul"],
SLT: "test",
formula: "Afghanistan2 test",
Country: 'Afghanistan2',
'City List': ['Kabul'],
SLT: 'test',
formula: 'Afghanistan2 test',
},
});
// Delete cell contents and verify
await dashboard.grid.cell.click({ index: 0, columnHeader: "SLT" });
await dashboard.grid.cell.click({ index: 0, columnHeader: 'SLT' });
// trigger delete button key
await dashboard.rootPage.keyboard.press("Delete");
await dashboard.rootPage.keyboard.press('Delete');
// Verify other non-virtual cells
await verifyRow({
index: 0,
value: {
Country: "Afghanistan2",
"City List": ["Kabul"],
SLT: "",
formula: "Afghanistan2",
Country: 'Afghanistan2',
'City List': ['Kabul'],
SLT: '',
formula: 'Afghanistan2',
},
});
});

44
scripts/playwright/tests/columnLookupRollup.spec.ts

@ -1,8 +1,8 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
test.describe("Virtual columns", () => {
test.describe('Virtual columns', () => {
let dashboard: DashboardPage;
let context: any;
@ -11,46 +11,46 @@ test.describe("Virtual columns", () => {
dashboard = new DashboardPage(page, context.project);
});
test("Lookup", async () => {
test('Lookup', async () => {
// close 'Team & Auth' tab
// await dashboard.closeTab({ title: "Team & Auth" });
const pinCode = ["4166", "77459", "41136", "8268", "33463"];
const cityCount = ["1", "3", "1", "2", "1"];
const pinCode = ['4166', '77459', '41136', '8268', '33463'];
const cityCount = ['1', '3', '1', '2', '1'];
await dashboard.treeView.openTable({ title: "City" });
await dashboard.treeView.openTable({ title: 'City' });
// Create LookUp column
await dashboard.grid.column.create({
title: "Lookup",
type: "Lookup",
childTable: "Address List",
childColumn: "PostalCode",
title: 'Lookup',
type: 'Lookup',
childTable: 'Address List',
childColumn: 'PostalCode',
});
for (let i = 0; i < pinCode.length; i++) {
await dashboard.grid.cell.verify({
index: i,
columnHeader: "Lookup",
columnHeader: 'Lookup',
value: pinCode[i],
});
}
await dashboard.closeTab({ title: "City" });
await dashboard.closeTab({ title: 'City' });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.treeView.openTable({ title: 'Country' });
// Create Rollup column
await dashboard.grid.column.create({
title: "Rollup",
type: "Rollup",
childTable: "City List",
childColumn: "City",
rollupType: "count",
title: 'Rollup',
type: 'Rollup',
childTable: 'City List',
childColumn: 'City',
rollupType: 'count',
});
for (let i = 0; i < pinCode.length; i++) {
await dashboard.grid.cell.verify({
index: i,
columnHeader: "Rollup",
columnHeader: 'Rollup',
value: cityCount[i],
});
}
await dashboard.closeTab({ title: "Country" });
await dashboard.closeTab({ title: 'Country' });
});
});

149
scripts/playwright/tests/columnMultiSelect.spec.ts

@ -1,66 +1,131 @@
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { GridPage } from '../pages/Dashboard/Grid'
import { GridPage } from '../pages/Dashboard/Grid';
import setup from '../setup';
test.describe('Multi select', () => {
let dashboard: DashboardPage, grid: GridPage;
let context: any;
test.beforeEach(async ({page}) => {
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
grid = dashboard.grid;
await dashboard.treeView.createTable({ title: 'sheet1' });
await grid.column.create({ title: 'MultiSelect', type: 'MultiSelect' });
await grid.addNewRow({index: 0, value: "Row 0"});
})
await grid.addNewRow({ index: 0, value: 'Row 0' });
});
test('Select and clear options and rename options', async () => {
await grid.cell.selectOption.select({index: 0, columnHeader: 'MultiSelect', option: 'Option 1', multiSelect: true});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'MultiSelect', option: 'Option 1', multiSelect: true});
await grid.cell.selectOption.select({index: 0, columnHeader: 'MultiSelect', option: 'Option 2', multiSelect: true});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'MultiSelect', option: 'Option 2', multiSelect: true});
await grid.addNewRow({index: 1, value: "Row 1"});
await grid.cell.selectOption.select({index: 1, columnHeader: 'MultiSelect', option: 'Option 1', multiSelect: true});
await grid.cell.selectOption.clear({index: 0, columnHeader: 'MultiSelect', multiSelect: true});
await grid.cell.click({index: 0, columnHeader: 'MultiSelect'});
await grid.column.selectOption.addOption({index: 2, option: 'Option 3', columnTitle: 'MultiSelect'});
await grid.cell.selectOption.select({index: 0, columnHeader: 'MultiSelect', option: 'Option 3', multiSelect: true});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'MultiSelect', option: 'Option 3', multiSelect: true});
await grid.column.selectOption.editOption({index: 2, columnTitle: 'MultiSelect', newOption: 'New Option 3'});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'MultiSelect', option: 'New Option 3', multiSelect: true});
await grid.cell.selectOption.verifyOptions({index: 0, columnHeader: 'MultiSelect', options: ['Option 1', 'Option 2', 'New Option 3']});
await grid.cell.selectOption.select({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 1',
multiSelect: true,
});
await grid.cell.selectOption.verify({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 1',
multiSelect: true,
});
await grid.cell.selectOption.select({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 2',
multiSelect: true,
});
await grid.cell.selectOption.verify({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 2',
multiSelect: true,
});
await grid.addNewRow({ index: 1, value: 'Row 1' });
await grid.cell.selectOption.select({
index: 1,
columnHeader: 'MultiSelect',
option: 'Option 1',
multiSelect: true,
});
await grid.cell.selectOption.clear({ index: 0, columnHeader: 'MultiSelect', multiSelect: true });
await grid.cell.click({ index: 0, columnHeader: 'MultiSelect' });
await grid.column.selectOption.addOption({ index: 2, option: 'Option 3', columnTitle: 'MultiSelect' });
await grid.cell.selectOption.select({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 3',
multiSelect: true,
});
await grid.cell.selectOption.verify({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 3',
multiSelect: true,
});
await grid.column.selectOption.editOption({ index: 2, columnTitle: 'MultiSelect', newOption: 'New Option 3' });
await grid.cell.selectOption.verify({
index: 0,
columnHeader: 'MultiSelect',
option: 'New Option 3',
multiSelect: true,
});
await grid.cell.selectOption.verifyOptions({
index: 0,
columnHeader: 'MultiSelect',
options: ['Option 1', 'Option 2', 'New Option 3'],
});
await grid.deleteRow(0);
await grid.deleteRow(0);
await grid.verifyRowDoesNotExist({index: 0});
await grid.verifyRowDoesNotExist({ index: 0 });
});
test('Remove a option, reorder option and delete the column', async () => {
await grid.cell.selectOption.select({index: 0, columnHeader: 'MultiSelect', option: 'Option 1', multiSelect: true});
await grid.column.selectOption.addOption({index: 2, option: 'Option 3', columnTitle: 'MultiSelect'});
await grid.cell.selectOption.select({index: 0, columnHeader: 'MultiSelect', option: 'Option 3', multiSelect: true});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'MultiSelect', option: 'Option 3', multiSelect: true});
await grid.column.selectOption.deleteOption({index: 2, columnTitle: 'MultiSelect'});
await grid.cell.selectOption.verifyNoOptionsSelected({index: 0, columnHeader: 'MultiSelect'});
await grid.column.selectOption.reorderOption({sourceOption: "Option 1", columnTitle: 'MultiSelect', destinationOption: "Option 2"});
await grid.cell.selectOption.verifyOptions({index: 0, columnHeader: 'MultiSelect', options: ['Option 2', 'Option 1']});
await grid.column.delete({title: 'MultiSelect'});
await grid.cell.selectOption.select({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 1',
multiSelect: true,
});
await grid.column.selectOption.addOption({ index: 2, option: 'Option 3', columnTitle: 'MultiSelect' });
await grid.cell.selectOption.select({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 3',
multiSelect: true,
});
await grid.cell.selectOption.verify({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option 3',
multiSelect: true,
});
await grid.column.selectOption.deleteOption({ index: 2, columnTitle: 'MultiSelect' });
await grid.cell.selectOption.verifyNoOptionsSelected({ index: 0, columnHeader: 'MultiSelect' });
await grid.column.selectOption.reorderOption({
sourceOption: 'Option 1',
columnTitle: 'MultiSelect',
destinationOption: 'Option 2',
});
await grid.cell.selectOption.verifyOptions({
index: 0,
columnHeader: 'MultiSelect',
options: ['Option 2', 'Option 1'],
});
await grid.column.delete({ title: 'MultiSelect' });
});
});

102
scripts/playwright/tests/columnRelationalExtendedTests.spec.ts

@ -1,8 +1,8 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
test.describe("Relational Columns", () => {
test.describe('Relational Columns', () => {
let dashboard: DashboardPage;
let context: any;
@ -11,19 +11,19 @@ test.describe("Relational Columns", () => {
dashboard = new DashboardPage(page, context.project);
});
test("Relational columns: HM, BT, MM", async () => {
test('Relational columns: HM, BT, MM', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
///////////// Has many
//
const cityList = [["Kabul"], ["Batna", "Bchar", "Skikda"]];
await dashboard.treeView.openTable({ title: "Country" });
const cityList = [['Kabul'], ['Batna', 'Bchar', 'Skikda']];
await dashboard.treeView.openTable({ title: 'Country' });
for (let i = 0; i < cityList.length; i++) {
await dashboard.grid.cell.verifyVirtualCell({
index: i,
columnHeader: "City List",
columnHeader: 'City List',
count: cityList[i].length,
value: cityList[i],
});
@ -32,39 +32,39 @@ test.describe("Relational Columns", () => {
// click on expand icon, open child list
await dashboard.grid.cell.inCellExpand({
index: 0,
columnHeader: "City List",
columnHeader: 'City List',
});
await dashboard.childList.verify({
cardTitle: ["Kabul"],
linkField: "City",
cardTitle: ['Kabul'],
linkField: 'City',
});
// open link record modal
//
await dashboard.childList.openLinkRecord({linkTableTitle: 'City'})
await dashboard.childList.openLinkRecord({ linkTableTitle: 'City' });
await dashboard.linkRecord.verify([
"A Corua (La Corua)",
"Abha",
"Abu Dhabi",
"Acua",
"Adana",
"Addis Abeba",
"Aden",
"Adoni",
"Ahmadnagar",
"Akishima",
'A Corua (La Corua)',
'Abha',
'Abu Dhabi',
'Acua',
'Adana',
'Addis Abeba',
'Aden',
'Adoni',
'Ahmadnagar',
'Akishima',
]);
await dashboard.linkRecord.close();
///////////// Belongs to
//
await dashboard.treeView.openTable({ title: "City" });
const countryList = [["Spain"], ["Saudi Arabia"]];
await dashboard.treeView.openTable({ title: 'City' });
const countryList = [['Spain'], ['Saudi Arabia']];
for (let i = 0; i < countryList.length; i++) {
await dashboard.grid.cell.verifyVirtualCell({
index: i,
columnHeader: "Country",
columnHeader: 'Country',
count: countryList[i].length,
value: countryList[i],
});
@ -72,25 +72,25 @@ test.describe("Relational Columns", () => {
///////////// Many to many
//
await dashboard.treeView.openTable({ title: "Actor" });
await dashboard.treeView.openTable({ title: 'Actor' });
const filmList = [
[
"ACADEMY DINOSAUR",
"ANACONDA CONFESSIONS",
"ANGELS LIFE",
"BULWORTH COMMANDMENTS",
"CHEAPER CLYDE",
"COLOR PHILADELPHIA",
"ELEPHANT TROJAN",
"GLEAMING JAWBREAKER",
"HUMAN GRAFFITI",
"KING EVOLUTION",
'ACADEMY DINOSAUR',
'ANACONDA CONFESSIONS',
'ANGELS LIFE',
'BULWORTH COMMANDMENTS',
'CHEAPER CLYDE',
'COLOR PHILADELPHIA',
'ELEPHANT TROJAN',
'GLEAMING JAWBREAKER',
'HUMAN GRAFFITI',
'KING EVOLUTION',
],
];
for (let i = 0; i < filmList.length; i++) {
await dashboard.grid.cell.verifyVirtualCell({
index: i,
columnHeader: "Film List",
columnHeader: 'Film List',
// Count hardwired to avoid verifying all 19 entries
count: 19,
value: filmList[i],
@ -99,27 +99,27 @@ test.describe("Relational Columns", () => {
// click on expand icon, open child list
await dashboard.grid.cell.inCellExpand({
index: 0,
columnHeader: "Film List",
columnHeader: 'Film List',
});
await dashboard.childList.verify({
cardTitle: filmList[0],
linkField: "Film",
linkField: 'Film',
});
// open link record modal
//
await dashboard.childList.openLinkRecord({linkTableTitle: 'Film'})
await dashboard.childList.openLinkRecord({ linkTableTitle: 'Film' });
await dashboard.linkRecord.verify([
"ACE GOLDFINGER",
"ADAPTATION HOLES",
"AFFAIR PREJUDICE",
"AFRICAN EGG",
"AGENT TRUMAN",
"AIRPLANE SIERRA",
"AIRPORT POLLOCK",
"ALABAMA DEVIL",
"ALADDIN CALENDAR",
"ALAMO VIDEOTAPE",
'ACE GOLDFINGER',
'ADAPTATION HOLES',
'AFFAIR PREJUDICE',
'AFRICAN EGG',
'AGENT TRUMAN',
'AIRPLANE SIERRA',
'AIRPORT POLLOCK',
'ALABAMA DEVIL',
'ALADDIN CALENDAR',
'ALAMO VIDEOTAPE',
]);
await dashboard.linkRecord.close();
});

74
scripts/playwright/tests/columnSingleSelect.spec.ts

@ -3,60 +3,70 @@ import { DashboardPage } from '../pages/Dashboard';
import { GridPage } from '../pages/Dashboard/Grid';
import setup from '../setup';
test.describe('Single select', () => {
let dashboard: DashboardPage, grid: GridPage;
let context: any;
test.beforeEach(async ({page}) => {
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
grid = dashboard.grid;
await dashboard.treeView.createTable({ title: 'sheet1' });
await grid.column.create({ title: 'SingleSelect', type: 'SingleSelect' });
await grid.addNewRow({index: 0, value: "Row 0"});
})
await grid.addNewRow({ index: 0, value: 'Row 0' });
});
test('Select and clear options and rename options', async () => {
await grid.cell.selectOption.select({index: 0, columnHeader: 'SingleSelect', option: 'Option 1'});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'SingleSelect', option: 'Option 1'});
await grid.cell.selectOption.select({ index: 0, columnHeader: 'SingleSelect', option: 'Option 1' });
await grid.cell.selectOption.verify({ index: 0, columnHeader: 'SingleSelect', option: 'Option 1' });
await grid.cell.selectOption.select({index: 0, columnHeader: 'SingleSelect', option: 'Option 2'});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'SingleSelect', option: 'Option 2'});
await grid.cell.selectOption.select({ index: 0, columnHeader: 'SingleSelect', option: 'Option 2' });
await grid.cell.selectOption.verify({ index: 0, columnHeader: 'SingleSelect', option: 'Option 2' });
await grid.cell.selectOption.clear({index: 0, columnHeader: 'SingleSelect'});
await grid.cell.click({index: 0, columnHeader: 'SingleSelect'});
await grid.cell.selectOption.clear({ index: 0, columnHeader: 'SingleSelect' });
await grid.cell.click({ index: 0, columnHeader: 'SingleSelect' });
await grid.column.selectOption.addOption({index: 2, option: 'Option 3', columnTitle: 'SingleSelect'});
await grid.column.selectOption.addOption({ index: 2, option: 'Option 3', columnTitle: 'SingleSelect' });
await grid.cell.selectOption.select({index: 0, columnHeader: 'SingleSelect', option: 'Option 3'});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'SingleSelect', option: 'Option 3'});
await grid.cell.selectOption.select({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.cell.selectOption.verify({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.column.selectOption.editOption({index: 2, columnTitle: 'SingleSelect', newOption: 'New Option 3'});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'SingleSelect', option: 'New Option 3'});
await grid.column.selectOption.editOption({ index: 2, columnTitle: 'SingleSelect', newOption: 'New Option 3' });
await grid.cell.selectOption.verify({ index: 0, columnHeader: 'SingleSelect', option: 'New Option 3' });
await grid.cell.selectOption.verifyOptions({index: 0, columnHeader: 'SingleSelect', options: ['Option 1', 'Option 2', 'New Option 3']});
await grid.cell.selectOption.verifyOptions({
index: 0,
columnHeader: 'SingleSelect',
options: ['Option 1', 'Option 2', 'New Option 3'],
});
await grid.deleteRow(0);
await grid.verifyRowDoesNotExist({index: 0});
await grid.verifyRowDoesNotExist({ index: 0 });
});
test('Remove a option, reorder option and delete the column', async () => {
await grid.cell.selectOption.select({index: 0, columnHeader: 'SingleSelect', option: 'Option 1'});
await grid.column.selectOption.addOption({index: 2, option: 'Option 3', columnTitle: 'SingleSelect'});
await grid.cell.selectOption.select({index: 0, columnHeader: 'SingleSelect', option: 'Option 3'});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'SingleSelect', option: 'Option 3'});
await grid.column.selectOption.deleteOption({index: 2, columnTitle: 'SingleSelect'});
await grid.cell.selectOption.verifyNoOptionsSelected({index: 0, columnHeader: 'SingleSelect'});
await grid.column.selectOption.reorderOption({sourceOption: "Option 1", columnTitle: 'SingleSelect', destinationOption: "Option 2"});
await grid.cell.selectOption.verifyOptions({index: 0, columnHeader: 'SingleSelect', options: ['Option 2', 'Option 1']});
await grid.column.delete({title: 'SingleSelect'});
await grid.cell.selectOption.select({ index: 0, columnHeader: 'SingleSelect', option: 'Option 1' });
await grid.column.selectOption.addOption({ index: 2, option: 'Option 3', columnTitle: 'SingleSelect' });
await grid.cell.selectOption.select({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.cell.selectOption.verify({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.column.selectOption.deleteOption({ index: 2, columnTitle: 'SingleSelect' });
await grid.cell.selectOption.verifyNoOptionsSelected({ index: 0, columnHeader: 'SingleSelect' });
await grid.column.selectOption.reorderOption({
sourceOption: 'Option 1',
columnTitle: 'SingleSelect',
destinationOption: 'Option 2',
});
await grid.cell.selectOption.verifyOptions({
index: 0,
columnHeader: 'SingleSelect',
options: ['Option 2', 'Option 1'],
});
await grid.column.delete({ title: 'SingleSelect' });
});
});

312
scripts/playwright/tests/erd.spec.ts

@ -1,13 +1,19 @@
import { test, expect } from "@playwright/test";
import { mysqlSakilaSqlViews, mysqlSakilaTables, pgSakilaSqlViews, pgSakilaTables, sqliteSakilaSqlViews } from "./utils/sakila";
import { DashboardPage } from "../pages/Dashboard";
import { SettingsSubTab, SettingTab } from "../pages/Dashboard/Settings";
import setup from "../setup";
import { isMysql, isPg, isSqlite } from "../setup/db";
import { GridPage } from "../pages/Dashboard/Grid";
import { SettingsErdPage } from "../pages/Dashboard/Settings/Erd";
test.describe("Erd", () => {
import { expect, test } from '@playwright/test';
import {
mysqlSakilaSqlViews,
mysqlSakilaTables,
pgSakilaSqlViews,
pgSakilaTables,
sqliteSakilaSqlViews,
} from './utils/sakila';
import { DashboardPage } from '../pages/Dashboard';
import { SettingsSubTab, SettingTab } from '../pages/Dashboard/Settings';
import setup from '../setup';
import { isMysql, isPg, isSqlite } from '../setup/db';
import { GridPage } from '../pages/Dashboard/Grid';
import { SettingsErdPage } from '../pages/Dashboard/Settings/Erd';
test.describe('Erd', () => {
let dashboard: DashboardPage;
let context: any;
let project: any;
@ -20,34 +26,34 @@ test.describe("Erd", () => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
project = context.project
project = context.project;
if (isPg(context)) {
sakilaTables = pgSakilaTables;
sakilaSqlViews = pgSakilaSqlViews;
} else if(isMysql(context)) {
} else if (isMysql(context)) {
sakilaTables = mysqlSakilaTables;
sakilaSqlViews = mysqlSakilaSqlViews;
} else if(isSqlite(context)) {
sakilaTables = mysqlSakilaTables.map((tableName) => `${project.prefix}${tableName}`);
sakilaSqlViews = sqliteSakilaSqlViews.map((viewName) => `${project.prefix}${viewName}`);
} else if (isSqlite(context)) {
sakilaTables = mysqlSakilaTables.map(tableName => `${project.prefix}${tableName}`);
sakilaSqlViews = sqliteSakilaSqlViews.map(viewName => `${project.prefix}${viewName}`);
}
});
// todo: Hack, edges are not getting rendered properly
const openSettingsErd = async () => {
await dashboard.gotoSettings();
await dashboard.settings.selectTab({tab: SettingTab.ProjectMetadata, subTab: SettingsSubTab.Miscellaneous});
await dashboard.settings.selectSubTab({subTab: SettingsSubTab.ERD});
}
await dashboard.settings.selectTab({ tab: SettingTab.ProjectMetadata, subTab: SettingsSubTab.Miscellaneous });
await dashboard.settings.selectSubTab({ subTab: SettingsSubTab.ERD });
};
// todo: remove this. Need for edges to be rendered
const openErdOfATableWithEdgesRendered = async (tableName: string) => {
await dashboard.treeView.openTable({title: tableName});
await dashboard.treeView.openTable({ title: tableName });
await dashboard.grid.toolbar.clickActions();
await dashboard.grid.toolbar.actions.click("ERD View");
}
await dashboard.grid.toolbar.actions.click('ERD View');
};
test.skip("Verify default config, all columns disabled, only PK and FK disabled, Sql views and MM table option, junction table names", async () => {
test.skip('Verify default config, all columns disabled, only PK and FK disabled, Sql views and MM table option, junction table names', async () => {
await openSettingsErd();
const erd: SettingsErdPage = dashboard.settings.erd;
@ -61,7 +67,7 @@ test.describe("Erd", () => {
circleCount: 29,
rectangleCount: 35,
});
} else {
} else {
await erd.verifyNodesCount(mysqlSakilaTables.length);
await erd.verifyEdgesCount({
count: 14,
@ -69,15 +75,21 @@ test.describe("Erd", () => {
rectangleCount: 17,
});
}
for(const tableName of sakilaTables) {
await erd.verifyNode({tableName});
for (const tableName of sakilaTables) {
await erd.verifyNode({ tableName });
}
// Verify Actor table
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}actor`, columns: actorTableColumn});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}actor`,
columns: actorTableColumn,
});
// Verify Payment table
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}payment`, columns: isPg(context) ? pgPaymentTableColumns : mysqlPaymentTableColumns});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}payment`,
columns: isPg(context) ? pgPaymentTableColumns : mysqlPaymentTableColumns,
});
// Disable show column names and pk/fk
// todo: rerender edges, otherwise some edges wont be rendered
@ -85,20 +97,32 @@ test.describe("Erd", () => {
await erd.clickShowJunctionTableNames();
await erd.clickShowJunctionTableNames();
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}actor`, columns: actorLTARColumns});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}payment`, columns: paymentLTARColumns});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}actor`,
columns: actorLTARColumns,
});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}payment`,
columns: paymentLTARColumns,
});
// Enable show column names and disable pk/fk
await erd.clickShowColumnNames();
await erd.clickShowPkAndFk();
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}actor`, columns: actorNonPkFkColumns});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}payment`, columns: isPg(context) ? pgPaymentNonPkFkColumns : paymentNonPkFkColumns});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}actor`,
columns: actorNonPkFkColumns,
});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}payment`,
columns: isPg(context) ? pgPaymentNonPkFkColumns : paymentNonPkFkColumns,
});
await erd.clickShowPkAndFk();
// Verify views
await erd.clickShowSqlViews();
if(isPg(context)) {
if (isPg(context)) {
await erd.verifyNodesCount(sakilaTables.length + sakilaSqlViews.length);
await erd.verifyEdgesCount({
count: 32,
@ -114,82 +138,77 @@ test.describe("Erd", () => {
});
}
for(const tableName of [...sakilaTables, ...sakilaSqlViews]) {
await erd.verifyNode({tableName});
for (const tableName of [...sakilaTables, ...sakilaSqlViews]) {
await erd.verifyNode({ tableName });
}
// Verify ActorInfo SQL View
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}sales_by_store`, columns: salesByStoreColumns});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}sales_by_store`,
columns: salesByStoreColumns,
});
await erd.clickShowSqlViews(); // disable sql views
await erd.verifyNodeDoesNotExist({tableName: `${isSqlite(context) ? project.prefix: ''}store`});
await erd.verifyNodeDoesNotExist({ tableName: `${isSqlite(context) ? project.prefix : ''}store` });
// // Verify MM tables
await erd.clickShowMMTables();
await erd.clickShowJunctionTableNames();
await erd.clickShowJunctionTableNames();
await erd.verifyNodesCount(isPg(context) ? 21: 16);
await erd.verifyNodesCount(isPg(context) ? 21 : 16);
await erd.verifyEdgesCount({
count: isPg(context) ? 44: 26,
circleCount: isPg(context) ? 40: 22,
rectangleCount: isPg(context) ? 48: 30,
count: isPg(context) ? 44 : 26,
circleCount: isPg(context) ? 40 : 22,
rectangleCount: isPg(context) ? 48 : 30,
});
await erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}store`});
await erd.verifyNode({ tableName: `${isSqlite(context) ? project.prefix : ''}store` });
// Verify show junction table names
await erd.clickShowJunctionTableNames();
await erd.verifyJunctionTableLabel({tableName: `${isSqlite(context) ? project.prefix: ''}film_actor`, tableTitle: 'filmactor'});
await erd.verifyJunctionTableLabel({
tableName: `${isSqlite(context) ? project.prefix : ''}film_actor`,
tableTitle: 'filmactor',
});
});
test("Verify ERD Table view, and verify column operations are reflected to the ERD view", async () => {
await openErdOfATableWithEdgesRendered("Country");
test('Verify ERD Table view, and verify column operations are reflected to the ERD view', async () => {
await openErdOfATableWithEdgesRendered('Country');
const erd = dashboard.grid.toolbar.actions.erd;
// Verify tables with default config
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columns: [
'country_id',
'country',
'last_update',
'city_list'
]});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}city`, columns: [
'city_id',
'city',
'country_id',
'last_update',
'country',
'address_list'
]});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}country`,
columns: ['country_id', 'country', 'last_update', 'city_list'],
});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}city`,
columns: ['city_id', 'city', 'country_id', 'last_update', 'country', 'address_list'],
});
// Verify with PK/FK disabled
await erd.clickShowPkAndFk();
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columns: [
'country',
'last_update',
'city_list'
]});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}city`, columns: [
'city',
'last_update',
'country',
'address_list'
]});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}country`,
columns: ['country', 'last_update', 'city_list'],
});
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}city`,
columns: ['city', 'last_update', 'country', 'address_list'],
});
// Verify with all columns disabled
await erd.clickShowColumnNames();
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columns: [
'city_list'
]});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}city`, columns: [
'country',
'address_list'
]});
await erd.verifyColumns({ tableName: `${isSqlite(context) ? project.prefix : ''}country`, columns: ['city_list'] });
await erd.verifyColumns({
tableName: `${isSqlite(context) ? project.prefix : ''}city`,
columns: ['country', 'address_list'],
});
// Enable All columns
await erd.clickShowColumnNames();
@ -197,93 +216,96 @@ test.describe("Erd", () => {
await erd.close();
// Add column
await dashboard.grid.column.create({title: "test_column"});
await dashboard.grid.column.create({ title: 'test_column' });
// Verify in Settings ERD and table ERD
await openSettingsErd();
await dashboard.settings.erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columnName: 'test_column'});
await dashboard.settings.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix : ''}country`,
columnName: 'test_column',
});
await dashboard.settings.close();
await dashboard.treeView.openTable({title: "Country"});
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.toolbar.clickActions();
await dashboard.grid.toolbar.actions.click("ERD View");
await dashboard.grid.toolbar.actions.erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columnName: 'test_column'});
await dashboard.grid.toolbar.actions.click('ERD View');
await dashboard.grid.toolbar.actions.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix : ''}country`,
columnName: 'test_column',
});
await dashboard.grid.toolbar.actions.erd.close();
// Update column
await dashboard.grid.column.openEdit({ title: "test_column" });
await dashboard.grid.column.fillTitle({ title: "new_test_column" });
await dashboard.grid.column.openEdit({ title: 'test_column' });
await dashboard.grid.column.fillTitle({ title: 'new_test_column' });
await dashboard.grid.column.save({
isUpdated: true,
});
// Verify in Settings ERD and table ERD
await openSettingsErd();
await dashboard.settings.erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columnName: 'new_test_column'});
await dashboard.settings.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix : ''}country`,
columnName: 'new_test_column',
});
await dashboard.settings.close();
await dashboard.treeView.openTable({title: "Country"});
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.toolbar.clickActions();
await dashboard.grid.toolbar.actions.click("ERD View");
await dashboard.grid.toolbar.actions.erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columnName: 'new_test_column'});
await dashboard.grid.toolbar.actions.click('ERD View');
await dashboard.grid.toolbar.actions.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix : ''}country`,
columnName: 'new_test_column',
});
await dashboard.grid.toolbar.actions.erd.close();
// Delete column
await dashboard.grid.column.delete({title: "new_test_column"});
await dashboard.grid.column.delete({ title: 'new_test_column' });
// Verify in Settings ERD and table ERD
await openSettingsErd();
await dashboard.settings.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix: ''}country`,
columnNameShouldNotExist: 'new_test_column'
tableName: `${isSqlite(context) ? project.prefix : ''}country`,
columnNameShouldNotExist: 'new_test_column',
});
await dashboard.settings.close();
})
});
test("Verify table operations sync with ERD", async () => {
test('Verify table operations sync with ERD', async () => {
await openSettingsErd();
await dashboard.settings.close()
await dashboard.settings.close();
await dashboard.treeView.openTable({title: "Country"});
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.toolbar.clickActions();
await dashboard.grid.toolbar.actions.click("ERD View");
await dashboard.grid.toolbar.actions.click('ERD View');
await dashboard.grid.toolbar.actions.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix: ''}country`,
columnNameShouldNotExist: 'new_test_column'
tableName: `${isSqlite(context) ? project.prefix : ''}country`,
columnNameShouldNotExist: 'new_test_column',
});
await dashboard.grid.toolbar.actions.erd.close();
// Create table and verify ERD
await dashboard.treeView.createTable({title: "Test"});
await dashboard.treeView.createTable({ title: 'Test' });
// Verify in Settings ERD and table ERD
await openSettingsErd();
await dashboard.settings.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix: ''}Test`,
tableName: `${isSqlite(context) ? project.prefix : ''}Test`,
});
await dashboard.settings.close();
// Delete table and verify ERD
await dashboard.treeView.deleteTable({title: "Test"});
await dashboard.treeView.deleteTable({ title: 'Test' });
await openSettingsErd();
await dashboard.settings.erd.verifyNodeDoesNotExist({
tableName: `${isSqlite(context) ? project.prefix: ''}Test`,
tableName: `${isSqlite(context) ? project.prefix : ''}Test`,
});
// Verify that `show mm table` option disabled will not trigger easter in ERD options
await dashboard.settings.selectSubTab({subTab: SettingsSubTab.Miscellaneous});
await dashboard.settings.selectSubTab({ subTab: SettingsSubTab.Miscellaneous });
await dashboard.settings.miscellaneous.clickShowM2MTables(); // disable
await dashboard.settings.selectSubTab({subTab: SettingsSubTab.ERD});
await dashboard.settings.selectSubTab({ subTab: SettingsSubTab.ERD });
await dashboard.settings.close();
})
});
});
const actorTableColumn = [
'actor_id',
'first_name',
'last_name',
'last_update',
'film_list'
]
const actorTableColumn = ['actor_id', 'first_name', 'last_name', 'last_update', 'film_list'];
const mysqlPaymentTableColumns = [
'payment_id',
@ -295,8 +317,8 @@ const mysqlPaymentTableColumns = [
'last_update',
'customer',
'rental',
'staff'
]
'staff',
];
const pgPaymentTableColumns = [
'payment_id',
@ -307,42 +329,16 @@ const pgPaymentTableColumns = [
'payment_date',
'customer',
'rental',
'staff'
]
const actorLTARColumns = [
'filmactor_list',
'film_list'
'staff',
];
const actorNonPkFkColumns = [
'first_name',
'last_name',
'last_update',
'film_list',
'filmactor_list'
];
const actorLTARColumns = ['filmactor_list', 'film_list'];
const paymentLTARColumns = [
'customer',
'rental',
'staff'
];
const actorNonPkFkColumns = ['first_name', 'last_name', 'last_update', 'film_list', 'filmactor_list'];
const pgPaymentNonPkFkColumns = [
'amount',
'payment_date',
'customer',
'rental',
'staff'
];
const paymentNonPkFkColumns = [
...pgPaymentNonPkFkColumns,
'last_update'
];
const paymentLTARColumns = ['customer', 'rental', 'staff'];
const salesByStoreColumns = [
'store',
'manager',
'total_sales'
];
const pgPaymentNonPkFkColumns = ['amount', 'payment_date', 'customer', 'rental', 'staff'];
const paymentNonPkFkColumns = [...pgPaymentNonPkFkColumns, 'last_update'];
const salesByStoreColumns = ['store', 'manager', 'total_sales'];

75
scripts/playwright/tests/expandedFormUrl.spec.ts

@ -1,10 +1,10 @@
import { expect, test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { GalleryPage } from "../pages/Dashboard/Gallery";
import { GridPage } from "../pages/Dashboard/Grid";
import setup from "../setup";
import { expect, test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { GalleryPage } from '../pages/Dashboard/Gallery';
import { GridPage } from '../pages/Dashboard/Grid';
import setup from '../setup';
test.describe("Expanded form URL", () => {
test.describe('Expanded form URL', () => {
let dashboard: DashboardPage;
let context: any;
@ -15,31 +15,31 @@ test.describe("Expanded form URL", () => {
async function viewTest(viewType: string) {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' });
let viewObj: GridPage | GalleryPage = dashboard.grid;
if (viewType === "grid") {
if (viewType === 'grid') {
viewObj = dashboard.grid;
} else if (viewType === "gallery") {
} else if (viewType === 'gallery') {
viewObj = dashboard.gallery;
}
if (viewType === "grid") {
await dashboard.viewSidebar.createGridView({ title: "CountryExpand" });
} else if (viewType === "gallery") {
if (viewType === 'grid') {
await dashboard.viewSidebar.createGridView({ title: 'CountryExpand' });
} else if (viewType === 'gallery') {
await dashboard.viewSidebar.createGalleryView({
title: "CountryExpand",
title: 'CountryExpand',
});
await viewObj.toolbar.clickFields();
await viewObj.toolbar.fields.click({ title: "City List" });
await viewObj.toolbar.fields.click({ title: 'City List' });
}
// expand row & verify URL
await viewObj.openExpandedRow({ index: 0 });
await dashboard.expandedForm.verify({
header: "Afghanistan",
url: "rowId=1",
header: 'Afghanistan',
url: 'rowId=1',
});
// // verify copied URL in clipboard
@ -49,50 +49,51 @@ test.describe("Expanded form URL", () => {
// access a new rowID using URL
await dashboard.expandedForm.close();
await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: "2" });
await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: '2' });
await dashboard.expandedForm.verify({
header: "Algeria",
url: "rowId=2",
header: 'Algeria',
url: 'rowId=2',
});
await dashboard.expandedForm.close();
// visit invalid rowID
await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: "999" });
await dashboard.verifyToast({ message: "Record not found" });
await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: '999' });
await dashboard.verifyToast({ message: 'Record not found' });
// ensure grid is displayed after invalid URL access
await viewObj.verifyRowCount({ count: 25 });
// todo: Implement `verifyRowCount` method
// await viewObj.verifyRowCount({ count: 25 });
// Nested URL
await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: "1" });
await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: '1' });
await dashboard.expandedForm.verify({
header: "Afghanistan",
url: "rowId=1",
header: 'Afghanistan',
url: 'rowId=1',
});
await dashboard.expandedForm.openChildCard({
column: "City List",
title: "Kabul",
column: 'City List',
title: 'Kabul',
});
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.expandedForm.verify({
header: "Kabul",
url: "rowId=1",
header: 'Kabul',
url: 'rowId=1',
});
await dashboard.expandedForm.verifyCount({count: 2});
await dashboard.expandedForm.verifyCount({ count: 2 });
// close child card
await dashboard.expandedForm.cancel();
await dashboard.expandedForm.verify({
header: "Afghanistan",
url: "rowId=1",
header: 'Afghanistan',
url: 'rowId=1',
});
await dashboard.expandedForm.cancel();
}
test("Grid", async () => {
await viewTest("grid");
test('Grid', async () => {
await viewTest('grid');
});
test("Gallery", async () => {
await viewTest("gallery");
test('Gallery', async () => {
await viewTest('gallery');
});
});

44
scripts/playwright/tests/import.spec.ts

@ -1,10 +1,10 @@
import { test } from "@playwright/test";
import { airtableApiBase, airtableApiKey } from "../constants";
import { DashboardPage } from "../pages/Dashboard";
import { quickVerify } from "../quickTests/commonTest";
import setup from "../setup";
import { test } from '@playwright/test';
import { airtableApiBase, airtableApiKey } from '../constants';
import { DashboardPage } from '../pages/Dashboard';
import { quickVerify } from '../quickTests/commonTest';
import setup from '../setup';
test.describe("Import", () => {
test.describe('Import', () => {
let dashboard: DashboardPage;
let context: any;
@ -16,8 +16,8 @@ test.describe("Import", () => {
dashboard = new DashboardPage(page, context.project);
});
test("Airtable", async () => {
await dashboard.treeView.quickImport({ title: "Airtable" });
test('Airtable', async () => {
await dashboard.treeView.quickImport({ title: 'Airtable' });
await dashboard.importAirtable.import({
key: airtableApiKey,
baseId: airtableApiBase,
@ -26,31 +26,31 @@ test.describe("Import", () => {
await quickVerify({ dashboard, airtableImport: true, context });
});
test("CSV", async () => {
await dashboard.treeView.quickImport({ title: "CSV file" });
test('CSV', async () => {
await dashboard.treeView.quickImport({ title: 'CSV file' });
});
test("Excel", async () => {
test('Excel', async () => {
const col = [
{ type: "Number", name: "number" },
{ type: "Decimal", name: "float" },
{ type: "SingleLineText", name: "text" },
{ type: 'Number', name: 'number' },
{ type: 'Decimal', name: 'float' },
{ type: 'SingleLineText', name: 'text' },
];
const expected = [
{ name: "Sheet2", columns: col },
{ name: "Sheet3", columns: col },
{ name: "Sheet4", columns: col },
{ name: 'Sheet2', columns: col },
{ name: 'Sheet3', columns: col },
{ name: 'Sheet4', columns: col },
];
await dashboard.treeView.quickImport({ title: "Microsoft Excel" });
await dashboard.treeView.quickImport({ title: 'Microsoft Excel' });
await dashboard.importTemplate.import({
file: `${process.cwd()}/fixtures/sampleFiles/simple.xlsx`,
result: expected,
});
let recordCells = { Number: "1", Float: "1.10", Text: "abc" };
const recordCells = { Number: '1', Float: '1.10', Text: 'abc' };
for (let [key, value] of Object.entries(recordCells)) {
for (const [key, value] of Object.entries(recordCells)) {
await dashboard.grid.cell.verify({
index: 0,
columnHeader: key,
@ -59,7 +59,7 @@ test.describe("Import", () => {
}
});
test("JSON", async () => {
await dashboard.treeView.quickImport({ title: "JSON file" });
test('JSON', async () => {
await dashboard.treeView.quickImport({ title: 'JSON file' });
});
});

78
scripts/playwright/tests/language.spec.ts

@ -1,44 +1,44 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { ProjectsPage } from "../pages/ProjectsPage";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { ProjectsPage } from '../pages/ProjectsPage';
import setup from '../setup';
const langMenu = [
"help-translate",
"ar.json",
"bn_IN.json",
"da.json",
"de.json",
"en.json",
"es.json",
"fa.json",
"fi.json",
"fr.json",
"he.json",
"hi.json",
"hr.json",
"id.json",
"it.json",
"ja.json",
"ko.json",
"lv.json",
"nl.json",
"no.json",
"pl.json",
"pt.json",
"pt_BR.json",
"ru.json",
"sl.json",
"sv.json",
"th.json",
"tr.json",
"uk.json",
"vi.json",
"zh-Hans.json",
"zh-Hant.json",
'help-translate',
'ar.json',
'bn_IN.json',
'da.json',
'de.json',
'en.json',
'es.json',
'fa.json',
'fi.json',
'fr.json',
'he.json',
'hi.json',
'hr.json',
'id.json',
'it.json',
'ja.json',
'ko.json',
'lv.json',
'nl.json',
'no.json',
'pl.json',
'pt.json',
'pt_BR.json',
'ru.json',
'sl.json',
'sv.json',
'th.json',
'tr.json',
'uk.json',
'vi.json',
'zh-Hans.json',
'zh-Hant.json',
];
test.describe("Common", () => {
test.describe('Common', () => {
let context: any;
let dashboard: DashboardPage;
let projectsPage: ProjectsPage;
@ -49,13 +49,13 @@ test.describe("Common", () => {
projectsPage = new ProjectsPage(page);
});
test("Language", async () => {
test('Language', async () => {
await dashboard.clickHome();
// Index is the order in which menu options appear
for (let i = 1; i < langMenu.length; i++) {
// scripts/playwright/tests/language.spec.ts
let json = require(`../../../packages/nc-gui/lang/${langMenu[i]}`);
const json = require(`../../../packages/nc-gui/lang/${langMenu[i]}`);
await projectsPage.openLanguageMenu();
await projectsPage.selectLanguage({ index: i });
await projectsPage.verifyLanguage({ json });

110
scripts/playwright/tests/metaSync.spec.ts

@ -5,7 +5,7 @@ import setup, { NcContext } from '../setup';
import { isSqlite, mysqlExec, sqliteExec } from '../setup/db';
// todo: Enable when view bug is fixed
test.describe("Meta sync", () => {
test.describe('Meta sync', () => {
let dashboard: DashboardPage;
let settings: SettingsPage;
let context: NcContext;
@ -18,75 +18,69 @@ test.describe("Meta sync", () => {
settings = dashboard.settings;
switch (context.dbType) {
case "sqlite":
case 'sqlite':
dbExec = sqliteExec;
break;
case "mysql":
case 'mysql':
dbExec = mysqlExec;
break;
}
projectPrefix = isSqlite(context) ? context.project.prefix : "";
projectPrefix = isSqlite(context) ? context.project.prefix : '';
});
test("Meta sync", async () => {
test('Meta sync', async () => {
test.setTimeout(process.env.CI ? 100000 : 70000);
await dashboard.gotoSettings();
await settings.selectTab({tab: SettingTab.ProjectMetadata});
await settings.selectTab({ tab: SettingTab.ProjectMetadata });
await dbExec(
`CREATE TABLE ${projectPrefix}table1 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`
);
await dbExec(
`CREATE TABLE ${projectPrefix}table2 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`
);
await dbExec(`CREATE TABLE ${projectPrefix}table1 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`);
await dbExec(`CREATE TABLE ${projectPrefix}table2 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`);
await settings.metaData.clickReload();
await settings.metaData.verifyRow({
index: 16,
model: `${projectPrefix}table1`,
state: "New table",
state: 'New table',
});
await settings.metaData.verifyRow({
index: 17,
model: `${projectPrefix}table2`,
state: "New table",
state: 'New table',
});
await settings.metaData.sync();
await settings.metaData.verifyRow({
index: 16,
model: "Table1",
state: "No change identified",
model: 'Table1',
state: 'No change identified',
});
await settings.metaData.verifyRow({
index: 17,
model: "Table2",
state: "No change identified",
model: 'Table2',
state: 'No change identified',
});
if (!isSqlite(context)) {
// Add relation
await dbExec(
`ALTER TABLE ${projectPrefix}table1 ADD INDEX fk1_idx (col1 ASC) VISIBLE`
);
await dbExec(`ALTER TABLE ${projectPrefix}table1 ADD INDEX fk1_idx (col1 ASC) VISIBLE`);
await dbExec(
`ALTER TABLE ${projectPrefix}table1 ADD CONSTRAINT fk1 FOREIGN KEY (col1) REFERENCES ${projectPrefix}table2 (id) ON DELETE NO ACTION ON UPDATE NO ACTION`
);
await settings.metaData.clickReload();
await settings.metaData.verifyRow({
index: 16,
model: "Table1",
state: "New relation added",
model: 'Table1',
state: 'New relation added',
});
//verify after sync
await settings.metaData.sync();
await settings.metaData.verifyRow({
index: 16,
model: "Table1",
state: "No change identified",
model: 'Table1',
state: 'No change identified',
});
// Remove relation
@ -95,16 +89,16 @@ test.describe("Meta sync", () => {
await settings.metaData.clickReload();
await settings.metaData.verifyRow({
index: 16,
model: "Table1",
state: "Relation removed",
model: 'Table1',
state: 'Relation removed',
});
//verify after sync
await settings.metaData.sync();
await settings.metaData.verifyRow({
index: 16,
model: "Table1",
state: "No change identified",
model: 'Table1',
state: 'No change identified',
});
}
@ -118,15 +112,15 @@ test.describe("Meta sync", () => {
await settings.metaData.verifyRow({
index: 16,
model: `Table1`,
state: "New column(newCol)",
state: 'New column(newCol)',
});
//verify after sync
await settings.metaData.sync();
await settings.metaData.verifyRow({
index: 16,
model: "Table1",
state: "No change identified",
model: 'Table1',
state: 'No change identified',
});
// Edit column
@ -139,15 +133,15 @@ test.describe("Meta sync", () => {
await settings.metaData.verifyRow({
index: 16,
model: `Table1`,
state: "New column(newColName), Column removed(newCol)",
state: 'New column(newColName), Column removed(newCol)',
});
//verify after sync
await settings.metaData.sync();
await settings.metaData.verifyRow({
index: 16,
model: "Table1",
state: "No change identified",
model: 'Table1',
state: 'No change identified',
});
// Delete column
@ -158,15 +152,15 @@ test.describe("Meta sync", () => {
await settings.metaData.verifyRow({
index: 16,
model: `Table1`,
state: "Column removed(newColName)",
state: 'Column removed(newColName)',
});
//verify after sync
await settings.metaData.sync();
await settings.metaData.verifyRow({
index: 16,
model: "Table1",
state: "No change identified",
model: 'Table1',
state: 'No change identified',
});
}
@ -177,12 +171,12 @@ test.describe("Meta sync", () => {
await settings.metaData.verifyRow({
index: 16,
model: `${projectPrefix}table1`,
state: "Table removed",
state: 'Table removed',
});
await settings.metaData.verifyRow({
index: 17,
model: `${projectPrefix}table2`,
state: "Table removed",
state: 'Table removed',
});
//verify after sync
@ -191,29 +185,29 @@ test.describe("Meta sync", () => {
if (isSqlite(context)) {
await settings.metaData.verifyRow({
index: 16,
model: "CustomerList",
state: "No change identified",
model: 'CustomerList',
state: 'No change identified',
});
await settings.metaData.verifyRow({
index: 17,
model: "FilmList",
state: "No change identified",
model: 'FilmList',
state: 'No change identified',
});
} else {
await settings.metaData.verifyRow({
index: 16,
model: "ActorInfo",
state: "No change identified",
model: 'ActorInfo',
state: 'No change identified',
});
await settings.metaData.verifyRow({
index: 17,
model: "CustomerList",
state: "No change identified",
model: 'CustomerList',
state: 'No change identified',
});
}
});
test("Hide, filter, sort", async () => {
test('Hide, filter, sort', async () => {
await dbExec(
`CREATE TABLE ${projectPrefix}table1 (id INT NOT NULL, col1 INT NULL, col2 INT NULL, col3 INT NULL, col4 INT NULL, PRIMARY KEY (id))`
);
@ -222,29 +216,29 @@ test.describe("Meta sync", () => {
);
await dashboard.gotoSettings();
await settings.selectTab({tab: SettingTab.ProjectMetadata});
await settings.selectTab({ tab: SettingTab.ProjectMetadata });
await settings.metaData.clickReload();
await settings.metaData.sync();
await settings.close();
await dashboard.treeView.openTable({ title: "Table1" });
await dashboard.treeView.openTable({ title: 'Table1' });
await dashboard.grid.toolbar.clickFields();
await dashboard.grid.toolbar.fields.click({ title: "Col1" });
await dashboard.grid.toolbar.fields.click({ title: 'Col1' });
await dashboard.grid.toolbar.clickFields();
await dashboard.grid.toolbar.sort.addSort({
columnTitle: "Col1",
columnTitle: 'Col1',
isAscending: false,
isLocallySaved: false
isLocallySaved: false,
});
await dashboard.grid.toolbar.filter.addNew({
columnTitle: "Col1",
opType: ">=",
value: "5",
isLocallySaved: false
columnTitle: 'Col1',
opType: '>=',
value: '5',
isLocallySaved: false,
});
await dashboard.grid.verifyRowCount({ count: 5 });

24
scripts/playwright/tests/pagination.spec.ts

@ -1,26 +1,26 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
test.describe("Grid pagination", () => {
test.describe('Grid pagination', () => {
let dashboard: DashboardPage;
let context: any;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
});
test("Access next page, prev page & offset page", async () => {
test('Access next page, prev page & offset page', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.treeView.openTable({ title: 'Country' });
// click ">" to go to next page
(await dashboard.grid.clickPagination({ page: ">" }));
await dashboard.grid.verifyActivePage({ page: "2" });
await dashboard.grid.clickPagination({ page: '>' });
await dashboard.grid.verifyActivePage({ page: '2' });
// click "<" to go to prev page
(await dashboard.grid.clickPagination({ page: "<" }));
await dashboard.grid.verifyActivePage({ page: "1" });
await dashboard.grid.clickPagination({ page: '<' });
await dashboard.grid.verifyActivePage({ page: '1' });
});
});

24
scripts/playwright/tests/projectOperations.spec.ts

@ -1,10 +1,10 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import { ProjectsPage } from "../pages/ProjectsPage";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
import { ProjectsPage } from '../pages/ProjectsPage';
test.describe("Project operations", () => {
test.describe('Project operations', () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any;
@ -17,17 +17,17 @@ test.describe("Project operations", () => {
toolbar = dashboard.grid.toolbar;
});
test("rename, delete", async () => {
test('rename, delete', async () => {
await dashboard.clickHome();
await projectPage.createProject({ name: "project-1", type: "xcdb" });
await projectPage.createProject({ name: 'project-1', type: 'xcdb' });
await dashboard.clickHome();
await projectPage.renameProject({
title: "project-1",
newTitle: "project-new",
title: 'project-1',
newTitle: 'project-new',
});
await dashboard.clickHome();
await projectPage.openProject({ title: "project-new" });
await projectPage.openProject({ title: 'project-new' });
await dashboard.clickHome();
await projectPage.deleteProject({ title: "project-new" });
await projectPage.deleteProject({ title: 'project-new' });
});
});

74
scripts/playwright/tests/rolesCreate.spec.ts

@ -1,22 +1,18 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import {
SettingsPage,
SettingsSubTab,
SettingTab,
} from "../pages/Dashboard/Settings";
import { SignupPage } from "../pages/SignupPage";
import { ProjectsPage } from "../pages/ProjectsPage";
let roleDb = [
{ email: "creator@nocodb.com", role: "creator", url: "" },
{ email: "editor@nocodb.com", role: "editor", url: "" },
{ email: "commenter@nocodb.com", role: "commenter", url: "" },
{ email: "viewer@nocodb.com", role: "viewer", url: "" },
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
import { SettingsPage, SettingsSubTab, SettingTab } from '../pages/Dashboard/Settings';
import { SignupPage } from '../pages/SignupPage';
import { ProjectsPage } from '../pages/ProjectsPage';
const roleDb = [
{ email: 'creator@nocodb.com', role: 'creator', url: '' },
{ email: 'editor@nocodb.com', role: 'editor', url: '' },
{ email: 'commenter@nocodb.com', role: 'commenter', url: '' },
{ email: 'viewer@nocodb.com', role: 'viewer', url: '' },
];
test.describe("User roles", () => {
test.describe('User roles', () => {
let dashboard: DashboardPage;
let settings: SettingsPage;
let signupPage: SignupPage;
@ -31,11 +27,11 @@ test.describe("User roles", () => {
projectsPage = new ProjectsPage(page);
});
test("Create role", async () => {
test.slow();
test('Create role', async () => {
test.slow();
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.gotoSettings();
await settings.selectTab({ tab: SettingTab.TeamAuth });
for (let i = 0; i < roleDb.length; i++) {
@ -53,12 +49,12 @@ test.describe("User roles", () => {
tab: SettingTab.ProjectMetadata,
subTab: SettingsSubTab.ACL,
});
await settings.acl.toggle({ table: "Language", role: "editor" });
await settings.acl.toggle({ table: "Language", role: "commenter" });
await settings.acl.toggle({ table: "Language", role: "viewer" });
await settings.acl.toggle({ table: "CustomerList", role: "editor" });
await settings.acl.toggle({ table: "CustomerList", role: "commenter" });
await settings.acl.toggle({ table: "CustomerList", role: "viewer" });
await settings.acl.toggle({ table: 'Language', role: 'editor' });
await settings.acl.toggle({ table: 'Language', role: 'commenter' });
await settings.acl.toggle({ table: 'Language', role: 'viewer' });
await settings.acl.toggle({ table: 'CustomerList', role: 'editor' });
await settings.acl.toggle({ table: 'CustomerList', role: 'commenter' });
await settings.acl.toggle({ table: 'CustomerList', role: 'viewer' });
await settings.acl.save();
await settings.close();
@ -74,7 +70,7 @@ test.describe("User roles", () => {
role: roleDb[roleIdx].role,
});
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.viewSidebar.validateRoleAccess({
role: roleDb[roleIdx].role,
@ -99,29 +95,29 @@ test.describe("User roles", () => {
// Access control validation
await dashboard.treeView.verifyTable({
title: "Language",
exists: roleDb[roleIdx].role === "creator" ? true : false,
title: 'Language',
exists: roleDb[roleIdx].role === 'creator' ? true : false,
});
await dashboard.treeView.verifyTable({
title: "CustomerList",
exists: roleDb[roleIdx].role === "creator" ? true : false,
title: 'CustomerList',
exists: roleDb[roleIdx].role === 'creator' ? true : false,
});
}
async function roleSignup(roleIdx: number) {
await dashboard.signOut();
await dashboard.rootPage.goto(roleDb[roleIdx].url);
await signupPage.signUp({
email: roleDb[roleIdx].email,
password: "Password123.",
password: 'Password123.',
});
await projectsPage.openProject({ title: 'externalREST', waitForAuthTab: roleDb[roleIdx].role === "creator" });
await projectsPage.openProject({ title: 'externalREST', waitForAuthTab: roleDb[roleIdx].role === 'creator' });
// close 'Team & Auth' tab
if (roleDb[roleIdx].role === "creator") {
await dashboard.closeTab({ title: "Team & Auth" });
if (roleDb[roleIdx].role === 'creator') {
await dashboard.closeTab({ title: 'Team & Auth' });
}
}
});

54
scripts/playwright/tests/rolesPreview.spec.ts

@ -1,16 +1,12 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import {
SettingsPage,
SettingsSubTab,
SettingTab,
} from "../pages/Dashboard/Settings";
let roles = ["Editor", "Commenter", "Viewer"];
test.describe("Preview Mode", () => {
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
import { SettingsPage, SettingsSubTab, SettingTab } from '../pages/Dashboard/Settings';
const roles = ['Editor', 'Commenter', 'Viewer'];
test.describe('Preview Mode', () => {
test.setTimeout(150000);
let dashboard: DashboardPage;
@ -26,9 +22,9 @@ test.describe("Preview Mode", () => {
settings = dashboard.settings;
});
test("Preview Mode", async () => {
test('Preview Mode', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
// configure ACL
// configure access control
@ -37,12 +33,12 @@ test.describe("Preview Mode", () => {
tab: SettingTab.ProjectMetadata,
subTab: SettingsSubTab.ACL,
});
await settings.acl.toggle({ table: "Language", role: "editor" });
await settings.acl.toggle({ table: "Language", role: "commenter" });
await settings.acl.toggle({ table: "Language", role: "viewer" });
await settings.acl.toggle({ table: "CustomerList", role: "editor" });
await settings.acl.toggle({ table: "CustomerList", role: "commenter" });
await settings.acl.toggle({ table: "CustomerList", role: "viewer" });
await settings.acl.toggle({ table: 'Language', role: 'editor' });
await settings.acl.toggle({ table: 'Language', role: 'commenter' });
await settings.acl.toggle({ table: 'Language', role: 'viewer' });
await settings.acl.toggle({ table: 'CustomerList', role: 'editor' });
await settings.acl.toggle({ table: 'CustomerList', role: 'commenter' });
await settings.acl.toggle({ table: 'CustomerList', role: 'viewer' });
await settings.acl.save();
await settings.close();
@ -55,18 +51,18 @@ test.describe("Preview Mode", () => {
async function roleTest(role: string) {
await dashboard.grid.projectMenu.toggle();
await dashboard.grid.projectMenu.click({
menu: "Preview as",
menu: 'Preview as',
subMenu: role,
});
// wait for preview mode to be enabled
await dashboard.rootPage.locator(".nc-preview-btn-exit-to-app").waitFor();
await dashboard.rootPage.locator('.nc-preview-btn-exit-to-app').waitFor();
await dashboard.validateProjectMenu({
role: role.toLowerCase(),
});
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.viewSidebar.validateRoleAccess({
role: role.toLowerCase(),
@ -91,15 +87,15 @@ test.describe("Preview Mode", () => {
// Access control validation
await dashboard.treeView.verifyTable({
title: "Language",
exists: role.toLowerCase() === "creator" ? true : false,
title: 'Language',
exists: role.toLowerCase() === 'creator' ? true : false,
});
await dashboard.treeView.verifyTable({
title: "CustomerList",
exists: role.toLowerCase() === "creator" ? true : false,
title: 'CustomerList',
exists: role.toLowerCase() === 'creator' ? true : false,
});
// close preview mode
await dashboard.rootPage.locator(".nc-preview-btn-exit-to-app").click();
await dashboard.rootPage.locator('.nc-preview-btn-exit-to-app').click();
}
});

52
scripts/playwright/tests/rolesSuperUser.spec.ts

@ -1,8 +1,8 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
test.describe("Super user", () => {
test.describe('Super user', () => {
let dashboard: DashboardPage;
let context: any;
@ -11,47 +11,39 @@ test.describe("Super user", () => {
dashboard = new DashboardPage(page, context.project);
});
test("AppStore access", async () => {
await dashboard.closeTab({ title: "Team & Auth" });
test('AppStore access', async () => {
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.rootPage.goto("/#/apps");
await dashboard.rootPage.waitForLoadState("load");
await dashboard.rootPage.goto('/#/apps');
await dashboard.rootPage.waitForLoadState('load');
const appPage = await dashboard.rootPage;
// Access slack card
const card = await appPage.locator(".nc-app-store-card-Slack");
const card = await appPage.locator('.nc-app-store-card-Slack');
await card.click();
await card.locator(".nc-app-store-card-install").click();
await card.locator('.nc-app-store-card-install').click();
// Configure slack
let slackModal = await appPage.locator(".nc-modal-plugin-install");
await slackModal
.locator('[placeholder="Channel Name"]')
.fill("Test Channel");
await slackModal
.locator('[placeholder="Webhook URL"]')
.fill("http://test.com");
let slackModal = await appPage.locator('.nc-modal-plugin-install');
await slackModal.locator('[placeholder="Channel Name"]').fill('Test Channel');
await slackModal.locator('[placeholder="Webhook URL"]').fill('http://test.com');
await slackModal.locator('button:has-text("Save")').click();
await dashboard.verifyToast({ message: "Successfully installed" });
await dashboard.verifyToast({ message: 'Successfully installed' });
// Modify configuration
await card.click();
await card.locator(".nc-app-store-card-edit").click();
slackModal = await appPage.locator(".nc-modal-plugin-install");
await slackModal
.locator('[placeholder="Channel Name"]')
.fill("Test Channel 2");
await slackModal
.locator('[placeholder="Webhook URL"]')
.fill("http://test2.com");
await card.locator('.nc-app-store-card-edit').click();
slackModal = await appPage.locator('.nc-modal-plugin-install');
await slackModal.locator('[placeholder="Channel Name"]').fill('Test Channel 2');
await slackModal.locator('[placeholder="Webhook URL"]').fill('http://test2.com');
await slackModal.locator('button:has-text("Save")').click();
await dashboard.verifyToast({ message: "Successfully installed" });
await dashboard.verifyToast({ message: 'Successfully installed' });
// Uninstall
await card.click();
await card.locator(".nc-app-store-card-reset").click();
slackModal = await appPage.locator(".nc-modal-plugin-uninstall");
await card.locator('.nc-app-store-card-reset').click();
slackModal = await appPage.locator('.nc-modal-plugin-uninstall');
await slackModal.locator('button:has-text("Confirm")').click();
await dashboard.verifyToast({ message: "Plugin uninstalled successfully" });
await dashboard.verifyToast({ message: 'Plugin uninstalled successfully' });
});
});

46
scripts/playwright/tests/tableColumnOperation.spec.ts

@ -1,9 +1,9 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { GridPage } from "../pages/Dashboard/Grid";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { GridPage } from '../pages/Dashboard/Grid';
import setup from '../setup';
test.describe("Table Column Operations", () => {
test.describe('Table Column Operations', () => {
let grid: GridPage, dashboard: DashboardPage;
let context: any;
@ -12,35 +12,35 @@ test.describe("Table Column Operations", () => {
dashboard = new DashboardPage(page, context.project);
grid = dashboard.grid;
await dashboard.treeView.createTable({ title: "sheet1" });
await dashboard.treeView.createTable({ title: 'sheet1' });
});
test("Create column", async () => {
await grid.column.create({ title: "column_name_a" });
await grid.column.verify({ title: "column_name_a" });
test('Create column', async () => {
await grid.column.create({ title: 'column_name_a' });
await grid.column.verify({ title: 'column_name_a' });
await grid.column.openEdit({ title: "column_name_a" });
await grid.column.fillTitle({ title: "column_name_b" });
await grid.column.selectType({ type: "LongText" });
await grid.column.openEdit({ title: 'column_name_a' });
await grid.column.fillTitle({ title: 'column_name_b' });
await grid.column.selectType({ type: 'LongText' });
await grid.column.save({ isUpdated: true });
await grid.column.verify({ title: "column_name_b" });
await grid.column.verify({ title: 'column_name_b' });
await grid.column.delete({ title: "column_name_b" });
await grid.column.verify({ title: "column_name_b", isVisible: false });
await grid.column.delete({ title: 'column_name_b' });
await grid.column.verify({ title: 'column_name_b', isVisible: false });
await grid.addNewRow({ index: 0, value: `Row 0` });
await grid.verifyRow({ index: 0 });
await grid.openExpandedRow({ index: 0 });
await dashboard.expandedForm.fillField({
columnTitle: "Title",
value: "value_a",
columnTitle: 'Title',
value: 'value_a',
});
await dashboard.expandedForm.save();
await grid.cell.verify({
index: 0,
columnHeader: "Title",
value: "value_a",
columnHeader: 'Title',
value: 'value_a',
});
await grid.deleteRow(0);
@ -58,14 +58,14 @@ test.describe("Table Column Operations", () => {
// add new row using toolbar button
await grid.toolbar.clickAddNewRow();
await dashboard.expandedForm.fillField({
columnTitle: "Title",
value: "value_a",
columnTitle: 'Title',
value: 'value_a',
});
await dashboard.expandedForm.save();
await grid.cell.verify({
index: 0,
columnHeader: "Title",
value: "value_a",
columnHeader: 'Title',
value: 'value_a',
});
// add new row using right-click menu

46
scripts/playwright/tests/tableOperations.spec.ts

@ -1,9 +1,9 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { SettingsPage, SettingTab } from "../pages/Dashboard/Settings";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { SettingsPage, SettingTab } from '../pages/Dashboard/Settings';
import setup from '../setup';
test.describe("Table Operations", () => {
test.describe('Table Operations', () => {
let dashboard: DashboardPage, settings: SettingsPage;
let context: any;
@ -13,38 +13,38 @@ test.describe("Table Operations", () => {
settings = dashboard.settings;
});
test("Create, and delete table, verify in audit tab, rename City table and reorder tables", async () => {
await dashboard.treeView.createTable({ title: "tablex" });
await dashboard.treeView.verifyTable({ title: "tablex" });
test('Create, and delete table, verify in audit tab, rename City table and reorder tables', async () => {
await dashboard.treeView.createTable({ title: 'tablex' });
await dashboard.treeView.verifyTable({ title: 'tablex' });
await dashboard.treeView.deleteTable({ title: "tablex" });
await dashboard.treeView.verifyTable({ title: "tablex", exists: false });
await dashboard.treeView.deleteTable({ title: 'tablex' });
await dashboard.treeView.verifyTable({ title: 'tablex', exists: false });
await dashboard.gotoSettings();
await settings.selectTab({ tab: SettingTab.Audit });
await settings.audit.verifyRow({
index: 0,
opType: "TABLE",
opSubtype: "DELETED",
user: "user@nocodb.com",
opType: 'TABLE',
opSubtype: 'DELETED',
user: 'user@nocodb.com',
});
await settings.audit.verifyRow({
index: 1,
opType: "TABLE",
opSubtype: "CREATED",
user: "user@nocodb.com",
opType: 'TABLE',
opSubtype: 'CREATED',
user: 'user@nocodb.com',
});
await settings.close();
await dashboard.treeView.renameTable({ title: "City", newTitle: "Cityx" });
await dashboard.treeView.verifyTable({ title: "Cityx" });
await dashboard.treeView.renameTable({ title: 'City', newTitle: 'Cityx' });
await dashboard.treeView.verifyTable({ title: 'Cityx' });
await dashboard.treeView.focusTable({ title: "Actor" });
await dashboard.treeView.verifyTable({ title: "Actor", index: 0 });
await dashboard.treeView.focusTable({ title: 'Actor' });
await dashboard.treeView.verifyTable({ title: 'Actor', index: 0 });
await dashboard.treeView.reorderTables({
sourceTable: "Actor",
destinationTable: "Address",
sourceTable: 'Actor',
destinationTable: 'Address',
});
await dashboard.treeView.verifyTable({ title: "Address", index: 0 });
await dashboard.treeView.verifyTable({ title: 'Address', index: 0 });
});
});

51
scripts/playwright/tests/toolbarOperations.spec.ts

@ -1,17 +1,16 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
import setup from '../setup';
test.describe("Toolbar operations (GRID)", () => {
test.describe('Toolbar operations (GRID)', () => {
let dashboard: DashboardPage, toolbar: ToolbarPage;
let context: any;
async function validateFirstRow(value: string) {
await dashboard.grid.cell.verify({
index: 0,
columnHeader: "Country",
columnHeader: 'Country',
value: value,
});
}
@ -22,53 +21,53 @@ test.describe("Toolbar operations (GRID)", () => {
toolbar = dashboard.grid.toolbar;
});
test("Hide, Sort, Filter", async () => {
test('Hide, Sort, Filter', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.column.verify({
title: "LastUpdate",
title: 'LastUpdate',
isVisible: true,
});
// hide column
await toolbar.fields.toggle({ title: "LastUpdate" });
await toolbar.fields.toggle({ title: 'LastUpdate' });
await dashboard.grid.column.verify({
title: "LastUpdate",
title: 'LastUpdate',
isVisible: false,
});
// un-hide column
await toolbar.fields.toggle({ title: "LastUpdate" });
await toolbar.fields.toggle({ title: 'LastUpdate' });
await dashboard.grid.column.verify({
title: "LastUpdate",
title: 'LastUpdate',
isVisible: true,
});
await validateFirstRow("Afghanistan");
await validateFirstRow('Afghanistan');
// Sort column
await toolbar.sort.addSort({ columnTitle: "Country", isAscending: false, isLocallySaved: false });
await validateFirstRow("Zambia");
await toolbar.sort.addSort({ columnTitle: 'Country', isAscending: false, isLocallySaved: false });
await validateFirstRow('Zambia');
// reset sort
await toolbar.sort.resetSort();
await validateFirstRow("Afghanistan");
await validateFirstRow('Afghanistan');
// Filter column
await toolbar.filter.addNew({
columnTitle: "Country",
value: "India",
opType: "is equal",
isLocallySaved: false
columnTitle: 'Country',
value: 'India',
opType: 'is equal',
isLocallySaved: false,
});
await validateFirstRow("India");
await validateFirstRow('India');
// Reset filter
await toolbar.filter.resetFilter();
await validateFirstRow("Afghanistan");
await validateFirstRow('Afghanistan');
await dashboard.closeTab({ title: "Country" });
await dashboard.closeTab({ title: 'Country' });
});
});

61
scripts/playwright/tests/utils/sakila.ts

@ -1,21 +1,58 @@
const mysqlSakilaTables = [
'actor', 'address', 'category', 'city', 'country', 'customer', 'film', 'film_text', 'language', 'payment', 'rental', 'staff'
]
'actor',
'address',
'category',
'city',
'country',
'customer',
'film',
'film_text',
'language',
'payment',
'rental',
'staff',
];
const mysqlSakilaSqlViews = [
'actor_info', 'customer_list', 'film_list', 'nicer_but_slower_film_list', 'sales_by_film_category', 'sales_by_store', 'staff_list'
]
'actor_info',
'customer_list',
'film_list',
'nicer_but_slower_film_list',
'sales_by_film_category',
'sales_by_store',
'staff_list',
];
const pgSakilaTables = [
'actor', 'address', 'category', 'city', 'country', 'customer', 'film', 'language', 'payment', 'payment_p2007_01', 'payment_p2007_02', 'payment_p2007_03', 'payment_p2007_04', 'payment_p2007_05', 'payment_p2007_06', 'rental', 'staff'
]
'actor',
'address',
'category',
'city',
'country',
'customer',
'film',
'language',
'payment',
'payment_p2007_01',
'payment_p2007_02',
'payment_p2007_03',
'payment_p2007_04',
'payment_p2007_05',
'payment_p2007_06',
'rental',
'staff',
];
const pgSakilaSqlViews = [
'actor_info', 'customer_list', 'film_list', 'nicer_but_slower_film_list', 'sales_by_film_category', 'sales_by_store', 'staff_list'
]
'actor_info',
'customer_list',
'film_list',
'nicer_but_slower_film_list',
'sales_by_film_category',
'sales_by_store',
'staff_list',
];
const sqliteSakilaSqlViews = [
'customer_list', 'film_list', 'staff_list', 'sales_by_store', 'sales_by_film_category'
]
const sqliteSakilaSqlViews = ['customer_list', 'film_list', 'staff_list', 'sales_by_store', 'sales_by_film_category'];
export { mysqlSakilaTables, mysqlSakilaSqlViews, pgSakilaTables, pgSakilaSqlViews, sqliteSakilaSqlViews }
export { mysqlSakilaTables, mysqlSakilaSqlViews, pgSakilaTables, pgSakilaSqlViews, sqliteSakilaSqlViews };

124
scripts/playwright/tests/viewForm.spec.ts

@ -1,11 +1,11 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { SettingTab } from "../pages/Dashboard/Settings";
import setup from "../setup";
import { FormPage } from "../pages/Dashboard/Form";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { SettingTab } from '../pages/Dashboard/Settings';
import setup from '../setup';
import { FormPage } from '../pages/Dashboard/Form';
// todo: Move most of the ui actions to page object and await on the api response
test.describe("Form view", () => {
test.describe('Form view', () => {
let dashboard: DashboardPage;
let form: FormPage;
let context: any;
@ -16,105 +16,105 @@ test.describe("Form view", () => {
form = dashboard.form;
});
test("Field re-order operations", async () => {
test('Field re-order operations', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.viewSidebar.createFormView({ title: "CountryForm" });
await dashboard.viewSidebar.verifyView({ title: "CountryForm", index: 1 });
await dashboard.viewSidebar.createFormView({ title: 'CountryForm' });
await dashboard.viewSidebar.verifyView({ title: 'CountryForm', index: 1 });
// verify form-view fields order
await form.verifyFormViewFieldsOrder({
fields: ["Country", "LastUpdate", "City List"],
fields: ['Country', 'LastUpdate', 'City List'],
});
// reorder & verify
await form.reorderFields({
sourceField: "LastUpdate",
destinationField: "Country",
sourceField: 'LastUpdate',
destinationField: 'Country',
});
await form.verifyFormViewFieldsOrder({
fields: ["LastUpdate", "Country", "City List"],
fields: ['LastUpdate', 'Country', 'City List'],
});
// remove & verify (drag-drop)
await form.removeField({ field: "City List", mode: "dragDrop" });
await form.removeField({ field: 'City List', mode: 'dragDrop' });
await form.verifyFormViewFieldsOrder({
fields: ["LastUpdate", "Country"],
fields: ['LastUpdate', 'Country'],
});
// add & verify (drag-drop)
await form.addField({ field: "City List", mode: "dragDrop" });
await form.addField({ field: 'City List', mode: 'dragDrop' });
await form.verifyFormViewFieldsOrder({
fields: ["LastUpdate", "City List", "Country"],
fields: ['LastUpdate', 'City List', 'Country'],
});
// remove & verify (hide field button)
await form.removeField({ field: "City List", mode: "hideField" });
await form.removeField({ field: 'City List', mode: 'hideField' });
await form.verifyFormViewFieldsOrder({
fields: ["LastUpdate", "Country"],
fields: ['LastUpdate', 'Country'],
});
// add & verify (hide field button)
await form.addField({ field: "City List", mode: "clickField" });
await form.addField({ field: 'City List', mode: 'clickField' });
await form.verifyFormViewFieldsOrder({
fields: ["LastUpdate", "Country", "City List"],
fields: ['LastUpdate', 'Country', 'City List'],
});
// remove-all & verify
await form.removeAllFields();
await dashboard.rootPage.waitForTimeout(2000);
await form.verifyFormViewFieldsOrder({
fields: ["Country"],
fields: ['Country'],
});
// // add-all & verify
await form.addAllFields();
await dashboard.rootPage.waitForTimeout(2000);
await form.verifyFormViewFieldsOrder({
fields: ["LastUpdate", "Country", "City List"],
fields: ['LastUpdate', 'Country', 'City List'],
});
});
test("Form elements validation", async () => {
test('Form elements validation', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.viewSidebar.createFormView({ title: "CountryForm" });
await dashboard.viewSidebar.verifyView({ title: "CountryForm", index: 1 });
await dashboard.viewSidebar.createFormView({ title: 'CountryForm' });
await dashboard.viewSidebar.verifyView({ title: 'CountryForm', index: 1 });
await form.configureHeader({
title: "Country",
subtitle: "Country subtitle",
title: 'Country',
subtitle: 'Country subtitle',
});
await form.verifyHeader({
title: "Country",
subtitle: "Country subtitle",
title: 'Country',
subtitle: 'Country subtitle',
});
// configure field title & description
await form.configureField({
field: "Country",
label: "Country new title",
helpText: "Country new description",
field: 'Country',
label: 'Country new title',
helpText: 'Country new description',
required: true,
});
await form.verifyFormFieldLabel({
index: 0,
label: "Country new title",
label: 'Country new title',
});
await form.verifyFormFieldHelpText({
index: 0,
helpText: "Country new description",
helpText: 'Country new description',
});
// revert configurations
await form.configureField({
field: "Country",
label: "Country",
helpText: "",
field: 'Country',
label: 'Country',
helpText: '',
required: true,
});
@ -122,27 +122,27 @@ test.describe("Form view", () => {
await form.removeAllFields();
// submit default form validation
await form.fillForm([{ field: "Country", value: "_abc" }]);
await form.fillForm([{ field: 'Country', value: '_abc' }]);
await form.submitForm();
await form.verifyStatePostSubmit({
message: "Successfully submitted form data",
message: 'Successfully submitted form data',
});
// submit custom form validation
await dashboard.viewSidebar.openView({ title: "CountryForm" });
await dashboard.viewSidebar.openView({ title: 'CountryForm' });
await form.configureSubmitMessage({
message: "Custom submit message",
message: 'Custom submit message',
});
await form.fillForm([{ field: "Country", value: "_abc" }]);
await form.fillForm([{ field: 'Country', value: '_abc' }]);
await form.submitForm();
await form.verifyStatePostSubmit({
message: "Custom submit message",
message: 'Custom submit message',
});
// enable 'submit another form' option
await dashboard.viewSidebar.openView({ title: "CountryForm" });
await dashboard.viewSidebar.openView({ title: 'CountryForm' });
await form.showAnotherFormRadioButton.click();
await form.fillForm([{ field: "Country", value: "_abc" }]);
await form.fillForm([{ field: 'Country', value: '_abc' }]);
await form.submitForm();
await dashboard.rootPage.waitForTimeout(2000);
await form.verifyStatePostSubmit({
@ -153,8 +153,8 @@ test.describe("Form view", () => {
// enable 'show another form' option
await form.showAnotherFormRadioButton.click();
await form.showAnotherFormAfter5SecRadioButton.click();
await form.fillForm([{ field: "Country", value: "_abc" }]);
await form.fillForm([{ field: "Country", value: "_abc" }]);
await form.fillForm([{ field: 'Country', value: '_abc' }]);
await form.fillForm([{ field: 'Country', value: '_abc' }]);
await form.submitForm();
await dashboard.rootPage.waitForTimeout(6000);
await form.verifyStatePostSubmit({
@ -165,27 +165,25 @@ test.describe("Form view", () => {
await form.showAnotherFormAfter5SecRadioButton.click();
await form.emailMeRadioButton.click();
await dashboard.verifyToast({
message:
"Please activate SMTP plugin in App store for enabling email notification",
message: 'Please activate SMTP plugin in App store for enabling email notification',
});
// activate SMTP plugin
await dashboard.gotoSettings();
await dashboard.settings.selectTab({ tab: SettingTab.AppStore });
await dashboard.settings.appStore.install({ name: "SMTP" });
await dashboard.settings.appStore.install({ name: 'SMTP' });
await dashboard.settings.appStore.configureSMTP({
email: "a@b.com",
host: "smtp.gmail.com",
port: "587",
email: 'a@b.com',
host: 'smtp.gmail.com',
port: '587',
});
await dashboard.verifyToast({
message:
"Successfully installed and email notification will use SMTP configuration",
message: 'Successfully installed and email notification will use SMTP configuration',
});
await dashboard.settings.close();
// enable 'email-me' option
await dashboard.viewSidebar.openView({ title: "CountryForm" });
await dashboard.viewSidebar.openView({ title: 'CountryForm' });
await form.emailMeRadioButton.click();
await form.verifyAfterSubmitMenuState({
emailMe: true,
@ -196,10 +194,10 @@ test.describe("Form view", () => {
// reset SMTP
await dashboard.gotoSettings();
await dashboard.settings.selectTab({ tab: SettingTab.AppStore });
await dashboard.settings.appStore.uninstall({ name: "SMTP" });
await dashboard.settings.appStore.uninstall({ name: 'SMTP' });
await dashboard.verifyToast({
message: "Plugin uninstalled successfully",
message: 'Plugin uninstalled successfully',
});
await dashboard.settings.close();
});

60
scripts/playwright/tests/viewFormShareSurvey.spec.ts

@ -1,9 +1,9 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { SurveyFormPage } from "../pages/Dashboard/SurveyForm";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { SurveyFormPage } from '../pages/Dashboard/SurveyForm';
import setup from '../setup';
test.describe("Share form", () => {
test.describe('Share form', () => {
let dashboard: DashboardPage;
let surveyForm: SurveyFormPage;
let context: any;
@ -13,20 +13,20 @@ test.describe("Share form", () => {
dashboard = new DashboardPage(page, context.project);
});
test("Survey", async () => {
test('Survey', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.viewSidebar.createFormView({
title: "Country Form",
title: 'Country Form',
});
await dashboard.form.configureHeader({
title: "Country Title",
subtitle: "Country Form Subtitle",
title: 'Country Title',
subtitle: 'Country Form Subtitle',
});
await dashboard.form.configureSubmitMessage({
message: "Thank you for submitting the form",
message: 'Thank you for submitting the form',
});
await dashboard.form.showAnotherFormRadioButton.click();
await dashboard.form.showAnotherFormAfter5SecRadioButton.click();
@ -40,39 +40,39 @@ test.describe("Share form", () => {
surveyForm = new SurveyFormPage(dashboard.rootPage);
await surveyForm.validate({
heading: "Country Title",
subHeading: "Country Form Subtitle",
fieldLabel: "Country *",
footer: "1 / 3",
heading: 'Country Title',
subHeading: 'Country Form Subtitle',
fieldLabel: 'Country *',
footer: '1 / 3',
});
await surveyForm.fill({
fieldLabel: "Country",
value: "New Country",
type: "SingleLineText",
fieldLabel: 'Country',
value: 'New Country',
type: 'SingleLineText',
});
await surveyForm.validate({
heading: "Country Title",
subHeading: "Country Form Subtitle",
fieldLabel: "LastUpdate",
footer: "2 / 3",
heading: 'Country Title',
subHeading: 'Country Form Subtitle',
fieldLabel: 'LastUpdate',
footer: '2 / 3',
});
await surveyForm.fill({
fieldLabel: "LastUpdate",
type: "DateTime",
fieldLabel: 'LastUpdate',
type: 'DateTime',
});
await surveyForm.validate({
heading: "Country Title",
subHeading: "Country Form Subtitle",
fieldLabel: "City List",
footer: "3 / 3",
heading: 'Country Title',
subHeading: 'Country Form Subtitle',
fieldLabel: 'City List',
footer: '3 / 3',
});
await surveyForm.submitButton.click();
// validate post submit data
await surveyForm.validateSuccessMessage({
message: "Thank you for submitting the form",
message: 'Thank you for submitting the form',
showAnotherForm: true,
});
});

159
scripts/playwright/tests/viewGridShare.spec.ts

@ -1,8 +1,8 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
test.describe("Shared view", () => {
test.describe('Shared view', () => {
let dashboard: DashboardPage;
let context: any;
@ -14,7 +14,7 @@ test.describe("Shared view", () => {
dashboard = new DashboardPage(page, context.project);
});
test("Grid share ", async ({ page }) => {
test('Grid share ', async ({ page }) => {
/**
* 1. Create Shared view
* - hide column
@ -27,23 +27,23 @@ test.describe("Shared view", () => {
**/
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Address" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Address' });
// hide column
await dashboard.grid.toolbar.fields.toggle({ title: "Address2" });
await dashboard.grid.toolbar.fields.toggle({ title: 'Address2' });
// sort
await dashboard.grid.toolbar.sort.addSort({
columnTitle: "District",
columnTitle: 'District',
isAscending: false,
isLocallySaved: false
isLocallySaved: false,
});
// filter
await dashboard.grid.toolbar.filter.addNew({
columnTitle: "Address",
value: "Ab",
opType: "is like",
isLocallySaved: false
columnTitle: 'Address',
value: 'Ab',
opType: 'is like',
isLocallySaved: false,
});
mainPageLink = page.url();
@ -63,39 +63,39 @@ test.describe("Shared view", () => {
await page.goto(sharedLink);
const sharedPage = new DashboardPage(page, context.project);
let expectedColumns = [
{ title: "Address", isVisible: true },
{ title: "Address2", isVisible: false },
{ title: "District", isVisible: true },
{ title: "City", isVisible: true },
{ title: "PostalCode", isVisible: true },
{ title: "Phone", isVisible: true },
{ title: "LastUpdate", isVisible: true },
{ title: "Customer List", isVisible: true },
{ title: "Staff List", isVisible: true },
{ title: "City", isVisible: true },
const expectedColumns = [
{ title: 'Address', isVisible: true },
{ title: 'Address2', isVisible: false },
{ title: 'District', isVisible: true },
{ title: 'City', isVisible: true },
{ title: 'PostalCode', isVisible: true },
{ title: 'Phone', isVisible: true },
{ title: 'LastUpdate', isVisible: true },
{ title: 'Customer List', isVisible: true },
{ title: 'Staff List', isVisible: true },
{ title: 'City', isVisible: true },
];
for (const column of expectedColumns) {
await sharedPage.grid.column.verify(column);
}
const expectedRecords = [
{ index: 0, columnHeader: "Address", value: "1013 Tabuk Boulevard" },
{ index: 0, columnHeader: 'Address', value: '1013 Tabuk Boulevard' },
{
index: 1,
columnHeader: "Address",
value: "1892 Nabereznyje Telny Lane",
columnHeader: 'Address',
value: '1892 Nabereznyje Telny Lane',
},
{ index: 2, columnHeader: "Address", value: "1993 Tabuk Lane" },
{ index: 0, columnHeader: "District", value: "West Bengali" },
{ index: 1, columnHeader: "District", value: "Tutuila" },
{ index: 2, columnHeader: "District", value: "Tamil Nadu" },
{ index: 0, columnHeader: "PostalCode", value: "96203" },
{ index: 1, columnHeader: "PostalCode", value: "28396" },
{ index: 2, columnHeader: "PostalCode", value: "64221" },
{ index: 0, columnHeader: "Phone", value: "158399646978" },
{ index: 1, columnHeader: "Phone", value: "478229987054" },
{ index: 2, columnHeader: "Phone", value: "648482415405" },
{ index: 2, columnHeader: 'Address', value: '1993 Tabuk Lane' },
{ index: 0, columnHeader: 'District', value: 'West Bengali' },
{ index: 1, columnHeader: 'District', value: 'Tutuila' },
{ index: 2, columnHeader: 'District', value: 'Tamil Nadu' },
{ index: 0, columnHeader: 'PostalCode', value: '96203' },
{ index: 1, columnHeader: 'PostalCode', value: '28396' },
{ index: 2, columnHeader: 'PostalCode', value: '64221' },
{ index: 0, columnHeader: 'Phone', value: '158399646978' },
{ index: 1, columnHeader: 'Phone', value: '478229987054' },
{ index: 2, columnHeader: 'Phone', value: '648482415405' },
];
// verify order of records (original sort & filter)
@ -103,10 +103,10 @@ test.describe("Shared view", () => {
await sharedPage.grid.cell.verify(record);
}
const expectedVirtualRecords = [
{ index: 0, columnHeader: "Customer List", count: 1, value: ["2"] },
{ index: 1, columnHeader: "Customer List", count: 1, value: ["2"] },
{ index: 0, columnHeader: "City", count: 1, value: ["Kanchrapara"] },
{ index: 1, columnHeader: "City", count: 1, value: ["Tafuna"] },
{ index: 0, columnHeader: 'Customer List', count: 1, value: ['2'] },
{ index: 1, columnHeader: 'Customer List', count: 1, value: ['2'] },
{ index: 0, columnHeader: 'City', count: 1, value: ['Kanchrapara'] },
{ index: 1, columnHeader: 'City', count: 1, value: ['Tafuna'] },
];
// verify virtual records
@ -123,17 +123,17 @@ test.describe("Shared view", () => {
// create new sort & filter criteria in shared view
await sharedPage.grid.toolbar.sort.addSort({
columnTitle: "Address",
columnTitle: 'Address',
isAscending: true,
isLocallySaved: true,
});
await sharedPage.grid.toolbar.filter.addNew({
columnTitle: "District",
value: "Ta",
opType: "is like",
columnTitle: 'District',
value: 'Ta',
opType: 'is like',
isLocallySaved: true,
});
await sharedPage.grid.toolbar.fields.toggle({ title: "LastUpdate", isLocallySaved: true });
await sharedPage.grid.toolbar.fields.toggle({ title: 'LastUpdate', isLocallySaved: true });
expectedColumns[6].isVisible = false;
// verify new sort & filter criteria
@ -142,18 +142,18 @@ test.describe("Shared view", () => {
}
const expectedRecords2 = [
{ index: 0, columnHeader: "Address", value: "1661 Abha Drive" },
{ index: 1, columnHeader: "Address", value: "1993 Tabuk Lane" },
{ index: 2, columnHeader: "Address", value: "381 Kabul Way" },
{ index: 0, columnHeader: "District", value: "Tamil Nadu" },
{ index: 1, columnHeader: "District", value: "Tamil Nadu" },
{ index: 2, columnHeader: "District", value: "Taipei" },
{ index: 0, columnHeader: "PostalCode", value: "14400" },
{ index: 1, columnHeader: "PostalCode", value: "64221" },
{ index: 2, columnHeader: "PostalCode", value: "87272" },
{ index: 0, columnHeader: "Phone", value: "270456873752" },
{ index: 1, columnHeader: "Phone", value: "648482415405" },
{ index: 2, columnHeader: "Phone", value: "55477302294" },
{ index: 0, columnHeader: 'Address', value: '1661 Abha Drive' },
{ index: 1, columnHeader: 'Address', value: '1993 Tabuk Lane' },
{ index: 2, columnHeader: 'Address', value: '381 Kabul Way' },
{ index: 0, columnHeader: 'District', value: 'Tamil Nadu' },
{ index: 1, columnHeader: 'District', value: 'Tamil Nadu' },
{ index: 2, columnHeader: 'District', value: 'Taipei' },
{ index: 0, columnHeader: 'PostalCode', value: '14400' },
{ index: 1, columnHeader: 'PostalCode', value: '64221' },
{ index: 2, columnHeader: 'PostalCode', value: '87272' },
{ index: 0, columnHeader: 'Phone', value: '270456873752' },
{ index: 1, columnHeader: 'Phone', value: '648482415405' },
{ index: 2, columnHeader: 'Phone', value: '55477302294' },
];
// verify order of records (original sort & filter)
@ -167,13 +167,10 @@ test.describe("Shared view", () => {
**/
// verify download
await sharedPage.grid.toolbar.clickDownload(
"Download as CSV",
"./expectedData.txt"
);
await sharedPage.grid.toolbar.clickDownload('Download as CSV', './expectedData.txt');
});
test("Shared view: password", async ({ page }) => {
test('Shared view: password', async ({ page }) => {
/**
* 5. Enable shared view password, disable download: verify
* - Incorrect password
@ -182,12 +179,12 @@ test.describe("Shared view", () => {
* - Add new record & column after shared view creation; verify
**/
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' });
// enable password & verify share link
await dashboard.grid.toolbar.clickShareView();
await dashboard.grid.toolbar.shareView.enablePassword("p@ssword");
await dashboard.grid.toolbar.shareView.enablePassword('p@ssword');
// disable download
await dashboard.grid.toolbar.shareView.toggleDownload();
@ -196,12 +193,12 @@ test.describe("Shared view", () => {
// add new column, record after share view creation
await dashboard.grid.column.create({
title: "New Column",
title: 'New Column',
});
await dashboard.grid.addNewRow({
index: 25,
columnHeader: "Country",
value: "New Country",
columnHeader: 'Country',
value: 'New Country',
});
await page.goto(sharedLink);
@ -209,16 +206,12 @@ test.describe("Shared view", () => {
// todo: Create shared view page
// verify if password request modal exists
const sharedPage2 = new DashboardPage(page, context.project);
await sharedPage2.rootPage
.locator('input[placeholder="Enter password"]')
.fill("incorrect p@ssword");
await sharedPage2.rootPage.locator('input[placeholder="Enter password"]').fill('incorrect p@ssword');
await sharedPage2.rootPage.click('button:has-text("Unlock")');
await sharedPage2.verifyToast({ message: "INVALID_SHARED_VIEW_PASSWORD" });
await sharedPage2.verifyToast({ message: 'INVALID_SHARED_VIEW_PASSWORD' });
// correct password
await sharedPage2.rootPage
.locator('input[placeholder="Enter password"]')
.fill("p@ssword");
await sharedPage2.rootPage.locator('input[placeholder="Enter password"]').fill('p@ssword');
await sharedPage2.rootPage.click('button:has-text("Unlock")');
// verify if download button is disabled
@ -226,19 +219,19 @@ test.describe("Shared view", () => {
// verify new column & record
await sharedPage2.grid.column.verify({
title: "New Column",
title: 'New Column',
isVisible: true,
});
await sharedPage2.grid.toolbar.filter.addNew({
columnTitle: "Country",
value: "New Country",
opType: "is like",
columnTitle: 'Country',
value: 'New Country',
opType: 'is like',
isLocallySaved: true,
});
await sharedPage2.grid.cell.verify({
index: 0,
columnHeader: "Country",
value: "New Country",
columnHeader: 'Country',
value: 'New Country',
});
});
});

118
scripts/playwright/tests/viewKanban.spec.ts

@ -1,10 +1,10 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
import setup from "../setup";
import setup from '../setup';
test.describe("View", () => {
test.describe('View', () => {
let dashboard: DashboardPage, toolbar: ToolbarPage;
let context: any;
@ -14,29 +14,29 @@ test.describe("View", () => {
toolbar = toolbar = dashboard.kanban.toolbar;
});
test("Kanban", async () => {
test('Kanban', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Film" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Film' });
await dashboard.viewSidebar.createKanbanView({
title: "Film Kanban",
title: 'Film Kanban',
});
await dashboard.viewSidebar.verifyView({
title: "Film Kanban",
title: 'Film Kanban',
index: 1,
});
// configure stack-by field
await toolbar.clickStackByField();
await toolbar.stackBy.click({ title: "Rating" });
await toolbar.stackBy.click({ title: 'Rating' });
// click again to close menu
await toolbar.clickStackByField();
const kanban = dashboard.kanban;
await kanban.verifyStackCount({ count: 6 });
await kanban.verifyStackOrder({
order: ["Uncategorized", "G", "PG", "PG-13", "R", "NC-17"],
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
});
await kanban.verifyStackFooter({
count: [0, 178, 194, 223, 195, 210],
@ -47,18 +47,18 @@ test.describe("View", () => {
// hide fields
await toolbar.fields.hideAll();
await toolbar.fields.toggle({ title: "Title" });
await toolbar.fields.toggle({ title: 'Title' });
await kanban.verifyCardCount({
count: [0, 25, 25, 25, 25, 25],
});
// verify card order
const order = [
["ACE GOLDFINGER", "AFFAIR PREJUDICE", "AFRICAN EGG"],
["ACADEMY DINOSAUR", "AGENT TRUMAN", "ALASKA PHANTOM"],
["AIRPLANE SIERRA", "ALABAMA DEVIL", "ALTER VICTORY"],
["AIRPORT POLLOCK", "ALONE TRIP", "AMELIE HELLFIGHTERS"],
["ADAPTATION HOLES", "ALADDIN CALENDAR", "ALICE FANTASIA"],
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
['AIRPLANE SIERRA', 'ALABAMA DEVIL', 'ALTER VICTORY'],
['AIRPORT POLLOCK', 'ALONE TRIP', 'AMELIE HELLFIGHTERS'],
['ADAPTATION HOLES', 'ALADDIN CALENDAR', 'ALICE FANTASIA'],
];
for (let i = 1; i <= order.length; i++)
await kanban.verifyCardOrder({
@ -78,7 +78,7 @@ test.describe("View", () => {
to: 2, // PG
});
await kanban.verifyStackOrder({
order: ["Uncategorized", "PG", "G", "PG-13", "R", "NC-17"],
order: ['Uncategorized', 'PG', 'G', 'PG-13', 'R', 'NC-17'],
});
// verify drag drop stack
await kanban.dragDropStack({
@ -86,19 +86,19 @@ test.describe("View", () => {
to: 1, // PG
});
await kanban.verifyStackOrder({
order: ["Uncategorized", "G", "PG", "PG-13", "R", "NC-17"],
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
});
// verify sort
await toolbar.sort.addSort({
columnTitle: "Title",
columnTitle: 'Title',
isAscending: false,
isLocallySaved: false,
});
// verify card order
const order2 = [
["YOUNG LANGUAGE", "WEST LION"],
["WORST BANGER", "WORDS HUNTER"],
['YOUNG LANGUAGE', 'WEST LION'],
['WORST BANGER', 'WORDS HUNTER'],
];
for (let i = 1; i <= order2.length; i++)
await kanban.verifyCardOrder({
@ -108,8 +108,8 @@ test.describe("View", () => {
await toolbar.sort.resetSort();
// verify card order
const order3 = [
["ACE GOLDFINGER", "AFFAIR PREJUDICE", "AFRICAN EGG"],
["ACADEMY DINOSAUR", "AGENT TRUMAN", "ALASKA PHANTOM"],
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
];
for (let i = 1; i <= order3.length; i++)
await kanban.verifyCardOrder({
@ -119,15 +119,15 @@ test.describe("View", () => {
// verify filter
await toolbar.filter.addNew({
columnTitle: "Title",
opType: "is like",
value: "BA",
columnTitle: 'Title',
opType: 'is like',
value: 'BA',
isLocallySaved: false,
});
// verify card order
const order4 = [
["BAKED CLEOPATRA", "BALLROOM MOCKINGBIRD"],
["ARIZONA BANG", "EGYPT TENENBAUMS"],
['BAKED CLEOPATRA', 'BALLROOM MOCKINGBIRD'],
['ARIZONA BANG', 'EGYPT TENENBAUMS'],
];
for (let i = 1; i <= order4.length; i++)
await kanban.verifyCardOrder({
@ -136,8 +136,8 @@ test.describe("View", () => {
});
await toolbar.filter.resetFilter();
const order5 = [
["ACE GOLDFINGER", "AFFAIR PREJUDICE", "AFRICAN EGG"],
["ACADEMY DINOSAUR", "AGENT TRUMAN", "ALASKA PHANTOM"],
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
];
for (let i = 1; i <= order5.length; i++)
await kanban.verifyCardOrder({
@ -148,43 +148,43 @@ test.describe("View", () => {
await dashboard.rootPage.waitForTimeout(1000);
});
test("Kanban view operations", async () => {
test('Kanban view operations', async () => {
test.slow();
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Film" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Film' });
await dashboard.viewSidebar.createKanbanView({
title: "Film Kanban",
title: 'Film Kanban',
});
await dashboard.viewSidebar.verifyView({
title: "Film Kanban",
title: 'Film Kanban',
index: 1,
});
await toolbar.sort.addSort({
columnTitle: "Title",
columnTitle: 'Title',
isAscending: false,
isLocallySaved: false,
});
await toolbar.filter.addNew({
columnTitle: "Title",
opType: "is like",
value: "BA",
columnTitle: 'Title',
opType: 'is like',
value: 'BA',
isLocallySaved: false,
});
await toolbar.fields.hideAll();
await toolbar.fields.toggle({ title: "Title" });
await toolbar.fields.toggle({ title: 'Title' });
await dashboard.viewSidebar.copyView({ title: "Film Kanban" });
await dashboard.viewSidebar.copyView({ title: 'Film Kanban' });
await dashboard.viewSidebar.verifyView({
title: "Kanban-1",
title: 'Kanban-1',
index: 2,
});
const kanban = dashboard.kanban;
await kanban.verifyStackCount({ count: 6 });
await kanban.verifyStackOrder({
order: ["Uncategorized", "G", "PG", "PG-13", "R", "NC-17"],
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
});
await kanban.verifyStackFooter({
count: [0, 4, 5, 8, 6, 6],
@ -194,8 +194,8 @@ test.describe("View", () => {
});
// verify card order
const order2 = [
["BAREFOOT MANCHURIAN", "BARBARELLA STREETCAR"],
["WORST BANGER", "PRESIDENT BANG"],
['BAREFOOT MANCHURIAN', 'BARBARELLA STREETCAR'],
['WORST BANGER', 'PRESIDENT BANG'],
];
for (let i = 1; i <= order2.length; i++)
await kanban.verifyCardOrder({
@ -203,17 +203,17 @@ test.describe("View", () => {
order: order2[i - 1],
});
await dashboard.viewSidebar.deleteView({ title: "Kanban-1" });
await dashboard.viewSidebar.deleteView({ title: 'Kanban-1' });
///////////////////////////////////////////////
await dashboard.viewSidebar.openView({ title: "Film Kanban" });
await dashboard.viewSidebar.openView({ title: 'Film Kanban' });
// add new stack
await kanban.addNewStack({ title: "Test" });
await kanban.addNewStack({ title: 'Test' });
await dashboard.rootPage.waitForTimeout(1000);
await kanban.verifyStackCount({ count: 7 });
await kanban.verifyStackOrder({
order: ["Uncategorized", "G", "PG", "PG-13", "R", "NC-17", "Test"],
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17', 'Test'],
});
// collapse stack
@ -226,26 +226,26 @@ test.describe("View", () => {
// add record to stack & verify
await toolbar.fields.hideAll();
await toolbar.fields.toggleShowSystemFields();
await toolbar.fields.toggle({ title: "LanguageId" });
await toolbar.fields.toggle({ title: "Title" });
await toolbar.fields.toggle({ title: 'LanguageId' });
await toolbar.fields.toggle({ title: 'Title' });
await toolbar.sort.resetSort();
await toolbar.filter.resetFilter();
await kanban.addCard({ stackIndex: 6 });
await dashboard.expandedForm.fillField({
columnTitle: "Title",
value: "New record",
columnTitle: 'Title',
value: 'New record',
});
await dashboard.expandedForm.fillField({
columnTitle: "LanguageId",
value: "1",
columnTitle: 'LanguageId',
value: '1',
});
// todo: Check why kanban doesnt reload the rows data
await dashboard.expandedForm.save({ waitForRowsData: false });
await kanban.verifyStackCount({ count: 7 });
await kanban.verifyStackOrder({
order: ["Uncategorized", "G", "PG", "PG-13", "R", "NC-17", "Test"],
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17', 'Test'],
});
await kanban.verifyCardCount({
count: [0, 25, 25, 25, 25, 25, 1],
@ -256,7 +256,7 @@ test.describe("View", () => {
await dashboard.rootPage.waitForTimeout(1000);
await kanban.verifyStackCount({ count: 6 });
await kanban.verifyStackOrder({
order: ["Uncategorized", "G", "PG", "PG-13", "R", "NC-17"],
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
});
await kanban.verifyCardCount({
count: [1, 25, 25, 25, 25, 25],

32
scripts/playwright/tests/viewMenu.spec.ts

@ -1,8 +1,8 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
test.describe("Grid view locked", () => {
test.describe('Grid view locked', () => {
let dashboard: DashboardPage;
let context: any;
@ -11,17 +11,17 @@ test.describe("Grid view locked", () => {
dashboard = new DashboardPage(page, context.project);
});
test("ReadOnly lock & collaboration mode", async () => {
test('ReadOnly lock & collaboration mode', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.toolbar.viewsMenu.verifyCollaborativeMode();
// enable view lock
await dashboard.grid.toolbar.viewsMenu.click({
menu: "Collaborative View",
subMenu: "Locked View",
menu: 'Collaborative View',
subMenu: 'Locked View',
});
// verify view lock
@ -29,21 +29,21 @@ test.describe("Grid view locked", () => {
// enable collaborative view
await dashboard.grid.toolbar.viewsMenu.click({
menu: "Locked View",
subMenu: "Collaborative View",
menu: 'Locked View',
subMenu: 'Collaborative View',
});
await dashboard.grid.toolbar.viewsMenu.verifyCollaborativeMode();
});
test("Download CSV", async () => {
test('Download CSV', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.toolbar.viewsMenu.click({
menu: "Download",
subMenu: "Download as CSV",
menu: 'Download',
subMenu: 'Download as CSV',
});
});
});

50
scripts/playwright/tests/views.spec.ts

@ -1,8 +1,8 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
test.describe("Views CRUD Operations", () => {
test.describe('Views CRUD Operations', () => {
let dashboard: DashboardPage;
let context: any;
@ -11,38 +11,38 @@ test.describe("Views CRUD Operations", () => {
dashboard = new DashboardPage(page, context.project);
});
test("Create views, reorder and delete", async () => {
await dashboard.treeView.openTable({ title: "City" });
await dashboard.viewSidebar.createGridView({ title: "CityGrid" });
await dashboard.viewSidebar.verifyView({ title: "CityGrid", index: 1 });
test('Create views, reorder and delete', async () => {
await dashboard.treeView.openTable({ title: 'City' });
await dashboard.viewSidebar.createGridView({ title: 'CityGrid' });
await dashboard.viewSidebar.verifyView({ title: 'CityGrid', index: 1 });
await dashboard.viewSidebar.renameView({
title: "CityGrid",
newTitle: "CityGrid2",
title: 'CityGrid',
newTitle: 'CityGrid2',
});
await dashboard.viewSidebar.verifyView({
title: "CityGrid2",
title: 'CityGrid2',
index: 1,
});
await dashboard.viewSidebar.createFormView({ title: "CityForm" });
await dashboard.viewSidebar.verifyView({ title: "CityForm", index: 2 });
await dashboard.viewSidebar.createFormView({ title: 'CityForm' });
await dashboard.viewSidebar.verifyView({ title: 'CityForm', index: 2 });
await dashboard.viewSidebar.renameView({
title: "CityForm",
newTitle: "CityForm2",
title: 'CityForm',
newTitle: 'CityForm2',
});
await dashboard.viewSidebar.verifyView({
title: "CityForm2",
title: 'CityForm2',
index: 2,
});
await dashboard.viewSidebar.createGalleryView({ title: "CityGallery" });
await dashboard.viewSidebar.verifyView({ title: "CityGallery", index: 3 });
await dashboard.viewSidebar.createGalleryView({ title: 'CityGallery' });
await dashboard.viewSidebar.verifyView({ title: 'CityGallery', index: 3 });
await dashboard.viewSidebar.renameView({
title: "CityGallery",
newTitle: "CityGallery2",
title: 'CityGallery',
newTitle: 'CityGallery2',
});
await dashboard.viewSidebar.verifyView({
title: "CityGallery2",
title: 'CityGallery2',
index: 3,
});
@ -60,16 +60,16 @@ test.describe("Views CRUD Operations", () => {
// index: 2,
// });
await dashboard.viewSidebar.deleteView({ title: "CityForm2" });
await dashboard.viewSidebar.deleteView({ title: 'CityForm2' });
await dashboard.viewSidebar.verifyViewNotPresent({
title: "CityForm2",
title: 'CityForm2',
index: 2,
});
// fix index after enabling reorder test
await dashboard.viewSidebar.deleteView({ title: "CityGallery2" });
await dashboard.viewSidebar.deleteView({ title: 'CityGallery2' });
await dashboard.viewSidebar.verifyViewNotPresent({
title: "CityGallery2",
title: 'CityGallery2',
index: 1,
});
});

2
scripts/playwright/tsconfig.json

@ -45,7 +45,7 @@
// "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
"lib": [
"es2017"
"es2017", "DOM"
],
"types": [
"node"

Loading…
Cancel
Save