Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/pr-215.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -192,14 +192,17 @@ export default class WdioMochaTestFramework extends TestFramework {
const gitConfig = await getGitMetaData()
const filename = test.file // || this._suiteFile

const scopes = getMochaTestHierarchy(test)

const testData: Record<string, unknown> = {
[TestFrameworkConstants.KEY_TEST_ID]: getUniqueIdentifier(test, framework),
[TestFrameworkConstants.KEY_TEST_NAME]: test.title || test.description,
[TestFrameworkConstants.KEY_TEST_CODE]: test.body || '',
[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
Expand Down
6 changes: 5 additions & 1 deletion packages/browserstack-service/src/insights-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getGitMetaData,
getHookType, getPlatformVersion,
getScenarioExamples,
getTestTags,
getUniqueIdentifier,
getUniqueIdentifierForCucumber,
isBrowserstackSession,
Expand Down Expand Up @@ -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',
Expand All @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion packages/browserstack-service/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Expand Down
23 changes: 23 additions & 0 deletions packages/browserstack-service/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = /(?<![\w-])@[\w-]+/g

/**
* Mocha and Jasmine have no tag construct, so `@tag` tokens written into the suite and
* test titles are the tag source. The leading `@` is kept so these match the pickle tags
* the Cucumber path in this service already forwards; note the node SDK strips it for
* Jest/Playwright, so the two SDKs emit different shapes for the same logical tag.
*/
export function getTestTags(test: Frameworks.Test, scopes?: string[]): string[] {
const titles = [...(scopes ?? getMochaTestHierarchy(test)), test.title || test.description || '']
const tags: string[] = []
for (const title of titles) {
for (const tag of title.match(TEST_TAG_PATTERN) || []) {
if (!tags.includes(tag)) {
tags.push(tag)
}
}
}
return tags
}

/**
* Checks if the capabilities represent a multiremote configuration
* @param capabilities - The capabilities to check
Expand Down
53 changes: 53 additions & 0 deletions packages/browserstack-service/tests/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2282,3 +2282,56 @@ describe('getCentralUser', () => {
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'])
})
})
Loading