diff --git a/.changeset/pr-131.md b/.changeset/pr-131.md new file mode 100644 index 0000000..b94fcf0 --- /dev/null +++ b/.changeset/pr-131.md @@ -0,0 +1,5 @@ +--- +"@wdio/browserstack-service": patch +--- + +- Fixed App Automate and Automate session names staying on the static `sessionName` capability instead of the test title, for suites that reload the session between tests or whose run ends before the WebdriverIO `after` hook. diff --git a/packages/browserstack-service/src/cli/modules/automateModule.ts b/packages/browserstack-service/src/cli/modules/automateModule.ts index 1a0aa04..712b8b2 100644 --- a/packages/browserstack-service/src/cli/modules/automateModule.ts +++ b/packages/browserstack-service/src/cli/modules/automateModule.ts @@ -25,6 +25,7 @@ interface TestResult { interface SessionData { lastTestName: string + appliedName?: string // last name successfully PUT for this session, for de-duping testResults: Map // testName -> TestResult } @@ -95,6 +96,42 @@ export default class AutomateModule extends BaseModule { } TestFramework.setState(instace, TestFrameworkConstants.KEY_AUTOMATE_SESSION_NAME, name) + + // SDK-7270: name the session NOW, while it is still the live session, instead of + // relying solely on the onAfterExecute sweep at worker teardown. Sessions are closed + // as soon as the suite reloads them (`browser.reloadSession()` per test), and a worker + // that never reaches `after()` — interrupted run, hard exit, crash — never fires + // onAfterExecute at all, leaving every session on the creation-time `sessionName` + // capability. Restores the pre-9.27 behaviour, where the rename was issued per test. + await this.flushSessionName(sessionId) + } + + /** + * PUT the session's current name if it has not already been applied. + * De-duped via `appliedName` so the onAfterExecute sweep does not re-send it. + */ + private async flushSessionName(sessionId: string): Promise { + const testContextOptions = this.config.testContextOptions as TestContextOptions + if (testContextOptions.skipSessionName) { + return + } + + if (!sessionId) { + return + } + + const sessionData = this.sessionMap.get(sessionId) + if (!sessionData || !sessionData.lastTestName || sessionData.appliedName === sessionData.lastTestName) { + return + } + + const name = sessionData.lastTestName + await this.markSessionName(sessionId, name, { + user: this.config.userName as string, + key: this.config.accessKey as string + }) + sessionData.appliedName = name + this.sessionMap.set(sessionId, sessionData) } async onAfterTest(args: Record) { @@ -180,9 +217,8 @@ export default class AutomateModule extends BaseModule { } } - if (!testContextOptions.skipSessionName) { - await this.markSessionName(sessionId, sessionData.lastTestName, { user: userName, key: accessKey }) - } + // Final sweep — a no-op for sessions already named per-test in onBeforeTest. + await this.flushSessionName(sessionId) if (!testContextOptions.skipSessionStatus) { await this.markSessionStatus(sessionId, sessionStatus, failureReason, { user: userName, key: accessKey })