diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0373fdfa892..b36357a1962 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -67,6 +67,9 @@ jobs: max_attempts: 5 shell: bash command: npm run package-compass + # When lerna fails to publish, it leaves the repo in the dirty state + # that will mess up the following attempts + on_retry_command: git reset HEAD --hard env: HADRON_PRODUCT: mongodb-compass HADRON_PRODUCT_NAME: MongoDB Compass @@ -102,4 +105,4 @@ jobs: if: ${{ cancelled() || failure() }} with: name: Spectron Debug Log ${{ runner.os }} - path: packages/compass-e2e-tests/.log/**/*.log + path: packages/compass-e2e-tests/.log/**/* diff --git a/packages/compass-e2e-tests/helpers/compass.js b/packages/compass-e2e-tests/helpers/compass.js index 6cbb4602bf8..5ef083002f6 100644 --- a/packages/compass-e2e-tests/helpers/compass.js +++ b/packages/compass-e2e-tests/helpers/compass.js @@ -19,6 +19,7 @@ const Selectors = require('./selectors'); * @property {(selector: string, timeout?: number) => Promise} clickVisible * @property {(selector: string, value: any, timeout?: number) => Promise} setValueVisible * @property {() => Promise} waitForConnectionScreen + * @property {() => Promise} closeTourModal * @property {() => Promise} closePrivacySettingsModal * @property {(timeout?: number) => Promise} doConnect * @property {(connectionString: string, timeout?: number) => Promise} connectWithConnectionString @@ -70,7 +71,12 @@ function getAtlasConnectionOptions() { return { host, username, password, srvRecord: true }; } +// For the tmpdirs let i = 0; +// For the screenshots +let j = 0; +// For the html +let k = 0; /** * @param {boolean} testPackagedApp Should compass start from the packaged binary or just from the source (defaults to source) @@ -120,11 +126,7 @@ async function startCompass( const shouldStoreAppLogs = process.env.ci || process.env.CI; - // Mimicking webdriver path with this for consistency - const nowFormatted = new Date() - .toISOString() - .replace(/:/g, '-') - .replace(/Z$/, ''); + const nowFormatted = formattedDate(); if (shouldStoreAppLogs) { const chromeDriverLogPath = path.join( @@ -180,6 +182,11 @@ async function startCompass( return app; } +function formattedDate() { + // Mimicking webdriver path with this for consistency + return new Date().toISOString().replace(/:/g, '-').replace(/Z$/, ''); +} + async function rebuildNativeModules(compassPath = COMPASS_PATH) { const { config: { @@ -306,9 +313,39 @@ function addCommands(app) { } ); + app.client.addCommand('closeTourModal', async function () { + // Wait a bit in any case if it exists or doesn't just so it has a chance to + // render if possible + await delay(1000); + if (await app.client.isExisting(Selectors.FeatureTourModal)) { + await app.client.waitUntil( + async () => { + return await app.client.isVisible(Selectors.FeatureTourModal); + }, + 1000, + 'Expected feature tour modal to be visible', + 50 + ); + // Wait a bit before clicking so that transition is through + await delay(100); + await app.client.clickVisible(Selectors.CloseFeatureTourModal); + await app.client.waitUntil( + async () => { + return !(await app.client.isExisting(Selectors.FeatureTourModal)); + }, + 5000, + 'Expected feature tour modal to disappear after closing it', + 50 + ); + } + }); + app.client.addCommand( 'closePrivacySettingsModal', async function closePrivacySettingsModal() { + // Wait a bit in any case if it exists or doesn't just so it has a chance to + // render if possible + await delay(1000); if (await app.client.isExisting(Selectors.PrivacySettingsModal)) { await app.client.waitUntil( async () => { @@ -600,6 +637,45 @@ function addCommands(app) { ); } +/** + * @param {ExtendedApplication} app + * @param {string} imgPathName + */ +async function capturePage( + app, + imgPathName = `screenshot-${formattedDate()}-${++j}.png` +) { + try { + const buffer = await app.browserWindow.capturePage(); + await fs.mkdir(LOG_PATH, { recursive: true }); + // @ts-expect-error buffer is Electron.NativeImage not a real buffer, but it + // can be used as a buffer when storing an image + await fs.writeFile(path.join(LOG_PATH, imgPathName), buffer); + return true; + } catch (_) { + return false; + } +} + +/** + * @param {ExtendedApplication} app + * @param {string} htmlPathName + */ +async function savePage( + app, + htmlPathName = `page-${formattedDate()}-${++k}.html` +) { + try { + await app.webContents.savePage( + path.join(LOG_PATH, htmlPathName), + 'HTMLComplete' + ); + return true; + } catch { + return false; + } +} + module.exports = { startCompass, rebuildNativeModules, @@ -608,7 +684,9 @@ module.exports = { getCompassBinPath, getAtlasConnectionOptions, buildCompass, + capturePage, + savePage, Selectors, COMPASS_PATH, - LOG_PATH, + LOG_PATH }; diff --git a/packages/compass-e2e-tests/helpers/selectors.js b/packages/compass-e2e-tests/helpers/selectors.js index a043c13b21a..b8f7412a793 100644 --- a/packages/compass-e2e-tests/helpers/selectors.js +++ b/packages/compass-e2e-tests/helpers/selectors.js @@ -2,6 +2,8 @@ module.exports = { ConnectSection: '[data-test-id="connect-section"]', PrivacySettingsModal: '[data-test-id="privacy-settings-modal"]', ClosePrivacySettingsButton: '[data-test-id="close-privacy-settings-button"]', + FeatureTourModal: '[data-test-id="feature-tour-modal"]', + CloseFeatureTourModal: '[data-test-id="close-tour-button"]', ConnectButton: '[data-test-id="connect-button"]', DatabasesTable: '[data-test-id="databases-table"]', ConnectionStringInput: 'input[name="connectionString"]', diff --git a/packages/compass-e2e-tests/tests/smoke.test.js b/packages/compass-e2e-tests/tests/smoke.test.js index 2269c3e4ea9..b65c4a7c0c1 100644 --- a/packages/compass-e2e-tests/tests/smoke.test.js +++ b/packages/compass-e2e-tests/tests/smoke.test.js @@ -3,7 +3,9 @@ const { expect } = require('chai'); const { createUnlockedKeychain } = require('../helpers/keychain'); const { startCompass, - getAtlasConnectionOptions + getAtlasConnectionOptions, + capturePage, + savePage } = require('../helpers/compass'); /** @@ -20,12 +22,17 @@ describe('Compass', function () { keychain.activate(); compass = await startCompass(); await compass.client.waitForConnectionScreen(); + await compass.client.closeTourModal(); await compass.client.closePrivacySettingsModal(); }); after(async () => { try { if (compass) { + if (process.env.CI) { + await capturePage(compass); + await savePage(compass); + } await compass.stop(); compass = null; } diff --git a/packages/compass/src/app/index.js b/packages/compass/src/app/index.js index 2d1d223e9af..af5a6cb59c1 100644 --- a/packages/compass/src/app/index.js +++ b/packages/compass/src/app/index.js @@ -289,7 +289,11 @@ var Application = View.extend({ var oldVersion = _.get(prefs, 'lastKnownVersion', '0.0.0'); var currentVersion = APP_VERSION; var save = false; - if (semver.lt(oldVersion, currentVersion)) { + if ( + semver.lt(oldVersion, currentVersion) || + // So we can test the tour in any e2e environment, not only on prod + process.env.APP_ENV === 'spectron' + ) { prefs.showFeatureTour = oldVersion; save = true; }