Full Code of trentmwillis/ember-exam for AI

main db0fe23257e2 cached
200 files
339.4 KB
95.3k tokens
157 symbols
1 requests
Download .txt
Showing preview only (385K chars total). Download the full file or copy to clipboard to get everything.
Repository: trentmwillis/ember-exam
Branch: main
Commit: db0fe23257e2
Files: 200
Total size: 339.4 KB

Directory structure:
gitextract_2rp793zd/

├── .codeclimate.yml
├── .editorconfig
├── .ember-cli
├── .github/
│   ├── renovate.json5
│   └── workflows/
│       ├── ci.yml
│       ├── gh-pages.yml
│       ├── plan-release.yml
│       ├── publish.yml
│       └── release.yml
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc.js
├── .release-plan.json
├── .watchmanconfig
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── RELEASE.md
├── addon-test-support/
│   ├── -private/
│   │   ├── async-iterator.js
│   │   ├── ember-exam-test-loader.js
│   │   ├── filter-test-modules.js
│   │   ├── get-url-params.js
│   │   ├── patch-testem-output.js
│   │   ├── split-test-modules.js
│   │   └── weight-test-modules.js
│   ├── index.d.ts
│   ├── index.js
│   ├── load.js
│   └── start.js
├── docs-app/
│   ├── .gitignore
│   ├── .vitepress/
│   │   ├── config.mts
│   │   └── theme/
│   │       ├── index.ts
│   │       └── style.css
│   ├── ember-try-and-ci.md
│   ├── filtering.md
│   ├── index.md
│   ├── load-balancing.md
│   ├── module-metadata.md
│   ├── package.json
│   ├── preserve-test-name.md
│   ├── quickstart.md
│   ├── randomization-iterator.md
│   ├── randomization.md
│   ├── split-parallel.md
│   ├── splitting.md
│   ├── test-suite-segmentation.md
│   └── tsconfig.json
├── ember-cli-build.js
├── eslint.config.mjs
├── index.js
├── lib/
│   ├── commands/
│   │   ├── exam/
│   │   │   └── iterate.js
│   │   ├── exam.js
│   │   ├── index.js
│   │   └── task/
│   │       ├── test-server.js
│   │       └── test.js
│   └── utils/
│       ├── config-reader.js
│       ├── execution-state-manager.js
│       ├── file-system-helper.js
│       ├── query-helper.js
│       ├── test-page-helper.js
│       ├── testem-events.js
│       └── tests-options-validator.js
├── node-tests/
│   ├── .eslintrc
│   ├── acceptance/
│   │   ├── exam/
│   │   │   └── vite/
│   │   │       └── vite-test.js
│   │   ├── exam-iterate-test.js
│   │   ├── exam-test.js
│   │   └── helpers.js
│   ├── fixtures/
│   │   ├── browser-exit.js
│   │   ├── failure.js
│   │   ├── test-helper-with-load.js
│   │   └── vite-eager-test-load.html
│   ├── list.mjs
│   └── unit/
│       ├── commands/
│       │   └── exam-test.js
│       └── utils/
│           ├── config-reader-test.js
│           ├── execution-state-manager-test.js
│           ├── query-helper-test.js
│           ├── test-page-helper-test.js
│           ├── testem-events-test.js
│           └── tests-options-validator-test.js
├── package.json
├── pnpm-workspace.yaml
├── test-apps/
│   ├── broccoli/
│   │   ├── .editorconfig
│   │   ├── .ember-cli
│   │   ├── .github/
│   │   │   └── workflows/
│   │   │       └── ci.yml
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc.js
│   │   ├── .stylelintignore
│   │   ├── .stylelintrc.js
│   │   ├── .template-lintrc.js
│   │   ├── .watchmanconfig
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── app.js
│   │   │   ├── components/
│   │   │   │   └── .gitkeep
│   │   │   ├── controllers/
│   │   │   │   └── .gitkeep
│   │   │   ├── helpers/
│   │   │   │   └── .gitkeep
│   │   │   ├── index.html
│   │   │   ├── models/
│   │   │   │   └── .gitkeep
│   │   │   ├── router.js
│   │   │   ├── routes/
│   │   │   │   └── .gitkeep
│   │   │   ├── styles/
│   │   │   │   └── app.css
│   │   │   └── templates/
│   │   │       └── application.hbs
│   │   ├── config/
│   │   │   ├── ember-cli-update.json
│   │   │   ├── environment.js
│   │   │   ├── optional-features.json
│   │   │   └── targets.js
│   │   ├── ember-cli-build.js
│   │   ├── eslint.config.mjs
│   │   ├── package.json
│   │   ├── public/
│   │   │   └── robots.txt
│   │   ├── testem.js
│   │   └── tests/
│   │       ├── index.html
│   │       └── test-helper.js
│   ├── embroider3-webpack/
│   │   ├── .editorconfig
│   │   ├── .ember-cli
│   │   ├── .github/
│   │   │   └── workflows/
│   │   │       └── ci.yml
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc.js
│   │   ├── .stylelintignore
│   │   ├── .stylelintrc.js
│   │   ├── .template-lintrc.js
│   │   ├── .watchmanconfig
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── app.js
│   │   │   ├── components/
│   │   │   │   └── .gitkeep
│   │   │   ├── controllers/
│   │   │   │   └── .gitkeep
│   │   │   ├── deprecation-workflow.js
│   │   │   ├── helpers/
│   │   │   │   └── .gitkeep
│   │   │   ├── index.html
│   │   │   ├── models/
│   │   │   │   └── .gitkeep
│   │   │   ├── router.js
│   │   │   ├── routes/
│   │   │   │   └── .gitkeep
│   │   │   ├── styles/
│   │   │   │   └── app.css
│   │   │   └── templates/
│   │   │       └── application.hbs
│   │   ├── config/
│   │   │   ├── ember-cli-update.json
│   │   │   ├── environment.js
│   │   │   ├── optional-features.json
│   │   │   └── targets.js
│   │   ├── ember-cli-build.js
│   │   ├── eslint.config.mjs
│   │   ├── package.json
│   │   ├── public/
│   │   │   └── robots.txt
│   │   ├── testem.js
│   │   └── tests/
│   │       ├── index.html
│   │       └── test-helper.js
│   └── vite-with-compat/
│       ├── .editorconfig
│       ├── .ember-cli
│       ├── .gitignore
│       ├── .prettierignore
│       ├── .prettierrc.mjs
│       ├── .template-lintrc.mjs
│       ├── .watchmanconfig
│       ├── README.md
│       ├── app/
│       │   ├── app.js
│       │   ├── config/
│       │   │   └── environment.js
│       │   └── router.js
│       ├── babel.config.cjs
│       ├── config/
│       │   ├── ember-cli-update.json
│       │   ├── environment.js
│       │   ├── optional-features.json
│       │   └── targets.js
│       ├── ember-cli-build.js
│       ├── eslint.config.mjs
│       ├── index.html
│       ├── package.json
│       ├── public/
│       │   └── robots.txt
│       ├── testem.cjs
│       ├── tests/
│       │   ├── index.html
│       │   ├── integration/
│       │   │   ├── a-test.gjs
│       │   │   └── b-test.gjs
│       │   └── test-helper.js
│       └── vite.config.mjs
├── testem.js
├── testem.multiple-test-page.js
├── testem.no-test-page.js
├── testem.simple-test-page.js
└── tests/
    ├── dummy/
    │   ├── app/
    │   │   ├── app.js
    │   │   ├── index.html
    │   │   ├── router.js
    │   │   └── styles/
    │   │       └── app.css
    │   ├── config/
    │   │   ├── ember-cli-update.json
    │   │   ├── ember-try.js
    │   │   ├── environment.js
    │   │   ├── optional-features.json
    │   │   └── targets.js
    │   └── public/
    │       ├── crossdomain.xml
    │       └── robots.txt
    ├── index.html
    ├── test-helper.js
    └── unit/
        ├── async-iterator-test.js
        ├── filter-test-modules-test.js
        ├── multiple-edge-cases-test.js
        ├── multiple-ember-tests-test.js
        ├── multiple-tests-test.js
        ├── test-loader-test.js
        ├── testem-output-test.js
        └── weight-test-modules-test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .codeclimate.yml
================================================
---
engines:
  duplication:
    enabled: true
    config:
      languages:
        javascript:
          mass_threshold: 50

  eslint:
    enabled: true
  fixme:
    enabled: true
ratings:
  paths:
  - "**.js"
exclude_paths:
- config/
- tests/
- vendor/


================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true


[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2

[*.hbs]
insert_final_newline = false

[*.{diff,md}]
trim_trailing_whitespace = false


================================================
FILE: .ember-cli
================================================
{
  /**
    Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
    rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
  */
  "isTypeScriptProject": false
}


================================================
FILE: .github/renovate.json5
================================================
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:base",
    ":automergeLinters",
    ":automergeTesters",
    ":dependencyDashboard",
    ":maintainLockFilesWeekly",
    ":pinOnlyDevDependencies",
    ":prConcurrentLimitNone",
    ":semanticCommitsDisabled",
    "github>Turbo87/renovate-config:automergeCaretConstraint",
    "github>Turbo87/renovate-config:commitTopics",
    "github>NullVoxPopuli/renovate:npm.json5"
  ],
}


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [ master, main, 'v*' ]
  pull_request:
    branches: [ master, main ]

concurrency:
  group: ci-${{ github.head_ref || github.ref }}
  cancel-in-progress: true

jobs:
  setup:
    name: 'Setup'
    runs-on: ubuntu-latest
    timeout-minutes: 2
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          # This version is different, because we want newer node features
          # so that we can skip pnpm install for this job
          node-version: 24
      - id: set-matrix
        run: |
          echo "matrix=$(node ./node-tests/list.mjs)" >> $GITHUB_OUTPUT

  lint:
    name: Lints
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 18
          cache: 'pnpm'
      - run: pnpm install
      - run: pnpm lint

  test:
    name: "Ember | ${{ matrix.app.name }}"
    runs-on: ubuntu-latest
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        app:
          - { name: "Broccoli (v1 Addon)", dir: ".", cmd: "pnpm test:ember" }
          - { name: "Broccoli (v1 App)", dir: './test-apps/broccoli', cmd: 'pnpm test:ember' }
          - { name: "Webpack + Embroider 3 ", dir: './test-apps/embroider3-webpack', cmd: 'pnpm test:ember' }
          - { name: "Vite + Compat", dir: './test-apps/vite-with-compat', cmd: 'pnpm build:tests && pnpm test:exam' }

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 18
          cache: 'pnpm'
      - run: pnpm install
      - run: ${{ matrix.app.cmd }}
        working-directory: ${{ matrix.app.dir }}

  test-node:
    name: "Mocha | ${{ matrix.name }}"
    needs: ["setup"]
    runs-on: ubuntu-latest
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix: ${{fromJson(needs.setup.outputs.matrix)}}

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 18
          cache: 'pnpm'
      - run: pnpm install
      - run: ${{ matrix.command }}

  floating-dependencies:
    name: "Floating Dependencies"
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 18
          cache: 'pnpm'
      - run: pnpm install
      - run: pnpm test:ember

  try-scenarios:
    name: "Try: ${{ matrix.ember-try-scenario }}"
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: test

    strategy:
      fail-fast: false
      matrix:
        ember-try-scenario:
          - ember-lts-4.8
          - ember-lts-4.12
          - ember-release
          - ember-beta
          - ember-canary

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 18
          cache: 'pnpm'
      - run: pnpm install
      - run: node_modules/.bin/ember try:one ${{ matrix.ember-try-scenario }} --skip-cleanup


================================================
FILE: .github/workflows/gh-pages.yml
================================================
name: Deploy

on:
  push:
    branches: [ master, main, 'v*' ]

concurrency:
  group: gh-pages-${{ github.head_ref || github.ref }}
  cancel-in-progress: true


jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 18
          cache: 'pnpm'
      - run: pnpm install
      - run: cd docs-app && pnpm docs:build

      - name: Upload static files as artifact
        id: deployment
        uses: actions/upload-pages-artifact@v4
        with:
          path: docs-app/.vitepress/dist

  deploy:
    needs: build

    permissions:
      pages: write      # to deploy to Pages
      id-token: write   # to verify the deployment originates from an appropriate source

    # Deploy to the github-pages environment
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}

    # Specify runner + deployment step
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action


================================================
FILE: .github/workflows/plan-release.yml
================================================
name: Plan Release
on:
  workflow_dispatch:
  push:
    branches:
      - main
      - master
  pull_request_target: # This workflow has permissions on the repo, do NOT run code from PRs in this workflow. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
    types:
      - labeled
      - unlabeled

concurrency:
  group: plan-release # only the latest one of these should ever be running
  cancel-in-progress: true

jobs:
  should-run-release-plan-prepare:
    name: Should we run release-plan prepare?
    runs-on: ubuntu-latest
    outputs:
      should-prepare: ${{ steps.should-prepare.outputs.should-prepare }}
    steps:
      - uses: release-plan/actions/should-prepare-release@v1
        with:
          ref: 'main'
        id: should-prepare

  create-prepare-release-pr:
    name: Create Prepare Release PR
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs: should-run-release-plan-prepare
    permissions:
      contents: write
      issues: read
      pull-requests: write
    if: needs.should-run-release-plan-prepare.outputs.should-prepare == 'true'
    steps:
      - uses: release-plan/actions/prepare@v1
        name: Run release-plan prepare
        with:
          ref: 'main'
        env:
          GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }}
        id: explanation

      - uses: peter-evans/create-pull-request@v8
        name: Create Prepare Release PR
        with:
          commit-message: "Prepare Release ${{ steps.explanation.outputs.new-version}} using 'release-plan'"
          labels: "internal"
          sign-commits: true
          branch: release-preview
          title: Prepare Release ${{ steps.explanation.outputs.new-version }}
          body: |
            This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR 👍

            -----------------------------------------

            ${{ steps.explanation.outputs.text }}


================================================
FILE: .github/workflows/publish.yml
================================================
# For every push to the primary branch with .release-plan.json modified,
# runs release-plan.

name: Publish Stable

on:
  workflow_dispatch:
  push:
    branches:
      - main
      - master
    paths:
      - '.release-plan.json'

concurrency:
  group: publish-${{ github.head_ref || github.ref }}
  cancel-in-progress: true

jobs:
  publish:
    name: "NPM Publish"
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
      attestations: write

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 22
          registry-url: 'https://registry.npmjs.org'
          cache: pnpm
      - run: npm install -g npm@latest # ensure that the globally installed npm is new enough to support OIDC
      - run: pnpm install --frozen-lockfile
      - name: Publish to NPM
        run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish
        env:
          GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    name: release
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 18
          registry-url: 'https://registry.npmjs.org'

      - run: yarn install
      - run: yarn auto-dist-tag --write

      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}


================================================
FILE: .gitignore
================================================
# compiled output
/dist/
/acceptance-dist/
dist-*/
test-execution-*.json
/declarations/

# dependencies
/node_modules/

# misc
/connect.lock
.log/
/.env*
/.pnp*
/.eslintcache
/coverage/
/libpeerconnection.log
/npm-debug.log*
/testem.log
/yarn-error.log

# ember-try
/.node_modules.ember-try/
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/.nyc_output/
/package-lock.json.ember-try
/yarn.lock.ember-try

# broccoli-debug
/DEBUG/


================================================
FILE: .npmignore
================================================
# compiled output
/dist/
/tmp/

# misc
/.codeclimate.yml
/.editorconfig
/.ember-cli
/.env*
/.eslintcache
/.eslintignore
/.eslintrc.js
/.git/
/.github/
/.gitignore
/.prettierignore
/.prettierrc.js
/.stylelintignore
/.stylelintrc.js
/.template-lintrc.js
/.travis.yml
/.watchmanconfig
/CHANGELOG.md
/CONTRIBUTING.md
/config/
/ember-cli-build.js
/node-tests/
/RELEASE.md
/testem*.js
/tests/
/yarn.lock
/*.tgz
.gitkeep
eslint.config.mjs

# ember-try
/.node_modules.ember-try/
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/.nyc_output/
/package-lock.json.ember-try
/yarn.lock.ember-try


================================================
FILE: .prettierignore
================================================
# unconventional js
/blueprints/*/files/

# compiled output
/dist/
docs-app/
test-apps/
acceptance-dist/
failure-dist/

# misc
/coverage/
!.*
.*/

# ember-try
/.node_modules.ember-try/

# Ignored when enabling prettier in CI
# Changes are too much, and also not super functional
*.yml
*.yaml
*.md
*.html
*.json


================================================
FILE: .prettierrc.js
================================================
'use strict';

module.exports = {
  overrides: [
    {
      files: '*.{js,ts}',
      options: {
        singleQuote: true,
      },
    },
  ],
};


================================================
FILE: .release-plan.json
================================================
{
  "solution": {
    "ember-exam": {
      "impact": "minor",
      "oldVersion": "10.0.1",
      "newVersion": "10.1.0",
      "tagName": "latest",
      "constraints": [
        {
          "impact": "minor",
          "reason": "Appears in changelog section :rocket: Enhancement"
        },
        {
          "impact": "patch",
          "reason": "Appears in changelog section :memo: Documentation"
        }
      ],
      "pkgJSONPath": "./package.json"
    }
  },
  "description": "## Release (2025-12-19)\n\n* ember-exam 10.1.0 (minor)\n\n#### :rocket: Enhancement\n* `ember-exam`\n  * [#1489](https://github.com/ember-cli/ember-exam/pull/1489) Better vite support ([@bendemboski](https://github.com/bendemboski))\n\n#### :memo: Documentation\n* `ember-exam`\n  * [#1489](https://github.com/ember-cli/ember-exam/pull/1489) Better vite support ([@bendemboski](https://github.com/bendemboski))\n  * [#1455](https://github.com/ember-cli/ember-exam/pull/1455) Update README.md with timeout help ([@apellerano-pw](https://github.com/apellerano-pw))\n\n#### Committers: 2\n- Andrew Pellerano ([@apellerano-pw](https://github.com/apellerano-pw))\n- Ben Demboski ([@bendemboski](https://github.com/bendemboski))\n"
}


================================================
FILE: .watchmanconfig
================================================
{
  "ignore_dirs": ["dist"]
}


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## Release (2025-12-19)

* ember-exam 10.1.0 (minor)

