Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend documentation for integration tests #337

Merged
merged 3 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 17 additions & 65 deletions integration_test/README.md
Original file line number Diff line number Diff line change
@@ -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 <kbd>R</kbd> key twice or select **"Reload"** from the **Developer Menu** (<kbd>Ctrl</kbd> + <kbd>M</kbd> (on Window and Linux) or <kbd>Cmd ⌘</kbd> + <kbd>M</kbd> (on macOS)) to see your changes!

For **iOS**: Hit <kbd>Cmd ⌘</kbd> + <kbd>R</kbd> 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.
145 changes: 145 additions & 0 deletions integration_test/playertesting/PlayerTesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,60 @@ 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<void>
): Promise<void> => {
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 <T>(
fn: (player: Player) => Promise<T>
): Promise<T> => {
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 <T extends Event>(
expectationConvertible: SingleEventExpectation | EventType,
timeoutSeconds: number = 10
Expand All @@ -36,6 +77,27 @@ export const expectEvent = async <T extends Event>(
);
};

/**
* 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
Expand All @@ -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 <E extends Event, P>(
fn: (player: Player) => Promise<P>,
expectationConvertible: SingleEventExpectation | EventType,
Expand All @@ -58,6 +138,30 @@ export const callPlayerAndExpectEvent = async <E extends Event, P>(
);
};

/**
* 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[],
Expand All @@ -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
Expand All @@ -80,13 +201,37 @@ 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
): Promise<TimeChangedEvent> => {
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
Expand Down