Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
0ee2a23
chore(web): add a simple static assets server to compass-web scripts
gribnoysup Jan 21, 2026
31edc7d
chore(e2e): allow multiple test filters to be provided
gribnoysup Jan 21, 2026
2c4775e
chore(e2e): add web extension builder to be used for redirects
gribnoysup Jan 21, 2026
6ceab06
chore(web): allow to build compass-web with some internals exposed
gribnoysup Jan 21, 2026
a2cf177
chore(e2e): rename atlas-cloud-sandbox args to atlas-cloud; remove at…
gribnoysup Jan 21, 2026
7481f97
chore(e2e): don't print args twice in debug log
gribnoysup Jan 21, 2026
9aff781
chore(e2e): change compass-web with atlas tests to run against remote…
gribnoysup Jan 21, 2026
2101b76
Merge remote-tracking branch 'origin/main' into compass-web-with-atla…
gribnoysup Jan 21, 2026
612b57b
chore(e2e): do not use :not selector for hover
gribnoysup Jan 22, 2026
3641221
chore(e2e): use a new element to avoid trying to find a cached one fr…
gribnoysup Jan 22, 2026
a5e8c43
chore(e2e): move atlas sign in and leafygreen helpers to their own br…
gribnoysup Jan 22, 2026
e8883c4
Merge branch 'main' into compass-web-with-atlas-e2e-refactor
gribnoysup Jan 22, 2026
109b2c6
chore(web): use proper response code for unsupported methods; sanitiz…
gribnoysup Jan 23, 2026
003830e
chore(web): remove "sanitize" code as bot is not happy with it anyway
gribnoysup Jan 23, 2026
b0ba327
Merge branch 'main' into compass-web-with-atlas-e2e-refactor
gribnoysup Jan 23, 2026
fd9c6ac
Merge remote-tracking branch 'origin/main' into compass-web-with-atla…
gribnoysup Jan 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .evergreen/functions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -775,8 +775,8 @@ functions:
<<: *compass-env
<<: *compass-e2e-secrets
DEBUG: ${debug|}
COMPASS_E2E_ATLAS_CLOUD_SANDBOX_USERNAME: ${e2e_tests_compass_web_atlas_username}
COMPASS_E2E_ATLAS_CLOUD_SANDBOX_PASSWORD: ${e2e_tests_compass_web_atlas_password}
COMPASS_E2E_ATLAS_CLOUD_USERNAME: ${e2e_tests_compass_web_atlas_username}
COMPASS_E2E_ATLAS_CLOUD_PASSWORD: ${e2e_tests_compass_web_atlas_password}
MCLI_PUBLIC_API_KEY: ${e2e_tests_mcli_public_api_key}
MCLI_PRIVATE_API_KEY: ${e2e_tests_mcli_private_api_key}
MCLI_ORG_ID: ${e2e_tests_mcli_org_id}
Expand All @@ -797,7 +797,7 @@ functions:
# clusters in CI is both pricey and flakey, so we want to limit the
# coverage to reduce those factors (at least for now)
npm run --unsafe-perm --workspace compass-e2e-tests test-ci -- -- web \
--test-atlas-cloud-sandbox \
--test-atlas-cloud \
--test-filter="atlas-cloud/**/*"

test-connectivity:
Expand Down
16 changes: 8 additions & 8 deletions .evergreen/start-atlas-cloud-cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ DOCKER_REGISTRY="${DOCKER_REGISTRY:-docker.io}"
# MCLI_ORG_ID Org ID
# MCLI_PROJECT_ID Project ID
#
# COMPASS_E2E_ATLAS_CLOUD_SANDBOX_USERNAME Cloud user you created
# COMPASS_E2E_ATLAS_CLOUD_SANDBOX_PASSWORD Cloud user password
# COMPASS_E2E_ATLAS_CLOUD_USERNAME Cloud user you created
# COMPASS_E2E_ATLAS_CLOUD_PASSWORD Cloud user password
#
# - Source the script followed by running the tests to make sure that some
# variables exported from this script are available for the test env:
#
# (ATLAS_CLOUD_TEST_CLUSTER_NAME="TestCluster" source .evergreen/start-atlas-cloud-cluster.sh \
# && npm run -w compass-e2e-tests test web -- --test-atlas-cloud-sandbox --test-filter="atlas-cloud/**/*")
# && npm run -w compass-e2e-tests test web -- --test-atlas-cloud --test-filter="atlas-cloud/**/*")