#### :rocket: Enhancement
* `ember-exam`
  * [#1489](https://github.com/ember-cli/ember-exam/pull/1489) Better vite support ([@bendemboski](https://github.com/bendemboski))

#### :memo: Documentation
* `ember-exam`
  * [#1489](https://github.com/ember-cli/ember-exam/pull/1489) Better vite support ([@bendemboski](https://github.com/bendemboski))
  * [#1455](https://github.com/ember-cli/ember-exam/pull/1455) Update README.md with timeout help ([@apellerano-pw](https://github.com/apellerano-pw))

#### Committers: 2
- Andrew Pellerano ([@apellerano-pw](https://github.com/apellerano-pw))
- Ben Demboski ([@bendemboski](https://github.com/bendemboski))

## Release (2025-12-03)

* ember-exam 10.0.1 (patch)

#### :bug: Bug Fix
* `ember-exam`
  * [#1482](https://github.com/ember-cli/ember-exam/pull/1482) Read configFile From commandOptions ([@jrjohnson](https://github.com/jrjohnson))

#### Committers: 1
- Jon Johnson ([@jrjohnson](https://github.com/jrjohnson))

## Release (2025-08-26)

* ember-exam 10.0.0 (major)

#### :boom: Breaking Change
* `ember-exam`
  * [#1430](https://github.com/ember-cli/ember-exam/pull/1430) Support vite ([@NullVoxPopuli](https://github.com/NullVoxPopuli))

#### :bug: Bug Fix
* `ember-exam`
  * [#1450](https://github.com/ember-cli/ember-exam/pull/1450) Support cjs testem configs ([@NullVoxPopuli](https://github.com/NullVoxPopuli))

#### :memo: Documentation
* `ember-exam`
  * [#1347](https://github.com/ember-cli/ember-exam/pull/1347) Update setup example for new qunit requirements ([@elwayman02](https://github.com/elwayman02))

#### :house: Internal
* `ember-exam`
  * [#1449](https://github.com/ember-cli/ember-exam/pull/1449) Spit node-tests in to parallel jobs for easier retries (we have a very short testem timeout) ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1451](https://github.com/ember-cli/ember-exam/pull/1451) Delete test duplication and use symlinks instead ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1452](https://github.com/ember-cli/ember-exam/pull/1452) Remove extraneous command in CI workflow ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1448](https://github.com/ember-cli/ember-exam/pull/1448) Split out try scenarios in to real apps for easier debugging ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1443](https://github.com/ember-cli/ember-exam/pull/1443) Remove unused deps ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1442](https://github.com/ember-cli/ember-exam/pull/1442) Get rid of custom resolver form an older era of the blueprint ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1439](https://github.com/ember-cli/ember-exam/pull/1439) Update renovate-config (move to weekly) ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1441](https://github.com/ember-cli/ember-exam/pull/1441) Set base for pages deployment ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1440](https://github.com/ember-cli/ember-exam/pull/1440) Fix static files path for gh-pages deploy ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1435](https://github.com/ember-cli/ember-exam/pull/1435) Strict dep management settings + re-roll lockfile, remove addon-docs, add vitepress ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1434](https://github.com/ember-cli/ember-exam/pull/1434) Add prettier to lint, don't run lint with tests ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1431](https://github.com/ember-cli/ember-exam/pull/1431) Upgrade eslint / prettier ([@NullVoxPopuli](https://github.com/NullVoxPopuli))

#### Committers: 2
- Jordan Hawker ([@elwayman02](https://github.com/elwayman02))
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)

## Release (2025-03-05)

ember-exam 9.1.0 (minor)

#### :rocket: Enhancement
* `ember-exam`
  * [#1313](https://github.com/ember-cli/ember-exam/pull/1313) Use ember-exam with vite ([@NullVoxPopuli](https://github.com/NullVoxPopuli))

#### :house: Internal
* `ember-exam`
  * [#1336](https://github.com/ember-cli/ember-exam/pull/1336) Update release-plan ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1333](https://github.com/ember-cli/ember-exam/pull/1333) Fix lints since eslint-plugin-ember was upgraded ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1332](https://github.com/ember-cli/ember-exam/pull/1332) Revert #1188 ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1330](https://github.com/ember-cli/ember-exam/pull/1330) Revert "Update dependency ember-qunit to v9" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1329](https://github.com/ember-cli/ember-exam/pull/1329) Revert "Update pnpm to v10.5.2" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1322](https://github.com/ember-cli/ember-exam/pull/1322) Setup Release plan ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1314](https://github.com/ember-cli/ember-exam/pull/1314) Convert to pnpm ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
  * [#1289](https://github.com/ember-cli/ember-exam/pull/1289) Add .codeclimate.yml to .npmignore ([@SergeAstapov](https://github.com/SergeAstapov))

#### Committers: 2
- Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
- [@NullVoxPopuli](https://github.com/NullVoxPopuli)



## v9.0.0 (2023-12-29)

#### :boom: Breaking Change
* [#1125](https://github.com/ember-cli/ember-exam/pull/1125) Update ember to 5.5, drop Nodes below 18, drop Mocha support ([@andreyfel](https://github.com/andreyfel))

#### :rocket: Enhancement
* [#963](https://github.com/ember-cli/ember-exam/pull/963) Add preserveTestName CLI flag to remove partition and browser ([@tasha-urbancic](https://github.com/tasha-urbancic))

#### :house: Internal
* [#1127](https://github.com/ember-cli/ember-exam/pull/1127) Run node tests in CI ([@andreyfel](https://github.com/andreyfel))

#### Committers: 2
- Andrey Fel ([@andreyfel](https://github.com/andreyfel))
- Natasha Urbancic ([@tasha-urbancic](https://github.com/tasha-urbancic))


## v8.0.0 (2022-01-25)

#### :boom: Breaking Change
* [#769](https://github.com/ember-cli/ember-exam/pull/769) Drop support for Ember 3.19 and below ([@Turbo87](https://github.com/Turbo87))

#### :house: Internal
* [#840](https://github.com/ember-cli/ember-exam/pull/840) Upgrade `@embroider/*` packages to 1.0.0 ([@SergeAstapov](https://github.com/SergeAstapov))
* [#745](https://github.com/ember-cli/ember-exam/pull/745) Upgrade eslint-plugin-ember from v8.9.1 to v10.5.8 ([@SergeAstapov](https://github.com/SergeAstapov))
* [#813](https://github.com/ember-cli/ember-exam/pull/813) Use `assert.strictEqual()` instead of `assert.equal()` ([@Turbo87](https://github.com/Turbo87))
* [#775](https://github.com/ember-cli/ember-exam/pull/775) Delete unused `herp-derp` component ([@Turbo87](https://github.com/Turbo87))
* [#774](https://github.com/ember-cli/ember-exam/pull/774) Migrate dummy app templates to use angle bracket invocation syntax ([@Turbo87](https://github.com/Turbo87))
* [#740](https://github.com/ember-cli/ember-exam/pull/740) CI: Enable Ember v4 scenarios again ([@Turbo87](https://github.com/Turbo87))
* [#768](https://github.com/ember-cli/ember-exam/pull/768) Upgrade `ember-cli-addon-docs` dependency ([@Turbo87](https://github.com/Turbo87))
* [#766](https://github.com/ember-cli/ember-exam/pull/766) CI: Disable failing `ember-release` scenario ([@Turbo87](https://github.com/Turbo87))
* [#748](https://github.com/ember-cli/ember-exam/pull/748) Add eslint-plugin-qunit per latest addon blueprint ([@SergeAstapov](https://github.com/SergeAstapov))
* [#744](https://github.com/ember-cli/ember-exam/pull/744) Update npmignore file ([@Turbo87](https://github.com/Turbo87))

#### Committers: 3
- Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
- Stephen Yeung ([@step2yeung](https://github.com/step2yeung))
- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))


## v7.0.1 (2021-11-02)
#### :bug: Bug Fix
* [#760](https://github.com/ember-cli/ember-exam/pull/760) Wait for all browser to completet beforer cleaning up StateManager([@step2yeung](https://github.com/step2yeung))
* [#750](https://github.com/ember-cli/ember-exam/pull/750) Ember exam failing when browser ID not found, return 0([@step2yeung](https://github.com/step2yeung))

#### :house: Internal
* [#748](https://github.com/ember-cli/ember-exam/pull/748) Add eslint-plugin-qunit per latest addon blueprint  internal ([@SergeAstapov](https://github.com/SergeAstapov))
* [#744](https://github.com/ember-cli/ember-exam/pull/744) Update npmignore file internal([@Turbo87](https://github.com/Turbo87))
#### Committers: 4

- Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))
- Stephen Yeung ([@step2yeung](https://github.com/step2yeung))

## v7.0.0 (2021-10-22)

#### :boom: Breaking Change
* [#739](https://github.com/ember-cli/ember-exam/pull/739) Update `ember-auto-import` to v2.x ([@Turbo87](https://github.com/Turbo87))
* [#690](https://github.com/ember-cli/ember-exam/pull/690) Drop support for Node 10 and upgrade deps ([@nlfurniss](https://github.com/nlfurniss))

#### :bug: Bug Fix
* [#688](https://github.com/ember-cli/ember-exam/pull/688) Fix embroider tests ([@nlfurniss](https://github.com/nlfurniss))

#### :memo: Documentation
* [#687](https://github.com/ember-cli/ember-exam/pull/687) Update README.md: Fix typo in flag name ([@bantic](https://github.com/bantic))
* [#644](https://github.com/ember-cli/ember-exam/pull/644) Docs: Fix information on Load Balancing ([@brkn](https://github.com/brkn))

#### :house: Internal
* [#743](https://github.com/ember-cli/ember-exam/pull/743) CI: Add `release` workflow ([@Turbo87](https://github.com/Turbo87))
* [#737](https://github.com/ember-cli/ember-exam/pull/737) Use `prettier` to format JS files ([@Turbo87](https://github.com/Turbo87))
* [#736](https://github.com/ember-cli/ember-exam/pull/736) CI: Disable Ember.js v4 scenarios ([@Turbo87](https://github.com/Turbo87))
* [#689](https://github.com/ember-cli/ember-exam/pull/689) Set ember edition to Octane to quiet build logging ([@nlfurniss](https://github.com/nlfurniss))

#### Committers: 4
- Berkan Ünal ([@brkn](https://github.com/brkn))
- Cory Forsyth ([@bantic](https://github.com/bantic))
- Nathaniel Furniss ([@nlfurniss](https://github.com/nlfurniss))
- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))


## v6.1.0 (2021-02-17)

#### :rocket: Enhancement
* [#652](https://github.com/ember-cli/ember-exam/pull/652) Update to support `ember-qunit@5` ([@thoov](https://github.com/thoov))

#### Committers: 1
- Travis Hoover ([@thoov](https://github.com/thoov))


## v6.0.1 (2020-10-28)

#### :bug: Bug Fix
* [#617](https://github.com/ember-cli/ember-exam/pull/617) Update @embroider/macros to fix ember-qunit@5.0.0-beta support. ([@rwjblue](https://github.com/rwjblue))

#### :house: Internal
* [#618](https://github.com/ember-cli/ember-exam/pull/618) Swap to GitHub actions for CI. ([@rwjblue](https://github.com/rwjblue))

#### Committers: 2
- Robert Jackson ([@rwjblue](https://github.com/rwjblue))
- [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)


## v6.0.0 (2020-10-12)

#### :boom: Breaking Change
* [#615](https://github.com/ember-cli/ember-exam/pull/615) Drop Node 13 support. ([@rwjblue](https://github.com/rwjblue))
* [#600](https://github.com/ember-cli/ember-exam/pull/600) Drop Node 11 support. ([@thoov](https://github.com/thoov))

#### :rocket: Enhancement
* [#599](https://github.com/ember-cli/ember-exam/pull/599) Embroider support when `staticAddonTestSupportTrees` enabled ([@thoov](https://github.com/thoov))

#### :bug: Bug Fix
* [#410](https://github.com/ember-cli/ember-exam/pull/410) Fail if parallel is not a numeric value ([@step2yeung](https://github.com/step2yeung))

#### :memo: Documentation
* [#612](https://github.com/ember-cli/ember-exam/pull/612) Update README.md ([@jrowlingson](https://github.com/jrowlingson))
* [#588](https://github.com/ember-cli/ember-exam/pull/588) Add note about `--random` and `--load-balance` ([@kellyselden](https://github.com/kellyselden))

#### :house: Internal
* [#614](https://github.com/ember-cli/ember-exam/pull/614) Update release automation setup. ([@rwjblue](https://github.com/rwjblue))
* [#604](https://github.com/ember-cli/ember-exam/pull/604) Fixing bad yarn lock merge ([@thoov](https://github.com/thoov))
* [#600](https://github.com/ember-cli/ember-exam/pull/600) Fix test suite to run mocha variants during CI ([@thoov](https://github.com/thoov))

#### Committers: 6
- Jack Rowlingson ([@jrowlingson](https://github.com/jrowlingson))
- Kelly Selden ([@kellyselden](https://github.com/kellyselden))
- Robert Jackson ([@rwjblue](https://github.com/rwjblue))
- Stephen Yeung ([@step2yeung](https://github.com/step2yeung))
- Travis Hoover ([@thoov](https://github.com/thoov))
- [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)


v5.0.1 / 2020-04-21
===================
* Bump fs-extra from 8.1.0 to 9.0.0 <dependabot[bot]>
* Bump sinon from 7.5.0 to 9.0.2 <dependabot[bot]>
* Bump cli-table3 from 0.5.1 to 0.6.0 <dependabot[bot]>
* Bump ember-resolver from 6.0.2 to 8.0.0 <dependabot[bot]>
* Bump ember-source from 3.17.2 to 3.18.0 <dependabot[bot]>
* Bump semver from 7.1.3 to 7.3.2 <dependabot[bot]>
* Bump eslint-plugin-node from 11.0.0 to 11.1.0 <dependabot[bot]>
* Bump semver from 7.1.3 to 7.3.2 <dependabot[bot]>
* Bump ember-template-lint from 2.4.1 to 2.5.2 <dependabot[bot]>
* Bump mocha from 7.1.0 to 7.1.1 <dependabot[bot]>
* Bump testdouble from 3.13.0 to 3.13.1 <dependabot[bot]>
* Bump nyc from 15.0.0 to 15.0.1 <dependabot[bot]>
* Bump ember-cli-htmlbars from 4.2.2 to 4.3.0 <dependabot[bot]>
* Bump ember-cli from 3.16.0 to 3.17.0 <dependabot[bot]>
* Bump ember-cli-babel from 7.18.0 to 7.19.0 <dependabot[bot]>
* Bump ember-source from 3.17.1 to 3.17.2 <dependabot[bot]>
* Bump ember-template-lint from 2.4.0 to 2.4.1  <dependabot[bot]>
* Bump ember-source from 3.17.0 to 3.17.1 <dependabot[bot]>
* Bump eslint-plugin-ember from 7.10.1 to 7.11.1 <dependabot[bot]>
* Bump eslint-plugin-ember from 7.9.0 to 7.10.1 <dependabot[bot]>
* Bump ember-template-lint from 2.3.0 to 2.4.0 <dependabot[bot]>


v5.0.0 / 2020-03-06
===================
* [Enhancement] Update docs for ember-cli-addon-docs (@choheekim)
* [Enhancement] Update node engine to be above 10 (@choheekim)
* [Enhancement] Enables to execute completeBrowserHandler() when there is browser(s) failed to attach to server (@choheekim)
* [Enhancement] _getTestFramework checks for ember-mocha package (@choheekim)
* [Enhancement] updating header comments to fix warnings during "ember build" (@dcombslinkedin)
* [BugFix] fix invalid ES module usage (@ef3)
* Bump ember-source from 3.16.3 to 3.17.0 <dependabot[bot]>
* Bump ember-template-lint from 1.14.0 to 2.3.0 <dependabot[bot]>
* Bump eslint-plugin-ember from 7.8.1 to 7.9.0 <dependabot[bot]>
* Bump testdouble from 3.12.5 to 3.13.0 <dependabot[bot]>
* Bump mocha from 7.0.1 to 7.1.0 <dependabot[bot]>
* Bump ember-template-lint from 1.13.2 to 1.14.0 <dependabot[bot]>
* Bump ember-source from 3.14.3 to 3.16.3 <dependabot[bot]>
* Bump semver from 7.1.2 to 7.1.3 <dependabot[bot]>
* Bump ember-cli from 3.15.2 to 3.16.0 <dependabot[bot]>
* Bump ember-cli-babel from 7.17.1 to 7.18.0 <dependabot[bot]>
* Bump eslint-plugin-ember from 7.7.2 to 7.8.1 <dependabot[bot]>
* Bump rimraf from 3.0.1 to 3.0.2 <dependabot[bot]>
* Bump ember-cli-babel from 7.14.1 to 7.17.1 <dependabot[bot]>
* Bump semver from 6.3.0 to 7.1.2 <dependabot[bot]>
* Bump mocha from 6.2.2 to 7.0.1 <dependabot[bot]>
* Bump ember-cli-babel from 7.13.2 to 7.14.1 <dependabot[bot]>
* Bump rimraf from 3.0.0 to 3.0.1 <dependabot[bot]>
* Bump ember-cli from 3.15.1 to 3.15.2 <dependabot[bot]>
* Bump ember-cli-addon-docs-yuidoc from 0.2.3 to 0.2.4 <dependabot[bot]>
* Bump ember-template-lint from 1.13.0 to 1.13.2 <dependabot[bot]>
* Bump ember-cli-htmlbars from 4.2.1 to 4.2.2 <dependabot[bot]>
* Bump ember-cli-htmlbars from 4.2.0 to 4.2.1 <dependabot[bot]>
* Bump eslint-plugin-node from 10.0.0 to 11.0.0 <dependabot[bot]>
* Bump nyc from 14.1.1 to 15.0.0 <dependabot[bot]>
* Bump ember-resolver from 6.0.0 to 6.0.1 <dependabot[bot]>
* Bump ember-template-lint from 1.11.1 to 1.12.1 <dependabot[bot]>
* Bump eslint-plugin-ember from 7.7.1 to 7.7.2 <dependabot[bot]>
* Bump ember-cli-babel from 7.13.0 to 7.13.2 <dependabot[bot]>
* Bump ember-cli-htmlbars from 4.1.0 to 4.2.0 <dependabot[bot]>
* Bump ember-try from 1.3.0 to 1.4.0 <dependabot[bot]>
* Bump ember-cli-htmlbars from 4.0.9 to 4.1.0 <dependabot[bot]>
* Bump ember-template-lint from 1.9.0 to 1.10.0 <dependabot[bot]>


v4.0.9 / 2019-12-05
===================
* [Enhancement] Add a number of total tests, failed tests, passed tests, and skipped tests to a module metadata file (@choheekim)
* [Enhancement] Update README.md corresponding to changes in the module metadata file contents (@choheekim)
* [BugFix] Update yarn.lock to use latest version of core-js-compat (v.3.4.7) (@choheekim)
* [BugFix] Fix process validation when registering callbacks for process.error & process.exit (@choheekim)
* Bump ember-template-lint from 1.6.0 to 1.6.1 <dependabot[bot]>
* Bump ember-qunit from 4.5.1 to 4.6.0 <dependabot[bot]>
* Bump eslint-plugin-ember from 7.2.0 to 7.3.0 <dependabot[bot]>
* Bump ember-load-initializers from 2.1.0 to 2.1.1 <dependabot[bot]>
* Bump ember-template-lint from 1.6.1 to 1.8.1 <dependabot[bot]>
* Bump ember-source from 3.13.3 to 3.14.1 <dependabot[bot]>
* Bump chalk from 2.4.2 to 3.0.0 <dependabot[bot]>
* Bump ember-export-application-global from 2.0.0 to 2.0.1 <dependabot[bot]>
* Bump ember-template-lint from 1.8.1 to 1.8.2 <dependabot[bot]>
* Bump ember-cli from 3.13.1 to 3.14.0 <dependabot[bot]>
* Bump ember-resolver from 5.3.0 to 6.0.0  <dependabot[bot]>
* Bump ember-cli-babel from 7.12.0 to 7.13.0 <dependabot[bot]>
* Bump execa from 3.3.0 to 3.4.0 <dependabot[bot]>
* Bump ember-source from 3.14.2 to 3.14.3 <dependabot[bot]>
* Bump ember-template-lint from 1.8.2 to 1.9.0 <dependabot[bot]>
* Bump ember-cli-htmlbars from 4.0.8 to 4.0.9 <dependabot[bot]>


v4.0.5 / 2019-10-25
===================
* [BugFix] Validate process object is defined when registering event callbacks for process.error & process.exit (@choheekim)
* [BugFix] Updates page title for dummy app to "Ember Exam" (@howie)
* Bump rimraf from 2.7.1 to 3.0.0 <dependabot[bot]>
* Bump ember-cli from 3.12.0 to 3.13.1 <dependabot[bot]>
* Bump ember-cli-deploy-build from 1.1.1 to 2.0.0 <dependabot[bot]>
* Bump mocha from 6.2.0 to 6.2.2 <dependabot[bot]>
* Bump ember-template-lint from 1.5.3 to 1.6.0 <dependabot[bot]>
* Bump ember-cli-babel from 7.11.1 to 7.12.0 <dependabot[bot]>
* Bump ember-cli-inject-live-reload from 2.0.1 to 2.0.2 <dependabot[bot]>
* Bump @ember/optional-features from 1.0.0 to 1.1.0  <dependabot[bot]>
* Bump ember-cli-addon-docs from 0.6.14 to 0.6.15 <dependabot[bot]>
* Bump ember-cli-htmlbars-inline-precompile from 2.1.0 to 3.0.1 <dependabot[bot]>
* Bump ember-source from 3.10.2 to 3.13.3 <dependabot[bot]>
* Bump eslint-plugin-node from 8.0.1 to 10.0.0  <dependabot[bot]>
* Bump @ember/optional-features from 0.7.0 to 1.0.0 <dependabot[bot]>
* Bump ember-cli-htmlbars from 3.1.0 to 4.0.8 <dependabot[bot]>
* Bump eslint-plugin-ember from 6.10.1 to 7.2.0 <dependabot[bot]>


v4.0.4 / 2019-09-30
===================
* [BugFix] Validate testem object is defined (@choheekim)
* Bump ember-cli-babel from 7.11.0 to 7.11.1 <dependabot[bot]>


v4.0.3 / 2019-09-24
===================
* [Feature] Introduce write-module-metadata-file (@choheekim)
* Bump ember-resolver from 5.2.1 to 5.3.0 <dependabot[bot]>


v4.0.2 / 2019-09-16
===================
* [BugFix] Ensure browserExitHandler is called for global errors (@step2yeung)
* Bump ember-cli-deploy-git from 1.3.3 to 1.3.4 <dependabot[bot]>


v4.0.1 / 2019-09-11
===================
* [Enhancement] Improve complete browser book keeping & improve request next module conditions (@step2yeung)
* Bump sinon from 7.4.0 to 7.4.2 <dependabot[bot]>


v4.0.0 / 2019-07-18
===================
* [Enhancement] Update to use node version >= 8 (@choheekim)
* [Enhancement] Throw error when there are no matching tests with a given input by file-path and module-path (@choheekim)
* [BugFix] Update yarn.lock to use v2.4.1 of ember-cli-addon-docs (@choheekim)
* Bump ember-source from 3.10.1 to 3.10.2 <dependabot[bot]>
* Bump eslint-plugin-ember from 6.6.0 to 6.7.0 <dependabot[bot]>
* Bump semver from 6.1.1 to 6.1.2 <dependabot[bot]>
* Bump testdouble from 3.12.0 to 3.12.2 <dependabot[bot]>


v3.0.3 / 2019-06-18
===================

* [Feature] Introduce module-path-filter and test-file-path-filter in ember-exam (@choheekim)
* Bump ember-source from 3.10.0 to 3.10.1 <dependabot[bot]>
* Bump rsvp from 4.8.4 to 4.8.5 <dependabot[bot]>
* Bump testdouble from 3.11.0 to 3.12.0 <dependabot[bot]>
* Bump ember-cli-addon-docs from 0.6.11 to 0.6.13 <dependabot[bot]>
* Bump ember-template-lint from 1.1.0 to 1.2.0 <dependabot[bot]>
* Bump eslint-plugin-ember from 6.5.1 to 6.6.0 <dependabot[bot]>
* Bump ember-cli-babel from 7.7.3 to 7.8.0 <dependabot[bot]>


v3.0.2 / 2019-06-03
===================

* [Enhancement] Update documentation (Add Table of Contents) (@Vasanth-freshworks)
* [Enhancement] Allow graceful exit when async iterator failes to get a module. Add emberExamExitOnError flag to hard fail (@step2yeung)
* [BugFix] Remove duplicate nav entry (@samselikoff)
* Bump ember-cli-addon-docs from 0.6.8 to 0.6.9 <dependabot[bot]>
* Bump mocha from 6.1.2 to 6.1.3 <dependabot[bot]>
* Bump ember-cli-addon-docs from 0.6.9 to 0.6.10 <dependabot[bot]>
* Bump sinon from 7.3.1 to 7.3.2 <dependabot[bot]>
* Bump eslint-plugin-ember from 6.3.0 to 6.4.1 <dependabot[bot]>
* Bump ember-source from 3.9.0 to 3.9.1 <dependabot[bot]>
* [Security] Bump jquery from 3.3.1 to 3.4.0 <dependabot[bot]>
* Bump nyc from 13.3.0 to 14.1.1 <dependabot[bot]>
* Bump ember-source from 3.9.1 to 3.10.0 <dependabot[bot]>
* Bump fs-extra from 7.0.1 to 8.0.1 <dependabot[bot]>
* Bump mocha from 6.1.3 to 6.1.4 <dependabot[bot]>
* Bump ember-cli-addon-docs from 0.6.10 to 0.6.11 <dependabot[bot]>
* Bump semver from 6.0.0 to 6.1.0 <dependabot[bot]>
* Bump eslint-plugin-ember from 6.4.1 to 6.5.0 <dependabot[bot]>
* Bump ember-try from 1.1.0 to 1.2.1 <dependabot[bot]>
* Bump semver from 6.1.0 to 6.1.1 <dependabot[bot]>
* Bump eslint-plugin-ember from 6.5.0 to 6.5.1 <dependabot[bot]>


v3.0.1 / 2019-04-09
===================

* [Enhancement] Update documentation (@step2yeung)


v3.0.0 / 2019-04-08
===================

* [Feature - Breaking] Introduce TestLoadBalancing (@choheekim) & (@step2yeung)

You will need to **replace** the use of `start()` from `Ember-Qunit` or `Ember-Mocha` in `test-helper.js` with `start()` from `ember-exam`:

```js
// test-helper.js
import start from 'ember-exam/test-support/start';

// Options passed to `start` will be passed-through to ember-qunit or ember-mocha
start();
```

This breaking change was motivated by wanting to remove the monkey-patching, of ember-qunit and ember-mocha's test-loader, ember exam was doing.

* [Bugfix] Ensure serialized test-execution browserId's are always treated as a string https://github.com/ember-cli/ember-exam/pull/233
* [Bugfix] fix breaking change: https://github.com/ember-cli/ember-exam/pull/242 (@step2yeung)
* [Enhancement] Prettify test-execution.json (@step2yeung)
* Bump ember-qunit from 4.4.0 to 4.4.1 (4 weeks ago) <dependabot[bot]>
* Bump ember-resolver from 5.1.2 to 5.1.3 (4 weeks ago) <dependabot[bot]>
* Bump testdouble from 3.10.0 to 3.11.0 (4 weeks ago) <dependabot[bot]>
* Bump ember-cli-babel from 7.4.3 to 7.5.0 (4 weeks ago) <dependabot[bot]>
* Bump ember-resolver from 5.1.1 to 5.1.2 (5 weeks ago) <dependabot[bot]>
* Bump mocha from 6.0.0 to 6.0.1 (5 weeks ago) <dependabot[bot]>
* Bump ember-cli-babel from 7.4.2 to 7.4.3 (5 weeks ago) <dependabot[bot]>
* Bump ember-qunit from 4.3.0 to 4.4.0 (5 weeks ago) <dependabot[bot]>
* Bump mocha from 5.2.0 to 6.0.0 (5 weeks ago) <dependabot[bot]>
* Bump ember-source from 3.7.3 to 3.8.0 (5 weeks ago) <dependabot[bot]>
* Bump sinon from 7.2.3 to 7.2.4 (5 weeks ago) <dependabot[bot]>
* Bump nyc from 13.2.0 to 13.3.0 (6 weeks ago) <dependabot[bot]>
* [Security] Bump handlebars from 4.0.12 to 4.1.0 (6 weeks ago) <dependabot[bot]>
* Bump ember-cli-babel from 7.4.1 to 7.4.2 (6 weeks ago) <dependabot[bot]>
* Bump ember-source from 3.7.2 to 3.7.3 (7 weeks ago) <dependabot[bot]>
* Bump ember-qunit from 4.2.0 to 4.3.0 (7 weeks ago) <dependabot[bot]>
* Bump nyc from 13.1.0 to 13.2.0 (7 weeks ago) <dependabot[bot]>
* Bump testdouble from 3.9.3 to 3.10.0 (7 weeks ago) <dependabot[bot]>
* Bump ember-cli-babel from 7.4.0 to 7.4.1 (8 weeks ago) <dependabot[bot]>
* Bump eslint-plugin-ember from 6.1.0 to 6.2.0 (8 weeks ago) <dependabot[bot]>


v2.1.5 / 2019-04-08
===================

* re-release 2.0.3 as 2.1.5, as 2.0.4...2.1.4 introduced a worth-while but unexpected breaking change. 2.0.4...2.1.4 will be re-released as 3.x


v2.1.4 / 2019-03-27
===================

* [Bugfix] Ensure serialized test-execution browserId's are always treated as a string https://github.com/ember-cli/ember-exam/pull/233

v2.1.3 / 2019-03-27
===================

* [Bugfix] fix breaking change: https://github.com/ember-cli/ember-exam/pull/242 (@step2yeung)
* [Enhancement] Prettify test-execution.json (@step2yeung)

v2.1.0 / 2019-03-27
===================

* [Feature] Introduce TestLoadBalancing <@choheekim> & <@step2yeung>
* Bump ember-qunit from 4.4.0 to 4.4.1 (4 weeks ago) <dependabot[bot]>
* Bump ember-resolver from 5.1.2 to 5.1.3 (4 weeks ago) <dependabot[bot]>
* Bump testdouble from 3.10.0 to 3.11.0 (4 weeks ago) <dependabot[bot]>
* Bump ember-cli-babel from 7.4.3 to 7.5.0 (4 weeks ago) <dependabot[bot]>
* Bump ember-resolver from 5.1.1 to 5.1.2 (5 weeks ago) <dependabot[bot]>
* Bump mocha from 6.0.0 to 6.0.1 (5 weeks ago) <dependabot[bot]>
* Bump ember-cli-babel from 7.4.2 to 7.4.3 (5 weeks ago) <dependabot[bot]>
* Bump ember-qunit from 4.3.0 to 4.4.0 (5 weeks ago) <dependabot[bot]>
* Bump mocha from 5.2.0 to 6.0.0 (5 weeks ago) <dependabot[bot]>
* Bump ember-source from 3.7.3 to 3.8.0 (5 weeks ago) <dependabot[bot]>
* Bump sinon from 7.2.3 to 7.2.4 (5 weeks ago) <dependabot[bot]>
* Bump nyc from 13.2.0 to 13.3.0 (6 weeks ago) <dependabot[bot]>
* [Security] Bump handlebars from 4.0.12 to 4.1.0 (6 weeks ago) <dependabot[bot]>
* Bump ember-cli-babel from 7.4.1 to 7.4.2 (6 weeks ago) <dependabot[bot]>
* Bump ember-source from 3.7.2 to 3.7.3 (7 weeks ago) <dependabot[bot]>
* Bump ember-qunit from 4.2.0 to 4.3.0 (7 weeks ago) <dependabot[bot]>
* Bump nyc from 13.1.0 to 13.2.0 (7 weeks ago) <dependabot[bot]>
* Bump testdouble from 3.9.3 to 3.10.0 (7 weeks ago) <dependabot[bot]>
* Bump ember-cli-babel from 7.4.0 to 7.4.1 (8 weeks ago) <dependabot[bot]>
* Bump eslint-plugin-ember from 6.1.0 to 6.2.0 (8 weeks ago) <dependabot[bot]>

v2.0.3 / 2019-01-22
===================

* ignore .nyc_output

v2.0.2 / 2019-01-22
===================

* Bump chalk from 2.4.1 to 2.4.2
* Bump debug from 4.1.0 to 4.1.1
* Bump ember-cli from 3.5.1 to 3.7.1
* Bump ember-cli-babel from 7.1.4 to 7.4.0
* Bump ember-cli-dependency-checker from 3.0.0 to 3.1.0
* Bump ember-cli-htmlbars-inline-precompile from 2.0.0 to 2.1.0
* Bump ember-qunit from 4.1.2 to 4.2.0
* Bump ember-source from 3.6.0 to 3.7.2
* Bump ember-template-lint from 0.8.23 to 1.1.0
* Bump eslint-plugin-ember from 6.0.1 to 6.1.0
* Bump eslint-plugin-node from 8.0.0 to 8.0.1
* Bump rimraf from 2.6.2 to 2.6.3
* Bump sinon from 7.1.1 to 7.2.3
* Bump testdouble from 3.9.1 to 3.9.3
* Run test:all to trigger ember & node test in ci, add missing single quote, and change number of tests running
* `setResolver()` from `@ember/test-helpers`

v2.0.1 / 2018-12-07
===================

  * ember-exam now sets `process.env.EMBER_EXAM_SPLIT_COUNT`, this allows testem scripts to pick up this configuration via `parallel: process.env.EMBER_EXAM_SPLIT_COUNT`

v2.0.0 / 2018-12-04
===================

  * Bump Node support to: ^6.14.0 || ^8.10.0 || >= 10.*
  * Update/Modernize all dependencies
  * Update/Modernize codebase
  * tranisition from ember-cli-qunit to ember-qunit

v1.0.0 / 2017-11-02
==================

==================

  * Remove auto-loading functionality
  * Update readme to better emphasize explicit loading

v0.8.1 / 2017-10-08
==================

  * Warn when auto-loading (deprecation)
  * Remove `#` from test output.

v0.8.0 / 2017-10-04
==================

  * Removed EMBER_TRY_SCENARIO's from .travis.yml file
  * Fix ESLint warning
  * Fix mocha integration
  * Revert `npm install` command in .travis.yml
  * Upgrade all dependencies version
  * Upgrade Ember CLI to version 2.15 and align with default blueprint

v0.7.2 / 2017-10-01
==================

  * fixes #109 - use local ember

v0.7.1 / 2017-09-14
==================

  * Make notes about turning on parallelization more visible
  * Move note on >= 0.7.0 into installation section
  * Add installation instructions
  * Remove jQuery usage
  * Specify when to call loadEmberExam when using ember-cli-qunit@4
  * fix version range
  * Add release process notes

v0.7.0 / 2017-06-01
==================

  * Document load API for version 0.7.0
  * Fix eslint errors for node-land code
  * Refactor core functionality
  * Extract TestLoader mods into utility function
  * Simplify and revamp code coverage
  * Fix tests from ESLint migration
  * Replace JSHint with ESLint
  * Tweak CI configs
  * Change ember try:one -> ember try:each
  * Remove Node 0.12 from Travis
  * Add Node LTS versions 4.x, 6.x, and stable to Travis

v0.6.2 / 2017-04-09
==================

  * Downgrade split < 2 error to warning
  * Fix mocha test commands


v0.6.1 / 2017-03-25
===================

  * Ensure iterate exits with proper code
  * Add Ember Exam video link to Readme
  * Add note about using random with a seed
  * Fix seed logging message for random option

v0.6.0 / 2016-11-27
===================

  * Close code coverage gap
  * Update README to include Mocha info
  * Add framework-specific logic
  * Run both Mocha and QUnit tests in CI
  * Add tests for ember-cli-mocha
  * Remove moduleForAcceptance
  * Move QUnit-based tests to sub-directory
  * Remove reliance on QUnit for handling url params

v0.5.3 / 2016-11-19
===================

  * Fixed issue with using a single partition with a double digit

v0.5.2 / 2016-11-15
===================

  * Support specifying multiple partitions (#63)

v0.5.1 / 2016-11-14
===================

  * move rimraf to dependencies from devDependencies
  * Add note about test splitting balancing

v0.5.0 / 2016-08-14
===================

  * Document randomization-iterator
  * Add tests for randomization-iterator
  * Rename main acceptance test to be semantic
  * Introduce exam:iterate command
  * Tighten up npmignore
  * Clarify README typos
  * Increase mass threshold for code climate
  * Improve acceptance test coverage
  * Improve advanced configuration section of readme

v0.4.6 / 2016-08-07
===================

  * Don't run Travis on non-master branches
  * Read in testem config for constructing test page urls

v0.4.5 / 2016-08-03
===================

  * Fix node tests after core-object changes
  * Fix tests of ember-exam in 2.7
  * Upgrade all deps to align with Ember 2.7.0.
  * Temporarily undocument `--weighted`.
  * Setup and document ember-try integration

v0.4.4 / 2016-06-21
===================

  * Remove unused dependencies
  * Make codeclimate and eslint configs local
  * Make requires lazy where possible
  * Remove unused Array utilities
  * Add CodeClimate badges to README
  * Setup Istanbul code coverage for node code
  * Fix issues found via CodeClimate
  * Fix Travis badge to point to master
  * Add additional badges to README

v0.4.3 / 2016-06-05
===================

  * Add Acceptance test for Testem output
  * Add partition number to Testem output only when applicable
  * Handle _split and _partition params as strings
  * Fix typo, partition -> _partition

v0.4.2 / 2016-06-02
===================

  * Introduce tests for TestLoader
  * Add useful errors to TestLoader
  * Don't fail when lint tests are disabled

v0.4.1 / 2016-05-24
===================

  * Fix super callbacks context

v0.4.0 / 2016-05-24
===================

  * Remove AST manipulations and refine API


================================================
FILE: CONTRIBUTING.md
================================================
# How To Contribute

## Installation

- `git clone https://github.com/ember-cli/ember-exam.git`
- `cd ember-exam`
- `yarn install`

## Linting

- `yarn lint:hbs`
- `yarn lint:js`
- `yarn lint:js --fix`

## Running tests

- `yarn test:ember` – Runs the test suite on the current Ember version
- `yarn test:ember --server` – Runs the test suite in "watch mode"
- `yarn test:node` - Runs the node tests
- `yarn test:all` – Runs the test suite against multiple Ember versions

## Running the dummy application

- `yarn start`
- Visit the dummy application at [http://localhost:4200](http://localhost:4200).

For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/).

## Debugging testem

Terminal 1
```bash
pnpm ember exam --load-balance --path ./dist --parallel 2 --testem-debug testem.log
```
Terminal 2
```bash
tail -f testem.log
```


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) 2015

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# Ember Exam
![Build Status](https://github.com/ember-cli/ember-exam/actions/workflows/ci.yml/badge.svg?event=push)
[![NPM Version](https://badge.fury.io/js/ember-exam.svg)](https://badge.fury.io/js/ember-exam)
[![Ember Observer Score](https://emberobserver.com/badges/ember-exam.svg)](https://emberobserver.com/addons/ember-exam)

Ember Exam is an addon to allow you more control over how you run your tests when used in conjunction with [ember-qunit](https://github.com/emberjs/ember-qunit). It provides the ability to randomize, split, parallelize, and load-balance your test suite by adding a more robust CLI command.

It started as a way to help reduce flaky tests and encourage healthy test driven development. It's like [Head & Shoulders](http://www.headandshoulders.com/) for your tests!

[![Introduction to Ember Exam](https://cloud.githubusercontent.com/assets/2922250/22800360/157ad67c-eed7-11e6-8d33-d2c59238c7f1.png)](https://embermap.com/video/ember-exam)

The [documentation website](https://ember-cli.github.io/ember-exam/) contains examples and API information.

## Table of Contents

- [Compatibility](#compatibility)
- [Installation](#installation)
- [How To Use](#how-to-use)
  * [Version < `3.0.0`](#version--300)
  * [Randomization](#randomization)
    + [Randomization Iterator](#randomization-iterator)
  * [Splitting](#splitting)
    + [Split Test Parallelization](#split-test-parallelization)
  * [Test Load Balancing](#test-load-balancing)
      - [Test Failure Reproduction](#test-failure-reproduction)
  * [Preserve Test Name](#preserve-test-name)
- [Advanced Configuration](#advanced-configuration)
  * [Ember Try & CI Integration](#ember-try--ci-integration)
  * [Test Suite Segmentation](#test-suite-segmentation)
  * [Exceeding Browser Timeout](#exceeding-browser-timeout)

## Compatibility

* Ember.js v4.8 or above
* Ember CLI v4.8 or above
* Node.js v18 or above

## Installation

Installation is as easy as running:

```bash
$ npm install --save-dev ember-exam
```

## How To Use

Using Ember Exam is fairly straightforward as it extends directly from the default Ember-CLI `test` command. So, by default, it will work exactly the same as `ember test`.

```bash
$ ember exam
$ ember exam --filter='acceptance'
$ ember exam --server
$ ember exam --load-balance --parallel=1
```

For more information and examples, please visit the [documentation website](https://ember-cli.github.io/ember-exam/).
```bash
# A value of filter is acceptance
$ ember exam --filter 'acceptance'

# A value of parallel is 2
$ ember exam --load-balance --parallel=2 --server

# If a `=` is not used to pass a value to an option that requires a value, it will take anything passed after a space as it's value
# In this instance, the value of parallel is --server
$ ember exam --load-balance --parallel --server
```

The idea is that you can replace `ember test` with `ember exam` and never look back.

To get the unique features of Ember Exam (described in-depth below), you will need to **replace** the use of `start()` from `ember-qunit` in `test-helper.js` with `start()` from `ember-exam`:

```js
// test-helper.js
- import { start, setupEmberOnerrorValidation } from 'ember-qunit';
+ import { setupEmberOnerrorValidation } from 'ember-qunit';
+ import { start } from 'ember-exam/test-support';

// Options passed to `start` will be passed-through to ember-qunit
start();
```

## How to use with Vite

All of the above applies, but we need to tell vite to build the app before telling ember/exam to run tests on that output.

Update your test-helper.js to call the ember-exam `start` function:
```diff
  // ...
  import { setApplication } from '@ember/test-helpers';
  import { setup } from 'qunit-dom';
- import { start as qunitStart, setupEmberOnerrorValidation } from 'ember-qunit';
+ import { setupEmberOnerrorValidation } from 'ember-qunit';
+ import { start as startEmberExam } from 'ember-exam/addon-test-support';

- export function start() {
+ export async function start(options) {
    setApplication(Application.create(config.APP));

    setup(QUnit.assert);
    setupEmberOnerrorValidation();

-   qunitStart();
+   // Options passed to `start` will be passed-through to ember-qunit
+   await startEmberExam(options);
  }
```

or if you have a test-helper.ts:
```diff
  // ...
  import { setApplication } from '@ember/test-helpers';
  import { setup } from 'qunit-dom';
- import { start as qunitStart, setupEmberOnerrorValidation } from 'ember-qunit';
+ import { setupEmberOnerrorValidation } from 'ember-qunit';
+ import {
+   start as startEmberExam,
+   type EmberExamStartOptions,
+ } from 'ember-exam/addon-test-support';

- export function start() {
+ export async function start(options: EmberExamStartOptions) {
    setApplication(Application.create(config.APP));

    setup(QUnit.assert);
    setupEmberOnerrorValidation();

-   qunitStart();
+   // Options passed to `start` will be passed-through to ember-qunit
+   await startEmberExam(options);
  }
```

Then, update your tests/index.html to pass availableModules to start:
```html
<script type="module">
  import { start } from './test-helper.js';

  const availableModules = {
    ...import.meta.glob('./application/**/*-test.{js,ts,gjs,gts}'),
    ...import.meta.glob('./rendering/**/*-test.{js,ts,gjs,gts}'),
    ...import.meta.glob('./unit/**/*-test.{js,ts,gjs,gts}'),
  };

	start({ availableModules });
</script>
```


Testing development:
```bash 
NODE_ENV=development vite build --mode development
ember exam --path dist --config-file ./testem.cjs
```

Testing production:
```bash
vite build --mode test
ember exam --path dist --config-file ./testem.cjs
```

> [!NOTE]
> Specifying the `--path` is important because otherwise ember-cli will try to build your vite app, and it will error. 

> [!NOTE]
> Specifying the `--config-path` is important because ember-cli (what backs ember-exam) doesn't know about cjs files. 


### Version < `3.0.0`


Prior to `2.1.0`, Ember Exam must be loaded by importing `addon-test-support/load.js` and calling `loadEmberExam`:

```js
// test-helper.js
import loadEmberExam from 'ember-exam/test-support/load';

loadEmberExam();

```

### Randomization

```bash
$ ember exam --random[=<seed>]
```

The `random` option allows you to randomize the order in which your tests run. You can optionally specify a "seed" value from which to randomize your tests in order to reproduce results. The seed can be any string value. Regardless of whether you specify a seed or not, Ember Exam will log the seed value used for the randomization at the beginning of the test run:

```bash
$ ember exam --random
$ Randomizing tests with seed: liv5d1ixkco6qlatl6o7mbo6r

$ ember exam --random=this_is1337
$ Randomizing tests with seed: this_is1337
```

If you use `random` without specifying a seed, it must be the last argument you pass. Otherwise, Ember Exam will attempt to interpret any following arguments as the seed value. In other words:

```bash
# don't do this
ember exam --random --split=2
Randomizing tests with seed: --split=2 # this is not what we wanted

# do this instead
ember exam --split=2 --random
Randomizing tests with seed: hwr74nkk55vzpvi
```

_Note: You must be using QUnit version `1.23.0` or greater for this feature to work properly.

#### Randomization Iterator

Randomization can be helpful for identifying non-atomic or order-dependent tests. To that end, Ember Exam provides an iterator to make it easy to test lots of variations in your test suite order quickly.

```bash
$ ember exam:iterate <num>
```

This command will build your application once, and then run the test suite with the `random` option for the specified number of iterations. You can optionally skip the build by using a previous build via the `path` option:

```bash
$ ember exam:iterate <num> --path <build-path>
```

Finally, you can pass additional options through to the exam command used to run the tests via the `options` flag:

```bash
$ ember exam:iterate <num> --options <options>
```

The `options` should be a string matching what you would use via the CLI.

### Generating Module Metadata File For Test Execution

```bash
$ ember exam --write-module-metadata-file
$ ember exam --wmmf
```

The `--write-module-metadata-file`, `wmmf` as an alias, allows you to generate a module metadata file after a test run. The file provides metadata about the test modules executed.

It creates a json file, `module-metadata-<timestamp>.json`, which contains an array of elements representing metadata of modules executed by sorted by ascending order:
```json
[
  {
    "moduleName": "Module-name",
    "total": "Total number of tests in the module",
    "passed": "A number of passed tests in the module",
    "failed": "A number of failed tests in the module",
    "skipped": "A number of skipped tests in the module",
    "duration": "ms in Total duration to execute the module",
    "failedTests": "A list of failed tests"
  }
]
```

and it looks something like below:
```json
[
  {
    "moduleName": "Slowest-module",
    "total": 12,
    "passed": 9,
    "failed": 1,
    "skipped": 2,
    "duration": 153,
    "failedTests": ["failed-test-1"]
  },
  {
    "moduleName": "Fastest-module",
    "total": 2,
    "passed": 1,
    "failed": 0,
    "skipped": 0,
    "duration": 123,
    "failedTests": []
  }
]
```


### Splitting

```bash
$ ember exam --split=<num>
```

The `split` option allows you to specify the number of partitions greater than one to spread your tests across. Ember Exam will then proceed to run the first batch of tests.

```bash
$ ember exam --split=<num> --partition=<num>
```

The `partition` option allows you to specify which test group to run after using the `split` option. It is one-indexed, so if you specify a split of 3, the last group you could run is 3 as well. You can also run multiple partitions, e.g.:

```bash
$ ember exam --split=4 --partition=1 --partition=2
```

_Note: Ember Exam splits tests by modifying the ember-qunit's `TestLoader` to bucket each test file into a partition, where each partition has an even number of test files. This makes it possible to have unbalanced partitions. To run your tests with balanced partitions, consider using `--load-balance`. For more info, see [_Test Load Balancing_](#test-load-balancing).

#### Split Test Parallelization

```bash
$ ember exam --split=<num> --parallel
```

The `parallel` option allows you to run your split tests across multiple test pages in parallel in [Testem](https://github.com/testem/testem). It will use a separate browser instance for each group of tests. So, if you specify a split of 3, then 3 browser instances will be spawned with the output looking something like:

```bash
ok 1 PhantomJS 1.9 - Exam Partition 1 - some test
ok 2 PhantomJS 1.9 - Exam Partition 3 - some other other test
ok 3 PhantomJS 1.9 - Exam Partition 2 - some other test
```

You can also combine the `parallel` option with the `partition` option to split tests, and then recombine partitions into parallel runs. This would, for example, allow you to run tests in multiple CI containers and have each CI container parallelize its list of tests.

For example, if you wanted to run your tests across two containers, but have one of them run twice as many tests as the other, and run them in parallel, you could do this:

```bash
# container 1
ember exam --split=3 --partition=1,2 --parallel
```

```bash
# container 2
ember exam --split=3 --partition=3 --parallel
```

**Note 1**: _Ember Exam will respect the `parallel` setting of your [Testem config file](https://github.com/testem/testem/blob/master/docs/config_file.md#config-level-options) while running tests in parallel. The default value for `parallel` in Testem is 1, which means you'll need a non-default value to actually see parallel behavior._

**Note 2**: _Ember Exam sets `process.env.EMBER_EXAM_SPLIT_COUNT` for convenience. You can use this in your Testem file._

**Note 3**: _You must be using Testem version `1.5.0` or greater for this feature to work properly._

### Filtering

Ember Exam provides options to filter test suites by two types - module path and test file path.

```bash
$ ember exam --module-path=<module-path>
```

The `module-path` option allows you to filter module paths by a given value. Module paths are mapped by test files and they are generated during `ember build`. After the build, `tests.js` file is created and it resides under <build-directory>/assets. The file is combined of all tests in an application and it has a form of `define("<module-path>", others..`.

The value for `module-path` can have either string or regular expression, for instance:

```bash
# When module path value is string. This will run all modules which match with the passed value
$ ember exam --module-path='dummy/tests/helpers/module-for-acceptance'

# When module path value is regex. This will run all modules which have `dummy` in it
$ ember exam --module-path='!/dummy/'
```

The `file-path` option is to filter tests by *test file path*. The test file path is a location of the test file in a file system. You can specify `file-path` to a location of specific test file path or you can use wildcards in paths to target multiple test files.

```bash
# This will run tests that are defined in `/my-application/tests/unit/my-test.js`
$ ember exam --file-path='/my-application/tests/unit/my-test.js'

# This will run all test files that are under `/my-application/tests/unit/`
$ ember exam --file-path='/my-application/tests/unit/*.js'
```

### Test Load Balancing

```bash
$ ember exam --parallel=<num> --load-balance
```

The `load-balance` option allows you to load balance test files against multiple browsers. It will order the test files by test types, e.g. acceptance | integration | unit, and load balance the ordered test files between the browsers dynamically rather than statically.
**Note:** parallel must be used along with load-balance to specify a number of browser(s)

The `load-balance` option was added to version 1.1 to address execution performance when running against a large test suite.

Web browsers and the testem server communicate via promise in order to send and receive test file. The promise timeout value is set to 15 seconds, and is configurable by adding `asyncTimeout=[timeout]` as a querystring param in the test URL or adding to the `test_page` option in the testem config.
For example, if you specify `load-balance` and `parallel` equals 3, then three browser instances will be created and the output will look something like:

```bash
# ember exam --parallel=3 --load-balance
ok 1 Chrome 66.0 - Browser Id 1 - some test
ok 2 Chrome 66.0 - Browser Id 2 - some another test
ok 3 Chrome 66.0 - Browser Id 3 - some the other test
```

You can also specify the `split` and `partition` options with `load-balance` to load a portion of test modules on multiple CI containers.

```bash
$ ember exam --split=<num> --partition=<num> --parallel=<num> --load-balance
```

This command will split test files and load-balance tests from the specified partition across the browsers. For example `ember exam --split=2 --partition=1 --parallel=3 --load-balance`, the complete list of test files are split into two halves. With the first half of the list load balanced against three browsers. The output will look something like below:

```bash
# ember exam --split=2 --partition=1 --parallel=3 --load-balance
ok 1 Chrome 66.0 - Exam Partition 1 - browser Id 1 - some test
ok 2 Chrome 66.0 - Exam Partition 1 - browser Id 2 - another test
ok 3 Chrome 66.0 - Exam Partition 1 - browser Id 3 - some the other test
```


**Important information on Load Balancing**

1. The `--load-balance` option is currently only supported in CI mode and for that reason no-launch cannot be used with load-balance.
2. You must be using `ember-cli` version 3.2.0 or greater for load balancing and test failure reproduction features to work properly.
3. You must be using `ember-qunit` version 4.1.1 or greater for this feature to work properly.
4. You must be using `qunit` version 2.13.0 or greater for this feature to work properly.

##### Test Failure Reproduction

Due to the dynamic nature of the load-balance option, test file execution order can vary between runs. In order to reproduce a past test execution, the execution must be recorded via passing --write-execution-file or --wef, which allows generating a JSON file that enables rerunning the past test execution. The option is only allowed when load-balance is passed.

```bash
# The command will load in test balanced mode with <num> of browser(s). After the test suite execution, it will generate a test-execution json file.
$ ember exam --parallel=<num> --load-balance --wef
$ ember exam --parallel=<num> --load-balance --write-execution-file
```

The file is stored in the root directory and the naming structure is `test-execution-<timestamp>.json`.
To replay the test execution for particular browser(s), do the following:

```bash
# The command will read a test execution file specified for `replay-execution` and execute a browser Id(s) from `replay-browser`
$ ember exam --replay-execution=[string] --replay-browser=[num]
```

`replay-execution` allows you to specify a path to the json file to run execution against and `replay-browser` is to specify browser ID(s) to execute.

```bash
# The command will read test-execution-000000.json and load the list of modules mapped to browserId 1
$ ember exam --replay-execution=test-execution-000000.json --replay-browser=1
```

The above command will read `test-execution-000000.json` and load the list of modules which is mapped by browser ID #1.

`replay-browser` can be an array of browser IDs. For instance `--replay-browser=1,2` will start two browsers and execute a list of modules which were previously run by browsers #1 and #2.

```bash
# The command will read test-execution-000000.json and load the list of module mapped to browserId 1 and 2
$ ember exam --replay-execution=test-execution-000000.json --replay-browser=1,2
```

When `replay-browser` value is not specified it will execute browserId(s) read from `failedBrowser` in the test execution file.

```bash
# The command will read test-execution-000000.json and load the list of modules mapped to browserIds from failedBrowser in the json file.
$ ember exam --replay-execution=test-execution-000000.json
```

When `replay-browser` value is not specified and there is no value for `failedBrowser` in the json file it will rerun all list of modules.

```bash
# The command will read test-execution-000000.json and load the list of module mapped to all browserIds when failedBrowser is none in the json file
$ ember exam --replay-execution=test-execution-000000.json
```

**Important information on `--replay-execution` and `--replay-browser`**

1. You must be using `ember-cli` version 3.2.0 or greater for load-balnce and test failure reproduction features to work properly.
2. You must be using `ember-qunit` version 4.1.1 or greater for this feature to work properly.
3. You must be using `qunit` version 2.8.0 or greater for this feature to work properly.

#### Preserve Test Name

When using `--split` and/or `--load-balance` the output will look something like:

```bash
# ember exam --split=2 --partition=1 --parallel=3 --load-balance
ok 1 Chrome 66.0 - Exam Partition 1 - browser Id 1 - some test
ok 2 Chrome 66.0 - Exam Partition 1 - browser Id 2 - another test
ok 3 Chrome 66.0 - Exam Partition 1 - browser Id 3 - some the other test
```
However, if you change the amount of parallelization, or randomize across partitions, the output will change for the same test, which may be an issue if you are tracking test insights over time.

```bash
# ember exam --split=2 --partition=1 --parallel=2 --load-balance
ok 1 Chrome 66.0 - Exam Partition 1 - browser Id 2 - some test
ok 2 Chrome 66.0 - Exam Partition 1 - browser Id 1 - another test
ok 3 Chrome 66.0 - Exam Partition 1 - browser Id 2 - some the other test
```
You can add `--preserve-test-name` to remove the dynamic segments of the output (partition and browser) to ensure the output test names are always the same.

```bash
# ember exam --split=2 --partition=1 --parallel=3 --load-balance --preserve-test-name
ok 1 Chrome 66.0 - some test
ok 2 Chrome 66.0 - another test
ok 3 Chrome 66.0 - some the other test
```

## Advanced Configuration

Ember Exam does its best to allow you to run your test suite in a way that is effective for your individual needs. To that end, there are lots of advanced ways to configure your setup by integrating with other aspects of the Ember testing environment. The following sections will cover a few of the more common scenarios.

### Ember Try & CI Integration

Integrating ember-exam with [ember-try](https://github.com/ember-cli/ember-try) is remarkably easy. Define a [`command` in your `ember-try.js` config](https://github.com/ember-cli/ember-try#configuration-files) that leverages the `exam` command:

```js
// config/ember-try.js
module.exports = {
  command: 'ember exam --split 3 --parallel',
  // ...
};
```

Using [environmental variables](https://nodejs.org/api/process.html#process_process_env) gives you flexibility in how you run your tests. For instance, you could distribute your tests across processes instead of parallelizing them by specifying a `PARTITION` variable in your process environment and then consuming it like so:

```js
module.exports = {
  command: 'ember exam --split 20 --partition ' + process.env.PARTITION,
  // ...
};
```

If you are working with [Travis CI](https://travis-ci.org/) then you can also easily set up seeded-random runs based on PR numbers. Similar to the following:

```js
const command = [ 'ember', 'exam', '--random' ];
const pr = process.env.TRAVIS_PULL_REQUEST;

if (pr) {
  command.push(pr);
}

module.exports = {
  command: command.join(' '),
  // ...
};
```

You can refer to [Travis' default environment variables](https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables) to see what else you could possibly leverage for your test setup.

### Test Suite Segmentation

Some test suites like to segment which tests run based on various facets such as type of test, feature being tested, and so on. This can be accomplished by leveraging Testem's ability to have multiple test pages:

```json
{
  "test_page": [
    "tests/index.html?filter=acceptance",
    "tests/index.html?filter=!acceptance"
  ]
}
```

You can use this feature in conjunction with Ember Exam's features, which will allow you to segment your test suite but still gain benefits from randomization and splitting.

### Exceeding Browser Timeout

If you have a lot of tests you may run into a timeout error, especially in CI environments with constrained resources.

```
Error: Browser timeout exceeded: 10s
```

You can work around this by increasing `browser_disconnect_timeout` in testem.js:

```js
module.exports = {
  browser_disconnect_timeout: 30,
};
```


================================================
FILE: RELEASE.md
================================================
# Release Process

Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged.

## Preparation

Since the majority of the actual release process is automated, the remaining tasks before releasing are:

- correctly labeling **all** pull requests that have been merged since the last release
- updating pull request titles so they make sense to our users

Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall
guiding principle here is that changelogs are for humans, not machines.

When reviewing merged PR's the labels to be used are:

- breaking - Used when the PR is considered a breaking change.
- enhancement - Used when the PR adds a new feature or enhancement.
- bug - Used when the PR fixes a bug included in a previous release.
- documentation - Used when the PR adds or updates documentation.
- internal - Internal changes or things that don't fit in any other category.

**Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal`

## Release

Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/ember-cli/ember-exam/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR


================================================
FILE: addon-test-support/-private/async-iterator.js
================================================
'use strict';

const iteratorCompleteResponse = { done: true, value: null };

/**
 * A class to iterate a sequencial set of asynchronous events.
 *
 * @class AsyncIterator
 */
export default class AsyncIterator {
  constructor(testem, options) {
    this._testem = testem;
    this._request = options.request;
    this._response = options.response;
    this._done = false;
    this._current = null;
    this._boundHandleResponse = this.handleResponse.bind(this);
    this._waiting = false;
    // Set a timeout value from either url parameter or default timeout value, 15 s.
    this._timeout = options.timeout || 15;
    this._browserId = options.browserId;
    this._emberExamExitOnError = options.emberExamExitOnError;

    testem.on(this._response, this._boundHandleResponse);
  }

  /**
   * Indicates whether the response queue is done or not.
   *
   * @method done
   * @return {bool} whether the response queue is done or not
   */
  get done() {
    return this._done;
  }

  /**
   * @method toString
   * @return {String} the stringified value of the iterator.
   */
  toString() {
    return `<AsyncIterator (request: ${this._request} response: ${this._response})>`;
  }

  /**
   * Handle a response when it's waiting for a response
   *
   * @method handleResponse
   * @param {*} response
   */
  handleResponse(response) {
    if (this._waiting === false) {
      throw new Error(
        `${this.toString()} Was not expecting a response, but got a response`,
      );
    } else {
      this._waiting = false;
    }

    try {
      if (response.done) {
        this.dispose();
      }
      this._current.resolve(response);
    } catch (e) {
      this._current.reject(e);
    } finally {
      this._current = null;

      if (this.timer) {
        clearTimeout(this.timer);
      }
    }
  }

  /**
   * Dispose when an iteration is finished.
   *
   * @method dispose
   */
  dispose() {
    this._done = true;
    this._testem.removeEventCallbacks(
      this._response,
      this._boundHandleResponse,
    );
  }

  /**
   * Emit the current request.
   *
   * @method _makeNextRequest
   */
  _makeNextRequest() {
    this._waiting = true;
    this._testem.emit(this._request, this._browserId);
  }

  /**
   * Set a timeout to reject a promise if it doesn't get response within the timeout threshold.
   *
   * @method _setTimeout
   * @param {*} resolve
   */
  _setTimeout(resolve, reject) {
    clearTimeout(this.timeout);
    this.timer = setTimeout(() => {
      if (!this._waiting) {
        return;
      }

      if (this._emberExamExitOnError) {
        let err = new Error(
          `EmberExam: Promise timed out after ${this._timeout} s while waiting for response for ${this._request}`,
        );
        reject(err);
      } else {
        console.error(
          `EmberExam: Promise timed out after ${this._timeout} s while waiting for response for ${this._request}. Closing browser to exit gracefully.`,
        );
        resolve(iteratorCompleteResponse);
      }
    }, this._timeout * 1000);
  }

  /**
   * Gets the next response from the request and resolve the promise.
   * if it's end of the iteration resolve the promise with done being true.
   *
   * @method next
   * @return {Promise}
   */
  next() {
    if (this._done) {
      return Promise.resolve(iteratorCompleteResponse);
    }
    if (this._current) {
      return this._current.promise;
    }

    let resolve, reject;
    let promise = new Promise((_resolve, _reject) => {
      resolve = _resolve;
      reject = _reject;
      this._setTimeout(resolve, reject);
    });

    this._current = {
      resolve,
      reject,
      promise,
    };

    this._makeNextRequest();

    return promise;
  }
}


================================================
FILE: addon-test-support/-private/ember-exam-test-loader.js
================================================
import { assert } from '@ember/debug';
import getUrlParams from './get-url-params';
import splitTestModules from './split-test-modules';
import weightTestModules from './weight-test-modules';
import { filterTestModules } from './filter-test-modules';
import { TestLoader } from 'ember-qunit/test-loader';
import AsyncIterator from './async-iterator';
import QUnit from 'qunit';

/**
 * EmberExamQUnitTestLoader allows delayed requiring of test modules to enable test load balancing
 * It extends ember-qunit/test-loader used by `ember test`, since it overrides moduleLoadFailure()
 * to log a test failure when a module fails to load
 * @class EmberExamQUnitTestLoader
 * @extends {TestLoader}
 */
export default class EmberExamTestLoader extends TestLoader {
  constructor(testem, urlParams, qunit = QUnit) {
    super();
    this._testModules = [];
    this._testem = testem;
    this._qunit = qunit;
    this._urlParams = urlParams || getUrlParams();
  }

  get urlParams() {
    return this._urlParams;
  }

  /**
   * ember-cli-test-loader instantiates a new TestLoader instance and calls loadModules.
   * EmberExamQUnitTestLoader does not support load() in favor of loadModules().
   *
   * @method load
   */
  static load() {
    throw new Error("`EmberExamQUnitTestLoader` doesn't support `load()`.");
  }

  /**
   * require() collects the full list of modules before requiring each module with
   * super.require(), instead of requiring and unseeing a module when each gets loaded.
   *
   * @method require
   * @param {string} moduleName
   */
  require(moduleName) {
    this._testModules.push(moduleName);
  }

  /**
   * Make unsee a no-op to avoid any unwanted resets
   *
   * @method unsee
   */
  unsee() {}

  /**
   * Loads the test modules depending on the urlParam
   *
   * @method loadModules
   */
  async loadModules({ availableModules } = {}) {
    const loadBalance = this._urlParams.get('loadBalance');
    const browserId = this._urlParams.get('browser');
    const modulePath = this._urlParams.get('modulePath');
    const filePath = this._urlParams.get('filePath');
    let partitions = this._urlParams.get('partition');
    let split = parseInt(this._urlParams.get('split'), 10);

    split = isNaN(split) ? 1 : split;

    if (partitions === undefined) {
      partitions = [1];
    } else if (!Array.isArray(partitions)) {
      partitions = [partitions];
    }

    if (!availableModules) {
      super.loadModules();
    } else {
      assert(
        `Available modules must be an object.`,
        typeof availableModules === 'object',
      );

      this._availableModules = availableModules;
      this._testModules = Object.keys(availableModules);
    }

    this.setupModuleMetadataHandler();

    if (modulePath || filePath) {
      this._testModules = filterTestModules(
        this._testModules,
        modulePath,
        filePath,
      );
    }

    if (loadBalance && this._testem) {
      this.setupLoadBalanceHandlers();
      this._testModules = splitTestModules(
        weightTestModules(this._testModules),
        split,
        partitions,
      );

      this._testem.emit(
        'testem:set-modules-queue',
        this._testModules,
        browserId,
      );
    } else {
      this._testModules = splitTestModules(
        this._testModules,
        split,
        partitions,
      );

      if (this._availableModules) {
        await this.loadAvailableModules();
        return;
      }

      /**
       * Legacy support
       */
      this._testModules.forEach((moduleName) => {
        super.require(moduleName);
        super.unsee(moduleName);
      });
    }
  }

  /**
   * availableModules are passed in from loadModules
   * from loadEmberExam
   * from start
   */
  async loadAvailableModules() {
    if (this._availableModules) {
      await Promise.all(
        this._testModules.map(async (moduleName) => {
          let loader = this._availableModules[moduleName];

          /**
           * If it's not a function, it's already loaded
           */
          if (typeof loader === 'function') {
            await loader();
          }
        }),
      );
    }
  }

  /**
   * Allow loading one module at a time.
   *
   * @method loadIndividualModule
   * @param {string} moduleName
   */
  async loadIndividualModule(moduleName) {
    if (moduleName === undefined) {
      throw new Error(
        'Failed to load a test module. `moduleName` is undefined in `loadIndividualModule`.',
      );
    }

    if (this._availableModules) {
      let loader = this._availableModules[moduleName];

      /**
       * If it's not a function, it's already loaded
       */
      if (typeof loader === 'function') {
        await loader();
      }

      return;
    }

    super.require(moduleName);
    super.unsee(moduleName);
  }

  /**
   * setupModuleMetadataHandler() register QUnit callback to enable generating module metadata file.
   *
   * @method setupModuleMetadataHandler
   */
  setupModuleMetadataHandler() {
    this._qunit.testDone((metadata) => {
      if (typeof this._testem !== 'undefined' && this._testem !== null) {
        // testem:test-done-metadata is sent to server to track test module details.
        // metadata contains name, module, failed, passed, total, duration, skipped, and todo.
        // https://api.qunitjs.com/callbacks/QUnit.testDone
        this._testem.emit('testem:test-done-metadata', metadata);
      }
    });
  }

  /**
   * setupLoadBalanceHandlers() registers QUnit callbacks needed for the load-balance option.
   *
   * @method setupLoadBalanceHandlers
   */
  setupLoadBalanceHandlers() {
    // nextModuleAsyncIterator handles the async testem events
    // it returns an element of {value: <moduleName>, done: boolean}
    const nextModuleAsyncIterator = new AsyncIterator(this._testem, {
      request: 'testem:next-module-request',
      response: 'testem:next-module-response',
      timeout: this._urlParams.get('asyncTimeout'),
      browserId: this._urlParams.get('browser'),
      emberExamExitOnError: this._urlParams.get('_emberExamExitOnError'),
    });

    const nextModuleHandler = () => {
      // if there are already tests queued up, don't request next module
      // this is possible if a test file has multiple qunit modules
      if (this._qunit.config.queue.length > 0) {
        return;
      }

      return nextModuleAsyncIterator
        .next()
        .then(async (response) => {
          if (!response.done) {
            const moduleName = response.value;
            await this.loadIndividualModule(moduleName);

            // if no tests were added, request the next module
            if (this._qunit.config.queue.length === 0) {
              return nextModuleHandler();
            }
          }
        })
        .catch((e) => {
          if (
            typeof e === 'object' &&
            e !== null &&
            typeof e.message === 'string'
          ) {
            e.message = `EmberExam: Failed to get next test module: ${e.message}`;
          }
          throw new Error(`EmberExam: Failed to get next test module: ${e}`);
        });
    };

    // it registers qunit begin callback to ask for a next test moudle to execute when the test suite begins.
    // By default ember-qunit adds `Ember.onerror` test to a qunit processing queue and once the test is complete it execute _qunit.moduleDone callback.
    // However, when `setupEmberOnerrorValidation: false` is passed the test is disabled and _qunit.begin callback needs to request a next test module to run.
    this._qunit.begin(() => {
      return nextModuleHandler();
    });

    this._qunit.moduleDone(() => {
      return nextModuleHandler();
    });
  }
}


================================================
FILE: addon-test-support/-private/filter-test-modules.js
================================================
// A regular expression to help parsing a string to verify regex.
const MODULE_PATH_REGEXP = /^(!?)\/(.*)\/(i?)$/;
const TEST_PATH_REGEX = /\/tests\/(.*?)$/;

/**
 * Return the matched test.
 * e.g. if an input is '!/weight/' it returns an array, ['!/weight/', '!', 'weight', ''];
 *
 * @function getRegexFilter
 * @param {*} modulePath
 */
function getRegexFilter(modulePath) {
  return MODULE_PATH_REGEXP.exec(modulePath);
}

/**
 * Determine if a given module path is matched with module filter with wildcard.
 * e.g. A given moduleFilter, /tests/integration/*, matches with /tests/integration/foo and /tests/integration/bar
 *
 * @function wildcardFilter
 * @param {*} module
 * @param {*} moduleFilter
 */
function wildcardFilter(module, moduleFilter) {
  // Generate a regular expression to handle wildcard from path filter
  const moduleFilterRule = [
    '^.*',
    moduleFilter.split('*').join('.*'),
    '$',
  ].join('');
  return new RegExp(moduleFilterRule).test(module);
}

/**
 * Return a list of test modules that contain a given module path string.
 *
 * @function stringFilter
 * @param {Array<string>} modules
 * @param {string} moduleFilter
 */
function stringFilter(modules, moduleFilter) {
  return modules.filter(
    (module) =>
      module.includes(moduleFilter) || wildcardFilter(module, moduleFilter),
  );
}

/**
 * Return a list of test modules that matches with a given regular expression.
 *
 * @function regexFilter
 * @param {Array<string>} modules
 * @param {Array<string>} modulePathRegexFilter
 */
function regexFilter(modules, modulePathRegexFilter) {
  const re = new RegExp(modulePathRegexFilter[2], modulePathRegexFilter[3]);
  const exclude = modulePathRegexFilter[1];

  return modules.filter(
    (module) => (!exclude && re.test(module)) || (exclude && !re.test(module)),
  );
}

/**
 * Return a module path that's mapped by a given test file path.
 *
 * @function convertFilePathToModulePath
 * @param {*} filePath
 */
function convertFilePathToModulePath(filePath) {
  const filePathWithNoExtension = filePath.replace(/\.[^/.]+$/, '');
  const testFilePathMatch = TEST_PATH_REGEX.exec(filePathWithNoExtension);
  if (typeof filePath !== 'undefined' && testFilePathMatch !== null) {
    return testFilePathMatch[0];
  }

  return filePathWithNoExtension;
}

/**
 * Returns a list of test modules that match with the given module path filter or test file path.
 *
 * @function filterTestModules
 * @param {Array<string>} modules
 * @param {string} modulePath
 * @param {string} filePath
 */
function filterTestModules(modules, modulePath, filePath) {
  // Generates an array with module filter value seperated by comma (,).
  const moduleFilters = (filePath || modulePath)
    .split(',')
    .map((value) => value.trim());

  const filteredTestModules = moduleFilters.reduce((result, moduleFilter) => {
    const modulePath = convertFilePathToModulePath(moduleFilter);
    const modulePathRegex = getRegexFilter(modulePath);

    if (modulePathRegex) {
      return result.concat(
        regexFilter(modules, modulePathRegex).filter(
          (module) => result.indexOf(module) === -1,
        ),
      );
    } else {
      return result.concat(
        stringFilter(modules, modulePath).filter(
          (module) => result.indexOf(module) === -1,
        ),
      );
    }
  }, []);

  if (filteredTestModules.length === 0) {
    throw new Error(
      `No tests matched with the filter: ${modulePath || filePath}.`,
    );
  }
  return filteredTestModules;
}

export { convertFilePathToModulePath, filterTestModules };


================================================
FILE: addon-test-support/-private/get-url-params.js
================================================
function decodeQueryParam(param) {
  return decodeURIComponent(param.replace(/\+/g, '%20'));
}

/**
 * Parses the url and return an object containing a param's key and value
 *
 * @export
 * @function getUrlParams
 * @return {Object} urlParams
 */
export default function getUrlParams() {
  const urlParams = new Map();
  const params = location.search.slice(1).split('&');

  for (let i = 0; i < params.length; i++) {
    if (params[i]) {
      const param = params[i].split('=');
      const name = decodeQueryParam(param[0]);

      // Allow just a key to turn on a flag, e.g., test.html?noglobals
      const value =
        param.length === 1 || decodeQueryParam(param.slice(1).join('='));
      if (urlParams.has(name)) {
        urlParams.set(name, [].concat(urlParams.get(name), value));
      } else {
        urlParams.set(name, value);
      }
    }
  }

  return urlParams;
}


================================================
FILE: addon-test-support/-private/patch-testem-output.js
================================================
/* globals Testem */

/**
 * Returns a modified test name including browser or partition information
 *
 * @function updateTestName
 * @param {Map} urlParams
 * @param {string} testName
 * @return {string} testName
 */
export function updateTestName(urlParams, testName) {
  const split = urlParams.get('split');
  const loadBalance = urlParams.get('loadBalance');

  const partition = urlParams.get('partition') || 1;
  const browser = urlParams.get('browser') || 1;

  const preserveTestName = !!urlParams.get('preserveTestName');

  if (preserveTestName) {
    return testName;
  } else if (split && loadBalance) {
    testName = `Exam Partition ${partition} - Browser Id ${browser} - ${testName}`;
  } else if (split) {
    testName = `Exam Partition ${partition} - ${testName}`;
  } else if (loadBalance) {
    testName = `Browser Id ${browser} - ${testName}`;
  }

  return testName;
}

/**
 * Setup testem test-result event to update the test name when a test completes
 *
 * @function patchTestemOutput
 * @param {Map} urlParams
 */
export function patchTestemOutput(urlParams) {
  Testem.on('test-result', (test) => {
    test.name = updateTestName(urlParams, test.name);
  });
}


================================================
FILE: addon-test-support/-private/split-test-modules.js
================================================
function createGroups(num) {
  const groups = new Array(num);

  for (let i = 0; i < num; i++) {
    groups[i] = [];
  }

  return groups;
}

function splitIntoGroups(arr, numGroups) {
  const groups = createGroups(numGroups);

  for (let i = 0; i < arr.length; i++) {
    groups[i % numGroups].push(arr[i]);
  }

  return groups;
}

/**
 * Splits the list of modules into unique subset of modules
 * return the subset indexed by the partition
 *
 * @export
 * @function splitTestModules
 * @param {Array<string>} modules
 * @param {number} split
 * @param {number} partitions
 * @return {Array<string>} tests
 */
export default function splitTestModules(modules, split, partitions) {
  if (split < 1) {
    throw new Error('You must specify a split greater than 0');
  }

  const testGroups = splitIntoGroups(modules, split);
  const tests = [];

  for (let i = 0; i < partitions.length; i++) {
    const partition = parseInt(partitions[i], 10);
    if (isNaN(partition)) {
      throw new Error(
        "You must specify numbers for partition (you specified '" +
          partitions +
          "')",
      );
    }

    if (split < partition) {
      throw new Error(
        'You must specify partitions numbered less than or equal to your split value of ' +
          split,
      );
    } else if (partition < 1) {
      throw new Error('You must specify partitions numbered greater than 0');
    }

    const group = partition - 1;
    tests.push(...testGroups[group]);
  }

  return tests;
}


================================================
FILE: addon-test-support/-private/weight-test-modules.js
================================================
const TEST_TYPE_WEIGHT = {
  unit: 10,
  integration: 20,
  acceptance: 150,
};
const WEIGHT_REGEX = /\/(unit|integration|acceptance)\//;
const DEFAULT_WEIGHT = 50;

/**
 * Return the weight for a given module name, a file path to the module
 * Ember tests consist of Acceptance, Integration, and Unit tests. In general, acceptance takes
 * longest time to execute, followed by integration and unit.
 * The weight assigned to a module corresponds to its test type execution speed, with slowest being the highest in weight.
 * If the test type is not identifiable from the modulePath, weight default to 50 (ordered after acceptance, but before integration)
 *
 * @function getWeight
 * @param {string} modulePath File path to a module
 */
function getWeight(modulePath) {
  const [, key] = WEIGHT_REGEX.exec(modulePath) || [];
  if (typeof TEST_TYPE_WEIGHT[key] === 'number') {
    return TEST_TYPE_WEIGHT[key];
  } else {
    return DEFAULT_WEIGHT;
  }
}

/**
 * Returns the list of modules sorted by its weight
 *
 * @export
 * @function weightTestModules
 * @param {Array<string>} modules
 * @return {Array<string>}
 */
export default function weightTestModules(modules) {
  const groups = new Map();

  modules.forEach((module) => {
    const moduleWeight = getWeight(module);
    let moduleWeightGroup = groups.get(moduleWeight);

    if (Array.isArray(moduleWeightGroup)) {
      moduleWeightGroup.push(module);
    } else {
      moduleWeightGroup = [module];
    }

    groups.set(moduleWeight, moduleWeightGroup);
  });

  // return modules sorted by weight and alphabetically within its weighted groups
  return Array.from(groups.keys())
    .sort((a, b) => b - a)
    .reduce((accumulatedArray, weight) => {
      const sortedModuleArr = groups.get(weight).sort();
      return accumulatedArray.concat(sortedModuleArr);
    }, []);
}


================================================
FILE: addon-test-support/index.d.ts
================================================
import { QUnitStartOptions } from 'ember-qunit';

export type EmberExamStartOptions = Omit<QUnitStartOptions, 'loadTests'> & {
  availableModules: Record<string, unknown>;
};

export function start(options: EmberExamStartOptions): Promise<void>;


================================================
FILE: addon-test-support/index.js
================================================
export { default as start } from './start';


================================================
FILE: addon-test-support/load.js
================================================
import EmberExamTestLoader from './-private/ember-exam-test-loader';
import { patchTestemOutput } from './-private/patch-testem-output';

let loaded = false;

/**
 * Setup EmberExamTestLoader to enable ember exam functionalities
 *
 * @function loadEmberExam
 * @return {*} testLoader
 */
export default function loadEmberExam() {
  if (loaded) {
    console.warn('Attempted to load Ember Exam more than once.');
    return;
  }

  loaded = true;

  const testLoader = new EmberExamTestLoader(window.Testem);

  if (window.Testem) {
    patchTestemOutput(testLoader.urlParams);
  }

  return testLoader;
}


================================================
FILE: addon-test-support/start.js
================================================
import loadEmberExam from './load';
import { start as qunitStart } from 'ember-qunit';

/**
 * Equivalent to ember-qunit's loadTest() except this does not create a new TestLoader instance
 *
 * @function loadTests
 * @param {*} testLoader
 * @param {*} loaderOptions
 */
async function loadTests(testLoader, loaderOptions = {}) {
  if (testLoader === undefined) {
    throw new Error(
      'A testLoader instance has not been created. You must call `loadEmberExam()` before calling `loadTest()`.',
    );
  }

  await testLoader.loadModules(loaderOptions);
}

/**
 * Ember-exam's own start function to set up EmberExamTestLoader, load tests and calls start() from
 * ember-qunit
 *
 * @function start
 * @param {*} qunitOptions
 */
export default async function start(qunitOptions = {}) {
  const { availableModules, ...modifiedOptions } =
    qunitOptions || Object.create(null);

  modifiedOptions.loadTests = false;

  const testLoader = loadEmberExam();
  await loadTests(testLoader, { availableModules });
  qunitStart(modifiedOptions);
}


================================================
FILE: docs-app/.gitignore
================================================
dist/
node_modules/
.vitepress/dist
.vitepress/cache


================================================
FILE: docs-app/.vitepress/config.mts
================================================
import { defineConfig } from 'vitepress'

// https://vitepress.dev/reference/site-config
export default defineConfig({
  title: "ember-exam",
  description: "Run your tests with randomization, splitting, and parallelization for beautiful tests.",
  base: '/ember-exam/',
  markdown: {
    // theme: {
    //   ...dark,
    //   settings: [
    //     {
    //       scope: 'comment',
    //       settings: {
    //         // 'foreground': 'rgb(200, 200, 200)'
    //       }
    //     },
    //   ]
    // },
  },
  themeConfig: {
    // https://vitepress.dev/reference/default-theme-config
    nav: [
      { text: 'Home', link: '/' },
      // { text: 'Examples', link: '/markdown-examples' }
    ],

    sidebar: [
      {
        text: 'Options',
        items: [
          { text: 'Randomization', link: '/randomization' },
          { text: 'Randomization Iterator', link: '/randomization-iterator' },
          { text: 'Generating Module Metadata For Test Execution', link: '/module-metadata' },
          { text: 'Splitting', link: '/splitting' },
          { text: 'Split Test Parallelization', link: '/split-parallel' },
          { text: 'Filtering', link: '/filtering' },
          { text: 'Test Load Balancing', link: '/load-balancing' },
        ]
      },
      {
        text: 'Advanced Configuration',
        items: [
          { text: 'Ember Try & CI Integration', link: '/ember-try-and-ci' },
          { text: 'Test Suite Segmentation', link: '/test-suite-segmentation' },
        ]
      }
    ],

    socialLinks: [
      { icon: 'github', link: 'https://github.com/ember-cli/ember-exam' }
    ]
  }
})


================================================
FILE: docs-app/.vitepress/theme/index.ts
================================================
// https://vitepress.dev/guide/custom-theme
import { h } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import './style.css'

export default {
  extends: DefaultTheme,
  Layout: () => {
    return h(DefaultTheme.Layout, null, {
      // https://vitepress.dev/guide/extending-default-theme#layout-slots
    })
  },
  enhanceApp({ app, router, siteData }) {
    // ...
  }
} satisfies Theme


================================================
FILE: docs-app/.vitepress/theme/style.css
================================================
/**
 * Customize default theme styling by overriding CSS variables:
 * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
 */

/**
 * Colors
 *
 * Each colors have exact same color scale system with 3 levels of solid
 * colors with different brightness, and 1 soft color.
 *
 * - `XXX-1`: The most solid color used mainly for colored text. It must
 *   satisfy the contrast ratio against when used on top of `XXX-soft`.
 *
 * - `XXX-2`: The color used mainly for hover state of the button.
 *
 * - `XXX-3`: The color for solid background, such as bg color of the button.
 *   It must satisfy the contrast ratio with pure white (#ffffff) text on
 *   top of it.
 *
 * - `XXX-soft`: The color used for subtle background such as custom container
 *   or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
 *   on top of it.
 *
 *   The soft color must be semi transparent alpha channel. This is crucial
 *   because it allows adding multiple "soft" colors on top of each other
 *   to create a accent, such as when having inline code block inside
 *   custom containers.
 *
 * - `default`: The color used purely for subtle indication without any
 *   special meanings attached to it such as bg color for menu hover state.
 *
 * - `brand`: Used for primary brand colors, such as link text, button with
 *   brand theme, etc.
 *
 * - `tip`: Used to indicate useful information. The default theme uses the
 *   brand color for this by default.
 *
 * - `warning`: Used to indicate warning to the users. Used in custom
 *   container, badges, etc.
 *
 * - `danger`: Used to show error, or dangerous message to the users. Used
 *   in custom container, badges, etc.
 * -------------------------------------------------------------------------- */

:root {
  --vp-c-default-1: var(--vp-c-gray-1);
  --vp-c-default-2: var(--vp-c-gray-2);
  --vp-c-default-3: var(--vp-c-gray-3);
  --vp-c-default-soft: var(--vp-c-gray-soft);

  --vp-c-brand-1: var(--vp-c-indigo-1);
  --vp-c-brand-2: var(--vp-c-indigo-2);
  --vp-c-brand-3: var(--vp-c-indigo-3);
  --vp-c-brand-soft: var(--vp-c-indigo-soft);

  --vp-c-tip-1: var(--vp-c-brand-1);
  --vp-c-tip-2: var(--vp-c-brand-2);
  --vp-c-tip-3: var(--vp-c-brand-3);
  --vp-c-tip-soft: var(--vp-c-brand-soft);

  --vp-c-warning-1: var(--vp-c-yellow-1);
  --vp-c-warning-2: var(--vp-c-yellow-2);
  --vp-c-warning-3: var(--vp-c-yellow-3);
  --vp-c-warning-soft: var(--vp-c-yellow-soft);

  --vp-c-danger-1: var(--vp-c-red-1);
  --vp-c-danger-2: var(--vp-c-red-2);
  --vp-c-danger-3: var(--vp-c-red-3);
  --vp-c-danger-soft: var(--vp-c-red-soft);
}

/**
 * Component: Button
 * -------------------------------------------------------------------------- */

:root {
  --vp-button-brand-border: transparent;
  --vp-button-brand-text: var(--vp-c-white);
  --vp-button-brand-bg: var(--vp-c-brand-3);
  --vp-button-brand-hover-border: transparent;
  --vp-button-brand-hover-text: var(--vp-c-white);
  --vp-button-brand-hover-bg: var(--vp-c-brand-2);
  --vp-button-brand-active-border: transparent;
  --vp-button-brand-active-text: var(--vp-c-white);
  --vp-button-brand-active-bg: var(--vp-c-brand-1);
}

/**
 * Component: Home
 * -------------------------------------------------------------------------- */

:root {
  --vp-home-hero-name-color: transparent;
  --vp-home-hero-name-background: -webkit-linear-gradient(
    120deg,
    #bd34fe 30%,
    #41d1ff
  );

  --vp-home-hero-image-background-image: linear-gradient(
    -45deg,
    #bd34fe 50%,
    #47caff 50%
  );
  --vp-home-hero-image-filter: blur(44px);
}

@media (min-width: 640px) {
  :root {
    --vp-home-hero-image-filter: blur(56px);
  }
}

@media (min-width: 960px) {
  :root {
    --vp-home-hero-image-filter: blur(68px);
  }
}

/**
 * Component: Custom Block
 * -------------------------------------------------------------------------- */

:root {
  --vp-custom-block-tip-border: transparent;
  --vp-custom-block-tip-text: var(--vp-c-text-1);
  --vp-custom-block-tip-bg: var(--vp-c-brand-soft);
  --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
}

/**
 * Component: Algolia
 * -------------------------------------------------------------------------- */

.DocSearch {
  --docsearch-primary-color: var(--vp-c-brand-1) !important;
}



.badges {
  > p {
    display: flex;
    gap: 1rem;
    justify-content: center;
    align-items: center;
  }
  img, a {
    display: inline-flex;
  }

}

/**
* Contrast fixes
*/
html {
  --vp-c-text-2: rgb(40, 40, 40);
}
html.dark {
  --vp-c-text-2: rgb(200, 200, 200);
}

html [class*='language-'] > span.lang {
  --vp-code-lang-color: rgb(40,40,40);
}
html.dark [class*='language-'] > span.lang {
  --vp-code-lang-color: rgb(200,200,200);
}


================================================
FILE: docs-app/ember-try-and-ci.md
================================================
### Ember Try & CI Integration

Integrating ember-exam with [ember-try](https://github.com/ember-cli/ember-try) is remarkably easy. Define a [`command` in your `ember-try.js` config](https://github.com/ember-cli/ember-try#configuration-files) that leverages the `exam` command:

```js
// config/ember-try.js
module.exports = {
  command: 'ember exam --split 3 --parallel',
  // ...
};
```

Using [environmental variables](https://nodejs.org/api/process.html#process_process_env) gives you flexibility in how you run your tests. For instance, you could distribute your tests across processes instead of parallelizing them by specifying a `PARTITION` variable in your process environment and then consuming it like so:

```js
module.exports = {
  command: 'ember exam --split 20 --partition ' + process.env.PARTITION,
  // ...
};
```

If you are working with [Travis CI](https://travis-ci.org/) then you can also easily set up seeded-random runs based on PR numbers. Similar to the following:

```js
const command = ['ember', 'exam', '--random'];
const pr = process.env.TRAVIS_PULL_REQUEST;

if (pr) {
  command.push(pr);
}

module.exports = {
  command: command.join(' '),
  // ...
};
```

You can refer to [Travis' default environment variables](https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables) to see what else you could possibly leverage for your test setup.


================================================
FILE: docs-app/filtering.md
================================================
### Filtering

Ember Exam provides options to filter test suites by two types - module path and test file path.

```bash
$ ember exam --module-path=<module-path>
```

#### For Vite Apps

The `file-path` option allows you to filter modules by the given relative path that is generated from `import.meta.glob(...)` in your `tests/index.html`.

```bash
# This will run tests that are defined in `/my-application/tests/unit/my-test.js`
$ ember exam --file-path='/my-application/tests/unit/my-test.js'

# This will run all test files that are under `/my-application/tests/unit/`
$ ember exam --file-path='/my-application/tests/unit/*.js'
```


#### For non-Vite Apps


The `module-path` option allows you to filter module paths by a given value. Module paths are mapped by test files and they are generated during `ember build`. After the build, `tests.js` file is created and it resides under [build-directory]/assets. 

The file is combined of all tests in an application and it has a form of `define("<module-path>", others..`.

The value for `module-path` can have either string or regular expression, for instance:

```bash
# When module path value is string. This will run all modules which match with the passed value
$ ember exam --module-path='dummy/tests/helpers/module-for-acceptance'

# When module path value is regex. This will run all modules which have `dummy` in it
$ ember exam --module-path='!/dummy/'
```

The `file-path` option is to filter tests by *test file path*. The test file path is a location of the test file in a file system. You can specify `file-path` to a location of specific test file path or you can use wildcards in paths to target multiple test files.

```bash
# This will run tests that are defined in `/my-application/tests/unit/my-test.js`
$ ember exam --file-path='/my-application/tests/unit/my-test.js'

# This will run all test files that are under `/my-application/tests/unit/`
$ ember exam --file-path='/my-application/tests/unit/*.js'
```


================================================
FILE: docs-app/index.md
================================================
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home

hero:
  name: "ember-exam"
  # text: "Run your tests with randomization, splitting, and parallelization for beautiful tests."
  tagline: "Run your tests with randomization, splitting, and parallelization for beautiful tests."
  actions:
    - theme: brand
      text: Quickstart 
      link: /quickstart
    # - theme: alt
    #   text: API Examples
    #   link: /api-examples

features:
  - title: Partitioning 
    details: Specify the number of parallel browser instances to use to speed up your test suite. 
  - title: Load Balancing 
    details: Balance tests to maximize the effectivess of parallel browsers that would otherwise completely quickly due to happenstance of being given quickly running tests.
  - title: Randomization 
    details: Find and eliminate brittle tests by changing the order of tests within the test suite. 
  - title: Replay 
    details: Record and replay test execution order for reliably reproducing potentially flaky behaviors. 
---


<span class="badges">

![Build Status](https://github.com/ember-cli/ember-exam/actions/workflows/ci.yml/badge.svg?event=push)
[![NPM Version](https://badge.fury.io/js/ember-exam.svg)][npm]
[![Ember Observer Score](https://emberobserver.com/badges/ember-exam.svg)][score]

</span>

[npm]: https://npmjs.com/package/ember-exam
[score]: https://emberobserver.com/addons/ember-exam

Ember Exam is an addon to allow you more control over how you run your tests when used in conjunction with [ember-qunit](https://github.com/emberjs/ember-qunit). It provides the ability to randomize, split, parallelize, and load-balance your test suite by adding a more robust CLI command.

It started as a way to help reduce flaky tests and encourage healthy test driven development. 

[![Introduction to Ember Exam](https://cloud.githubusercontent.com/assets/2922250/22800360/157ad67c-eed7-11e6-8d33-d2c59238c7f1.png)](https://embermap.com/video/ember-exam)




================================================
FILE: docs-app/load-balancing.md
================================================
# Test Load Balancing

```bash
ember exam --parallel=<num> --load-balance
```

The `load-balance` option allows you to load balance test files against multiple browsers. It will order the test files by test types, e.g. acceptance | integration | unit, and load balance the ordered test files between the browsers dynamically rather than statically.
**Note:** parallel must be used along with load-balance to specify a number of browser(s)

The `load-balance` option was added to version 1.1 to address execution performance when running against a large test suite.

Web browsers and the testem server communicate via promise in order to send and receive a test file. The promise timeout value is set to be 2 seconds, and the timeout can be customized by adding asyncTimeout=[timeout] as a querystring param in the test URL or adding to a testem config.
For example, if you specify `load-balance` and `parallel` equals 3, then three browser instances will be created and the output will look something like:

```bash
# ember exam --parallel=3 --load-balance
ok 1 Chrome 66.0 - Browser Id 1 - some test
ok 2 Chrome 66.0 - Browser Id 2 - some another test
ok 3 Chrome 66.0 - Browser Id 3 - some the other test
```

You can also specify the `split` and `partition` options with `load-balance` to load a portion of test modules on multiple CI containers.

```bash
ember exam --split=<num> --partition=<num> --parallel=<num> --load-balance
```

This command will split test files and load-balance tests from the specified partition across the browsers. For example `ember exam --split=2 -partition=1 --parallel=3 --load-balance`, the complete list of test files are split into two halves. With the first half of the list load balanced against three browsers. The output will look something like below:

```bash
# ember exam --split=2 --partition=1 --parallel=3 --load-balance
ok 1 Chrome 66.0 - Exam Partition 1 - browser Id 1 - some test
ok 2 Chrome 66.0 - Exam Partition 1 - browser Id 2 - another test
ok 3 Chrome 66.0 - Exam Partition 1 - browser Id 3 - some the other test
```

**Important information on Load Balancing**

1. The `--load-balance` option is currently only supported in CI mode and for that reason no-launch cannot be used with load-balance.
2. You must be using `ember-cli` version 3.2.0 or greater for load balancing and test failure reproduction features to work properly.
3. You must be using `ember-qunit` version 4.1.1 or greater for this feature to work properly.
4. You must be using `qunit` version 2.13.0 or greater for this feature to work properly.

## Test Failure Reproduction

Due to the dynamic nature of the load-balance option, test file execution order can vary between runs. In order to reproduce a past test execution, the execution must be recorded via passing --write-execution-file or --wef, which allows generating a JSON file that enables rerunning the past test execution. The option is only allowed when load-balance is passed.

```bash
# The command will load in test balanced mode with <num> of browser(s). After the test suite execution, it will generate a test-execution json file.
ember exam --parallel=<num> --load-balance --wef
ember exam --parallel=<num> --load-balance --write-execution-file
```

The file is stored in the root directory and the naming structure is `test-execution-<timestamp>.json`.
To replay the test execution for particular browser(s), do the following:

```bash
# The command will read a test execution file specified for `replay-execution` and execute a browser Id(s) from `replay-browser`
ember exam --replay-execution=[string] --replay-browser=[num]
```

`replay-execution` allows you to specify a path to the json file to run execution against and `replay-browser` is to specify browser ID(s) to execute.

```bash
# The command will read test-execution-000000.json and load the list of modules mapped to browserId 1
ember exam --replay-execution=test-execution-000000.json --replay-browser=1
```

The above command will read `test-execution-000000.json` and load the list of modules which is mapped by browser ID #1.

`replay-browser` can be an array of browser IDs. For instance `--replay-browser=1,2` will start two browsers and execute a list of modules which were previously run by browsers #1 and #2.

```bash
# The command will read test-execution-000000.json and load the list of module mapped to browserId 1 and 2
ember exam --replay-execution=test-execution-000000.json --replay-browser=1,2
```

When `replay-browser` value is not specified it will execute browserId(s) read from `failedBrowser` in the test execution file.

```bash
# The command will read test-execution-000000.json and load the list of modules mapped to browserIds from failedBrowser in the json file.
ember exam --replay-execution=test-execution-000000.json
```

When `replay-browser` value is not specified and there is no value for `failedBrowser` in the json file it will rerun all list of modules.

```bash
# The command will read test-execution-000000.json and load the list of module mapped to all browserIds when failedBrowser is none in the json file
ember exam --replay-execution=test-execution-000000.json
```

**Important information on `--replay-execution` and `--replay-browser`**

1. You must be using `ember-cli` version 3.2.0 or greater for load-balnce and test failure reproduction features to work properly.
2. You must be using `ember-qunit` version 4.1.1 or greater for this feature to work properly.
3. You must be using `qunit` version 2.8.0 or greater for this feature to work properly.


================================================
FILE: docs-app/module-metadata.md
================================================
### Generating Module Metadata File For Test Execution

```bash
$ ember exam --write-module-metadata-file
$ ember exam --wmmf
```

The `--write-module-metadata-file`, `wmmf` as an alias, allows you to generate a module metadata file after a test run. The module metadata file provides information about the test modules executed.

It creates a json file, `module-metadata-<timestamp>.json`, which contains an array of elements representing metadata of modules executed by sorted by ascending order:
```json
[
  {
    "moduleName": "Module-name",
    "total": "Total number of tests in the module",
    "passed": "A number of passed tests in the module",
    "failed": "A number of failed tests in the module",
    "skipped": "A number of skipped tests in the module",
    "duration": "(ms) duration to execute all tests within the module",
    "failedTests": "A list of failed tests"
  }
]
```

and it looks something like below:
```json
[
  {
    "moduleName": "Slowest-module",
    "total": 12,
    "passed": 9,
    "failed": 1,
    "skipped": 2,
    "duration": 153,
    "failedTests": ["failed-test-1"]
  },
  {
    "moduleName": "Fastest-module",
    "total": 2,
    "passed": 1,
    "failed": 0,
    "skipped": 0,
    "duration": 123,
    "failedTests": []
  }
]
```



================================================
FILE: docs-app/package.json
================================================
{
  "name": "docs",
  "private": true,
  "version": "1.0.0",
  "scripts": {
    "docs:dev": "vitepress dev .",
    "docs:build": "vitepress build .",
    "docs:preview": "vitepress preview ."
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "packageManager": "pnpm@10.33.0",
  "devDependencies": {
    "@algolia/client-search": "5.46.4",
    "search-insights": "2.17.3",
    "typescript": "5.9.3",
    "vite": "7.3.2",
    "vitepress": "1.6.4",
    "vitepress-plugin-llms": "1.10.0",
    "vue": "3.5.31"
  },
  "dependencies": {
    "shiki": "^3.8.1"
  }
}


================================================
FILE: docs-app/preserve-test-name.md
================================================
# Preserve Test Name

When using `--split` and/or `--load-balance` the output will look something like:

```bash
# ember exam --split=2 --partition=1 --parallel=3 --load-balance
ok 1 Chrome 66.0 - Exam Partition 1 - browser Id 1 - some test
ok 2 Chrome 66.0 - Exam Partition 1 - browser Id 2 - another test
ok 3 Chrome 66.0 - Exam Partition 1 - browser Id 3 - some the other test
```
However, if you change the amount of parallelization, or randomize accross partitions, the output will change for the same test, which may be an issue if you are tracking test insights over time. 

```bash
# ember exam --split=2 --partition=1 --parallel=2 --load-balance
ok 1 Chrome 66.0 - Exam Partition 1 - browser Id 2 - some test
ok 2 Chrome 66.0 - Exam Partition 1 - browser Id 1 - another test
ok 3 Chrome 66.0 - Exam Partition 1 - browser Id 2 - some the other test
```
You can add `--preserve-test-name` to remove the dynamic segments of the output (partition and browser) to ensure the output test names are always the same.

```bash
# ember exam --split=2 --partition=1 --parallel=3 --load-balance --preserve-test-name
ok 1 Chrome 66.0 - some test
ok 2 Chrome 66.0 - another test
ok 3 Chrome 66.0 - some the other test
```

================================================
FILE: docs-app/quickstart.md
================================================
# Quickstart

## Installation

Installation is as easy as running:

```bash
npm add --save-dev ember-exam
```

## Usage

Using Ember Exam is fairly straightforward as it extends directly from the default Ember-CLI `test` command. So, by default, it will work exactly the same as `ember test`.

```bash
ember exam
ember exam --filter='acceptance'
ember exam --server
ember exam --load-balance --parallel=1
```

A value to an option can be passed with either `=` or a space.

```bash
# A value of filter is acceptance
ember exam --filter 'acceptance'

# A value of parallel is 2
ember exam --load-balance --parallel=2 --server --no-launch

# If a `=` is not used to pass a value to an option that requires a value, it will take anything passed after a space as it's value
# In this instance, the value of parallel is --server
ember exam --load-balance --parallel --server --no-launch
```

The idea is that you can replace `ember test` with `ember exam` and never look back.

To get the unique features of Ember Exam (described in-depth below), you will need to **replace** the use of `start()` from `ember-qunit` in `test-helper.js` with `start()` from `ember-exam`:


## Setup

### With Vite


Update your test-helper.js or test-helper.ts, to have add the ember-exam `start` function:
```diff
  // ...
  import { setApplication } from '@ember/test-helpers';
  import { setup } from 'qunit-dom';
- import { start as qunitStart, setupEmberOnerrorValidation } from 'ember-qunit';
+ import { setupEmberOnerrorValidation } from 'ember-qunit';
+ import { start as startEmberExam } from 'ember-exam/test-support';

- export function start() {
+ export async function start({ availableModules }) {
    setApplication(Application.create(config.APP));

    setup(QUnit.assert);
    setupEmberOnerrorValidation();

-   qunitStart();
+   // Options passed to `start` will be passed-through to ember-qunit
+   await startEmberExam({ availableModules });
  }
```

Then, update your tests/index.html to pass availableModules to start:
```html
<script type="module">
  import { start } from './test-helper.js';

  const availableModules = {
    ...import.meta.glob('./application/**/*-test.{js,ts,gjs,gts}'),
    ...import.meta.glob('./rendering/**/*-test.{js,ts,gjs,gts}'),
    ...import.meta.glob('./unit/**/*-test.{js,ts,gjs,gts}'),
  };

	start({ availableModules });
</script>
```

 We need to tell vite to build the app before telling ember/exam to run tests on that output.

Testing development:
```bash 
NODE_ENV=development vite build --mode development
ember exam --path dist --config-file ./testem.cjs
```

Testing production:
```bash
vite build --mode test
ember exam --path dist --config-file ./testem.cjs
```

> [!NOTE]
> Specifying the `--path` is important because otherwise ember-cli will try to build your vite app, and it will error. 

> [!NOTE]
> Specifying the `--config-path` is important because ember-cli (what backs ember-exam) doesn't know about cjs files. 


### broccoli / ember-cli 


To get the unique features of Ember Exam (described in-depth below), you will need to **replace** the use of `start()` from `ember-qunit` in `test-helper.js` with `start()` from `ember-exam`:

```js
// test-helper.js
- import { start, setupEmberOnerrorValidation } from 'ember-qunit';
+ import { setupEmberOnerrorValidation } from 'ember-qunit';
+ import { start } from 'ember-exam/test-support';

// Options passed to `start` will be passed-through to ember-qunit
start();
```

### Version < `3.0.0`

Prior to `2.1.0`, Ember Exam must be loaded by importing `addon-test-support/load.js` and calling `loadEmberExam`:

```js
// test-helper.js
import loadEmberExam from 'ember-exam/test-support/load';

loadEmberExam();
```


================================================
FILE: docs-app/randomization-iterator.md
================================================
# Randomization Iterator

Randomization can be helpful for identifying non-atomic or order-dependent tests. To that end, Ember Exam provides an iterator to make it easy to test lots of variations in your test suite order quickly.

```bash
ember exam:iterate <num>
```

This command will build your application once, and then run the test suite with the `random` option for the specified number of iterations. You can optionally skip the build by using a previous build via the `path` option:

```bash
ember exam:iterate <num> --path <build-path>
```

Finally, you can pass additional options through to the exam command used to run the tests via the `options` flag:

```bash
ember exam:iterate <num> --options <options>
```

The `options` should be a string matching what you would use via the CLI.



================================================
FILE: docs-app/randomization.md
================================================
# Randomization

```bash
ember exam --random[=<seed>]
```

The `random` option allows you to randomize the order in which your tests run. You can optionally specify a "seed" value from which to randomize your tests in order to reproduce results. The seed can be any string value. Regardless of whether you specify a seed or not, Ember Exam will log the seed value used for the randomization at the beginning of the test run:

```bash
ember exam --random
Randomizing tests with seed: liv5d1ixkco6qlatl6o7mbo6r

ember exam --random=this_is1337
Randomizing tests with seed: this_is1337
```

If you use `random` without specifying a seed, it must be the last argument you pass. Otherwise, Ember Exam will attempt to interpret any following arguments as the seed value. In other words:

```bash
# don't do this
ember exam --random --split=2
Randomizing tests with seed: --split=2 # this is not what we wanted

# do this instead
ember exam --split=2 --random
Randomizing tests with seed: hwr74nkk55vzpvi
```

_Note: You must be using QUnit version `1.23.0` or greater for this feature to work properly.


================================================
FILE: docs-app/split-parallel.md
================================================
# Split Test Parallelization

```bash
ember exam --split=<num> --parallel
```

The `parallel` option allows you to run your split tests across multiple test pages in parallel in [Testem](https://github.com/testem/testem). It will use a separate browser instance for each group of tests. So, if you specify a split of 3, then 3 browser instances will be spawned with the output looking something like:

```bash
ok 1 PhantomJS 1.9 - Exam Partition 1 - some test
ok 2 PhantomJS 1.9 - Exam Partition 3 - some other other test
ok 3 PhantomJS 1.9 - Exam Partition 2 - some other test
```

You can also combine the `parallel` option with the `partition` option to split tests, and then recombine partitions into parallel runs. This would, for example, allow you to run tests in multiple CI containers and have each CI container parallelize its list of tests.

For example, if you wanted to run your tests across two containers, but have one of them run twice as many tests as the other, and run them in parallel, you could do this:

```bash
# container 1
ember exam --split=3 --partition=1,2 --parallel
```

```bash
# container 2
ember exam --split=3 --partition=3 --parallel
```

**Note 1**: _Ember Exam will respect the `parallel` setting of your [Testem config file](https://github.com/testem/testem/blob/master/docs/config_file.md#config-level-options) while running tests in parallel. The default value for `parallel` in Testem is 1, which means you'll need a non-default value to actually see parallel behavior._

**Note 2**: _Ember Exam sets `process.env.EMBER_EXAM_SPLIT_COUNT` for convenience. You can use this in your Testem file._

**Note 3**: _You must be using Testem version `1.5.0` or greater for this feature to work properly._


================================================
FILE: docs-app/splitting.md
================================================
# Splitting

```bash
ember exam --split=<num>
```

The `split` option allows you to specify the number of partitions greater than one to spread your tests across. Ember Exam will then proceed to run the first batch of tests.

```bash
ember exam --split=<num> --partition=<num>
```

The `partition` option allows you to specify which test group to run after using the `split` option. It is one-indexed, so if you specify a split of 3, the last group you could run is 3 as well. You can also run multiple partitions, e.g.:

```bash
ember exam --split=4 --partition=1 --partition=2
```

_Note: Ember Exam splits tests by modifying the ember-qunit's `TestLoader` to bucket each test file into a partition, where each partition has an even number of test files. This makes it possible to have unbalanced partitions. To run your tests with balanced partitions, consider using `--load-balance`. For more info, see [_Test Load Balancing_](#test-load-balancing).


================================================
FILE: docs-app/test-suite-segmentation.md
================================================
# Test Suite Segmentation

Some test suites like to segment which tests run based on various facets such as type of test, feature being tested, and so on. This can be accomplished by leveraging Testem's ability to have multiple test pages:

```json
{
  "test_page": [
    "tests/index.html?filter=acceptance",
    "tests/index.html?filter=!acceptance"
  ]
}
```

You can use this feature in conjunction with Ember Exam's features, which will allow you to segment your test suite but still gain benefits from randomization and splitting.


================================================
FILE: docs-app/tsconfig.json
================================================
{
  "compilerOptions": {
    "module": "esnext",
    "target": "esnext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "noUnusedLocals": true,
    "resolveJsonModule": true,
    "verbatimModuleSyntax": true,
    "jsx": "preserve",
    "lib": ["esnext", "dom", "dom.iterable"]
  },
  "exclude": [
    "**/node_modules/**",
    "**/dist/**",
    "template",
    "bin",
    "docs/snippets",
    "scripts"
  ]
}


================================================
FILE: ember-cli-build.js
================================================
'use strict';

const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');

module.exports = function (defaults) {
  const self = defaults.project.findAddonByName('ember-exam');
  const autoImport = self.options.autoImport;
  let app = new EmberAddon(defaults, {
    autoImport,
    babel: {
      plugins: [
        // ... any other plugins
        require.resolve('ember-concurrency/async-arrow-task-transform'),

        // NOTE: put any code coverage plugins last, after the transform.
      ],
    },
  });

  const { maybeEmbroider } = require('@embroider/test-setup');
  return maybeEmbroider(app, {});
};


================================================
FILE: eslint.config.mjs
================================================
import globals from "globals";
import { ember } from "ember-eslint";
import * as url from "url";

// Needed until Node 20
const dirname = url.fileURLToPath(new URL(".", import.meta.url));

export default [
  ...ember.recommended(dirname),
  {
    name: "monorepo-root:ignores",
    ignores: [
      "docs-app/**/*",
      "test-apps/**/*",
      "acceptance-dist/**/*",
      "failure-dist/**/*",
      "addon-test-support/index.d.ts",
    ],
  },
  {
    name: "monorepo-root:lib",
    files: ["lib/**/*"],
    languageOptions: {
      globals: {
        ...globals.node,
      },
    },
  },
  {
    name: "monorepo-root:node-tests",
    files: ["node-tests/**/*"],
    languageOptions: {
      globals: {
        ...globals.node,
        ...globals.mocha,
      },
    },
    rules: {
      "ember/no-test-support-import": "off",
    },
  },
];


================================================
FILE: index.js
================================================
/* eslint-env node */

'use strict';

module.exports = {
  name: require('./package').name,

  includedCommands() {
    return require('./lib/commands');
  },
};


================================================
FILE: lib/commands/exam/iterate.js
================================================
'use strict';

module.exports = {
  name: 'exam:iterate',

  description:
    "Runs your app's test suite in a random order for a number of iterations with the 'exam' command.",

  works: 'insideProject',

  anonymousOptions: ['<iterations>'],

  availableOptions: [
    {
      name: 'options',
      type: String,
      default: '',
      description: 'A string of options to passthrough to the exam command',
    },
    {
      name: 'path',
      type: String,
      default: '',
      description: 'The output path of a previous build to run tests against',
    },
  ],

  /**
   * The output directory of the build used to run the test iterations.
   *
   * @type {String}
   */
  _outputDir: 'iteration-dist',

  /**
   * Runs `ember exam` with random seeds for a number of iterations. The results
   * of each run are displayed in a table at the end of the command. This is
   * useful for pre-emptively identifying flaky/non-atomic tests in an offline
   * job.
   *
   * @override
   */
  async run(commandOptions, anonymousOptions) {
    const needsBuild = !commandOptions.path;

    if (needsBuild) {
      await this._buildForTests();
    } else {
      this._outputDir = commandOptions.path;
    }

    const numIterations = parseInt(anonymousOptions[0], 10);
    const options = commandOptions.options;
    const results = await this._runIterations(numIterations, options);

    if (needsBuild) {
      await this._cleanupBuild();
    }

    await this._write(results.toString(), true);
  },

  /**
   * Writes out a line with a standard color, unless specifically turned off.
   *
   * @param {String} input
   * @param {Boolean} noColor
   */
  async _write(input, noColor) {
    if (!noColor) {
      const chalk = (await import('chalk')).default;
      input = chalk.blue(input);
    }

    console.info(input);
  },

  /**
   * Builds the application into a special output directory to run the tests
   * against repeatedly without rebuilding.
   */
  async _buildForTests() {
    await this._write('\nBuilding app for test iterations.');
    const { execa } = await import('execa');
    await execa(
      './node_modules/.bin/ember',
      ['build', '--output-path', `${this._outputDir}`],
      { stdio: 'inherit' },
    );
  },

  /**
   * Cleans up the build artifacts used for the test iterations.
   */
  async _cleanupBuild() {
    await this._write('\nCleaning up test iterations.\n');
    const { execa } = await import('execa');
    await execa('rm', ['-rf', `${this._outputDir}`]);
  },

  /**
   * Runs iterations of the test suite and returns a table to display the
   * results.
   *
   * @param {Number} numIterations
   * @param {String} options
   * @return {Table} results
   */
  async _runIterations(numIterations, options) {
    const chalk = (await import('chalk')).default;
    const Table = require('cli-table3');

    const results = new Table({
      head: [
        chalk.blue('Iteration'),
        chalk.blue('Seed'),
        chalk.blue('Exit Code'),
        chalk.blue('Command'),
      ],
    });

    for (let i = 0; i < numIterations; i++) {
      await this._write('\nRunning iteration #' + (i + 1) + '.');
      const result = await this._runTests(options);
      results.push([i].concat(result));
    }

    await this._write('\nRan ' + numIterations + ' iterations.');

    return results;
  },

  /**
   * Runs the test suite in a random order while allowing additional options.
   * Returns an array representing a row in the result table for _runIterations.
   *
   * @param {String} options
   * @return {Array} results
   */
  async _runTests(options) {
    const chalk = (await import('chalk')).default;
    const execSync = require('child_process').execSync;

    const seed = Math.random().toString(36).slice(2);
    const command =
      './node_modules/.bin/ember exam --random ' +
      seed +
      ' --path ' +
      this._outputDir +
      ' ' +
      options;
    let exitCode;

    try {
      execSync(command, { stdio: 'inherit' });
      exitCode = 0;
    } catch (error) {
      await this._write('Returned non-zero exit code with error: ' + error);
      exitCode = 1;
      process.exitCode = 1;
    }

    const color = exitCode ? chalk.red : chalk.green;
    return [color(seed), color(exitCode), color(command)];
  },
};


================================================
FILE: lib/commands/exam.js
================================================
'use strict';

const { addToQuery } = require('../utils/query-helper');
// npmlog is used to write to testem server logs and `--testem-debug` enables to save the log file.
const log = require('npmlog');
const {
  combineOptionValueIntoArray,
  getBrowserId,
  getMultipleTestPages,
} = require('../utils/test-page-helper');
const TestemEvents = require('../utils/testem-events');
const TestCommand = require('ember-cli/lib/commands/test');
const TestServerTask = require('./task/test-server');
const TestTask = require('./task/test');

module.exports = TestCommand.extend({
  name: 'exam',

  description: `Runs your app's test suite with more options than 'test'.`,

  works: 'insideProject',

  availableOptions: [
    {
      name: 'split',
      type: Number,
      description: 'A number of files to split your tests across.',
    },
    {
      name: 'partition',
      type: [Array, Number, String],
      description: 'The number of the partition(s) to run after splitting.',
    },
    {
      name: 'parallel',
      type: [Number, String],
      description: 'Runs your split tests on parallel child processes.',
    },
    {
      name: 'load-balance',
      type: Boolean,
      default: false,
      description:
        'Load balance test modules. Test modules will be sorted by weight from slowest (acceptance) to fastest (unit).',
    },
    {
      name: 'preserve-test-name',
      type: Boolean,
      default: false,
      aliases: ['ptn'],
      description:
        'Preserve the test name when using load balance or split by omitting the partition and browser numbers.',
    },
    {
      name: 'random',
      type: String,
      default: false,
      description:
        'Randomizes your modules and tests while running your test suite.',
    },
    {
      name: 'module-path',
      type: [String],
      aliases: ['mp'],
      description:
        'Filters the list of modules to only those that matches by module paths, the value accepts either string or regex.',
    },
    {
      name: 'file-path',
      type: [String],
      aliases: ['fp'],
      description:
        'Filters the list of modules to only those that matches by test file paths, the value accepts either string or regex.',
    },
    {
      name: 'replay-execution',
      type: String,
      default: false,
      aliases: ['re'],
      description:
        'A JSON file path which maps from browser id(s) to a list of modules',
    },
    {
      name: 'replay-browser',
      type: [Array, Number, String],
      aliases: ['rb'],
      description: 'The browser id(s) to replay from the replay-execution file',
    },
    {
      name: 'write-execution-file',
      type: Boolean,
      default: false,
      aliases: ['wef'],
      description:
        'Allows writing a test-execution json file after running your test suite',
    },
    {
      name: 'write-module-metadata-file',
      type: Boolean,
      default: false,
      aliases: ['wmmf'],
      description:
        'Allows writing a module metadata json file after running your test suite',
    },
  ].concat(TestCommand.prototype.availableOptions),

  init() {
    this._super(...arguments);
    this.tasks.Test = TestTask;
    this.tasks.TestServer = TestServerTask;
    this.testemEvents = new TestemEvents(this.project.root);
    this.emberCliVersion =
      this.project.pkg.devDependencies['ember-cli'] ||
      this.project.pkg.dependencies['ember-cli'];
  },

  /**
   *  Validates commandOptions
   *
   * @private
   * @param {Object} commandOptions
   * @return {Object} A map of what switches are enabled
   */
  _validateOptions(commandOptions) {
    const Validator = require('../utils/tests-options-validator');
    const validator = new Validator(commandOptions, this.emberCliVersion);
    return validator.validateCommands();
  },

  /**
   * Validates the command options and then runs the original test command.
   *
   * @param {Object} commandOptions
   * @override
   */
  run(commandOptions) {
    this.commands = this._validateOptions(commandOptions);

    // TODO: explore not mutating the commandOptions input
    if (commandOptions.split) {
      commandOptions.query = addToQuery(
        commandOptions.query,
        'split',
        commandOptions.split,
      );

      process.env.EMBER_EXAM_SPLIT_COUNT = commandOptions.split;

      // Ignore the partition option when paralleling (we'll fill it in later)
      if (!commandOptions.parallel && commandOptions.partition) {
        const partitions = combineOptionValueIntoArray(
          commandOptions.partition,
        );
        for (let i = 0; i < partitions.length; i++) {
          commandOptions.query = addToQuery(
            commandOptions.query,
            'partition',
            partitions[i],
          );
        }
      }
    }

    if (commandOptions.modulePath) {
      commandOptions.query = addToQuery(
        commandOptions.query,
        'modulePath',
        commandOptions.modulePath,
      );
    }

    if (commandOptions.preserveTestName) {
      commandOptions.query = addToQuery(
        commandOptions.query,
        'preserveTestName',
        commandOptions.preserveTestName,
      );
    }

    if (commandOptions.filePath) {
      commandOptions.query = addToQuery(
        commandOptions.query,
        'filePath',
        commandOptions.filePath,
      );
    }

    if (commandOptions.loadBalance) {
      commandOptions.query = addToQuery(
        commandOptions.query,
        'loadBalance',
        commandOptions.loadBalance,
      );
    }

    if (commandOptions.replayBrowser) {
      commandOptions.replayBrowser = combineOptionValueIntoArray(
        commandOptions.replayBrowser,
      );
    }

    if (typeof commandOptions.random !== 'undefined') {
      commandOptions.query = this._randomize(
        commandOptions.random,
        commandOptions.query,
      );
    }

    return this._super.run.apply(this, arguments);
  },

  /**
   * Adds a `seed` param to the `query` to support randomization. Currently
   * only works with QUnit.
   *
   * @param {string} random
   * @param {string} query
   * @return {string}
   */
  _randomize(random, query) {
    const seed = random !== '' ? random : Math.random().toString(36).slice(2);

    this.ui.writeLine('Randomizing tests with seed: ' + seed);

    return addToQuery(query, 'seed', seed);
  },

  /**
   * Customizes the Testem config to have multiple test pages if attempting to
   * run in parallel or load-balance. It is important that the user specifies
   * the number of launchers to run in parallel in their testem.json config.
   *
   * @param {Object} commandOptions
   * @override
   */
  _generateCustomConfigs(commandOptions) {
    const config = this._super._generateCustomConfigs.apply(this, arguments);
    let additionalEvents = this._setupAndGetBrowserSocketEvents(config);

    if (commandOptions.loadBalance || commandOptions.replayExecution) {
      const loadBalancingEvents = this._getLoadBalancingBrowserSocketEvents(
        {
          isLoadBalance: this.commands.get('loadBalance'),
          isReplayExecution: this.commands.get('replayExecution'),
          isWriteExecutionFile: this.commands.get('writeExecutionFile'),
        },
        this.testemEvents,
      );
      additionalEvents = Object.assign(additionalEvents, loadBalancingEvents);
    }

    config.custom_browser_socket_events = Object.assign(
      config.custom_browser_socket_events || {},
      additionalEvents,
    );

    if (
      !commandOptions.loadBalance &&
      !commandOptions.replayExecution &&
      !commandOptions.parallel
    )
      return config;

    config.testPage = getMultipleTestPages(config, commandOptions);

    if (commandOptions.replayExecution) {
      this.testemEvents.setReplayExecutionMap(
        commandOptions.replayExecution,
        commandOptions.replayBrowser,
      );
    }

    return config;
  },

  /**
   * Returns an event object to enable to send and receive module metadata
   *
   * @param {Object} config
   */
  _setupAndGetBrowserSocketEvents(config) {
    const commands = this.commands;
    const testemEvents = this.testemEvents;
    const ui = this.ui;

    const browserExitHandler = function (failed = false) {
      const launcherId = this.launcher.id;
      if (!failed && commands.get('loadBalance')) {
        const browserId = getBrowserId(this.launcher);
        log.info(
          `Browser ${browserId} exiting. [ # of modules in current module queue ${
            testemEvents.stateManager.getTestModuleQueue().length
          } ]`,
        );
        // if getBrowserId cannot get the browserId
        // but the test queue is not empty, report the number of test modules left in the queue
        // otherwise, fail because testModuleQueue was not set
        if (browserId === 0) {
          if (testemEvents.stateManager.getTestModuleQueue() !== null) {
            ui.writeLine(
              `[ # of modules in current module queue ${
                testemEvents.stateManager.getTestModuleQueue().length
              } ]`,
            );
          } else {
            throw new Error('testModuleQueue is not set.');
          }
        }
      }

      // config.testPage is undefined when parallization options are not used
      // Set browserCount default value to 1 to allow
      let browserCount = 1;
      // When using multiple browsers config.testPage is an array of test page urls.
      if (typeof config.testPage !== 'undefined') {
        browserCount = Object.keys(config.testPage).length;
      }

      testemEvents.completedBrowsersHandler(
        browserCount,
        launcherId,
        ui,
        commands,
        Date.now(),
      );
    };

    const browserTerminationHandler = function () {
      // browserTerminationHandler is called for disconnect, processError or processExit events.
      // disconnect and processExit events is fired during global error and successful test runs.
      // On successful test runs, browserExitHandler should already be called. And is unnecessary
      // to call it again, so we should return. This is covered by this.finish = true
      // On global failure cases, it's possible that this.finish is also true. So we must check
      // the timers set by onProcessExit
      // https://github.com/testem/testem/blob/master/lib/runners/browser_test_runner.js#L266
      // or onProcessError in testem.
      // https://github.com/testem/testem/blob/master/lib/runners/browser_test_runner.js#L252
      // If either timers is set, we should record the failed browser and call browserExitHandler
      if (this.finished && !this.onProcessExitTimer && !this.pendingTimer) {
        return;
      }
      if (commands.get('writeExecutionFile')) {
        testemEvents.recordFailedBrowserId(this.launcher, ui);
      }

      browserExitHandler.call(this, true);
    };

    return this._getModuleMetadataAndBrowserExitSocketEvents(
      browserExitHandler,
      browserTerminationHandler,
    );
  },

  /**
   * Add browser socket events are needed for both with load-balance and without load-balance
   *
   * @param {Object} browserExitHandler
   * @param {Object} browserTerminationHandler
   */
  _getModuleMetadataAndBrowserExitSocketEvents(
    browserExitHandler,
    browserTerminationHandler,
  ) {
    const events = {};
    const testemEvents = this.testemEvents;
    let init = false;

    events['tests-start'] = function () {
      if (!init) {
        // process object is instantiated only when browsers are launched by testem server.
        // 1. `ember test/exam` where browsers are instantiated by testem - process is available
        // 2. `ember test/exam --server` where browsers can be instantiated by testem or manually
        // - process is available only when browsers are instantiated by testem
        // 3. `ember test/exam --serve --no-launch` where browsers are instantiated manually - process is undefined
        // 4. `ember serve` where browsers are instantiated manually by developer - process is available.
        if (typeof this.process !== 'undefined' && this.process !== null) {
          this.process.on('processExit', browserTerminationHandler.bind(this));
          this.process.on('processError', browserTerminationHandler.bind(this));
        }
        init = true;
      }

      if (typeof this.launcher !== 'undefined' && this.launcher !== null) {
        testemEvents.recordStartedLauncherId(this.launcher.id);
      }
    };

    events['after-tests-complete'] = browserExitHandler;

    events['disconnect'] = function () {
      // To prevent handling exiting browser browser disconnects from errors `disconnect` callback's needed to be registered.
      browserTerminationHandler.bind(this)();
    };

    events['testem:test-done-metadata'] = (details) => {
      // Ensure module detail is available
      if (typeof details === 'object' && details !== null) {
        //store module name, test name, # of failed assertion, and duration.
        this.testemEvents.recordModuleMetadata({
          moduleName: details.module,
          testName: details.name,
          passed: details.passed == details.total,
          failed: details.failed > 0,
          skipped: details.skipped,
          duration: details.runtime,
        });
      }
    };

    return events;
  },

  /**
   * Return an event object which enables load balancing.
   * These event handlers will be registered on Testem's browserTestRunner socket instance
   *
   * @param {Object} commands
   * @param {Object} testemEvents
   */
  _getLoadBalancingBrowserSocketEvents(
    { isLoadBalance, isReplayExecution, isWriteExecutionFile },
    testemEvents,
  ) {
    const events = {};
    const ui = this.ui;

    events['testem:set-modules-queue'] = function (modules, browserId) {
      testemEvents.setModuleQueue(
        browserId,
        modules,
        isLoadBalance,
        isReplayExecution,
      );
    };
    events['testem:next-module-request'] = function (browserId) {
      testemEvents.nextModuleResponse(
        browserId,
        this.socket,
        isWriteExecutionFile,
      );
    };
    events['test-result'] = function (result) {
      if (result.failed && isWriteExecutionFile) {
        testemEvents.recordFailedBrowserId(this.launcher, ui);
      }
    };

    return events;
  },
});


================================================
FILE: lib/commands/index.js
================================================
'use strict';

module.exports = {
  exam: require('./exam'),
  'exam:iterate': require('./exam/iterate'),
};


================================================
FILE: lib/commands/task/test-server.js
================================================
const TestServerTask = require('ember-cli/lib/tasks/test-server');

module.exports = TestServerTask.extend({
  transformOptions(options) {
    const transformOptions = this._super(...arguments);
    transformOptions.custom_browser_socket_events =
      options.custom_browser_socket_events;
    transformOptions.browser_module_mapping = options.browser_module_mapping;

    return transformOptions;
  },
});


================================================
FILE: lib/commands/task/test.js
================================================
const TestTask = require('ember-cli/lib/tasks/test');

module.exports = TestTask.extend({
  transformOptions(options) {
    const transformOptions = this._super(...arguments);
    transformOptions.custom_browser_socket_events =
      options.custom_browser_socket_events;
    transformOptions.browser_module_mapping = options.browser_module_mapping;

    if (options.loadBalance) {
      /**
       * the parallel option is how testem knows to boot browsers simultaneously.
       * setting testPage to an array isn't enough.
       * default behavior is 1 browser at a time, which defeats the purpose of loadBalance.
       */
      transformOptions.parallel = options.testPage.length;
    }

    return transformOptions;
  },
});


================================================
FILE: lib/utils/config-reader.js
================================================
'use strict';

const fs = require('fs-extra');
const yaml = require('js-yaml');
const path = require('path');
const debug = require('debug')('exam:config-reader');

const potentialConfigFiles = ['testem.js', 'testem.json', 'testem.cjs'];

/**
 * Given an array of file paths, returns the first one that exists and is
 * accessible. Paths are relative to the process' cwd.
 *
 * @param {Array<string>} files
 * @return {string} file
 */
function _findValidFile(files) {
  for (let i = 0; i < files.length; i++) {
    // TODO: investigate this cwd() usually they are in-error...
    const file = path.join(process.cwd(), files[i]);
    try {
      fs.accessSync(file, fs.F_OK);
      return file;
    } catch (error) {
      debug(`Failed to find ${file} due to error: ${error}`);
      continue;
    }
  }
}

/**
 * Reads in a given file according to it's 'type' as determined by file
 * extension. Supported types are `js` and `json`.
 *
 * @param {string} file
 * @return {Object} fileContents
 */
function _readFileByType(file) {
  if (typeof file === 'string') {
    const fileType = file.split('.').pop();
    switch (fileType) {
      case 'js':
      case 'cjs':
        return require(file);
      case 'json':
        return fs.readJsonSync(file);
      case 'yaml':
        return yaml.load(fs.readFileSync(file));
      default:
        throw new Error(`Unrecognized file extension for: ${file}`);
    }
  }
}

/**
 * Gets the application's testem config by trying a custom file first and then
 * defaulting to either `testem.js` or `testem.json`.
 *
 * @param {string} file
 * @param {Array<string>} potentialFiles
 * @return {Object} config
 */
module.exports = function readTestemConfig(
  file,
  potentialFiles = potentialConfigFiles,
) {
  if (file) {
    potentialFiles.unshift(file);
  }

  const configFile = _findValidFile(potentialFiles);

  return configFile && _readFileByType(configFile);
};


================================================
FILE: lib/utils/execution-state-manager.js
================================================
'use strict';

/**
 * A class to store the state of an execution.
 *
 * @class ExecutionStateManager
 */
class ExecutionStateManager {
  constructor(replayExecutionMap) {
    // A set of launcher id of attached browsers
    this._startedLaunchers = new Set();

    // A map of browserId to test modules executed on that browser read from test-execution.json.
    this._replayExecutionMap = replayExecutionMap || null;

    // A map of browserId to test modules executed for the current test execution.
    this._browserToModuleMap = new Map();

    // A map keeping the module execution details
    this._moduleMetadata = new Map();

    // An array keeping the browserId of a browser with failing test
    this._failedBrowsers = [];
    this._completedBrowsers = new Map();

    // An array of modules to load balance against browsers. This is used by `--load-balance`
    this._testModuleQueue = null;

    // A map of browserId to an array of test modules. This is used by `--replay-execution`
    this._replayExecutionModuleQueue = null;
  }

  /**
   * Returns the startedLaunchers
   *
   * @returns {Set}
   */
  getStartedLaunchers() {
    return this._startedLaunchers;
  }

  /**
   * Add a new laucnher id to the startedLaunchers array.
   *
   * @param {number} launcherId
   * @returns {Boolean}
   */
  addToStartedLaunchers(launcherId) {
    return this._startedLaunchers.add(launcherId);
  }

  /**
   * Returns the replayExecutionMap
   *
   * @returns {Object}
   */
  getReplayExecutionMap() {
    return this._replayExecutionMap;
  }

  /**
   * Sets the replayExecutionMap
   *
   * @param {Object} replayModuleMap
   */
  setReplayExecutionMap(replayModuleMap) {
    this._replayExecutionMap = replayModuleMap;
  }

  /**
   * Returns the testModuleQueue
   *
   * @returns {Object}
   */
  getTestModuleQueue() {
    return this._testModuleQueue;
  }

  /**
   * Sets the shared module queue.
   *
   * @param {Object} moduleQueue
   */
  setTestModuleQueue(moduleQueue) {
    this._testModuleQueue = moduleQueue;
  }

  /**
   * Gets the next module from the shared module queue
   *
   * @returns {string}
   */
  getNextModuleTestModuleQueue() {
    if (this._testModuleQueue) {
      return this._testModuleQueue.shift();
    }
    return null;
  }

  /**
   * Returns the array of modules belonging to browserId
   *
   * @param {number} browserId
   * @returns {Array<number>}
   */
  getReplayExecutionModuleQueue(browserId) {
    if (this._replayExecutionModuleQueue) {
      return this._replayExecutionModuleQueue.get(browserId);
    }
    return null;
  }

  /**
   * Sets the array of modules in browser module queue for browserId
   *
   * @param {Array<string>} moduleQueue
   * @param {number} browserId
   */
  setReplayExecutionModuleQueue(moduleQueue, browserId) {
    if (!this._replayExecutionModuleQueue) {
      this._replayExecutionModuleQueue = new Map();
    }
    this._replayExecutionModuleQueue.set(browserId, moduleQueue.slice());
  }

  /**
   * Gets the next module from the module array of browserId
   *
   * @param {number} browserId
   * @returns {string}
   */
  getNextModuleReplayExecutionModuleQueue(browserId) {
    if (
      this._replayExecutionModuleQueue &&
      this._replayExecutionModuleQueue.get(browserId)
    ) {
      return this._replayExecutionModuleQueue.get(browserId).shift();
    }
    return null;
  }

  /**
   * Returns the TestModuleQueue
   *
   * @returns {Set<number>}
   */
  getFailedBrowsers() {
    return this._failedBrowsers;
  }

  /**
   * Returns the whether or not the browserId is contained in the failBrowsers array.
   *
   * @param {number} browserId
   * @returns {Boolean}
   */
  containsFailedBrowser(browserId) {
    return this._failedBrowsers.includes(browserId);
  }

  /**
   * Add a new browserId to the failedBrowser array.
   *
   * @param {number} browserId
   * @returns {Boolean}
   */
  addFailedBrowsers(browserId) {
    return this._failedBrowsers.push(browserId);
  }

  /**
   * Returns the a map of browserId to modules array
   *
   * @returns {Object}
   */
  getModuleMap() {
    return this._browserToModuleMap;
  }

  /**
   * Returns an array of modules run details
   *
   * @returns {Array}
   */
  getModuleMetadata() {
    return this._moduleMetadata;
  }

  /**
   * Pushes the moduleName into the moduleArray of browserId
   *
   * @param {string} moduleName
   * @param {number} browserId
   */
  addModuleNameToReplayExecutionMap(moduleName, browserId) {
    let browserModuleList = this._browserToModuleMap.get(browserId);
    if (Array.isArray(browserModuleList)) {
      browserModuleList.push(moduleName);
    } else {
      browserModuleList = [moduleName];
    }
    this._browserToModuleMap.set(browserId, browserModuleList);
  }

  /**
   * Add module metadata mapped by moduleName to moduleMetadata Map.
   *
   * @param {string} moduleName
   * @param {number} total - Total number of tests
   * @param {number} passed - Number of passed tests
   * @param {number} failed - Number of failed tests
   * @param {number} duration - duration to execute tests in module in ms
   * @param {Array<string>} failedTests - A list of failed test names
   */
  _injectModuleMetadata(
    moduleName,
    total,
    passed,
    failed,
    skipped,
    duration,
    failedTests,
  ) {
    this._moduleMetadata.set(moduleName, {
      moduleName,
      total,
      passed,
      failed,
      skipped,
      duration,
      failedTests,
    });
  }

  /**
   * Pushes the module detail into the moduleMetadata array
   *
   * @param {Object} metaData
   */
  addToModuleMetadata(metadata) {
    if (!this._moduleMetadata.has(metadata.moduleName)) {
      // modulename, total, passed, failed, skipped, duration, failed tests
      this._injectModuleMetadata(metadata.moduleName, 0, 0, 0, 0, 0, []);
    }

    const curModuleMetadata = this._moduleMetadata.get(metadata.moduleName);

    if (!metadata.skipped && metadata.failed) {
      curModuleMetadata.failedTests.push(metadata.testName);
    }

    this._injectModuleMetadata(
      metadata.moduleName,
      curModuleMetadata.total + 1,
      !metadata.skipped && metadata.passed
        ? curModuleMetadata.passed + 1
        : curModuleMetadata.passed,
      !metadata.skipped && metadata.failed
        ? curModuleMetadata.failed + 1
        : curModuleMetadata.failed,
      metadata.skipped
        ? curModuleMetadata.skipped + 1
        : curModuleMetadata.skipped,
      curModuleMetadata.duration + metadata.duration,
      curModuleMetadata.failedTests,
    );
  }

  /**
   * Returns the number of completed browsers
   *
   * @returns {number}
   */
  getCompletedBrowser() {
    return this._completedBrowsers.size;
  }

  /**
   * Book keep the browser id that has completed
   *
   * @param {number} browserId
   */
  incrementCompletedBrowsers(browserId) {
    this._completedBrowsers.set(browserId, true);
  }
}

module.exports = ExecutionStateManager;


================================================
FILE: lib/utils/file-system-helper.js
================================================
const fs = require('fs-extra');

/**
 * Creates a file with targetJsonObject
 *
 * @param {string} fileName
 * @param {Object} targetJsonObject
 * @param {Object} option
 */
module.exports = function writeJsonToFile(
  fileName,
  targetJsonObject,
  option = {},
) {
  try {
    fs.writeJsonSync(fileName, targetJsonObject, option);
  } catch (err) {
    if (typeof err === 'object' && err !== null) {
      err.file = err.file || fileName;
    }
    throw err;
  }
};


================================================
FILE: lib/utils/query-helper.js
================================================
'use strict';

/**
 * Creates a valid query string by appending a given param and value to query.
 *
 * @param {string} query
 * @param {string} param
 * @param {string} value
 */
function addToQuery(query, param, value) {
  if (!value) {
    return query;
  }

  const queryAddParam = query ? query + '&' + param : param;

  return value !== true ? queryAddParam + '=' + value : queryAddParam;
}

/**
 * Adds a valid query string to a given url.
 *
 * @param {string} url
 * @param {string} param
 * @param {string} value
 */
function addToUrl(url, param, value) {
  const urlParts = url.split('?');
  const base = urlParts[0];
  const query = urlParts[1];

  return base + '?' + addToQuery(query, param, value);
}

module.exports = {
  addToQuery,
  addToUrl,
};


================================================
FILE: lib/utils/test-page-helper.js
================================================
'use strict';

const fs = require('fs-extra');
const readTestemConfig = require('../utils/config-reader');
const { addToUrl } = require('./query-helper');

/**
 * Add paramater such as split, loadbalance or partition to a base url if options are valid.
 *
 * @param {Object} commandOptions
 * @param {string} baseUrl
 * @return {string} baseUrl
 */
function _appendParamToBaseUrl(commandOptions, baseUrl) {
  if (commandOptions.parallel || commandOptions.split) {
    baseUrl = addToUrl(baseUrl, 'split', commandOptions.split);
  }
  // `loadBalance` is added to url when running replay-execution in order to emit `set-module-queue` in patch-test-loader.
  if (commandOptions.loadBalance || commandOptions.replayExecution) {
    const partitions = commandOptions.partition;
    baseUrl = addToUrl(baseUrl, 'loadBalance', true);
    if (partitions) {
      for (let i = 0; i < partitions.length; i++) {
        baseUrl = addToUrl(baseUrl, 'partition', partitions[i]);
      }
    }
  }

  return baseUrl;
}

/**
 * Generates an array by parsing optionValue. optionValue can be in a string form of '1,2', '3..5'
 * or '1,3..5' where '3..5' indicates a number sequence starting from 2 to 5.
 *
 * @param {string} optionValue
 * @return {Array<number>}
 */
function _formatStringOptionValue(optionValue) {
  let valueArray = [];

  optionValue.split(',').forEach(function (val) {
    if (val.indexOf('..') > 0) {
      const arr = val.split('..');
      const filledArray = _getFilledArray(arr.shift(), arr.pop());
      valueArray = valueArray.concat(filledArray);
    } else {
      valueArray.push(val);
    }
  });

  return valueArray;
}

/**
 * Generates multiple test pages: for a given baseUrl, it appends the partition numbers
 * or the browserId each page is running as query params.
 *
 * @param {string} customBaseUrl
 * @param {string} appendingParam
 * @param {Array<number} browserIds
 * @return {Array<string>} testPages
 */
function _generateTestPages(customBaseUrl, appendingParam, browserIds) {
  const testPages = [];
  for (let i = 0; i < browserIds.length; i++) {
    const url = addToUrl(customBaseUrl, appendingParam, browserIds[i]);
    testPages.push(url);
  }

  return testPages;
}

/**
 * Creates an array of numbers between the range of start to end.
 *
 * @param {number} start
 * @param {number} end
 * @return {Array}
 */
function _getFilledArray(start, end) {
  const length = end - start + 1;
  return Array.from({ length }, (_, i) => i + Number(start));
}

/**
 * returns the failed browsers from the test execution json defined in executionJsonPath
 * other wise return an array of 1 to number of browsers spawned during the execution
 *
 * @param {string} executionJsonPath
 * @return {Array<number>} testPages
 */
function _getReplayBrowsers(executionJsonPath) {
  const executionJson = fs.readJsonSync(executionJsonPath);

  if (executionJson.failedBrowsers.length > 0) {
    return executionJson.failedBrowsers;
  }
  return _getFilledArray(1, executionJson.numberOfBrowsers);
}

/**
 * Returns an array populated with numeric values represented by the optionValue.
 * e.g. [1, '2,3'] => [1, 2, 3], [1, '3..6'] => [1, 3, 4, 5, 6]
 *
 * @param {*} optionValue
 * @return {Array<number}
 */
function combineOptionValueIntoArray(optionValue) {
  if (!optionValue) return [];

  let optionArray = Array.isArray(optionValue) ? optionValue : [optionValue];

  return optionArray.reduce((result, element) => {
    if (typeof element === 'string') {
      return result.concat(_formatStringOptionValue(element));
    }
    return result.concat(element);
  }, []);
}

/**
 * Returns the browserId of launcher
 *
 * @param {Object} launcher
 * @return {string}
 */
function getBrowserId(launcher) {
  try {
    const testPage = launcher.settings.test_page;
    const browserIdMatch = /browser=\s*([0-9]*)/.exec(testPage);

    if (Array.isArray(browserIdMatch) !== null && browserIdMatch !== null) {
      return browserIdMatch[1];
    }
  } catch (err) {
    const errMsg = `${err.message} \n${
      err.stack
    } \nLauncher Settings: ${JSON.stringify(launcher.settings, null, 2)}`;
    console.warn(errMsg);
  }
  return 0;
}

/**
 * Gets a test url in testem config to modify the url in order to generate multiple test pages
 *
 * @param {Object} configFile
 * @return {string} testPage
 */
function getTestUrlFromTestemConfig(configFile) {
  // Attempt to read in the testem config and use the test_page definition
  const testemConfig = readTestemConfig(configFile);
  let testPage = testemConfig && testemConfig.test_page;

  // If there is no test_page to use as the testPage, we warn that we're using
  // a default value
  if (!testPage) {
    console.warn(
      'No test_page value found in the config. Defaulting to "tests/index.html?hidepassed"',
    );
    testPage = 'tests/index.html?hidepassed';
  }

  // Get the testPage from the generated config or the Testem config and
  // use it as the baseUrl to customize for the parallelized test pages or test load balancing
  return testPage;
}

/**
 * Creates an array of custom base urls by appending options that are specified
 *
 * @param {Object} commandOptions
 * @param {*} baseUrl
 * @return {string}
 */
function getCustomBaseUrl(commandOptions, baseUrl) {
  if (Array.isArray(baseUrl)) {
    return baseUrl.map((currentUrl) => {
      return _appendParamToBaseUrl(commandOptions, currentUrl);
    });
  } else {
    return _appendParamToBaseUrl(commandOptions, baseUrl);
  }
}

/**
 * Ember-exam allows serving multiple browsers to run test suite. In order to acheive that test_page in testem config
 * has to be set with an array of multiple urls reflecting to command passed.
 *
 * @param {Object} config
 * @param {Object} commandOptions
 * @return {Array<string>} testPages
 */
function getMultipleTestPages(config, commandOptions) {
  let testPages = Object.create(null);
  let browserIds = combineOptionValueIntoArray(commandOptions.partition);
  let appendingParam = 'partition';

  if (commandOptions.loadBalance) {
    appendingParam = 'browser';
    browserIds = _getFilledArray(1, commandOptions.parallel);
  } else if (commandOptions.parallel === 1 && browserIds.length === 0) {
    browserIds = _getFilledArray(1, commandOptions.split);
  } else if (commandOptions.replayExecution) {
    appendingParam = 'browser';
    browserIds = combineOptionValueIntoArray(commandOptions.replayBrowser);
    if (browserIds.length === 0) {
      browserIds = _getReplayBrowsers(commandOptions.replayExecution);
    }
  }

  const baseUrl =
    config.testPage || getTestUrlFromTestemConfig(commandOptions.configFile);
  const customBaseUrl = getCustomBaseUrl(commandOptions, baseUrl);

  if (Array.isArray(customBaseUrl)) {
    testPages = customBaseUrl.reduce(function (testPages, customBaseUrl) {
      return testPages.concat(
        _generateTestPages(customBaseUrl, appendingParam, browserIds),
      );
    }, []);
  } else {
    testPages = _generateTestPages(customBaseUrl, appendingParam, browserIds);
  }

  return testPages;
}

module.exports = {
  combineOptionValueIntoArray,
  getBrowserId,
  getCustomBaseUrl,
  getMultipleTestPages,
  getTestUrlFromTestemConfig,
};


================================================
FILE: lib/utils/testem-events.js
================================================
'use strict';

const fs = require('fs-extra');
const path = require('path');
const ExecutionStateManager = require('./execution-state-manager');
const { getBrowserId } = require('../utils/test-page-helper');
const writeJsonToFile = require('./file-system-helper');

/**
 * Return sorted module metadata object by module duration.
 *
 * @param {Map} moduleMetadata
 */
function getSortedModuleMetaData(moduleMetadata) {
  return new Map(
    [...moduleMetadata.entries()].sort((a, b) => b[1].duration - a[1].duration),
  );
}

/**
 * A class to coordinate testem events to enable load-balance functionality.
 *
 * @class TestemEvents
 */
class TestemEvents {
  constructor(root) {
    this.stateManager = new ExecutionStateManager();
    this.root = root;
  }

  /**
   * Read the executionFilePath then:
   * if failed browsers are available, set the module map to the modules from the failed browsers.
   * else if replay-browser param is passed, set the module map specified browser id
   * else set module map to all the browser ran, effectively rerunning the same execution
   *
   * @param {string} executionFilePath
   * @param {Array<number>} browserIdsToReplay
   * @return {Object}
   */
  setReplayExecutionMap(executionFilePath, browserIdsToReplay) {
    const browserModuleMap = new Map();
    let executionJson;

    try {
      executionJson = fs.readJsonSync(executionFilePath);
    } catch (err) {
      throw new Error(`Error reading reply execution JSON file - ${err}`);
    }

    if (browserIdsToReplay && browserIdsToReplay.length > 0) {
      browserIdsToReplay.forEach((browserId) => {
        browserModuleMap.set(
          browserId.toString(),
          executionJson.executionMapping[browserId.toString()],
        );
      });
    } else if (executionJson.failedBrowsers.length > 0) {
      executionJson.failedBrowsers.forEach((browserId) => {
        browserModuleMap.set(
          browserId,
          executionJson.executionMapping[browserId],
        );
      });
    } else {
      for (
        let browserId = 1;
        browserId <= executionJson.numberOfBrowsers;
        browserId++
      ) {
        browserModuleMap.set(
          browserId.toString(),
          executionJson.executionMapping[browserId.toString()],
        );
      }
    }

    this.stateManager.setReplayExecutionMap(browserModuleMap);
  }

  /**
   * Set the moduleQueue, a list of test modules to be passed to browsers to execute.
   *
   * @param {number} browserId
   * @param {Array<string>} modules
   * @param {boolean} loadBalance
   * @param {boolean} replayExecution
   * @param {string} writeExecutionFile
   */
  setModuleQueue(browserId, modules, loadBalance, replayExecution) {
    const replayExecutionMap = this.stateManager.getReplayExecutionMap();

    if (replayExecution) {
      if (!replayExecutionMap) {
        throw new Error('No replay execution map was set on the stateManager.');
      } else if (!this.stateManager.getReplayExecutionModuleQueue(browserId)) {
        // Only set the moduleQueue once, ignore repeated requests
        this.stateManager.setReplayExecutionModuleQueue(
          replayExecutionMap.get(browserId),
          browserId,
        );
      }
    } else if (loadBalance && !this.stateManager.getTestModuleQueue()) {
      // Only set the moduleQueue once, ignore repeated requests
      this.stateManager.setTestModuleQueue(modules);
    }
  }

  /**
   * Gets the next test module from the moduleQueue and emit back to the browser.
   * If moduleQueue is already empty, emit the module-queue-complete event,
   * signaling no more test module to run.
   *
   * @param {number} browserId
   * @param {Object} socket
   * @param {boolean} loadBalance
   * @param {boolean} writeExecutionFile
   */
  nextModuleResponse(browserId, socket, writeExecutionFile) {
    const moduleQueue =
      this.stateManager.getTestModuleQueue() ||
      this.stateManager.getReplayExecutionModuleQueue(browserId);
    if (!moduleQueue) {
      throw new Error('No moduleQueue was set.');
    }

    const moduleName = moduleQueue.shift();
    socket.emit('testem:next-module-response', {
      done: !moduleQueue.length && !moduleName,
      value: moduleName,
    });

    // Keep track of the modules executed per browserId when running test suite with load-balance.
    // In replay-execution mode, we are already running a predefined set of modules, so no need
    // to save this again
    if (moduleName && writeExecutionFile) {
      this.stateManager.addModuleNameToReplayExecutionMap(
        moduleName,
        browserId,
      );
    }
  }

  /**
   * Record the launched browser id
   *
   * @param {number} browserId
   */
  recordStartedLauncherId(browserId) {
    this.stateManager.addToStartedLaunchers(browserId);
  }

  /**
   * Record the module run details to the stateManager
   *
   * @param {Object} metaData
   */
  recordModuleMetadata(metaData) {
    this.stateManager.addToModuleMetadata(metaData);
  }

  /**
   * Gets browser id of launcher and stores the browser id stateManager
   *
   * @param {Object} launcher
   * @param {Object} ui
   */
  recordFailedBrowserId(launcher, ui) {
    let browserId;
    try {
      browserId = getBrowserId(launcher);
    } catch (err) {
      ui.writeLine(err.message);
    }
    if (
      (browserId !== null || typeof browserId !== 'undefined') &&
      !this.stateManager.containsFailedBrowser(browserId)
    ) {
      this.stateManager.addFailedBrowsers(browserId);
    }
  }

  /**
   * Generates an object for test execution
   *
   * @param {number} browserCount
   */
  _generatesModuleMapJsonObject(browserCount) {
    return {
      numberOfBrowsers: browserCount,
      failedBrowsers: this.stateManager.getFailedBrowsers(),
      executionMapping: (() => {
        let executionMapping = Object.create(null);
        for (const [
          browserId,
          moduleList,
        ] of this.stateManager.getModuleMap()) {
          executionMapping[browserId] = moduleList;
        }

        return executionMapping;
      })(),
    };
  }

  /**
   * Keep track of the number of browsers that completed executing its tests.
   * When all browsers complete, write test-execution.json to disk and clean up the stateManager
   *
   * @param {number} browserCount
   * @param {number} launcherId
   * @param {Object} ui
   * @param {Object} commands
   * @param {Object} currentDate
   */
  completedBrowsersHandler(
    browserCount,
    launcherId,
    ui,
    commands,
    currentDate,
  ) {
    const browsersStarted = this.stateManager.getStartedLaunchers();
    let browsersCompleted = false;

    this.stateManager.incrementCompletedBrowsers(launcherId);
    const completedBrowser = this.stateManager.getCompletedBrowser();
    if (completedBrowser === browsersStarted.size) {
      if (commands.get('writeModuleMetadataFile')) {
        const moduleDetailFileName = path.join(
          this.root,
          `module-metadata-${currentDate}.json`,
        );
        const sortedModuleMetadata = getSortedModuleMetaData(
          this.stateManager.getModuleMetadata(),
        );

        writeJsonToFile(
          moduleDetailFileName,
          {
            requested: `${browserCount} browser(s)`,
            launched: `${browsersStarted.size} browser(s)`,
            modules: Array.from(sortedModuleMetadata.values()),
          },
          { spaces: 2 },
        );
        ui.writeLine(
          `\nExecution module details were recorded at ${moduleDetailFileName}`,
        );
      }

      if (commands.get('writeExecutionFile') && commands.get('loadBalance')) {
        const moduleMapJson = this._generatesModuleMapJsonObject(browserCount);
        const testExecutionPath = path.join(
          this.root,
          `test-execution-${currentDate}.json`,
        );

        writeJsonToFile(testExecutionPath, moduleMapJson, { spaces: 2 });
        ui.writeLine(`\nExecution was recorded at ${testExecutionPath}`);
      }

      ui.writeLine(
        `Out of requested ${browserCount} browser(s), ${browsersStarted.size} browser(s) was launched & completed.`,
      );

      if (browserCount !== browsersStarted.size) {
        ui.writeLine('Waiting for remaining browsers to exited.');
      }
    }

    if (completedBrowser === browserCount) {
      ui.writeLine('All browsers to exited.');
      // --server mode allows rerun of tests by refreshing the browser
      // replayExecutionMap should be reused so the test-execution json
      // does not need to be reread
      const replayExecutionMap = this.stateManager.getReplayExecutionMap();
      this.stateManager = new ExecutionStateManager(replayExecutionMap);
      browsersCompleted = true;
    }
    return browsersCompleted;
  }
}

module.exports = TestemEvents;


================================================
FILE: lib/utils/tests-options-validator.js
================================================
'use strict';

const fs = require('fs-extra');
const SilentError = require('silent-error');
const semver = require('semver');

/**
 * Validates the specified partitions
 *
 * @private
 * @param {Array<Number>} partitions
 * @param {Number} split
 */
function validatePartitions(partitions, split) {
  validatePartitionSplit(partitions, split);
  validateElementsUnique(partitions, 'partition');
}

/**
 * Returns thr number of browsers defined within the test execution file.
 *
 * @param {*} fileName
 */
function getNumberOfBrowser(fileName) {
  const executionJson = fs.readJsonSync(fileName);
  return executionJson.numberOfBrowsers;
}

/**
 * Validates the specified replay-browser
 *
 * @param {String} replayExecution
 * @param {Array<Number>} replayBrowser
 */
function validateReplayBrowser(replayExecution, replayBrowser) {
  if (!replayExecution) {
    throw new SilentError(
      'EmberExam: You must specify replay-execution when using the replay-browser option.',
    );
  }

  const numberOfBrowsers = getNumberOfBrowser(replayExecution);

  for (const i in replayBrowser) {
    const browserId = replayBrowser[i];
    if (browserId < 1) {
      throw new SilentError(
        'EmberExam: You must specify replay-browser values greater than or equal to 1.',
      );
    }
    if (browserId > numberOfBrowsers) {
      throw new SilentError(
        'EmberExam: You must specify replayBrowser value smaller than a number of browsers in the specified json file.',
      );
    }
  }

  validateElementsUnique(replayBrowser, 'replayBrowser');
}

/**
 * Determines if the specified partitions value makes sense for a given split.
 *
 * @private
 * @param {Array<Number>} partitions
 * @param {Number} split
 */
function validatePartitionSplit(partitions, split) {
  if (!split) {
    throw new SilentError(
      'EmberExam: You must specify a `split` value in order to use `partition`.',
    );
  }

  for (let i = 0; i < partitions.length; i++) {
    const partition = partitions[i];
    if (partition < 1) {
      throw new SilentError(
        'EmberExam: Split tests are one-indexed, so you must specify partition values greater than or equal to 1.',
      );
    }
    if (partition > split) {
      throw new SilentError(
        'EmberExam: You must specify `partition` values that are less than or equal to your `split` value.',
      );
    }
  }
}

/**
 * Ensures that there is no value duplicated in a given array.
 *
 * @private
 * @param {Array} arr
 * @param {String} typeOfValue
 */
function validateElementsUnique(arr, typeOfValue) {
  const sorted = arr.slice().sort();
  for (let i = 0; i < sorted.length - 1; i++) {
    if (sorted[i] === sorted[i + 1]) {
      const errorMsg = `EmberExam: You cannot specify the same ${typeOfValue} value twice. ${sorted[i]} is repeated.`;
      throw new SilentError(errorMsg);
    }
  }
}

/**
 * Performs logic related to validating command options for testing and
 * determining which functions to run on the tests.
 *
 * @class TestsOptionsValidator
 */
module.exports = class TestsOptionsValidator {
  constructor(options, emberCliVersion) {
    this.options = options;
    this.emberCliVersion = emberCliVersion;
  }

  /**
   * Validates the command and returns a map of the options and whether they are enabled or not.
   *
   * @public
   * @return {Object} Map of the options and whether they are enabled or not.
   */
  validateCommands() {
    const validatedOptions = new Map();
    if (this.options.writeModuleMetadataFile) {
      validatedOptions.set('writeModuleMetadataFile', true);
    }

    if (this.options.split || this.options.partition) {
      validatedOptions.set('split', this.validateSplit());
    }

    // The parallel option accepts a number, which can be 0
    if (typeof this.options.parallel !== 'undefined') {
      validatedOptions.set('parallel', this.validateParallel());
    }

    // As random option can be an empty string it should check a type of random option rather than the option is not empty.
    if (typeof this.options.random !== 'undefined') {
      validatedOptions.set('random', this.validateRandom());
    }

    if (typeof this.options.writeExecutionFile !== 'undefined') {
      validatedOptions.set(
        'writeExecutionFile',
        this.validateWriteExecutionFile(),
      );
    }

    if (this.options.loadBalance) {
      validatedOptions.set('loadBalance', this.validateLoadBalance());
    }

    if (this.options.replayExecution || this.options.replayBrowser) {
      validatedOptions.set('replayExecution', this.validateReplayExecution());
    }

    return validatedOptions;
  }

  /**
   * Determines if we should split the tests file and validates associated options
   * (`split`, `partition`).
   *
   * @return {boolean}
   */
  validateSplit() {
    const options = this.options;
    let split = options.split;

    if (typeof split !== 'undefined' && split < 2) {
      console.warn(
        'You should specify a number of files greater than 1 to split your tests across. Defaulting to 1 split which is the same as not using `split`.',
      );
      split = 1;
    }

    if (
      typeof split !== 'undefined' &&
      typeof this.options.replayBrowser !== 'undefined'
    ) {
      throw new SilentError(
        'EmberExam: You must not use the `replay-browser` option with the `split` option.',
      );
    }

    if (typeof split !== 'undefined' && this.options.replayExecution) {
      throw new SilentError(
        'EmberExam: You must not use the `replay-execution` option with the `split` option.',
      );
    }

    const partitions = options.partition;

    if (typeof partitions !== 'undefined') {
      validatePartitions(partitions, split);
    }

    return !!split;
  }

  /**
   * Determines if we should randomize the tests and validates associated options
   * (`random`).
   *
   * @return {boolean}
   */
  validateRandom() {
    return typeof this.options.random === 'string';
  }

  /**
   * Determines if a test execution json file should be written after running a test suite and validates associated
   *
   *  @return {boolean}
   */
  validateWriteExecutionFile() {
    if (!this.options.writeExecutionFile) {
      return false;
    }

    if (!this.options.loadBalance) {
      throw new SilentError(
        'EmberExam: You must run test suite with the `load-balance` option in order to use the `write-execution-file` option.',
      );
    } else if (this.options.launch === 'false') {
      //When `--no-launch` option is passed, a value for launch in testem config is set to be string false.
      throw new SilentError(
        'EmberExam: You must not use no-launch with write-execution-file option.',
      );
    }

    return true;
  }

  /**
   * Determines if we should run split tests in parallel and validates associated
   * options (`parallel`).
   *
   * @return {boolean}
   */
  validateParallel() {
    const parallelValue = parseInt(this.options.parallel, 10);

    if (isNaN(parallelValue)) {
      throw new SilentError(
        `EmberExam: You must specify a Numeric value to 'parallel'. Value passed: ${this.options.parallel}`,
      );
    }
    this.options.parallel = parallelValue;

    if (typeof this.options.replayBrowser !== 'undefined') {
      throw new SilentError(
        'EmberExam: You must not use the `replay-browser` option with the `parallel` option.',
      );
    }

    if (this.options.replayExecution) {
      throw new SilentError(
        'EmberExam: You must not use the `replay-execution` option with the `parallel` option.',
      );
    }

    if (!this.options.loadBalance) {
      if (!this.options.split) {
        throw new SilentError(
          'EmberExam: You must specify the `split` option in order to run your tests in parallel.',
        );
      } else if (this.options.parallel !== 1) {
        throw new SilentError(
          'EmberExam: When used with `split` or `partition`, `parallel` does not accept a value other than 1.',
        );
      }
    }

    if (this.options.parallel < 1) {
      throw new SilentError(
        'EmberExam: You must specify a value greater than 1 to `parallel`.',
      );
    }

    return true;
  }

  /**
   * Determines if we should run tests in load balance mode.
   * options (`load-balance`).
   *
   * @return {boolean}
   */
  validateLoadBalance() {
    // It's required to use ember-cli version 3.2.0 or greater to support the `load-balance` feature.
    const emberCliVersionRange = semver.validRange(this.emberCliVersion);
    if (semver.gtr('3.2.0', emberCliVersionRange)) {
      throw new SilentError(
        'EmberExam: You must be using ember-cli version ^3.2.0 for this feature to work properly.',
      );
    }

    if (typeof this.options.replayBrowser !== 'undefined') {
      throw new SilentError(
        'EmberExam: You must not use the `replay-browser` option with the `load-balance` option.',
      );
    }

    if (this.options.replayExecution) {
      throw new SilentError(
        'EmberExam: You must not use the `replay-execution` option with the `load-balance` option.',
      );
    }

    //When `--no-launch` option is passed, a value for launch in testem config is set to be string false.
    if (this.options.launch === 'false') {
      throw new SilentError(
        'EmberExam: You must not use `no-launch` option with the `load-balance` option.',
      );
    }

    if (!this.options.parallel) {
      throw new SilentError(
        'EmberExam: You should specify the number of browsers to load-balance against using `parallel` when using `load-balance`.',
      );
    }

    return true;
  }

  /**
   * Determines if we should replay execution for reproduction.
   * options (`replay-execution`).
   *
   * @return {boolean}
   */
  validateReplayExecution() {
    const replayBrowser = this.options.replayBrowser;
    const replayExecution = this.options.replayExecution;

    if (!replayExecution) {
      return false;
    }

    if (this.options.launch === 'false') {
      throw new SilentError(
        'EmberExam: You must not use `no-launch` option with the `replay-execution` option.',
      );
    }

    if (replayBrowser) {
      validateReplayBrowser(replayExecution, replayBrowser, this.options);
    }

    return true;
  }
};


================================================
FILE: node-tests/.eslintrc
================================================
{
  "env": {
    "mocha": true
  },
  "rules": {
    "no-var": 0
  }
}


================================================
FILE: node-tests/acceptance/exam/vite/vite-test.js
================================================
const path = require('path');
const assert = require('assert');

const { rimrafSync } = require('rimraf');
const { ROOT, execa, getNumberOfTests } = require('../../helpers.js');

const DIR = path.resolve(ROOT, 'test-apps/vite-with-compat');
const TEST_OUTPUT_DIR = 'dist';

describe('Command | exam | vite', function () {
  this.timeout(300000);

  before(function () {
    // Cleanup any previous runs
    rimrafSync(TEST_OUTPUT_DIR);

    // Build the app
    return execa('pnpm', ['build:tests', '--outDir', TEST_OUTPUT_DIR], {
      cwd: DIR,
    });
  });

  after(function () {
    rimrafSync(TEST_OUTPUT_DIR);
  });

  describe('without exam', function () {
    it('has passing tests with just testem', async function () {
      let result = await execa('testem', ['ci'], {
        cwd: DIR,
        env: {
          TESTEM_DIR: TEST_OUTPUT_DIR,
        },
      });

      assert.strictEqual(getNumberOfTests(result.stdout), 6);
      assert.strictEqual(result.stdout.includes('Suite A'), true);
      assert.strictEqual(result.stdout.includes('Suite B'), true);
    });
  });

  describe('split', function () {
    describe('parallel', function () {
      it('has no shared tests between partitions', async function () {
        let resultA = await execa(
          'ember',
          [
            'exam',
            '--test-port',
            '1337',
            '--split',
            '2',
            '--partition',
            '1',
            '--path',
            TEST_OUTPUT_DIR,
            '--parallel',
          ],
          { cwd: DIR },
        );

        let resultB = await execa(
          'ember',
          [
            'exam',
            '--test-port',
            '1338',
            '--split',
            '2',
            '--partition',
            '2',
            '--path',
            TEST_OUTPUT_DIR,
            '--parallel',
          ],
          { cwd: DIR },
        );

        assert.strictEqual(getNumberOfTests(resultA.stdout), 3);
        assert.strictEqual(resultA.stdout.includes('Suite A'), true);
        assert.strictEqual(resultA.stdout.includes('Suite B'), false);

        assert.strictEqual(getNumberOfTests(resultB.stdout), 3);
        assert.strictEqual(resultB.stdout.includes('Suite B'), true);
        assert.strictEqual(resultB.stdout.includes('Suite A'), false);
      });
    });
  });

  describe('loadBalance', function () {
    it('has no shared tests between partitions', async function () {
      let result = await execa(
        'ember',
        [
          'exam',
          '--test-port',
          '1339',
          '--load-balance',
          '--path',
          TEST_OUTPUT_DIR,
          '--parallel',
          '2',
        ],
        { cwd: DIR },
      );

      assert.strictEqual(getNumberOfTests(result.stdout), 6);
      assert.strictEqual(result.stdout.includes('Suite A'), true);
      assert.strictEqual(result.stdout.includes('Suite B'), true);
    });
  });
});


================================================
FILE: node-tests/acceptance/exam-iterate-test.js
================================================
'use strict';

const assert = require('assert');
const { rimrafSync } = require('rimraf');
const fs = require('fs-extra');
const path = require('path');

function assertExpectRejection() {
  assert.ok(false, 'Expected promise to reject, but it fullfilled');
}

async function execa(command, args) {
  const { execa: originalExeca } = await import('execa');
  return originalExeca(command, args);
}

describe('Acceptance | Exam Iterate Command', function () {
  this.timeout(300000);

  it('should build the app, test it a number of times, and clean it up', function () {
    return execa('ember', ['exam:iterate', '2']).then((child) => {
      const stdout = child.stdout;
      assert.ok(
        stdout.includes('Building app for test iterations.'),
        'Logged building message from command',
      );
      assert.ok(
        stdout.includes('Built project successfully.'),
        'Built successfully according to Ember-CLI',
      );

      assert.ok(
        stdout.includes('Running iteration #1.'),
        'Logs first iteration',
      );
      assert.ok(
        stdout.includes('Running iteration #2.'),
        'Logs second iteration',
      );

      const seedRE = /Randomizing tests with seed: (.*)/g;

      const firstSeed = seedRE.exec(stdout)[1];
      const secondSeed = seedRE.exec(stdout)[1];

      assert.ok(firstSeed, 'first seed exists');
      assert.ok(secondSeed, 'second seed exists');
      assert.notEqual(
        firstSeed,
        secondSeed,
        'the first and second seeds are not the same',
      );

      assert.ok(
        stdout.includes('Cleaning up test iterations.'),
        'Logged cleaning up message from command',
      );
      assert.throws(
        () => fs.accessSync('iteration-dist', fs.F_OK),
        'iteration-dist is cleaned up',
      );
    });
  });

  it('should test the app with additional options passed in and catch failure cases', function () {
    const execution = execa('ember', [
      'exam:iterate',
      '2',
      '--options',
      '--parallel',
    ]);
    return execution.then(assertExpectRejection, (error) => {
      const splitErrorRE =
        /You must specify the `split` option in order to run your tests in parallel./g;

      assert.ok(
        splitErrorRE.test(error.stderr),
        'expected stderr to contain the appropriate error message',
      );
      assert.strictEqual(error.exitCode, 1);
      assert.strictEqual(error.failed, true);
      assert.strictEqual(error.killed, false);
    });
  });

  describe('building', function () {
    const buildDir = path.join(process.cwd(), 'dist');

    afterEach(() => rimrafSync(buildDir));

    it('should not build the app or clean it up, but use an existing build to test', function () {
      return execa('ember', ['build']).then(() => {
        execa('ember', ['exam:iterate', '2', '--path', 'dist']).then(
          (child) => {
            const stdout = child.stdout;

            assert.ok(
              !stdout.includes('Building app for test iterations.'),
              'No logged building message from command',
            );
            assert.ok(
              !stdout.includes('Built project successfully.'),
              'Not built successfully according to Ember-CLI',
            );

            assert.ok(
              stdout.includes('Running iteration #1.'),
              'Logs first iteration',
            );
            assert.ok(
              stdout.includes('Running iteration #2.'),
              'Logs second iteration',
            );

            const seedRE = /Randomizing tests with seed: (.*)/g;

            const firstSeed = seedRE.exec(stdout)[1];
            const secondSeed = seedRE.exec(stdout)[1];

            assert.ok(firstSeed, 'first seed exists');
            assert.ok(secondSeed, 'second seed exists');
            assert.notEqual(
              firstSeed,
              secondSeed,
              'the first and second seeds are not the same',
            );

            assert.ok(
              !stdout.includes('Cleaning up test iterations.'),
              'No logged cleaning up message from command',
            );
            assert.throws(
              () => fs.accessSync('iteration-dist', fs.F_OK),
              'iteration-dist is non-existent',
            );

            assert.doesNotThrow(
              () => fs.accessSync(buildDir, fs.F_OK),
              'dist is not cleaned up',
            );
          },
        );
      });
    });
  });

  describe('Exit Code', function () {
    const destPath = path.join(
      __dirname,
      '..',
      '..',
      'tests',
      'unit',
      'failing-test.js',
    );

    beforeEach(function () {
      const failingTestPath = path.join(
        __dirname,
        '..',
        'fixtures',
        'failure.js',
      );
      fs.copySync(failingTestPath, destPath);
    });

    afterEach(function () {
      fs.removeSync(destPath);
    });

    it('should have an exitCode of 1 when a test fails', function () {
      return execa('ember', ['exam:iterate', '1']).then(
        assertExpectRejection,
        (error) => {
          assert.strictEqual(error.exitCode, 1);
          assert.strictEqual(error.killed, false);
        },
      );
    });
  });
});


================================================
FILE: node-tests/acceptance/exam-test.js
================================================
'use strict';

const assert = require('assert');
const fixturify = require('fixturify');
const fs = require('fs-extra');
const path = require('path');
const { rimrafSync } = require('rimraf');
const glob = require('glob');
const { execa, getNumberOfTests } = require('./helpers');

function assertExpectRejection() {
  assert.ok(false, 'Expected promise to reject, but it fullfilled');
}

const TOTAL_NUM_TESTS = 67; // Total Number of tests without the global 'Ember.onerror validation tests'

function getTotalNumberOfTests(output) {
  // In ember-qunit 3.4.0, this new check was added: https://github.com/emberjs/ember-qunit/commit/a7e93c4b4b535dae62fed992b46c00b62bfc83f4
  // which adds this Ember.onerror validation test.
  // As Ember.onerror validation test is added per browser the total number of tests executed should be the sum of TOTAL_NUM_TESTS defined and a number of browsers.
  const emberOnerror = output.match(
    /ember-qunit: Ember.onerror validation: Ember.onerror is functioning proper
Download .txt
gitextract_2rp793zd/

├── .codeclimate.yml
├── .editorconfig
├── .ember-cli
├── .github/
│   ├── renovate.json5
│   └── workflows/
│       ├── ci.yml
│       ├── gh-pages.yml
│       ├── plan-release.yml
│       ├── publish.yml
│       └── release.yml
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc.js
├── .release-plan.json
├── .watchmanconfig
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── RELEASE.md
├── addon-test-support/
│   ├── -private/
│   │   ├── async-iterator.js
│   │   ├── ember-exam-test-loader.js
│   │   ├── filter-test-modules.js
│   │   ├── get-url-params.js
│   │   ├── patch-testem-output.js
│   │   ├── split-test-modules.js
│   │   └── weight-test-modules.js
│   ├── index.d.ts
│   ├── index.js
│   ├── load.js
│   └── start.js
├── docs-app/
│   ├── .gitignore
│   ├── .vitepress/
│   │   ├── config.mts
│   │   └── theme/
│   │       ├── index.ts
│   │       └── style.css
│   ├── ember-try-and-ci.md
│   ├── filtering.md
│   ├── index.md
│   ├── load-balancing.md
│   ├── module-metadata.md
│   ├── package.json
│   ├── preserve-test-name.md
│   ├── quickstart.md
│   ├── randomization-iterator.md
│   ├── randomization.md
│   ├── split-parallel.md
│   ├── splitting.md
│   ├── test-suite-segmentation.md
│   └── tsconfig.json
├── ember-cli-build.js
├── eslint.config.mjs
├── index.js
├── lib/
│   ├── commands/
│   │   ├── exam/
│   │   │   └── iterate.js
│   │   ├── exam.js
│   │   ├── index.js
│   │   └── task/
│   │       ├── test-server.js
│   │       └── test.js
│   └── utils/
│       ├── config-reader.js
│       ├── execution-state-manager.js
│       ├── file-system-helper.js
│       ├── query-helper.js
│       ├── test-page-helper.js
│       ├── testem-events.js
│       └── tests-options-validator.js
├── node-tests/
│   ├── .eslintrc
│   ├── acceptance/
│   │   ├── exam/
│   │   │   └── vite/
│   │   │       └── vite-test.js
│   │   ├── exam-iterate-test.js
│   │   ├── exam-test.js
│   │   └── helpers.js
│   ├── fixtures/
│   │   ├── browser-exit.js
│   │   ├── failure.js
│   │   ├── test-helper-with-load.js
│   │   └── vite-eager-test-load.html
│   ├── list.mjs
│   └── unit/
│       ├── commands/
│       │   └── exam-test.js
│       └── utils/
│           ├── config-reader-test.js
│           ├── execution-state-manager-test.js
│           ├── query-helper-test.js
│           ├── test-page-helper-test.js
│           ├── testem-events-test.js
│           └── tests-options-validator-test.js
├── package.json
├── pnpm-workspace.yaml
├── test-apps/
│   ├── broccoli/
│   │   ├── .editorconfig
│   │   ├── .ember-cli
│   │   ├── .github/
│   │   │   └── workflows/
│   │   │       └── ci.yml
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc.js
│   │   ├── .stylelintignore
│   │   ├── .stylelintrc.js
│   │   ├── .template-lintrc.js
│   │   ├── .watchmanconfig
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── app.js
│   │   │   ├── components/
│   │   │   │   └── .gitkeep
│   │   │   ├── controllers/
│   │   │   │   └── .gitkeep
│   │   │   ├── helpers/
│   │   │   │   └── .gitkeep
│   │   │   ├── index.html
│   │   │   ├── models/
│   │   │   │   └── .gitkeep
│   │   │   ├── router.js
│   │   │   ├── routes/
│   │   │   │   └── .gitkeep
│   │   │   ├── styles/
│   │   │   │   └── app.css
│   │   │   └── templates/
│   │   │       └── application.hbs
│   │   ├── config/
│   │   │   ├── ember-cli-update.json
│   │   │   ├── environment.js
│   │   │   ├── optional-features.json
│   │   │   └── targets.js
│   │   ├── ember-cli-build.js
│   │   ├── eslint.config.mjs
│   │   ├── package.json
│   │   ├── public/
│   │   │   └── robots.txt
│   │   ├── testem.js
│   │   └── tests/
│   │       ├── index.html
│   │       └── test-helper.js
│   ├── embroider3-webpack/
│   │   ├── .editorconfig
│   │   ├── .ember-cli
│   │   ├── .github/
│   │   │   └── workflows/
│   │   │       └── ci.yml
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc.js
│   │   ├── .stylelintignore
│   │   ├── .stylelintrc.js
│   │   ├── .template-lintrc.js
│   │   ├── .watchmanconfig
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── app.js
│   │   │   ├── components/
│   │   │   │   └── .gitkeep
│   │   │   ├── controllers/
│   │   │   │   └── .gitkeep
│   │   │   ├── deprecation-workflow.js
│   │   │   ├── helpers/
│   │   │   │   └── .gitkeep
│   │   │   ├── index.html
│   │   │   ├── models/
│   │   │   │   └── .gitkeep
│   │   │   ├── router.js
│   │   │   ├── routes/
│   │   │   │   └── .gitkeep
│   │   │   ├── styles/
│   │   │   │   └── app.css
│   │   │   └── templates/
│   │   │       └── application.hbs
│   │   ├── config/
│   │   │   ├── ember-cli-update.json
│   │   │   ├── environment.js
│   │   │   ├── optional-features.json
│   │   │   └── targets.js
│   │   ├── ember-cli-build.js
│   │   ├── eslint.config.mjs
│   │   ├── package.json
│   │   ├── public/
│   │   │   └── robots.txt
│   │   ├── testem.js
│   │   └── tests/
│   │       ├── index.html
│   │       └── test-helper.js
│   └── vite-with-compat/
│       ├── .editorconfig
│       ├── .ember-cli
│       ├── .gitignore
│       ├── .prettierignore
│       ├── .prettierrc.mjs
│       ├── .template-lintrc.mjs
│       ├── .watchmanconfig
│       ├── README.md
│       ├── app/
│       │   ├── app.js
│       │   ├── config/
│       │   │   └── environment.js
│       │   └── router.js
│       ├── babel.config.cjs
│       ├── config/
│       │   ├── ember-cli-update.json
│       │   ├── environment.js
│       │   ├── optional-features.json
│       │   └── targets.js
│       ├── ember-cli-build.js
│       ├── eslint.config.mjs
│       ├── index.html
│       ├── package.json
│       ├── public/
│       │   └── robots.txt
│       ├── testem.cjs
│       ├── tests/
│       │   ├── index.html
│       │   ├── integration/
│       │   │   ├── a-test.gjs
│       │   │   └── b-test.gjs
│       │   └── test-helper.js
│       └── vite.config.mjs
├── testem.js
├── testem.multiple-test-page.js
├── testem.no-test-page.js
├── testem.simple-test-page.js
└── tests/
    ├── dummy/
    │   ├── app/
    │   │   ├── app.js
    │   │   ├── index.html
    │   │   ├── router.js
    │   │   └── styles/
    │   │       └── app.css
    │   ├── config/
    │   │   ├── ember-cli-update.json
    │   │   ├── ember-try.js
    │   │   ├── environment.js
    │   │   ├── optional-features.json
    │   │   └── targets.js
    │   └── public/
    │       ├── crossdomain.xml
    │       └── robots.txt
    ├── index.html
    ├── test-helper.js
    └── unit/
        ├── async-iterator-test.js
        ├── filter-test-modules-test.js
        ├── multiple-edge-cases-test.js
        ├── multiple-ember-tests-test.js
        ├── multiple-tests-test.js
        ├── test-loader-test.js
        ├── testem-output-test.js
        └── weight-test-modules-test.js
Download .txt
SYMBOL INDEX (157 symbols across 37 files)

FILE: addon-test-support/-private/async-iterator.js
  class AsyncIterator (line 10) | class AsyncIterator {
    method constructor (line 11) | constructor(testem, options) {
    method done (line 33) | get done() {
    method toString (line 41) | toString() {
    method handleResponse (line 51) | handleResponse(response) {
    method dispose (line 81) | dispose() {
    method _makeNextRequest (line 94) | _makeNextRequest() {
    method _setTimeout (line 105) | _setTimeout(resolve, reject) {
    method next (line 133) | next() {

FILE: addon-test-support/-private/ember-exam-test-loader.js
  class EmberExamTestLoader (line 17) | class EmberExamTestLoader extends TestLoader {
    method constructor (line 18) | constructor(testem, urlParams, qunit = QUnit) {
    method urlParams (line 26) | get urlParams() {
    method load (line 36) | static load() {
    method require (line 47) | require(moduleName) {
    method unsee (line 56) | unsee() {}
    method loadModules (line 63) | async loadModules({ availableModules } = {}) {
    method loadAvailableModules (line 141) | async loadAvailableModules() {
    method loadIndividualModule (line 164) | async loadIndividualModule(moduleName) {
    method setupModuleMetadataHandler (line 193) | setupModuleMetadataHandler() {
    method setupLoadBalanceHandlers (line 209) | setupLoadBalanceHandlers() {

FILE: addon-test-support/-private/filter-test-modules.js
  constant MODULE_PATH_REGEXP (line 2) | const MODULE_PATH_REGEXP = /^(!?)\/(.*)\/(i?)$/;
  constant TEST_PATH_REGEX (line 3) | const TEST_PATH_REGEX = /\/tests\/(.*?)$/;
  function getRegexFilter (line 12) | function getRegexFilter(modulePath) {
  function wildcardFilter (line 24) | function wildcardFilter(module, moduleFilter) {
  function stringFilter (line 41) | function stringFilter(modules, moduleFilter) {
  function regexFilter (line 55) | function regexFilter(modules, modulePathRegexFilter) {
  function convertFilePathToModulePath (line 70) | function convertFilePathToModulePath(filePath) {
  function filterTestModules (line 88) | function filterTestModules(modules, modulePath, filePath) {

FILE: addon-test-support/-private/get-url-params.js
  function decodeQueryParam (line 1) | function decodeQueryParam(param) {
  function getUrlParams (line 12) | function getUrlParams() {

FILE: addon-test-support/-private/patch-testem-output.js
  function updateTestName (line 11) | function updateTestName(urlParams, testName) {
  function patchTestemOutput (line 39) | function patchTestemOutput(urlParams) {

FILE: addon-test-support/-private/split-test-modules.js
  function createGroups (line 1) | function createGroups(num) {
  function splitIntoGroups (line 11) | function splitIntoGroups(arr, numGroups) {
  function splitTestModules (line 32) | function splitTestModules(modules, split, partitions) {

FILE: addon-test-support/-private/weight-test-modules.js
  constant TEST_TYPE_WEIGHT (line 1) | const TEST_TYPE_WEIGHT = {
  constant WEIGHT_REGEX (line 6) | const WEIGHT_REGEX = /\/(unit|integration|acceptance)\//;
  constant DEFAULT_WEIGHT (line 7) | const DEFAULT_WEIGHT = 50;
  function getWeight (line 19) | function getWeight(modulePath) {
  function weightTestModules (line 36) | function weightTestModules(modules) {

FILE: addon-test-support/index.d.ts
  type EmberExamStartOptions (line 3) | type EmberExamStartOptions = Omit<QUnitStartOptions, 'loadTests'> & {

FILE: addon-test-support/load.js
  function loadEmberExam (line 12) | function loadEmberExam() {

FILE: addon-test-support/start.js
  function loadTests (line 11) | async function loadTests(testLoader, loaderOptions = {}) {
  function start (line 28) | async function start(qunitOptions = {}) {

FILE: docs-app/.vitepress/theme/index.ts
  method enhanceApp (line 14) | enhanceApp({ app, router, siteData }) {

FILE: index.js
  method includedCommands (line 8) | includedCommands() {

FILE: lib/commands/exam.js
  method init (line 107) | init() {
  method _validateOptions (line 124) | _validateOptions(commandOptions) {
  method run (line 136) | run(commandOptions) {
  method _randomize (line 220) | _randomize(random, query) {
  method _generateCustomConfigs (line 236) | _generateCustomConfigs(commandOptions) {
  method _setupAndGetBrowserSocketEvents (line 281) | _setupAndGetBrowserSocketEvents(config) {
  method _getModuleMetadataAndBrowserExitSocketEvents (line 361) | _getModuleMetadataAndBrowserExitSocketEvents(
  method _getLoadBalancingBrowserSocketEvents (line 421) | _getLoadBalancingBrowserSocketEvents(

FILE: lib/commands/exam/iterate.js
  method run (line 43) | async run(commandOptions, anonymousOptions) {
  method _write (line 69) | async _write(input, noColor) {
  method _buildForTests (line 82) | async _buildForTests() {
  method _cleanupBuild (line 95) | async _cleanupBuild() {
  method _runIterations (line 109) | async _runIterations(numIterations, options) {
  method _runTests (line 140) | async _runTests(options) {

FILE: lib/commands/task/test-server.js
  method transformOptions (line 4) | transformOptions(options) {

FILE: lib/commands/task/test.js
  method transformOptions (line 4) | transformOptions(options) {

FILE: lib/utils/config-reader.js
  function _findValidFile (line 17) | function _findValidFile(files) {
  function _readFileByType (line 38) | function _readFileByType(file) {

FILE: lib/utils/execution-state-manager.js
  class ExecutionStateManager (line 8) | class ExecutionStateManager {
    method constructor (line 9) | constructor(replayExecutionMap) {
    method getStartedLaunchers (line 38) | getStartedLaunchers() {
    method addToStartedLaunchers (line 48) | addToStartedLaunchers(launcherId) {
    method getReplayExecutionMap (line 57) | getReplayExecutionMap() {
    method setReplayExecutionMap (line 66) | setReplayExecutionMap(replayModuleMap) {
    method getTestModuleQueue (line 75) | getTestModuleQueue() {
    method setTestModuleQueue (line 84) | setTestModuleQueue(moduleQueue) {
    method getNextModuleTestModuleQueue (line 93) | getNextModuleTestModuleQueue() {
    method getReplayExecutionModuleQueue (line 106) | getReplayExecutionModuleQueue(browserId) {
    method setReplayExecutionModuleQueue (line 119) | setReplayExecutionModuleQueue(moduleQueue, browserId) {
    method getNextModuleReplayExecutionModuleQueue (line 132) | getNextModuleReplayExecutionModuleQueue(browserId) {
    method getFailedBrowsers (line 147) | getFailedBrowsers() {
    method containsFailedBrowser (line 157) | containsFailedBrowser(browserId) {
    method addFailedBrowsers (line 167) | addFailedBrowsers(browserId) {
    method getModuleMap (line 176) | getModuleMap() {
    method getModuleMetadata (line 185) | getModuleMetadata() {
    method addModuleNameToReplayExecutionMap (line 195) | addModuleNameToReplayExecutionMap(moduleName, browserId) {
    method _injectModuleMetadata (line 215) | _injectModuleMetadata(
    method addToModuleMetadata (line 240) | addToModuleMetadata(metadata) {
    method getCompletedBrowser (line 274) | getCompletedBrowser() {
    method incrementCompletedBrowsers (line 283) | incrementCompletedBrowsers(browserId) {

FILE: lib/utils/query-helper.js
  function addToQuery (line 10) | function addToQuery(query, param, value) {
  function addToUrl (line 27) | function addToUrl(url, param, value) {

FILE: lib/utils/test-page-helper.js
  function _appendParamToBaseUrl (line 14) | function _appendParamToBaseUrl(commandOptions, baseUrl) {
  function _formatStringOptionValue (line 39) | function _formatStringOptionValue(optionValue) {
  function _generateTestPages (line 64) | function _generateTestPages(customBaseUrl, appendingParam, browserIds) {
  function _getFilledArray (line 81) | function _getFilledArray(start, end) {
  function _getReplayBrowsers (line 93) | function _getReplayBrowsers(executionJsonPath) {
  function combineOptionValueIntoArray (line 109) | function combineOptionValueIntoArray(optionValue) {
  function getBrowserId (line 128) | function getBrowserId(launcher) {
  function getTestUrlFromTestemConfig (line 151) | function getTestUrlFromTestemConfig(configFile) {
  function getCustomBaseUrl (line 177) | function getCustomBaseUrl(commandOptions, baseUrl) {
  function getMultipleTestPages (line 195) | function getMultipleTestPages(config, commandOptions) {

FILE: lib/utils/testem-events.js
  function getSortedModuleMetaData (line 14) | function getSortedModuleMetaData(moduleMetadata) {
  class TestemEvents (line 25) | class TestemEvents {
    method constructor (line 26) | constructor(root) {
    method setReplayExecutionMap (line 41) | setReplayExecutionMap(executionFilePath, browserIdsToReplay) {
    method setModuleQueue (line 90) | setModuleQueue(browserId, modules, loadBalance, replayExecution) {
    method nextModuleResponse (line 119) | nextModuleResponse(browserId, socket, writeExecutionFile) {
    method recordStartedLauncherId (line 149) | recordStartedLauncherId(browserId) {
    method recordModuleMetadata (line 158) | recordModuleMetadata(metaData) {
    method recordFailedBrowserId (line 168) | recordFailedBrowserId(launcher, ui) {
    method _generatesModuleMapJsonObject (line 188) | _generatesModuleMapJsonObject(browserCount) {
    method completedBrowsersHandler (line 216) | completedBrowsersHandler(

FILE: lib/utils/tests-options-validator.js
  function validatePartitions (line 14) | function validatePartitions(partitions, split) {
  function getNumberOfBrowser (line 24) | function getNumberOfBrowser(fileName) {
  function validateReplayBrowser (line 35) | function validateReplayBrowser(replayExecution, replayBrowser) {
  function validatePartitionSplit (line 68) | function validatePartitionSplit(partitions, split) {
  function validateElementsUnique (line 97) | function validateElementsUnique(arr, typeOfValue) {
  method constructor (line 114) | constructor(options, emberCliVersion) {
  method validateCommands (line 125) | validateCommands() {
  method validateSplit (line 169) | validateSplit() {
  method validateRandom (line 210) | validateRandom() {
  method validateWriteExecutionFile (line 219) | validateWriteExecutionFile() {
  method validateParallel (line 244) | validateParallel() {
  method validateLoadBalance (line 293) | validateLoadBalance() {
  method validateReplayExecution (line 336) | validateReplayExecution() {

FILE: node-tests/acceptance/exam-iterate-test.js
  function assertExpectRejection (line 8) | function assertExpectRejection() {
  function execa (line 12) | async function execa(command, args) {

FILE: node-tests/acceptance/exam-test.js
  function assertExpectRejection (line 11) | function assertExpectRejection() {
  constant TOTAL_NUM_TESTS (line 15) | const TOTAL_NUM_TESTS = 67;
  function getTotalNumberOfTests (line 17) | function getTotalNumberOfTests(output) {
  function assertOutput (line 42) | function assertOutput(output, text, good, bad) {
  function assertAllPartitions (line 58) | function assertAllPartitions(output) {
  function assertSomePartitions (line 67) | function assertSomePartitions(output, good, bad) {
  function assertTestExecutionFailedBrowsers (line 290) | function assertTestExecutionFailedBrowsers(output, numberOfFailedBrowser...
  function assertModuleDetailJson (line 311) | function assertModuleDetailJson(output) {

FILE: node-tests/acceptance/exam/vite/vite-test.js
  constant DIR (line 7) | const DIR = path.resolve(ROOT, 'test-apps/vite-with-compat');
  constant TEST_OUTPUT_DIR (line 8) | const TEST_OUTPUT_DIR = 'dist';

FILE: node-tests/acceptance/helpers.js
  function execa (line 4) | async function execa(command, args, options) {
  function getNumberOfTests (line 9) | function getNumberOfTests(str) {
  constant ROOT (line 14) | const ROOT = path.resolve(__dirname, '../../');
  constant FIXTURE_DIR (line 15) | const FIXTURE_DIR = path.resolve(ROOT, 'node-tests/fixtures');
  function applyFixture (line 17) | function applyFixture({ fixture, to }) {

FILE: node-tests/unit/commands/exam-test.js
  constant RSVP (line 6) | const RSVP = require('rsvp');
  function createCommand (line 12) | function createCommand() {

FILE: node-tests/unit/utils/tests-options-validator-test.js
  function validateCommand (line 16) | function validateCommand(validator, cmd) {
  function shouldThrow (line 35) | function shouldThrow(cmd, options, message, emberCliVer = '3.7.0') {
  function shouldEqual (line 40) | function shouldEqual(cmd, options, value, emberCliVer = '3.7.0') {
  function shouldWarn (line 45) | function shouldWarn(cmd, options, value, emberCliVer = '3.7.0') {
  function shouldSplitThrows (line 63) | function shouldSplitThrows(options, message) {
  function shouldSplitEqual (line 67) | function shouldSplitEqual(options, message) {
  function shouldRandomizeEqual (line 121) | function shouldRandomizeEqual(options, message) {

FILE: test-apps/broccoli/app/app.js
  class App (line 6) | class App extends Application {

FILE: test-apps/broccoli/app/router.js
  class Router (line 4) | class Router extends EmberRouter {

FILE: test-apps/embroider3-webpack/app/app.js
  class App (line 11) | class App extends Application {

FILE: test-apps/embroider3-webpack/app/router.js
  class Router (line 4) | class Router extends EmberRouter {

FILE: test-apps/vite-with-compat/app/app.js
  class App (line 6) | class App extends Application {

FILE: test-apps/vite-with-compat/app/router.js
  class Router (line 4) | class Router extends EmberRouter {

FILE: test-apps/vite-with-compat/tests/test-helper.js
  function start (line 8) | async function start({ availableModules }) {

FILE: test-apps/vite-with-compat/vite.config.mjs
  function guessPkgName (line 33) | function guessPkgName(id) {

FILE: tests/dummy/app/app.js
  class App (line 6) | class App extends Application {
Condensed preview — 200 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (374K chars).
[
  {
    "path": ".codeclimate.yml",
    "chars": 254,
    "preview": "---\nengines:\n  duplication:\n    enabled: true\n    config:\n      languages:\n        javascript:\n          mass_threshold:"
  },
  {
    "path": ".editorconfig",
    "chars": 368,
    "preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
  },
  {
    "path": ".ember-cli",
    "chars": 247,
    "preview": "{\n  /**\n    Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript\n    rather "
  },
  {
    "path": ".github/renovate.json5",
    "chars": 472,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:base\",\n    \":automergeLin"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 3330,
    "preview": "name: CI\n\non:\n  push:\n    branches: [ master, main, 'v*' ]\n  pull_request:\n    branches: [ master, main ]\n\nconcurrency:\n"
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "chars": 1211,
    "preview": "name: Deploy\n\non:\n  push:\n    branches: [ master, main, 'v*' ]\n\nconcurrency:\n  group: gh-pages-${{ github.head_ref || gi"
  },
  {
    "path": ".github/workflows/plan-release.yml",
    "chars": 2011,
    "preview": "name: Plan Release\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n      - master\n  pull_request_target: # T"
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 1029,
    "preview": "# For every push to the primary branch with .release-plan.json modified,\n# runs release-plan.\n\nname: Publish Stable\n\non:"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 447,
    "preview": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    name: release\n    runs-on: ubuntu-latest\n\n    st"
  },
  {
    "path": ".gitignore",
    "chars": 437,
    "preview": "# compiled output\n/dist/\n/acceptance-dist/\ndist-*/\ntest-execution-*.json\n/declarations/\n\n# dependencies\n/node_modules/\n\n"
  },
  {
    "path": ".npmignore",
    "chars": 590,
    "preview": "# compiled output\n/dist/\n/tmp/\n\n# misc\n/.codeclimate.yml\n/.editorconfig\n/.ember-cli\n/.env*\n/.eslintcache\n/.eslintignore\n"
  },
  {
    "path": ".prettierignore",
    "chars": 311,
    "preview": "# unconventional js\n/blueprints/*/files/\n\n# compiled output\n/dist/\ndocs-app/\ntest-apps/\nacceptance-dist/\nfailure-dist/\n\n"
  },
  {
    "path": ".prettierrc.js",
    "chars": 149,
    "preview": "'use strict';\n\nmodule.exports = {\n  overrides: [\n    {\n      files: '*.{js,ts}',\n      options: {\n        singleQuote: t"
  },
  {
    "path": ".release-plan.json",
    "chars": 1220,
    "preview": "{\n  \"solution\": {\n    \"ember-exam\": {\n      \"impact\": \"minor\",\n      \"oldVersion\": \"10.0.1\",\n      \"newVersion\": \"10.1.0"
  },
  {
    "path": ".watchmanconfig",
    "chars": 30,
    "preview": "{\n  \"ignore_dirs\": [\"dist\"]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 32565,
    "preview": "# Changelog\n\n## Release (2025-12-19)\n\n* ember-exam 10.1.0 (minor)\n\n#### :rocket: Enhancement\n* `ember-exam`\n  * [#1489]("
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 892,
    "preview": "# How To Contribute\n\n## Installation\n\n- `git clone https://github.com/ember-cli/ember-exam.git`\n- `cd ember-exam`\n- `yar"
  },
  {
    "path": "LICENSE.md",
    "chars": 1066,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015\n\nPermission is hereby granted, free of charge, to any person obtaining a copy "
  },
  {
    "path": "README.md",
    "chars": 22982,
    "preview": "# Ember Exam\n![Build Status](https://github.com/ember-cli/ember-exam/actions/workflows/ci.yml/badge.svg?event=push)\n[![N"
  },
  {
    "path": "RELEASE.md",
    "chars": 1608,
    "preview": "# Release Process\n\nReleases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/re"
  },
  {
    "path": "addon-test-support/-private/async-iterator.js",
    "chars": 3722,
    "preview": "'use strict';\n\nconst iteratorCompleteResponse = { done: true, value: null };\n\n/**\n * A class to iterate a sequencial set"
  },
  {
    "path": "addon-test-support/-private/ember-exam-test-loader.js",
    "chars": 7718,
    "preview": "import { assert } from '@ember/debug';\nimport getUrlParams from './get-url-params';\nimport splitTestModules from './spli"
  },
  {
    "path": "addon-test-support/-private/filter-test-modules.js",
    "chars": 3571,
    "preview": "// A regular expression to help parsing a string to verify regex.\nconst MODULE_PATH_REGEXP = /^(!?)\\/(.*)\\/(i?)$/;\nconst"
  },
  {
    "path": "addon-test-support/-private/get-url-params.js",
    "chars": 888,
    "preview": "function decodeQueryParam(param) {\n  return decodeURIComponent(param.replace(/\\+/g, '%20'));\n}\n\n/**\n * Parses the url an"
  },
  {
    "path": "addon-test-support/-private/patch-testem-output.js",
    "chars": 1189,
    "preview": "/* globals Testem */\n\n/**\n * Returns a modified test name including browser or partition information\n *\n * @function upd"
  },
  {
    "path": "addon-test-support/-private/split-test-modules.js",
    "chars": 1502,
    "preview": "function createGroups(num) {\n  const groups = new Array(num);\n\n  for (let i = 0; i < num; i++) {\n    groups[i] = [];\n  }"
  },
  {
    "path": "addon-test-support/-private/weight-test-modules.js",
    "chars": 1844,
    "preview": "const TEST_TYPE_WEIGHT = {\n  unit: 10,\n  integration: 20,\n  acceptance: 150,\n};\nconst WEIGHT_REGEX = /\\/(unit|integratio"
  },
  {
    "path": "addon-test-support/index.d.ts",
    "chars": 246,
    "preview": "import { QUnitStartOptions } from 'ember-qunit';\n\nexport type EmberExamStartOptions = Omit<QUnitStartOptions, 'loadTests"
  },
  {
    "path": "addon-test-support/index.js",
    "chars": 44,
    "preview": "export { default as start } from './start';\n"
  },
  {
    "path": "addon-test-support/load.js",
    "chars": 606,
    "preview": "import EmberExamTestLoader from './-private/ember-exam-test-loader';\nimport { patchTestemOutput } from './-private/patch"
  },
  {
    "path": "addon-test-support/start.js",
    "chars": 1045,
    "preview": "import loadEmberExam from './load';\nimport { start as qunitStart } from 'ember-qunit';\n\n/**\n * Equivalent to ember-qunit"
  },
  {
    "path": "docs-app/.gitignore",
    "chars": 53,
    "preview": "dist/\nnode_modules/\n.vitepress/dist\n.vitepress/cache\n"
  },
  {
    "path": "docs-app/.vitepress/config.mts",
    "chars": 1629,
    "preview": "import { defineConfig } from 'vitepress'\n\n// https://vitepress.dev/reference/site-config\nexport default defineConfig({\n "
  },
  {
    "path": "docs-app/.vitepress/theme/index.ts",
    "chars": 435,
    "preview": "// https://vitepress.dev/guide/custom-theme\nimport { h } from 'vue'\nimport type { Theme } from 'vitepress'\nimport Defaul"
  },
  {
    "path": "docs-app/.vitepress/theme/style.css",
    "chars": 4731,
    "preview": "/**\n * Customize default theme styling by overriding CSS variables:\n * https://github.com/vuejs/vitepress/blob/main/src/"
  },
  {
    "path": "docs-app/ember-try-and-ci.md",
    "chars": 1398,
    "preview": "### Ember Try & CI Integration\n\nIntegrating ember-exam with [ember-try](https://github.com/ember-cli/ember-try) is remar"
  },
  {
    "path": "docs-app/filtering.md",
    "chars": 1982,
    "preview": "### Filtering\n\nEmber Exam provides options to filter test suites by two types - module path and test file path.\n\n```bash"
  },
  {
    "path": "docs-app/index.md",
    "chars": 1990,
    "preview": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\n\nhero:\n  name: \"ember-exam\"\n  # text: \"Run yo"
  },
  {
    "path": "docs-app/load-balancing.md",
    "chars": 5566,
    "preview": "# Test Load Balancing\n\n```bash\nember exam --parallel=<num> --load-balance\n```\n\nThe `load-balance` option allows you to l"
  },
  {
    "path": "docs-app/module-metadata.md",
    "chars": 1274,
    "preview": "### Generating Module Metadata File For Test Execution\n\n```bash\n$ ember exam --write-module-metadata-file\n$ ember exam -"
  },
  {
    "path": "docs-app/package.json",
    "chars": 569,
    "preview": "{\n  \"name\": \"docs\",\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"docs:dev\": \"vitepress dev .\",\n    \"docs"
  },
  {
    "path": "docs-app/preserve-test-name.md",
    "chars": 1216,
    "preview": "# Preserve Test Name\n\nWhen using `--split` and/or `--load-balance` the output will look something like:\n\n```bash\n# ember"
  },
  {
    "path": "docs-app/quickstart.md",
    "chars": 3719,
    "preview": "# Quickstart\n\n## Installation\n\nInstallation is as easy as running:\n\n```bash\nnpm add --save-dev ember-exam\n```\n\n## Usage\n"
  },
  {
    "path": "docs-app/randomization-iterator.md",
    "chars": 800,
    "preview": "# Randomization Iterator\n\nRandomization can be helpful for identifying non-atomic or order-dependent tests. To that end,"
  },
  {
    "path": "docs-app/randomization.md",
    "chars": 1097,
    "preview": "# Randomization\n\n```bash\nember exam --random[=<seed>]\n```\n\nThe `random` option allows you to randomize the order in whic"
  },
  {
    "path": "docs-app/split-parallel.md",
    "chars": 1737,
    "preview": "# Split Test Parallelization\n\n```bash\nember exam --split=<num> --parallel\n```\n\nThe `parallel` option allows you to run y"
  },
  {
    "path": "docs-app/splitting.md",
    "chars": 954,
    "preview": "# Splitting\n\n```bash\nember exam --split=<num>\n```\n\nThe `split` option allows you to specify the number of partitions gre"
  },
  {
    "path": "docs-app/test-suite-segmentation.md",
    "chars": 537,
    "preview": "# Test Suite Segmentation\n\nSome test suites like to segment which tests run based on various facets such as type of test"
  },
  {
    "path": "docs-app/tsconfig.json",
    "chars": 476,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"esnext\",\n    \"target\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"esModul"
  },
  {
    "path": "ember-cli-build.js",
    "chars": 619,
    "preview": "'use strict';\n\nconst EmberAddon = require('ember-cli/lib/broccoli/ember-addon');\n\nmodule.exports = function (defaults) {"
  },
  {
    "path": "eslint.config.mjs",
    "chars": 848,
    "preview": "import globals from \"globals\";\nimport { ember } from \"ember-eslint\";\nimport * as url from \"url\";\n\n// Needed until Node 2"
  },
  {
    "path": "index.js",
    "chars": 162,
    "preview": "/* eslint-env node */\n\n'use strict';\n\nmodule.exports = {\n  name: require('./package').name,\n\n  includedCommands() {\n    "
  },
  {
    "path": "lib/commands/exam/iterate.js",
    "chars": 4303,
    "preview": "'use strict';\n\nmodule.exports = {\n  name: 'exam:iterate',\n\n  description:\n    \"Runs your app's test suite in a random or"
  },
  {
    "path": "lib/commands/exam.js",
    "chars": 14359,
    "preview": "'use strict';\n\nconst { addToQuery } = require('../utils/query-helper');\n// npmlog is used to write to testem server logs"
  },
  {
    "path": "lib/commands/index.js",
    "chars": 109,
    "preview": "'use strict';\n\nmodule.exports = {\n  exam: require('./exam'),\n  'exam:iterate': require('./exam/iterate'),\n};\n"
  },
  {
    "path": "lib/commands/task/test-server.js",
    "chars": 408,
    "preview": "const TestServerTask = require('ember-cli/lib/tasks/test-server');\n\nmodule.exports = TestServerTask.extend({\n  transform"
  },
  {
    "path": "lib/commands/task/test.js",
    "chars": 732,
    "preview": "const TestTask = require('ember-cli/lib/tasks/test');\n\nmodule.exports = TestTask.extend({\n  transformOptions(options) {\n"
  },
  {
    "path": "lib/utils/config-reader.js",
    "chars": 1916,
    "preview": "'use strict';\n\nconst fs = require('fs-extra');\nconst yaml = require('js-yaml');\nconst path = require('path');\nconst debu"
  },
  {
    "path": "lib/utils/execution-state-manager.js",
    "chars": 6963,
    "preview": "'use strict';\n\n/**\n * A class to store the state of an execution.\n *\n * @class ExecutionStateManager\n */\nclass Execution"
  },
  {
    "path": "lib/utils/file-system-helper.js",
    "chars": 470,
    "preview": "const fs = require('fs-extra');\n\n/**\n * Creates a file with targetJsonObject\n *\n * @param {string} fileName\n * @param {O"
  },
  {
    "path": "lib/utils/query-helper.js",
    "chars": 765,
    "preview": "'use strict';\n\n/**\n * Creates a valid query string by appending a given param and value to query.\n *\n * @param {string} "
  },
  {
    "path": "lib/utils/test-page-helper.js",
    "chars": 7185,
    "preview": "'use strict';\n\nconst fs = require('fs-extra');\nconst readTestemConfig = require('../utils/config-reader');\nconst { addTo"
  },
  {
    "path": "lib/utils/testem-events.js",
    "chars": 8787,
    "preview": "'use strict';\n\nconst fs = require('fs-extra');\nconst path = require('path');\nconst ExecutionStateManager = require('./ex"
  },
  {
    "path": "lib/utils/tests-options-validator.js",
    "chars": 10277,
    "preview": "'use strict';\n\nconst fs = require('fs-extra');\nconst SilentError = require('silent-error');\nconst semver = require('semv"
  },
  {
    "path": "node-tests/.eslintrc",
    "chars": 71,
    "preview": "{\n  \"env\": {\n    \"mocha\": true\n  },\n  \"rules\": {\n    \"no-var\": 0\n  }\n}\n"
  },
  {
    "path": "node-tests/acceptance/exam/vite/vite-test.js",
    "chars": 2954,
    "preview": "const path = require('path');\nconst assert = require('assert');\n\nconst { rimrafSync } = require('rimraf');\nconst { ROOT,"
  },
  {
    "path": "node-tests/acceptance/exam-iterate-test.js",
    "chars": 5245,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst { rimrafSync } = require('rimraf');\nconst fs = require('fs-extra'"
  },
  {
    "path": "node-tests/acceptance/exam-test.js",
    "chars": 17352,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst fixturify = require('fixturify');\nconst fs = require('fs-extra');"
  },
  {
    "path": "node-tests/acceptance/helpers.js",
    "chars": 628,
    "preview": "const path = require('path');\nconst fsExtra = require('fs-extra');\n\nasync function execa(command, args, options) {\n  con"
  },
  {
    "path": "node-tests/fixtures/browser-exit.js",
    "chars": 255,
    "preview": "import { module, test } from 'qunit';\n\nmodule('Module With Infinite Loop');\n\ntest('Infinite loop test #1', function (ass"
  },
  {
    "path": "node-tests/fixtures/failure.js",
    "chars": 17,
    "preview": "throw 'failure';\n"
  },
  {
    "path": "node-tests/fixtures/test-helper-with-load.js",
    "chars": 436,
    "preview": "import Application from 'dummy/app';\nimport config from 'dummy/config/environment';\nimport { setApplication } from '@emb"
  },
  {
    "path": "node-tests/fixtures/vite-eager-test-load.html",
    "chars": 1279,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>ViteWithCompat Tests</title>\n    <meta name=\"descr"
  },
  {
    "path": "node-tests/list.mjs",
    "chars": 1316,
    "preview": "/**\n * This file is used by CI to list all the test files we have to generate a matrix to run.\n * Since our node-tests d"
  },
  {
    "path": "node-tests/unit/commands/exam-test.js",
    "chars": 4794,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst MockProject = require('ember-cli/tests/helpers/mock-project');\nco"
  },
  {
    "path": "node-tests/unit/utils/config-reader-test.js",
    "chars": 2276,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst fixturify = require('fixturify');\nconst fs = require('fs-extra');"
  },
  {
    "path": "node-tests/unit/utils/execution-state-manager-test.js",
    "chars": 5562,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst ExecutionStateManager = require('../../../lib/utils/execution-sta"
  },
  {
    "path": "node-tests/unit/utils/query-helper-test.js",
    "chars": 1857,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst { addToQuery, addToUrl } = require('../../../lib/utils/query-help"
  },
  {
    "path": "node-tests/unit/utils/test-page-helper-test.js",
    "chars": 12507,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst sinon = require('sinon');\nconst {\n  combineOptionValueIntoArray,\n"
  },
  {
    "path": "node-tests/unit/utils/testem-events-test.js",
    "chars": 15433,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst fixturify = require('fixturify');\nconst fs = require('fs-extra');"
  },
  {
    "path": "node-tests/unit/utils/tests-options-validator-test.js",
    "chars": 12583,
    "preview": "'use strict';\n\nconst assert = require('assert');\nconst fixturify = require('fixturify');\nconst fs = require('fs-extra');"
  },
  {
    "path": "package.json",
    "chars": 3128,
    "preview": "{\n  \"name\": \"ember-exam\",\n  \"version\": \"10.1.0\",\n  \"description\": \"Run your tests with randomization, splitting, and par"
  },
  {
    "path": "pnpm-workspace.yaml",
    "chars": 2866,
    "preview": "packages:\n  - .\n  - ./docs-app\n  - test-apps/*\n\n## We do not want to auto-install peers because\n## We want to ensure we "
  },
  {
    "path": "test-apps/broccoli/.editorconfig",
    "chars": 367,
    "preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
  },
  {
    "path": "test-apps/broccoli/.ember-cli",
    "chars": 247,
    "preview": "{\n  /**\n    Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript\n    rather "
  },
  {
    "path": "test-apps/broccoli/.github/workflows/ci.yml",
    "chars": 876,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main\n      - master\n  pull_request: {}\n\nconcurrency:\n  group: ci-${{ github."
  },
  {
    "path": "test-apps/broccoli/.gitignore",
    "chars": 332,
    "preview": "# compiled output\n/dist/\n/declarations/\n\n# dependencies\n/node_modules/\n\n# misc\n/.env*\n/.pnp*\n/.eslintcache\n/coverage/\n/n"
  },
  {
    "path": "test-apps/broccoli/.prettierignore",
    "chars": 133,
    "preview": "# unconventional js\n/blueprints/*/files/\n\n# compiled output\n/dist/\n\n# misc\n/coverage/\n!.*\n.*/\n\n# ember-try\n/.node_module"
  },
  {
    "path": "test-apps/broccoli/.prettierrc.js",
    "chars": 149,
    "preview": "'use strict';\n\nmodule.exports = {\n  overrides: [\n    {\n      files: '*.{js,ts}',\n      options: {\n        singleQuote: t"
  },
  {
    "path": "test-apps/broccoli/.stylelintignore",
    "chars": 106,
    "preview": "# unconventional files\n/blueprints/*/files/\n\n# compiled output\n/dist/\n\n# addons\n/.node_modules.ember-try/\n"
  },
  {
    "path": "test-apps/broccoli/.stylelintrc.js",
    "chars": 113,
    "preview": "'use strict';\n\nmodule.exports = {\n  extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],\n};\n"
  },
  {
    "path": "test-apps/broccoli/.template-lintrc.js",
    "chars": 63,
    "preview": "'use strict';\n\nmodule.exports = {\n  extends: 'recommended',\n};\n"
  },
  {
    "path": "test-apps/broccoli/.watchmanconfig",
    "chars": 30,
    "preview": "{\n  \"ignore_dirs\": [\"dist\"]\n}\n"
  },
  {
    "path": "test-apps/broccoli/README.md",
    "chars": 1446,
    "preview": "# broccoli\n\nThis README outlines the details of collaborating on this Ember application.\nA short introduction of this ap"
  },
  {
    "path": "test-apps/broccoli/app/app.js",
    "chars": 391,
    "preview": "import Application from '@ember/application';\nimport Resolver from 'ember-resolver';\nimport loadInitializers from 'ember"
  },
  {
    "path": "test-apps/broccoli/app/components/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/broccoli/app/controllers/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/broccoli/app/helpers/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/broccoli/app/index.html",
    "chars": 635,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Broccoli</title>\n    <meta name=\"description\" cont"
  },
  {
    "path": "test-apps/broccoli/app/models/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/broccoli/app/router.js",
    "chars": 243,
    "preview": "import EmberRouter from '@ember/routing/router';\nimport config from 'broccoli/config/environment';\n\nexport default class"
  },
  {
    "path": "test-apps/broccoli/app/routes/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/broccoli/app/styles/app.css",
    "chars": 116,
    "preview": "/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */\n"
  },
  {
    "path": "test-apps/broccoli/app/templates/application.hbs",
    "chars": 160,
    "preview": "{{page-title \"Broccoli\"}}\n\n{{outlet}}\n\n{{! The following component displays Ember's default welcome message. }}\n<Welcome"
  },
  {
    "path": "test-apps/broccoli/config/ember-cli-update.json",
    "chars": 432,
    "preview": "{\n  \"schemaVersion\": \"1.0.0\",\n  \"packages\": [\n    {\n      \"name\": \"ember-cli\",\n      \"version\": \"5.12.0\",\n      \"bluepri"
  },
  {
    "path": "test-apps/broccoli/config/environment.js",
    "chars": 1167,
    "preview": "'use strict';\n\nmodule.exports = function (environment) {\n  const ENV = {\n    modulePrefix: 'broccoli',\n    environment,\n"
  },
  {
    "path": "test-apps/broccoli/config/optional-features.json",
    "chars": 189,
    "preview": "{\n  \"application-template-wrapper\": false,\n  \"default-async-observers\": true,\n  \"jquery-integration\": false,\n  \"template"
  },
  {
    "path": "test-apps/broccoli/config/targets.js",
    "chars": 157,
    "preview": "'use strict';\n\nconst browsers = [\n  'last 1 Chrome versions',\n  'last 1 Firefox versions',\n  'last 1 Safari versions',\n]"
  },
  {
    "path": "test-apps/broccoli/ember-cli-build.js",
    "chars": 213,
    "preview": "'use strict';\n\nconst EmberApp = require('ember-cli/lib/broccoli/ember-app');\n\nmodule.exports = function (defaults) {\n  c"
  },
  {
    "path": "test-apps/broccoli/eslint.config.mjs",
    "chars": 206,
    "preview": "import { ember } from 'ember-eslint';\nimport * as url from 'url';\n\n// Needed until Node 20\nconst dirname = url.fileURLTo"
  },
  {
    "path": "test-apps/broccoli/package.json",
    "chars": 2075,
    "preview": "{\n  \"name\": \"broccoli\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"description\": \"Small description for broccoli goes h"
  },
  {
    "path": "test-apps/broccoli/public/robots.txt",
    "chars": 51,
    "preview": "# http://www.robotstxt.org\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "test-apps/broccoli/testem.js",
    "chars": 589,
    "preview": "'use strict';\n\nmodule.exports = {\n  test_page: 'tests/index.html?hidepassed',\n  disable_watching: true,\n  launch_in_ci: "
  },
  {
    "path": "test-apps/broccoli/tests/index.html",
    "chars": 1179,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Broccoli Tests</title>\n    <meta name=\"description"
  },
  {
    "path": "test-apps/broccoli/tests/test-helper.js",
    "chars": 329,
    "preview": "import Application from 'broccoli/app';\nimport config from 'broccoli/config/environment';\nimport * as QUnit from 'qunit'"
  },
  {
    "path": "test-apps/embroider3-webpack/.editorconfig",
    "chars": 367,
    "preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
  },
  {
    "path": "test-apps/embroider3-webpack/.ember-cli",
    "chars": 696,
    "preview": "{\n  /**\n    Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript\n    rather "
  },
  {
    "path": "test-apps/embroider3-webpack/.github/workflows/ci.yml",
    "chars": 876,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main\n      - master\n  pull_request: {}\n\nconcurrency:\n  group: ci-${{ github."
  },
  {
    "path": "test-apps/embroider3-webpack/.gitignore",
    "chars": 188,
    "preview": "# compiled output\n/dist/\n/declarations/\n\n# dependencies\n/node_modules/\n\n# misc\n/.env*\n/.pnp*\n/.eslintcache\n/coverage/\n/n"
  },
  {
    "path": "test-apps/embroider3-webpack/.prettierignore",
    "chars": 139,
    "preview": "# unconventional js\n/blueprints/*/files/\n\n# compiled output\n/dist/\n\n# misc\n/coverage/\n!.*\n.*/\n/pnpm-lock.yaml\nember-cli-"
  },
  {
    "path": "test-apps/embroider3-webpack/.prettierrc.js",
    "chars": 260,
    "preview": "'use strict';\n\nmodule.exports = {\n  plugins: ['prettier-plugin-ember-template-tag'],\n  overrides: [\n    {\n      files: '"
  },
  {
    "path": "test-apps/embroider3-webpack/.stylelintignore",
    "chars": 70,
    "preview": "# unconventional files\n/blueprints/*/files/\n\n# compiled output\n/dist/\n"
  },
  {
    "path": "test-apps/embroider3-webpack/.stylelintrc.js",
    "chars": 79,
    "preview": "'use strict';\n\nmodule.exports = {\n  extends: ['stylelint-config-standard'],\n};\n"
  },
  {
    "path": "test-apps/embroider3-webpack/.template-lintrc.js",
    "chars": 63,
    "preview": "'use strict';\n\nmodule.exports = {\n  extends: 'recommended',\n};\n"
  },
  {
    "path": "test-apps/embroider3-webpack/.watchmanconfig",
    "chars": 30,
    "preview": "{\n  \"ignore_dirs\": [\"dist\"]\n}\n"
  },
  {
    "path": "test-apps/embroider3-webpack/README.md",
    "chars": 1466,
    "preview": "# embroider3-webpack\n\nThis README outlines the details of collaborating on this Ember application.\nA short introduction "
  },
  {
    "path": "test-apps/embroider3-webpack/app/app.js",
    "chars": 566,
    "preview": "import Application from '@ember/application';\nimport Resolver from 'ember-resolver';\nimport loadInitializers from 'ember"
  },
  {
    "path": "test-apps/embroider3-webpack/app/components/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/embroider3-webpack/app/controllers/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/embroider3-webpack/app/deprecation-workflow.js",
    "chars": 740,
    "preview": "import setupDeprecationWorkflow from 'ember-cli-deprecation-workflow';\n\n/**\n * Docs: https://github.com/ember-cli/ember-"
  },
  {
    "path": "test-apps/embroider3-webpack/app/helpers/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/embroider3-webpack/app/index.html",
    "chars": 664,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Embroider3Webpack</title>\n    <meta name=\"descript"
  },
  {
    "path": "test-apps/embroider3-webpack/app/models/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/embroider3-webpack/app/router.js",
    "chars": 253,
    "preview": "import EmberRouter from '@ember/routing/router';\nimport config from 'embroider3-webpack/config/environment';\n\nexport def"
  },
  {
    "path": "test-apps/embroider3-webpack/app/routes/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test-apps/embroider3-webpack/app/styles/app.css",
    "chars": 116,
    "preview": "/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */\n"
  },
  {
    "path": "test-apps/embroider3-webpack/app/templates/application.hbs",
    "chars": 169,
    "preview": "{{page-title \"Embroider3Webpack\"}}\n\n{{outlet}}\n\n{{! The following component displays Ember's default welcome message. }}"
  },
  {
    "path": "test-apps/embroider3-webpack/config/ember-cli-update.json",
    "chars": 458,
    "preview": "{\n  \"schemaVersion\": \"1.0.0\",\n  \"packages\": [\n    {\n      \"name\": \"ember-cli\",\n      \"version\": \"6.6.0\",\n      \"blueprin"
  },
  {
    "path": "test-apps/embroider3-webpack/config/environment.js",
    "chars": 1177,
    "preview": "'use strict';\n\nmodule.exports = function (environment) {\n  const ENV = {\n    modulePrefix: 'embroider3-webpack',\n    env"
  },
  {
    "path": "test-apps/embroider3-webpack/config/optional-features.json",
    "chars": 189,
    "preview": "{\n  \"application-template-wrapper\": false,\n  \"default-async-observers\": true,\n  \"jquery-integration\": false,\n  \"template"
  },
  {
    "path": "test-apps/embroider3-webpack/config/targets.js",
    "chars": 157,
    "preview": "'use strict';\n\nconst browsers = [\n  'last 1 Chrome versions',\n  'last 1 Firefox versions',\n  'last 1 Safari versions',\n]"
  },
  {
    "path": "test-apps/embroider3-webpack/ember-cli-build.js",
    "chars": 919,
    "preview": "'use strict';\n\nconst EmberApp = require('ember-cli/lib/broccoli/ember-app');\n\nmodule.exports = function (defaults) {\n  c"
  },
  {
    "path": "test-apps/embroider3-webpack/eslint.config.mjs",
    "chars": 206,
    "preview": "import { ember } from 'ember-eslint';\nimport * as url from 'url';\n\n// Needed until Node 20\nconst dirname = url.fileURLTo"
  },
  {
    "path": "test-apps/embroider3-webpack/package.json",
    "chars": 2320,
    "preview": "{\n  \"name\": \"embroider3-webpack\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"description\": \"Small description for embro"
  },
  {
    "path": "test-apps/embroider3-webpack/public/robots.txt",
    "chars": 51,
    "preview": "# http://www.robotstxt.org\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "test-apps/embroider3-webpack/testem.js",
    "chars": 589,
    "preview": "'use strict';\n\nmodule.exports = {\n  test_page: 'tests/index.html?hidepassed',\n  disable_watching: true,\n  launch_in_ci: "
  },
  {
    "path": "test-apps/embroider3-webpack/tests/index.html",
    "chars": 1208,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Embroider3Webpack Tests</title>\n    <meta name=\"de"
  },
  {
    "path": "test-apps/embroider3-webpack/tests/test-helper.js",
    "chars": 388,
    "preview": "import Application from 'embroider3-webpack/app';\nimport config from 'embroider3-webpack/config/environment';\nimport { s"
  },
  {
    "path": "test-apps/vite-with-compat/.editorconfig",
    "chars": 367,
    "preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
  },
  {
    "path": "test-apps/vite-with-compat/.ember-cli",
    "chars": 698,
    "preview": "{\n  /**\n    Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript\n    rather "
  },
  {
    "path": "test-apps/vite-with-compat/.gitignore",
    "chars": 339,
    "preview": "# compiled output\n/dist/\n/declarations/\n/tmp/\n\n# dependencies\n/node_modules/\n\n# misc\n*.local\n/.pnp*\n/.eslintcache\n/cover"
  },
  {
    "path": "test-apps/vite-with-compat/.prettierignore",
    "chars": 139,
    "preview": "# unconventional js\n/blueprints/*/files/\n\n# compiled output\n/dist/\n\n# misc\n/coverage/\n!.*\n.*/\n/pnpm-lock.yaml\nember-cli-"
  },
  {
    "path": "test-apps/vite-with-compat/.prettierrc.mjs",
    "chars": 666,
    "preview": "export default {\n  plugins: ['prettier-plugin-ember-template-tag'],\n  singleQuote: true,\n  overrides: [\n    {\n      file"
  },
  {
    "path": "test-apps/vite-with-compat/.template-lintrc.mjs",
    "chars": 46,
    "preview": "export default {\n  extends: 'recommended',\n};\n"
  },
  {
    "path": "test-apps/vite-with-compat/.watchmanconfig",
    "chars": 30,
    "preview": "{\n  \"ignore_dirs\": [\"dist\"]\n}\n"
  },
  {
    "path": "test-apps/vite-with-compat/README.md",
    "chars": 1448,
    "preview": "# vite-with-compat\n\nThis README outlines the details of collaborating on this Ember application.\nA short introduction of"
  },
  {
    "path": "test-apps/vite-with-compat/app/app.js",
    "chars": 388,
    "preview": "import Application from '@ember/application';\nimport compatModules from '@embroider/virtual/compat-modules';\nimport Reso"
  },
  {
    "path": "test-apps/vite-with-compat/app/config/environment.js",
    "chars": 754,
    "preview": "import loadConfigFromMeta from '@embroider/config-meta-loader';\nimport { assert } from '@ember/debug';\n\nconst config = l"
  },
  {
    "path": "test-apps/vite-with-compat/app/router.js",
    "chars": 251,
    "preview": "import EmberRouter from '@ember/routing/router';\nimport config from 'vite-with-compat/config/environment';\n\nexport defau"
  },
  {
    "path": "test-apps/vite-with-compat/babel.config.cjs",
    "chars": 904,
    "preview": "const {\n  babelCompatSupport,\n  templateCompatSupport,\n} = require('@embroider/compat/babel');\n\nmodule.exports = {\n  plu"
  },
  {
    "path": "test-apps/vite-with-compat/config/ember-cli-update.json",
    "chars": 345,
    "preview": "{\n  \"schemaVersion\": \"1.0.0\",\n  \"packages\": [\n    {\n      \"name\": \"@ember/app-blueprint\",\n      \"version\": \"0.8.1\",\n    "
  },
  {
    "path": "test-apps/vite-with-compat/config/environment.js",
    "chars": 1175,
    "preview": "'use strict';\n\nmodule.exports = function (environment) {\n  const ENV = {\n    modulePrefix: 'vite-with-compat',\n    envir"
  },
  {
    "path": "test-apps/vite-with-compat/config/optional-features.json",
    "chars": 189,
    "preview": "{\n  \"application-template-wrapper\": false,\n  \"default-async-observers\": true,\n  \"jquery-integration\": false,\n  \"template"
  },
  {
    "path": "test-apps/vite-with-compat/config/targets.js",
    "chars": 157,
    "preview": "'use strict';\n\nconst browsers = [\n  'last 1 Chrome versions',\n  'last 1 Firefox versions',\n  'last 1 Safari versions',\n]"
  },
  {
    "path": "test-apps/vite-with-compat/ember-cli-build.js",
    "chars": 756,
    "preview": "'use strict';\n\nconst EmberApp = require('ember-cli/lib/broccoli/ember-app');\nconst { compatBuild } = require('@embroider"
  },
  {
    "path": "test-apps/vite-with-compat/eslint.config.mjs",
    "chars": 206,
    "preview": "import { ember } from 'ember-eslint';\nimport * as url from 'url';\n\n// Needed until Node 20\nconst dirname = url.fileURLTo"
  },
  {
    "path": "test-apps/vite-with-compat/index.html",
    "chars": 133,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      window.location.href = '/tests/';\n    </script>\n  </head>\n  <body></b"
  },
  {
    "path": "test-apps/vite-with-compat/package.json",
    "chars": 2618,
    "preview": "{\n  \"name\": \"vite-with-compat\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"description\": \"Small description for vite-wi"
  },
  {
    "path": "test-apps/vite-with-compat/public/robots.txt",
    "chars": 51,
    "preview": "# http://www.robotstxt.org\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "test-apps/vite-with-compat/testem.cjs",
    "chars": 749,
    "preview": "'use strict';\n\nif (typeof module !== 'undefined') {\n  module.exports = {\n    test_page: 'tests/index.html?hidepassed',\n "
  },
  {
    "path": "test-apps/vite-with-compat/tests/index.html",
    "chars": 1262,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>ViteWithCompat Tests</title>\n    <meta name=\"descr"
  },
  {
    "path": "test-apps/vite-with-compat/tests/integration/a-test.gjs",
    "chars": 662,
    "preview": "import { module, test } from 'qunit';\nimport { setupRenderingTest } from 'ember-qunit';\nimport { render } from '@ember/t"
  },
  {
    "path": "test-apps/vite-with-compat/tests/integration/b-test.gjs",
    "chars": 664,
    "preview": "import { module, test } from 'qunit';\nimport { setupRenderingTest } from 'ember-qunit';\nimport { render } from '@ember/t"
  },
  {
    "path": "test-apps/vite-with-compat/tests/test-helper.js",
    "chars": 470,
    "preview": "import Application from 'vite-with-compat/app';\nimport config from 'vite-with-compat/config/environment';\nimport * as QU"
  },
  {
    "path": "test-apps/vite-with-compat/vite.config.mjs",
    "chars": 1224,
    "preview": "import { defineConfig } from 'vite';\nimport { extensions, classicEmberSupport, ember } from '@embroider/vite';\nimport { "
  },
  {
    "path": "testem.js",
    "chars": 575,
    "preview": "module.exports = {\n  test_page: 'tests/index.html?hidepassed',\n  disable_watching: true,\n  launch_in_ci: ['Chrome'],\n  l"
  },
  {
    "path": "testem.multiple-test-page.js",
    "chars": 408,
    "preview": "module.exports = {\n  framework: 'qunit',\n  test_page: [\n    'tests/index.html?hidepassed&derp=herp',\n    'tests/index.ht"
  },
  {
    "path": "testem.no-test-page.js",
    "chars": 300,
    "preview": "module.exports = {\n  framework: 'qunit',\n  disable_watching: true,\n  launch_in_ci: ['Chrome'],\n  launch_in_dev: ['Chrome"
  },
  {
    "path": "testem.simple-test-page.js",
    "chars": 36,
    "preview": "module.exports = {\n  foo: 'bar',\n};\n"
  },
  {
    "path": "tests/dummy/app/app.js",
    "chars": 390,
    "preview": "import Application from '@ember/application';\nimport Resolver from 'ember-resolver';\nimport loadInitializers from 'ember"
  },
  {
    "path": "tests/dummy/app/index.html",
    "chars": 631,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Ember Exam</title>\n    <meta name=\"description\" co"
  },
  {
    "path": "tests/dummy/app/router.js",
    "chars": 716,
    "preview": "import AddonDocsRouter, { docsRoute } from 'ember-cli-addon-docs/router';\nimport config from './config/environment';\n\nco"
  },
  {
    "path": "tests/dummy/app/styles/app.css",
    "chars": 298,
    "preview": ":root {\n  --brand-primary: #751c27;\n}\n\n.home {\n  padding-left: 1rem;\n  padding-right: 1rem;\n  max-width: 900px;\n  margin"
  },
  {
    "path": "tests/dummy/config/ember-cli-update.json",
    "chars": 451,
    "preview": "{\n  \"schemaVersion\": \"1.0.0\",\n  \"packages\": [\n    {\n      \"name\": \"ember-cli\",\n      \"version\": \"5.5.0\",\n      \"blueprin"
  },
  {
    "path": "tests/dummy/config/ember-try.js",
    "chars": 1315,
    "preview": "'use strict';\n\nconst getChannelURL = require('ember-source-channel-url');\nconst { embroiderSafe, embroiderOptimized } = "
  },
  {
    "path": "tests/dummy/config/environment.js",
    "chars": 1267,
    "preview": "'use strict';\n\nmodule.exports = function (environment) {\n  const ENV = {\n    modulePrefix: 'dummy',\n    environment,\n   "
  },
  {
    "path": "tests/dummy/config/optional-features.json",
    "chars": 119,
    "preview": "{\n  \"application-template-wrapper\": false,\n  \"jquery-integration\": false,\n  \"template-only-glimmer-components\": true\n}\n"
  },
  {
    "path": "tests/dummy/config/targets.js",
    "chars": 157,
    "preview": "'use strict';\n\nconst browsers = [\n  'last 1 Chrome versions',\n  'last 1 Firefox versions',\n  'last 1 Safari versions',\n]"
  },
  {
    "path": "tests/dummy/public/crossdomain.xml",
    "chars": 585,
    "preview": "<?xml version=\"1.0\"?>\n<!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\">\n<cro"
  },
  {
    "path": "tests/dummy/public/robots.txt",
    "chars": 51,
    "preview": "# http://www.robotstxt.org\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "tests/index.html",
    "chars": 1331,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Ember Exam</title>\n    <meta name=\"description\" co"
  },
  {
    "path": "tests/test-helper.js",
    "chars": 362,
    "preview": "import Application from 'dummy/app';\nimport config from 'dummy/config/environment';\nimport { setApplication } from '@emb"
  },
  {
    "path": "tests/unit/async-iterator-test.js",
    "chars": 5572,
    "preview": "import AsyncIterator from 'ember-exam/test-support/-private/async-iterator';\nimport { module, test } from 'qunit';\n\nmodu"
  },
  {
    "path": "tests/unit/filter-test-modules-test.js",
    "chars": 4968,
    "preview": "import {\n  convertFilePathToModulePath,\n  filterTestModules,\n} from 'ember-exam/test-support/-private/filter-test-module"
  },
  {
    "path": "tests/unit/multiple-edge-cases-test.js",
    "chars": 195,
    "preview": "import { module, test } from 'qunit';\n\nmodule('#3: Module With Multiple Edge Case Tests', function () {\n  test('#1 RegEx"
  },
  {
    "path": "tests/unit/multiple-ember-tests-test.js",
    "chars": 709,
    "preview": "import { module, test } from 'qunit';\nimport { setupTest } from 'ember-qunit';\n\nmodule('#1: Module-For With Multiple Tes"
  },
  {
    "path": "tests/unit/multiple-tests-test.js",
    "chars": 638,
    "preview": "import { module, test } from 'qunit';\n\nmodule('#2: Module With Multiple Tests', function () {\n  test('#1', function (ass"
  },
  {
    "path": "tests/unit/test-loader-test.js",
    "chars": 6356,
    "preview": "import EmberExamTestLoader from 'ember-exam/test-support/-private/ember-exam-test-loader';\nimport { module, test } from "
  },
  {
    "path": "tests/unit/testem-output-test.js",
    "chars": 3453,
    "preview": "import * as TestemOutput from 'ember-exam/test-support/-private/patch-testem-output';\nimport { module, test } from 'quni"
  },
  {
    "path": "tests/unit/weight-test-modules-test.js",
    "chars": 1320,
    "preview": "import weightTestModules from 'ember-exam/test-support/-private/weight-test-modules';\nimport { module, test } from 'quni"
  }
]

About this extraction

This page contains the full source code of the trentmwillis/ember-exam GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 200 files (339.4 KB), approximately 95.3k tokens, and a symbol index with 157 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!