Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/worker/runner/test-runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { filterExtraStackFrames } = require("../../../browser/stacktrace/utils");
const { extendWithCodeSnippet } = require("../../../error-snippets");
const { startSelectivity } = require("../../../browser/cdp/selectivity");
const { noopProfilerRuntime } = require("../../../profiler/runtime/noop");
const { flushSessionManagerErrors } = require("@testplane/webdriverio");

const SNAPSHOTS_TIMEOUT_MS = 10000;
const SNAPSHOTS_WARNING_TIMEOUT_MS = 2000;
Expand Down Expand Up @@ -108,6 +109,25 @@ module.exports = class TestRunner {
async finishRun(error) {
const testplaneCtx = this._test.testplaneCtx;
const { callstackHistory } = this._browser;
let additionalContextError;

// Older WebdriverIO versions do not expose the test-boundary check.
if (typeof flushSessionManagerErrors === "function") {
try {
await flushSessionManagerErrors(this._browser.publicAPI);
} catch (contextError) {
// A failed context update may leave the session pointing at a stale frame.
// The master pool discards browsers marked as broken instead of reusing them.
if (!this._browser.state.isBroken) {
this._browser.markAsBroken({ stubBrowserCommands: true });
}
if (error && error !== contextError) {
additionalContextError = contextError;
} else {
error = contextError;
}
}
}

const assertViewResults = testplaneCtx.assertViewResults;
if (!error && assertViewResults && assertViewResults.hasFails()) {
Expand Down Expand Up @@ -172,6 +192,10 @@ module.exports = class TestRunner {
// The original cause must be removed to avoid possible duplicates later.
delete error.cause;

if (additionalContextError) {
error.stack += `\n\nAdditional context manager error: ${additionalContextError.stack}`;
}

await extendWithCodeSnippet(error);

throw Object.assign(error, results);
Expand Down
33 changes: 33 additions & 0 deletions test/src/worker/runner/test-runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe("worker/runner/test-runner", () => {
const sandbox = sinon.createSandbox();
let historyRunGroupStub;
let captureFailScreenshotStub;
let flushSessionManagerErrorsStub;
let TestRunner;

const mkTest_ = (opts = {}) => {
Expand Down Expand Up @@ -91,6 +92,7 @@ describe("worker/runner/test-runner", () => {
beforeEach(() => {
historyRunGroupStub = sandbox.stub().callsFake(history.runGroup);
captureFailScreenshotStub = sandbox.stub().resolves(null);
flushSessionManagerErrorsStub = sandbox.stub().resolves();

TestRunner = proxyquire("src/worker/runner/test-runner", {
"../../../browser/history": {
Expand All @@ -102,6 +104,9 @@ describe("worker/runner/test-runner", () => {
"./capture-fail-screenshot": {
captureFailScreenshot: captureFailScreenshotStub,
},
"@testplane/webdriverio": {
flushSessionManagerErrors: flushSessionManagerErrorsStub,
},
});

sandbox.stub(BrowserAgent.prototype, "getBrowser").resolves(mkBrowser_());
Expand Down Expand Up @@ -838,6 +843,21 @@ describe("worker/runner/test-runner", () => {
assert.calledOnceWith(BrowserAgent.prototype.freeBrowser, browser);
});

it("should fail on a context manager error after the last command", async () => {
const browser = mkBrowser_();
BrowserAgent.prototype.getBrowser.resolves(browser);
flushSessionManagerErrorsStub.rejects(new Error("late context failure"));

await assert.isRejected(run_(), "late context failure");

assert.calledOnceWithExactly(flushSessionManagerErrorsStub, browser.publicAPI);
assert.calledOnceWithExactly(BrowserAgent.prototype.freeBrowser, browser);
assert.callOrder(flushSessionManagerErrorsStub, BrowserAgent.prototype.freeBrowser);
assert.isTrue(browser.state.isLastTestFailed);
assert.calledOnceWithExactly(browser.markAsBroken, { stubBrowserCommands: true });
assert.isTrue(browser.state.isBroken);
});

it("should release browser after all browser data has been used", async () => {
ExecutionThread.create.callsFake(({ browser }) => {
ExecutionThread.prototype.run.callsFake(() => {
Expand Down Expand Up @@ -976,6 +996,19 @@ describe("worker/runner/test-runner", () => {
assert.calledOnceWith(BrowserAgent.prototype.freeBrowser, browser);
});

it("should retain the original test error when context manager also fails", async () => {
const browser = mkBrowser_();
BrowserAgent.prototype.getBrowser.resolves(browser);
ExecutionThread.prototype.run.rejects(new Error("test failure"));
flushSessionManagerErrorsStub.rejects(new Error("context failure"));

const error = await run_().catch(err => err);
assert.include(error.message, "test failure");
assert.include(error.stack, "context failure");
assert.calledOnce(flushSessionManagerErrorsStub);
assert.calledOnceWithExactly(browser.markAsBroken, { stubBrowserCommands: true });
});

[SAVE_HISTORY_MODE.ONLY_FAILED, SAVE_HISTORY_MODE.ALL].forEach(saveHistoryMode => {
it(`should pass history if "saveHistory" is ${saveHistoryMode}`, async () => {
const history = Symbol();
Expand Down
Loading