ROAR DocumentationROAR Documentation
  • Databases
  • Workflows
  • Application
  • GitHub Actions
  • Dashboard Components
  • Firebase App Check
  • Cloud Functions
  • Backend Architecture
  • Internationalization
  • Integrating New Apps
  • Optimizing Assets
  • ROAR Redivis Instance
  • Logging and Querying
  • Emulation
  • Data Guidelines
  • Data Organization
  • Data Requests
GitHub
  • Databases
  • Workflows
  • Application
  • GitHub Actions
  • Dashboard Components
  • Firebase App Check
  • Cloud Functions
  • Backend Architecture
  • Internationalization
  • Integrating New Apps
  • Optimizing Assets
  • ROAR Redivis Instance
  • Logging and Querying
  • Emulation
  • Data Guidelines
  • Data Organization
  • Data Requests
GitHub
  • Databases
    • Database Information
    • gse-roar-admin
    • gse-roar-assessment
  • BigQuery
    • Querying Assessment Data
    • BigQuery schema: classes
    • BigQuery schema: districts
    • BigQuery schema: families
    • BigQuery schema: groups
    • BigQuery schema: schools
    • BigQuery schema: user_runs
    • BigQuery schema: user_trials
    • BigQuery schema: users
  • Workflows
    • Workflows
    • Creating an Assignment
    • Authentication
    • Creating new Users
    • User Roster Changes
    • How to Impersonate a Clever User on Localhost
  • Application

    • Auth
  • GitHub Actions
    • ROAR Apps GitHub Actions
      • GitHub Actions in ROAR Apps
      • firebase-deploy-preview.yml
      • firebase-hosting-merge.yml
      • publish-to-npm-create-new-release.yml
      • submit-dashboard-pr.yml
    • ROAR Dashboard GitHub Actions
      • GitHub Actions in the ROAR Dashboard
  • Dashboard Components
    • Dashboard Components
    • Organization Score Reports
  • Firebase App Check
    • Firebase App Check Configuration for roar-firekit and roar-dashboard
  • Backend Architecture
    • Architecture
      • Backend Architecture in ROAR
      • Data Models
      • Database Implementations
      • Error Handling Architecture in ROAR
      • Repository Layer Architecture
      • Service Layer Architecture
    • API
      • Classes
        • Class: AdministrationServiceError
        • Class: FirebaseClientError
        • Class: FirebaseImplementationError
        • Class: FirestoreAdministrationRepository
        • Class: FirestoreAdministrationRepositoryError
        • Class: abstract FirestoreBaseRepository<T>
        • Class: FirestoreFilterAdapter
        • Class: FirestoreIdentityProviderRepository
        • Class: FirestoreIdentityProviderRepositoryError
        • Class: FirestoreOrgRepository
        • Class: FirestoreOrgRepositoryError
        • Class: FirestoreRepositoryError
        • Class: FirestoreUserClaimRepository
        • Class: FirestoreUserClaimRepositoryError
        • Class: FirestoreUserRepository
        • Class: FirestoreUserRepositoryError
        • Class: IdentityProviderServiceError
        • Classes
      • Enumerations
        • Enumeration: CollectionType
        • Enumeration: IdentityProviderType
        • Enumeration: Operator
        • Enumerations
      • Functions
        • Functions
        • Function: chunkOrgs()
        • Function: createAdministrationService()
        • Function: createFirestoreImplementation()
        • Function: createIdentityProviderService()
        • Function: isEmptyOrgs()
      • Interfaces
        • Interface: Administration
        • Interface: AdministrationBaseRepository
        • Interface: AdministrationService
        • Interface: AssentConsent
        • Interface: Assessment
        • Interface: BaseModel
        • Interface: BaseRepository<T>
        • Interface: Claims
        • Interface: CompositeCondition
        • Interface: CompositeFilter
        • Interface: CreateAdministrationServiceParams<AdminRepo, OrgRepo, UserClaimRepo>
        • Interface: CreateParams
        • Interface: DeleteParams
        • Interface: EducationalOrgsList
        • Interface: FieldCondition
        • Interface: FilterAdapter<T>
        • Interface: FirestoreCreateParams
        • Interface: FirestoreDeleteParams
        • Interface: FirestoreFetchDocumentParams
        • Interface: FirestoreGetAllParams
        • Interface: FirestoreGetByIdParams
        • Interface: FirestoreGetByNameParams
        • Interface: FirestoreGetByRoarUidParams
        • Interface: FirestoreGetParams
        • Interface: FirestoreGetWithFiltersParams
        • Interface: FirestoreImplementation
        • Interface: FirestoreRunTransactionParams<T>
        • Interface: FirestoreUpdateParams
        • Interface: FutureParams
        • Interface: GetAdministrationIdsForAdministratorParams
        • Interface: GetAdministrationIdsFromOrgsParams
        • Interface: GetAllParams
        • Interface: GetByNameParams
        • Interface: GetByProviderIdParams
        • Interface: GetByRoarUidParams
        • Interface: GetParams
        • Interface: GetRoarUidParams
        • Interface: IdentityProvider
        • Interface: IdentityProviderBaseRepository
        • Interface: IdentityProviderService
        • Interface: Legal
        • Interface: OrgBase
        • Interface: OrgBaseRepository
        • Interface: OrgsList
        • Interfaces
        • Interface: Result<T>
        • Interface: RunTransactionParams<T>
        • Interface: SingleFilter
        • Interface: UpdateParams
        • Interface: User
        • Interface: UserBaseRepository
        • Interface: UserClaim
        • Interface: UserClaimBaseRepository
        • Interface: createIdentityProviderServiceParams<IDPRepo, UserClaimRepo, UserRepo>
        • Interface: getAdministrationIdsFromOrgsParams
        • Interface: _setAdministrationIdsParams
      • Type Aliases
        • Type Alias: BaseFilter
        • Type Alias: ComparisonOperator
        • Type Alias: Condition
        • Type Alias: DocumentCreatedEvent
        • Type Alias: DocumentDeletedEvent
        • Type Alias: DocumentUpdatedEvent
        • Type Alias: DocumentWrittenEvent
        • Type Alias: ParameterValue
        • Type Aliases
        • Type Alias: SelectAllCondition
      • Variables
        • Variable: FirebaseAppClient
        • Variable: FirebaseAuthClient
        • Variable: FirestoreClient
        • Variable: ORG_NAMES
        • Variables API Documentation
    • Examples
      • Examples
    • Guides
      • Guides
  • Cloud Functions
    • gse-roar-admin
      • Admin Database
      • appendToAdminClaims()
      • associateassessmentuid()
      • createAdministratorAccount()
      • createGuestDocsForGoogleUsers()
      • createLevanteGroup()
      • createLevanteUsers()
      • createnewfamily()
      • createstudentaccount()
      • mirrorClasses()
      • mirrorCustomClaims
      • mirrorDistricts()
      • mirrorFamilies()
      • mirrorGroups()
      • mirrorSchools()
      • removefromadminclaims()
      • saveSurveyResponses()
      • setuidcustomclaims()
      • softDeleteUserAssignment()
      • softDeleteUserExternalData
      • softDeleteUser()
      • syncAssignmentCreated()
      • syncAssignmentDeleted()
      • syncAssignmentUpdated()
      • syncAssignmentsOnAdministrationUpdate()
      • syncAssignmentsOnUserUpdate()
      • syncCleverOrgs()
      • syncCleverUser()
    • gse-roar-assessment
      • Assessment Database
      • organizeBucketLogsByDate()
      • setuidclaims()
      • softDeleteGuestTrial()
      • softDeleteGuest()
      • softDeleteUserRun()
      • softDeleteUserTrial()
      • syncOnRunDocUpdate()
  • Internationalization
    • ROAM Fluency
    • ROAR Letter
    • ROAR Phoneme
    • Internationalization of ROAR Apps
    • ROAR Sentence
    • ROAR Word
  • Integrating New Apps
    • Integrating Roar Apps into the Dashboard
    • Dashboard Integration
    • Monitoring and Testing
    • Preparing the App for Packaging and Deployment
    • Packaging and Publishing to npm
    • Secrets in the GitHub Repository
  • Assets Optimization
    • Optimizing Assets
    • Audio Optimization Guide
    • Image Optimization Guide
  • ROAR Redivis Instance
    • ROAR Redivis Instance
    • ROAR Data Validator Trigger
    • ROAR Data Validator
  • Logging and Querying
    • ROAR Logging
  • Emulation
    • Running the Emulator
      • Commands
    • Emulator Configuration Guide
      • Configuration
      • Cypress Configuration
      • Setup and Dependencies
      • Firebase CLI Configuration
      • Firebase Emulator Configuration
      • GitHub Secrets and Workflows
      • Importing and Exporting Data
      • Local Environment Variables
  • Clowder Implementation
    • Clowder Integration
    • Letter - Clowder
    • Multichoice - Clowder
    • Phoneme - Clowder
    • ARF & CALF - Clowder

