Browse Source

feat(testing): Added Table Column Operations test suite

pull/3848/head
Muhammed Mustafa 2 years ago
parent
commit
9e0eddf833
  1. 9
      packages/nc-gui/components/cell/MultiSelect.vue
  2. 9
      packages/nc-gui/components/cell/SingleSelect.vue
  3. 5
      packages/nc-gui/components/smartsheet/Cell.vue
  4. 4
      packages/nc-gui/components/smartsheet/Grid.vue
  5. 1
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  6. 11
      scripts/playwright/pages/Cell/SelectOptionCell.ts
  7. 12
      scripts/playwright/pages/Cell/index.ts
  8. 9
      scripts/playwright/pages/Column/index.ts
  9. 3
      scripts/playwright/pages/Dashboard.ts
  10. 38
      scripts/playwright/pages/ExpandedForm/index.ts
  11. 41
      scripts/playwright/pages/Grid.ts
  12. 2
      scripts/playwright/playwright.config.ts
  13. 53
      scripts/playwright/tests/tableColumnOperation.spec.ts

9
packages/nc-gui/components/cell/MultiSelect.vue

@ -20,6 +20,7 @@ import MdiCloseCircle from '~icons/mdi/close-circle'
interface Props { interface Props {
modelValue?: string | string[] modelValue?: string | string[]
rowIndex?: number
} }
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
@ -150,7 +151,13 @@ watch(isOpen, (n, _o) => {
@keydown="handleKeys" @keydown="handleKeys"
@click="isOpen = !isOpen" @click="isOpen = !isOpen"
> >
<a-select-option v-for="op of options" :key="op.id" :value="op.title" @click.stop> <a-select-option
v-for="op of options"
:key="op.id"
:value="op.title"
:pw-data="`select-option-${column.title}-${rowIndex}`"
@click.stop
>
<a-tag class="rounded-tag" :color="op.color"> <a-tag class="rounded-tag" :color="op.color">
<span <span
:style="{ :style="{

9
packages/nc-gui/components/cell/SingleSelect.vue

@ -6,6 +6,7 @@ import { ActiveCellInj, ColumnInj, IsKanbanInj, ReadonlyInj, computed, inject, r
interface Props { interface Props {
modelValue?: string | undefined modelValue?: string | undefined
rowIndex?: number
} }
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
@ -81,7 +82,13 @@ watch(isOpen, (n, _o) => {
@keydown="handleKeys" @keydown="handleKeys"
@click="isOpen = !isOpen" @click="isOpen = !isOpen"
> >
<a-select-option v-for="op of options" :key="op.title" :value="op.title" @click.stop> <a-select-option
v-for="op of options"
:key="op.title"
:value="op.title"
:pw-data="`select-option-${column.title}-${rowIndex}`"
@click.stop
>
<a-tag class="rounded-tag" :color="op.color"> <a-tag class="rounded-tag" :color="op.color">
<span <span
:style="{ :style="{

5
packages/nc-gui/components/smartsheet/Cell.vue

@ -126,15 +126,14 @@ const syncAndNavigate = (dir: NavigateDir, e: KeyboardEvent) => {
<div <div
class="nc-cell w-full" class="nc-cell w-full"
:class="[`nc-cell-${(column?.uidt || 'default').toLowerCase()}`, { 'text-blue-600': isPrimary && !virtual && !isForm }]" :class="[`nc-cell-${(column?.uidt || 'default').toLowerCase()}`, { 'text-blue-600': isPrimary && !virtual && !isForm }]"
:data-pw="`cell-${column?.title}-${rowIndex}`"
@keydown.enter.exact="syncAndNavigate(NavigateDir.NEXT, $event)" @keydown.enter.exact="syncAndNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="syncAndNavigate(NavigateDir.PREV, $event)" @keydown.shift.enter.exact="syncAndNavigate(NavigateDir.PREV, $event)"
> >
<LazyCellTextArea v-if="isTextArea" v-model="vModel" /> <LazyCellTextArea v-if="isTextArea" v-model="vModel" />
<LazyCellCheckbox v-else-if="isBoolean" v-model="vModel" /> <LazyCellCheckbox v-else-if="isBoolean" v-model="vModel" />
<LazyCellAttachment v-else-if="isAttachment" v-model="vModel" :row-index="props.rowIndex" /> <LazyCellAttachment v-else-if="isAttachment" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellSingleSelect v-else-if="isSingleSelect" v-model="vModel" /> <LazyCellSingleSelect v-else-if="isSingleSelect" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellMultiSelect v-else-if="isMultiSelect" v-model="vModel" /> <LazyCellMultiSelect v-else-if="isMultiSelect" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellDatePicker v-else-if="isDate" v-model="vModel" /> <LazyCellDatePicker v-else-if="isDate" v-model="vModel" />
<LazyCellYearPicker v-else-if="isYear" v-model="vModel" /> <LazyCellYearPicker v-else-if="isYear" v-model="vModel" />
<LazyCellDateTimePicker v-else-if="isDateTime" v-model="vModel" /> <LazyCellDateTimePicker v-else-if="isDateTime" v-model="vModel" />

4
packages/nc-gui/components/smartsheet/Grid.vue

@ -461,7 +461,7 @@ watch(
<template> <template>
<div class="relative flex flex-col h-full min-h-0 w-full"> <div class="relative flex flex-col h-full min-h-0 w-full">
<general-overlay :model-value="isLoading" inline transition class="!bg-opacity-15" pw-data="grid-load-spinner"> <general-overlay :model-value="isLoading" inline transition class="!bg-opacity-15">
<div class="flex items-center justify-center h-full w-full !bg-white !bg-opacity-85 z-1000"> <div class="flex items-center justify-center h-full w-full !bg-white !bg-opacity-85 z-1000">
<a-spin size="large" /> <a-spin size="large" />
</div> </div>
@ -568,6 +568,7 @@ watch(
<div <div
v-if="!readOnly || hasRole('commenter', true) || hasRole('viewer', true)" v-if="!readOnly || hasRole('commenter', true) || hasRole('viewer', true)"
class="nc-expand" class="nc-expand"
:pw-data="`nc-expand-${rowIndex}`"
:class="{ 'nc-comment': row.rowMeta?.commentCount }" :class="{ 'nc-comment': row.rowMeta?.commentCount }"
> >
<a-spin v-if="row.rowMeta.saving" class="!flex items-center" /> <a-spin v-if="row.rowMeta.saving" class="!flex items-center" />
@ -607,7 +608,6 @@ watch(
:data-key="rowIndex + columnObj.id" :data-key="rowIndex + columnObj.id"
:data-col="columnObj.id" :data-col="columnObj.id"
:data-title="columnObj.title" :data-title="columnObj.title"
:data-pw="`cell-${columnObj.title}-${rowIndex}`"
@click="selectCell(rowIndex, colIndex)" @click="selectCell(rowIndex, colIndex)"
@dblclick="makeEditable(row, columnObj)" @dblclick="makeEditable(row, columnObj)"
@mousedown="startSelectRange($event, rowIndex, colIndex)" @mousedown="startSelectRange($event, rowIndex, colIndex)"

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

@ -150,6 +150,7 @@ export default {
:key="col.title" :key="col.title"
class="mt-2 py-2" class="mt-2 py-2"
:class="`nc-expand-col-${col.title}`" :class="`nc-expand-col-${col.title}`"
:pw-data="`nc-expand-col-${col.title}`"
> >
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" /> <LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" />

11
scripts/playwright/pages/Cell/SelectOptionCell.ts

@ -9,19 +9,12 @@ export class SelectOptionCellPageObject {
async select({index, columnHeader, option, multiSelect}: {index: number, columnHeader: string, option: string, multiSelect?: boolean}) { async select({index, columnHeader, option, multiSelect}: {index: number, columnHeader: string, option: string, multiSelect?: boolean}) {
await this.cell.get({index, columnHeader}).click(); await this.cell.get({index, columnHeader}).click();
const count = await this.cell.page.locator('.rc-virtual-list-holder .ant-select-item-option-content', {hasText: option}).count();
for(let i = 0; i < count; i++) { await this.cell.page.locator(`[pw-data="select-option-${columnHeader}-${index}"]`, {hasText: option}).click();
if(await this.cell.page.locator('.rc-virtual-list-holder .ant-select-item-option-content', {hasText: option}).nth(i).isVisible()) {
await this.cell.page.locator('.rc-virtual-list-holder .ant-select-item-option-content', {hasText: option}).nth(i).click();
}
}
if(multiSelect) await this.cell.get({index, columnHeader}).click(); if(multiSelect) await this.cell.get({index, columnHeader}).click();
await this.cell.page.locator(`.nc-dropdown-single-select-cell`).nth(index).waitFor({state: 'hidden'}); await this.cell.page.locator(`[pw-data="select-option-${columnHeader}-${index}"]`, {hasText: option}).waitFor({state: 'hidden'});
// todo: Remove this wait. Should be solved by adding pw-data-attribute with cell info to the a-select-option of the cell
// await this.cell.page.waitForTimeout(200);
} }
async clear({index, columnHeader, multiSelect}: {index: number, columnHeader: string, multiSelect?: boolean}) { async clear({index, columnHeader, multiSelect}: {index: number, columnHeader: string, multiSelect?: boolean}) {

12
scripts/playwright/pages/Cell/index.ts

@ -1,4 +1,4 @@
import { Page, Locator } from "@playwright/test"; import { Page, Locator,expect } from "@playwright/test";
import { SelectOptionCellPageObject } from "./SelectOptionCell"; import { SelectOptionCellPageObject } from "./SelectOptionCell";
export class CellPageObject { export class CellPageObject {
@ -11,10 +11,18 @@ export class CellPageObject {
} }
get({index, columnHeader}: {index: number, columnHeader: string}): Locator { get({index, columnHeader}: {index: number, columnHeader: string}): Locator {
return this.page.locator(`td[data-pw="cell-${columnHeader}-${index}"]`); return this.page.locator(`td[data-pw=cell-${columnHeader}-${index}]`);
} }
async click({index, columnHeader}: {index: number, columnHeader: string}) { async click({index, columnHeader}: {index: number, columnHeader: string}) {
return this.get({index, columnHeader}).click(); return this.get({index, columnHeader}).click();
} }
async dblclick({index, columnHeader}: {index: number, columnHeader: string}) {
return this.get({index, columnHeader}).dblclick();
}
async verify({index, columnHeader, value}: {index: number, columnHeader: string, value: string}) {
return expect(await this.get({index, columnHeader}).innerText()).toBe(value);
}
} }

9
scripts/playwright/pages/Column/index.ts

@ -56,7 +56,7 @@ export class ColumnPageObject {
} }
async delete({title}: {title: string}) { async delete({title}: {title: string}) {
await this.page.locator(`text=#Title${title} >> svg >> nth=3`).click(); await this.page.locator(`th[data-title="${title}"] >> svg.ant-dropdown-trigger`).click();
await this.page.locator('li[role="menuitem"]:has-text("Delete")').waitFor() await this.page.locator('li[role="menuitem"]:has-text("Delete")').waitFor()
await this.page.locator('li[role="menuitem"]:has-text("Delete")').click(); await this.page.locator('li[role="menuitem"]:has-text("Delete")').click();
@ -81,4 +81,11 @@ export class ColumnPageObject {
await this.page.locator('form[data-pw="add-or-edit-column"]').waitFor({state: 'hidden'}); await this.page.locator('form[data-pw="add-or-edit-column"]').waitFor({state: 'hidden'});
await this.page.waitForTimeout(200); await this.page.waitForTimeout(200);
} }
async verify({title, isDeleted}: {title: string, isDeleted?: boolean}) {
if(isDeleted) {
return expect(await this.page.locator(`th[data-title="${title}"]`).count()).toBe(0);
}
await expect(this.page.locator(`th[data-title="${title}"]`)).toHaveText(title);
}
} }

3
scripts/playwright/pages/Dashboard.ts

@ -1,6 +1,7 @@
// playwright-dev-page.ts // playwright-dev-page.ts
import { expect, Locator, Page } from '@playwright/test'; import { expect, Locator, Page } from '@playwright/test';
import { BasePage } from './Base'; import { BasePage } from './Base';
import { ExpandedFormPage } from './ExpandedForm';
export class DashboardPage { export class DashboardPage {
readonly project: any; readonly project: any;
@ -8,6 +9,7 @@ export class DashboardPage {
readonly tablesSideBar: Locator; readonly tablesSideBar: Locator;
readonly tabBar: Locator; readonly tabBar: Locator;
readonly base: BasePage; readonly base: BasePage;
readonly expandedForm: ExpandedFormPage;
constructor(page: Page, project: any) { constructor(page: Page, project: any) {
this.page = page; this.page = page;
@ -15,6 +17,7 @@ export class DashboardPage {
this.project = project; this.project = project;
this.tablesSideBar = page.locator('.nc-treeview-container'); this.tablesSideBar = page.locator('.nc-treeview-container');
this.tabBar = page.locator('.nc-tab-bar'); this.tabBar = page.locator('.nc-tab-bar');
this.expandedForm = new ExpandedFormPage(page);
} }
async goto() { async goto() {

38
scripts/playwright/pages/ExpandedForm/index.ts

@ -0,0 +1,38 @@
// playwright-dev-page.ts
import { Locator, Page, expect } from '@playwright/test';
import { BasePage } from '../Base';
import { CellPageObject } from '../Cell';
import { ColumnPageObject } from '../Column';
export class ExpandedFormPage {
readonly page: Page;
readonly addNewTableButton: Locator;
readonly column: ColumnPageObject;
readonly cell: CellPageObject;
readonly base: BasePage;
constructor(page: Page) {
this.page = page;
this.addNewTableButton = page.locator('.nc-add-new-table');
this.column = new ColumnPageObject(page);
this.cell = new CellPageObject(page);
this.base = new BasePage(page);
}
get() {
return this.page.locator(`.nc-drawer-expanded-form`);
}
async fillField({columnTitle, value}: {columnTitle: string, value: string}) {
const field = this.get().locator(`[pw-data="nc-expand-col-${columnTitle}"]`);
await field.locator('input').fill(value);
}
async save() {
await this.get().locator('button:has-text("Save Row")').click();
await this.get().press('Escape');
await this.get().waitFor({state: 'hidden'});
await this.base.toastWait({message: `updated successfully.`});
await this.page.waitForTimeout(400);
}
}

41
scripts/playwright/pages/Grid.ts

@ -16,21 +16,31 @@ export class GridPage {
this.cell = new CellPageObject(page); this.cell = new CellPageObject(page);
} }
row(index: number) {
return this.page.locator(`tr[data-pw="grid-row-${index}"]`);
}
async addNewRow({index = 0, title}: {index?: number, title?: string} = {}) { async addNewRow({index = 0, title}: {index?: number, title?: string} = {}) {
const rowCount = await this.page.locator('.nc-grid-row').count(); const rowCount = await this.page.locator('.nc-grid-row').count();
await this.page.locator('.nc-grid-add-new-cell').click(); await this.page.locator('.nc-grid-add-new-cell').click();
if(rowCount + 1 !== await this.page.locator('.nc-grid-row').count()) { if(rowCount + 1 !== await this.page.locator('.nc-grid-row').count()) {
await this.page.locator('.nc-grid-add-new-cell').click(); await this.page.locator('.nc-grid-add-new-cell').click();
} }
// Double click td >> nth=1
await this.page.locator('td[data-title="Title"]').nth(index).dblclick();
// Fill text=1Add new row >> input >> nth=1 const cell = this.cell.get({index, columnHeader: 'Title'});
await this.page.locator(`div[data-pw="cell-Title-${index}"] >> input`).fill(title ?? `Row ${index}`); await this.cell.dblclick({
index,
columnHeader: 'Title'
});
await this.page.locator('span[title="Title"]').click(); await cell.locator('input').fill(title ?? `Row ${index}`);
await this.page.locator('.nc-grid-wrapper').click(); await cell.locator('input').press('Enter');
}
async verifyRow({index}: {index: number}) {
await this.page.locator(`td[data-pw="cell-Title-${index}"]`).waitFor({state: 'visible'});
expect(await this.page.locator(`td[data-pw="cell-Title-${index}"]`).count()).toBe(1);
} }
async verifyRowDoesNotExist({index}: {index: number}) { async verifyRowDoesNotExist({index}: {index: number}) {
@ -48,4 +58,21 @@ export class GridPage {
await this.page.locator('span.ant-dropdown-menu-title-content > nc-project-menu-item').waitFor({state: 'hidden'}); await this.page.locator('span.ant-dropdown-menu-title-content > nc-project-menu-item').waitFor({state: 'hidden'});
} }
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();
}
async selectAll() {
await this.page.locator('[pw-data="nc-check-all"]').hover();
await this.page.locator('[pw-data="nc-check-all"]').locator('input[type="checkbox"]').click();
}
async deleteAll() {
await this.selectAll();
await this.page.locator('[pw-data="nc-check-all"]').locator('input[type="checkbox"]').click({
button: 'right'
});
await this.page.locator('text=Delete Selected Rows').click();
}
} }

2
scripts/playwright/playwright.config.ts

@ -34,7 +34,7 @@ const config: PlaywrightTestConfig = {
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0, actionTimeout: process.env.CI ? 0: 10000,
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3000', baseURL: 'http://localhost:3000',

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

@ -0,0 +1,53 @@
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { GridPage } from '../pages/Grid';
import setup from '../setup';
test.describe('Table Column Operations', () => {
let grid: GridPage, dashboard: DashboardPage;
let context: any;
test.beforeEach(async ({page}) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
grid = new GridPage(page);
await dashboard.createTable({title: "sheet1"});
})
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.save({isUpdated: true});
await grid.column.verify({title: "column_name_b"});
await grid.column.delete({title: "column_name_b"});
await grid.column.verify({title: "column_name_b", isDeleted: true});
await grid.addNewRow({index: 0});
await grid.verifyRow({index: 0})
await grid.openExpandedRow({index: 0});
await dashboard.expandedForm.fillField({columnTitle: "Title", value: "value_a"});
await dashboard.expandedForm.save();
await grid.cell.verify({index: 0, columnHeader: "Title", value: "value_a"});
await grid.deleteRow(0);
await grid.verifyRowDoesNotExist({index: 0});
await grid.addNewRow({index: 0});
await grid.addNewRow({index: 1});
await grid.addNewRow({index: 2});
await grid.addNewRow({index: 3});
await grid.addNewRow({index: 4});
await grid.deleteAll();
await grid.verifyRowDoesNotExist({index: 0});
});
});
Loading…
Cancel
Save