diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02ba458c..ad40bb66 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -131,6 +131,13 @@ yarn integration-test test:ios See available API for testing [here](/integration_test/playertesting/PlayerTesting.ts). +### Adding new tests + +To add new tests: + +1. create a new file in the `specs/` folder. +1. import the new file to the `specs/index.ts` file and add it to the default exported array. + A Player Test has the following structure always: ```ts diff --git a/integration_test/README.md b/integration_test/README.md index 8bd066df..ea2bffdc 100644 --- a/integration_test/README.md +++ b/integration_test/README.md @@ -1,79 +1,31 @@ -This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). +# Integration Tests -# Getting Started +This is the integration test suite for the Bitmovin Player React Native SDK. It is based on [Cavy](https://github.com/pixielabs/cavy). +This is intended for maintainers to test the SDK on different platforms and devices. -> **Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. +## Setup -## Step 1: Start the Metro Server +To run the integration tests, you need to install depencencies first. -First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. +Run the following command from the repository root: -To start Metro, run the following command from the _root_ of your React Native project: - -```bash -# using npm -npm start - -# OR using Yarn -yarn start +```sh +yarn bootstrap ``` -## Step 2: Start your Application - -Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: +## Running the tests -### For Android +To run the tests, run the following command from the repository root: -```bash -# using npm -npm run android - -# OR using Yarn -yarn android +```sh +yarn integration-test test:ios # Run tests on iOS +yarn integration-test test:android # Run tests on Android ``` -### For iOS - -```bash -# using npm -npm run ios +Hint: You can provide a specific iOS simulator by name when using `--simulator` flag. `xcrun simctl list devices available` provides you with a list of available devices in your environment. -# OR using Yarn -yarn ios +```sh +yarn example ios --simulator="iPhone 14 Pro" ``` -If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. - -This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. - -## Step 3: Modifying your App - -Now that you have successfully run the app, let's modify it. - -1. Open `App.tsx` in your text editor of choice and edit some lines. -2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! - - For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! - -## Congratulations! :tada: - -You've successfully run and modified your React Native App. :partying_face: - -### Now what? - -- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). -- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). - -# Troubleshooting - -If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. - -# Learn More - -To learn more about React Native, take a look at the following resources: - -- [React Native Website](https://reactnative.dev) - learn more about React Native. -- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. -- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. -- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. -- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. +Note: The tests are currently only supported on iOS simulators and Android emulators. Running them on real devices is not supported at the moment. diff --git a/integration_test/playertesting/PlayerTesting.ts b/integration_test/playertesting/PlayerTesting.ts index 80a61cf0..74c13a70 100644 --- a/integration_test/playertesting/PlayerTesting.ts +++ b/integration_test/playertesting/PlayerTesting.ts @@ -13,6 +13,20 @@ import { MultipleEventsExpectation, } from './expectations'; +/** + * Starts a player test with the given configuration and test function. + * @param config The player configuration to use for the test. Pass `{}` to use the default configuration. + * @param fn The test function to run. + * @returns A promise that resolves when the test is finished. + * @throws An error if the test fails. + * @example + * ```typescript + * await startPlayerTest({}, async () => { + * // ... + * }); + * ``` + * @see {@link PlayerConfig} + */ export const startPlayerTest = async ( config: PlayerConfig, fn: () => Promise @@ -20,12 +34,39 @@ export const startPlayerTest = async ( return await PlayerTestWorld.shared.startPlayerTest(config, fn); }; +/** + * Calls the given function with the player instance. + * @param fn The function to call. + * @returns A promise that resolves when the function is finished. + * @example + * ```typescript + * await callPlayer(async (player) => { + * // ... + * }); + * ``` + * @see {@link Player} + */ export const callPlayer = async ( fn: (player: Player) => Promise ): Promise => { return await PlayerTestWorld.shared.callPlayer(fn); }; +/** + * Expects the given event to occur. + * @param expectationConvertible The event to expect. + * @param timeoutSeconds The number of seconds to wait for the event to occur. + * @returns A promise that resolves when the function is finished. + * @throws An error if the event does not occur. + * @example + * ```typescript + * await callPlayerAndExpectEvent(async (player) => { + * // ... + * }, EventType.Play); + * ``` + * @see {@link Player} + * @see {@link EventType} + */ export const expectEvent = async ( expectationConvertible: SingleEventExpectation | EventType, timeoutSeconds: number = 10 @@ -36,6 +77,27 @@ export const expectEvent = async ( ); }; +/** + * Expects the given events to occur. + * @param expectationsConvertible The events to expect. + * @param timeoutSeconds The number of seconds to wait for the events to occur. + * @returns A promise that resolves when the function is finished. + * @throws An error if the events do not occur. + * @example + * ```typescript + * await callPlayerAndExpectEvents(async (player) => { + * // ... + * }, [EventType.Play, EventType.Playing]); + * ``` + * + * ```typescript + * await callPlayerAndExpectEvents(async (player) => { + * // ... + * }, EventSequence(EventType.Play, EventType.Playing)); + * ``` + * @see {@link Player} + * @see {@link EventType} + */ export const expectEvents = async ( expectationsConvertible: MultipleEventsExpectation | EventType[], timeoutSeconds: number = 10 @@ -46,6 +108,24 @@ export const expectEvents = async ( ); }; +/** + * Starts listening for the specified `SingleEventExpectation` before executing the passed `fn` function. + * This is the race-condition-safe version of calling `callPlayer` and `expectEvent` after that. + * Useful when events are directly tied to calls in the `fn` function block and therefore synchronously emitted. + * @param fn The function to call. + * @param expectationConvertible The event to expect. + * @param timeoutSeconds The number of seconds to wait for the event to occur. + * @returns A promise that resolves when the function is finished. + * @throws An error if the event does not occur. + * @example + * ```typescript + * await callPlayerAndExpectEvent(async (player) => { + * // ... + * }, EventType.Play); + * ``` + * @see {@link Player} + * @see {@link EventType} + */ export const callPlayerAndExpectEvent = async ( fn: (player: Player) => Promise

