This commit is contained in:
2025-08-18 21:51:28 -04:00
parent 1eb0637d38
commit f3ecb8c35b
19 changed files with 179 additions and 646 deletions

View File

@@ -1,4 +1,4 @@
import { browser } from '@wdio/globals';
import {browser} from '@wdio/globals';
export enum DocumentReadyState {
Loading = 'Loading',
@@ -7,9 +7,9 @@ export enum DocumentReadyState {
}
export async function waitUntilDocumentReadyState(waitForReadyState: DocumentReadyState): Promise<void> {
await browser.waitUntil(() => browser.execute(`document.readyState === "${DocumentReadyState[waitForReadyState]}"`) as Promise<boolean>, {
await browser.waitUntil(() => browser.execute(`document.readyState === "${DocumentReadyState[waitForReadyState]}"`) as Promise<boolean>, {
timeout: 10000,
timeoutMsg: `Document did not have a readyState of [${waitForReadyState}] after [10] seconds`,
interval: 1000
});
}
}

View File

@@ -1,27 +1,27 @@
import SupportedBrowser from "./SupportedBrowser";
import SupportedBrowser from './SupportedBrowser';
// Source: https://github.com/browserstack/api
export class BrowserstackApi {
private readonly _baseUrl: string = 'https://api.browserstack.com/5';
private readonly _authorizationHeader: string;
private readonly _baseUrl: string = 'https://api.browserstack.com/5';
private readonly _authorizationHeader: string;
constructor(username: string, accessKey: string) {
this._authorizationHeader = `Basic ${Buffer.from(`${username}:${accessKey}`).toString('base64')}`;
constructor(username: string, accessKey: string) {
this._authorizationHeader = `Basic ${Buffer.from(`${username}:${accessKey}`).toString('base64')}`;
}
public async getSupportedBrowsers(): Promise<SupportedBrowser[]> {
const endpoint = `${this._baseUrl}/browsers?flat=true`;
const headers = {
Authorization: this._authorizationHeader
};
const response = await fetch(endpoint, {headers});
if (!response.ok) {
throw new Error(`Failed to fetch BrowserStack supported browsers due to [ ${response.statusText}]`);
}
public async getSupportedBrowsers(): Promise<SupportedBrowser[]> {
const endpoint = `${this._baseUrl}/browsers?flat=true`;
const headers = {
'Authorization': this._authorizationHeader
};
const response = await fetch(endpoint, { headers });
if (!response.ok) {
throw new Error(`Failed to fetch BrowserStack supported browsers due to [ ${response.statusText}]`);
}
return response.json() as Promise<SupportedBrowser[]>;
}
}
return response.json() as Promise<SupportedBrowser[]>;
}
}

View File

@@ -1,10 +1,10 @@
type SupportedBrowser = {
os: string;
os_version: string;
browser: string;
device: string;
browser_version: string | null;
real_mobile: boolean;
}
os: string;
os_version: string;
browser: string;
device: string;
browser_version: string | null;
real_mobile: boolean;
};
export default SupportedBrowser;
export default SupportedBrowser;

View File

@@ -27,43 +27,61 @@ const defaultUseBrowserstack = false;
const defaultUseBrowserstackLocal = false;
export default class CommandLine {
private static readonly _program: Command = new Command();
private static readonly _program: Command = new Command();
public static parse(args: string[]): CommandLineOptions {
CommandLine._program.parse(args);
return CommandLine._program.opts<CommandLineOptions>();
const rawOptions = CommandLine._program.opts();
// Convert the string log level back to LoggingLevel enum
const logLevel = rawOptions.logLevel ? LoggingLevelMapping.convertLoggingLevelTypeToLoggingLevel(rawOptions.logLevel) : defaultLogLevel;
return {
...rawOptions,
logLevel
} as CommandLineOptions;
}
static {
CommandLine._program.version(PackageJson.version);
const setupProgramOptions = (): void => {
const handleArrayOption = (value: string, previousValue: string[]) => {
if (previousValue) {
return previousValue.concat(value);
}
return [value];
};
// Required options
CommandLine._program.requiredOption('--application-id <applicationId>', 'The application ID to use');
CommandLine._program.requiredOption('--secret <secret>', 'The secret to use');
CommandLine._program.requiredOption('--pcast-uri <pcastUri>', 'The pcast URI to use');
CommandLine._program.requiredOption('--channel-uri <channelUri>', 'The channel URI to use');
CommandLine._program.option('--ingest-uri <ingestUri>', 'The ingest URI to use');
CommandLine._program.option('--publisher-uri <publisherUri>', 'The publisher URI to use');
CommandLine._program.option('--viewer <browser@version:OS@OSVersion...>', 'The browser and OS for simulating a viewer to use', handleArrayOption, defaultViewers);
CommandLine._program.option('--publisher <browser@version:OS@OSVersion...>', 'The browser and OS for simulating a publisher to use', handleArrayOption, defaultPublishers);
CommandLine._program.option(
'--viewer <browser@version:OS@OSVersion...>',
'The browser and OS for simulating a viewer to use',
handleArrayOption,
defaultViewers
);
CommandLine._program.option(
'--publisher <browser@version:OS@OSVersion...>',
'The browser and OS for simulating a publisher to use',
handleArrayOption,
defaultPublishers
);
CommandLine._program.option('-t, --test <test...>', 'The test to run', handleArrayOption, defaultTests);
CommandLine._program.option('--use-browserstack', 'Run tests using BrowserStack', defaultUseBrowserstack);
CommandLine._program.option('--use-browserstack-local', 'Run tests using BrowserStack Local', defaultUseBrowserstackLocal);
CommandLine._program.option('--browserstack-user <username>', 'The BrowserStack username to use', process.env.BROWSERSTACK_USER || '');
CommandLine._program.option('--browserstack-key <key>', 'The BrowserStack key to use', process.env.BROWSERSTACK_KEY || '');
CommandLine._program.option('--log-level <logLevel>', 'The log level to use', LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(defaultLogLevel));
}
};
setupProgramOptions();
}

View File

@@ -1,78 +1,77 @@
import CommandLine, { CommandLineOptions } from './CommandLine';
import type {CommandLineOptions} from './CommandLine';
type ApplicationCredentials = {
applicationId: string;
secret: string;
}
applicationId: string;
secret: string;
};
type Uri = {
pcast: string;
ingest: string | undefined;
channel: string | undefined;
publisher: string | undefined;
}
pcast: string;
ingest: string | undefined;
channel: string | undefined;
publisher: string | undefined;
};
type BrowserstackConfiguration = {
enabled: boolean;
local: boolean;
user: string;
key: string;
}
enabled: boolean;
local: boolean;
user: string;
key: string;
};
export default class TestConfiguration {
private readonly _applicationCredentials: ApplicationCredentials;
private readonly _uri: Uri;
private readonly _viewers: string[];
private readonly _publishers: string[];
private readonly _tests: string[];
private readonly _browserstack: BrowserstackConfiguration;
private readonly _applicationCredentials: ApplicationCredentials;
private readonly _uri: Uri;
private readonly _viewers: string[];
private readonly _publishers: string[];
private readonly _tests: string[];
private readonly _browserstack: BrowserstackConfiguration;
constructor(commandLineOptions: CommandLineOptions) {
this._applicationCredentials = {
applicationId: commandLineOptions.applicationId,
secret: commandLineOptions.secret,
};
constructor(commandLineOptions: CommandLineOptions) {
this._applicationCredentials = {
applicationId: commandLineOptions.applicationId,
secret: commandLineOptions.secret
};
this._uri = {
pcast: commandLineOptions.pcastUri,
ingest: commandLineOptions.ingestUri,
channel: commandLineOptions.channelUri,
publisher: commandLineOptions.publisherUri,
};
this._uri = {
pcast: commandLineOptions.pcastUri,
ingest: commandLineOptions.ingestUri,
channel: commandLineOptions.channelUri,
publisher: commandLineOptions.publisherUri
};
this._viewers = commandLineOptions.viewers;
this._publishers = commandLineOptions.publishers;
this._tests = commandLineOptions.tests;
this._browserstack = {
enabled: commandLineOptions.useBrowserstack,
local: commandLineOptions.useBrowserstackLocal,
user: commandLineOptions.browserstackUser,
key: commandLineOptions.browserstackKey,
};
}
this._viewers = commandLineOptions.viewers;
this._publishers = commandLineOptions.publishers;
this._tests = commandLineOptions.tests;
this._browserstack = {
enabled: commandLineOptions.useBrowserstack,
local: commandLineOptions.useBrowserstackLocal,
user: commandLineOptions.browserstackUser,
key: commandLineOptions.browserstackKey
};
}
get applicationCredentials(): ApplicationCredentials {
return this._applicationCredentials;
}
get applicationCredentials(): ApplicationCredentials {
return this._applicationCredentials;
}
get uri(): Uri {
return this._uri;
}
get uri(): Uri {
return this._uri;
}
get viewers(): string[] {
return this._viewers;
}
get viewers(): string[] {
return this._viewers;
}
get publishers(): string[] {
return this._publishers;
}
get publishers(): string[] {
return this._publishers;
}
get tests(): string[] {
return this._tests;
}
get tests(): string[] {
return this._tests;
}
get browserstack(): BrowserstackConfiguration {
return this._browserstack;
}
}
get browserstack(): BrowserstackConfiguration {
return this._browserstack;
}
}

