diff --git a/.changeset/pr-215.md b/.changeset/pr-215.md new file mode 100644 index 00000000..b7406483 --- /dev/null +++ b/.changeset/pr-215.md @@ -0,0 +1,5 @@ +--- +"@wdio/browserstack-service": patch +--- + +- Fixed test tags not being reported to Test Observability for Mocha and Jasmine. Tags written as `@tag` tokens in suite or test titles are now sent with each test. diff --git a/packages/browserstack-service/src/cli/frameworks/wdioMochaTestFramework.ts b/packages/browserstack-service/src/cli/frameworks/wdioMochaTestFramework.ts index 4c384b1f..b5706ee2 100644 --- a/packages/browserstack-service/src/cli/frameworks/wdioMochaTestFramework.ts +++ b/packages/browserstack-service/src/cli/frameworks/wdioMochaTestFramework.ts @@ -10,7 +10,7 @@ import TrackedInstance from '../instances/trackedInstance.js' import { TestFrameworkConstants } from './constants/testFrameworkConstants.js' import { BStackLogger as logger } from '../cliLogger.js' import type { Frameworks } from '@wdio/types' -import { getGitMetaData, getMochaTestHierarchy, getUniqueIdentifier, isUndefined, removeAnsiColors } from '../../util.js' +import { getGitMetaData, getMochaTestHierarchy, getTestTags, getUniqueIdentifier, isUndefined, removeAnsiColors } from '../../util.js' import { TEST_ANALYTICS_ID } from '../../constants.js' export default class WdioMochaTestFramework extends TestFramework { @@ -192,6 +192,8 @@ export default class WdioMochaTestFramework extends TestFramework { const gitConfig = await getGitMetaData() const filename = test.file // || this._suiteFile + const scopes = getMochaTestHierarchy(test) + const testData: Record = { [TestFrameworkConstants.KEY_TEST_ID]: getUniqueIdentifier(test, framework), [TestFrameworkConstants.KEY_TEST_NAME]: test.title || test.description, @@ -199,7 +201,8 @@ export default class WdioMochaTestFramework extends TestFramework { [TestFrameworkConstants.KEY_TEST_FILE_PATH]: (gitConfig?.root && filename) ? path.relative(gitConfig.root, filename) : undefined, [TestFrameworkConstants.KEY_TEST_LOCATION]: filename ? path.relative(process.cwd(), filename) : undefined, [TestFrameworkConstants.KEY_TEST_SCOPE]: fullTitle, - [TestFrameworkConstants.KEY_TEST_SCOPES]: getMochaTestHierarchy(test), + [TestFrameworkConstants.KEY_TEST_SCOPES]: scopes, + [TestFrameworkConstants.KEY_TEST_TAGS]: getTestTags(test, scopes), } return testData diff --git a/packages/browserstack-service/src/insights-handler.ts b/packages/browserstack-service/src/insights-handler.ts index 767b4d8e..ee2a23ae 100644 --- a/packages/browserstack-service/src/insights-handler.ts +++ b/packages/browserstack-service/src/insights-handler.ts @@ -15,6 +15,7 @@ import { getGitMetaData, getHookType, getPlatformVersion, getScenarioExamples, + getTestTags, getUniqueIdentifier, getUniqueIdentifierForCucumber, isBrowserstackSession, @@ -725,6 +726,8 @@ class _InsightsHandler { InsightsHandler.currentTest.name = test.title || test.description } + const scopes = this.getHierarchy(test) + const testData: TestData = { uuid: testMetaData.uuid, type: test.type || 'test', @@ -734,7 +737,8 @@ class _InsightsHandler { code: test.body }, scope: fullTitle, - scopes: this.getHierarchy(test), + scopes, + tags: getTestTags(test, scopes), identifier: fullTitle, file_name: filename ? path.relative(process.cwd(), filename) : undefined, location: filename ? path.relative(process.cwd(), filename) : undefined, diff --git a/packages/browserstack-service/src/reporter.ts b/packages/browserstack-service/src/reporter.ts index 88608e03..d9449827 100644 --- a/packages/browserstack-service/src/reporter.ts +++ b/packages/browserstack-service/src/reporter.ts @@ -17,7 +17,8 @@ import { getGitMetaData, removeAnsiColors, getHookType, - getPlatformVersion + getPlatformVersion, + getTestTags } from './util.js' import { BStackLogger } from './bstackLogger.js' import type { Capabilities } from '@wdio/types' @@ -270,6 +271,7 @@ class _TestReporter extends WDIOReporter { }, scope: scope, scopes: scopes, + tags: getTestTags(testStats as unknown as Frameworks.Test, scopes), identifier: identifier, file_name: suiteFileName ? path.relative(process.cwd(), suiteFileName) : undefined, location: suiteFileName ? path.relative(process.cwd(), suiteFileName) : undefined, diff --git a/packages/browserstack-service/src/util.ts b/packages/browserstack-service/src/util.ts index 8a14f3be..5e7c3a24 100644 --- a/packages/browserstack-service/src/util.ts +++ b/packages/browserstack-service/src/util.ts @@ -1937,6 +1937,29 @@ export function getMochaTestHierarchy(test: Frameworks.Test) { return value.reverse() } +// The lookbehind is load-bearing: without it every `@` starts a match, so an address +// like `user@example.com` in a title yields a bogus `@example` tag. +const TEST_TAG_PATTERN = /(? { expect(utils.getCentralUser()).toEqual({}) }) }) + +describe('getTestTags', () => { + const tagsFor = (title: string, scopes: string[] = []) => + utils.getTestTags({ title } as any, scopes) + + it('picks up a tag in the test title', () => { + expect(tagsFor('logs in @smoke')).toEqual(['@smoke']) + }) + + it('picks up a tag from the describe scope', () => { + expect(tagsFor('logs in', ['auth @regression'])).toEqual(['@regression']) + }) + + it('merges scope and title tags, deduped', () => { + expect(tagsFor('logs in @smoke', ['auth @smoke', 'nested @regression'])) + .toEqual(['@smoke', '@regression']) + }) + + it('returns an empty array when nothing is tagged', () => { + expect(tagsFor('logs in', ['auth'])).toEqual([]) + }) + + it('picks up multiple tags from one title', () => { + expect(tagsFor('logs in @smoke @p1')).toEqual(['@smoke', '@p1']) + }) + + it('keeps hyphens in a tag', () => { + expect(tagsFor('logs in @smoke-test')).toEqual(['@smoke-test']) + }) + + it('falls back to the Jasmine description when there is no title', () => { + expect(utils.getTestTags({ description: 'logs in @jasmine' } as any, [])).toEqual(['@jasmine']) + }) + + it('ignores an @ embedded in a larger token', () => { + expect(tagsFor('sends the invite to user@example.com')).toEqual([]) + expect(tagsFor('installs pkg@1.2.3')).toEqual([]) + }) + + it('derives scopes from the mocha hierarchy when none are supplied', () => { + const test = { + title: 'logs in @smoke', + ctx: { test: {} }, + parent: { title: 'auth @regression', parent: { title: '' } } + } + expect(utils.getTestTags(test as any)).toEqual(['@regression', '@smoke']) + }) + + it('derives scopes from the jasmine hierarchy when none are supplied', () => { + const test = { description: 'logs in @smoke', fullName: 'auth @regression logs in @smoke' } + expect(utils.getTestTags(test as any)).toEqual(['@regression', '@smoke']) + }) +})