From 4d66a743f92cda77ab38b1415811ef0b851b31fd Mon Sep 17 00:00:00 2001 From: Vladimir Y Date: Tue, 25 Dec 2018 12:26:21 +0300 Subject: [PATCH] tweak e2e tests * don't fail on Object has been destroyed: "sender" electron-rpc-api module error * take more screenshots --- src/e2e/index.spec.ts | 87 +++++++++++++++++++++++++++++-------------- src/e2e/workflow.ts | 10 ++++- 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/e2e/index.spec.ts b/src/e2e/index.spec.ts index 7493d8de0..676280f8c 100644 --- a/src/e2e/index.spec.ts +++ b/src/e2e/index.spec.ts @@ -2,6 +2,7 @@ // TODO track this issue https://github.com/DefinitelyTyped/DefinitelyTyped/issues/25186 // tslint:disable:await-promise +import byline from "byline"; import fs from "fs"; import path from "path"; import psNode from "ps-node"; // see also https://www.npmjs.com/package/find-process @@ -10,7 +11,7 @@ import {platform} from "os"; import {promisify} from "util"; import {ACCOUNTS_CONFIG_ENTRY_URL_LOCAL_PREFIX} from "src/shared/constants"; -import {APP_NAME, CI, ENV, initApp, test} from "./workflow"; +import {APP_NAME, CI, ENV, initApp, saveScreenshot, test} from "./workflow"; test.serial("general actions: app start, master password setup, add accounts, logout, auto login", async (t) => { // setup and login @@ -65,42 +66,72 @@ test.serial("general actions: app start, master password setup, add accounts, lo await workflow.destroyApp(); })(); - // making sure log file has not been created (no errors happened) - t.false(fs.existsSync(t.context.logFilePath), `"${t.context.logFilePath}" file should not exist`); + if (fs.existsSync(t.context.logFilePath)) { + await new Promise((resolve, reject) => { + const stream = byline.createStream( + fs.createReadStream(t.context.logFilePath), + ); + stream.on("data", (_, line = String(_)) => { + if ( + line.includes("[electron-rpc-api]") && + line.includes(`Object has been destroyed: "sender"`) + ) { + return; + } + line = null; // WARN: don't print log line + t.fail(`App log file error line`); + }); + stream.on("error", reject); + stream.on("end", resolve); + }); + } // additionally making sure that settings file is actually encrypted by simply scanning it for the raw "login" value const rawSettings = promisify(fs.readFile)(path.join(t.context.userDataDirPath, "settings.bin")); t.true(rawSettings.toString().indexOf(ENV.loginPrefix) === -1); }); -if (CI) { +test.beforeEach(async (t) => { + t.context.testStatus = "initial"; +}); + +test.afterEach(async (t) => { + t.context.testStatus = "success"; +}); + +test.afterEach.always(async (t) => { + if (t.context.testStatus !== "success") { + await saveScreenshot(t); + } + + if (!CI) { + return; + } + // kill processes to avoid appveyor error during preparing logs for uploading: // The process cannot access the file because it is being used by another process: output\e2e\1545563294836\chrome-driver.log + await (async () => { + // HINT: add "- ps: Get-Process" line to appveyor.yml to list the processes + const processes: Array<{ pid: number }> = await Promise.all( + [ + {command: APP_NAME}, {arguments: APP_NAME}, + {command: "electron"}, {arguments: "electron"}, + {command: "chrome"}, {arguments: "chrome"}, + {command: "webdriver"}, {arguments: "webdriver"}, + {command: "chrome-driver"}, {arguments: "chrome-driver"}, + {arguments: "log"}, + {arguments: "e2e"}, + ].map((criteria) => promisify(psNode.lookup)(criteria)), + ); - test.afterEach(async () => { - await (async () => { - // HINT: add "- ps: Get-Process" line to appveyor.yml to list the processes - const processes: Array<{ pid: number }> = await Promise.all( - [ - {command: APP_NAME}, {arguments: APP_NAME}, - {command: "electron"}, {arguments: "electron"}, - {command: "chrome"}, {arguments: "chrome"}, - {command: "webdriver"}, {arguments: "webdriver"}, - {command: "chrome-driver"}, {arguments: "chrome-driver"}, - {arguments: "log"}, - {arguments: "e2e"}, - ].map((criteria) => promisify(psNode.lookup)(criteria)), - ); - - for (const {pid} of processes) { - try { - await killSelfAndChildrenProcesses(pid); - } catch { - // NOOP - } + for (const {pid} of processes) { + try { + await killSelfAndChildrenProcesses(pid); + } catch { + // NOOP } - })(); - }); + } + })(); async function killSelfAndChildrenProcesses(pid: number) { const processesToKill = [ @@ -116,4 +147,4 @@ if (CI) { } } } -} +}); diff --git a/src/e2e/workflow.ts b/src/e2e/workflow.ts index 72aa7c267..a8b3fc103 100644 --- a/src/e2e/workflow.ts +++ b/src/e2e/workflow.ts @@ -17,6 +17,7 @@ import {AccountType} from "src/shared/model/account"; import {CI_ENV_LOGOUT_ACTION_TIMEOUT_MS} from "./shared-constants"; export interface TestContext { + testStatus: "initial" | "success" | "fail"; app: Application; outputDirPath: string; userDataDirPath: string; @@ -57,7 +58,6 @@ const GLOBAL_STATE = { export async function initApp(t: ExecutionContext, options: { initial: boolean }): Promise { t.context.workflow = buildWorkflow(t); - t.context.sinon = { addAccountSpy: sinon.spy(t.context.workflow, "addAccount"), }; @@ -165,13 +165,17 @@ export async function initApp(t: ExecutionContext, options: { initi function buildWorkflow(t: ExecutionContext) { const workflow = { async destroyApp() { + await saveScreenshot(t); + // TODO update to electron 2: app.isRunning() returns undefined, uncomment as soon as it's fixed if (!t.context.app || !t.context.app.isRunning()) { t.pass("app is not running"); return; } + await t.context.app.stop(); t.is(t.context.app.isRunning(), false); + delete t.context.app; }, @@ -422,6 +426,10 @@ export async function catchError(t: ExecutionContext, error?: Error } export async function saveScreenshot(t: ExecutionContext) { + if (!t.context.app || !t.context.app.browserWindow) { + return; + } + const file = path.join( t.context.outputDirPath, `sreenshot-${t.title}-${new Date().toISOString()}.png`.replace(/[^A-Za-z0-9\.]/g, "_"),