Cypress Configuration

We can set up Cypress so that it can run tests against the Firebase Emulator Suite. This allows us to test the Firebase services that are used in the project locally and inspect the data that is being sent to the Firebase services.

Directory Structure

The following directory structure is used to configure Cypress to run tests against the Firebase Emulator Suite:

cypress/
├── e2e/
│   ├── default-tests/
│   │   ├── test1.cy.js
│   │   ├── test2.cy.js
│   │   ├── variant-tests/
│   │   │   ├── test1.cy.js
│   │   │   └── test2.cy.js
│   ├── generateVariantTests.cy.js
├── fixtures/
│   ├── generatedTestTemplate.js
│   ├── data1.json
│   └── data2.json
├── support/
│   ├── commands.js
│   ├── constants.js
│   ├── devFirebase.js
│   ├── devFirebaseConfig.js
│   ├── e2e.js
│   ├── query.js
│   └── utils.js

cypress.config.js

The cypress.config.js file is used to configure the Cypress test runner. This file is located in the root of the project directory. The following configuration options are used to setup Cypress to run tests against the Firebase Emulator Suite:

Create or overwrite the cypress.config.js file with the following content:

// eslint-disable-next-line import/no-extraneous-dependencies
import { defineConfig } from 'cypress';
import dotenv from 'dotenv';
// eslint-disable-next-line import/no-extraneous-dependencies
import cypressFsPlugin from 'cypress-fs/plugins/index';

