diff --git a/.gitignore b/.gitignore index c03ef959ca9a19..4c719be9fd491c 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ cached-requests.json /desktop/id_rsa /desktop/e2e/logs /desktop/e2e/screenshots +/desktop/e2e/result.xml # Typescript *.tsbuildinfo diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index e39e0d14f83955..949a523ead394e 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -8,7 +8,6 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.pullRequests import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.ScriptBuildStep import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.dockerRegistry -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.DockerCommandStep import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.dockerCommand import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubConnection import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.schedule @@ -650,16 +649,172 @@ object WpCalypso : GitVcsRoot({ object WpDesktop : Project({ name = "Desktop" buildType(WpDesktop_DesktopE2ETests) + + params { + text("docker_image_dekstop", "registry.a8c.com/calypso/ci-desktop:latest", label = "Docker image", description = "Docker image to use for the run", allowEmpty = true) + password("CALYPSO_SECRETS_ENCRYPTION_KEY", "credentialsJSON:ff451a7d-df79-4635-b6e8-cbd6ec18ddd8", description = "password for encrypting/decrypting certificates and general secrets for the wp-desktop and simplenote-electron repo", display = ParameterDisplay.HIDDEN) + password("E2EGUTENBERGUSER", "credentialsJSON:27ca9d7b-c6b5-4e84-94d5-ea43879d8184", display = ParameterDisplay.HIDDEN) + password("E2EPASSWORD", "credentialsJSON:2c4425c4-07d2-414c-9f18-b64da307bdf2", display = ParameterDisplay.HIDDEN) + } }) object WpDesktop_DesktopE2ETests : BuildType({ name = "Desktop e2e tests" + description = "Run wp-desktop e2e tests in Linux" + + artifactRules = """ + desktop/release => release + desktop/e2e/logs => logs + desktop/e2e/screenshots => screenshots + """.trimIndent() vcs { root(WpCalypso) cleanCheckout = true } - steps { } + steps { + script { + name = "Prepare environment" + scriptContent = """ + set -e + + export CHROMEDRIVER_SKIP_DOWNLOAD=true + export PUPPETEER_SKIP_DOWNLOAD=true + + # Update node + . "${'$'}NVM_DIR/nvm.sh" --no-use + nvm install + + # Restore mtime to maximize cache hits + /usr/lib/git-core/git-restore-mtime --force --commit-time --skip-missing + + # Decript certs + openssl aes-256-cbc -md md5 -d -in desktop/resource/calypso/secrets.json.enc -out config/secrets.json -k "%CALYPSO_SECRETS_ENCRYPTION_KEY%" + + # Install modules + yarn install + yarn run build-desktop:install-app-deps + """.trimIndent() + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + dockerPull = true + dockerImage = "%docker_image_dekstop%" + dockerRunParameters = "-u %env.UID%" + } + + script { + name = "Build Calypso source" + scriptContent = """ + set -e + + # Update node + . "${'$'}NVM_DIR/nvm.sh" --no-use + nvm install + + # Build desktop + yarn run build-desktop:source + """.trimIndent() + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + dockerPull = true + dockerImage = "%docker_image_dekstop%" + dockerRunParameters = "-u %env.UID%" + } + + script { + name = "Build app (linux)" + scriptContent = """ + set -e + + export ELECTRON_BUILDER_ARGS='-c.linux.target=dir' + export USE_HARD_LINKS=false + + # Update node + . "${'$'}NVM_DIR/nvm.sh" --no-use + nvm install + + # Build app + yarn run build-desktop:app + """.trimIndent() + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + dockerPull = true + dockerImage = "%docker_image_dekstop%" + dockerRunParameters = "-u %env.UID%" + } + + script { + name = "Run tests (linux)" + scriptContent = """ + set -e + + export E2EGUTENBERGUSER="%E2EGUTENBERGUSER%" + export E2EPASSWORD="%E2EPASSWORD%" + export DISPLAY=:99 + export CI=true + + # Update node + . "${'$'}NVM_DIR/nvm.sh" --no-use + nvm install + + # Start framebuffer + Xvfb ${'$'}{DISPLAY} -screen 0 1280x1024x24 & + + # Run tests + yarn run test-desktop:e2e + """.trimIndent() + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + dockerPull = true + dockerImage = "%docker_image_dekstop%" + dockerRunParameters = "-u %env.UID%" + } + } + + failureConditions { + executionTimeoutMin = 15 + } + + features { + feature { + type = "xml-report-plugin" + param("xmlReportParsing.reportType", "junit") + param("xmlReportParsing.reportDirs", "desktop/e2e/result.xml") + } + perfmon { + } + pullRequests { + vcsRootExtId = "${WpCalypso.id}" + provider = github { + authType = token { + token = "credentialsJSON:57e22787-e451-48ed-9fea-b9bf30775b36" + } + filterAuthorRole = PullRequests.GitHubRoleFilter.EVERYBODY + } + } + commitStatusPublisher { + vcsRootExtId = "${WpCalypso.id}" + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:57e22787-e451-48ed-9fea-b9bf30775b36" + } + } + } + + notifications { + notifierSettings = slackNotifier { + connection = "PROJECT_EXT_11" + sendTo = "#wp-desktop-calypso-e2e" + messageFormat = verboseMessageFormat { + addBranch = true + maximumNumberOfChanges = 10 + } + } + buildFailedToStart = true + buildFailed = true + buildFinishedSuccessfully = true + firstSuccessAfterFailure = true + buildProbablyHanging = true + } + } + }) diff --git a/desktop/e2e/mocha-reporter.json b/desktop/e2e/mocha-reporter.json new file mode 100644 index 00000000000000..9762d7db6ee1a3 --- /dev/null +++ b/desktop/e2e/mocha-reporter.json @@ -0,0 +1,6 @@ +{ + "reporterEnabled": "spec, mocha-junit-reporter", + "mochaJunitReporterReporterOptions": { + "mochaFile": "./e2e/result.xml" + } +} diff --git a/desktop/e2e/run.js b/desktop/e2e/run.js index 67348ccdb412f6..51713a20c90d54 100644 --- a/desktop/e2e/run.js +++ b/desktop/e2e/run.js @@ -18,6 +18,8 @@ const APP_ARGS = [ '--disable-http-cache', '--start-maximized', '--remote-debugging-port=9222', + '--disable-setuid-sandbox', + '--no-sandbox', ]; let BUILT_APP_DIR; @@ -63,18 +65,21 @@ function initLogs( timestamp ) { const appLogPath = path.join( dir, `app-${ timestamp }.log` ); const driverLogPath = path.join( dir, `chromedriver-${ timestamp }.log` ); + const electronLogPath = path.join( dir, `electron-${ timestamp }.log` ); const appLogFd = openSync( appLogPath, 'a' ); const driverLogFd = openSync( driverLogPath, 'a' ); + const electronLogFd = openSync( electronLogPath, 'a' ); - if ( ! appLogFd || ! driverLogFd ) { + if ( ! appLogFd || ! driverLogFd || ! electronLogFd ) { throw 'failed to initialize logs'; } const appLog = { path: appLogPath, fd: appLogFd }; const driverLog = { path: driverLogPath, fd: driverLogFd }; + const electronLog = { path: electronLogPath, fd: electronLogFd }; - return { appLog, driverLog }; + return { appLog, driverLog, electronLog }; } const delay = promisify( setTimeout ); @@ -111,12 +116,12 @@ async function run() { // Replace `:` with `-` to format timestamp as YYYY-MM-DDTHH-MM-SS.mmmZ const timestamp = new Date().toJSON().replace( /:/g, '-' ); - const { appLog, driverLog } = initLogs( timestamp ); + const { appLog, driverLog, electronLog } = initLogs( timestamp ); await videoRecorder.startVideo(); const parentEnv = process.env; - app = spawnDetached( CWD, SPAWN_CMD, APP_ARGS, null, { + app = spawnDetached( CWD, SPAWN_CMD, APP_ARGS, electronLog.fd, { WP_DEBUG_LOG: appLog.path, DEBUG: true, ...parentEnv, @@ -132,9 +137,14 @@ async function run() { ); const tests = path.join( E2E_DIR, 'tests', 'e2e.js' ); - execSync( `npx mocha ${ tests } --timeout 20000 --exit`, { - stdio: 'inherit', - } ); + const reporterOptions = path.join( E2E_DIR, 'mocha-reporter.json' ); + execSync( + `npx mocha ${ tests } --timeout 20000 --exit --reporter mocha-multi-reporters --reporter-options configFile=${ reporterOptions }`, + { + cwd: PROJECT_DIR, + stdio: 'inherit', + } + ); } catch ( err ) { console.error( err ); // eslint-disable-line no-console process.exitCode = 1; diff --git a/desktop/e2e/tests/lib/video-recorder.js b/desktop/e2e/tests/lib/video-recorder.js index f9ca062a4a24da..ce520d48433b30 100644 --- a/desktop/e2e/tests/lib/video-recorder.js +++ b/desktop/e2e/tests/lib/video-recorder.js @@ -42,21 +42,27 @@ exports.startVideo = function () { const fileName = `e2e-test-run-${ dateTime }.mpg`; file = path.join( E2E_DIR, 'screenshots', 'videos', fileName ); this.createDir( path.dirname( file ) ); - ffVideo = child_process.spawn( ffmpeg.path, [ - '-f', - 'avfoundation', - '-video_size', - '1440x1000', - '-r', - 30, - '-i', - '0:none', - '-pix_fmt', - 'yuv420p', - '-loglevel', - 'error', - file, - ] ); + ffVideo = child_process.spawn( + ffmpeg.path, + [ + '-f', + 'avfoundation', + '-video_size', + '1440x1000', + '-r', + 30, + '-i', + '0:none', + '-pix_fmt', + 'yuv420p', + '-loglevel', + 'error', + file, + ], + { + stdio: 'inherit', + } + ); }; exports.stopVideo = function () { diff --git a/desktop/package.json b/desktop/package.json index ab4ab9c4b9de48..fd44a145408381 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -35,6 +35,8 @@ "ffmpeg-static": "2.4.0", "lodash": "^4.17.15", "mocha": "^8.1.3", + "mocha-junit-reporter": "^2.0.0", + "mocha-multi-reporters": "^1.5.1", "mocha-steps": "1.3.0", "ncp": "^2.0.0", "selenium-webdriver": "4.0.0-alpha.1" diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 4e3565e2970402..05a077e2d5efd2 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -480,6 +480,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + chokidar@3.4.2: version "3.4.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" @@ -626,6 +631,11 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -1462,6 +1472,11 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + is-buffer@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" @@ -1867,6 +1882,15 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" +md5@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + meow@^3.1.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -1934,13 +1958,32 @@ mkdirp@0.5.4: dependencies: minimist "^1.2.5" -mkdirp@^0.5.1, mkdirp@^0.5.4: +mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" +mocha-junit-reporter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-2.0.0.tgz#3bf990fce7a42c0d2b718f188553a25d9f24b9a2" + integrity sha512-20HoWh2HEfhqmigfXOKUhZQyX23JImskc37ZOhIjBKoBEsb+4cAFRJpAVhFpnvsztLklW/gFVzsrobjLwmX4lA== + dependencies: + debug "^2.2.0" + md5 "^2.1.0" + mkdirp "~0.5.1" + strip-ansi "^4.0.0" + xml "^1.0.0" + +mocha-multi-reporters@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz#c73486bed5519e1d59c9ce39ac7a9792600e5676" + integrity sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg== + dependencies: + debug "^4.1.1" + lodash "^4.17.15" + mocha-steps@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/mocha-steps/-/mocha-steps-1.3.0.tgz#2449231ec45ec56810f65502cb22e2571862957f" @@ -3238,6 +3281,11 @@ xml2js@^0.4.17: sax ">=0.6.0" xmlbuilder "~11.0.0" +xml@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"