import path from 'node:path'
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
import logger from '@wdio/logger'
import BrowserstackService from '../src/service.js'
import * as utils from '../src/util.js'
import InsightsHandler from '../src/insights-handler.js'
import * as bstackLogger from '../src/bstackLogger.js'
const jasmineSuiteTitle = 'Jasmine__TopLevel__Suite'
const sessionBaseUrl = 'https://api.browserstack.com/automate/sessions'
const sessionId = 'session123'
const sessionIdA = 'session456'
vi.mock('fetch')
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
vi.useFakeTimers().setSystemTime(new Date('2020-01-01'))
vi.mock('uuid', () => ({ v4: () => '123456789' }))
const bstackLoggerSpy = vi.spyOn(bstackLogger.BStackLogger, 'logToFile')
bstackLoggerSpy.mockImplementation(() => {})
const log = logger('test')
let service: BrowserstackService
let browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
const user = 'foo'
const key = 'bar'
const encodedAuth = Buffer.from(`${user}:${key}`, 'utf8').toString('base64')
const headers: any = {
'Content-Type': 'application/json; charset=utf-8',
Authorization: `Basic ${encodedAuth}`,
}
beforeEach(() => {
vi.mocked(log.info).mockClear()
vi.mocked(fetch).mockClear()
vi.mocked(fetch).mockReturnValue(Promise.resolve(Response.json({ automation_session: {
browser_url: 'https://www.browserstack.com/automate/builds/1/sessions/2'
} })))
browser = {
execute: vi.fn(),
executeScript: vi.fn(),
on: vi.fn(),
sessionId: sessionId,
config: {},
capabilities: {
device: '',
os: 'OS X',
os_version: 'Sierra',
browserName: 'chrome'
},
instances: ['browserA', 'browserB'],
isMultiremote: false,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {
sessionId: sessionIdA,
capabilities: {
'bstack:options': {
device: '',
os: 'Windows',
osVersion: 10,
browserName: 'chrome'
}
}
},
browserB: {},
} as unknown as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
service = new BrowserstackService({ testObservability: false } as any, [] as any, { user: 'foo', key: 'bar' } as any)
})
function assertMethodCalls(mock: { mock: { calls: any[] } }, expectedMethod: any, expectedCallCount: any) {
const matchingCalls = mock.mock.calls.filter(
([, options]) => options.method === expectedMethod
)
expect(matchingCalls.length).toBe(expectedCallCount)
}
it('should initialize correctly', () => {
service = new BrowserstackService({} as any, [] as any, {} as any)
expect(service['_failReasons']).toEqual([])
})
describe('onReload()', () => {
it('should update and get session', async () => {
const isBrowserstackSessionSpy = vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
const updateSpy = vi.spyOn(service, '_update')
service['_browser'] = browser
await service.onReload('1', '2')
expect(updateSpy).toHaveBeenCalled()
expect(vi.mocked(fetch).mock.calls[0][1]?.method).toEqual('PUT')
expect(isBrowserstackSessionSpy).toHaveBeenCalled()
})
it('should update and get multiremote session', async () => {
const isBrowserstackSessionSpy = vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
browser.isMultiremote = true as any
service['_browser'] = browser
const updateSpy = vi.spyOn(service, '_update')
await service.onReload('1', '2')
expect(updateSpy).toHaveBeenCalled()
expect(vi.mocked(fetch).mock.calls[0][1]?.method).toEqual('PUT')
expect(isBrowserstackSessionSpy).toHaveBeenCalled()
})
it('should reset failures', async () => {
const updateSpy = vi.spyOn(service, '_update')
service['_browser'] = browser
service['_failReasons'] = ['Custom Error: Button should be enabled', 'Expected something']
await service.onReload('1', '2')
expect(updateSpy).toHaveBeenCalledWith('1', {
status: 'failed',
reason: 'Custom Error: Button should be enabled' + '\n' + 'Expected something'
})
expect(service['_failReasons']).toEqual([])
})
it('should return if no browser object', async () => {
const updateSpy = vi.spyOn(service, '_update')
service['_browser'] = undefined
await service.onReload('1', '2')
expect(updateSpy).toBeCalledTimes(0)
})
it ('should not reset suiteTitle', async() => {
const updateSpy = vi.spyOn(service, '_update')
service['_browser'] = browser
service['_failReasons'] = []
service['_suiteTitle'] = 'my suite title'
await service.onReload('1', '2')
expect(updateSpy).toHaveBeenCalledWith('1', {
status: 'passed',
})
expect(service['_suiteTitle']).toEqual('my suite title')
})
})
describe('beforeSession', () => {
describe('testObservabilityOpts not passed', () => {
it('should set some default to make missing user and key parameter apparent', () => {
service.beforeSession({} as any)
expect(service['_config']).toEqual({ user: 'NotSetUser', key: 'NotSetKey' })
})
it('should set username default to make missing user parameter apparent', () => {
service.beforeSession({ user: 'foo' } as any)
expect(service['_config']).toEqual({ user: 'foo', key: 'NotSetKey' })
})
it('should set key default to make missing key parameter apparent', () => {
service.beforeSession({ key: 'bar' } as any)
expect(service['_config']).toEqual({ user: 'NotSetUser', key: 'bar' })
})
})
describe('testObservabilityOpts passed', () => {
it('should not set some default value if user and key in observability options', () => {
const observabilityService = new BrowserstackService(
{
testObservability: true,
testObservabilityOptions: {
user: 'foo',
key: 'random',
}
} as any,
[] as any,
{ user: 'foo', key: 'bar' } as any
)
observabilityService.beforeSession({} as any)
expect(observabilityService['_config']).toEqual({ user: undefined, key: undefined })
})
it('should set set some default value if user and key not in observability options', () => {
const observabilityService = new BrowserstackService(
{
testObservability: true,
testObservabilityOptions: {}
} as any,
[] as any,
{ user: 'foo', key: 'bar' } as any
)
observabilityService.beforeSession({} as any)
expect(observabilityService['_config']).toEqual({ user: 'NotSetUser', key: 'NotSetKey' })
})
})
})
describe('_multiRemoteAction', () => {
it('resolve if no browser object', () => {
const tmpService = new BrowserstackService({ testObservability: false }, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
tmpService['_browser'] = undefined
expect(tmpService._multiRemoteAction({} as any)).toEqual(Promise.resolve())
})
})
describe('_update', () => {
describe('should call fetch with put method', () => {
const getCloudProviderSpy = vi.spyOn(utils, 'getCloudProvider').mockReturnValue('browserstack')
beforeEach(() => {
getCloudProviderSpy.mockClear()
})
it('should resolve if not a browserstack session', () => {
service['_browser'] = browser
service._update('sessionId', {})
expect(vi.mocked(fetch).mock.calls[0][1]?.method).toEqual('PUT')
})
afterEach(() => {
getCloudProviderSpy.mockClear()
})
})
})
describe('_printSessionURL', () => {
it('should get and log session details', async () => {
browser.isMultiremote = false
service['_browser'] = browser
const logInfoSpy = vi.spyOn(log, 'info').mockImplementation((string) => string)
const isBrowserstackSessionSpy = vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
await service._printSessionURL()
expect(fetch).toHaveBeenCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{ method: 'GET', headers })
expect(logInfoSpy).toHaveBeenCalled()
expect(logInfoSpy).toHaveBeenCalledWith(
'OS X Sierra chrome session: https://www.browserstack.com/automate/builds/1/sessions/2'
)
expect(isBrowserstackSessionSpy).toHaveBeenCalled()
})
it('should get and log multi remote session details', async () => {
browser.isMultiremote = true as any
service['_browser'] = browser
const logInfoSpy = vi.spyOn(log, 'info').mockImplementation((string) => string)
const isBrowserstackSessionSpy = vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
await service._printSessionURL()
expect(fetch).toHaveBeenCalledWith(`${sessionBaseUrl}/${sessionIdA}.json`, { method: 'GET', headers })
expect(logInfoSpy).toHaveBeenCalled()
expect(logInfoSpy).toHaveBeenCalledWith(
'Windows 10 chrome session: https://www.browserstack.com/automate/builds/1/sessions/2'
)
expect(isBrowserstackSessionSpy).toHaveBeenCalled()
})
describe('if cant print', () => {
describe('no browser object', () => {
const logInfoSpy = vi.spyOn(log, 'info').mockImplementation((string) => string)
const isBrowserstackSessionSpy = vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
beforeEach(() => {
logInfoSpy.mockClear()
isBrowserstackSessionSpy.mockClear()
})
it('should resolve if not no browser object', async () => {
service['_browser'] = undefined
await service._printSessionURL()
expect(isBrowserstackSessionSpy).toBeCalledTimes(0)
expect(logInfoSpy).toBeCalledTimes(0)
})
afterEach(() => {
logInfoSpy.mockClear()
isBrowserstackSessionSpy.mockClear()
})
})
})
})
describe('_printSessionURL Appium', () => {
beforeEach(() => {
vi.mocked(fetch).mockReturnValueOnce(Promise.resolve(Response.json({ automation_session: {
name: 'Smoke Test',
duration: 65,
os: 'ios',
os_version: '12.1',
browser_version: 'app',
browser: null,
device: 'iPhone XS',
status: 'failed',
reason: 'CLIENT_STOPPED_SESSION',
browser_url: 'https://app-automate.browserstack.com/builds/1/sessions/2'
} })))
browser.capabilities = {
device: 'iPhone XS',
os: 'iOS',
os_version: '12.1',
browserName: '',
}
})
it('should get and log session details', async () => {
service['_browser'] = browser
await service._printSessionURL()
expect(log.info).toHaveBeenCalled()
expect(log.info).toHaveBeenCalledWith(
'iPhone XS iOS 12.1 session: https://app-automate.browserstack.com/builds/1/sessions/2'
)
})
})
describe('_printSessionURL TurboScale', () => {
beforeEach(() => {
vi.mocked(fetch).mockReturnValueOnce(Promise.resolve(Response.json({
name: 'Smoke Test',
duration: 65,
browser_version: '116',
browser: 'chrome',
status: 'failed',
reason: 'CLIENT_STOPPED_SESSION',
url: 'https://grid.browserstack.com/dashboard/builds/1/sessions/2'
})))
browser.capabilities = {
browserName: 'chrome',
browserVersion: '116'
}
})
it('should get and log session details', async () => {
service['_browser'] = browser
service['_turboScale'] = true
await service._printSessionURL()
expect(log.info).toHaveBeenCalledWith(
'chrome 116 session: https://grid.browserstack.com/dashboard/builds/1/sessions/2'
)
})
})
describe('before', () => {
it('should set auth to default values if not provided', async () => {
let service = new BrowserstackService({} as any, [{}] as any, { capabilities: {} })
await service.beforeSession({} as unknown as any)
await service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_config'].user).toEqual('NotSetUser')
expect(service['_config'].key).toEqual('NotSetKey')
service = new BrowserstackService({} as any, [{}] as any, { capabilities: {} })
service.beforeSession({ user: 'blah' } as unknown as any)
await service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_config'].user).toEqual('blah')
expect(service['_config'].key).toEqual('NotSetKey')
service = new BrowserstackService({} as any, [{}] as any, { capabilities: {} })
service.beforeSession({ key: 'blah' } as unknown as any)
await service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_config'].user).toEqual('NotSetUser')
expect(service['_config'].key).toEqual('blah')
})
it('should initialize correctly', () => {
const service = new BrowserstackService({} as any, [{}] as any, {
user: 'foo',
key: 'bar',
capabilities: {}
})
service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_sessionBaseUrl']).toEqual(sessionBaseUrl)
})
it('should initialize correctly for multiremote', () => {
const service = new BrowserstackService(
{} as any,
[{}] as any,
{
user: 'foo',
key: 'bar',
capabilities: [{}]
}
)
service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_sessionBaseUrl']).toEqual(sessionBaseUrl)
})
it('should initialize correctly for appium', () => {
const service = new BrowserstackService(
{} as any,
[{ app: 'test-app' }] as any,
{
user: 'foo',
key: 'bar',
capabilities: {
app: 'test-app'
} as any
}
)
browser.capabilities = {
'appium:app': 'test-app',
device: 'iPhone XS',
os: 'iOS',
os_version: '12.1',
browserName: '',
}
service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_sessionBaseUrl']).toEqual('https://api-cloud.browserstack.com/app-automate/sessions')
})
it('should initialize correctly for appium without global browser capabilities', () => {
const service = new BrowserstackService({} as any, {
'appium:app': 'bs://BrowserStackMobileAppId'
}, {
user: 'foo',
key: 'bar',
capabilities: {
app: 'test-app' as any
}
})
service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_sessionBaseUrl']).toEqual('https://api-cloud.browserstack.com/app-automate/sessions')
})
it('should initialize correctly for appium if using valid W3C Webdriver capabilities', () => {
const service = new BrowserstackService({} as any, {
'appium:app': 'bs://BrowserStackMobileAppId'
}, {
user: 'foo',
key: 'bar',
capabilities: {
['appium:app']: 'test-app'
} as any
})
service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_sessionBaseUrl']).toEqual('https://api-cloud.browserstack.com/app-automate/sessions')
})
it('should log the url', async () => {
const service = new BrowserstackService({} as any, [{}] as any, { capabilities: {} })
await service.before(service['_config'] as any, [], browser)
expect(log.info).toHaveBeenCalled()
expect(log.info).toHaveBeenCalledWith(
'OS X Sierra chrome session: https://www.browserstack.com/automate/builds/1/sessions/2')
})
it('should initialize correctly for turboScale when option passed', () => {
const service = new BrowserstackService({
turboScale: true
} as any, {}, {
user: 'foo',
key: 'bar',
capabilities: {}
})
service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
expect(service['_sessionBaseUrl']).toEqual('https://api.browserstack.com/automate-turboscale/v1/sessions')
})
it('should initialize correctly for turboScale when env var is set', () => {
process.env.BROWSERSTACK_TURBOSCALE = 'true'
const service = new BrowserstackService({} as any, {}, {
user: 'foo',
key: 'bar',
capabilities: {}
})
service.before(service['_config'] as any, [], browser)
delete process.env.BROWSERSTACK_TURBOSCALE
expect(service['_failReasons']).toEqual([])
expect(service['_sessionBaseUrl']).toEqual('https://api.browserstack.com/automate-turboscale/v1/sessions')
})
})
describe('beforeHook', () => {
service = new BrowserstackService({}, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
it('call insightsHandler.beforeHook', () => {
service['_insightsHandler'] = new InsightsHandler(browser)
const methodSpy = vi.spyOn(service['_insightsHandler'], 'beforeHook')
service.beforeHook({ title: 'foo2', parent: 'bar2' } as any,
{} as any)
expect(methodSpy).toBeCalled()
})
})
describe('afterHook', () => {
service = new BrowserstackService({}, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
it('call insightsHandler.afterHook', () => {
service['_insightsHandler'] = new InsightsHandler(browser)
const methodSpy = vi.spyOn(service['_insightsHandler'], 'afterHook')
service.afterHook({ title: 'foo2', parent: 'bar2' } as any,
undefined as never, {} as any)
expect(methodSpy).toBeCalled()
})
})
describe('beforeStep', () => {
const service = new BrowserstackService({}, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
it('call insightsHandler.beforeStep', () => {
vi.spyOn(utils, 'getUniqueIdentifierForCucumber').mockReturnValue('test title')
service['_insightsHandler'] = new InsightsHandler(browser)
const methodSpy = vi.spyOn(service['_insightsHandler'], 'beforeStep')
service.beforeStep({ keyword: 'Given', text: 'this is a test' } as any,
undefined as never)
expect(methodSpy).toBeCalled()
})
})
describe('afterStep', () => {
const service = new BrowserstackService({}, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
it('call insightsHandler.afterStep', () => {
vi.spyOn(utils, 'getUniqueIdentifierForCucumber').mockReturnValue('test title')
service['_insightsHandler'] = new InsightsHandler(browser)
const methodSpy = vi.spyOn(service['_insightsHandler'], 'afterStep')
service.afterStep({ title: 'foo2', parent: 'bar2' } as any,
undefined as never, {} as any)
expect(methodSpy).toBeCalled()
})
})
describe('beforeScenario', () => {
const service = new BrowserstackService({}, [] as any, { user: 'foo', key: 'bar' } as any)
it('call insightsHandler.beforeScenario', async () => {
service['_insightsHandler'] = new InsightsHandler(browser)
service['_accessibilityHandler'] = undefined
vi.spyOn(utils, 'getUniqueIdentifierForCucumber').mockReturnValue('test title')
const methodSpy = vi.spyOn(service['_insightsHandler'], 'beforeScenario')
await service.beforeScenario({ pickle: { name: '', tags: [] }, gherkinDocument: { uri: '', feature: { name: '', description: '' } } } as any)
expect(methodSpy).toBeCalled()
})
})
describe('beforeSuite', () => {
it('should send request to set the session name as suite name for Mocha tests', async () => {
await service.before(service['_config'] as any, [], browser)
expect(service['_suiteTitle']).toBeUndefined()
expect(service['_fullTitle']).toBeUndefined()
await service.beforeSuite({ title: 'foobar' } as any)
expect(service['_suiteTitle']).toBe('foobar')
expect(service['_fullTitle']).toBe('foobar')
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'foobar' }),
headers
}
)
})
it('should not send request to set the session name as suite name for Jasmine tests', async () => {
await service.before(service['_config'] as any, [], browser)
expect(service['_suiteTitle']).toBeUndefined()
expect(service['_fullTitle']).toBeUndefined()
await service.beforeSuite({ title: jasmineSuiteTitle } as any)
expect(service['_suiteTitle']).toBe(jasmineSuiteTitle)
expect(service['_fullTitle']).toBeUndefined()
expect(fetch).not.toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
method: 'POST',
}))
})
it('should not send request to set the session name if option setSessionName is false', async () => {
const service = new BrowserstackService({ setSessionName: false } as any, [] as any, { user: 'foo', key: 'bar' } as any)
await service.beforeSuite({ title: 'Project Title' } as any)
expect(fetch).not.toBeCalled()
})
})
describe('beforeTest', () => {
it('should not send request to set the session name if option setSessionName is false', async () => {
const service = new BrowserstackService({ setSessionName: false } as any, [] as any, { user: 'foo', key: 'bar' } as any)
await service.beforeSuite({ title: 'Project Title' } as any)
await service.beforeTest({ title: 'Test Title', parent: 'Suite Title' } as any)
expect(fetch).not.toBeCalled()
})
describe('sessionNamePrependTopLevelSuiteTitle is true', () => {
it('should set title for Mocha tests using concatenation of top level suite name, innermost suite name, and test title', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
const service = new BrowserstackService({ sessionNamePrependTopLevelSuiteTitle: true } as any, [] as any, { user: 'foo', key: 'bar' } as any)
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeSuite({ title: 'Project Title' } as any)
expect(service['_fullTitle']).toBe('Project Title')
await service.beforeTest({ title: 'Test Title', parent: 'Suite Title' } as any)
expect(service['_fullTitle']).toBe('Project Title - Suite Title - Test Title')
assertMethodCalls(vi.mocked(fetch), 'PUT', 2)
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'Project Title' }),
headers
}
)
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'Project Title - Suite Title - Test Title' }),
headers
}
)
})
})
describe('sessionNameOmitTestTitle is true', () => {
beforeEach(() => {
service = new BrowserstackService({ sessionNameOmitTestTitle: true } as any, [] as any, { user: 'foo', key: 'bar' } as any)
})
it('should not set title for Mocha tests', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeSuite({ title: 'Suite Title' } as any)
expect(service['_fullTitle']).toBe('Suite Title')
await service.beforeTest({ title: 'bar', parent: 'Suite Title' } as any)
expect(service['_fullTitle']).toBe('Suite Title')
await service.afterTest({ title: 'bar', parent: 'Suite Title' } as any, undefined as never, {} as any)
expect(service['_fullTitle']).toBe('Suite Title')
assertMethodCalls(vi.mocked(fetch), 'PUT', 1)
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'Suite Title' }),
headers
}
)
})
})
describe('sessionNamePrependTopLevelSuiteTitle is true, sessionNameOmitTestTitle is true', () => {
beforeEach(() => {
service = new BrowserstackService({ sessionNameOmitTestTitle: true, sessionNamePrependTopLevelSuiteTitle: true } as any, [] as any, { user: 'foo', key: 'bar' } as any)
})
it('should set title for Mocha tests using concatenation of top level suite name and innermost suite name', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeSuite({ title: 'Project Title' } as any)
expect(service['_fullTitle']).toBe('Project Title')
await service.beforeTest({ title: 'Test Title', parent: 'Suite Title' } as any)
expect(service['_fullTitle']).toBe('Project Title - Suite Title')
assertMethodCalls(vi.mocked(fetch), 'PUT', 2)
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'Project Title' }),
headers
}
)
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'Project Title - Suite Title' }),
headers
}
)
})
})
describe('sessionNameFormat is defined', () => {
beforeEach(() => {
service = new BrowserstackService({
sessionNameFormat: (config, caps, suiteTitle, testTitle) => {
if (testTitle) {
return `${config.region} - ${(caps as any).browserName} - ${suiteTitle} - ${testTitle}`
}
return `${config.region} - ${(caps as any).browserName} - ${suiteTitle}`
}
} as any, {
browserName: 'foobar'
}, {
user: 'foo',
key: 'bar',
region: 'barfoo'
} as any)
})
it('should set title via sessionNameFormat method', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
service['_browser'] = browserWithExecuteScript
service['_suiteTitle'] = 'Suite Title'
await service.beforeSuite({ title: 'Suite Title' } as any)
expect(service['_fullTitle']).toBe('barfoo - foobar - Suite Title')
await service.beforeTest({ title: 'Test Title', parent: 'Suite Title' } as any)
expect(service['_fullTitle']).toBe('barfoo - foobar - Suite Title - Test Title')
assertMethodCalls(vi.mocked(fetch), 'PUT', 2)
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'barfoo - foobar - Suite Title' }),
headers
}
)
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'barfoo - foobar - Suite Title - Test Title' }),
headers
}
)
})
})
describe('Jasmine only', () => {
it('should set suite name of first test as title', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeSuite({ title: jasmineSuiteTitle } as any)
await service.beforeTest({ fullName: 'foo bar baz', description: 'baz' } as any)
service.afterTest({ fullName: 'foo bar baz', description: 'baz' } as any, undefined as never, {} as any)
expect(service['_fullTitle']).toBe('foo bar')
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'foo bar' }),
headers
}
)
})
it('should set parent suite name as title', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeSuite({ title: jasmineSuiteTitle } as any)
await service.beforeTest({ fullName: 'foo bar baz', description: 'baz' } as any)
await service.beforeTest({ fullName: 'foo xyz', description: 'xyz' } as any)
service.afterTest({ fullName: 'foo bar baz', description: 'baz' } as any, undefined as never, {} as any)
service.afterTest({ fullName: 'foo xyz', description: 'xyz' } as any, undefined as never, {} as any)
expect(service['_fullTitle']).toBe('foo')
expect(fetch).toBeCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{
method: 'PUT',
body: JSON.stringify({ name: 'foo' }),
headers
}
)
})
})
})
describe('afterTest', () => {
it('should increment failure reasons on fails', () => {
service.before(service['_config'] as any, [], browser)
service['_fullTitle'] = ''
service.beforeSuite({ title: 'foo' } as any)
service.beforeTest({ title: 'foo', parent: 'bar' } as any)
service.afterTest(
{ title: 'foo', parent: 'bar' } as any,
undefined as never,
{ error: { message: 'cool reason' }, result: 1, duration: 5, passed: false } as any)
expect(service['_failReasons']).toContain('cool reason')
service.beforeTest({ title: 'foo2', parent: 'bar2' } as any)
service.afterTest(
{ title: 'foo2', parent: 'bar2' } as any,
undefined as never,
{ error: { message: 'not so cool reason' }, result: 1, duration: 7, passed: false } as any)
expect(service['_failReasons']).toHaveLength(2)
expect(service['_failReasons']).toContain('cool reason')
expect(service['_failReasons']).toContain('not so cool reason')
service.beforeTest({ title: 'foo3', parent: 'bar3' } as any)
service.afterTest(
{ title: 'foo3', parent: 'bar3' } as any,
undefined as never,
{ error: undefined, result: 1, duration: 7, passed: false } as any)
expect(service['_fullTitle']).toBe('bar3 - foo3')
expect(service['_failReasons']).toHaveLength(3)
expect(service['_failReasons']).toContain('cool reason')
expect(service['_failReasons']).toContain('not so cool reason')
expect(service['_failReasons']).toContain('Unknown Error')
})
it('should not increment failure reasons on passes', () => {
service.before(service['_config'] as any, [], browser)
service.beforeSuite({ title: 'foo' } as any)
service.beforeTest({ title: 'foo', parent: 'bar' } as any)
service.afterTest(
{ title: 'foo', parent: 'bar' } as any,
undefined as never,
{ error: { message: 'cool reason' }, result: 1, duration: 5, passed: true } as any)
expect(service['_failReasons']).toEqual([])
service.beforeTest({ title: 'foo2', parent: 'bar2' } as any)
service.afterTest(
{ title: 'foo2', parent: 'bar2' } as any,
undefined as never,
{ error: { message: 'not so cool reason' }, result: 1, duration: 5, passed: true } as any)
expect(service['_fullTitle']).toBe('bar2 - foo2')
expect(service['_failReasons']).toEqual([])
})
})
describe('afterScenario', () => {
it('should increment failure reasons on non-passing statuses (strict mode off)', () => {
service = new BrowserstackService({ testObservability: false } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
expect(service['_failReasons']).toEqual([])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'SKIPPED' } })
expect(service['_failReasons']).toEqual([])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'FAILED', message: 'I am Error, most likely' } })
expect(service['_failReasons']).toEqual(['I am Error, most likely'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'SKIPPED' } })
expect(service['_failReasons']).toEqual(['I am Error, most likely'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'FAILED', message: 'I too am Error' } })
expect(service['_failReasons']).toEqual(['I am Error, most likely', 'I too am Error'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'UNDEFINED', message: 'Step XYZ is undefined' } })
expect(service['_failReasons']).toEqual(['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'AMBIGUOUS', message: 'Step XYZ2 is ambiguous' } })
expect(service['_failReasons']).toEqual(
['I am Error, most likely',
'I too am Error',
'Step XYZ is undefined',
'Step XYZ2 is ambiguous'])
service.afterScenario({ pickle: { name: 'Can do something' }, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'PENDING' } })
expect(service['_failReasons']).toEqual(
['I am Error, most likely',
'I too am Error',
'Step XYZ is undefined',
'Step XYZ2 is ambiguous'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'SKIPPED' } })
expect(service['_failReasons']).toEqual([
'I am Error, most likely',
'I too am Error',
'Step XYZ is undefined',
'Step XYZ2 is ambiguous'])
})
it('should increment failure reasons on non-passing statuses (strict mode on)', () => {
service = new BrowserstackService({ testObservability: false } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: true }, capabilities: {} })
expect(service['_failReasons']).toEqual([])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'SKIPPED' } })
expect(service['_failReasons']).toEqual([])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, message: 'I am Error, most likely', status: 'FAILED' } })
expect(service['_failReasons']).toEqual(['I am Error, most likely'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'SKIPPED' } })
expect(service['_failReasons']).toEqual(['I am Error, most likely'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'FAILED', message: 'I too am Error' } })
expect(service['_failReasons']).toEqual(['I am Error, most likely', 'I too am Error'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'UNDEFINED', message: 'Step XYZ is undefined' } })
expect(service['_failReasons']).toEqual(['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'AMBIGUOUS', message: 'Step XYZ2 is ambiguous' } })
expect(service['_failReasons']).toEqual(
['I am Error, most likely',
'I too am Error',
'Step XYZ is undefined',
'Step XYZ2 is ambiguous'])
service.afterScenario({ pickle: { name: 'Can do something' }, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'PENDING' } })
expect(service['_failReasons']).toEqual(
['I am Error, most likely',
'I too am Error',
'Step XYZ is undefined',
'Step XYZ2 is ambiguous',
'Some steps/hooks are pending for scenario "Can do something"'])
service.afterScenario({ pickle: {}, result: { duration: { seconds: 0, nanos: 1000000 }, willBeRetried: false, status: 'SKIPPED' } })
expect(service['_failReasons']).toEqual([
'I am Error, most likely',
'I too am Error',
'Step XYZ is undefined',
'Step XYZ2 is ambiguous',
'Some steps/hooks are pending for scenario "Can do something"'])
})
})
describe('after', () => {
it('should call _update when session has no errors (exit code 0)', async () => {
const updateSpy = vi.spyOn(service, '_update')
await service.before(service['_config'] as any, [], browser)
service['_failReasons'] = []
service['_fullTitle'] = 'foo - bar'
service['_specsRan'] = true
await service.after(0)
expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId,
{
status: 'passed',
name: 'foo - bar'
})
expect(fetch).toHaveBeenCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{ method: 'PUT', body: JSON.stringify({
status: 'passed',
name: 'foo - bar'
}), headers })
})
it('should call _update when session has errors (exit code 1)', async () => {
const updateSpy = vi.spyOn(service, '_update')
await service.before(service['_config'] as any, [], browser)
service['_fullTitle'] = 'foo - bar'
service['_failReasons'] = ['I am failure']
await service.after(1)
expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId,
{
status: 'failed',
name: 'foo - bar',
reason: 'I am failure'
})
expect(fetch).toHaveBeenCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{ method: 'PUT', body: JSON.stringify({
status: 'failed',
name: 'foo - bar',
reason: 'I am failure'
}), headers })
})
it('should call _update with failed when session has no errors (exit code 0) but no tests ran', async () => {
const updateSpy = vi.spyOn(service, '_update')
await service.before(service['_config'] as any, [], browser)
service['_failReasons'] = []
service['_fullTitle'] = 'foo - bar'
await service.after(0)
expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId,
{
status: 'failed',
name: 'foo - bar'
})
expect(fetch).toHaveBeenCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{ method: 'PUT', body: JSON.stringify({
status: 'failed',
name: 'foo - bar'
}), headers })
})
it('should not set session status if option setSessionStatus is false', async () => {
const service = new BrowserstackService({ setSessionStatus: false } as any, [] as any, { user: 'foo', key: 'bar' } as any)
const updateSpy = vi.spyOn(service, '_update')
await service.before(service['_config'] as any, [], browser)
service['_fullTitle'] = 'foo - bar'
service['_failReasons'] = ['I am failure']
await service.after(1)
expect(updateSpy).not.toHaveBeenCalled()
expect(fetch).not.toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
method: 'POST',
}))
})
it('should not set session name if option setSessionName is false', async () => {
const service = new BrowserstackService({ setSessionName: false } as any, [] as any, { user: 'foo', key: 'bar' } as any)
const updateSpy = vi.spyOn(service, '_update')
await service.before(service['_config'] as any, [], browser)
service['_failReasons'] = []
service['_fullTitle'] = 'foo - bar'
service['_specsRan'] = true
await service.after(0)
expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, { status: 'passed' })
expect(fetch).toHaveBeenCalledWith(
`${sessionBaseUrl}/${sessionId}.json`,
{ method: 'PUT', body: JSON.stringify({ status: 'passed' }), headers })
})
describe('Cucumber only', function () {
it('should call _update with status "failed" if strict mode is "on" and all tests are pending', async () => {
service = new BrowserstackService({ testObservability: false } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: true } } as any)
const updateSpy = vi.spyOn(service, '_update')
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeFeature(null, { name: 'Feature1' })
await service.afterScenario({ pickle: { name: 'Can do something but pending 1' }, result: { status: 'PENDING' } as any })
await service.afterScenario({ pickle: { name: 'Can do something but pending 2' }, result: { status: 'PENDING' } as any })
await service.afterScenario({ pickle: { name: 'Can do something but pending 3' }, result: { status: 'PENDING' } as any })
await service.after(1)
expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, {
name: 'Feature1',
reason: 'Some steps/hooks are pending for scenario "Can do something but pending 1"' + '\n' +
'Some steps/hooks are pending for scenario "Can do something but pending 2"' + '\n' +
'Some steps/hooks are pending for scenario "Can do something but pending 3"',
status: 'failed',
})
expect(updateSpy).toHaveBeenCalled()
})
it('should call _update with status "passed" when strict mode is "off" and only passed and pending tests ran', async () => {
service = new BrowserstackService({ testObservability: false } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
const updateSpy = vi.spyOn(service, '_update')
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeFeature(null, { name: 'Feature1' })
await service.afterScenario({ pickle: { name: 'Can do something' }, result: { status: 'PASSED' } as any })
await service.afterScenario({ pickle: { name: 'Can do something' }, result: { status: 'PENDING' } as any })
await service.afterScenario({ pickle: { name: 'Can do something' }, result: { status: 'PASSED' } as any })
await service.after(0)
expect(updateSpy).toHaveBeenCalled()
expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, {
name: 'Feature1',
status: 'passed',
})
})
it('should call _update with status is "failed" when strict mode is "on" and only passed and pending tests ran', async () => {
service = new BrowserstackService({ testObservability: false } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: true } } as any)
const updateSpy = vi.spyOn(service, '_update')
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeFeature(null, { name: 'Feature1' })
await service.afterScenario({ pickle: { name: 'Can do something 1' }, result: { status: 'PASSED' } as any })
await service.afterScenario({ pickle: { name: 'Can do something but pending' }, result: { status: 'PENDING' } as any })
await service.afterScenario({ pickle: { name: 'Can do something 2' }, result: { status: 'PASSED' } as any })
await service.after(1)
expect(updateSpy).toHaveBeenCalled()
expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, {
name: 'Feature1',
reason: 'Some steps/hooks are pending for scenario "Can do something but pending"',
status: 'failed',
})
})
it('should call _update with status "passed" when all tests are skipped', async () => {
const updateSpy = vi.spyOn(service, '_update')
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeFeature(null, { name: 'Feature1' })
await service.afterScenario({ pickle: { name: 'Can do something skipped 1' }, result: { status: 'SKIPPED' } as any })
await service.afterScenario({ pickle: { name: 'Can do something skipped 2' }, result: { status: 'SKIPPED' } as any })
await service.afterScenario({ pickle: { name: 'Can do something skipped 3' }, result: { status: 'SKIPPED' } as any })
await service.after(0)
expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, {
name: 'Feature1',
status: 'passed',
})
})
it('should call _update with status "failed" when strict mode is "on" and only failed and pending tests ran', async () => {
service = new BrowserstackService({ testObservability: false } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: true } } as any)
const updateSpy = vi.spyOn(service, '_update')
const afterSpy = vi.spyOn(service, 'after')
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.beforeSession(service['_config'] as any)
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeFeature(null, { name: 'Feature1' })
expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, {
name: 'Feature1'
})
await service.afterScenario({ pickle: { name: 'Can do something failed 1' }, result: { message: 'I am error, hear me roar', status: 'FAILED' } as any })
await service.afterScenario({ pickle: { name: 'Can do something but pending 2' }, result: { status: 'PENDING' } as any })
await service.afterScenario({ pickle: { name: 'Can do something but passed 3' }, result: { status: 'SKIPPED' } as any })
await service.after(1)
expect(updateSpy).toHaveBeenCalledTimes(2)
expect(updateSpy).toHaveBeenLastCalledWith(
service['_browser']?.sessionId, {
name: 'Feature1',
reason:
'I am error, hear me roar' +
'\n' +
'Some steps/hooks are pending for scenario "Can do something but pending 2"',
status: 'failed',
})
expect(afterSpy).toHaveBeenCalledTimes(1)
})
it('should call _update with status "failed" when strict mode is "off" and only failed and pending tests ran', async () => {
const updateSpy = vi.spyOn(service, '_update')
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.beforeSession(service['_config'] as any)
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeFeature(null, { name: 'Feature1' })
expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, {
name: 'Feature1'
})
await service.afterScenario({ pickle: { name: 'Can do something failed 1' }, result: { message: 'I am error, hear me roar', status: 'FAILED' } as any })
await service.afterScenario({ pickle: { name: 'Can do something but pending 2' }, result: { status: 'PENDING' } as any })
await service.afterScenario({ pickle: { name: 'Can do something but passed 3' }, result: { status: 'SKIPPED' } as any })
await service.after(1)
expect(updateSpy).toHaveBeenCalledTimes(2)
expect(updateSpy).toHaveBeenLastCalledWith(
service['_browser']?.sessionId, {
name: 'Feature1',
reason: 'I am error, hear me roar',
status: 'failed',
}
)
})
describe('preferScenarioName', () => {
describe('enabled', () => {
[
{ status: 'FAILED', body: {
name: 'Can do something single',
reason: 'Unknown Error',
status: 'failed',
} }
].map(({ status, body }) =>
it(`should call _update /w status failed and name of Scenario when single "${status}" Scenario ran`, async () => {
service = new BrowserstackService({ testObservability: false, preferScenarioName : true } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
service.before({}, [], browserWithExecuteScript)
const updateSpy = vi.spyOn(service, '_update')
await service.beforeFeature(null, { name: 'Feature1' })
await service.afterScenario({ pickle: { name: 'Can do something single' }, result: { status } as any })
await service.after(1)
expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, body)
})
)
it('should call _update /w status passed and name of Scenario when single "passed" Scenario ran', async () => {
service = new BrowserstackService({ testObservability: false, preferScenarioName : true } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
service.before({}, [], browserWithExecuteScript)
const updateSpy = vi.spyOn(service, '_update')
await service.beforeFeature(null, { name: 'Feature1' })
await service.afterScenario({
pickle: { name: 'Can do something single' },
result: { status: 'passed' } as any
})
await service.after(0)
expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, {
name: 'Can do something single',
status: 'passed',
})
})
})
describe('disabled', () => {
['FAILED', 'AMBIGUOUS', 'UNDEFINED', 'UNKNOWN'].map(status =>
it(`should call _update /w status failed and name of Feature when single "${status}" Scenario ran`, async () => {
service = new BrowserstackService({ testObservability: false, preferScenarioName : false } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
service.before({}, [], browserWithExecuteScript)
const updateSpy = vi.spyOn(service, '_update')
await service.beforeFeature(null, { name: 'Feature1' })
await service.afterScenario({ pickle: { name: 'Can do something single' }, result: { status } as any })
await service.after(1)
expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, {
name: 'Feature1',
reason: 'Unknown Error',
status: 'failed',
})
})
)
it('should call _update /w status passed and name of Feature when single "passed" Scenario ran', async () => {
service = new BrowserstackService({ testObservability: false, preferScenarioName : false } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any)
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
service.before({}, [], browserWithExecuteScript)
const updateSpy = vi.spyOn(service, '_update')
await service.beforeFeature(null, { name: 'Feature1' })
await service.afterScenario({
pickle: { name: 'Can do something single' },
result: { status: 'PASSED' } as any
})
await service.after(0)
expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, {
name: 'Feature1',
status: 'passed',
})
})
})
})
})
describe('Observability only', function () {
it('should call _update with status "failed" if strict mode is "on" and all tests are pending', async () => {
service = new BrowserstackService({ testObservability: true } as any, [] as any,
{ user: 'foo', key: 'bar', cucumberOpts: { strict: true } } as any)
service['_failReasons'] = []
const updateSpy = vi.spyOn(service, '_updateJob')
await service.after(1)
expect(updateSpy).toHaveBeenCalled()
})
})
})
describe('_updateCaps', () => {
it('calls fn', () => {
const fnSpy = vi.fn()
service._updateCaps(fnSpy)
expect(fnSpy).toBeCalledTimes(1)
})
it('calls fn - caps present', () => {
const fnSpy = vi.fn()
service['_caps'] = { capabilities: { browserName: 'chrome' } } as any
service._updateCaps(fnSpy)
expect(fnSpy).toBeCalledTimes(1)
})
})
describe('setAnnotation', () => {
describe('Cucumber', () => {
it('should correctly annotate Features, Scenarios, and Steps', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
const service = new BrowserstackService({ sessionNamePrependTopLevelSuiteTitle: true } as any, [] as any, { user: 'foo', key: 'bar' } as any)
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeFeature(null, { name: 'Feature1' })
await service.beforeScenario({ pickle: { name: 'foobar' } })
const step = {
id: '5',
text: 'I am a step',
astNodeIds: ['0'],
keyword: 'Given ',
}
await service.beforeStep(step)
expect(browser.execute).toBeCalledTimes(3)
expect(browserWithExecuteScript.executeScript).toHaveBeenNthCalledWith(1, 'browserstack_executor: {"action":"annotate","arguments":{"data":"Feature: Feature1","level":"info"}}', [])
expect(browserWithExecuteScript.executeScript).toHaveBeenNthCalledWith(2, 'browserstack_executor: {"action":"annotate","arguments":{"data":"Scenario: foobar","level":"info"}}', [])
expect(browserWithExecuteScript.executeScript).toHaveBeenNthCalledWith(3, 'browserstack_executor: {"action":"annotate","arguments":{"data":"Step: Given I am a step","level":"info"}}', [])
})
})
describe('Jasmine', () => {
it('should correctly annotate Tests', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeSuite({ title: jasmineSuiteTitle } as any)
await service.beforeTest({ fullName: 'foo bar baz', description: 'baz' } as any)
expect(browser.execute).toBeCalledTimes(1)
expect(browser.execute).toBeCalledWith('browserstack_executor: {"action":"annotate","arguments":{"data":"Test: foo bar baz","level":"info"}}', [])
})
})
describe('Mocha', () => {
it('should correctly annotate Tests', async () => {
const browserWithExecuteScript = {
...browser,
executeScript: browser.execute
} as WebdriverIO.Browser
await service.before(service['_config'] as any, [], browserWithExecuteScript)
await service.beforeSuite({ title: 'My Feature' } as any)
await service.beforeTest({ title: 'Test Title', parent: 'Suite Title' } as any)
expect(browser.execute).toBeCalledTimes(1)
expect(browser.execute).toBeCalledWith('browserstack_executor: {"action":"annotate","arguments":{"data":"Test: Test Title","level":"info"}}', [])
})
})
})
describe('ignoreHooksStatus feature', () => {
let service: BrowserstackService
beforeEach(() => {
service = new BrowserstackService({ testObservability: false } as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
})
describe('afterHook with ignoreHooksStatus=true', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true }
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
service['_insightsHandler'] = new InsightsHandler(browser)
})
it('should track hook failures but not add them to main _failReasons when ignoreHooksStatus=true', async () => {
const methodSpy = vi.spyOn(service['_insightsHandler'], 'afterHook')
await service.afterHook({ title: 'foo', parent: 'bar' } as any,
undefined as never, { passed: false, error: { message: 'Hook failed' } } as any)
expect(service['_hookFailReasons']).toEqual(['Hook failed'])
expect(service['_failReasons']).toEqual([])
expect(methodSpy).toBeCalled()
})
it('should add hook failures to _failReasons when ignoreHooksStatus=false', async () => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: false }
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_insightsHandler'] = new InsightsHandler(browser)
await service.afterHook({ title: 'foo', parent: 'bar' } as any,
undefined as never, { passed: false, error: { message: 'Hook failed' } } as any)
expect(service['_hookFailReasons']).toEqual(['Hook failed'])
expect(service['_failReasons']).toEqual(['Hook failed'])
})
it('should add hook failures to _failReasons when ignoreHooksStatus is not set', async () => {
service = new BrowserstackService({
testObservability: false
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_insightsHandler'] = new InsightsHandler(browser)
await service.afterHook({ title: 'foo', parent: 'bar' } as any,
undefined as never, { passed: false, error: { message: 'Hook failed' } } as any)
expect(service['_hookFailReasons']).toEqual(['Hook failed'])
expect(service['_failReasons']).toEqual(['Hook failed'])
})
})
describe('afterTest with pure test failures', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true }
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
service['_insightsHandler'] = new InsightsHandler(browser)
})
it('should track pure test failures in both _failReasons and _pureTestFailReasons', async () => {
await service.afterTest({ title: 'foo', parent: 'bar' } as any,
undefined as never, { passed: false, error: { message: 'Test failed' } } as any)
expect(service['_failReasons']).toEqual(['Test failed'])
expect(service['_pureTestFailReasons']).toEqual(['Test failed'])
})
})
describe('session status with ignoreHooksStatus=true', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
service['_insightsHandler'] = new InsightsHandler(browser)
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
})
it('should mark session as passed when only hooks fail and ignoreHooksStatus=true', async () => {
await service.afterHook({ title: 'hook', parent: 'suite' } as any,
undefined as never, { passed: false, error: { message: 'Hook failed' } } as any)
service['_specsRan'] = true
await service.after(0)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'passed'
})
})
it('should mark session as failed when tests fail even with ignoreHooksStatus=true', async () => {
await service.afterHook({ title: 'hook', parent: 'suite' } as any,
undefined as never, { passed: false, error: { message: 'Hook failed' } } as any)
await service.afterTest({ title: 'test', parent: 'suite' } as any,
undefined as never, { passed: false, error: { message: 'Test failed' } } as any)
await service.after(0)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed',
reason: 'Test failed'
})
})
it('should include hook and test failures in reason when ignoreHooksStatus=false', async () => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: false },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
service['_insightsHandler'] = new InsightsHandler(browser)
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
await service.afterHook({ title: 'hook', parent: 'suite' } as any,
undefined as never, { passed: false, error: { message: 'Hook failed' } } as any)
await service.afterTest({ title: 'test', parent: 'suite' } as any,
undefined as never, { passed: false, error: { message: 'Test failed' } } as any)
await service.after(0)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed',
reason: 'Hook failed\nTest failed'
})
})
})
describe('onReload with ignoreHooksStatus=true', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
service['_insightsHandler'] = new InsightsHandler(browser)
vi.spyOn(service, '_update').mockResolvedValue({} as any)
})
it('should use pure test failures for status when ignoreHooksStatus=true', async () => {
service['_hookFailReasons'].push('Hook failed')
service['_failReasons'].push('Test failed')
service['_pureTestFailReasons'].push('Test failed')
await service.onReload('oldSessionId', 'newSessionId')
expect(service['_update']).toHaveBeenCalledWith('oldSessionId', {
status: 'failed',
reason: 'Test failed'
})
})
it('should pass when only hook failures exist and ignoreHooksStatus=true', async () => {
service['_hookFailReasons'].push('Hook failed')
await service.onReload('oldSessionId', 'newSessionId')
expect(service['_update']).toHaveBeenCalledWith('oldSessionId', {
status: 'passed'
})
})
it('should reset all failure arrays on reload', async () => {
service['_failReasons'].push('Some failure')
service['_hookFailReasons'].push('Hook failed')
service['_pureTestFailReasons'].push('Test failed')
await service.onReload('oldSessionId', 'newSessionId')
expect(service['_failReasons']).toEqual([])
expect(service['_hookFailReasons']).toEqual([])
expect(service['_pureTestFailReasons']).toEqual([])
})
})
describe('ignoreHooksStatus feature - Comprehensive Test Suite', () => {
describe('afterHook method - Extended Coverage', () => {
describe('when ignoreHooksStatus is true', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
})
it('should handle multiple consecutive hook failures correctly', async () => {
const hook1Error = { message: 'Before hook failed' }
const hook2Error = { message: 'After hook failed' }
const hook3Error = { message: 'Setup hook failed' }
await service.afterHook({ title: 'beforeEach' } as any, undefined, { passed: false, error: hook1Error } as any)
await service.afterHook({ title: 'afterEach' } as any, undefined, { passed: false, error: hook2Error } as any)
await service.afterHook({ title: 'beforeAll' } as any, undefined, { passed: false, error: hook3Error } as any)
expect(service['_hookFailReasons']).toEqual(['Before hook failed', 'After hook failed', 'Setup hook failed'])
expect(service['_failReasons']).toEqual([])
})
it('should handle hooks that pass after failures correctly', async () => {
const hookError = { message: 'First hook failed' }
await service.afterHook({ title: 'hook1' } as any, undefined, { passed: false, error: hookError } as any)
await service.afterHook({ title: 'hook2' } as any, undefined, { passed: true } as any)
await service.afterHook({ title: 'hook3' } as any, undefined, { passed: true } as any)
expect(service['_hookFailReasons']).toEqual(['First hook failed'])
expect(service['_failReasons']).toEqual([])
})
it('should handle null/undefined errors gracefully', async () => {
await service.afterHook({ title: 'hook' } as any, undefined, { passed: false, error: null } as any)
await service.afterHook({ title: 'hook' } as any, undefined, { passed: false, error: undefined } as any)
await service.afterHook({ title: 'hook' } as any, undefined, { passed: false } as any)
expect(service['_hookFailReasons']).toEqual(['Hook failed', 'Hook failed', 'Hook failed'])
expect(service['_failReasons']).toEqual([])
})
})
describe('when ignoreHooksStatus is undefined/not specified', () => {
it('should default to false and add hook failures to failReasons', async () => {
service = new BrowserstackService({
testObservability: false,
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
const hookError = { message: 'Hook failed' }
await service.afterHook({ title: 'hook' } as any, undefined, { passed: false, error: hookError } as any)
expect(service['_hookFailReasons']).toEqual(['Hook failed'])
expect(service['_failReasons']).toEqual(['Hook failed'])
})
})
})
describe('after method - Complex Session Status Scenarios', () => {
describe('when ignoreHooksStatus is true', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
})
it('should handle complex mixed failure scenarios', async () => {
service['_specsRan'] = true
service['_hookFailReasons'] = ['Before hook failed', 'After hook failed', 'Setup hook failed']
service['_failReasons'] = ['Test assertion failed', 'Another test failed']
service['_pureTestFailReasons'] = ['Test assertion failed', 'Another test failed']
await service.after(1)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed',
reason: 'Test assertion failed\nAnother test failed'
})
})
it('should override to passed when exit code is 1 but only hooks failed', async () => {
service['_specsRan'] = true
service['_hookFailReasons'] = ['Multiple', 'Hook', 'Failures']
service['_failReasons'] = []
service['_pureTestFailReasons'] = []
await service.after(1)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'passed'
})
})
it('should handle empty failure arrays correctly', async () => {
service['_specsRan'] = true
service['_hookFailReasons'] = []
service['_failReasons'] = []
service['_pureTestFailReasons'] = []
await service.after(0)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'passed'
})
})
it('should mark as failed when specs did not run regardless of hook status', async () => {
service['_specsRan'] = false
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = []
service['_pureTestFailReasons'] = []
await service.after(0)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed'
})
})
it('should handle scenario where setSessionName is enabled', async () => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true,
setSessionName: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
service['_specsRan'] = true
service['_fullTitle'] = 'My Test Suite'
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = []
service['_pureTestFailReasons'] = []
await service.after(1)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'passed',
name: 'My Test Suite'
})
})
it('should respect session name and status options independently', async () => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: false,
setSessionName: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
service['_specsRan'] = true
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = []
service['_pureTestFailReasons'] = []
await service.after(1)
expect(service['_updateJob']).not.toHaveBeenCalled()
})
})
describe('comparison with ignoreHooksStatus disabled', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: false },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
})
it('should mark as failed when hooks fail (ignoreHooksStatus=false)', async () => {
service['_specsRan'] = true
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = ['Hook failed']
service['_pureTestFailReasons'] = []
await service.after(0)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed',
reason: 'Hook failed'
})
})
it('should combine hook and test failure reasons', async () => {
service['_specsRan'] = true
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = ['Hook failed', 'Test failed']
service['_pureTestFailReasons'] = ['Test failed']
await service.after(1)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed',
reason: 'Hook failed\nTest failed'
})
})
})
})
describe('Integration test scenarios', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
})
it('should handle complete test lifecycle with mixed outcomes', async () => {
await service.afterHook({ title: 'beforeEach' } as any, undefined, {
passed: false,
error: { message: 'Setup failed' }
} as any)
await service.afterTest({ title: 'test1' } as any, undefined as never, {
passed: true
} as any)
await service.afterTest({ title: 'test2' } as any, undefined as never, {
passed: false,
error: { message: 'Assertion failed' }
} as any)
await service.afterHook({ title: 'afterEach' } as any, undefined, {
passed: false,
error: { message: 'Cleanup failed' }
} as any)
await service.afterTest({ title: 'test3' } as any, undefined as never, {
passed: true
} as any)
expect(service['_hookFailReasons']).toEqual(['Setup failed', 'Cleanup failed'])
expect(service['_failReasons']).toEqual(['Assertion failed'])
expect(service['_pureTestFailReasons']).toEqual(['Assertion failed'])
await service.after(1)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed',
reason: 'Assertion failed'
})
})
it('should handle only hook failures across entire test lifecycle', async () => {
await service.afterHook({ title: 'beforeAll' } as any, undefined, {
passed: false,
error: { message: 'Global setup failed' }
} as any)
await service.afterHook({ title: 'beforeEach' } as any, undefined, {
passed: false,
error: { message: 'Test setup failed' }
} as any)
await service.afterTest({ title: 'test1' } as any, undefined as never, { passed: true } as any)
await service.afterTest({ title: 'test2' } as any, undefined as never, { passed: true } as any)
await service.afterTest({ title: 'test3' } as any, undefined as never, { passed: true } as any)
await service.afterHook({ title: 'afterEach' } as any, undefined, {
passed: false,
error: { message: 'Test cleanup failed' }
} as any)
await service.afterHook({ title: 'afterAll' } as any, undefined, {
passed: false,
error: { message: 'Global cleanup failed' }
} as any)
expect(service['_hookFailReasons']).toEqual([
'Global setup failed',
'Test setup failed',
'Test cleanup failed',
'Global cleanup failed'
])
expect(service['_failReasons']).toEqual([])
expect(service['_pureTestFailReasons']).toEqual([])
await service.after(1)
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'passed'
})
})
})
describe('Boundary and edge cases', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
})
it('should handle extremely long error messages', async () => {
const longErrorMessage = 'A'.repeat(10000)
await service.afterHook({ title: 'hook' } as any, undefined, {
passed: false,
error: { message: longErrorMessage }
} as any)
expect(service['_hookFailReasons']).toEqual([longErrorMessage])
expect(service['_failReasons']).toEqual([])
})
it('should handle special characters in error messages', async () => {
const specialMessage = 'Error with special chars: \n\t\r"\'\\$`@#%^&*()[]{}|;:<>?/~'
await service.afterTest({ title: 'test' } as any, undefined as never, {
passed: false,
error: { message: specialMessage }
} as any)
expect(service['_failReasons']).toEqual([specialMessage])
expect(service['_pureTestFailReasons']).toEqual([specialMessage])
})
it('should handle rapid alternating pass/fail scenarios', async () => {
for (let i = 0; i < 100; i++) {
const shouldFail = i % 2 === 0
await (shouldFail ? service.afterTest({ title: `test${i}` } as any, undefined as never, {
passed: false,
error: { message: `Test ${i} failed` }
} as any) : service.afterTest({ title: `test${i}` } as any, undefined as never, {
passed: true
} as any))
}
expect(service['_failReasons']).toHaveLength(50)
expect(service['_pureTestFailReasons']).toHaveLength(50)
})
})
describe('Cucumber afterScenario with ignoreHooksStatus', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
service['_insightsHandler'] = new InsightsHandler(browser)
})
it('should not add failure when scenario fails due to hooks only', async () => {
const world = {
pickle: { name: 'Test scenario' },
result: { status: 'FAILED', message: 'Hook failed' }
} as any
vi.spyOn(service['_insightsHandler'], 'hasTestStepFailures').mockReturnValue(false)
await service.afterScenario(world)
expect(service['_failReasons']).toEqual([])
expect(service['_pureTestFailReasons']).toEqual([])
expect(service['_scenariosThatRan']).toEqual(['Test scenario'])
})
it('should add failure when scenario fails due to test steps', async () => {
const world = {
pickle: { name: 'Test scenario' },
result: { status: 'FAILED', message: 'Test step failed' }
} as any
vi.spyOn(service['_insightsHandler'], 'hasTestStepFailures').mockReturnValue(true)
await service.afterScenario(world)
expect(service['_failReasons']).toEqual(['Test step failed'])
expect(service['_pureTestFailReasons']).toEqual(['Test step failed'])
expect(service['_scenariosThatRan']).toEqual(['Test scenario'])
})
it('should add failure when ignoreHooksStatus is false regardless of step failures', async () => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: false },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
service['_insightsHandler'] = new InsightsHandler(browser)
const world = {
pickle: { name: 'Test scenario' },
result: { status: 'FAILED', message: 'Hook failed' }
} as any
vi.spyOn(service['_insightsHandler'], 'hasTestStepFailures').mockReturnValue(false)
await service.afterScenario(world)
expect(service['_failReasons']).toEqual(['Hook failed'])
expect(service['_pureTestFailReasons']).toEqual(['Hook failed'])
})
it('should handle pending scenarios with ignoreHooksStatus', async () => {
const world = {
pickle: { name: 'Pending scenario' },
result: { status: 'PENDING' }
} as any
vi.spyOn(service['_insightsHandler'], 'hasTestStepFailures').mockReturnValue(false)
await service.afterScenario(world)
expect(service['_failReasons']).toEqual([])
expect(service['_pureTestFailReasons']).toEqual([])
})
})
describe('Process exit override functionality', () => {
beforeEach(() => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: true },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
})
it('should override process exit when only hooks fail and ignoreHooksStatus=true', async () => {
service['_specsRan'] = true
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = []
service['_pureTestFailReasons'] = []
const result = await service.after(1)
expect(result).toBeUndefined()
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'passed'
})
})
it('should not override process exit when tests actually failed', async () => {
service['_specsRan'] = true
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = ['Test failed']
service['_pureTestFailReasons'] = ['Test failed']
const result = await service.after(1)
expect(result).toBeUndefined()
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed',
reason: 'Test failed'
})
})
it('should not override process exit when ignoreHooksStatus=false', async () => {
service = new BrowserstackService({
testObservability: false,
testObservabilityOptions: { ignoreHooksStatus: false },
setSessionStatus: true
} as any, [] as any, { user: 'foo', key: 'bar' } as any)
service['_browser'] = browser
vi.spyOn(service, '_updateJob').mockResolvedValue({} as any)
service['_specsRan'] = true
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = ['Hook failed']
const result = await service.after(1)
expect(result).toBeUndefined()
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed',
reason: 'Hook failed'
})
})
it('should not override process exit when specs did not run', async () => {
service['_specsRan'] = false
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = []
const result = await service.after(1)
expect(result).toBeUndefined()
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'failed'
})
})
it('should not override process exit when exit code is 0', async () => {
service['_specsRan'] = true
service['_hookFailReasons'] = ['Hook failed']
service['_failReasons'] = []
const result = await service.after(0)
expect(result).toBeUndefined()
expect(service['_updateJob']).toHaveBeenCalledWith({
status: 'passed'
})
})
})
})
})