_ATLAS_CLOUD_TEST_CLUSTER_NAME=${ATLAS_CLOUD_TEST_CLUSTER_NAME:-""}

Expand Down Expand Up @@ -101,8 +101,8 @@ atlascli dbusers create atlasAdmin \
--password "$ATLAS_TEST_DB_PASSWORD" \
--deleteAfter "$DELETE_AFTER" # so that it's autoremoved if cleaning up failed for some reason

export COMPASS_E2E_ATLAS_CLOUD_SANDBOX_DBUSER_USERNAME="$ATLAS_TEST_DB_USERNAME"
export COMPASS_E2E_ATLAS_CLOUD_SANDBOX_DBUSER_PASSWORD="$ATLAS_TEST_DB_PASSWORD"
export COMPASS_E2E_ATLAS_CLOUD_DBUSER_USERNAME="$ATLAS_TEST_DB_USERNAME"
export COMPASS_E2E_ATLAS_CLOUD_DBUSER_PASSWORD="$ATLAS_TEST_DB_PASSWORD"

echo "Creating Atlas deployment \`$ATLAS_CLUSTER_NAME\` to test against..."
(atlascli clusters create $ATLAS_CLUSTER_NAME \
Expand All @@ -117,7 +117,7 @@ atlascli clusters watch $ATLAS_CLUSTER_NAME
echo "Getting connection string for provisioned cluster..."
CONNECTION_STRINGS_JSON="$(atlascli clusters connectionStrings describe $ATLAS_CLUSTER_NAME -o json)"

export COMPASS_E2E_ATLAS_CLOUD_SANDBOX_CLOUD_CONFIG=$(
export COMPASS_E2E_ATLAS_CLOUD_ENVIRONMENT=$(
if [[ "$MCLI_OPS_MANAGER_URL" =~ "-dev" ]]; then
echo "dev"
elif [[ "$MCLI_OPS_MANAGER_URL" =~ "-qa" ]]; then
Expand All @@ -126,7 +126,7 @@ export COMPASS_E2E_ATLAS_CLOUD_SANDBOX_CLOUD_CONFIG=$(
echo "prod"
fi
)
echo "Cloud config: $COMPASS_E2E_ATLAS_CLOUD_SANDBOX_CLOUD_CONFIG"
echo "Cloud environment: $COMPASS_E2E_ATLAS_CLOUD_ENVIRONMENT"

export COMPASS_E2E_ATLAS_CLOUD_SANDBOX_DEFAULT_CONNECTIONS="{\"$ATLAS_CLUSTER_NAME\": $CONNECTION_STRINGS_JSON}"
export COMPASS_E2E_ATLAS_CLOUD_DEFAULT_CONNECTIONS="{\"$ATLAS_CLUSTER_NAME\": $CONNECTION_STRINGS_JSON}"
echo "Cluster connections: $COMPASS_E2E_ATLAS_CLOUD_SANDBOX_DEFAULT_CONNECTIONS"
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions packages/compass-e2e-tests/helpers/commands/connect-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import type { ConnectFormState } from '../connect-form-state';
import Debug from 'debug';
import {
DEFAULT_CONNECTIONS,
isTestingAtlasCloudExternal,
isTestingAtlasCloudSandbox,
isTestingAtlasCloud,
} from '../test-runner-context';
import { getConnectionTitle } from '@mongodb-js/connection-info';
const debug = Debug('compass-e2e-tests');
Expand Down Expand Up @@ -925,7 +924,7 @@ let screenshotCounter = 0;
export async function setupDefaultConnections(browser: CompassBrowser) {
// When running tests against Atlas Cloud, connections can't be added or
// removed from the UI manually, so we skip setup for default connections
if (isTestingAtlasCloudExternal() || isTestingAtlasCloudSandbox()) {
if (isTestingAtlasCloud()) {
return;
}

Expand Down
5 changes: 2 additions & 3 deletions packages/compass-e2e-tests/helpers/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import * as Selectors from '../selectors';
import Debug from 'debug';
import {
DEFAULT_CONNECTION_NAMES,
isTestingAtlasCloudExternal,
isTestingAtlasCloudSandbox,
isTestingAtlasCloud,
} from '../test-runner-context';

const debug = Debug('compass-e2e-tests');
Expand Down Expand Up @@ -52,7 +51,7 @@ export async function connectWithConnectionString(
// When testing Atlas Cloud, we can't really create a new connection, so just
// assume a connection name was passed (with a fallback to a default one) and
// try to use it
if (isTestingAtlasCloudExternal() || isTestingAtlasCloudSandbox()) {
if (isTestingAtlasCloud()) {
await browser.connectByName(
connectionStringOrName ?? DEFAULT_CONNECTION_NAME_1
);
Expand Down
232 changes: 81 additions & 151 deletions packages/compass-e2e-tests/helpers/compass-web-sandbox.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import assert from 'node:assert/strict';

import crossSpawn from 'cross-spawn';
import { remote } from 'webdriverio';
import { execFile } from 'child_process';
import { promisify } from 'util';
import Debug from 'debug';
import {
COMPASS_WEB_SANDBOX_RUNNER_PATH,
COMPASS_WEB_WDIO_USER_DATA_PATH,
MONOREPO_ELECTRON_CHROMIUM_VERSION,
ELECTRON_PATH,
} from './test-runner-paths';
import type { ConnectionInfo } from '@mongodb-js/connection-info';
import ConnectionString from 'mongodb-connection-string-url';

Expand All @@ -21,18 +14,40 @@ const debug = Debug('compass-e2e-tests:compass-web-sandbox');
process.env.OPEN_BROWSER = 'false'; // tell webpack dev server not to open the default browser
process.env.DISABLE_DEVSERVER_OVERLAY = 'true';
process.env.APP_ENV = 'webdriverio';
process.env.COMPASS_WEB_EXPOSE_INTERNALS = 'true';

const wait = (ms: number) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};

export function spawnCompassWebSandbox() {
const waitUntil = async (
fn: () => boolean | Promise<boolean>,
signal: AbortSignal,
timeoutMs = 120_000,
timeoutMessage = `Timed out while waiting for`,
intervalMs = 2000
): Promise<void> => {
let done = false;
const start = Date.now();
while (!done) {
if (signal.aborted) {
return;
}
if (Date.now() - start >= timeoutMs) {
throw new Error(timeoutMessage);
}
await wait(intervalMs);
done = await fn();
}
};

export function spawnCompassWebSandbox(signal: AbortSignal) {
const proc = crossSpawn.spawn(
'npm',
['run', '--unsafe-perm', 'start', '--workspace', '@mongodb-js/compass-web'],
{ env: process.env }
{ env: process.env, signal }
);
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
Expand All @@ -43,152 +58,67 @@ export async function waitForCompassWebSandboxToBeReady(
sandboxUrl: string,
signal: AbortSignal
) {
let serverReady = false;
const start = Date.now();
while (!serverReady) {
if (signal.aborted) {
return;
}
if (Date.now() - start >= 120_000) {
throw new Error(
'The compass-web sandbox is still not running after 120000ms'
);
}
// No point in trying to fetch sandbox URL right away, give the spawn script
// some time to run
await wait(2000);
try {
const res = await fetch(sandboxUrl);
serverReady = res.ok;
debug('Web server ready:', serverReady);
} catch (err) {
debug('Failed to connect to dev server:', (err as any).message);
}
}
}

export async function spawnCompassWebSandboxAndSignInToAtlas(
{
username,
password,
sandboxUrl,
waitforTimeout,
}: {
username: string;
password: string;
sandboxUrl: string;
waitforTimeout: number;
},
signal: AbortSignal
) {
debug('Starting electron-proxy using webdriver ...');

const electronProxyRemote = await remote({
capabilities: {
browserName: 'chromium',
browserVersion: MONOREPO_ELECTRON_CHROMIUM_VERSION,
'goog:chromeOptions': {
binary: ELECTRON_PATH,
args: [
`--user-data-dir=${COMPASS_WEB_WDIO_USER_DATA_PATH}`,
`--app=${COMPASS_WEB_SANDBOX_RUNNER_PATH}`,
],
},
'wdio:enforceWebDriverClassic': true,
},
waitforTimeout,
});

if (signal.aborted) {
return electronProxyRemote;
}

debug('Signing in to Atlas as %s ...', username);

const authenticatePromise = fetch(`${sandboxUrl}/authenticate`, {
method: 'POST',
});

const authWindowHandler = await electronProxyRemote.waitUntil(async () => {
const handlers = await electronProxyRemote.getWindowHandles();
// First window is about:blank, second one is the one we triggered above
// with `/authenticate` request
return handlers[1];
});
await electronProxyRemote.switchToWindow(authWindowHandler);

await electronProxyRemote.$('input[name="username"]').waitForEnabled();
await electronProxyRemote.$('input[name="username"]').setValue(username);

await electronProxyRemote.$('button=Next').waitForEnabled();
await electronProxyRemote.$('button=Next').click();

await electronProxyRemote.$('input[name="password"]').waitForEnabled();
await electronProxyRemote.$('input[name="password"]').setValue(password);

await electronProxyRemote.$('button=Login').waitForEnabled();
await electronProxyRemote.$('button=Login').click();

if (signal.aborted) {
return electronProxyRemote;
}

debug('Waiting for the auth to finish ...');

let authenticatedPromiseSettled = false;

// Atlas Cloud will periodically remind user to enable MFA (which we can't
// enable in e2e CI environment), so to account for that, in parallel to
// waiting for auth to finish, we'll wait for the MFA screen to show up and
// skip it if it appears
const [, settledRes] = await Promise.allSettled([
(async () => {
const remindMeLaterButton = 'button*=Remind me later';

await electronProxyRemote.waitUntil(
async () => {
return (
authenticatedPromiseSettled ||
(await electronProxyRemote.$(remindMeLaterButton).isDisplayed())
);
},
// Takes awhile for the redirect to land on this reminder page when it
// happens, so no need to bombard the browser with displayed checks
{ interval: 2000 }
);

if (authenticatedPromiseSettled) {
return;
await waitUntil(
async () => {
try {
const res = await fetch(sandboxUrl);
debug('Web server ready:', res.ok);
return res.ok;
} catch (err) {
debug('Failed to connect to dev server:', (err as any).message);
return false;
}

await electronProxyRemote.$(remindMeLaterButton).click();
})(),
authenticatePromise.finally(() => {
authenticatedPromiseSettled = true;
}),
]);

if (settledRes.status === 'rejected') {
throw settledRes.reason;
}

const res = settledRes.value;
assert(
res.ok,
`Failed to authenticate in Atlas Cloud: ${res.statusText} (${res.status})`
},
signal,
120_000,
'The compass-web sandbox is still not running after 2 mins'
);
}

const body = await res.json();
assert(
typeof body === 'object' && body !== null && 'projectId' in body,
'Expected a project id'
export function buildCompassWebPackage(signal: AbortSignal) {
return promisify(execFile)(
'npm',
['run', 'compile', '--workspace', '@mongodb-js/compass-web'],
{ env: process.env, signal }
);
}

if (signal.aborted) {
return electronProxyRemote;
}
export function spawnCompassWebStaticServer(signal: AbortSignal) {
const proc = crossSpawn.spawn(
'npm',
[
'run',
'--unsafe-perm',
'serve-dist',
'--workspace',
'@mongodb-js/compass-web',
],
{ env: process.env, signal }
);
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
return proc;
}

return electronProxyRemote;
export async function waitForCompassWebStaticAssetsToBeReady(
assetUrl: string,
signal: AbortSignal
) {
await waitUntil(
async () => {
try {
const res = await fetch(assetUrl, {
method: 'HEAD',
});
return res.ok;
} catch {
return false;
}
},
signal,
120_000,
'Compass-web assets are still not ready after 2 mins'
);
}

export const getAtlasCloudSandboxDefaultConnections = (
Expand Down
Loading
Loading