import { expect, Locator, Page } from '@playwright/test'; import BasePage from '../Base'; import { GridPage } from './Grid'; import { FormPage } from './Form'; import { ExpandedFormPage } from './ExpandedForm'; import { BulkUpdatePage } from './BulkUpdate'; 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 { LeftSidebarPage } from './common/LeftSidebar'; import { ProjectViewPage } from './ProjectView'; import { GalleryPage } from './Gallery'; import { KanbanPage } from './Kanban'; import { MapPage } from './Map'; import { ImportAirtablePage } from './Import/Airtable'; import { ImportTemplatePage } from './Import/ImportTemplate'; import { WebhookFormPage } from './WebhookForm'; import { FindRowByScanOverlay } from './FindRowByScanOverlay'; import { SidebarPage } from './Sidebar'; import { DocsPageGroup } from './Docs'; import { ShareProjectButtonPage } from './ShareProjectButton'; import { ProjectTypes } from 'nocodb-sdk'; import { WorkspacePage } from '../WorkspacePage'; import { DetailsPage } from './Details'; import { WorkspaceSettingsObject } from './WorkspaceSettings'; export class DashboardPage extends BasePage { readonly project: any; readonly tablesSideBar: Locator; readonly projectMenuLink: Locator; readonly workspaceMenuLink: Locator; readonly tabBar: Locator; readonly treeView: TreeViewPage; readonly grid: GridPage; readonly gallery: GalleryPage; readonly form: FormPage; readonly kanban: KanbanPage; readonly map: MapPage; readonly expandedForm: ExpandedFormPage; readonly bulkUpdateForm: BulkUpdatePage; readonly webhookForm: WebhookFormPage; readonly findRowByScanOverlay: FindRowByScanOverlay; readonly childList: ChildList; readonly linkRecord: LinkRecord; readonly settings: SettingsPage; readonly viewSidebar: ViewSidebarPage; readonly leftSidebar: LeftSidebarPage; readonly projectView: ProjectViewPage; readonly importAirtable: ImportAirtablePage; readonly importTemplate = new ImportTemplatePage(this); readonly docs: DocsPageGroup; readonly sidebar: SidebarPage; readonly shareProjectButton: ShareProjectButtonPage; readonly details: DetailsPage; readonly workspaceSettings: WorkspaceSettingsObject; constructor(rootPage: Page, project: any) { super(rootPage); this.project = project; this.tablesSideBar = rootPage.locator('.nc-treeview-container'); this.workspaceMenuLink = rootPage.getByTestId('nc-project-menu'); this.projectMenuLink = rootPage .locator(`.project-title-node:has-text("${project.title}")`) .locator('[data-testid="nc-sidebar-context-menu"]') .first(); this.tabBar = rootPage.locator('.nc-tab-bar'); this.treeView = new TreeViewPage(this, project); this.grid = new GridPage(this); this.gallery = new GalleryPage(this); this.form = new FormPage(this); this.kanban = new KanbanPage(this); this.map = new MapPage(this); this.expandedForm = new ExpandedFormPage(this); this.bulkUpdateForm = new BulkUpdatePage(this); this.webhookForm = new WebhookFormPage(this); this.findRowByScanOverlay = new FindRowByScanOverlay(this); this.childList = new ChildList(this); this.linkRecord = new LinkRecord(this); this.settings = new SettingsPage(this); this.viewSidebar = new ViewSidebarPage(this); this.leftSidebar = new LeftSidebarPage(this); this.projectView = new ProjectViewPage(this); this.importAirtable = new ImportAirtablePage(this); this.sidebar = new SidebarPage(this); this.docs = new DocsPageGroup(this); this.shareProjectButton = new ShareProjectButtonPage(this); this.details = new DetailsPage(this); this.workspaceSettings = new WorkspaceSettingsObject(this); } get() { return this.rootPage.locator('html'); } async goto() { await this.rootPage.goto(`/#/${this.project.fk_workspace_id}/${this.project.id}`); } getProjectMenuLink({ title }: { title: string }) { return this.rootPage.locator(`div.nc-project-menu-item:has-text("${title}")`); } async verifyTeamAndSettingsLinkIsVisible() { await this.projectMenuLink.click(); const teamAndSettingsLink = this.getProjectMenuLink({ title: ' Team & Settings' }); await expect(teamAndSettingsLink).toBeVisible(); await this.projectMenuLink.click(); } async verifyTeamAndSettingsLinkIsNotVisible() { await this.projectMenuLink.click(); const teamAndSettingsLink = this.getProjectMenuLink({ title: ' Team & Settings' }); await expect(teamAndSettingsLink).not.toBeVisible(); await this.projectMenuLink.click(); } async gotoSettings() { await this.projectMenuLink.click(); await this.rootPage.locator('.ant-dropdown').locator(`.nc-menu-item:has-text("Settings")`).click(); } async gotoProjectSubMenu({ title }: { title: string }) { await this.projectMenuLink.click(); await this.rootPage.locator(`div.nc-project-menu-item:has-text("${title}")`).click(); } async verifyInTabBar({ title }: { title: string }) { await this.tabBar.textContent().then(text => expect(text).toContain(title)); } async closeTab({ title }: { title: string }) {} async clickHome() { await this.leftSidebar.clickHome(); // wait for workspace page to render const workspacePage = new WorkspacePage(this.rootPage); await workspacePage.waitFor({ state: 'visible' }); } async verifyOpenedTab({ title, mode = 'standard', emoji }: { title: string; mode?: string; emoji?: string }) { await this.tabBar.locator(`.ant-tabs-tab-active:has-text("${title}")`).isVisible(); if (emoji) { await expect( this.tabBar.locator(`.ant-tabs-tab-active:has-text("${title}")`).getByTestId(`nc-tab-icon-emojione:${emoji}`) ).toBeVisible(); } } async verifyTabIsNotOpened({ title }: { title: string }) { await expect(this.tabBar.locator(`.ant-tabs-tab:has-text("${title}")`)).not.toBeVisible(); } private async _waitForDocsTabRender({ title, mode }: { title: string; mode: string }) { 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.getByTestId(`nc-root-tabs-${title}`).evaluate(el => { return window.getComputedStyle(el).getPropertyValue('color'); }); }) .toBe('rgb(67, 81, 232)'); await this.rootPage.waitForTimeout(500); } // When a tab is opened, it is not always immediately visible. // Hence will wait till contents are visible async waitForTabRender({ title, mode = 'standard', type = ProjectTypes.DATABASE, }: { title: string; mode?: string; type?: ProjectTypes; }) {} async toggleMobileMode() { await this.projectMenuLink.click(); const projMenu = this.rootPage.locator('.nc-dropdown-project-menu'); await projMenu.locator('[data-menu-id="mobile-mode"]:visible').click(); await this.projectMenuLink.click(); } async signOut() { await this.sidebar.userMenu.click(); await this.rootPage.waitForTimeout(1000); await this.rootPage.getByTestId('nc-sidebar-user-logout').waitFor({ state: 'visible' }); await this.sidebar.userMenu.clickLogout(); await this.rootPage.waitForTimeout(1000); await this.rootPage.locator('[data-testid="nc-form-signin"]:visible').waitFor(); await new Promise(resolve => setTimeout(resolve, 150)); } async validateProjectMenu(param: { role: string; mode?: string }) { await this.rootPage.locator('[data-testid="nc-project-menu"]').click(); 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'], commenter: ['Copy Project Info', 'Copy Auth Token', 'Language', 'Account'], viewer: ['Copy Project Info', 'Copy Auth Token', 'Language', 'Account'], }; if (param?.mode === 'shareBase') { menuItems = { creator: [], commenter: [], editor: ['Language'], viewer: ['Language'], }; } // common items for (const item of menuItems[param.role]) { await expect(pMenu).toContainText(item); } await this.rootPage.locator('[data-testid="nc-project-menu"]').click(); } // 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('[data-testid="nc-loading"]').waitFor({ state: 'hidden' }); } /* async closeAllTabs() { await this.tabBar.locator(`.ant-tabs-tab`).waitFor({ state: 'visible' }); const tab = await this.tabBar.locator(`.ant-tabs-tab`); const tabCount = await tab.count(); for (let i = 0; i < tabCount; i++) { await tab.nth(i).locator('button.ant-tabs-tab-remove').click(); await tab.nth(i).waitFor({ state: 'detached' }); } }*/ async closeAllTabs() { const tab = this.tabBar.locator(`.ant-tabs-tab`); const tabCount = await tab.count(); for (let i = 0; i < tabCount; i++) { await tab.nth(i).locator('button.ant-tabs-tab-remove').click(); await this.rootPage.waitForTimeout(200); } } async validateWorkspaceMenu(param: { role: string; mode?: string }) { await this.grid.workspaceMenu.toggle(); await this.grid.workspaceMenu.get().waitFor({ state: 'visible' }); const pMenu = this.grid.workspaceMenu.get(); // menu items let menuItems = { creator: ['Collaborators', 'Settings', 'Copy Auth Token', 'Themes', 'Preview as', 'Language', 'Account'], editor: ['Collaborators', 'Settings', 'Copy Auth Token', 'Language', 'Account'], commenter: ['Collaborators', 'Settings', 'Copy Auth Token', 'Language', 'Account'], viewer: ['Collaborators', 'Settings', 'Copy Auth Token', 'Language', 'Account'], }; if (param?.mode === 'shareBase') { menuItems = { creator: [], commenter: [], editor: ['Language'], viewer: ['Language'], }; } // common items for (const item of menuItems[param.role]) { await expect(pMenu).toContainText(item); } // menuItems.creator is a super set. validate if the corresponding items missing in editor, commenter, viewer are not present for (const item of menuItems.creator) { if (!menuItems[param.role].includes(item)) { await expect(pMenu).not.toContainText(item); } } await this.grid.workspaceMenu.toggle(); } }