, expectationConvertible: SingleEventExpectation | EventType, @@ -58,6 +138,30 @@ export const callPlayerAndExpectEvent = async ( ); }; +/** + * Starts listening for the specified `MultipleEventExpectation` before executing the passed `fn` function. + * This is the race-condition-safe version of calling `callPlayer` and `expectEvents` after that. + * Useful when events are directly tied to calls in the `fn` function block and therefore synchronously emitted. + * @param fn The function to call. + * @param expectationsConvertible The events to expect. + * @param timeoutSeconds The number of seconds to wait for the events to occur. + * @returns A promise that resolves when the function is finished. + * @throws An error if the events do not occur. + * @example + * ```typescript + * await callPlayerAndExpectEvents(async (player) => { + * // ... + * }, [EventType.Play, EventType.Playing]); + * ``` + * + * ```typescript + * await callPlayerAndExpectEvents(async (player) => { + * // ... + * }, EventSequence(EventType.Play, EventType.Playing)); + * ``` + * @see {@link Player} + * @see {@link EventType} + */ export const callPlayerAndExpectEvents = async ( fn: (player: Player) => void, expectationsConvertible: MultipleEventsExpectation | EventType[], @@ -70,6 +174,23 @@ export const callPlayerAndExpectEvents = async ( ); }; +/** + * Loads the given source configuration and expects `ReadyEvent` to occur. + * @param sourceConfig The source configuration to load. + * @param timeoutSeconds The number of seconds to wait for the event to occur. + * @returns A promise that resolves when the function is finished. + * @throws An error if the event does not occur. + * @example + * ```typescript + * await loadSourceConfig({ + * url: 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8', + * type: SourceType.HLS, + * }); + * ``` + * @see {@link SourceConfig} + * @see {@link EventType} + * @see {@link ReadyEvent} + */ export const loadSourceConfig = async ( sourceConfig: SourceConfig, timeoutSeconds: number = 10 @@ -80,6 +201,18 @@ export const loadSourceConfig = async ( ); }; +/** + * Plays the player for the given time and expects `TimeChangedEvent` to occur. + * @param time The time to play for. + * @param timeoutSeconds The number of seconds to wait for the event to occur. + * @returns A promise that resolves when the function is finished. + * @throws An error if the event does not occur. + * @example + * ```typescript + * await playFor(5); + * ``` + * @see {@link TimeChangedEvent} + */ export const playFor = async ( time: number, timeoutSeconds: number = 10 @@ -87,6 +220,18 @@ export const playFor = async ( return await PlayerTestWorld.shared.playFor(time, timeoutSeconds); }; +/** + * Plays the player until the given time and expects `TimeChangedEvent` to occur. + * @param time The time to play until. + * @param timeoutSeconds The number of seconds to wait for the event to occur. + * @returns A promise that resolves when the function is finished. + * @throws An error if the event does not occur. + * @example + * ```typescript + * await playUntil(5); + * ``` + * @see {@link TimeChangedEvent} + */ export const playUntil = async ( time: number, timeoutSeconds: number = 10