diff --git a/package-lock.json b/package-lock.json index 25745d7defd..9b9925b9b9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109567,7 +109567,7 @@ "@mongodb-js/prettier-config-compass": "^0.5.0", "@mongodb-js/tsconfig-compass": "^0.6.0", "@mongodb-js/webpack-config-compass": "^0.6.0", - "@types/lodash.isempty": "*", + "@types/lodash.isempty": "^4.4.6", "ace-builds": "^1.4.3", "acorn-loose": "^8.0.2", "astring": "^1.7.0", diff --git a/packages/compass-e2e-tests/fixtures/ca.pem b/packages/compass-e2e-tests/fixtures/ca.pem new file mode 100644 index 00000000000..a1102228465 --- /dev/null +++ b/packages/compass-e2e-tests/fixtures/ca.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDvzCCAqegAwIBAgIUWHODQPXBJh8z9c+OvakB5EWSGykwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCQVUxDDAKBgNVBAgMA05TVzEVMBMGA1UECgwMT3JnYW5p +c2F0aW9uMQwwCgYDVQQLDANDQXMxDTALBgNVBAMMBHJvb3QxHjAcBgkqhkiG9w0B +CQEWD3VzZXJAZG9tYWluLmNvbTAeFw0yMjAxMTExOTAyMDhaFw0zMjAxMDkxOTAy +MDhaMG8xCzAJBgNVBAYTAkFVMQwwCgYDVQQIDANOU1cxFTATBgNVBAoMDE9yZ2Fu +aXNhdGlvbjEMMAoGA1UECwwDQ0FzMQ0wCwYDVQQDDARyb290MR4wHAYJKoZIhvcN +AQkBFg91c2VyQGRvbWFpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC71ot540Z/uAZDXxUUacY8nScIFOeTeUAqVi4Dct5k7+PYe1HKOCtV+Uzk +98Wjys9GIrl7IQnk9cyLfqPQzc9/fc0zGYtMeQZY3DcrGfUOEoCxNIawBIkJHrL9 +2pynlyvjFV5Ve3/inwZZuuX1HbQJMm4kVrWijjxaKJjtTYxmYDQtNdbi0aa00QCm +No4dk1/z2R2xiaMBledJvUwmVObfgshrNFhCCHb0d4X5buDNIj1J7gkxkxNMq0sH +mCgsnYqsdFeRPdf63+lQ+SEraeOieV0j2sPiZUjm7bGPotjQfGGXm6hiLM6Ut0N5 +G+4StcmQjs+OAw0PwQNnp2cMHTVpAgMBAAGjUzBRMB0GA1UdDgQWBBThU9SZ3NLh +NrXDgNOaI+HOLPPhojAfBgNVHSMEGDAWgBThU9SZ3NLhNrXDgNOaI+HOLPPhojAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC3PXkIl1sTtgU1HcMZ +RTC8rRgeSdGX2w4fQqO98s4ZC4RPNj8hf2PGTJnkyhg+sySX2IXaNJnHKaSfMRU3 +1UCDdubNHXTf+JIlcmrGYa/MYgcXHfjyZWhrUcNq64NFXglaG7EAf0IPZf3zlmiH +5y6PU0A1VDqfufKAdX2/fVFhOvI1oy8sk0nBLXcTEncPkxKRxul0/0C+gqXb6T/b +1vZybGyPBZzDCrm5bkNfeNSvbjOiJvfAEBcqsVObma858sYxJ4ijUdbJ9a7KivtY +V5noXQzx2gtUM9vY/NFBRk10YmSocQVi8+5zxrfMKkbFYhRJvp9mXOfXm9ohYrNh +JFIx +-----END CERTIFICATE----- diff --git a/packages/compass-e2e-tests/fixtures/client.pem b/packages/compass-e2e-tests/fixtures/client.pem new file mode 100644 index 00000000000..7cb110d77be --- /dev/null +++ b/packages/compass-e2e-tests/fixtures/client.pem @@ -0,0 +1,47 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAwWV9IejXo0WIzq1I9EFUsvt/3xulyC1kyKzendGFPM/sedyT +qYpuu5/jvjpZT8AiGxhlcHkdIHgZGkhsw4TgZa1F7yIbvwXzbXp61n+2Vdrbau9/ +etQWh4JdHPNBtI76oRlpcZFVNOmxLG6KGoR+VZD5Ep12aGDblW9u6tO4svHju+eO +yU17K9z6dAhlutBYxWTzjH5CWXPci+ETnjKdzLDixuX6qXqWm/iFN//6m3ZNYVgZ +bikxUpfr9lixOZ6kbdumhkqEMB/g7Ff1kA94Gh2GZrhEcFRijQCTynVnxK8bMRPr +7INYYt+Cq/n5XUtZNK6MgOdo7+GNpdokKXdxiQIDAQABAoIBAEFrbEXBNtiDGAEZ +uvjs9JLK4nl9J7osKNB9MZzfGBv3Fb4vMEAElOqg7nAV2spQavkRapb+USz/kzZB +05Db7PBYCbUXq50VjT+2U8ElWG94ZgqpU00gWzKhlY/Kdap3Ry6hovqIt/L7LjSi +e9L2iKm3LZnva/No+D53gGStE+hrtaEd2uJqWj+uo3RaEdI1mpkfUEPvon6mA5g8 +lEPLPBwKX/U9qyVbvTS71nWd2SJ657ke7EAT4afMzBUbr5zNXPqK8y1l5aHINViu +SSIhf+TtRWiq+sVPpivAfYi+2hdNRbZj9pF/291SBukH5vX+1i8SFS0QTmjemp6/ +e8IxYYECgYEA8RoQ9ShthSoTKf3YUtdqrXVP2ScBgZ/sf8DqvBkUfewmMuyHYNP5 +fcNiddUMlfkywzr00TfGk80rCBdmR1B0z4qRN3HUdqbJkupZbgzy2gkgCRTIALSZ +WpQH4MNXPs8yWz6kY2y/1kOxeqXmIf22de7/yK8vvTiAuMtRlhghqzkCgYEAzVjI +PxGN3Gq/DOEOKARrJR7j86mnf03RYVENOTnm6pHofRIjMf9GA0IM6Abgs0f45Kv2 +sfdDotNDS/xTq9qUOQHS1or8RVyOofjaTnZAfufm1zgi9xx7ymVTuehQ1T+JyXRi +nKICVbsERbjiMrGDudQnktr/B6I2E42o0yZH6NECgYB6uwsg0PiXTei9enOxD+lE +7S+9WcbBhngsPDcBkz7ELv7u80qitqUNKPWpB8/FVDpL+WoASoUyXcFm+ApfiQw9 +TctITxCZaaO4vsTRaZQB+50sIkEBYu2hlzM/bhCub2ix2/xwhD5PQtxIk8THTtCf +zg60yMDjcKzN5OneHuCcuQKBgEXkK/PbY69b3b7ictEH//fYdbmfStU3hUZcMeYC +YSgBlWHMZJJF0myVdJMclCoxGvOp3ANip3Cp+0PHlCrv8HceucYv9AEfqaRTcOo3 +sWgAQj+Kacw1s6SFrGOgQLL0N0+L/2xUb/sB9khMzyB17uG25elEBH1ypnktRALq +CpuhAoGAYSJhJ+mKXG2xVC4mvwddFbBIKytynm2viaH1s4sOX/lNk3x9/L4+A6Wm +WibJ3UNyAJ7MmwAGgFvGHHZGW6kiGRh20bk6dQQyzRWY6TQLQELeGUkYS8qClPSq +cH0eu97Q1vnagDk/FEDnE5mSrxWIakXo1xXnYvGJlahgPZD/718= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDWTCCAkECAQMwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCQVUxDDAKBgNV +BAgMA05TVzEVMBMGA1UECgwMT3JnYW5pc2F0aW9uMQwwCgYDVQQLDANDQXMxDTAL +BgNVBAMMBHJvb3QxHjAcBgkqhkiG9w0BCQEWD3VzZXJAZG9tYWluLmNvbTAeFw0y +MjAxMTExOTAyMDhaFw0zMjAxMDkxOTAyMDhaMHYxCzAJBgNVBAYTAkFVMQwwCgYD +VQQIDANOU1cxFTATBgNVBAoMDE9yZ2FuaXNhdGlvbjEQMA4GA1UECwwHY2xpZW50 +czEQMA4GA1UEAwwHY2xpZW50MTEeMBwGCSqGSIb3DQEJARYPdXNlckBkb21haW4u +Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwWV9IejXo0WIzq1I +9EFUsvt/3xulyC1kyKzendGFPM/sedyTqYpuu5/jvjpZT8AiGxhlcHkdIHgZGkhs +w4TgZa1F7yIbvwXzbXp61n+2Vdrbau9/etQWh4JdHPNBtI76oRlpcZFVNOmxLG6K +GoR+VZD5Ep12aGDblW9u6tO4svHju+eOyU17K9z6dAhlutBYxWTzjH5CWXPci+ET +njKdzLDixuX6qXqWm/iFN//6m3ZNYVgZbikxUpfr9lixOZ6kbdumhkqEMB/g7Ff1 +kA94Gh2GZrhEcFRijQCTynVnxK8bMRPr7INYYt+Cq/n5XUtZNK6MgOdo7+GNpdok +KXdxiQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQATnp2hYq7dYzcIlVSWji35JNHh +VJcteLNi7OVHHYADZk46a00/pKQCf0ACTQeK2xNNkpH9RNH8q1c0L4e2JuugCe/o +PEJQUmk2b39KqjUsGTkMt9GHoPUGOxP1aknUoUYTvOXzX4XcQAJgtpK7x7qiG0yR +rds48FIXSr9TQnEN/Ag65m+VrISKZ/IsHT/GpBqT+GlwS+2CXak4Omj870mIiny/ +UzoEOHywzZ7hdQj9HQiatLFFIA52N+5H+Z5beCFSzdfEUn37TI8m04jZnApJxhRU +P7ghytVx0w4RdFAy4ttU+VUPT2tsPN3o4bpFSd/IRYGgB7bh9V2WxjCw7oTF +-----END CERTIFICATE----- diff --git a/packages/compass-e2e-tests/helpers/commands/click-parent.ts b/packages/compass-e2e-tests/helpers/commands/click-parent.ts new file mode 100644 index 00000000000..ca94a1e1a39 --- /dev/null +++ b/packages/compass-e2e-tests/helpers/commands/click-parent.ts @@ -0,0 +1,10 @@ +import type { CompassBrowser } from '../compass-browser'; + +export async function clickParent( + browser: CompassBrowser, + selector: string +): Promise { + const element = await browser.$(selector).parentElement(); + await element.waitForExist(); + await element.click(); +} diff --git a/packages/compass-e2e-tests/helpers/commands/index.ts b/packages/compass-e2e-tests/helpers/commands/index.ts index 9f1d897b4c9..2daceaa912d 100644 --- a/packages/compass-e2e-tests/helpers/commands/index.ts +++ b/packages/compass-e2e-tests/helpers/commands/index.ts @@ -32,3 +32,4 @@ export * from './get-query-id'; export * from './run-find'; export * from './export-to-language'; export * from './get-active-tab-namespace'; +export * from './click-parent'; diff --git a/packages/compass-e2e-tests/helpers/commands/set-value-visible.ts b/packages/compass-e2e-tests/helpers/commands/set-value-visible.ts index c542fac5fbe..4a7a3846c98 100644 --- a/packages/compass-e2e-tests/helpers/commands/set-value-visible.ts +++ b/packages/compass-e2e-tests/helpers/commands/set-value-visible.ts @@ -5,7 +5,18 @@ export async function setValueVisible( selector: string, value: string ): Promise { - const element = await browser.$(selector); - await element.waitForDisplayed(); - await element.setValue(value); + await browser.waitUntil( + async () => { + const element = await browser.$(selector); + await element.waitForDisplayed(); + await element.clearValue(); + await element.setValue(value); + const actualValue = await element.getValue(); + if (actualValue !== value) { + console.log(actualValue, '!==', value); + } + return actualValue === value; + }, + { timeout: 60000 } + ); } diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index b8d95e887da..9c5da70f071 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -32,10 +32,143 @@ export const ConnectionFormInputUsername = '[data-testid="connection-username-input"]'; export const ConnectionFormInputPassword = '[data-testid="connection-password-input"]'; - export const ConnectionFormErrorMessage = '[data-testid="connection-error-summary"]'; +export const AdvancedOptionsTabs = '[aria-label="Advanced Options Tabs"]'; +export const SelectedAdvancedOptionsTab = `${AdvancedOptionsTabs} [aria-selected="true"]`; + +export const ConnectionFormSchemeRadios = + '#connection-schema-radio-box-group input[type="radio"]'; +export const ConnectionFormHostInputs = + '[aria-labelledby="connection-host-input-label"]'; +export const ConnectionFormDirectConnectionCheckbox = + '#direct-connection-checkbox'; +export const ConnectionFormAuthenticationMethodRadios = + '#authentication-method-radio-box-group input[type="radio"]'; +export const ConnectionFormInputAuthSource = '#authSourceInput'; +export const ConnectionFormAuthMechanismRadios = + '#authentication-mechanism-radio-box-group input[type="radio"]'; +export const ConnectionFormInputGssApiPrincipal = + '[data-testid="gssapi-principal-input"]'; +export const ConnectionFormInputGssApiServiceName = + '[data-testid="gssapi-service-name-input"]'; +export const ConnectionFormCanonicalizeHostNameRadios = + '#canonicalize-hostname-select input[type="radio"]'; +export const ConnectionFormInputGssApiServiceRealm = + '[data-testid="gssapi-service-realm-input"]'; +export const ConnectionFormGssApiPasswordCheckbox = + '[data-testid="gssapi-password-checkbox"]'; +export const ConnectionFormInputGssApiPassword = + '[data-testid="gssapi-password-input"]'; +export const ConnectionFormInputPlainUsername = + '[data-testid="connection-plain-username-input"]'; +export const ConnectionFormInputPlainPassword = + '[data-testid="connection-plain-password-input"]'; +export const ConnectionFormInputAWSAccessKeyId = + '[data-testid="connection-form-aws-access-key-id-input"]'; +export const ConnectionFormInputAWSSecretAccessKey = + '[data-testid="connection-form-aws-secret-access-key-input"]'; +export const ConnectionFormInputAWSSessionToken = + '[data-testid="connection-form-aws-secret-token-input"]'; +export const ConnectionFormSSLConnectionRadios = + '#connection-schema-radio-box-group input[type="radio"]'; +export const ConnectionFormTlsCaButton = '#tlsCAFile'; +export const ConnectionFormTlsCertificateKeyButton = '#tlsCertificateKeyFile'; +export const ConnectionFormTlsCaFile = '[data-testid="tlsCAFile-input"]'; +export const ConnectionFormTlsCertificateKeyFile = + '[data-testid="tlsCertificateKeyFile-input"]'; +export const ConnectionFormInputTlsCertificateKeyFilePassword = + '[data-testid="tlsCertificateKeyFilePassword-input"]'; +export const ConnectionFormTlsInsecureCheckbox = + '[data-testid="tlsInsecure-input"]'; +export const ConnectionFormTlsAllowInvalidHostnamesCheckbox = + '[data-testid="tlsAllowInvalidHostnames-input"]'; +export const ConnectionFormTlsAllowInvalidCertificatesCheckbox = + '[data-testid="tlsAllowInvalidCertificates-input"]'; +export const ConnectionFormProxyMethodRadios = + '#ssh-options-radio-box-group input[type="radio"]'; +export const ConnectionFormInputSshPasswordHost = + '[data-testid="ssh-password-tab-content"] [data-testid="host"]'; +export const ConnectionFormInputSshPasswordPort = + '[data-testid="ssh-password-tab-content"] [data-testid="port"]'; +export const ConnectionFormInputSshPasswordUsername = + '[data-testid="ssh-password-tab-content"] [data-testid="username"]'; +export const ConnectionFormInputSshPasswordPassword = + '[data-testid="ssh-password-tab-content"] [data-testid="password"]'; +export const ConnectionFormInputSshIdentityHost = + '[data-testid="ssh-identity-tab-content"] [data-testid="host"]'; +export const ConnectionFormInputSshIdentityPort = + '[data-testid="ssh-identity-tab-content"] [data-testid="port"]'; +export const ConnectionFormInputSshIdentityUsername = + '[data-testid="ssh-identity-tab-content"] [data-testid="username"]'; +export const ConnectionFormSshIdentityKeyButton = + '[data-testid="ssh-identity-tab-content"] #identityKeyFile'; +export const ConnectionFormSshIdentityKeyFile = + '[data-testid="ssh-identity-tab-content"] [data-testid="identityKeyFile"]'; +export const ConnectionFormInputSshIdentityPassword = + '[data-testid="ssh-identity-tab-content"] [data-testid="identityKeyPassphrase"]'; +export const ConnectionFormInputSocksHost = + '[data-testid="socks-tab-content"] [data-testid="proxyHost"]'; +export const ConnectionFormInputSocksPort = + '[data-testid="socks-tab-content"] [data-testid="proxyPort"]'; +export const ConnectionFormInputSocksUsername = + '[data-testid="socks-tab-content"] [data-testid="proxyUsername"]'; +export const ConnectionFormInputSocksPassword = + '[data-testid="socks-tab-content"] [data-testid="proxyPassword"]'; +export const ConnectionFormReadPreferenceRadios = + '#read-preferences input[type="radio"]'; +export const ConnectionFormInputReplicaset = + '[data-testid="connection-advanced-tab"] [data-testid="replica-set"]'; +export const ConnectionFormInputDefaultDatabase = + '[data-testid="connection-advanced-tab"] [data-testid="default-database"]'; +export const ConnectionFormUrlOptionKeys = + '[data-testid="connection-advanced-tab"] button[name="name"]'; +export const ConnectionFormUrlOptionValues = + '[data-testid="connection-advanced-tab"] input[aria-labelledby="Enter value"]'; + +export const advancedOptionsTab = (tabName: string): string => { + return `${AdvancedOptionsTabs} button[name="${tabName}"]`; +}; +export const advancedOptionsTabPanel = (tabName: string): string => { + return `[role="tabpanel"][aria-label="${tabName}"]`; +}; +export const connectionFormSchemeRadio = (value: string): string => { + return `#connection-schema-radio-box-group input[value="${value}"]`; +}; +export const connectionFormAuthenticationMethodRadio = ( + value: string +): string => { + return `#authentication-method-radio-box-group input[value="${value}"]`; +}; +export const connectionFormAuthMechanismRadio = (value: string): string => { + return `#authentication-mechanism-radio-box-group input[value="${value}"]`; +}; +export const connectionFormCanonicalizeHostNameRadio = ( + value: string +): string => { + return `#canonicalize-hostname-select input[value="${value}"]`; +}; +export const connectionFormSSLConnectionRadio = (value: string): string => { + return `#connection-schema-radio-box-group input[value="${value}"]`; +}; +export const connectionFormProxyMethodRadio = (value: string): string => { + return `#ssh-options-radio-box-group input[value="${value}"]`; +}; +export const connectionFormReadPreferenceRadio = (value: string): string => { + return `#read-preferences input[value="${value}"]`; +}; +export const connectionFormUrlOptionKeyButton = (index: number): string => { + return `[data-testid="url-options-table"] tr:nth-child(${ + index + 1 + }) button[name="name"]`; +}; +export const connectionFormUrlOptionValueInput = (index: number): string => { + return `[data-testid="url-options-table"] tr:nth-child(${ + index + 1 + }) input[aria-labelledby="Enter value"]`; +}; + // Connection Sidebar export const SidebarTreeItems = '[data-test-id="databases-and-collections"] [role="treeitem"]'; diff --git a/packages/compass-e2e-tests/tests/connection-form.test.ts b/packages/compass-e2e-tests/tests/connection-form.test.ts new file mode 100644 index 00000000000..7c3611a9a80 --- /dev/null +++ b/packages/compass-e2e-tests/tests/connection-form.test.ts @@ -0,0 +1,1151 @@ +import path from 'path'; +import { expect } from 'chai'; +import type { CompassBrowser } from '../helpers/compass-browser'; +import { beforeTests, afterTests, afterTest } from '../helpers/compass'; +import type { Compass } from '../helpers/compass'; +import * as Selectors from '../helpers/selectors'; + +describe('Connection form', function () { + let compass: Compass; + let browser: CompassBrowser; + + before(async function () { + compass = await beforeTests(); + browser = compass.browser; + }); + + after(async function () { + await afterTests(compass, this.currentTest); + }); + + beforeEach(async function () { + await resetForm(browser); + }); + + afterEach(async function () { + await afterTest(compass, this.currentTest); + }); + + it('starts with the expected initial state', async function () { + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: 'mongodb://localhost:27017', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }); + }); + + it('parses and formats a URI for direct connection', async function () { + const connectionString = 'mongodb://localhost:27017/?directConnection=true'; + + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: true, + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI for multiple hosts', async function () { + const connectionString = 'mongodb://localhost:27017,localhost:27018/'; + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017', 'localhost:27018'], + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI for mongodb+srv scheme', async function () { + const connectionString = 'mongodb+srv://localhost/'; + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB_SRV', + hosts: ['localhost'], + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI for username/password authentication', async function () { + const connectionString = + 'mongodb://foo:bar@localhost:27017/?authMechanism=SCRAM-SHA-1&authSource=source'; + + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'DEFAULT', + defaultUsername: 'foo', + defaultPassword: 'bar', + defaultAuthSource: 'source', + defaultAuthMechanism: 'SCRAM-SHA-1', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI for X.509 authentication', async function () { + const fixturesPath = path.resolve(__dirname, '..', 'fixtures'); + const tlsCAFile = path.join(fixturesPath, 'ca.pem'); + const tlsCertificateKeyFile = path.join(fixturesPath, 'client.pem'); + const connectionString = `mongodb://localhost:27017/?authMechanism=MONGODB-X509&authSource=%24external&tls=true&tlsCAFile=${encodeURIComponent( + tlsCAFile + )}&tlsCertificateKeyFile=${encodeURIComponent( + tlsCertificateKeyFile + )}&tlsCertificateKeyFilePassword=password&tlsInsecure=true&tlsAllowInvalidHostnames=true&tlsAllowInvalidCertificates=true`; + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'MONGODB-X509', + proxyMethod: 'none', + sslConnection: 'ON', + tlsCAFile: 'ca.pem', + tlsCertificateKeyFile: 'client.pem', + clientKeyPassword: 'password', + tlsInsecure: true, + tlsAllowInvalidHostnames: true, + tlsAllowInvalidCertificates: true, + readPreference: 'defaultReadPreference', + }; + + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + expectedState.tlsCAFile = tlsCAFile; + expectedState.tlsCertificateKeyFile = tlsCertificateKeyFile; + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI for Kerberos authentication', async function () { + const connectionString = + 'mongodb://principal:password@localhost:27017/?authMechanism=GSSAPI&authSource=%24external&authMechanismProperties=SERVICE_NAME%3Aservice+name%2CCANONICALIZE_HOST_NAME%3Aforward%2CSERVICE_REALM%3Aservice+realm'; + + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'GSSAPI', + kerberosPassword: 'password', + kerberosPrincipal: 'principal', + kerberosProvidePassword: true, + kerberosServiceName: 'service name', + kerberosCanonicalizeHostname: 'forward', + kerberosServiceRealm: 'service realm', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI for LDAP authentication', async function () { + const connectionString = + 'mongodb://username:password@localhost:27017/?authMechanism=PLAIN&authSource=%24external'; + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'PLAIN', + ldapUsername: 'username', + ldapPassword: 'password', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI for AWS IAM authentication', async function () { + const connectionString = + 'mongodb://id:key@localhost:27017/?authMechanism=MONGODB-AWS&authSource=%24external&authMechanismProperties=AWS_SESSION_TOKEN%3Atoken'; + + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'MONGODB-AWS', + awsAccessKeyId: 'id', + awsSecretAccessKey: 'key', + awsSessionToken: 'token', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI for Socks5 authentication', async function () { + const connectionString = + 'mongodb://localhost:27017/?proxyHost=hostname&proxyPort=1234&proxyUsername=username&proxyPassword=password'; + + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'AUTH_NONE', + proxyMethod: 'socks', + socksHost: 'hostname', + socksPort: '1234', + socksUsername: 'username', + socksPassword: 'password', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('parses and formats a URI with advanced options', async function () { + const connectionString = + 'mongodb://localhost:27017/default-db?readPreference=primary&replicaSet=replica-set&connectTimeoutMS=1234&maxPoolSize=100'; + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'primary', + replicaSet: 'replica-set', + defaultDatabase: 'default-db', + urlOptions: { + connectTimeoutMS: '1234', + maxPoolSize: '100', + }, + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$(Selectors.ConnectionStringInput).getValue() + ).to.equal(connectionString); + }); + + it('does not update the URI for SSH tunnel with password authentication', async function () { + const state = { + proxyMethod: 'password', + sshPasswordHost: 'host', + sshPasswordPort: '1234', + sshPasswordUsername: 'username', + sshPasswordPassword: 'password', + }; + await setFormState(browser, state); + expect(await getFormState(browser)).to.deep.equal({ + authMethod: 'AUTH_NONE', + connectionString: 'mongodb://localhost:27017/', + directConnection: false, + hosts: ['localhost:27017'], + proxyMethod: 'password', + scheme: 'MONGODB', + sshPasswordHost: 'host', + sshPasswordPassword: 'password', + sshPasswordPort: '1234', + sshPasswordUsername: 'username', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }); + }); + + it('does not update the URI for SSH tunnel with identity file authentication', async function () { + const fixturesPath = path.resolve(__dirname, '..', 'fixtures'); + // reuse the .pem file from above. contents doesn't matter. + const sshIdentityKeyFile = path.join(fixturesPath, 'client.pem'); + + const state = { + proxyMethod: 'identity', + sshIdentityHost: 'host', + sshIdentityPort: '1234', + sshIdentityUsername: 'username', + sshIdentityKeyFile: sshIdentityKeyFile, + sshIdentityPassword: 'password', + }; + await setFormState(browser, state); + expect(await getFormState(browser)).to.deep.equal({ + authMethod: 'AUTH_NONE', + connectionString: 'mongodb://localhost:27017/', + directConnection: false, + hosts: ['localhost:27017'], + proxyMethod: 'identity', + sshIdentityHost: 'host', + sshIdentityKeyFile: 'client.pem', + sshIdentityPassword: 'password', + sshIdentityPort: '1234', + sshIdentityUsername: 'username', + scheme: 'MONGODB', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'defaultReadPreference', + }); + }); +}); + +async function getCheckedRadioValue( + browser: CompassBrowser, + selector: string +): Promise { + const elements = await browser.$$(selector); + for (const element of elements) { + if (await element.isSelected()) { + return element.getValue(); + } + } + + return null; +} + +async function getCheckboxValue( + browser: CompassBrowser, + selector: string +): Promise { + const element = await browser.$(selector); + if (!(await element.isExisting())) { + return null; // as opposed to true for checked and false for not + } + + return element.isSelected(); +} + +async function getText( + browser: CompassBrowser, + selector: string +): Promise { + const element = await browser.$(selector); + if (!(await element.isExisting())) { + return null; + } + + const text = await element.getText(); + return text || null; +} + +async function getFilename( + browser: CompassBrowser, + selector: string +): Promise { + const text = await getText(browser, selector); + return text === 'Select a file...' ? null : text; +} + +async function getValue( + browser: CompassBrowser, + selector: string +): Promise { + const element = await browser.$(selector); + if (!(await element.isExisting())) { + return null; + } + + const value = await element.getValue(); + return value || null; +} + +async function getMultipleValues( + browser: CompassBrowser, + selector: string +): Promise { + const elements = await browser.$$(selector); + const results = ( + await Promise.all( + elements.map((element) => { + return element.getValue(); + }) + ) + ).filter((result) => result !== ''); + + return results.length ? results : null; +} + +async function maybeExpandAdvancedOptions( + browser: CompassBrowser +): Promise { + const advancedButton = await browser.$(Selectors.ShowConnectionFormButton); + await advancedButton.waitForDisplayed(); + + if ((await advancedButton.getAttribute('aria-expanded')) === 'false') { + await advancedButton.click(); + await browser.waitUntil(async () => { + return (await advancedButton.getAttribute('aria-expanded')) === 'true'; + }); + return false; // it was collapsed and had to expand + } + + return true; // it was expanded already +} + +interface NamedPromises { + [key: string]: Promise; +} + +async function promiseMap(map: NamedPromises) { + const results = await Promise.all(Object.values(map)); + return Object.fromEntries( + Object.keys(map) + .map((k, i) => [k, results[i]]) + .filter(([, v]) => v !== null) + ); +} + +async function browseToTab( + browser: CompassBrowser, + tabName: string +): Promise { + // get the active tab + // if it is not the target tab, click the target tab and wait for it to become visible + // return the initially active tab so we can return to it if we want to + + const initialTab = await browser + .$(Selectors.SelectedAdvancedOptionsTab) + .getAttribute('name'); + if (initialTab !== tabName) { + await browser.clickVisible(Selectors.advancedOptionsTab(tabName)); + await browser + .$(Selectors.advancedOptionsTabPanel(initialTab)) + .waitForDisplayed({ reverse: true }); + } + + await browser + .$(Selectors.advancedOptionsTabPanel(tabName)) + .waitForDisplayed(); + + return initialTab; +} + +async function getFormState(browser: CompassBrowser) { + const wasExpanded = await maybeExpandAdvancedOptions(browser); + + const connectionString = await browser + .$(Selectors.ConnectionStringInput) + .getValue(); + + // General + const initialTab = await browseToTab(browser, 'General'); + + const defaultState = await promiseMap({ + scheme: getCheckedRadioValue(browser, Selectors.ConnectionFormSchemeRadios), + hosts: getMultipleValues(browser, Selectors.ConnectionFormHostInputs), + directConnection: getCheckboxValue( + browser, + Selectors.ConnectionFormDirectConnectionCheckbox + ), + }); + + // Authentication + await browseToTab(browser, 'Authentication'); + const authenticationState = await promiseMap({ + authMethod: getCheckedRadioValue( + browser, + Selectors.ConnectionFormAuthenticationMethodRadios + ), + + // Username/Password + defaultUsername: getValue(browser, Selectors.ConnectionFormInputUsername), + defaultPassword: getValue(browser, Selectors.ConnectionFormInputPassword), + defaultAuthSource: getValue( + browser, + Selectors.ConnectionFormInputAuthSource + ), + defaultAuthMechanism: getCheckedRadioValue( + browser, + Selectors.ConnectionFormAuthMechanismRadios + ), + + // Kerberos + kerberosPrincipal: getValue( + browser, + Selectors.ConnectionFormInputGssApiPrincipal + ), + kerberosServiceName: getValue( + browser, + Selectors.ConnectionFormInputGssApiServiceName + ), + kerberosCanonicalizeHostname: getCheckedRadioValue( + browser, + Selectors.ConnectionFormCanonicalizeHostNameRadios + ), + kerberosServiceRealm: getValue( + browser, + Selectors.ConnectionFormInputGssApiServiceRealm + ), + kerberosProvidePassword: getCheckboxValue( + browser, + Selectors.ConnectionFormGssApiPasswordCheckbox + ), + kerberosPassword: getValue( + browser, + Selectors.ConnectionFormInputGssApiPassword + ), + + // LDAP + ldapUsername: getValue(browser, Selectors.ConnectionFormInputPlainUsername), + ldapPassword: getValue(browser, Selectors.ConnectionFormInputPlainPassword), + + // AWS IAM + awsAccessKeyId: getValue( + browser, + Selectors.ConnectionFormInputAWSAccessKeyId + ), + awsSecretAccessKey: getValue( + browser, + Selectors.ConnectionFormInputAWSSecretAccessKey + ), + awsSessionToken: getValue( + browser, + Selectors.ConnectionFormInputAWSSessionToken + ), + }); + + // TLS/SSL + await browseToTab(browser, 'TLS/SSL'); + const tlsState = await promiseMap({ + sslConnection: getCheckedRadioValue( + browser, + Selectors.ConnectionFormSSLConnectionRadios + ), + + // these are just the button text, not actually the file path + tlsCAFile: getFilename(browser, Selectors.ConnectionFormTlsCaButton), + tlsCertificateKeyFile: getFilename( + browser, + Selectors.ConnectionFormTlsCertificateKeyButton + ), + + clientKeyPassword: getValue( + browser, + Selectors.ConnectionFormInputTlsCertificateKeyFilePassword + ), + tlsInsecure: getCheckboxValue( + browser, + Selectors.ConnectionFormTlsInsecureCheckbox + ), + tlsAllowInvalidHostnames: getCheckboxValue( + browser, + Selectors.ConnectionFormTlsAllowInvalidHostnamesCheckbox + ), + tlsAllowInvalidCertificates: getCheckboxValue( + browser, + Selectors.ConnectionFormTlsAllowInvalidCertificatesCheckbox + ), + }); + + // Proxy/SSH Tunnel + await browseToTab(browser, 'Proxy/SSH Tunnel'); + + const proxyState = await promiseMap({ + proxyMethod: getCheckedRadioValue( + browser, + Selectors.ConnectionFormProxyMethodRadios + ), + + // SSH with Password + // NOTE: these don't go in the URI so will likely never return values + sshPasswordHost: getValue( + browser, + Selectors.ConnectionFormInputSshPasswordHost + ), + sshPasswordPort: getValue( + browser, + Selectors.ConnectionFormInputSshPasswordPort + ), + sshPasswordUsername: getValue( + browser, + Selectors.ConnectionFormInputSshPasswordUsername + ), + sshPasswordPassword: getValue( + browser, + Selectors.ConnectionFormInputSshPasswordPassword + ), + + // SSH with Identity File + // NOTE: same as above these are unlikely ever return values in these tests + sshIdentityHost: getValue( + browser, + Selectors.ConnectionFormInputSshIdentityHost + ), + sshIdentityPort: getValue( + browser, + Selectors.ConnectionFormInputSshIdentityPort + ), + sshIdentityUsername: getValue( + browser, + Selectors.ConnectionFormInputSshIdentityUsername + ), + // same as above: this is the button text, not the file path + sshIdentityKeyFile: getFilename( + browser, + Selectors.ConnectionFormSshIdentityKeyButton + ), + sshIdentityPassword: getValue( + browser, + Selectors.ConnectionFormInputSshIdentityPassword + ), + + socksHost: getValue(browser, Selectors.ConnectionFormInputSocksHost), + socksPort: getValue(browser, Selectors.ConnectionFormInputSocksPort), + socksUsername: getValue( + browser, + Selectors.ConnectionFormInputSocksUsername + ), + socksPassword: getValue( + browser, + Selectors.ConnectionFormInputSocksPassword + ), + }); + + // Advanced + await browseToTab(browser, 'Advanced'); + const advancedState = await promiseMap({ + readPreference: getCheckedRadioValue( + browser, + Selectors.ConnectionFormReadPreferenceRadios + ), + replicaSet: getValue(browser, Selectors.ConnectionFormInputReplicaset), + defaultDatabase: getValue( + browser, + Selectors.ConnectionFormInputDefaultDatabase + ), + urlOptionKeys: getMultipleValues( + browser, + Selectors.ConnectionFormUrlOptionKeys + ), + urlOptionValues: getMultipleValues( + browser, + Selectors.ConnectionFormUrlOptionValues + ), + }); + + if (advancedState.urlOptionKeys) { + advancedState.urlOptions = Object.fromEntries( + advancedState.urlOptionKeys.map((k: string, i: number) => [ + k, + advancedState.urlOptionValues[i], + ]) + ); + + delete advancedState.urlOptionKeys; + delete advancedState.urlOptionValues; + } + + const result = { + connectionString, + ...defaultState, + ...authenticationState, + ...tlsState, + ...proxyState, + ...advancedState, + }; + + // restore the initial state + if (wasExpanded) { + // get back to the tab it was on + await browseToTab(browser, initialTab); + } else { + // collapse it again + await browser.clickVisible(Selectors.ShowConnectionFormButton); + + await browser.waitUntil(async () => { + const advancedButton = await browser.$( + Selectors.ShowConnectionFormButton + ); + return (await advancedButton.getAttribute('aria-expanded')) === 'false'; + }); + } + + return result; +} + +async function resetForm(browser: CompassBrowser) { + await browser.clickVisible(Selectors.SidebarNewConnectionButton); + + await browser.waitUntil(async () => { + return ( + (await browser.$(Selectors.ConnectionStringInput).getValue()) === + 'mongodb://localhost:27017' + ); + }); +} + +async function setFormState(browser: CompassBrowser, state: any) { + await resetForm(browser); + + await maybeExpandAdvancedOptions(browser); + + // General + await browseToTab(browser, 'General'); + + if (state.scheme) { + await browser.clickParent( + Selectors.connectionFormSchemeRadio(state.scheme) + ); + } + + if (state.hosts) { + for (let i = 0; i < state.hosts.length; ++i) { + if (i > 0) { + await browser.clickVisible( + '[data-testid="host-input-container"]:last-child [data-testid="connection-add-host-button"]' + ); + } + await browser.setValueVisible( + `[data-testid="connection-host-input-${i}"]`, + state.hosts[i] + ); + } + } + + if (state.directConnection) { + await browser.clickParent(Selectors.ConnectionFormDirectConnectionCheckbox); + } + + // Authentication + await browseToTab(browser, 'Authentication'); + + if (state.authMethod) { + await browser.clickParent( + Selectors.connectionFormAuthenticationMethodRadio(state.authMethod) + ); + } + + // Username/Password + if (state.defaultUsername) { + await browser.setValueVisible( + Selectors.ConnectionFormInputUsername, + state.defaultUsername + ); + await browser.setValueVisible( + Selectors.ConnectionFormInputPassword, + state.defaultPassword + ); + } + if (state.defaultAuthSource) { + await browser.setValueVisible( + Selectors.ConnectionFormInputAuthSource, + state.defaultAuthSource + ); + } + if (state.defaultAuthMechanism) { + await browser.clickParent( + Selectors.connectionFormAuthMechanismRadio(state.defaultAuthMechanism) + ); + } + + // Kerberos + if (state.kerberosPrincipal) { + await browser.setValueVisible( + Selectors.ConnectionFormInputGssApiPrincipal, + state.kerberosPrincipal + ); + } + if (state.kerberosServiceName) { + await browser.setValueVisible( + Selectors.ConnectionFormInputGssApiServiceName, + state.kerberosServiceName + ); + } + if (state.kerberosCanonicalizeHostname) { + await browser.clickParent( + Selectors.connectionFormCanonicalizeHostNameRadio( + state.kerberosCanonicalizeHostname + ) + ); + } + if (state.kerberosServiceRealm) { + await browser.setValueVisible( + Selectors.ConnectionFormInputGssApiServiceRealm, + state.kerberosServiceRealm + ); + } + if (state.kerberosServiceRealm) { + await browser.clickParent(Selectors.ConnectionFormGssApiPasswordCheckbox); + } + if (state.kerberosPassword) { + await browser.setValueVisible( + Selectors.ConnectionFormInputGssApiPassword, + state.kerberosPassword + ); + } + + // LDAP + if (state.ldapUsername) { + await browser.setValueVisible( + Selectors.ConnectionFormInputPlainUsername, + state.ldapUsername + ); + await browser.setValueVisible( + Selectors.ConnectionFormInputPlainPassword, + state.ldapPassword + ); + } + + // AWS IAM + if (state.awsAccessKeyId) { + await browser.setValueVisible( + Selectors.ConnectionFormInputAWSAccessKeyId, + state.awsAccessKeyId + ); + await browser.setValueVisible( + Selectors.ConnectionFormInputAWSSecretAccessKey, + state.awsSecretAccessKey + ); + } + if (state.awsSessionToken) { + await browser.setValueVisible( + Selectors.ConnectionFormInputAWSSessionToken, + state.awsSessionToken + ); + } + + // TLS/SSL + await browseToTab(browser, 'TLS/SSL'); + + if (state.sslConnection) { + await browser.clickParent( + Selectors.connectionFormSSLConnectionRadio(state.sslConnection) + ); + } + + if (state.tlsCAFile) { + await browser.selectFile( + Selectors.ConnectionFormTlsCaFile, + state.tlsCAFile + ); + } + if (state.tlsCertificateKeyFile) { + await browser.selectFile( + Selectors.ConnectionFormTlsCertificateKeyFile, + state.tlsCertificateKeyFile + ); + } + if (state.clientKeyPassword) { + await browser.setValueVisible( + Selectors.ConnectionFormInputTlsCertificateKeyFilePassword, + state.clientKeyPassword + ); + } + if (state.tlsInsecure) { + await browser.clickParent(Selectors.ConnectionFormTlsInsecureCheckbox); + } + if (state.tlsAllowInvalidHostnames) { + await browser.clickParent( + Selectors.ConnectionFormTlsAllowInvalidHostnamesCheckbox + ); + } + if (state.tlsAllowInvalidCertificates) { + await browser.clickParent( + Selectors.ConnectionFormTlsAllowInvalidCertificatesCheckbox + ); + } + + // Proxy/SSH Tunnel + await browseToTab(browser, 'Proxy/SSH Tunnel'); + + //proxyMethod + if (state.proxyMethod) { + await browser.clickParent( + Selectors.connectionFormProxyMethodRadio(state.proxyMethod) + ); + } + + // SSH with Password + // NOTE: these don't affect the URI + if (state.sshPasswordHost) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSshPasswordHost, + state.sshPasswordHost + ); + await browser.setValueVisible( + Selectors.ConnectionFormInputSshPasswordPort, + state.sshPasswordPort + ); + } + if (state.sshPasswordUsername) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSshPasswordUsername, + state.sshPasswordUsername + ); + } + if (state.sshPasswordPassword) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSshPasswordPassword, + state.sshPasswordPassword + ); + } + + // SSH with Identity File + // NOTE: these don't affect the URI + if (state.sshIdentityHost) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSshIdentityHost, + state.sshIdentityHost + ); + await browser.setValueVisible( + Selectors.ConnectionFormInputSshIdentityPort, + state.sshIdentityPort + ); + } + if (state.sshIdentityUsername) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSshIdentityUsername, + state.sshIdentityUsername + ); + } + if (state.sshIdentityKeyFile) { + await browser.selectFile( + Selectors.ConnectionFormSshIdentityKeyFile, + state.sshIdentityKeyFile + ); + } + if (state.sshIdentityPassword) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSshIdentityPassword, + state.sshIdentityPassword + ); + } + + // Socks5 + if (state.socksHost) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSocksHost, + state.socksHost + ); + } + if (state.socksPort) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSocksPort, + state.socksPort + ); + } + if (state.socksUsername) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSocksUsername, + state.socksUsername + ); + } + if (state.socksPassword) { + await browser.setValueVisible( + Selectors.ConnectionFormInputSocksPassword, + state.socksPassword + ); + } + + // Advanced + await browseToTab(browser, 'Advanced'); + + if (state.readPreference) { + await browser.clickParent( + Selectors.connectionFormReadPreferenceRadio(state.readPreference) + ); + } + if (state.replicaSet) { + await browser.setValueVisible( + Selectors.ConnectionFormInputReplicaset, + state.replicaSet + ); + } + if (state.defaultDatabase) { + await browser.setValueVisible( + Selectors.ConnectionFormInputDefaultDatabase, + state.defaultDatabase + ); + } + if (state.urlOptions) { + for (const [index, [key, value]] of Object.entries( + state.urlOptions + ).entries()) { + // key + await browser.clickVisible( + Selectors.connectionFormUrlOptionKeyButton(index) + ); + // this is quite hacky, unfortunately + const options = await browser.$$('#select-key-menu [role="option"]'); + for (const option of options) { + const span = await option.$(`span=${key}`); + if (await span.isExisting()) { + await span.waitForDisplayed(); + await span.click(); + break; + } + } + + // value + await browser.setValueVisible( + Selectors.connectionFormUrlOptionValueInput(index), + value as string + ); + } + } +} diff --git a/packages/connection-form/src/components/advanced-options-tabs/advanced-tab/url-options-table.tsx b/packages/connection-form/src/components/advanced-options-tabs/advanced-tab/url-options-table.tsx index 84e751bc56a..02e7563ad4c 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/advanced-tab/url-options-table.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/advanced-tab/url-options-table.tsx @@ -156,6 +156,7 @@ function UrlOptionsTable({ >