Привет!
В продолжение темы особенностей GlaceJS, в этой статье я расскажу про то, как MochaJS
**(не)**обрабатывает множественные вызовы after
-хуков.
Рассмотрим пример:
describe("scope", () => {
it("test", () => {});
after(() => {
console.log("After #1");
});
after(() => {
console.log("After #2");
});
});
И смотрим вывод:
$ mocha proba.js
scope
√ test
After #1
After #2
1 passing (6ms)
Как видим использовать несколько after
вполне возможно. Но до тех пор, пока в первом не возникнет исключение:
describe("scope", () => {
it("test", () => {});
after(() => {
console.log("After #1");
throw new Error("BOOM!");
});
after(() => {
console.log("After #2");
});
});
И ситуация меняется, второй хук не выполняется:
$ mocha proba.js
scope
√ test
After #1
1) "after all" hook
1 passing (8ms)
1 failing
1) scope
"after all" hook:
Error: BOOM!
at Context.after (proba.js:5:15)
MochaJS
обрывает выполнение хуков при первом же исключении. Это приводит к тому, что попытка использовать несколько after
как независимые финалайзеры, провалится:
scope("My test", () => {
before(async () => {
await startProxy();
await launchBrowser();
});
it("some test #1", async () => {
await openSomeUrl();
});
it("some test #2", async () => {
await openAnotherUrl();
});
after(async () => await closeBrowser());
after(async () => await stopProxy());
});
Если произойдет исключение в closeBrowser()
, то хук c stopProxy()
не будет выполнен, и прокси останется висеть. Конечно можно сказать, что лучше выключать и включать прокси в хуке before
. Но это скорее workaround
, rootcause
в том, что MochaJS
не поддерживает независимое выполнение after
-хуков, как это н-р работает в финализации фикстур в pytest
. Но это легко исправить путем патча MochaJS
. Вот как это реализуется в GlaceJS
:
https://github.com/schipiga/glacejs/blob/master/lib/hacking.js#L87. После чего after
-хуки выполняются все, даже если в одном было порождено исключение.
Еще одна проблема, связанная с хуками в том, что если в хуке before
происходит исключение, то хук after
будет вызван (дефолтное поведение MochaJS
). И из-за невыполнения before
, в after
также может произойти исключение. Это немного портит анализ репортов. В GlaceJS
используется подход не выполнять стэп-финализатор, если стэп-инициатор не был выполнен. Н-р:
/* test example */
test("Some test", () => {
before(async () => {
await SS.launchBrowser();
});
chunk(async () => {
await SS.openApp();
});
after(async () => {
await SS.closeBrowser();
});
});
/* steps example */
var WebSteps = {
/**
* Step to launch browser. Step recall will be skipped if
* browser wasn't closed before.
*
* @method
* @instance
* @async
* @arg {object} [opts] - Step options.
* @arg {boolean} [opts.check=true] - Flag to check that browser was
* launched.
* @throws {AssertionError} - If browser wasn't launched.
*/
launchBrowser: async function (opts) {
if (this._isBrowserLaunched) {
logger.stepDebug("Step to launch browser was passed already");
return;
};
opts = U.defVal(opts, {});
var check = U.defVal(opts.check, true);
var hostname = os.hostname().toLowerCase();
var proxyOptions = [ "ignore-certificate-errors",
`proxy-server=${hostname}:${CONF.globalProxyPort}`,
`proxy-bypass-list=localhost,127.0.0.1,${hostname}` ];
var chromeArgs = this._webdriver.desiredCapabilities.chromeOptions.args;
for (var option of proxyOptions) {
if (this._isGlobalProxyStarted) {
if (!chromeArgs.includes(option)) {
chromeArgs.push(option);
};
} else {
if (chromeArgs.includes(option)) {
var idx = chromeArgs.indexOf(option);
chromeArgs.splice(idx, 1);
};
};
};
await this._webdriver.init();
if (check) {
expect(await this._webdriver.session(),
"Browser wasn't launched").to.exist;
};
this._isBrowserLaunched = true;
},
/**
* Step to close browser. Step will be skipped if browser wasn't launched
* before.
*
* @method
* @instance
* @async
* @arg {object} [opts] - Step options.
* @arg {boolean} [opts.check=true] - Flag to check that browser was closed.
* @throws {AssertionError} - If browser wasn't closed.
*/
closeBrowser: async function (opts) {
if (!this._isBrowserLaunched) {
logger.stepDebug("Step to launch browser wasn't passed yet");
return;
};
opts = U.defVal(opts, {});
opts.check = U.defVal(opts.check, true);
await this._webdriver.end();
await this.pause(1, "webdriver process will be stopped");
if (opts.check) {
expect(await this._webdriver.session(),
"Browser wasn't closed").to.not.exist;
};
this._isBrowserLaunched = false;
},
}