Skip to content

Commit

Permalink
Merge pull request #426 from THEOplayer/maintenance/additional_e2e
Browse files Browse the repository at this point in the history
Add more e2e tests
  • Loading branch information
tvanlaerhoven authored Oct 30, 2024
2 parents 913bfb3 + 7445e87 commit aae4aa8
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 52 deletions.
6 changes: 3 additions & 3 deletions e2e/src/tests/Ads.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TestScope } from 'cavy';
import { AdDescription, AdEventType, PlayerEventType, SourceDescription } from 'react-native-theoplayer';
import { AdDescription, AdEventType, PlayerEventType, SourceDescription, AdEvent } from 'react-native-theoplayer';
import hls from '../res/hls.json';
import ads from '../res/ads.json';
import { getTestPlayer } from '../components/TestableTHEOplayerView';
Expand All @@ -16,8 +16,8 @@ export default function (spec: TestScope) {
const playEventsPromise = waitForPlayerEventTypes(player, [PlayerEventType.SOURCE_CHANGE, PlayerEventType.PLAY, PlayerEventType.PLAYING]);

const adEventsPromise = waitForPlayerEvents(player, [
{ type: PlayerEventType.AD_EVENT, subType: AdEventType.AD_BREAK_BEGIN },
{ type: PlayerEventType.AD_EVENT, subType: AdEventType.AD_BEGIN },
{ type: PlayerEventType.AD_EVENT, subType: AdEventType.AD_BREAK_BEGIN } as AdEvent,
{ type: PlayerEventType.AD_EVENT, subType: AdEventType.AD_BEGIN } as AdEvent,
]);

// Start autoplay
Expand Down
62 changes: 47 additions & 15 deletions e2e/src/tests/Basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,69 @@ import { PlayerEventType, SourceDescription } from 'react-native-theoplayer';
import dash from '../res/dash.json';
import hls from '../res/hls.json';
import mp4 from '../res/mp4.json';
import { getTestPlayer } from '../components/TestableTHEOplayerView';
import { expect, waitForPlayerEventType, waitForPlayerEventTypes } from '../utils/Actions';
import { expect, preparePlayerWithSource, waitForPlayerEventType, waitForPlayerEventTypes } from '../utils/Actions';

function testBasicPlayout(spec: TestScope, title: string, source: SourceDescription, autoplay: boolean) {
const SEEK_THRESHOLD = 1e-1;

function testBasicPlayout(spec: TestScope, title: string, source: SourceDescription) {
spec.describe(title, function () {
spec.it('dispatches sourcechange, play and playing in order', async function () {
const player = await getTestPlayer();
const eventsPromise = waitForPlayerEventTypes(player, [PlayerEventType.SOURCE_CHANGE, PlayerEventType.PLAY, PlayerEventType.PLAYING]);
spec.it('dispatches sourcechange event on setting a source without autoplay', async function () {
// Set source and wait for playback
const player = await preparePlayerWithSource(source, false);

// Start autoplay
player.autoplay = autoplay;
player.source = source;
// Still playing
expect(player.paused).toBeTruthy();
});

// Expect events.
await eventsPromise;
spec.it('dispatches sourcechange, play and playing events in order on setting a source with autoplay', async function () {
// Set source and wait for playback
const player = await preparePlayerWithSource(source);

// Still playing
expect(player.paused).toBeFalsy();
});

spec.it('dispatches a seeked event after seeking', async function () {
// Set source and wait for playback
const player = await preparePlayerWithSource(source);

// Seek
const seekPromise = waitForPlayerEventType(player, PlayerEventType.SEEKED);
player.currentTime = 10e3;
const seekTime = 10e3;
player.currentTime = seekTime;

// Wait for `seeked` event.
await seekPromise;

// Expect currentTime to be updated.
expect(player.currentTime).toBeSmallerThanOrEqual(seekTime + SEEK_THRESHOLD);
});

spec.it('dispatches paused, play and playing events after pausing & resuming playback', async function () {
// Set source and wait for playback
const player = await preparePlayerWithSource(source);

// Pause play-out.
const pausePromise = waitForPlayerEventType(player, PlayerEventType.PAUSE);
player.pause();

// Wait for 'paused' event.
await pausePromise;

// Resume play-out.
const playPromises = waitForPlayerEventTypes(player, [PlayerEventType.PLAY, PlayerEventType.PLAYING]);
player.play();

// Wait for 'play' and 'playing' events.
await playPromises;
});
});
}

export default function (spec: TestScope) {
if (Platform.OS === 'android' || Platform.OS === 'web') {
testBasicPlayout(spec, 'Set DASH source and auto-play', dash[0], true);
testBasicPlayout(spec, 'Set DASH source and auto-play', dash[0]);
}
testBasicPlayout(spec, 'Set HLS source and auto-play', hls[0], true);
testBasicPlayout(spec, 'Set mp4 source and auto-play', mp4[0], true);
testBasicPlayout(spec, 'Set HLS source and auto-play', hls[0]);
testBasicPlayout(spec, 'Set mp4 source and auto-play', mp4[0]);
}
24 changes: 11 additions & 13 deletions e2e/src/tests/Connector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ type PlayerFn = (player: THEOplayer) => Promise<void> | void;
const NoOpPlayerFn: PlayerFn = (_player: THEOplayer) => {};

function testConnector(spec: TestScope, onCreate: PlayerFn, onUseAPI: PlayerFn, onDestroy: PlayerFn) {
spec.it('successfully creates the connector and connects to the player', async function () {
await onCreate(await getTestPlayer());
});

spec.it('handles sourcechange, play and playing events on play-out', async function () {
spec.it('successfully creates the connector, connects to the player, uses API, and cleans up and destroys.', async function () {
const player = await getTestPlayer();

// Create connector.
await onCreate(player);

const eventsPromise = waitForPlayerEventTypes(player, [PlayerEventType.SOURCE_CHANGE, PlayerEventType.PLAY, PlayerEventType.PLAYING]);

// Start autoplay
Expand All @@ -28,16 +28,14 @@ function testConnector(spec: TestScope, onCreate: PlayerFn, onUseAPI: PlayerFn,

// Expect events.
await eventsPromise;
});

if (onUseAPI !== NoOpPlayerFn) {
spec.it('successfully uses connector API', async function () {
await onUseAPI(await getTestPlayer());
});
}
// Use connector API
if (onUseAPI !== NoOpPlayerFn) {
await onUseAPI(player);
}

spec.it('successfully cleans up on destroy', async function () {
await onDestroy(await getTestPlayer());
// Clean-up and destroy connector.
await onDestroy(player);
});
}

Expand Down
42 changes: 42 additions & 0 deletions e2e/src/tests/PresentationMode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { TestScope } from 'cavy';
import { PlayerEventType, PresentationMode, PresentationModeChangeEvent } from 'react-native-theoplayer';
import hls from '../res/hls.json';
import { expect, preparePlayerWithSource, waitForPlayerEvent } from '../utils/Actions';
import { sleep } from '../utils/TimeUtils';

export default function (spec: TestScope) {
spec.describe('Switches between presentation modes.', function () {
spec.it('dispatches presentationmodechange events between inline and fullscreen.', async function () {
const player = await preparePlayerWithSource(hls[0]);

// Switch to fullscreen.
const fullscreenPromise = waitForPlayerEvent(player, {
type: PlayerEventType.PRESENTATIONMODE_CHANGE,
presentationMode: PresentationMode.fullscreen,
previousPresentationMode: PresentationMode.inline,
} as PresentationModeChangeEvent);
player.presentationMode = PresentationMode.fullscreen;

// Wait for 'presentationmodechange' event.
await fullscreenPromise;

// Play-out should not pause.
await sleep(500);
expect(player.paused).toBeFalsy();

// Switch back to inline.
const inlinePromise = waitForPlayerEvent(player, {
type: PlayerEventType.PRESENTATIONMODE_CHANGE,
presentationMode: PresentationMode.inline,
previousPresentationMode: PresentationMode.inline,
} as PresentationModeChangeEvent);
player.presentationMode = PresentationMode.inline;

// Wait for 'presentationmodechange' event.
await inlinePromise;

// Play-out should not pause.
expect(player.paused).toBeFalsy();
});
});
}
5 changes: 3 additions & 2 deletions e2e/src/tests/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Basic from './Basic.spec';
import Ads from './Ads.spec';
import Basic from './Basic.spec';
import Connector from './Connector.spec';
import PresentationMode from './PresentationMode.spec';

export default [Basic, Ads, Connector];
export default [Basic, Ads, Connector, PresentationMode];
81 changes: 62 additions & 19 deletions e2e/src/utils/Actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// noinspection JSUnusedGlobalSymbols

import { ErrorEvent, type Event, PlayerEventType, THEOplayer } from 'react-native-theoplayer';
import { ErrorEvent, type Event, PlayerEventType, SourceDescription, THEOplayer } from 'react-native-theoplayer';
import { getTestPlayer } from '../components/TestableTHEOplayerView';

export interface TestOptions {
timeout: number;
Expand All @@ -10,6 +11,24 @@ export const defaultTestOptions: TestOptions = {
timeout: 10000,
};

export async function preparePlayerWithSource(source: SourceDescription, autoplay: boolean = true): Promise<THEOplayer> {
const player = await getTestPlayer();
const eventsPromise = waitForPlayerEventType(player, PlayerEventType.SOURCE_CHANGE);
const eventsPromiseAutoPlay = waitForPlayerEventTypes(player, [PlayerEventType.SOURCE_CHANGE, PlayerEventType.PLAY, PlayerEventType.PLAYING]);

// Start autoplay
player.autoplay = autoplay;
player.source = source;

// Wait for `sourcechange`, `play` and `playing` events.
if (autoplay) {
await eventsPromiseAutoPlay;
} else {
await eventsPromise;
}
return player;
}

export const waitForPlayerEventType = async (
player: THEOplayer,
type: PlayerEventType,
Expand Down Expand Up @@ -63,7 +82,7 @@ export const waitForPlayerEvents = async <EType extends Event<PlayerEventType>>(
}
const expectedEvent = eventMap[0].event;
events.push(receivedEvent);
console.debug('[waitForPlayerEvents]', `Received event '${JSON.stringify(receivedEvent.type)}' - waiting for ${expectedEvent.type}`);
console.debug('[waitForPlayerEvents]', `Received event ${JSON.stringify(receivedEvent.type)} - waiting for ${expectedEvent.type}`);
const index = eventMap.findIndex((e) => propsMatch(e.event, receivedEvent));
const isExpected = index <= 0;

Expand Down Expand Up @@ -108,34 +127,58 @@ const withTimeOut = (promise: Promise<any>, timeout: number): Promise<any> => {
});
};

export function expect(actual: any) {
export function expect(actual: any, desc?: string) {
const descPrefix = desc ? `${desc}: ` : '';

const logPass = (msg: string) => console.log(`${descPrefix}${msg} ✅`);
const throwErr = (msg: string) => {
throw new Error(`${descPrefix}${msg} ❌`);
};

return {
toBe(expected: any) {
if (actual !== expected) {
throw new Error(`Expected ${actual} to be ${expected} ❌`);
}
console.log(`Passed: ${actual} == ${expected} ✅`);
if (actual === expected) logPass(`${actual} == ${expected}`);
else throwErr(`Expected ${actual} to be ${expected}`);
},

toNotBe(expected: any) {
if (actual !== expected) logPass(`${actual} != ${expected}`);
else throwErr(`Expected ${actual} not to be ${expected}`);
},

toEqual(expected: any) {
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(`Expected ${JSON.stringify(actual)} to equal ${JSON.stringify(expected)} ❌`);
}
console.log(`Passed: ${JSON.stringify(actual)} !== ${JSON.stringify(expected)} ✅`);
if (JSON.stringify(actual) === JSON.stringify(expected)) logPass(`Expected ${actual} to equal ${expected}`);
else throwErr(`Expected ${actual} to equal ${expected}`);
},

toBeGreaterThan(expected: number) {
if (actual > expected) logPass(`${actual} > ${expected}`);
else throwErr(`Expected ${actual} to be greater than ${expected}`);
},

toBeGreaterThanOrEqual(expected: number) {
if (actual >= expected) logPass(`${actual} >= ${expected}`);
else throwErr(`Expected ${actual} to be greater than or equal to ${expected}`);
},

toBeSmallerThan(expected: number) {
if (actual < expected) logPass(`${actual} < ${expected}`);
else throwErr(`Expected ${actual} to be smaller than ${expected}`);
},

toBeSmallerThanOrEqual(expected: number) {
if (actual <= expected) logPass(`${actual} <= ${expected}`);
else throwErr(`Expected ${actual} to be smaller than or equal to ${expected}`);
},

toBeTruthy() {
if (!actual) {
throw new Error(`Expected ${actual} to be truthy ❌`);
}
console.log(`Passed: ${actual} is truthy ✅`);
if (actual) logPass(`${actual} is truthy`);
else throwErr(`Expected ${actual} to be truthy`);
},

toBeFalsy() {
if (actual) {
throw new Error(`Expected ${actual} to be falsy ❌`);
}
console.log(`Passed: ${actual} is falsy ✅`);
if (!actual) logPass(`${actual} is falsy`);
else throwErr(`Expected ${actual} to be falsy`);
},
};
}
Expand Down

0 comments on commit aae4aa8

Please sign in to comment.