View File

@@ -65,7 +65,7 @@ export class Logger {
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, ...optionalParameters);
const levelString = LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(level);
// Safely call appenders with error handling
this._appenders.forEach(appender => {
try {
@@ -86,22 +86,23 @@ export class Logger {
// More efficient parameter substitution
let result = message;
let paramIndex = 0;
// Replace all {} placeholders with parameters
while (result.includes('{}') && paramIndex < optionalParameters.length) {
const paramString = this.parameterToString(optionalParameters[paramIndex]);
result = result.replace('{}', paramString);
paramIndex++;
}
// Append remaining parameters if any
if (paramIndex < optionalParameters.length) {
const remainingParams = optionalParameters.slice(paramIndex)
const remainingParams = optionalParameters
.slice(paramIndex)
.map(param => this.parameterToString(param))
.join(' ');
result += ` ${remainingParams}`;
}
return result;
}

View File

@@ -1,20 +1,20 @@
import { ConsoleAppender } from './appenders/ConsoleAppender';
import {ConsoleAppender} from './appenders/ConsoleAppender';
import IAppender from './appenders/IAppender';
import {Logger} from './Logger';
import {LoggingLevel, LoggingLevelMapping, LoggingLevelType} from './LoggingLevel';
import {LoggingLevel, LoggingLevelMapping} from './LoggingLevel';
import {Threshold} from './Threshold';
export default class LoggerFactory {
private static readonly _loggerForCategory: Map<string, Logger> = new Map();
private static readonly _threshold: {level: LoggingLevel} = {level: LoggingLevel.Info};
private static readonly _appenders: IAppender[] = [new ConsoleAppender()];
private static readonly _appenders: IAppender[] = [new ConsoleAppender()];
public static getLogger(category: string): Logger {
let logger = LoggerFactory._loggerForCategory.get(category);
if (logger === undefined) {
logger = new Logger({category, threshold: new Threshold(LoggerFactory._threshold), appenders: LoggerFactory._appenders});
LoggerFactory._loggerForCategory.set(category, logger);
}
@@ -29,4 +29,4 @@ export default class LoggerFactory {
private constructor() {
throw new Error('[LoggerFactory] is a static class that may not be instantiated');
}
}
}

View File

@@ -12,19 +12,19 @@ export class ConsoleAppender implements IAppender {
console.info(`${timestamp} [${category}] ${message}`);
break;
case LoggingLevel.Error:
case LoggingLevel.Error:
console.error(`${timestamp} [${category}] ${message}`);
break;
case LoggingLevel.Fatal:
case LoggingLevel.Fatal:
console.error(`${timestamp} [${category}] ${message}`);
break;
case LoggingLevel.Off:
case LoggingLevel.All:
case LoggingLevel.Off:
case LoggingLevel.All:
break;
default:
default:
assertUnreachable(level);
}
}

View File

@@ -1,33 +1,33 @@
import {browser} from '@wdio/globals';
import {browser} from '@wdio/globals';
export type PageOptions = {
browser?: typeof browser; // MultiRemote usecase
};
export type PageOptions = {
browser?: typeof browser; // MultiRemote usecase
export type PageOpenOptions = {
queryParameters?: Record<string, string | number>;
isNewTabRequest?: boolean;
endpoint?: string;
requestPath?: string;
};
}
export default class Page {
private readonly _baseUrl: string;
export type PageOpenOptions = {
queryParameters?: Record<string, string | number>;
isNewTabRequest?: boolean;
endpoint?: string;
requestPath?: string;
};
export default class Page {
private readonly _baseUrl: string;
constructor(baseUrl: string) {
constructor(baseUrl: string) {
this._baseUrl = baseUrl;
}
public async open(options: PageOpenOptions = {}): Promise<void> {
public async open(options: PageOpenOptions = {}): Promise<void> {
const {queryParameters, isNewTabRequest, endpoint, requestPath} = options;
const pageUrl = `${this._baseUrl}/${endpoint}${requestPath}?${Object.entries(queryParameters ?? {}).map(([queryParameterName, queryParamterValue]) => (`${queryParameterName}=${queryParamterValue}&`)).join('')}`;
const pageUrl = `${this._baseUrl}/${endpoint}${requestPath}?${Object.entries(queryParameters ?? {})
.map(([queryParameterName, queryParamterValue]) => `${queryParameterName}=${queryParamterValue}&`)
.join('')}`;
if (isNewTabRequest) {
await browser.newWindow(pageUrl);
} else {
await browser.url(pageUrl);
}
} else {
await browser.url(pageUrl);
}
}
}

View File

@@ -1,6 +1,6 @@
import Page, { PageOpenOptions } from './Page.ts';
import Page, {PageOpenOptions} from './Page.ts';
export type SubscribingPageOptions = { };
export type SubscribingPageOptions = {};
export class SubscribingPage extends Page {
constructor(baseUri: string, options: SubscribingPageOptions) {
@@ -12,7 +12,6 @@ export class SubscribingPage extends Page {
}
public async open(options?: PageOpenOptions): Promise<void> {
await super.open(options);
await super.open(options);
}
}

View File

@@ -1,6 +1,6 @@
import LoggerFactory from '../logger/LoggerFactory';
import CommandLine from '../config/CommandLine';
import { CommandLineOptions } from '../config/CommandLine';
import {CommandLineOptions} from '../config/CommandLine';
export default class TestRunner {
private static readonly _logger = LoggerFactory.getLogger('TestRunner');
@@ -16,4 +16,4 @@ export default class TestRunner {
TestRunner._logger.info('TestRunner started');
TestRunner._logger.info(JSON.stringify(TestRunner._commandLineOptions, null, 2));
}
}
}

View File

@@ -1,203 +0,0 @@
import {before, describe, it, after} from 'mocha';
import {expect, use} from 'chai';
import ChaiAsPromised from 'chai-as-promised';
import {SubscribingPage} from '../pages';
use(ChaiAsPromised);
// Helper function to add delays and make tests more observable
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
// Simple, working test implementation
describe('Subscribe Workflow Tests', () => {
let subscribingPage: SubscribingPage;
before(async () => {
console.log('\n🚀 Starting Subscribe Workflow Tests...');
console.log('⏱️ Tests will run with delays for real-time observation');
await delay(1000);
// Initialize the page object with a real URL
const testUrl = 'http://dl.phenixrts.com/JsSDK/2025.2.latest/examples/channel-viewer-plain.html';
subscribingPage = new SubscribingPage(testUrl, {});
console.log(`✅ SubscribingPage initialized with URL: ${testUrl}`);
await delay(500);
});
after(async () => {
console.log('\n🧹 Test cleanup completed');
});
describe('RealTime Stream Subscription', () => {
it('should initialize page object correctly', async () => {
console.log('🔍 Testing page initialization...');
await delay(300);
expect(subscribingPage).to.exist;
console.log('✅ Page object exists');
await delay(200);
expect(subscribingPage).to.be.instanceOf(SubscribingPage);
console.log('✅ Page object is correct instance');
await delay(200);
});
it('should open the streaming page successfully', async () => {
console.log('🌐 Testing page navigation...');
await delay(500);
try {
// Open the page with test parameters
await subscribingPage.open({
queryParameters: {
channelId: 'test-channel-realtime',
token: 'test-token-realtime',
streamType: 'realtime'
}
});
console.log('✅ Page opened successfully');
await delay(1000);
// Wait for page to load
console.log('⏳ Waiting for page to load...');
await delay(2000);
} catch (error) {
console.warn('⚠️ Page navigation failed:', error);
throw error; // Fail the test if navigation fails
}
});
it('should find video element on the page', async () => {
console.log('🔍 Looking for video element...');
await delay(500);
try {
const videoElement = await subscribingPage.videoElement;
console.log('📺 Video element found:', videoElement ? 'Yes' : 'No');
if (videoElement) {
expect(videoElement).to.exist;
console.log('✅ Video element exists and is accessible');
} else {
console.log('⚠️ No video element found - this might indicate a page loading issue');
// Don't fail the test, but log the issue
}
} catch (error) {
console.warn('⚠️ Video element test failed:', error);
// Don't fail the test for video element issues
}
await delay(500);
});
});
describe('HLS Stream Subscription', () => {
it('should handle HLS stream configuration', async () => {
console.log('🔍 Testing HLS stream configuration...');
await delay(300);
const streamConfig = {
type: 'hls',
url: 'https://example.com/stream.m3u8',
quality: 'high'
};
expect(streamConfig.type).to.equal('hls');
console.log('✅ HLS stream type configured');
await delay(200);
expect(streamConfig.url).to.include('.m3u8');
console.log('✅ HLS playlist URL format correct');
await delay(200);
});
});
describe('DASH Stream Subscription', () => {
it('should handle DASH stream configuration', async () => {
console.log('🔍 Testing DASH stream configuration...');
await delay(300);
const streamConfig = {
type: 'dash',
url: 'https://example.com/stream.mpd',
quality: 'adaptive'
};
expect(streamConfig.type).to.equal('dash');
console.log('✅ DASH stream type configured');
await delay(200);
expect(streamConfig.url).to.include('.mpd');
console.log('✅ DASH manifest URL format correct');
await delay(200);
});
});
describe('WebRTC Stream Subscription', () => {
it('should handle WebRTC stream configuration', async () => {
console.log('🔍 Testing WebRTC stream configuration...');
await delay(300);
const streamConfig = {
type: 'webrtc',
url: 'wss://example.com/webrtc',
latency: 'low'
};
expect(streamConfig.type).to.equal('webrtc');
console.log('✅ WebRTC stream type configured');
await delay(200);
expect(streamConfig.url).to.include('wss://');
console.log('✅ WebRTC WebSocket URL format correct');
await delay(200);
});
});
describe('Stream Subscription Workflow', () => {
it('should demonstrate complete subscription workflow', async () => {
console.log('🔍 Testing complete subscription workflow...');
await delay(500);
// Step 1: Channel setup
const channel = {
id: 'workflow-test-channel',
name: 'Workflow Test Channel',
status: 'active'
};
expect(channel.status).to.equal('active');
console.log('✅ Channel setup completed');
await delay(300);
// Step 2: Authentication
const auth = {
token: 'workflow-test-token',
expires: Date.now() + 3600000, // 1 hour from now
permissions: ['view', 'subscribe']
};
expect(auth.permissions).to.include('subscribe');
console.log('✅ Authentication configured');
await delay(300);
// Step 3: Stream connection
const connection = {
status: 'connecting',
streamType: 'realtime',
quality: 'high'
};
expect(connection.status).to.equal('connecting');
console.log('✅ Stream connection initiated');
await delay(300);
// Step 4: Subscription active
connection.status = 'active';
expect(connection.status).to.equal('active');
console.log('✅ Subscription active');
await delay(300);
console.log('🎯 Complete workflow test passed');
});
});
});

View File

@@ -1,98 +0,0 @@
import { ITestContext, IChannelTestContext, IPublisherTestContext, ISubscriberTestContext } from '../interfaces/ITestContext';
export interface TestContextConfig {
applicationCredentials: {
id: string;
secret: string;
};
uri: {
pcast: string;
ingest: string;
channel: string;
};
streamKind: string;
}
export class TestContextFactory {
private static createBaseContext(config: TestContextConfig): ITestContext {
return {
applicationCredentials: config.applicationCredentials,
uri: config.uri,
streamKind: config.streamKind,
cleanup: [],
addCleanupTask: function(task: () => Promise<void>): void {
this.cleanup.push(task);
},
executeCleanup: async function(): Promise<void> {
for (const task of this.cleanup) {
try {
await task();
} catch (error) {
console.error('Cleanup task failed:', error);
}
}
}
};
}
static createChannelContext(config: TestContextConfig): IChannelTestContext {
const baseContext = this.createBaseContext(config);
try {
// Try to import the real PCastApi if available
const PCastApi = require('@techniker-me/pcast-api');
const pcastApi = new PCastApi(config.uri.pcast, config.applicationCredentials);
return {
...baseContext,
pcastApi,
channel: null as any, // Will be set later
};
} catch (error) {
console.warn('Real PCastApi not available, using mock API:', error);
// Return mock context for testing
return {
...baseContext,
pcastApi: {
createChannel: async () => ({ channelId: 'mock-channel' }),
deleteChannel: async () => ({ status: 'ok' })
} as any,
channel: null as any,
};
}
}
static createPublisherContext(config: TestContextConfig): IPublisherTestContext {
const channelContext = this.createChannelContext(config);
try {
// Try to import the real RtmpPush if available
const RtmpPush = require('@techniker-me/rtmp-push');
return {
...channelContext,
publishSource: new RtmpPush(),
};
} catch (error) {
console.warn('Real RtmpPush not available, using mock:', error);
return {
...channelContext,
publishSource: {} as any,
};
}
}
static createSubscriberContext(config: TestContextConfig): ISubscriberTestContext {
const channelContext = this.createChannelContext(config);
return {
...channelContext,
publishDestination: {
token: '',
capabilities: [],
},
};
}
}

View File

@@ -1,32 +0,0 @@
export interface ITestContext {
readonly applicationCredentials: {
readonly id: string;
readonly secret: string;
};
readonly uri: {
readonly pcast: string;
readonly ingest: string;
readonly channel: string;
};
readonly streamKind: string;
readonly cleanup: (() => Promise<void>)[];
addCleanupTask(task: () => Promise<void>): void;
executeCleanup(): Promise<void>;
}
export interface IChannelTestContext extends ITestContext {
channel: any; // Mutable for test setup
readonly pcastApi: any; // Replace 'any' with proper PCastApi type
}
export interface IPublisherTestContext extends IChannelTestContext {
readonly publishSource: any; // Replace 'any' with proper RtmpPush type
}
export interface ISubscriberTestContext extends IChannelTestContext {
publishDestination: { // Mutable for test setup
token: string;
readonly capabilities: string[];
};
}

View File

@@ -1,53 +0,0 @@
import { IChannelTestContext } from '../interfaces/ITestContext';
// Mock Channel type for now to avoid import issues
interface MockChannel {
channelId: string;
status?: string;
}
export class ChannelService {
static async createTestChannel(context: IChannelTestContext): Promise<MockChannel> {
try {
// Try to import the real API if available
const { Channel } = await import('@techniker-me/pcast-api');
const channelAlias = `UAT#${new Date().toISOString()}#${context.streamKind}`;
const channelDescription = `UAT#SubscribeWorkflow#${context.streamKind}`;
const channelOptions: string[] = [];
const channel = await context.pcastApi.createChannel(channelAlias, channelDescription, channelOptions);
// Add cleanup task
context.addCleanupTask(async () => {
console.log(`${new Date().toISOString()} [SubscribeWorkflow] [cleanup] deleting channel [${JSON.stringify(channel, null, 2)}]`);
try {
const deleteResponse = await context.pcastApi.deleteChannel(channel.channelId);
if (deleteResponse.status !== 'ok') {
throw new Error(`[SubscribeWorkflow] [cleanup] Error: Unable to delete test channel due to [${JSON.stringify(deleteResponse, null, 2)}]`);
}
} catch (error) {
console.error('Failed to delete channel during cleanup:', error);
}
});
return channel;
} catch (error) {
console.warn('Real PCast API not available, using mock channel:', error);
// Return mock channel for testing
const mockChannel: MockChannel = {
channelId: `mock-${Date.now()}-${context.streamKind}`
};
// Add mock cleanup task
context.addCleanupTask(async () => {
console.log(`${new Date().toISOString()} [SubscribeWorkflow] [cleanup] cleaning up mock channel [${mockChannel.channelId}]`);
});
return mockChannel;
}
}
}

View File

@@ -1,34 +0,0 @@
import { ISubscriberTestContext } from '../interfaces/ITestContext';
export class TokenService {
static generateSubscriberToken(context: ISubscriberTestContext): string {
try {
// Try to import the real TokenBuilder if available
const TokenBuilder = require('phenix-edge-auth');
const { browser } = require('@wdio/globals');
const { capabilities: { browserName, browserVersion, platformName } } = browser;
const subscriberTag = `UAT#${new Date().toISOString()}#SubscribeWorkflow#${context.streamKind}#${browserName}@${browserVersion}:${platformName}`;
const tokenBuilder = new TokenBuilder()
.withApplicationId(context.applicationCredentials.id)
.withSecret(context.applicationCredentials.secret)
.expiresInSeconds(3600)
.forChannel(context.channel.channelId)
.applyTags(subscriberTag);
// Add capabilities if they exist
context.publishDestination.capabilities?.forEach(subscriberCapability =>
tokenBuilder.withCapability(subscriberCapability)
);
return tokenBuilder.build();
} catch (error) {
console.warn('Real TokenBuilder not available, using mock token:', error);
// Return mock token for testing
return `mock-token-${Date.now()}-${context.streamKind}`;
}
}
}

View File

@@ -1,79 +0,0 @@
export interface IStreamKindStrategy {
getStreamKind(): string;
getChannelOptions(): string[];
getTestDescription(): string;
}
export class RealTimeStreamStrategy implements IStreamKindStrategy {
getStreamKind(): string {
return 'RealTime';
}
getChannelOptions(): string[] {
return [];
}
getTestDescription(): string {
return 'RealTime stream subscription test';
}
}
export class ChunkedStreamHLSStrategy implements IStreamKindStrategy {
getStreamKind(): string {
return 'ChunkedStream HLS';
}
getChannelOptions(): string[] {
return ['hls'];
}
getTestDescription(): string {
return 'ChunkedStream HLS subscription test';
}
}
export class ChunkedStreamDASHStrategy implements IStreamKindStrategy {
getStreamKind(): string {
return 'ChunkedStream DASH';
}
getChannelOptions(): string[] {
return ['dash'];
}
getTestDescription(): string {
return 'ChunkedStream DASH subscription test';
}
}
// Example of how easy it is to add new stream types
export class WebRTCStreamStrategy implements IStreamKindStrategy {
getStreamKind(): string {
return 'WebRTC';
}
getChannelOptions(): string[] {
return ['webrtc', 'low-latency'];
}
getTestDescription(): string {
return 'WebRTC stream subscription test';
}
}
export class StreamKindStrategyFactory {
static createStrategy(streamType: string): IStreamKindStrategy {
switch (streamType.toLowerCase()) {
case 'realtime':
return new RealTimeStreamStrategy();
case 'hls':
return new ChunkedStreamHLSStrategy();
case 'dash':
return new ChunkedStreamDASHStrategy();
case 'webrtc':
return new WebRTCStreamStrategy();
default:
throw new Error(`Unknown stream type: ${streamType}`);
}
}
}