Skip to content

Commit

Permalink
chore(e2e): add E2E test for multiple connection errors COMPASS-8153 (#…
Browse files Browse the repository at this point in the history
…6117)

* remove commented code

* fix connectionStatus: either

* test multiple connection errors
  • Loading branch information
lerouxb authored Aug 15, 2024
1 parent 5e1a374 commit b163935
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 51 deletions.
7 changes: 5 additions & 2 deletions packages/compass-e2e-tests/helpers/commands/click-visible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { CompassBrowser } from '../compass-browser';
import type { ChainablePromiseElement } from 'webdriverio';

interface ClickOptions {
timeout?: number;
scroll?: boolean;
screenshot?: string;
}
Expand All @@ -11,16 +12,18 @@ export async function clickVisible(
selector: string | ChainablePromiseElement<WebdriverIO.Element>,
options?: ClickOptions
): Promise<void> {
const waitOptions = { timeout: options?.timeout };

function getElement() {
return typeof selector === 'string' ? browser.$(selector) : selector;
}

const displayElement = getElement();

await displayElement.waitForDisplayed();
await displayElement.waitForDisplayed(waitOptions);

// Clicking a thing that's still animating is unreliable at best.
await browser.waitForAnimations(selector);
await browser.waitForAnimations(selector, waitOptions);

if (options?.scroll) {
const scrollElement = getElement();
Expand Down
43 changes: 30 additions & 13 deletions packages/compass-e2e-tests/helpers/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,38 @@ export async function waitForConnectionResult(
browser: CompassBrowser,
connectionName: string,
{ connectionStatus = 'success', timeout }: ConnectionResultOptions = {}
) {
): Promise<string | undefined> {
const waitOptions = typeof timeout !== 'undefined' ? { timeout } : undefined;

if (TEST_MULTIPLE_CONNECTIONS) {
if (await browser.$(Selectors.SidebarFilterInput).isDisplayed()) {
// Clear the filter to make sure every connection shows
await browser.clickVisible(Selectors.SidebarFilterInput);
await browser.setValueVisible(Selectors.SidebarFilterInput, '');
}
}

if (connectionStatus === 'either') {
// TODO(COMPASS-7600,COMPASS-8153): this doesn't support compass-web or
// multiple connections yet, but also isn't encountered yet For the rare
// cases where we don't care whether it fails or succeeds
await browser
.$(`${Selectors.DatabasesTable},${Selectors.ConnectionFormErrorMessage}`)
.waitForDisplayed();
// For the very rare cases where we don't care whether it fails or succeeds.
// Usually because the exact result is a race condition.
if (TEST_MULTIPLE_CONNECTIONS) {
const successSelector = Selectors.Multiple.connectionItemByName(
connectionName,
{
connected: true,
}
);
const failureSelector = Selectors.ConnectionToastErrorText;
await browser
.$(`${successSelector},${failureSelector}`)
.waitForDisplayed(waitOptions);
} else {
// TODO(COMPASS-7600): this doesn't support compass-web yet, but also
// isn't encountered yet
await browser
.$(`${Selectors.MyQueriesList},${Selectors.ConnectionFormErrorMessage}`)
.waitForDisplayed();
}
} else if (connectionStatus === 'success') {
// Wait for the first meaningful thing on the screen after being connected
// and assume that's a good enough indicator that we are connected to the
Expand All @@ -151,13 +173,8 @@ export async function waitForConnectionResult(
// In compass-web, for now, we land on the Databases tab after connecting
await browser
.$('[data-testid="workspace-tab-button"][data-type=Databases]')
.waitForDisplayed();
.waitForDisplayed({ timeout });
} else if (TEST_MULTIPLE_CONNECTIONS) {
if (await browser.$(Selectors.SidebarFilterInput).isDisplayed()) {
// Clear the filter to make sure every connection shows
await browser.clickVisible(Selectors.SidebarFilterInput);
await browser.setValueVisible(Selectors.SidebarFilterInput, '');
}
await browser
.$(
Selectors.Multiple.connectionItemByName(connectionName, {
Expand Down
48 changes: 37 additions & 11 deletions packages/compass-e2e-tests/helpers/commands/hide-visible-toasts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,55 @@ import Debug from 'debug';

const debug = Debug('compass-e2e-tests');

async function isToastContainerVisible(
browser: CompassBrowser
): Promise<boolean> {
const toastContainer = await browser.$(Selectors.LGToastContainer);
return await toastContainer.isDisplayed();
}

export async function hideAllVisibleToasts(
browser: CompassBrowser
): Promise<void> {
const toastContainer = await browser.$(Selectors.LGToastContainer);
const isToastContainerVisible = await toastContainer.isDisplayed();
if (!isToastContainerVisible) {
// If there's some race condition where something else is closing the toast at
// the same time we're trying to close the toast, then make it error out
// quickly so it can be ignored and we move on.
const waitOptions = { timeout: 2_000 };

// LG toasts are stacked in scroll container and we need to close them all.
if (!(await isToastContainerVisible(browser))) {
return;
}
// LG toasts are stacked in scroll container and we need to close them all.
const toasts = await toastContainer.$$('div');
for (const toast of toasts) {

const toasts = await browser.$(Selectors.LGToastContainer).$$('div');
for (const _toast of toasts) {
// if they all went away at some point, just stop
if (!(await isToastContainerVisible(browser))) {
return;
}

const toastTestId = await _toast.getAttribute('data-testid');
const toastSelector = `[data-testid=${toastTestId}]`;

try {
await browser.hover(Selectors.LGToastContainer);
const isToastVisible = await toast.isDisplayed();
const isToastVisible = await browser.$(toastSelector).isDisplayed();
if (!isToastVisible) {
continue;
}
debug('closing toast', await toast.getAttribute('data-testid'));
await browser.clickVisible(toast.$(Selectors.LGToastCloseButton));
await toast.waitForExist({ reverse: true });
debug('closing toast', toastTestId);
await browser.clickVisible(
`${toastSelector} ${Selectors.LGToastCloseButton}`,
waitOptions
);
debug('waiting for toast to go away', toastTestId);
await browser
.$(toastSelector)
.waitForExist({ ...waitOptions, reverse: true });
} catch (err) {
// if the toast disappears by itself in the meantime, that's fine
continue;
debug('ignoring', err);
}
debug('done closing', toastTestId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { CompassBrowser } from '../compass-browser';

export async function waitForAnimations(
browser: CompassBrowser,
selector: string | ChainablePromiseElement<WebdriverIO.Element>
selector: string | ChainablePromiseElement<WebdriverIO.Element>,
options?: { timeout?: number }
): Promise<void> {
function getElement() {
return typeof selector === 'string' ? browser.$(selector) : selector;
Expand All @@ -31,7 +32,7 @@ export async function waitForAnimations(
const stopped = _.isEqual(result, previousResult);
previousResult = result;
return stopped;
});
}, options);
} catch (err: any) {
if (err.name !== 'stale element reference') {
throw err;
Expand Down
3 changes: 3 additions & 0 deletions packages/compass-e2e-tests/helpers/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ export const ConnectionFormConnectionColor =
'[data-testid="personalization-color-input"]';
export const ConnectionFormFavoriteCheckbox =
'[data-testid="personalization-favorite-checkbox"]';
export const connectionToastById = (connectionId: string) => {
return `[data-testid="toast-connection-status--${connectionId}"]`;
};
export const ConnectionToastErrorText = '[data-testid="connection-error-text"]';
export const ConnectionToastErrorReviewButton =
'[data-testid="connection-error-review"]';
Expand Down
128 changes: 116 additions & 12 deletions packages/compass-e2e-tests/tests/connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ async function assertCannotCreateCollection(
await createModalElement.waitForDisplayed({ reverse: true });
}

function assertNotError(result: any) {
if (typeof result === 'string' && result.includes('MongoNetworkError')) {
expect.fail(result);
}
}

/**
* Connection tests
*/
Expand Down Expand Up @@ -322,10 +328,13 @@ describe('Connection string', function () {
it('fails for authentication errors', async function () {
skipForWeb(this, 'connect happens on the outside');

// connect
await browser.connectWithConnectionString(
`mongodb://a:[email protected]:${MONGODB_TEST_SERVER_PORT}/test`,
{ connectionStatus: 'failure' }
);

// check the error
if (TEST_MULTIPLE_CONNECTIONS) {
const toastTitle = await browser.$(Selectors.LGToastTitle).getText();
expect(toastTitle).to.equal('Authentication failed.');
Expand Down Expand Up @@ -949,6 +958,113 @@ describe('Connection form', function () {
assertNotError(result);
expect(result).to.have.property('ok', 1);
});

it('fails for multiple authentication errors', async function () {
if (!TEST_MULTIPLE_CONNECTIONS) {
this.skip();
}

const connection1Name = 'error-1';
const connection2Name = 'error-2';

const connections: {
state: ConnectFormState;
connectionId?: string;
connectionError: string;
toastErrorText: string;
}[] = [
{
state: {
hosts: ['127.0.0.1:27091'],
defaultUsername: 'a',
defaultPassword: 'b',
connectionName: connection1Name,
},
connectionError: 'Authentication failed.',
toastErrorText: `There was a problem connecting to ${connection1Name}`,
},
{
state: {
hosts: ['127.0.0.1:16666'],
connectionName: connection2Name,
},
connectionError: 'connect ECONNREFUSED 127.0.0.1:16666',
toastErrorText: `There was a problem connecting to ${connection2Name}`,
},
];

// connect to two connections that will both fail
// This actually connects back to back rather than truly simultaneously, but
// we can only really check that both toasts display and work anyway.
for (const connection of connections) {
await browser.connectWithConnectionForm(connection.state, {
connectionStatus: 'failure',
});
}

// pull the connection ids out of the sidebar
for (const connection of connections) {
if (!connection.state.connectionName) {
throw new Error('expected connectionName');
}
connection.connectionId = await browser.getConnectionIdByName(
connection.state.connectionName
);
}

// the last connection to be connected's toast appears on top, so we have to
// deal with the toasts in reverse
const expectedConnections = connections.slice().reverse();

for (const expected of expectedConnections) {
if (!expected.connectionId) {
throw new Error('expected connectionId');
}

// the toast should appear
const toastSelector = Selectors.connectionToastById(
expected.connectionId
);
await browser.$(toastSelector).waitForDisplayed();

// check the toast title
const toastTitle = await browser
.$(`${toastSelector} ${Selectors.LGToastTitle}`)
.getText();
expect(toastTitle).to.equal(expected.connectionError);

// check the toast body text
const errorMessage = await browser
.$(`${toastSelector} ${Selectors.ConnectionToastErrorText}`)
.getText();
expect(errorMessage).to.equal(expected.toastErrorText);

// click the review button in the toast
await browser.clickVisible(
`${toastSelector} ${Selectors.ConnectionToastErrorReviewButton}`
);

// the toast should go away because the action was clicked
await browser.$(toastSelector).waitForDisplayed({ reverse: true });

// make sure the connection form is populated with this connection
await browser.$(Selectors.ConnectionModal).waitForDisplayed();
const errorText = await browser
.$(Selectors.ConnectionFormErrorMessage)
.getText();
expect(errorText).to.equal(expected.connectionError);

const state = await browser.getConnectFormState();
expect(state.hosts).to.deep.equal(expected.state.hosts);
expect(state.connectionName).to.equal(expected.state.connectionName);

// close the modal
await browser.clickVisible(Selectors.ConnectionModalCloseButton);
await browser
.$(Selectors.ConnectionModal)
.waitForDisplayed({ reverse: true });
}
});
});

// eslint-disable-next-line mocha/max-top-level-suites
Expand All @@ -961,12 +1077,6 @@ describe('SRV connectivity', function () {
});

it('resolves SRV connection string using OS DNS APIs', async function () {
if (TEST_MULTIPLE_CONNECTIONS) {
// TODO(COMPASS-8153): we have to add support in custom commands for when
// connections fail
this.skip();
}

const compass = await init(this.test?.fullTitle());
const browser = compass.browser;

Expand Down Expand Up @@ -1141,9 +1251,3 @@ describe('FLE2', function () {
expect(result).to.be.equal('test');
});
});

function assertNotError(result: any) {
if (typeof result === 'string' && result.includes('MongoNetworkError')) {
expect.fail(result);
}
}
11 changes: 0 additions & 11 deletions packages/compass-e2e-tests/tests/my-queries-tab.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,6 @@ describe('My Queries tab', function () {

let client_1: MongoClient;
let client_2: MongoClient;
/*
const connectionStrings = [DEFAULT_CONNECTION_STRING_1];
if (TEST_MULTIPLE_CONNECTIONS) {
connectionStrings.push(DEFAULT_CONNECTION_STRING_2);
}
clients = connectionStrings.map(
(connectionString) => new MongoClient(connectionString)
);
await Promise.all(clients.map((client) => client.connect()));
*/

before(async function () {
skipForWeb(this, 'saved queries not yet available in compass-web');
Expand Down

0 comments on commit b163935

Please sign in to comment.