diff --git a/packages/compass-components/src/components/workspace-tabs/tab.tsx b/packages/compass-components/src/components/workspace-tabs/tab.tsx index 961fdbc57aa..3a498be3ff4 100644 --- a/packages/compass-components/src/components/workspace-tabs/tab.tsx +++ b/packages/compass-components/src/components/workspace-tabs/tab.tsx @@ -184,6 +184,8 @@ const selectedCloseButtonStyles = css({ type IconGlyph = Extract; type TabProps = { + connectionName?: string; + type: string; title: string; isSelected: boolean; isDragging: boolean; @@ -196,6 +198,8 @@ type TabProps = { }; function Tab({ + connectionName, + type, title, isSelected, isDragging, @@ -254,6 +258,8 @@ function Tab({ tabIndex={isSelected ? 0 : -1} aria-controls={tabContentId} data-testid="workspace-tab-button" + data-connectionName={connectionName} + data-type={type} title={subtitle ? subtitle : title} {...tabProps} > diff --git a/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx b/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx index 8d94e63901c..d1b2e9beb87 100644 --- a/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx +++ b/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx @@ -170,6 +170,7 @@ type WorkspaceTabsProps = { export type TabProps = { id: string; + type: string; title: string; subtitle?: string; connectionId?: string; diff --git a/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx b/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx index bc8ce3cd9cd..706aac11d3e 100644 --- a/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx +++ b/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx @@ -572,7 +572,10 @@ describe('ConnectionsNavigationTree', function () { userEvent.click(otherActions); expect(screen.getByText('Open MongoDB shell')).to.be.visible; - if (name !== 'when connection is datalake') { + if ( + name !== 'when connection is datalake' && + name !== 'when connection is not writable' + ) { expect( screen.getByTestId( 'sidebar-navigation-item-actions-open-shell-action' diff --git a/packages/compass-connections-navigation/src/tree-data.ts b/packages/compass-connections-navigation/src/tree-data.ts index 574b397f7d6..9d6b033bd83 100644 --- a/packages/compass-connections-navigation/src/tree-data.ts +++ b/packages/compass-connections-navigation/src/tree-data.ts @@ -170,7 +170,7 @@ const connectedConnectionToItems = ({ const colorCode = connectionInfo.favorite?.color; const hasWriteActionsDisabled = preferencesReadOnly || isDataLake || !isWritable; - const isShellEnabled = !preferencesReadOnly && isWritable; + const isShellEnabled = !preferencesReadOnly; const connectionTI: ConnectedConnectionTreeItem = { id: connectionInfo.id, level: 1, diff --git a/packages/compass-e2e-tests/helpers/commands/close-shell.ts b/packages/compass-e2e-tests/helpers/commands/close-shell.ts new file mode 100644 index 00000000000..96e66d4df0a --- /dev/null +++ b/packages/compass-e2e-tests/helpers/commands/close-shell.ts @@ -0,0 +1,23 @@ +import { TEST_MULTIPLE_CONNECTIONS } from '../compass'; +import type { CompassBrowser } from '../compass-browser'; +import retryWithBackoff from '../retry-with-backoff'; +import * as Selectors from '../selectors'; + +export async function closeShell( + browser: CompassBrowser, + connectionName: string +): Promise { + if (TEST_MULTIPLE_CONNECTIONS) { + await browser.closeWorkspaceTab({ + connectionName, + type: 'Shell', + }); + } else { + await retryWithBackoff(async function () { + const shellContentElement = await browser.$(Selectors.ShellContent); + if (await shellContentElement.isDisplayed()) { + await browser.clickVisible(Selectors.ShellExpandButton); + } + }); + } +} diff --git a/packages/compass-e2e-tests/helpers/commands/close-workspace-tabs.ts b/packages/compass-e2e-tests/helpers/commands/close-workspace-tabs.ts deleted file mode 100644 index abf855745f7..00000000000 --- a/packages/compass-e2e-tests/helpers/commands/close-workspace-tabs.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { CompassBrowser } from '../compass-browser'; -import * as Selectors from '../selectors'; - -export async function closeWorkspaceTabs( - browser: CompassBrowser, - autoConfirmTabClose = true -): Promise { - const countTabs = async () => { - return (await browser.$$(Selectors.workspaceTab(null))).length; - }; - - while ((await countTabs()) > 0) { - const currentActiveTab = await browser.$( - Selectors.workspaceTab(null, true) - ); - await currentActiveTab.click(); - await browser.waitUntil(async () => { - await currentActiveTab.$(Selectors.CloseWorkspaceTab).click(); - if (autoConfirmTabClose) { - // Tabs in "dirty" state can't be closed without confirmation - if (await browser.$(Selectors.ConfirmTabCloseModal).isExisting()) { - await browser.clickVisible( - browser.$(Selectors.ConfirmTabCloseModal).$('button=Close tab') - ); - } - } - return (await currentActiveTab.isExisting()) === false; - }); - } -} diff --git a/packages/compass-e2e-tests/helpers/commands/collection-workspaces.ts b/packages/compass-e2e-tests/helpers/commands/collection-workspaces.ts index 7399dca5ee5..ebfc699cdf9 100644 --- a/packages/compass-e2e-tests/helpers/commands/collection-workspaces.ts +++ b/packages/compass-e2e-tests/helpers/commands/collection-workspaces.ts @@ -113,7 +113,7 @@ export async function waitUntilActiveCollectionSubTab( export async function getActiveTabNamespace(browser: CompassBrowser) { const activeWorkspaceNamespace = await browser - .$(Selectors.workspaceTab(null, true)) + .$(Selectors.workspaceTab({ active: true })) .getAttribute('data-namespace'); return activeWorkspaceNamespace || null; } diff --git a/packages/compass-e2e-tests/helpers/commands/connect-with-connection-string.ts b/packages/compass-e2e-tests/helpers/commands/connect-with-connection-string.ts index aba0aa08649..5a03de4a0d8 100644 --- a/packages/compass-e2e-tests/helpers/commands/connect-with-connection-string.ts +++ b/packages/compass-e2e-tests/helpers/commands/connect-with-connection-string.ts @@ -71,7 +71,7 @@ export async function connectWithConnectionString( // some connection should be expanded (ie. connected) by now await browser - .$(`${Selectors.SidebarTreeItems} [aria-expanded=true]`) - .waitForExist({ reverse: true }); + .$(`${Selectors.SidebarTreeItems}[aria-expanded=true]`) + .waitForExist(); } } diff --git a/packages/compass-e2e-tests/helpers/commands/disconnect.ts b/packages/compass-e2e-tests/helpers/commands/disconnect.ts index 677bd156b8b..45b27a078fe 100644 --- a/packages/compass-e2e-tests/helpers/commands/disconnect.ts +++ b/packages/compass-e2e-tests/helpers/commands/disconnect.ts @@ -44,7 +44,7 @@ export async function disconnect(browser: CompassBrowser): Promise { // no active connections left. Use a different command if you expect to // disconnect just one connection and still keep others around. await browser - .$(`${Selectors.SidebarTreeItems} [aria-expanded=true]`) + .$(`${Selectors.SidebarTreeItems}[aria-expanded=true]`) .waitForExist({ reverse: true }); // The potential problem here is that the list is virtual, so it is possible diff --git a/packages/compass-e2e-tests/helpers/commands/hide-shell.ts b/packages/compass-e2e-tests/helpers/commands/hide-shell.ts deleted file mode 100644 index 5303b5afe3e..00000000000 --- a/packages/compass-e2e-tests/helpers/commands/hide-shell.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { CompassBrowser } from '../compass-browser'; -import retryWithBackoff from '../retry-with-backoff'; -import * as Selectors from '../selectors'; - -export async function hideShell(browser: CompassBrowser): Promise { - await retryWithBackoff(async function () { - const shellContentElement = await browser.$(Selectors.ShellContent); - if (await shellContentElement.isDisplayed()) { - await browser.clickVisible(Selectors.ShellExpandButton); - } - }); -} diff --git a/packages/compass-e2e-tests/helpers/commands/index.ts b/packages/compass-e2e-tests/helpers/commands/index.ts index ecfa9e55207..7189e3fb559 100644 --- a/packages/compass-e2e-tests/helpers/commands/index.ts +++ b/packages/compass-e2e-tests/helpers/commands/index.ts @@ -15,7 +15,7 @@ export * from './collection-workspaces'; export * from './run-find-operation'; export * from './focus-stage-operator'; export * from './select-stage-operator'; -export * from './close-workspace-tabs'; +export * from './workspace-tabs'; export * from './set-validation'; export * from './wait-for-animations'; export * from './wait-for-aria-disabled'; @@ -47,8 +47,8 @@ export * from './select-connections-menu-item'; export * from './open-settings-modal'; export * from './wait-for-connection-result'; export * from './screenshot'; -export * from './show-shell'; -export * from './hide-shell'; +export * from './open-shell'; +export * from './close-shell'; export * from './select-option'; export * from './select-stage-menu-option'; export * from './select-text-pipeline-output-option'; diff --git a/packages/compass-e2e-tests/helpers/commands/open-shell.ts b/packages/compass-e2e-tests/helpers/commands/open-shell.ts new file mode 100644 index 00000000000..9dd5f14a43d --- /dev/null +++ b/packages/compass-e2e-tests/helpers/commands/open-shell.ts @@ -0,0 +1,42 @@ +import { TEST_MULTIPLE_CONNECTIONS } from '../compass'; +import type { CompassBrowser } from '../compass-browser'; +import retryWithBackoff from '../retry-with-backoff'; +import * as Selectors from '../selectors'; + +export async function openShell( + browser: CompassBrowser, + connectionName: string +): Promise { + if (TEST_MULTIPLE_CONNECTIONS) { + await browser.selectConnectionMenuItem( + connectionName, + Selectors.Multiple.OpenShellItem + ); + + // try and make sure the shell tab is active and ready + await browser.waitUntil(async () => { + const currentActiveTab = await browser.$( + Selectors.workspaceTab({ active: true }) + ); + const activeType = await currentActiveTab.getAttribute('data-type'); + const activeConnectionName = await currentActiveTab.getAttribute( + 'data-connectionName' + ); + return activeType === 'Shell' && activeConnectionName === connectionName; + }); + + await browser.clickVisible(Selectors.ShellInputEditor); + } else { + // Expand the shell + await retryWithBackoff(async function () { + const shellContentElement = await browser.$(Selectors.ShellContent); + if (!(await shellContentElement.isDisplayed())) { + // The toasts may be covering the shell, so we need to close them. + await browser.hideAllVisibleToasts(); + await browser.clickVisible(Selectors.ShellExpandButton); + } + + await browser.clickVisible(Selectors.ShellInputEditor); + }); + } +} diff --git a/packages/compass-e2e-tests/helpers/commands/shell-eval.ts b/packages/compass-e2e-tests/helpers/commands/shell-eval.ts index 8acaf3efb4f..7ee0599b7b6 100644 --- a/packages/compass-e2e-tests/helpers/commands/shell-eval.ts +++ b/packages/compass-e2e-tests/helpers/commands/shell-eval.ts @@ -9,10 +9,13 @@ async function getOutputText(browser: CompassBrowser): Promise { export async function shellEval( browser: CompassBrowser, + connectionName: string, str: string, parse = false ): Promise { - await browser.showShell(); + // Keep in mind that for multiple connections this will open a new tab and + // focus it. + await browser.openShell(connectionName); const numLines = (await getOutputText(browser)).length; @@ -52,7 +55,11 @@ export async function shellEval( } } - await browser.hideShell(); + // For multiple connections we're currently making the assumption that closing + // the shell will put the user back on the tab they were on before + // opening the shell tab. This might not stay true as we start testing more + // complicated user flows. + await browser.closeShell(connectionName); return result as string; } diff --git a/packages/compass-e2e-tests/helpers/commands/show-shell.ts b/packages/compass-e2e-tests/helpers/commands/show-shell.ts deleted file mode 100644 index 3179ced6f02..00000000000 --- a/packages/compass-e2e-tests/helpers/commands/show-shell.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { CompassBrowser } from '../compass-browser'; -import retryWithBackoff from '../retry-with-backoff'; -import * as Selectors from '../selectors'; - -export async function showShell(browser: CompassBrowser): Promise { - // Expand the shVdell - await retryWithBackoff(async function () { - const shellContentElement = await browser.$(Selectors.ShellContent); - if (!(await shellContentElement.isDisplayed())) { - // The toasts may be covering the shell, so we need to close them. - await browser.hideAllVisibleToasts(); - await browser.clickVisible(Selectors.ShellExpandButton); - } - - await browser.clickVisible(Selectors.ShellInputEditor); - }); -} diff --git a/packages/compass-e2e-tests/helpers/commands/wait-for-connection-result.ts b/packages/compass-e2e-tests/helpers/commands/wait-for-connection-result.ts index 1b4b104eb6f..556f68874ec 100644 --- a/packages/compass-e2e-tests/helpers/commands/wait-for-connection-result.ts +++ b/packages/compass-e2e-tests/helpers/commands/wait-for-connection-result.ts @@ -21,7 +21,7 @@ export async function waitForConnectionResult( selector = TEST_COMPASS_WEB ? '[data-testid="workspace-tab-button"][title=Databases]' : TEST_MULTIPLE_CONNECTIONS - ? Selectors.SidebarTreeItems + ? `${Selectors.SidebarTreeItems}[aria-expanded=true]` : Selectors.MyQueriesList; } else { // TODO(COMPASS-7600): this doesn't support compass-web yet, but also isn't @@ -40,5 +40,10 @@ export async function waitForConnectionResult( await browser .$(Selectors.ConnectionModal) .waitForDisplayed({ reverse: true }); + + // make sure the placeholders for databases & collections that are loading are all gone + await browser + .$(Selectors.DatabaseCollectionPlaceholder) + .waitForDisplayed({ reverse: true }); } } diff --git a/packages/compass-e2e-tests/helpers/commands/workspace-tabs.ts b/packages/compass-e2e-tests/helpers/commands/workspace-tabs.ts new file mode 100644 index 00000000000..fd0aba00097 --- /dev/null +++ b/packages/compass-e2e-tests/helpers/commands/workspace-tabs.ts @@ -0,0 +1,83 @@ +import type { CompassBrowser } from '../compass-browser'; +import * as Selectors from '../selectors'; +import type { WorkspaceTabSelectorOptions } from '../selectors'; + +export async function navigateToMyQueries(browser: CompassBrowser) { + await browser.clickVisible(Selectors.SidebarMyQueriesTab); + await browser + .$(Selectors.workspaceTab({ title: 'My Queries', active: true })) + .waitForDisplayed(); +} + +async function closeTab( + browser: CompassBrowser, + selectorOptions: WorkspaceTabSelectorOptions, + autoConfirmTabClose: boolean +): Promise { + await browser + .$(Selectors.workspaceTab(selectorOptions)) + .$(Selectors.CloseWorkspaceTab) + .click(); + + // wait until the tab goes away and if the confirmation modal opens, maybe confirm + await browser.waitUntil(async () => { + if (autoConfirmTabClose) { + // Tabs in "dirty" state can't be closed without confirmation + if (await browser.$(Selectors.ConfirmTabCloseModal).isExisting()) { + await browser.clickVisible( + browser.$(Selectors.ConfirmTabCloseModal).$('button=Close tab') + ); + await browser + .$(Selectors.ConfirmTabCloseModal) + .waitForDisplayed({ reverse: true }); + } + } + return ( + (await browser + .$(Selectors.workspaceTab(selectorOptions)) + .isExisting()) === false + ); + }); +} + +export async function closeWorkspaceTabs( + browser: CompassBrowser, + autoConfirmTabClose = true +): Promise { + const countTabs = async () => { + return (await browser.$$(Selectors.workspaceTab())).length; + }; + + while ((await countTabs()) > 0) { + const currentActiveTab = await browser.$( + Selectors.workspaceTab({ active: true }) + ); + await currentActiveTab.click(); + // Close this exact active tab rather than "the active one" because if there + // are multiple tabs then another tab will immediately become active and + // trip up the logic that checks that the tab you closed went away. + const id = await currentActiveTab.getAttribute('id'); + await closeTab(browser, { id }, autoConfirmTabClose); + } +} + +export async function closeWorkspaceTab( + browser: CompassBrowser, + selectorOptions: WorkspaceTabSelectorOptions +): Promise { + const currentTabId = await browser + .$(Selectors.workspaceTab({ active: true })) + .getAttribute('id'); + const targetTabId = await browser + .$(Selectors.workspaceTab(selectorOptions)) + .getAttribute('id'); + + if (currentTabId !== targetTabId) { + // The tab we want to close isn't the active one so the close button isn't + // visible. We can either focus it which would have the side-effect of + // changing the tab focus or we can try and hover over it. + await browser.hover(Selectors.workspaceTab(selectorOptions)); + } + + await closeTab(browser, selectorOptions, true); +} diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index 13ceb5f264b..98fe21b5992 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1,5 +1,14 @@ import { TEST_MULTIPLE_CONNECTIONS } from './compass'; +export type WorkspaceTabSelectorOptions = { + id?: string; + connectionName?: string; + namespace?: string; + type?: string; + title?: string; + active?: boolean; +}; + // Settings Modal export const SettingsModal = '[data-testid="settings-modal"]'; export const CloseSettingsModalButton = `${SettingsModal} [aria-label="Close modal"]`; @@ -316,6 +325,7 @@ export const RenameCollectionButton = export const DropDatabaseButton = '[data-action="drop-database"]'; export const CreateCollectionButton = '[data-action="create-collection"]'; export const DropCollectionButton = '[data-action="drop-collection"]'; +export const DatabaseCollectionPlaceholder = '[data-testid="placeholder"]'; export const FleConnectionConfigurationBanner = '[data-testid="fle-connection-configuration"]'; @@ -1178,36 +1188,46 @@ export const sidebarInstanceNavigationItem = ( export const SidebarMyQueriesTab = `${Sidebar} [aria-label="My Queries"]`; export const WorkspaceTab = '[role="tablist"][aria-label="Workspace Tabs"] [role="tab"]'; -export const workspaceTab = ( - title: string | null, - active: boolean | null = null -) => { - const _active = active === null ? '' : `[aria-selected="${String(active)}"]`; - const _title = - title === null - ? '' - : ['My Queries', 'Performance', 'Databases'].includes(title) - ? `[title="${title}"]` - : `[data-namespace="${title}"]`; - return `${WorkspaceTab}${_title}${_active}`; +export const workspaceTab = ({ + id, + connectionName, + namespace, + type, + title, + active, +}: WorkspaceTabSelectorOptions = {}) => { + const parts: string[] = [WorkspaceTab]; + if (id !== undefined) { + parts.push(`[id="${id}"]`); + } + if (connectionName !== undefined) { + parts.push(`[data-connectionName="${connectionName}"]`); + } + if (namespace !== undefined) { + parts.push(`[data-namespace="${namespace}"]`); + } + if (type !== undefined) { + parts.push(`[data-type="${type}"]`); + } + if (title !== undefined) { + parts.push(`[title="${title}"]`); + } + if (active !== undefined) { + parts.push(`[aria-selected="${String(active)}"]`); + } + return parts.join(''); }; export const connectionWorkspaceTab = ( tabName: 'Performance' | 'Databases', - active: boolean | null = null + active?: boolean ) => { - return workspaceTab(tabName, active); + return workspaceTab({ title: tabName, active }); }; -export const databaseWorkspaceTab = ( - dbName: string, - active: boolean | null = null -) => { - return workspaceTab(dbName, active); +export const databaseWorkspaceTab = (dbName: string, active?: boolean) => { + return workspaceTab({ title: dbName, active }); }; -export const collectionWorkspaceTab = ( - namespace: string, - active: boolean | null = null -) => { - return workspaceTab(namespace, active); +export const collectionWorkspaceTab = (namespace: string, active: boolean) => { + return workspaceTab({ namespace, active }); }; // Export modal diff --git a/packages/compass-e2e-tests/tests/auto-connect.test.ts b/packages/compass-e2e-tests/tests/auto-connect.test.ts index a25c0bf6696..d104680b4e2 100644 --- a/packages/compass-e2e-tests/tests/auto-connect.test.ts +++ b/packages/compass-e2e-tests/tests/auto-connect.test.ts @@ -7,6 +7,7 @@ import { skipForWeb, TEST_MULTIPLE_CONNECTIONS, screenshotPathName, + connectionNameFromString, } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; import os from 'os'; @@ -93,6 +94,7 @@ describe('Automatically connecting from the command line', function () { .getText(); expect(sidebarTitle).to.eq(expectedTitle); const result = await compass.browser.shellEval( + connectionNameFromString(connectionStringSuccess), 'db.runCommand({ connectionStatus: 1 })', true ); diff --git a/packages/compass-e2e-tests/tests/connection.test.ts b/packages/compass-e2e-tests/tests/connection.test.ts index 0345c867022..59a93233cd3 100644 --- a/packages/compass-e2e-tests/tests/connection.test.ts +++ b/packages/compass-e2e-tests/tests/connection.test.ts @@ -15,6 +15,7 @@ import { TEST_COMPASS_WEB, TEST_MULTIPLE_CONNECTIONS, connectionNameFromString, + DEFAULT_CONNECTION_STRING, } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import type { ConnectFormState } from '../helpers/connect-form-state'; @@ -287,8 +288,9 @@ describe('Connection string', function () { it('can connect using connection string', async function () { await browser.connectWithConnectionString(); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionNameFromString(DEFAULT_CONNECTION_STRING), 'db.runCommand({ connectionStatus: 1 })', true ); @@ -309,8 +311,9 @@ describe('Connection string', function () { const connectionString = await resolveMongodbSrv(withSRV); await browser.connectWithConnectionString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionNameFromString(connectionString), 'db.runCommand({ connectionStatus: 1 })', true ); @@ -339,8 +342,10 @@ describe('Connection string', function () { const connectionString = parsedString.toString(); await browser.connectWithConnectionString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + if (!TEST_COMPASS_WEB) { + await browser.screenshot('direct-connection-shell.png'); const result = await browser.shellEval( + connectionNameFromString(connectionString), 'db.runCommand({ connectionStatus: 1 })', true ); @@ -358,11 +363,13 @@ describe('Connection string', function () { const password = process.env.E2E_TESTS_ATLAS_PASSWORD ?? ''; const host = process.env.E2E_TESTS_SERVERLESS_HOST ?? ''; const connectionString = `mongodb+srv://${username}:${password}@${host}`; + const connectionName = connectionNameFromString(connectionString); await browser.connectWithConnectionString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -380,11 +387,13 @@ describe('Connection string', function () { const password = process.env.E2E_TESTS_ATLAS_PASSWORD ?? ''; const host = process.env.E2E_TESTS_DATA_LAKE_HOST ?? ''; const connectionString = `mongodb://${username}:${password}@${host}/?authSource=admin&tls=true`; + const connectionName = connectionNameFromString(connectionString); await browser.connectWithConnectionString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -402,11 +411,13 @@ describe('Connection string', function () { const password = process.env.E2E_TESTS_ATLAS_PASSWORD ?? ''; const host = process.env.E2E_TESTS_ANALYTICS_NODE_HOST ?? ''; const connectionString = `mongodb+srv://${username}:${password}@${host}`; + const connectionName = connectionNameFromString(connectionString); await browser.connectWithConnectionString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -424,10 +435,12 @@ describe('Connection string', function () { const password = process.env.E2E_TESTS_ATLAS_PASSWORD ?? ''; const host = process.env.E2E_TESTS_FREE_TIER_HOST ?? ''; const connectionString = `mongodb+srv://${username}:${password}@${host}`; + const connectionName = connectionNameFromString(connectionString); await browser.connectWithConnectionString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -441,12 +454,15 @@ describe('Connection string', function () { return this.skip(); } - await browser.connectWithConnectionString( - process.env.E2E_TESTS_ATLAS_READWRITEANY_STRING ?? '' - ); + const connectionString = + process.env.E2E_TESTS_ATLAS_READWRITEANY_STRING ?? ''; + const connectionName = connectionNameFromString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + await browser.connectWithConnectionString(connectionString); + + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -454,10 +470,6 @@ describe('Connection string', function () { expect(result).to.have.property('ok', 1); } - const connectionName = connectionNameFromString( - process.env.E2E_TESTS_ATLAS_READWRITEANY_STRING ?? '' - ); - await assertCanReadData( browser, connectionName, @@ -471,12 +483,16 @@ describe('Connection string', function () { return this.skip(); } - await browser.connectWithConnectionString( - process.env.E2E_TESTS_ATLAS_READANYDATABASE_STRING ?? '' - ); + const connectionString = + process.env.E2E_TESTS_ATLAS_READANYDATABASE_STRING ?? ''; + + await browser.connectWithConnectionString(connectionString); + + const connectionName = connectionNameFromString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -484,10 +500,6 @@ describe('Connection string', function () { expect(result).to.have.property('ok', 1); } - const connectionName = connectionNameFromString( - process.env.E2E_TESTS_ATLAS_READANYDATABASE_STRING ?? '' - ); - await assertCanReadData( browser, connectionName, @@ -519,12 +531,16 @@ describe('Connection string', function () { return this.skip(); } - await browser.connectWithConnectionString( - process.env.E2E_TESTS_ATLAS_CUSTOMROLE_STRING ?? '' - ); + const connectionString = + process.env.E2E_TESTS_ATLAS_CUSTOMROLE_STRING ?? ''; - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + await browser.connectWithConnectionString(connectionString); + + const connectionName = connectionNameFromString(connectionString); + + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -532,10 +548,6 @@ describe('Connection string', function () { expect(result).to.have.property('ok', 1); } - const connectionName = connectionNameFromString( - process.env.E2E_TESTS_ATLAS_CUSTOMROLE_STRING ?? '' - ); - await assertCanReadData(browser, connectionName, 'test', 'users'); await assertCannotCreateDb( browser, @@ -556,12 +568,16 @@ describe('Connection string', function () { return this.skip(); } - await browser.connectWithConnectionString( - process.env.E2E_TESTS_ATLAS_SPECIFICPERMISSION_STRING ?? '' - ); + const connectionString = + process.env.E2E_TESTS_ATLAS_SPECIFICPERMISSION_STRING ?? ''; + + await browser.connectWithConnectionString(connectionString); - if (!TEST_COMPASS_WEB && !TEST_MULTIPLE_CONNECTIONS) { + const connectionName = connectionNameFromString(connectionString); + + if (!TEST_COMPASS_WEB) { const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -569,10 +585,6 @@ describe('Connection string', function () { expect(result).to.have.property('ok', 1); } - const connectionName = connectionNameFromString( - process.env.E2E_TESTS_ATLAS_CUSTOMROLE_STRING ?? '' - ); - await assertCanReadData(browser, connectionName, 'test', 'users'); await assertCannotInsertData(browser, connectionName, 'test', 'users'); await assertCannotCreateDb( @@ -615,20 +627,19 @@ describe('Connection form', function () { }); it('can connect using connection form', async function () { - const connectionName = this.test?.fullTitle(); + const connectionName = this.test?.fullTitle() ?? ''; await browser.connectWithConnectionForm({ hosts: ['127.0.0.1:27091'], connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); it('can connect to an Atlas cluster with username/password authentication (SCRAM-SHA-1)', async function () { @@ -636,7 +647,7 @@ describe('Connection form', function () { return this.skip(); } - const connectionName = this.test?.fullTitle(); + const connectionName = this.test?.fullTitle() ?? ''; const atlasConnectionOptions: ConnectFormState = basicAtlasOptions( process.env.E2E_TESTS_ATLAS_HOST ?? '' @@ -645,14 +656,14 @@ describe('Connection form', function () { ...atlasConnectionOptions, connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + await browser.screenshot('SCDAM-SHA1-shell.png'); + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); it('can connect to an Atlas cluster with X.509 authentication', async function () { @@ -660,7 +671,7 @@ describe('Connection form', function () { return this.skip(); } - const connectionName = this.test?.fullTitle(); + const connectionName = this.test?.fullTitle() ?? ''; let tempdir; try { @@ -679,14 +690,13 @@ describe('Connection form', function () { ...atlasConnectionOptions, connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); } finally { if (tempdir) { await fs.rmdir(tempdir, { recursive: true }); @@ -699,7 +709,7 @@ describe('Connection form', function () { return this.skip(); } - const connectionName = this.test?.fullTitle(); + const connectionName = this.test?.fullTitle() ?? ''; const atlasConnectionOptions: ConnectFormState = { hosts: [process.env.E2E_TESTS_FREE_TIER_HOST ?? ''], @@ -713,14 +723,14 @@ describe('Connection form', function () { ...atlasConnectionOptions, connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + await browser.screenshot('without-session-token-shell.png'); + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); it('can connect to an Atlas cluster with AWS IAM authentication (including session token)', async function () { @@ -745,18 +755,19 @@ describe('Connection form', function () { awsSecretAccessKey: secret, awsSessionToken: token, }; + const connectionName = this.test?.fullTitle() ?? ''; await browser.connectWithConnectionForm({ ...atlasConnectionOptions, - connectionName: this.test?.fullTitle(), + connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + await browser.screenshot('including-session-token-shell.png'); + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); it('can connect to an Atlas with tlsUseSystemCA', async function () { @@ -767,6 +778,7 @@ describe('Connection form', function () { const username = process.env.E2E_TESTS_ATLAS_USERNAME ?? ''; const password = process.env.E2E_TESTS_ATLAS_PASSWORD ?? ''; const host = process.env.E2E_TESTS_ATLAS_HOST ?? ''; + const connectionName = this.test?.fullTitle() ?? ''; await browser.connectWithConnectionForm({ scheme: 'MONGODB_SRV', @@ -776,19 +788,20 @@ describe('Connection form', function () { hosts: [host], sslConnection: 'ON', useSystemCA: true, - connectionName: this.test?.fullTitle(), + connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - // NB: The fact that we can use the shell is a regression test for COMPASS-5802. - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - await new Promise((resolve) => setTimeout(resolve, 10000)); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + await browser.screenshot('tlsUseSystemCA-shell.png'); + + // NB: The fact that we can use the shell is a regression test for COMPASS-5802. + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + await new Promise((resolve) => setTimeout(resolve, 10000)); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); it('can connect to Atlas Serverless', async function () { @@ -799,18 +812,18 @@ describe('Connection form', function () { const atlasConnectionOptions: ConnectFormState = basicAtlasOptions( process.env.E2E_TESTS_SERVERLESS_HOST ?? '' ); + const connectionName = this.test?.fullTitle() ?? ''; await browser.connectWithConnectionForm({ ...atlasConnectionOptions, - connectionName: this.test?.fullTitle(), + connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); it('can connect to Atlas Datalake', async function () { @@ -825,19 +838,19 @@ describe('Connection form', function () { atlasConnectionOptions.defaultDatabase = 'test'; atlasConnectionOptions.sslConnection = 'ON'; atlasConnectionOptions.defaultAuthSource = 'admin'; + const connectionName = this.test?.fullTitle() ?? ''; await browser.connectWithConnectionForm({ ...atlasConnectionOptions, - connectionName: this.test?.fullTitle(), + connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); it('can connect to Atlas Analytics Node', async function () { @@ -848,18 +861,18 @@ describe('Connection form', function () { const atlasConnectionOptions: ConnectFormState = basicAtlasOptions( process.env.E2E_TESTS_ANALYTICS_NODE_HOST ?? '' ); + const connectionName = this.test?.fullTitle() ?? ''; await browser.connectWithConnectionForm({ ...atlasConnectionOptions, - connectionName: this.test?.fullTitle(), + connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); it('can connect to Atlas Free Tier', async function () { @@ -870,18 +883,18 @@ describe('Connection form', function () { const atlasConnectionOptions: ConnectFormState = basicAtlasOptions( process.env.E2E_TESTS_FREE_TIER_HOST ?? '' ); + const connectionName = this.test?.fullTitle() ?? ''; await browser.connectWithConnectionForm({ ...atlasConnectionOptions, - connectionName: this.test?.fullTitle(), + connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval( - 'db.runCommand({ connectionStatus: 1 })', - true - ); - assertNotError(result); - expect(result).to.have.property('ok', 1); - } + const result = await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })', + true + ); + assertNotError(result); + expect(result).to.have.property('ok', 1); }); }); @@ -965,22 +978,20 @@ describe('System CA access', function () { }); it('allows using the system certificate store for connections', async function () { - // TODO(COMPASS-8004): this uses shellEval and that's not working in multiple connections yet - if (TEST_MULTIPLE_CONNECTIONS) { - this.skip(); - } - const compass = await init(this.test?.fullTitle()); const browser = compass.browser; + const connectionName = this.test?.fullTitle() ?? ''; + try { await browser.connectWithConnectionForm({ hosts: ['127.0.0.1:27091'], sslConnection: 'DEFAULT', useSystemCA: true, - connectionName: this.test?.fullTitle(), + connectionName, }); const result = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 })', true ); @@ -1045,6 +1056,8 @@ describe('FLE2', function () { return this.skip(); } + const connectionName = this.test?.fullTitle() ?? ''; + await browser.connectWithConnectionForm({ hosts: ['127.0.0.1:27091'], fleKeyVaultNamespace: 'alena.keyvault', @@ -1060,12 +1073,14 @@ describe('FLE2', function () { ] } }`, - connectionName: this.test?.fullTitle(), + connectionName, }); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval('db.getName()', true); - expect(result).to.be.equal('test'); - } + const result = await browser.shellEval( + connectionName, + 'db.getName()', + true + ); + expect(result).to.be.equal('test'); }); }); diff --git a/packages/compass-e2e-tests/tests/force-connection-options.test.ts b/packages/compass-e2e-tests/tests/force-connection-options.test.ts index 8867d492143..ff462782111 100644 --- a/packages/compass-e2e-tests/tests/force-connection-options.test.ts +++ b/packages/compass-e2e-tests/tests/force-connection-options.test.ts @@ -8,6 +8,7 @@ import { TEST_COMPASS_WEB, TEST_MULTIPLE_CONNECTIONS, Selectors, + connectionNameFromString, } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import { expect } from 'chai'; @@ -58,15 +59,19 @@ describe('forceConnectionOptions', function () { await browser.clickVisible(Selectors.ConnectionModalCloseButton); } - await browser.connectWithConnectionString( - 'mongodb://127.0.0.1:27091/?appName=userSpecifiedAppName' - ); + const connectionString = + 'mongodb://127.0.0.1:27091/?appName=userSpecifiedAppName'; + const connectionName = connectionNameFromString(connectionString); - if (!TEST_MULTIPLE_CONNECTIONS) { - const result = await browser.shellEval('db.getMongo()._uri', true); - expect(new ConnectionString(result).searchParams.get('appName')).to.equal( - 'testAppName' - ); - } + await browser.connectWithConnectionString(connectionString); + + const result = await browser.shellEval( + connectionName, + 'db.getMongo()._uri', + true + ); + expect(new ConnectionString(result).searchParams.get('appName')).to.equal( + 'testAppName' + ); }); }); diff --git a/packages/compass-e2e-tests/tests/global-preferences.test.ts b/packages/compass-e2e-tests/tests/global-preferences.test.ts index 141c4d0c1c3..9dbf1cf78ea 100644 --- a/packages/compass-e2e-tests/tests/global-preferences.test.ts +++ b/packages/compass-e2e-tests/tests/global-preferences.test.ts @@ -177,6 +177,10 @@ describe('Global preferences', function () { const compass = await init(this.test?.fullTitle()); const browser = compass.browser; try { + // TODO(COMPASS-8071): check that the shell is there before toggling the + // section so we can have some level of confidence that toggling the + // setting did something and that it would be detected by our assertions + // below. await browser.openSettingsModal('Privacy'); await browser.clickVisible(Selectors.GeneralSettingsButton); { @@ -201,6 +205,8 @@ describe('Global preferences', function () { 'This setting cannot be modified as it has been set in the global Compass configuration file.' ); } + // TODO(COMPASS-8071): This just passes for multiple connections because + // the shell section is never there. { const shellSection = await browser.$(Selectors.ShellSection); const isShellSectionExisting = await shellSection.isExisting(); diff --git a/packages/compass-e2e-tests/tests/in-use-encryption.test.ts b/packages/compass-e2e-tests/tests/in-use-encryption.test.ts index 312783f53dd..752a8f33915 100644 --- a/packages/compass-e2e-tests/tests/in-use-encryption.test.ts +++ b/packages/compass-e2e-tests/tests/in-use-encryption.test.ts @@ -187,6 +187,7 @@ describe('CSFLE / QE', function () { describe('when fleEncryptedFieldsMap is not specified while connecting', function () { const databaseName = 'db-for-fle'; const collectionName = 'my-encrypted-collection'; + const connectionName = 'fle'; let compass: Compass; let browser: CompassBrowser; @@ -197,8 +198,14 @@ describe('CSFLE / QE', function () { hosts: [CONNECTION_HOSTS], fleKeyVaultNamespace: `${databaseName}.keyvault`, fleKey: 'A'.repeat(128), - connectionName: this.test?.fullTitle(), + connectionName, }); + + await browser.shellEval( + connectionName, + `db.getMongo().getDB('${databaseName}').createCollection('default')` + ); + await refresh(browser); }); after(async function () { @@ -207,13 +214,6 @@ describe('CSFLE / QE', function () { } }); - beforeEach(async function () { - await browser.shellEval( - `db.getMongo().getDB('${databaseName}').createCollection('default')` - ); - await refresh(browser); - }); - afterEach(async function () { if (compass) { await screenshotIfFailed(compass, this.currentTest); @@ -282,6 +282,7 @@ describe('CSFLE / QE', function () { let compass: Compass; let browser: CompassBrowser; let plainMongo: MongoClient; + let connectionName: string; before(async function () { compass = await init(this.test?.fullTitle()); @@ -289,6 +290,8 @@ describe('CSFLE / QE', function () { }); beforeEach(async function () { + connectionName = this.test?.fullTitle() ?? ''; + await browser.connectWithConnectionForm({ hosts: [CONNECTION_HOSTS], fleKeyVaultNamespace: `${databaseName}.keyvault`, @@ -330,10 +333,11 @@ describe('CSFLE / QE', function () { ] } }`, - connectionName: this.test?.fullTitle(), + connectionName, }); - await browser.shellEval(`use ${databaseName}`); + await browser.shellEval(connectionName, `use ${databaseName}`); await browser.shellEval( + connectionName, 'db.keyvault.insertOne({' + '"_id": UUID("28bbc608-524e-4717-9246-33633361788e"),' + '"keyMaterial": BinData(0, "/yeYyj8IxowIIZGOs5iUcJaUm7KHhoBDAAzNxBz8c5mr2hwBIsBWtDiMU4nhx3fCBrrN3cqXG6jwPgR22gZDIiMZB5+xhplcE9EgNoEEBtRufBE2VjtacpXoqrMgW0+m4Dw76qWUCsF/k1KxYBJabM35KkEoD6+BI1QxU0rwRsR1rE/OLuBPKOEq6pmT5x74i+ursFlTld+5WiOySRDcZg=="),' + @@ -411,7 +415,10 @@ describe('CSFLE / QE', function () { }); it('can insert a document with an encrypted field and a non-encrypted field', async function () { - await browser.shellEval(`db.createCollection('${collectionName}')`); + await browser.shellEval( + connectionName, + `db.createCollection('${collectionName}')` + ); await refresh(browser); await browser.navigateToCollectionTab( @@ -467,8 +474,12 @@ describe('CSFLE / QE', function () { }); it('shows a decrypted field icon', async function () { - await browser.shellEval(`db.createCollection('${collectionName}')`); await browser.shellEval( + connectionName, + `db.createCollection('${collectionName}')` + ); + await browser.shellEval( + connectionName, `db[${JSON.stringify( collectionName )}].insertOne({ "phoneNumber": "30303030", "name": "Person X" })` @@ -513,8 +524,12 @@ describe('CSFLE / QE', function () { const toString = (v: any) => v?.toISOString?.()?.replace(/Z$/, '+00:00') ?? JSON.stringify(v); - await browser.shellEval(`db.createCollection('${coll}')`); await browser.shellEval( + connectionName, + `db.createCollection('${coll}')` + ); + await browser.shellEval( + connectionName, `db[${JSON.stringify( coll )}].insertOne({ "${field}": ${oldValue}, "name": "Person X" })` @@ -581,8 +596,12 @@ describe('CSFLE / QE', function () { } it('can edit and query the encrypted field in the JSON view', async function () { - await browser.shellEval(`db.createCollection('${collectionName}')`); await browser.shellEval( + connectionName, + `db.createCollection('${collectionName}')` + ); + await browser.shellEval( + connectionName, `db[${JSON.stringify( collectionName )}].insertOne({ "phoneNumber": "30303030", "name": "Person X" })` @@ -635,8 +654,12 @@ describe('CSFLE / QE', function () { }); it('can not edit the copied encrypted field', async function () { - await browser.shellEval(`db.createCollection('${collectionName}')`); await browser.shellEval( + connectionName, + `db.createCollection('${collectionName}')` + ); + await browser.shellEval( + connectionName, `db[${JSON.stringify( collectionName )}].insertOne({ "phoneNumber": "30303030", "name": "Person Z" })` @@ -716,8 +739,12 @@ describe('CSFLE / QE', function () { }); it('shows incomplete schema for cloned document banner', async function () { - await browser.shellEval(`db.createCollection('${collectionName}')`); await browser.shellEval( + connectionName, + `db.createCollection('${collectionName}')` + ); + await browser.shellEval( + connectionName, `db[${JSON.stringify( collectionName )}].insertOne({ "phoneNumber": "30303030", "name": "First" })` @@ -810,8 +837,12 @@ describe('CSFLE / QE', function () { }); it('can enable and disable in-use encryption from the sidebar', async function () { - await browser.shellEval(`db.createCollection('${collectionName}')`); await browser.shellEval( + connectionName, + `db.createCollection('${collectionName}')` + ); + await browser.shellEval( + connectionName, `db[${JSON.stringify( collectionName )}].insertOne({ "phoneNumber": "30303030", "name": "Person Z" })` @@ -882,6 +913,7 @@ describe('CSFLE / QE', function () { describe('server version gte 6.0 and lt 7.0', function () { const databaseName = 'db-for-fle'; const collectionName = 'my-encrypted-collection'; + const connectionName = 'fle'; let compass: Compass; let browser: CompassBrowser; @@ -911,25 +943,31 @@ describe('CSFLE / QE', function () { // connect without QE and insert some fixture data that we generated against a 6.x database using the shell await browser.connectWithConnectionForm({ hosts: [CONNECTION_HOSTS], - connectionName: this.test?.fullTitle(), + connectionName, }); - await browser.shellEval(`use ${databaseName}`); + await browser.shellEval(connectionName, `use ${databaseName}`); // insert the dataKey that was used to encrypt the payloads used below await browser.shellEval( + connectionName, 'dataKey = new UUID("2871cd1d-8317-4d0c-92be-1ac934ed26b1");' ); - await browser.shellEval(`db.getCollection("keyvault").insertOne({ + await browser.shellEval( + connectionName, + `db.getCollection("keyvault").insertOne({ _id: new UUID("2871cd1d-8317-4d0c-92be-1ac934ed26b1"), keyMaterial: Binary.createFromHexString("519e2b15d20f00955a3960aab31e70a8e3fdb661129ef0d8a752291599488f8fda23ca64ddcbced93dbc715d03f45ab53a8e8273f2230c41c0e64d9ef746d6959cbdc1abcf0e9d020856e2da09a91ef129ac60ef13a98abcd5ee0cbfba21f1de153974996ab002bddccf7dc0268fed90a172dc373e90b63bc2369a5a1bfc78e0c2d7d81e65e970a38ca585248fef53b70452687024b8ecd308930a25414518e3", 0), creationDate: ISODate("2023-05-05T10:58:12.473Z"), updateDate: ISODate("2023-05-05T10:58:12.473Z"), status: 0, masterKey: { provider: 'local' } - });`); + });` + ); - await browser.shellEval(`db.runCommand({ + await browser.shellEval( + connectionName, + `db.runCommand({ create: '${collectionName}', encryptedFields: { fields: [{ @@ -939,10 +977,13 @@ describe('CSFLE / QE', function () { queries: [{ queryType: 'equality' }] }] } - });`); + });` + ); // these payloads were encrypted using dataKey - await browser.shellEval(`db.runCommand({ + await browser.shellEval( + connectionName, + `db.runCommand({ insert: '${collectionName}', documents: [ { @@ -961,9 +1002,12 @@ describe('CSFLE / QE', function () { } ], bypassDocumentValidation: true - });`); + });` + ); - await browser.shellEval(`db.runCommand({ + await browser.shellEval( + connectionName, + `db.runCommand({ insert: 'enxcol_.${collectionName}.ecoc', documents: [ { @@ -978,9 +1022,12 @@ describe('CSFLE / QE', function () { } ], bypassDocumentValidation: true - });`); + });` + ); - await browser.shellEval(`db.runCommand({ + await browser.shellEval( + connectionName, + `db.runCommand({ insert: 'enxcol_.${collectionName}.esc', documents: [ { @@ -993,7 +1040,8 @@ describe('CSFLE / QE', function () { } ], bypassDocumentValidation: true - });`); + });` + ); await browser.disconnect(); diff --git a/packages/compass-e2e-tests/tests/instance-my-queries-tab.test.ts b/packages/compass-e2e-tests/tests/instance-my-queries-tab.test.ts index ba887ad8d07..3cb46e8ccc4 100644 --- a/packages/compass-e2e-tests/tests/instance-my-queries-tab.test.ts +++ b/packages/compass-e2e-tests/tests/instance-my-queries-tab.test.ts @@ -15,13 +15,6 @@ import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; import { createNumbersCollection } from '../helpers/insert-data'; -async function navigateToMyQueries(browser: CompassBrowser) { - await browser.clickVisible(Selectors.SidebarMyQueriesTab); - await browser - .$(Selectors.workspaceTab('My Queries', true)) - .waitForDisplayed(); -} - async function openMenuForQueryItem( browser: CompassBrowser, favoriteQueryName: string @@ -51,6 +44,7 @@ async function openMenuForQueryItem( describe('Instance my queries tab', function () { let compass: Compass; let browser: CompassBrowser; + const connectionName = connectionNameFromString(DEFAULT_CONNECTION_STRING); before(async function () { skipForWeb(this, 'saved queries not yet available in compass-web'); @@ -115,7 +109,7 @@ describe('Instance my queries tab', function () { connectionNameFromString(DEFAULT_CONNECTION_STRING), 'Databases' ); - await navigateToMyQueries(browser); + await browser.navigateToMyQueries(); // open the menu await openMenuForQueryItem(browser, favoriteQueryName); @@ -163,8 +157,11 @@ describe('Instance my queries tab', function () { await renameModal.waitForDisplayed({ reverse: true }); // rename the collection associated with the query to force the open item modal - await browser.shellEval('use test'); - await browser.shellEval('db.numbers.renameCollection("numbers-renamed")'); + await browser.shellEval(connectionName, 'use test'); + await browser.shellEval( + connectionName, + 'db.numbers.renameCollection("numbers-renamed")' + ); await browser.clickVisible(Selectors.Single.RefreshDatabasesButton); // browse to the query @@ -201,7 +198,7 @@ describe('Instance my queries tab', function () { connectionNameFromString(DEFAULT_CONNECTION_STRING), 'Databases' ); - await navigateToMyQueries(browser); + await browser.navigateToMyQueries(); // open the menu await openMenuForQueryItem(browser, newFavoriteQueryName); @@ -264,7 +261,7 @@ describe('Instance my queries tab', function () { await createButton.click(); await browser.closeWorkspaceTabs(); - await navigateToMyQueries(browser); + await browser.navigateToMyQueries(); await browser.clickVisible(Selectors.myQueriesItem(savedAggregationName)); const namespace = await browser.getActiveTabNamespace(); @@ -310,7 +307,7 @@ describe('Instance my queries tab', function () { connectionNameFromString(DEFAULT_CONNECTION_STRING), 'Databases' ); - await navigateToMyQueries(browser); + await browser.navigateToMyQueries(); // open the menu await openMenuForQueryItem(browser, favoriteQueryName); @@ -337,8 +334,9 @@ describe('Instance my queries tab', function () { } // rename the collection associated with the query to force the open item modal - await browser.shellEval('use test'); + await browser.shellEval(connectionName, 'use test'); await browser.shellEval( + connectionName, `db.numbers.renameCollection('${newCollectionName}')` ); await browser.clickVisible(Selectors.Single.RefreshDatabasesButton); @@ -346,7 +344,7 @@ describe('Instance my queries tab', function () { beforeEach(setup); it('users can permanently associate a new namespace for an aggregation/query', async function () { - await navigateToMyQueries(browser); + await browser.navigateToMyQueries(); // browse to the query await browser.clickVisible(Selectors.myQueriesItem(favoriteQueryName)); @@ -374,7 +372,7 @@ describe('Instance my queries tab', function () { await confirmOpenButton.click(); await openModal.waitForDisplayed({ reverse: true }); - await navigateToMyQueries(browser); + await browser.navigateToMyQueries(); const [databaseNameElement, collectionNameElement] = [ await browser.$('span=test'), diff --git a/packages/compass-e2e-tests/tests/logging.test.ts b/packages/compass-e2e-tests/tests/logging.test.ts index fd12a19f1e6..6e1bb2cfc51 100644 --- a/packages/compass-e2e-tests/tests/logging.test.ts +++ b/packages/compass-e2e-tests/tests/logging.test.ts @@ -5,6 +5,8 @@ import { screenshotIfFailed, skipForWeb, TEST_MULTIPLE_CONNECTIONS, + connectionNameFromString, + DEFAULT_CONNECTION_STRING, } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import { startTelemetryServer } from '../helpers/telemetry'; @@ -13,16 +15,12 @@ import type { Telemetry, LogEntry } from '../helpers/telemetry'; describe('Logging and Telemetry integration', function () { before(function () { skipForWeb(this, 'telemetry not yet available in compass-web'); - - // TODO(COMPASS-8004): skipping for multiple connections due to the use of shellEval for now - if (TEST_MULTIPLE_CONNECTIONS) { - this.skip(); - } }); describe('after running an example path through Compass', function () { let logs: LogEntry[]; let telemetry: Telemetry; + const connectionName = connectionNameFromString(DEFAULT_CONNECTION_STRING); before(async function () { telemetry = await startTelemetryServer(); @@ -31,8 +29,17 @@ describe('Logging and Telemetry integration', function () { try { await browser.connectWithConnectionString(); - await browser.shellEval('use test'); - await browser.shellEval('db.runCommand({ connectionStatus: 1 })'); + + if (TEST_MULTIPLE_CONNECTIONS) { + // make sure we generate the screen event that the tests expect + await browser.navigateToMyQueries(); + } + + await browser.shellEval(connectionName, 'use test'); + await browser.shellEval( + connectionName, + 'db.runCommand({ connectionStatus: 1 })' + ); } finally { await cleanup(compass); await telemetry.stop(); diff --git a/packages/compass-e2e-tests/tests/oidc.test.ts b/packages/compass-e2e-tests/tests/oidc.test.ts index e9191abe9ab..c71809906a0 100644 --- a/packages/compass-e2e-tests/tests/oidc.test.ts +++ b/packages/compass-e2e-tests/tests/oidc.test.ts @@ -7,7 +7,7 @@ import { serverSatisfies, skipForWeb, TEST_COMPASS_WEB, - TEST_MULTIPLE_CONNECTIONS, + connectionNameFromString, } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; import type { Compass } from '../helpers/compass'; @@ -60,7 +60,6 @@ function getTestBrowserShellCommand() { describe('OIDC integration', function () { let compass: Compass; let browser: CompassBrowser; - let getTokenPayload: typeof oidcMockProviderConfig.getTokenPayload; let overrideRequestHandler: typeof oidcMockProviderConfig.overrideRequestHandler; let oidcMockProviderConfig: OIDCMockProviderConfig; @@ -71,6 +70,7 @@ describe('OIDC integration', function () { let tmpdir: string; let cluster: MongoCluster; let connectionString: string; + let connectionName: string; let getFavoriteConnectionInfo: ( favoriteName: string ) => Promise | undefined>; @@ -78,11 +78,6 @@ describe('OIDC integration', function () { before(async function () { skipForWeb(this, 'feature flags not yet available in compass-web'); - // TODO(COMPASS-8004): skipping for multiple connections due to the use of shellEval for now - if (TEST_MULTIPLE_CONNECTIONS) { - this.skip(); - } - // OIDC is only supported on Linux in the 7.0+ enterprise server. if ( process.platform !== 'linux' || @@ -151,6 +146,7 @@ describe('OIDC integration', function () { cs.searchParams.set('authMechanism', 'MONGODB-OIDC'); connectionString = cs.toString(); + connectionName = connectionNameFromString(connectionString); } { @@ -203,6 +199,7 @@ describe('OIDC integration', function () { }; await browser.connectWithConnectionString(connectionString); const result: any = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 }).authInfo', true ); @@ -243,6 +240,7 @@ describe('OIDC integration', function () { await browser.connectWithConnectionString(connectionString); emitter.emit('secondConnectionEstablished'); const result: any = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 }).authInfo', true ); @@ -264,6 +262,7 @@ describe('OIDC integration', function () { }); const result: any = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 }).authInfo', true ); @@ -297,6 +296,7 @@ describe('OIDC integration', function () { afterReauth = true; await browser.clickVisible(`${modal} ${confirmButton}`); const result: any = await browser.shellEval( + connectionName, 'db.runCommand({ connectionStatus: 1 }).authInfo', true ); diff --git a/packages/compass-e2e-tests/tests/shell.test.ts b/packages/compass-e2e-tests/tests/shell.test.ts index 5561017d485..9ae8fae67f5 100644 --- a/packages/compass-e2e-tests/tests/shell.test.ts +++ b/packages/compass-e2e-tests/tests/shell.test.ts @@ -7,6 +7,8 @@ import { screenshotIfFailed, skipForWeb, TEST_COMPASS_WEB, + connectionNameFromString, + DEFAULT_CONNECTION_STRING, TEST_MULTIPLE_CONNECTIONS, } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; @@ -20,11 +22,6 @@ describe('Shell', function () { before(async function () { skipForWeb(this, 'shell not available on compass-web'); - // TODO(COMPASS-8004): best to just port this once the shell works with multiple connections - if (TEST_MULTIPLE_CONNECTIONS) { - this.skip(); - } - telemetry = await startTelemetryServer(); compass = await init(this.test?.fullTitle()); browser = compass.browser; @@ -35,10 +32,6 @@ describe('Shell', function () { return; } - if (TEST_MULTIPLE_CONNECTIONS) { - return; - } - await cleanup(compass); await telemetry.stop(); }); @@ -50,8 +43,9 @@ describe('Shell', function () { it('has an info modal', async function () { await browser.connectWithConnectionString(); + const connectionName = connectionNameFromString(DEFAULT_CONNECTION_STRING); - await browser.showShell(); + await browser.openShell(connectionName); await browser.clickVisible(Selectors.ShellInfoButton); const infoModalElement = await browser.$(Selectors.ShellInfoModal); @@ -60,10 +54,17 @@ describe('Shell', function () { await browser.clickVisible(Selectors.ShellInfoModalCloseButton); await infoModalElement.waitForDisplayed({ reverse: true }); - await browser.hideShell(); + await browser.closeShell(connectionName); }); it('shows and hides shell based on settings', async function () { + // TODO(COMPASS-8071): Leaving this skipped until we decide what we're going + // to do. hide the buttons & menu items, disable them or keep them enabled + // and open a shell tab that just has an error banner. + if (TEST_MULTIPLE_CONNECTIONS) { + this.skip(); + } + await browser.connectWithConnectionString(); // Will fail if shell is not on the screen eventually diff --git a/packages/compass-e2e-tests/tests/tabs.test.ts b/packages/compass-e2e-tests/tests/tabs.test.ts index e418261d91c..83a5019aa79 100644 --- a/packages/compass-e2e-tests/tests/tabs.test.ts +++ b/packages/compass-e2e-tests/tests/tabs.test.ts @@ -42,7 +42,7 @@ describe('Global Tabs', function () { false ); } - expect(await browser.$$(Selectors.workspaceTab(null))).to.have.lengthOf(1); + expect(await browser.$$(Selectors.workspaceTab())).to.have.lengthOf(1); }); it('should open tabs over each other when not modified', async function () { @@ -58,7 +58,7 @@ describe('Global Tabs', function () { Selectors.queryBarApplyFilterButton('Documents') ); } - expect(await browser.$$(Selectors.workspaceTab(null))).to.have.lengthOf(3); + expect(await browser.$$(Selectors.workspaceTab())).to.have.lengthOf(3); }); it('should close tabs without warning even when "modified" by interacting with the tab', async function () { @@ -75,7 +75,7 @@ describe('Global Tabs', function () { ); } await browser.closeWorkspaceTabs(false); - expect(await browser.$$(Selectors.workspaceTab(null))).to.have.lengthOf(0); + expect(await browser.$$(Selectors.workspaceTab())).to.have.lengthOf(0); }); it('should ask for confirmation when closing modified Aggregations tab', async function () { @@ -101,7 +101,7 @@ describe('Global Tabs', function () { .waitForExist({ reverse: true }); // Checking first that cancel leaves the tab on the screen - expect(await browser.$$(Selectors.workspaceTab(null))).to.have.lengthOf(1); + expect(await browser.$$(Selectors.workspaceTab())).to.have.lengthOf(1); await browser.clickVisible(Selectors.CloseWorkspaceTab); await browser.$(Selectors.ConfirmTabCloseModal).waitForDisplayed(); @@ -114,6 +114,6 @@ describe('Global Tabs', function () { .waitForExist({ reverse: true }); // When confirmed, should remove the tab - expect(await browser.$$(Selectors.workspaceTab(null))).to.have.lengthOf(0); + expect(await browser.$$(Selectors.workspaceTab())).to.have.lengthOf(0); }); }); diff --git a/packages/compass-workspaces/src/components/workspaces.tsx b/packages/compass-workspaces/src/components/workspaces.tsx index c8d7ba259d6..696fb6d748a 100644 --- a/packages/compass-workspaces/src/components/workspaces.tsx +++ b/packages/compass-workspaces/src/components/workspaces.tsx @@ -161,18 +161,22 @@ const CompassWorkspaces: React.FunctionComponent = ({ case 'Welcome': return { id: tab.id, + type: tab.type, title: tab.type, iconGlyph: 'Logo', } as const; case 'My Queries': return { id: tab.id, + type: tab.type, title: tab.type, iconGlyph: 'CurlyBraces', } as const; case 'Shell': return { id: tab.id, + connectionName: getConnectionTitleById(tab.connectionId), + type: tab.type, title: getConnectionTitleById(tab.connectionId) ?? 'MongoDB Shell', iconGlyph: 'Shell', tabTheme: getThemeOf(tab.connectionId), @@ -180,6 +184,8 @@ const CompassWorkspaces: React.FunctionComponent = ({ case 'Databases': return { id: tab.id, + connectionName: getConnectionTitleById(tab.connectionId), + type: tab.type, title: tab.type, iconGlyph: 'Database', tabTheme: getThemeOf(tab.connectionId), @@ -187,6 +193,8 @@ const CompassWorkspaces: React.FunctionComponent = ({ case 'Performance': return { id: tab.id, + connectionName: getConnectionTitleById(tab.connectionId), + type: tab.type, title: tab.type, iconGlyph: 'Gauge', tabTheme: getThemeOf(tab.connectionId), @@ -194,6 +202,8 @@ const CompassWorkspaces: React.FunctionComponent = ({ case 'Collections': return { id: tab.id, + connectionName: getConnectionTitleById(tab.connectionId), + type: tab.type, title: tab.namespace, iconGlyph: 'Database', 'data-namespace': tab.namespace, @@ -218,6 +228,8 @@ const CompassWorkspaces: React.FunctionComponent = ({ : `${database} > ${collection}`; return { id: tab.id, + connectionName: getConnectionTitleById(tab.connectionId), + type: tab.type, title: collection, subtitle, iconGlyph: