mirror of
https://github.com/nocodb/nocodb.git
synced 2026-02-02 02:57:23 +00:00
feat: scripts general pw tests
This commit is contained in:
103
tests/playwright/pages/Dashboard/Scripts/ConfigPanel.ts
Normal file
103
tests/playwright/pages/Dashboard/Scripts/ConfigPanel.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { expect, Locator } from '@playwright/test';
|
||||
import BasePage from '../../Base';
|
||||
import { ScriptsPage } from './index';
|
||||
|
||||
export class ScriptsConfigPanel extends BasePage {
|
||||
readonly scriptsPage: ScriptsPage;
|
||||
|
||||
constructor(scriptsPage: ScriptsPage) {
|
||||
super(scriptsPage.rootPage);
|
||||
this.scriptsPage = scriptsPage;
|
||||
}
|
||||
|
||||
get(): Locator {
|
||||
return this.rootPage.getByTestId('nc-script-config-panel');
|
||||
}
|
||||
|
||||
async verifyVisible(): Promise<void> {
|
||||
await expect(this.get()).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyTitle(title: string): Promise<void> {
|
||||
await expect(this.rootPage.getByTestId('nc-script-config-title')).toHaveText(title);
|
||||
}
|
||||
|
||||
async verifyDescription(description: string): Promise<void> {
|
||||
await expect(this.rootPage.getByTestId('nc-script-config-description')).toContainText(description);
|
||||
}
|
||||
|
||||
async verifyWarningVisible(): Promise<void> {
|
||||
await expect(this.rootPage.getByTestId('nc-script-config-warning')).toBeVisible();
|
||||
}
|
||||
|
||||
async fillInput(
|
||||
key: string,
|
||||
value: string | number,
|
||||
type?: 'text' | 'number' | 'select' | 'table' | 'view' | 'field'
|
||||
): Promise<void> {
|
||||
if (!type) {
|
||||
// Try to find which input exists
|
||||
const textInput = this.rootPage.getByTestId(`nc-script-config-text-${key}`);
|
||||
const numberInput = this.rootPage.getByTestId(`nc-script-config-number-${key}`);
|
||||
const selectInput = this.rootPage.getByTestId(`nc-script-config-select-${key}`);
|
||||
const tableInput = this.rootPage.getByTestId(`nc-script-config-table-${key}`);
|
||||
const viewInput = this.rootPage.getByTestId(`nc-script-config-view-${key}`);
|
||||
const fieldInput = this.rootPage.getByTestId(`nc-script-config-field-${key}`);
|
||||
|
||||
if (await textInput.isVisible().catch(() => false)) {
|
||||
type = 'text';
|
||||
} else if (await numberInput.isVisible().catch(() => false)) {
|
||||
type = 'number';
|
||||
} else if (await selectInput.isVisible().catch(() => false)) {
|
||||
type = 'select';
|
||||
} else if (await tableInput.isVisible().catch(() => false)) {
|
||||
type = 'table';
|
||||
} else if (await viewInput.isVisible().catch(() => false)) {
|
||||
type = 'view';
|
||||
} else if (await fieldInput.isVisible().catch(() => false)) {
|
||||
type = 'field';
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'text':
|
||||
await this.rootPage.getByTestId(`nc-script-config-text-${key}`).fill(value.toString());
|
||||
break;
|
||||
case 'number':
|
||||
await this.rootPage.getByTestId(`nc-script-config-number-${key}`).fill(value.toString());
|
||||
break;
|
||||
case 'select':
|
||||
await this.rootPage.getByTestId(`nc-script-config-select-${key}`).click();
|
||||
await this.rootPage.waitForTimeout(300);
|
||||
await this.rootPage
|
||||
.locator(`.ant-select-dropdown:visible .ant-select-item`)
|
||||
.filter({ hasText: value.toString() })
|
||||
.first()
|
||||
.click();
|
||||
await this.rootPage.waitForTimeout(300);
|
||||
break;
|
||||
case 'table':
|
||||
case 'view':
|
||||
case 'field': {
|
||||
// Click the selector to open dropdown
|
||||
await this.rootPage.getByTestId(`nc-script-config-${type}-${key}`).click();
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
|
||||
// Find and click the item in the NcList dropdown
|
||||
const dropdown = this.rootPage.locator('.nc-dropdown-list-wrapper:visible, .nc-list-wrapper:visible');
|
||||
await dropdown.waitFor({ state: 'visible', timeout: 5000 });
|
||||
|
||||
// Click the item by text
|
||||
const item = dropdown.locator('.nc-list-item').filter({ hasText: value.toString() }).first();
|
||||
await item.click();
|
||||
await this.rootPage.waitForTimeout(300);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
await this.rootPage.getByTestId('nc-script-config-save-btn').click();
|
||||
await this.rootPage.waitForTimeout(1000);
|
||||
}
|
||||
}
|
||||
108
tests/playwright/pages/Dashboard/Scripts/Playground.ts
Normal file
108
tests/playwright/pages/Dashboard/Scripts/Playground.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { expect, Locator } from '@playwright/test';
|
||||
import BasePage from '../../Base';
|
||||
import { ScriptsPage } from './index';
|
||||
|
||||
export class ScriptsPlayground extends BasePage {
|
||||
readonly scriptsPage: ScriptsPage;
|
||||
|
||||
constructor(scriptsPage: ScriptsPage) {
|
||||
super(scriptsPage.rootPage);
|
||||
this.scriptsPage = scriptsPage;
|
||||
}
|
||||
|
||||
get(): Locator {
|
||||
return this.rootPage.locator('.nc-playground-container').first();
|
||||
}
|
||||
|
||||
async verifyTextOutput(text: string): Promise<void> {
|
||||
const textItem = this.rootPage.getByTestId('nc-playground-text-output').filter({ hasText: text });
|
||||
await expect(textItem).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
async verifyMarkdownOutput(text: string): Promise<void> {
|
||||
const markdownItem = this.rootPage.getByTestId('nc-playground-markdown-output').filter({ hasText: text });
|
||||
await expect(markdownItem).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
async verifyTableOutput(): Promise<void> {
|
||||
const tableItem = this.rootPage.getByTestId('nc-playground-table-output');
|
||||
await expect(tableItem).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
async verifyInspectOutput(): Promise<void> {
|
||||
const inspectItem = this.rootPage.getByTestId('nc-playground-inspect-output');
|
||||
await expect(inspectItem).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
// Input interaction methods
|
||||
async fillTextInput(label: string, value: string): Promise<void> {
|
||||
const inputContainer = this.rootPage.getByTestId('nc-playground-input');
|
||||
await inputContainer.waitFor({ state: 'visible', timeout: 10000 });
|
||||
|
||||
const textInput = inputContainer.locator('input[type="text"], textarea').first();
|
||||
await textInput.fill(value);
|
||||
|
||||
const submitButton = inputContainer.locator('button').first();
|
||||
await submitButton.click();
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async clickButton(buttonLabel: string): Promise<void> {
|
||||
const inputContainer = this.rootPage.getByTestId('nc-playground-input');
|
||||
await inputContainer.waitFor({ state: 'visible', timeout: 10000 });
|
||||
|
||||
const button = inputContainer.locator(`button:has-text("${buttonLabel}")`);
|
||||
await button.click();
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async selectOption(optionLabel: string): Promise<void> {
|
||||
const inputContainer = this.rootPage.getByTestId('nc-playground-input');
|
||||
await inputContainer.waitFor({ state: 'visible', timeout: 10000 });
|
||||
|
||||
const select = inputContainer.locator('select, .ant-select').first();
|
||||
await select.click();
|
||||
await this.rootPage.waitForTimeout(300);
|
||||
|
||||
const option = this.rootPage.locator(`.ant-select-dropdown:visible .ant-select-item:has-text("${optionLabel}")`);
|
||||
await option.click();
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async uploadFile(filePath: string): Promise<void> {
|
||||
const inputContainer = this.rootPage.getByTestId('nc-playground-input');
|
||||
await inputContainer.waitFor({ state: 'visible', timeout: 10000 });
|
||||
|
||||
const fileInput = inputContainer.locator('input[type="file"]');
|
||||
await fileInput.setInputFiles(filePath);
|
||||
await this.rootPage.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
// Generic output verification
|
||||
async verifyOutputContains(text: string): Promise<void> {
|
||||
await expect(this.get()).toContainText(text, { timeout: 10000 });
|
||||
}
|
||||
|
||||
async verifyOutputNotContains(text: string): Promise<void> {
|
||||
await expect(this.get()).not.toContainText(text);
|
||||
}
|
||||
|
||||
// Verify playground is cleared (no items)
|
||||
async verifyClear(): Promise<void> {
|
||||
// After clear, playground items should not exist
|
||||
const playgroundItems = this.rootPage.locator('[data-testid^="nc-playground-item-"]');
|
||||
await expect(playgroundItems).toHaveCount(0);
|
||||
}
|
||||
|
||||
// Verify empty state message
|
||||
async verifyEmptyState(): Promise<void> {
|
||||
const emptyMessage = this.rootPage.getByTestId('nc-playground-empty');
|
||||
await expect(emptyMessage).toBeVisible();
|
||||
}
|
||||
|
||||
// Workflow step verification
|
||||
async verifyWorkflowStep(title: string): Promise<void> {
|
||||
const workflowStep = this.get().locator('.workflow-step-card .step-header', { hasText: title });
|
||||
await expect(workflowStep).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
}
|
||||
106
tests/playwright/pages/Dashboard/Scripts/Topbar.ts
Normal file
106
tests/playwright/pages/Dashboard/Scripts/Topbar.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { expect, Locator } from '@playwright/test';
|
||||
import BasePage from '../../Base';
|
||||
import { ScriptsPage } from './index';
|
||||
|
||||
export class ScriptsTopbar extends BasePage {
|
||||
readonly scriptsPage: ScriptsPage;
|
||||
|
||||
constructor(scriptsPage: ScriptsPage) {
|
||||
super(scriptsPage.rootPage);
|
||||
this.scriptsPage = scriptsPage;
|
||||
}
|
||||
|
||||
// Settings button
|
||||
getSettingsButton(): Locator {
|
||||
return this.rootPage.getByTestId('nc-script-settings-btn');
|
||||
}
|
||||
|
||||
async clickSettings(): Promise<void> {
|
||||
await this.getSettingsButton().click();
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async verifySettingsButtonVisible(): Promise<void> {
|
||||
await expect(this.getSettingsButton()).toBeVisible();
|
||||
}
|
||||
|
||||
async verifySettingsButtonActive(isActive: boolean): Promise<void> {
|
||||
const button = this.rootPage.getByTestId('nc-script-settings-btn');
|
||||
if (isActive) {
|
||||
await expect(button).toHaveClass(/is-settings-open/);
|
||||
} else {
|
||||
await expect(button).not.toHaveClass(/is-settings-open/);
|
||||
}
|
||||
}
|
||||
|
||||
// Run button
|
||||
getRunButton(): Locator {
|
||||
return this.rootPage.getByTestId('nc-script-run-btn');
|
||||
}
|
||||
|
||||
async clickRun(): Promise<void> {
|
||||
await this.getRunButton().click();
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async verifyRunButtonState(enabled: boolean) {
|
||||
if (enabled) {
|
||||
await expect(this.getRunButton()).toBeEnabled();
|
||||
} else {
|
||||
await expect(this.getRunButton()).toBeDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop button
|
||||
getStopButton(): Locator {
|
||||
return this.rootPage.getByTestId('nc-script-stop-btn');
|
||||
}
|
||||
|
||||
async clickStop(): Promise<void> {
|
||||
await this.getStopButton().click();
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async verifyStopButtonVisible(): Promise<void> {
|
||||
await expect(this.getStopButton()).toBeVisible();
|
||||
}
|
||||
|
||||
// Restart button
|
||||
getRestartButton(): Locator {
|
||||
return this.rootPage.getByTestId('nc-script-restart-btn');
|
||||
}
|
||||
|
||||
async clickRestart(): Promise<void> {
|
||||
await this.getRestartButton().click();
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async verifyScriptState({ state }: { state: 'running' | 'stopped' }) {
|
||||
const elem = this.rootPage.getByTestId('nc-script-running-indicator');
|
||||
if (state === 'running') {
|
||||
await expect(elem).toBeVisible();
|
||||
} else {
|
||||
await expect(elem).not.toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for execution to complete
|
||||
async waitForExecutionComplete(timeout: number = 10000): Promise<void> {
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
|
||||
// Wait for running indicator to disappear
|
||||
const runningIndicator = this.rootPage.getByTestId('nc-script-running-indicator');
|
||||
await runningIndicator.waitFor({ state: 'hidden', timeout });
|
||||
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async runScript(): Promise<void> {
|
||||
await this.clickRun();
|
||||
await this.waitForExecutionComplete();
|
||||
}
|
||||
|
||||
get(): Locator {
|
||||
return this.rootPage.locator('.nc-table-topbar');
|
||||
}
|
||||
}
|
||||
59
tests/playwright/pages/Dashboard/Scripts/index.ts
Normal file
59
tests/playwright/pages/Dashboard/Scripts/index.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import BasePage from '../../Base';
|
||||
import { DashboardPage } from '..';
|
||||
import { ScriptsTopbar } from './Topbar';
|
||||
import { ScriptsConfigPanel } from './ConfigPanel';
|
||||
import { ScriptsPlayground } from './Playground';
|
||||
|
||||
export class ScriptsPage extends BasePage {
|
||||
readonly dashboardPage: DashboardPage;
|
||||
readonly topbar: ScriptsTopbar;
|
||||
readonly configPanel: ScriptsConfigPanel;
|
||||
readonly playground: ScriptsPlayground;
|
||||
|
||||
constructor(dashboard: DashboardPage) {
|
||||
super(dashboard.rootPage);
|
||||
this.dashboardPage = dashboard;
|
||||
this.topbar = new ScriptsTopbar(this);
|
||||
this.configPanel = new ScriptsConfigPanel(this);
|
||||
this.playground = new ScriptsPlayground(this);
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.dashboardPage.get().locator('.nc-scripts-content-resizable-wrapper');
|
||||
}
|
||||
// Editor methods
|
||||
async getEditorContent(): Promise<string> {
|
||||
const editorContainer = this.rootPage.getByTestId('nc-scripts-editor');
|
||||
const content = await editorContainer.getAttribute('data-code');
|
||||
return content || '';
|
||||
}
|
||||
|
||||
async setEditorContent(
|
||||
content: string,
|
||||
scriptId: string,
|
||||
workspaceId: string,
|
||||
baseId: string,
|
||||
api: any
|
||||
): Promise<void> {
|
||||
await api.internal.postOperation(
|
||||
workspaceId,
|
||||
baseId,
|
||||
{
|
||||
operation: 'updateScript',
|
||||
},
|
||||
{
|
||||
id: scriptId,
|
||||
script: content,
|
||||
}
|
||||
);
|
||||
|
||||
await this.rootPage.waitForTimeout(500);
|
||||
await this.dashboardPage.waitForLoaderToDisappear();
|
||||
}
|
||||
|
||||
async verifyEditorContentContains(text: string): Promise<void> {
|
||||
const content = await this.getEditorContent();
|
||||
expect(content).toContain(text);
|
||||
}
|
||||
}
|
||||
@@ -161,6 +161,24 @@ export class TreeViewPage extends BasePage {
|
||||
}
|
||||
}
|
||||
|
||||
async openScript({ title, baseTitle }: { title: string; baseTitle?: string }) {
|
||||
if (baseTitle) {
|
||||
await this.dashboard.sidebar.baseNode.verifyActiveProject({ baseTitle, open: true });
|
||||
}
|
||||
|
||||
const scriptNode = this.get().getByTestId(`view-sidebar-script-${title}`);
|
||||
|
||||
await scriptNode.waitFor({ state: 'visible' });
|
||||
|
||||
await scriptNode.click({});
|
||||
|
||||
// todo: remove this after fixing the issue
|
||||
await this.rootPage.waitForTimeout(1000);
|
||||
await scriptNode.click({
|
||||
// x:10, y:10
|
||||
});
|
||||
}
|
||||
|
||||
async createEntity({
|
||||
type,
|
||||
skipOpeningModal,
|
||||
|
||||
@@ -29,6 +29,7 @@ import { CmdK } from './Command/CmdKPage';
|
||||
import { CmdL } from './Command/CmdLPage';
|
||||
import { CalendarPage } from './Calendar';
|
||||
import { Extensions } from './Extensions';
|
||||
import { ScriptsPage } from './Scripts';
|
||||
|
||||
export class DashboardPage extends BasePage {
|
||||
readonly base: any;
|
||||
@@ -63,6 +64,7 @@ export class DashboardPage extends BasePage {
|
||||
readonly cmdK: CmdK;
|
||||
readonly cmdL: CmdL;
|
||||
readonly extensions: Extensions;
|
||||
readonly scripts: ScriptsPage;
|
||||
|
||||
constructor(rootPage: Page, base: any) {
|
||||
super(rootPage);
|
||||
@@ -97,6 +99,7 @@ export class DashboardPage extends BasePage {
|
||||
this.cmdK = new CmdK(this);
|
||||
this.cmdL = new CmdL(this);
|
||||
this.extensions = new Extensions(this);
|
||||
this.scripts = new ScriptsPage(this);
|
||||
}
|
||||
|
||||
get() {
|
||||
|
||||
Reference in New Issue
Block a user