dotenv.config({ path: '.env.test' });

export default defineConfig({
  projectId: 'app-project-id',
  e2e: {
    experimentalRunAllSpecs: true,
    retries: 2,
    // eslint-disable-next-line no-unused-vars
    setupNodeEvents(on, config) {
      return cypressFsPlugin(on, config);
    },
    env: {
      baseUrl: process.env.CYPRESS_BASE_URL ?? 'http://localhost:8000',
      timeout: 10000,
      superAdminUsername: process.env.SUPER_ADMIN_USERNAME,
      superAdminPassword: process.env.SUPER_ADMIN_PASSWORD,
      superAdminEmail: process.env.SUPER_ADMIN_EMAIL,
      superAdminId: process.env.SUPER_ADMIN_ID,
      appCheckDebugToken: process.env.APPCHECK_DEBUG_TOKEN,
    },
  },
});

This configuration allows us to mainain a separate .env.test file for the Cypress environment variables.
The projectId is set to the project ID of the Cypress Cloud project which stores the test results. Substitute with the actualy projectId found in the Cypress Cloud for the project.
The experimentalRunAllSpecs is set to true to run all the tests. The retries is set to 2 to retry the tests if they fail. The setupNodeEvents is used to setup the cypress-fs plugin to allow Cypress to read and write files.
The baseUrl is set to the local server that is running the Firebase Emulator Suite. The timeout is set to 10 seconds. The superAdminUsername, superAdminPassword, superAdminEmail, superAdminId, and appCheckDebugToken are set to the environment variables that are used in the Cypress tests.
The timeout is set to 10 seconds to allow the tests to run for a longer period of time.
The superAdminUsername, superAdminPassword, superAdminEmail, superAdminId, and appCheckDebugToken are set to the environment variables that are used in the Cypress tests and are also stored as GitHub secrets.

Supporting Files

The following files are used to support the Cypress configuration, in addition to the Cypress provisioned e2e.js, and commands.js.

constants.js

The constants.js file is used to define constants that are used in the Cypress tests. This file is located in the cypress/support/ directory.

export const TIMEOUT = Cypress.env('timeout');
export const TASK = 'task-name';
export const TEST_DIR_NAME = 'cypress/e2e/default-tests/variant-tests/';
export const COMMAND = 'play-game-command';

The contents of this file will vary depending on the app, but should at least contain the TIMEOUT, TASK, TEST_DIR_NAME, and COMMAND constants. Replace task-name with the name of the task that the variants are registered under. Replace play-game-command with the command that is used to play the game.

The COMMAND constant should be a Cypress command which initializes the game. The command should take an object as an argument, and this object should contain keys for variant parameters and language, with other keys as needed.
The command should be defined in the commands.js file.

Example COMMAND constant for ROAR Sentence:

