diff --git a/docs/suite.md b/docs/suite.md index 7629a778..8877d0e3 100644 --- a/docs/suite.md +++ b/docs/suite.md @@ -176,7 +176,7 @@ suite.scenario("User login flow", "appium", { deviceName: "Name of device", platformName: "Android" | "iOS", automationName: "Uiautomator2" | "XCUITest" | "Espresso", - app: "/path/to/apk/or/ipa" + app: "/path/to/apk/or/ipa", }); ``` @@ -334,6 +334,19 @@ The execution options specified from the command line arguments or defaults. Thi const baseDomain = suite.executionOptions.environment?.defaultDomain; ``` +### maxSuiteDuration: number + +How long a suite will run before timing out. The default is 60000 ( one minute ) + +```javascript +const suite = flagpole("A very long suite"); + +// this suite can run for two minutes +suite.maxSuiteDuration = 120000; + +suite.browser("Interact with the browser for a while"); +``` + ### title: string The title of this suite, which is specified in the constructor. diff --git a/flagpole-should-fail.json b/flagpole-should-fail.json index e9d2e0d6..957aff01 100644 --- a/flagpole-should-fail.json +++ b/flagpole-should-fail.json @@ -25,6 +25,16 @@ "id": "", "name": "timeout", "tags": [] + }, + "dangle": { + "id": "", + "name": "dangle", + "tags": [] + }, + "noNext": { + "id": "", + "name": "noNext", + "tags": [] } } } diff --git a/src/interfaces.ts b/src/interfaces.ts index 4eb4f522..0eeb4a91 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -730,6 +730,7 @@ export interface iSuite { totalDuration: number | null; executionDuration: number | null; maxScenarioDuration: number; + maxSuiteDuration: number; concurrencyLimit: number; title: string; finished: Promise; diff --git a/src/suite.ts b/src/suite.ts index d3f3ad19..48c4059d 100644 --- a/src/suite.ts +++ b/src/suite.ts @@ -191,6 +191,25 @@ export class Suite implements iSuite { this._taskManager.maxScenarioDuration = timeoutMs; } + /** + * If a suite hasn't completed in this period of time, cut the scenarios off + * + * @param timeout + * @returns + */ + public setMaxSuiteDuration(timeout: number): iSuite { + this._taskManager.maxSuiteDuration = timeout; + return this; + } + + public get maxSuiteDuration(): number { + return this._taskManager.maxSuiteDuration; + } + + public set maxSuiteDuration(timeoutMs: number) { + this._taskManager.maxSuiteDuration = timeoutMs; + } + /** * Print all logs to console * diff --git a/src/suitetaskmanager.ts b/src/suitetaskmanager.ts index cfb1bc2a..6c008d69 100644 --- a/src/suitetaskmanager.ts +++ b/src/suitetaskmanager.ts @@ -38,7 +38,7 @@ export class SuiteTaskManager { private _statusCallbacks: SuiteStatusCallback[] = []; private _concurrencyLimit: number = 99; private _maxScenarioDuration: number = 30000; - private _maxTimeToWaitForPendingScenariosToBeReady = 30000; + private _maxSuiteDuration: number = 60000; private _finishedPromise: Promise; private _finishedResolve = () => {}; @@ -54,6 +54,14 @@ export class SuiteTaskManager { this._maxScenarioDuration = value; } + public get maxSuiteDuration(): number { + return this._maxSuiteDuration; + } + + public set maxSuiteDuration(value: number) { + this._maxSuiteDuration = value; + } + public get concurrencyLimit(): number { return this._concurrencyLimit; } @@ -343,6 +351,17 @@ export class SuiteTaskManager { // If we have some pending + we didn't start any new ones, kill them // Important! Don't finish it off right away, there may be something pending in a few milliseconds runAsync(() => { + if ( + this.scenariosWaitingToExecute.length > 0 && + this._dateExecutionBegan && + Date.now() - this._dateExecutionBegan > this._maxSuiteDuration + ) { + resolve( + this._cancelPendingScenarios( + "Suite timed out! This scenario was never called or had no assertions." + ) + ); + } // If there are more scenarios now ready to execute, do those if (this.scenariosWaitingToExecute.length > 0) { return execute(); diff --git a/tests/src/dangle.ts b/tests/src/dangle.ts new file mode 100644 index 00000000..d9cfd266 --- /dev/null +++ b/tests/src/dangle.ts @@ -0,0 +1,18 @@ +import flagpole from "../../dist"; + +const suite = flagpole("Scenario with no open method"); + +suite.maxSuiteDuration = 5000; + +suite + .scenario("Scenario should timeout", "html") + .open("https://dribbble.com/") + .next((context) => { + context.assert(context.response.statusCode).equals(200); + throw "foo"; + scenarioB.open("https://www.dezeen.com/"); + }); + +const scenarioB = suite.scenario("Scenario B", "html").next((context) => { + context.assert(context.response.statusCode).equals(200); +}); diff --git a/tests/src/noNext.ts b/tests/src/noNext.ts new file mode 100644 index 00000000..e23e256a --- /dev/null +++ b/tests/src/noNext.ts @@ -0,0 +1,7 @@ +import flagpole from "../../dist"; + +const suite = flagpole("No .next will dangle"); + +suite.maxSuiteDuration = 3000; + +suite.html("Homepage Loads").open("https://dribbble.com/");