mirror of
https://github.com/nocodb/nocodb.git
synced 2026-04-25 01:25:32 +00:00
feat(testing): Added quick test, added isEmptyProject option to test setup to have project created with no sakila tables
This commit is contained in:
@@ -31,7 +31,7 @@ async function editHook(hook: Record<string, any>) {
|
||||
class="nc-drawer-webhook"
|
||||
@keydown.esc="vModel = false"
|
||||
>
|
||||
<a-layout>
|
||||
<a-layout class="nc-drawer-webhook-body">
|
||||
<a-layout-content class="px-10 py-5 scrollbar-thin-primary">
|
||||
<LazyWebhookEditor v-if="editOrAdd" :hook="currentHook" @back-to-list="editOrAdd = false" />
|
||||
|
||||
|
||||
@@ -284,7 +284,11 @@ const copyProjectMeta = async () => {
|
||||
<div class="flex items-center gap-2">
|
||||
<MdiEditOutline v-e="['c:project:edit:rename']" class="nc-action-btn" @click.stop="navigateTo(`/${text}`)" />
|
||||
|
||||
<MdiDeleteOutline class="nc-action-btn" @click.stop="deleteProject(record)" />
|
||||
<MdiDeleteOutline
|
||||
class="nc-action-btn"
|
||||
:pw-data="`delete-project-${record.title}`"
|
||||
@click.stop="deleteProject(record)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
|
||||
@@ -8,6 +8,7 @@ export async function reset(req: Request<any, any>, res) {
|
||||
const service = new TestResetService({
|
||||
parallelId: req.body.parallelId,
|
||||
dbType: req.body.dbType,
|
||||
isEmptyProject: req.body.isEmptyProject,
|
||||
});
|
||||
|
||||
res.json(await service.process());
|
||||
|
||||
@@ -28,11 +28,21 @@ export class TestResetService {
|
||||
private knex: Knex | null = null;
|
||||
private readonly parallelId;
|
||||
private readonly dbType;
|
||||
private readonly isEmptyProject: boolean;
|
||||
|
||||
constructor({ parallelId, dbType }: { parallelId: string; dbType: string }) {
|
||||
constructor({
|
||||
parallelId,
|
||||
dbType,
|
||||
isEmptyProject,
|
||||
}: {
|
||||
parallelId: string;
|
||||
dbType: string;
|
||||
isEmptyProject: boolean;
|
||||
}) {
|
||||
this.knex = Noco.ncMeta.knex;
|
||||
this.parallelId = parallelId;
|
||||
this.dbType = dbType;
|
||||
this.isEmptyProject = isEmptyProject;
|
||||
}
|
||||
|
||||
async process() {
|
||||
@@ -81,6 +91,7 @@ export class TestResetService {
|
||||
metaKnex,
|
||||
title,
|
||||
oldProject: project,
|
||||
isEmptyProject: this.isEmptyProject,
|
||||
});
|
||||
} else if (dbType == 'mysql') {
|
||||
await resetMysqlSakilaProject({
|
||||
@@ -88,6 +99,7 @@ export class TestResetService {
|
||||
title,
|
||||
parallelId,
|
||||
oldProject: project,
|
||||
isEmptyProject: this.isEmptyProject,
|
||||
});
|
||||
} else if (dbType == 'pg') {
|
||||
await resetPgSakilaProject({
|
||||
@@ -95,6 +107,7 @@ export class TestResetService {
|
||||
title,
|
||||
parallelId,
|
||||
oldProject: project,
|
||||
isEmptyProject: this.isEmptyProject,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,17 +20,21 @@ const resetMetaSakilaSqliteProject = async ({
|
||||
token,
|
||||
title,
|
||||
oldProject,
|
||||
isEmptyProject,
|
||||
}: {
|
||||
metaKnex: Knex;
|
||||
token: string;
|
||||
title: string;
|
||||
oldProject: Project;
|
||||
isEmptyProject: boolean;
|
||||
}) => {
|
||||
const project = await createProject(token, title);
|
||||
|
||||
await dropTablesAndViews(metaKnex, oldProject.prefix);
|
||||
if (oldProject) await dropTablesAndViews(metaKnex, oldProject.prefix);
|
||||
await dropTablesAndViews(metaKnex, project.prefix);
|
||||
|
||||
if (isEmptyProject) return;
|
||||
|
||||
await resetMetaSakilaSqlite(metaKnex, project.prefix, oldProject);
|
||||
|
||||
await syncMeta(project, token);
|
||||
|
||||
@@ -63,7 +63,11 @@ const isSakilaMysqlToBeReset = async (
|
||||
return audits?.length > 0;
|
||||
};
|
||||
|
||||
const resetSakilaMysql = async (knex: Knex, parallelId: string) => {
|
||||
const resetSakilaMysql = async (
|
||||
knex: Knex,
|
||||
parallelId: string,
|
||||
isEmptyProject: boolean
|
||||
) => {
|
||||
const testsDir = __dirname.replace(
|
||||
'/src/lib/services/test/TestResetService',
|
||||
'/tests'
|
||||
@@ -76,6 +80,8 @@ const resetSakilaMysql = async (knex: Knex, parallelId: string) => {
|
||||
}
|
||||
await knex.raw(`CREATE DATABASE test_sakila_${parallelId}`);
|
||||
|
||||
if (isEmptyProject) return;
|
||||
|
||||
const trx = await knex.transaction();
|
||||
|
||||
try {
|
||||
@@ -105,11 +111,13 @@ const resetMysqlSakilaProject = async ({
|
||||
title,
|
||||
parallelId,
|
||||
oldProject,
|
||||
isEmptyProject,
|
||||
}: {
|
||||
token: string;
|
||||
title: string;
|
||||
parallelId: string;
|
||||
oldProject?: Project | undefined;
|
||||
isEmptyProject: boolean;
|
||||
}) => {
|
||||
const knex = Knex(config);
|
||||
|
||||
@@ -120,8 +128,11 @@ const resetMysqlSakilaProject = async ({
|
||||
await knex.raw(`USE test_sakila_${parallelId}`);
|
||||
}
|
||||
|
||||
if (await isSakilaMysqlToBeReset(knex, parallelId, oldProject)) {
|
||||
await resetSakilaMysql(knex, parallelId);
|
||||
if (
|
||||
isEmptyProject ||
|
||||
(await isSakilaMysqlToBeReset(knex, parallelId, oldProject))
|
||||
) {
|
||||
await resetSakilaMysql(knex, parallelId, isEmptyProject);
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
|
||||
@@ -66,7 +66,11 @@ const isSakilaPgToBeReset = async (knex: Knex, project?: Project) => {
|
||||
return audits?.length > 0;
|
||||
};
|
||||
|
||||
const resetSakilaPg = async (pgknex: Knex, parallelId: string) => {
|
||||
const resetSakilaPg = async (
|
||||
pgknex: Knex,
|
||||
parallelId: string,
|
||||
isEmptyProject: boolean
|
||||
) => {
|
||||
const testsDir = __dirname.replace(
|
||||
'/src/lib/services/test/TestResetService',
|
||||
'/tests'
|
||||
@@ -75,6 +79,8 @@ const resetSakilaPg = async (pgknex: Knex, parallelId: string) => {
|
||||
await pgknex.raw(`DROP DATABASE IF EXISTS sakila_${parallelId}`);
|
||||
await pgknex.raw(`CREATE DATABASE sakila_${parallelId}`);
|
||||
|
||||
if (isEmptyProject) return;
|
||||
|
||||
const sakilaKnex = Knex(sakilaKnexConfig(parallelId));
|
||||
|
||||
const schemaFile = await fs.readFile(
|
||||
@@ -103,11 +109,13 @@ const resetPgSakilaProject = async ({
|
||||
title,
|
||||
parallelId,
|
||||
oldProject,
|
||||
isEmptyProject,
|
||||
}: {
|
||||
token: string;
|
||||
title: string;
|
||||
parallelId: string;
|
||||
oldProject?: Project | undefined;
|
||||
isEmptyProject: boolean;
|
||||
}) => {
|
||||
const pgknex = Knex(config);
|
||||
|
||||
@@ -117,9 +125,9 @@ const resetPgSakilaProject = async ({
|
||||
|
||||
const sakilaKnex = Knex(sakilaKnexConfig(parallelId));
|
||||
|
||||
if (await isSakilaPgToBeReset(sakilaKnex, oldProject)) {
|
||||
if (isEmptyProject || (await isSakilaPgToBeReset(sakilaKnex, oldProject))) {
|
||||
await sakilaKnex.destroy();
|
||||
await resetSakilaPg(pgknex, parallelId);
|
||||
await resetSakilaPg(pgknex, parallelId, isEmptyProject);
|
||||
} else {
|
||||
await sakilaKnex.destroy();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "TRACE=true npx playwright test --workers=4",
|
||||
"quick-test": "TRACE=true PW_QUICK_TEST=1 npx playwright test --workers=4",
|
||||
"test-debug": "./startPlayWrightServer.sh; PW_TEST_REUSE_CONTEXT=1 PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:31000/ PWDEBUG=console npx playwright test -c playwright.config.ts --headed --project=chromium --retries 0 --timeout 0 --workers 1 --max-failures=1",
|
||||
"test-debug-quick-sqlite": "./startPlayWrightServer.sh; PW_QUICK_TEST=1 PW_TEST_REUSE_CONTEXT=1 PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:31000/ PWDEBUG=console npx playwright test -c playwright.config.ts --headed --project=chromium --retries 0 --timeout 5 --workers 1 --max-failures=1",
|
||||
"ci-test-mysql": "E2E_DB_TYPE=mysql npx playwright test --workers=2"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
@@ -22,9 +22,10 @@ export class WebhookFormPage extends BasePage {
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.dashboard.get().locator(`.nc-drawer-webhook`);
|
||||
return this.dashboard.get().locator(`.nc-drawer-webhook-body`);
|
||||
}
|
||||
|
||||
// todo: Removing opening webhook drawer logic as it belongs to `Toolbar` page
|
||||
async create({
|
||||
title,
|
||||
event,
|
||||
@@ -147,6 +148,14 @@ export class WebhookFormPage extends BasePage {
|
||||
await this.dashboard.get().locator(`.nc-hook`).nth(index).click();
|
||||
}
|
||||
|
||||
async openForm({ index }: { index: number }) {
|
||||
await this.dashboard.get().locator(`.nc-hook`).nth(index).click();
|
||||
}
|
||||
|
||||
async click({ index }: { index: number }) {
|
||||
await this.dashboard.get().locator(`.nc-hook`).nth(index).click();
|
||||
}
|
||||
|
||||
async configureHeader({ key, value }: { key: string; value: string }) {
|
||||
// hardcode "Content-type: application/json"
|
||||
await this.get().locator(`.ant-tabs-tab-btn:has-text("Headers")`).click();
|
||||
@@ -163,4 +172,31 @@ export class WebhookFormPage extends BasePage {
|
||||
.locator("input.ant-checkbox-input")
|
||||
.click();
|
||||
}
|
||||
|
||||
async verifyForm({
|
||||
title,
|
||||
hookEvent,
|
||||
url,
|
||||
notificationType,
|
||||
urlMethod,
|
||||
condition,
|
||||
}: {
|
||||
title: string;
|
||||
hookEvent: string;
|
||||
url: string;
|
||||
notificationType: string;
|
||||
urlMethod: string;
|
||||
condition: boolean;
|
||||
}) {
|
||||
await expect(await this.get().locator('input.nc-text-field-hook-title').inputValue()).toBe(title);
|
||||
await expect(await this.get().locator('.nc-text-field-hook-event >> .ant-select-selection-item').innerText()).toBe(hookEvent);
|
||||
await expect(await this.get().locator('.nc-select-hook-notification-type >> .ant-select-selection-item').innerText()).toBe(notificationType);
|
||||
await expect(await this.get().locator('.nc-select-hook-url-method >> .ant-select-selection-item').innerText()).toBe(urlMethod);
|
||||
await expect(await this.get().locator('input.nc-text-field-hook-url-path').inputValue()).toBe(url);
|
||||
await expect(await this.get().locator('label.nc-check-box-hook-condition >> input[type="checkbox"]').isChecked()).toBe(condition);
|
||||
}
|
||||
|
||||
async goBackFromForm() {
|
||||
await this.get().locator('svg.nc-icon-hook-navigate-left').click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export class ToolbarActionsPage extends BasePage {
|
||||
return this.rootPage.locator(`[pw-data="toolbar-actions"]`);
|
||||
}
|
||||
|
||||
// todo: use enum
|
||||
async click(label: string) {
|
||||
await this.get().locator(`span:has-text("${label}")`).click();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import BasePage from "../../../Base";
|
||||
import { ToolbarPage } from "./index";
|
||||
|
||||
@@ -22,6 +23,15 @@ export class ToolbarFieldsPage extends BasePage {
|
||||
await this.toolbar.clickFields();
|
||||
}
|
||||
|
||||
async verify({ title, checked }: { title: string, checked: boolean }) {
|
||||
await expect(
|
||||
await this.get()
|
||||
.locator(`[pw-data="nc-fields-menu-${title}"]`)
|
||||
.locator('input[type="checkbox"]')
|
||||
.isChecked()
|
||||
).toBe(checked);
|
||||
}
|
||||
|
||||
async click({ title }: { title: string }) {
|
||||
await this.get()
|
||||
.locator(`[pw-data="nc-fields-menu-${title}"]`)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import BasePage from "../../../Base";
|
||||
import { ToolbarPage } from "./index";
|
||||
|
||||
@@ -13,6 +14,37 @@ 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;
|
||||
}) {
|
||||
await expect(
|
||||
await this.get().locator('.nc-filter-field-select').nth(index).innerText()
|
||||
).toBe(column);
|
||||
await expect(
|
||||
await this.get().locator('.nc-filter-operation-select').nth(index).innerText()
|
||||
).toBe(operator);
|
||||
await expect(
|
||||
await this.get().locator('input.nc-filter-value-select').nth(index).inputValue()
|
||||
).toBe(value);
|
||||
}
|
||||
|
||||
async verifyFilter({ title }: { title: string }) {
|
||||
await expect(
|
||||
await this.get()
|
||||
.locator(`[pw-data="nc-fields-menu-${title}"]`)
|
||||
.locator('input[type="checkbox"]')
|
||||
.isChecked()
|
||||
).toBe(true);
|
||||
}
|
||||
|
||||
// Todo: Handle the case of operator does not need a value
|
||||
async addNew({
|
||||
columnTitle,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import BasePage from "../../../Base";
|
||||
import { ToolbarPage } from "./index";
|
||||
|
||||
@@ -13,6 +14,23 @@ 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;
|
||||
}) {
|
||||
await expect(
|
||||
await this.get().locator('.nc-sort-field-select').nth(index).innerText()
|
||||
).toBe(column);
|
||||
await expect(
|
||||
await this.get().locator('.nc-sort-dir-select >> span.ant-select-selection-item').nth(index).innerText()
|
||||
).toBe(direction);
|
||||
}
|
||||
|
||||
async addSort({
|
||||
columnTitle,
|
||||
isAscending,
|
||||
@@ -61,6 +79,7 @@ export class ToolbarSortPage extends BasePage {
|
||||
await this.toolbar.parent.waitLoading();
|
||||
}
|
||||
|
||||
// todo: remove this opening sort menu logic
|
||||
async resetSort() {
|
||||
// open sort menu
|
||||
await this.toolbar.clickSort();
|
||||
|
||||
@@ -54,7 +54,7 @@ export class ToolbarPage extends BasePage {
|
||||
async clickFields() {
|
||||
const menuOpen = await this.fields.get().isVisible();
|
||||
|
||||
await this.get().locator(`button:has-text("Fields")`).click();
|
||||
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" });
|
||||
@@ -63,7 +63,7 @@ export class ToolbarPage extends BasePage {
|
||||
async clickSort() {
|
||||
const menuOpen = await this.sort.get().isVisible();
|
||||
|
||||
await this.get().locator(`button:has-text("Sort")`).click();
|
||||
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" });
|
||||
@@ -72,7 +72,7 @@ export class ToolbarPage extends BasePage {
|
||||
async clickFilter() {
|
||||
const menuOpen = await this.filter.get().isVisible();
|
||||
|
||||
await this.get().locator(`button:has-text("Filter")`).click();
|
||||
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" });
|
||||
@@ -80,7 +80,7 @@ export class ToolbarPage extends BasePage {
|
||||
|
||||
async clickShareView() {
|
||||
const menuOpen = await this.shareView.get().isVisible();
|
||||
await this.get().locator(`button:has-text("Share View")`).click();
|
||||
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" });
|
||||
|
||||
@@ -41,4 +41,9 @@ export class ProjectsPage extends BasePage {
|
||||
return project;
|
||||
}
|
||||
|
||||
async delete({title}: {title: string}) {
|
||||
await this.get().locator(`[pw-data="delete-project-${title}"]`).click();
|
||||
await this.rootPage.locator(`button:has-text("Yes")`).click();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
import { devices } from '@playwright/test';
|
||||
|
||||
process.env.E2E_AIRTABLE_API_KEY = "keyn1MR87qgyUsYg4";
|
||||
process.env.E2E_AIRTABLE_BASE_ID = "https://airtable.com/shr4z0qmh6dg5s3eB";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
@@ -11,7 +14,7 @@ require('dotenv').config();
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: './tests',
|
||||
testDir: process.env.PW_QUICK_TEST ? './quickTests' : './tests',
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: process.env.CI ? 80 * 1000 : 65 * 1000,
|
||||
expect: {
|
||||
|
||||
249
scripts/playwright/quickTests/commonTest.ts
Normal file
249
scripts/playwright/quickTests/commonTest.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
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",
|
||||
};
|
||||
|
||||
// links/ computed fields
|
||||
let recordsVirtualCells = {
|
||||
Duration: "00:01",
|
||||
Done: true,
|
||||
Date: "2022-05-31",
|
||||
Rating: 1,
|
||||
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 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++) {
|
||||
await dashboard.treeView.verifyTable({ title: tn[i] });
|
||||
}
|
||||
|
||||
let cellIndex = 0;
|
||||
let columnCount = cn.length;
|
||||
|
||||
if (airtableImport) {
|
||||
cellIndex = 2;
|
||||
columnCount -= 3;
|
||||
}
|
||||
for (let i = 0; i < columnCount; i++) {
|
||||
await dashboard.grid.column.verify({ title: cn[i] });
|
||||
}
|
||||
|
||||
// Verify cells
|
||||
// normal cells
|
||||
for (let [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" });
|
||||
|
||||
// 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 });
|
||||
|
||||
// LinkToAnotherRecord
|
||||
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){
|
||||
// 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 });
|
||||
|
||||
// LinkToAnotherRecord
|
||||
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.form.verifyFieldsIsEditable({ index: 0 });
|
||||
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" });
|
||||
|
||||
if(!airtableImport){
|
||||
// Verify webhooks
|
||||
await dashboard.grid.toolbar.clickActions();
|
||||
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.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.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.close();
|
||||
}
|
||||
|
||||
// Verify pagination
|
||||
await dashboard.grid.verifyActivePage({ page: '1' });
|
||||
(await dashboard.grid.clickPagination({ page: ">" }));
|
||||
await dashboard.grid.verifyActivePage({ page: '2' });
|
||||
(await dashboard.grid.clickPagination({ page: "<" }));
|
||||
await dashboard.grid.verifyActivePage({ page: '1' });
|
||||
|
||||
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" });
|
||||
|
||||
// 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 });
|
||||
|
||||
// Verify Sort
|
||||
await dashboard.grid.toolbar.clickSort();
|
||||
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" });
|
||||
|
||||
|
||||
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" });
|
||||
|
||||
// verify BT relation
|
||||
await dashboard.grid.cell.verifyVirtualCell({ index: 0, columnHeader: "FilmRead", value: ['Movie-1'] });
|
||||
}
|
||||
|
||||
if(airtableImport){
|
||||
// Delete project
|
||||
await dashboard.clickHome();
|
||||
const projectsPage = new ProjectsPage(dashboard.rootPage);
|
||||
await projectsPage.delete({ title: context.project.title });
|
||||
}
|
||||
}
|
||||
|
||||
export { quickVerify };
|
||||
@@ -3,67 +3,13 @@ import { expect, test } from "@playwright/test";
|
||||
import { DashboardPage } from "../pages/Dashboard";
|
||||
import { LoginPage } from "../pages/LoginPage";
|
||||
import { ProjectsPage } from "../pages/ProjectsPage";
|
||||
|
||||
// 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",
|
||||
};
|
||||
|
||||
// links/ computed fields
|
||||
let recordsVirtualCells = {
|
||||
Duration: "00:01",
|
||||
Done: true,
|
||||
Date: "2022-05-31",
|
||||
Rating: 1,
|
||||
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",
|
||||
];
|
||||
import { quickVerify } from "../quickTests/commonTest";
|
||||
import { NcContext } from "../setup";
|
||||
|
||||
test.describe("Quick tests", () => {
|
||||
let dashboard: DashboardPage;
|
||||
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// });
|
||||
|
||||
test("Quick tests test", async ({page}) => {
|
||||
let cellIndex = 0;
|
||||
let columnCount = cn.length;
|
||||
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.fillEmail("user@nocodb.com");
|
||||
@@ -73,64 +19,12 @@ test.describe("Quick tests", () => {
|
||||
const projectsPage = new ProjectsPage(page);
|
||||
const project = await projectsPage.selectAndGetProject("sample");
|
||||
dashboard = new DashboardPage(page, project);
|
||||
|
||||
// verify if all tables exist
|
||||
for (let i = 0; i < tn.length; i++) {
|
||||
await dashboard.treeView.verifyTable({ title: tn[i] });
|
||||
|
||||
const context: NcContext = {
|
||||
project,
|
||||
token: '',
|
||||
dbType: (process.env.CI ? process.env.E2E_DB_TYPE : process.env.E2E_DEV_DB_TYPE) || 'mysql'
|
||||
}
|
||||
|
||||
await dashboard.treeView.openTable({ title: "Film" });
|
||||
// for Film table, verify columns
|
||||
for (let i = 0; i < columnCount; i++) {
|
||||
await dashboard.grid.column.verify({ title: cn[i] });
|
||||
}
|
||||
|
||||
// normal cells
|
||||
for (let [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" });
|
||||
|
||||
// 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 });
|
||||
|
||||
// LinkToAnotherRecord
|
||||
await dashboard.grid.cell.verifyVirtualCell({ index: cellIndex, columnHeader: "Actor", value: 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] });
|
||||
|
||||
// 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 });
|
||||
|
||||
// LinkToAnotherRecord
|
||||
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.form.verifyFieldsIsEditable({ index: 0 });
|
||||
await dashboard.form.verifyAfterSubmitMsg({ msg: "Thank you for submitting the form!" });
|
||||
await dashboard.form.verifyAfterSubmitMenuState({
|
||||
emailMe: false,
|
||||
showBlankForm: true,
|
||||
submitAnotherForm: true,
|
||||
});
|
||||
|
||||
// Verify webhooks
|
||||
await dashboard.treeView.openTable({ title: "Actor" });
|
||||
// await dashboard.webhookForm.open({})
|
||||
await quickVerify({dashboard, context});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,13 +7,14 @@ export interface NcContext {
|
||||
dbType?: string;
|
||||
}
|
||||
|
||||
const setup = async ({page}: {page: Page}): 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`, {
|
||||
parallelId: process.env.TEST_PARALLEL_INDEX,
|
||||
dbType,
|
||||
isEmptyProject
|
||||
});
|
||||
|
||||
if(response.status !== 200) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { test } from "@playwright/test";
|
||||
import { DashboardPage } from "../pages/Dashboard";
|
||||
import { quickVerify } from "../quickTests/commonTest";
|
||||
import setup from "../setup";
|
||||
|
||||
const apiKey = process.env.E2E_AIRTABLE_API_KEY;
|
||||
@@ -9,45 +10,33 @@ test.describe("Import", () => {
|
||||
let dashboard: DashboardPage;
|
||||
let context: any;
|
||||
|
||||
test.setTimeout(150000);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
context = await setup({ page });
|
||||
page.setDefaultTimeout(50000);
|
||||
context = await setup({ page, isEmptyProject: true });
|
||||
dashboard = new DashboardPage(page, context.project);
|
||||
});
|
||||
|
||||
test("Airtable", async () => {
|
||||
// create empty project
|
||||
await dashboard.clickHome();
|
||||
await dashboard.createProject({ name: "airtable", type: "xcdb" });
|
||||
|
||||
await dashboard.treeView.quickImport({ title: "Airtable" });
|
||||
await dashboard.importAirtable.import({
|
||||
key: apiKey,
|
||||
baseId: apiBase,
|
||||
key: apiKey!,
|
||||
baseId: apiBase!,
|
||||
});
|
||||
await dashboard.rootPage.waitForTimeout(1000);
|
||||
await quickVerify({dashboard, airtableImport: true, context});
|
||||
});
|
||||
|
||||
test("Excel", async () => {
|
||||
// create empty project
|
||||
await dashboard.clickHome();
|
||||
await dashboard.createProject({ name: "excel", type: "xcdb" });
|
||||
|
||||
await dashboard.treeView.quickImport({ title: "Microsoft Excel" });
|
||||
});
|
||||
|
||||
test("CSV", async () => {
|
||||
// create empty project
|
||||
await dashboard.clickHome();
|
||||
await dashboard.createProject({ name: "CSV", type: "xcdb" });
|
||||
|
||||
await dashboard.treeView.quickImport({ title: "CSV file" });
|
||||
});
|
||||
|
||||
test("JSON", async () => {
|
||||
// create empty project
|
||||
await dashboard.clickHome();
|
||||
await dashboard.createProject({ name: "JSON", type: "xcdb" });
|
||||
|
||||
await dashboard.treeView.quickImport({ title: "JSON file" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ test.describe("User roles", () => {
|
||||
let settings: SettingsPage;
|
||||
let context: any;
|
||||
|
||||
test.beforeAll(async ({ page }) => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
context = await setup({ page });
|
||||
dashboard = new DashboardPage(page, context.project);
|
||||
toolbar = dashboard.grid.toolbar;
|
||||
|
||||
@@ -70,7 +70,7 @@ test.describe("Views CRUD Operations", () => {
|
||||
await dashboard.viewSidebar.deleteView({ title: "CityGallery2" });
|
||||
await dashboard.viewSidebar.verifyViewNotPresent({
|
||||
title: "CityGallery2",
|
||||
index: 2,
|
||||
index: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ test.describe("Webhook", () => {
|
||||
let dashboard: DashboardPage, toolbar: ToolbarPage, webhook: WebhookFormPage;
|
||||
let context: any;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
test.beforeEach(async () => {
|
||||
// start a server locally for webhook tests
|
||||
await makeServer();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user