Cypress.Commands.add(
  'playSREGame',
  ({
    startText = START_TEXT_DEFAULT,
    endText = END_TEXT_DEFAULT,
    language = 'en',
    variantParams = null,
  } = {}) => {
    if (variantParams) {
      cy.visit(`${Cypress.env('baseUrl')}/?${variantParams}`, { timeout: 2 * TIMEOUT });
    } else {
      cy.visit(`${Cypress.env('baseUrl')}`, { timeout: 2 * TIMEOUT });
    }

    cy.playIntro({
      startText: startText,
      variantParams: variantParams,
    });

    cy.playSRELoop(endText);
  },
);

generateVariantTests.cy.js

The generateVariantTests.cy.js file is used to generate the test cases for the variants. This file is located in the cypress/ directory at the top level. The following content is used to generate the test cases for the variants:

import { getRegisteredVariants } from '../support/query';
import { useDevFirebase, signInAsSuperAdmin, mapVariantParameters } from '../support/utils';
import { generatedTestTemplate } from '../fixtures/generatedTestTemplate';
import { TIMEOUT, TASK, TEST_DIR_NAME, COMMAND } from '../support/constants';

const { auth, db } = useDevFirebase('assessmentDev');

describe('Generating variant tests.', () => {
  before(() => {
    signInAsSuperAdmin(auth);
  });
  it('Creates a test spec for each variant.', () => {
    cy.wrap(getRegisteredVariants(db, TASK), { timeout: TIMEOUT }).then((docs) => {
      docs.forEach((variant) => {
        cy.log(`Found registered variant: ${variant.name} with params: ${JSON.stringify(variant.params)}`);

        const variantParams = mapVariantParameters(variant.params);

        // Create a test spec for each registered variant
        cy.writeFile(
          `${TEST_DIR_NAME}/${variant.name}.cy.js`,
          generatedTestTemplate({
            command: COMMAND,
            name: variant.name,
            params: variantParams,
          }),
        ).then(() => {
          cy.log('Successfully created test spec:', variant.name);
        });
      });
    });
  });
});

generatedTestTemplate.js

The generatedTestTemplate.js file is used to generate the test template for the variants. This file is located in the cypress/fixtures/ directory.

The following content is used to generate the test template for the variants:

export const generatedTestTemplate = ({ command = null, name = null, params = null } = {}) => `
describe('Test play through of variant: ${name}', () => {
  it('Plays the generated test spec.', () => {
    ${command}({
        variantParams: '${params}'
    });
  });
});
`;

devFirebase.js

The devFirebase.js file is used to setup the Firebase configuration for the Cypress tests. This file is located in the cypress/support/ directory.

import { getApps, initializeApp } from 'firebase/app';
import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';
import devFirebaseConfig from './devFirebaseConfig';

/**
 * Initializes a Firebase app and retrieves the Auth and Firestore services.
 * If the app with the specified name already exists, it reuses the existing app.
 * Optionally connects to the Firestore emulator if `useEmulator` is true.
 *
 * @param {Object} config - The Firebase configuration object.
 * @param {string} name - The name of the Firebase app instance.
 * @param {boolean} useEmulator - A flag indicating whether to connect to the Firestore emulator.
 * @returns {Object} - An object containing the Firebase app, Auth, and Firestore services.
 */
const initializeAndGetFirebase = (config, name, useEmulator) => {
  const existingApp = getApps().find((app) => app.name === name);

  if (existingApp) {
    console.log('Found existing app:', existingApp);
  }
  const app = existingApp || initializeApp(config, name);
  const auth = getAuth(app);
  const db = getFirestore(app);

  if (useEmulator) {
    console.log('Connecting to Firestore emulator...');
    connectFirestoreEmulator(db, 'localhost', 8080);
    console.log('Connected to Firestore emulator.');
  }

  return {
    app,
    auth,
    db,
  };
};

/**
 * Retrieves the Firebase app, Auth, and Firestore services based on the given config key.
 * It supports multiple environments (e.g., adminDev, assessmentDev) and connects to the Firestore emulator
 * if `useEmulator` is true. Returns null if the specified config is not found.
 *
 * @param {string} config - The key for selecting the Firebase configuration (e.g., 'adminDev', 'assessmentDev').
 * @param {boolean} [useEmulator=true] - A flag indicating whether to connect to the Firestore emulator.
 * @returns {Object|null} - An object containing the Firebase app, Auth, and Firestore services, or null if no config is found.
 */
export const getDevFirebase = (config, useEmulator = true) => {
  const configMap = {
    adminDev: devFirebaseConfig.adminDev,
    assessmentDev: devFirebaseConfig.assessmentDev,
  };

  const firebaseConfig = configMap[config];
  return firebaseConfig ? initializeAndGetFirebase(firebaseConfig, config, useEmulator) : null;
};

devFirebaseConfig.js

The devFirebaseConfig.js file is used to store the Firebase configuration for the Cypress tests. This file is located in the cypress/support/ directory.

const devFirebaseConfig = {
  adminDev: {
    apiKey: 'AIzaSyCl-JsYraUfofQZXpzshQ6s-E0nYzlCvvg',
    authDomain: 'gse-roar-admin-dev.firebaseapp.com',
    projectId: 'gse-roar-admin-dev',
    storageBucket: 'gse-roar-admin-dev.appspot.com',
    messagingSenderId: '401455396681',
    appId: '1:401455396681:web:859ea073a116d0aececc98',
    siteKey: '6LeTgCEqAAAAAPVXEVtWoinVf_CLYF30PaETyyiT',
    debugToken: Cypress.env('appCheckDebugToken'),
  },
  assessmentDev: {
    apiKey: 'AIzaSyCEUxEgYMp4fg2zORT0lsgn4Q6CCoMVzjU',
    authDomain: 'gse-roar-assessment-dev.firebaseapp.com',
    projectId: 'gse-roar-assessment-dev',
    storageBucket: 'gse-roar-assessment-dev.appspot.com',
    messagingSenderId: '26086061121',
    appId: '1:26086061121:web:262163d6c145b7a80bc2c0',
    siteKey: '6Ldq2SEqAAAAAKXTxXs9GnykkEZLYeVijzAKzqfQ',
    debugToken: Cypress.env('appCheckDebugToken'),
    emulatorPorts: {
      db: 8080,
      auth: 9099,
      functions: 9000,
      hosting: 5000,
    },
  },
};

export default devFirebaseConfig;

query.js

The query.js file is used to query the Firestore database. This file is located in the cypress/support/ directory.


import { collection, getDocs, query, where } from 'firebase/firestore';

export async function getRegisteredVariants(firestore, task) {
  try {
    const variantsRef = collection(firestore, 'tasks', task, 'variants');
    const q = query(variantsRef, where('registered', '==', true));
    const variantsSnapshot = await getDocs(q);

    if (variantsSnapshot.empty) {
      console.log('No registered variants found.');
      return [];
    }
    const docs = [];
    console.log(`Found ${variantsSnapshot.size} registered variants.`);
    variantsSnapshot.forEach((doc) => {
      docs.push(doc.data());
    });
    return docs;
  } catch (error) {
    console.log('Error getting documents:', error);
    return [];
  }
}

utils.js

The utils.js file is used to define utility functions for the Cypress tests. This file is located in the cypress/support/ directory.


import { signInWithEmailAndPassword } from 'firebase/auth';
import { getDevFirebase } from './devFirebase';

/**
 * Retrieves the Firebase app, Auth, and Firestore services for the specified environment.
 * This function abstracts the process of accessing Firebase services based on the provided environment name.
 *
 * @param {string} name - The environment name (e.g., 'adminDev', 'assessmentDev') used to get the corresponding Firebase configuration.
 * @returns {Object} - An object containing the Firebase app, Auth, and Firestore services for the specified environment.
 */
export const useFirebaseEmulator = (name) => {
  const firebase = getDevFirebase(name);
  return {
    app: firebase?.app,
    auth: firebase?.auth,
    db: firebase?.db,
  };
};

/**
 * Signs in as a Super Admin using Firebase Authentication within a Cypress test.
 * The credentials (email and password) are pulled from Cypress environment variables.
 *
 * @param {Object} auth - The Firebase Auth instance used to sign in.
 * @returns {Promise<Object>} - A promise that resolves to the authenticated user object after a successful sign-in.
 */
export function signInAsSuperAdmin(auth) {
  cy.then(() =>
    signInWithEmailAndPassword(auth, Cypress.env('superAdminEmail'), Cypress.env('superAdminPassword')),
  ).then((userCredential) => userCredential.user);
}

/**
 * Converts an object of variant parameters into a URL query string format.
 *
 * @param {Object} params - The object containing variant parameters to be converted.
 * @returns {string} - A string representing the variant parameters in URL query string format.
 */
export function mapVariantParameters(params) {
  return Object.keys(params)
    .map((key) => `${key}=${params[key]}`)
    .join('&');
}
Edit this page
Last Updated:
Contributors: Elijah Kelly, Kyle
Prev
Configuration
Next
Setup and Dependencies