A WebdriverIO project demonstrating randomized test distribution across two BrowserStack devices, with dynamic suite selection and two selectable run modes.
This project uses CommonJS (require/module.exports) throughout —
there is no "type": "module" in package.json, so it works with plain
node/wdio invocations without any ESM-related warnings.
npm install
cp .env.example .env
# Edit .env and add your BrowserStack username + access keyResolves all spec files for the requested suite, shuffles them with a Fisher-Yates algorithm, and splits them 5/5 across two devices (Samsung Galaxy S23 on Chrome, iPhone 14 on Safari) — all sessions run concurrently under one build.
# Run the default suite (test/specs/**/*.js)
npm test
# Run a specific named suite
npm test -- --suite=A
npm test -- --suite=B
npm test -- --suite=C
npm test -- --suite=priorityFtResolves all spec files for the requested suite, shuffles them, then assigns them using a Device 1 priority queue:
- Device 1 (Samsung Galaxy S23) slots are filled first in every round.
- Device 2 (iPhone 14) only receives specs when all Device 1 slots are taken.
- Any "remainder" specs always go to Device 1 — never Device 2.
- A single wdio process is spawned with both devices as capabilities, so all sessions appear under one build on the BrowserStack dashboard.
| Total specs | Device 1 | Device 2 | Notes |
|---|---|---|---|
| 3 | 3 | 0 | All fit in D1 |
| 7 | 5 | 2 | D1 full first round, D2 gets remainder |
| 10 | 5 | 5 | One full round each |
| 13 | 8 | 5 | D1 gets extra round remainder |
| 20 | 10 | 10 | Two full rounds each |
# Run the default suite
npm run test:preferred
# Run a specific named suite
npm run test:preferred -- --suite=A
npm run test:preferred -- --suite=B # 13 specs — shows Device 1 preference skew (D1=8, D2=5)
npm run test:preferred -- --suite=C
npm run test:preferred -- --suite=priorityFtAll suites are defined in one place — wdio.conf.js at the project root.
run-tests.js reads them via require('./wdio.conf.js').config.suites —
no separate file required.
// inside wdio.conf.js → exports.config.suites
const suites = {
A: ['test/suites/suite-a/**/*.js'], // 20 specs
B: ['test/suites/suite-b/**/*.js'], // 13 specs — shows D1 preference (D1=8, D2=5)
C: ['test/suites/suite-c/**/*.js'], // 10 specs
priorityFt: [
'tests/ui/e2e/patient_onboarding/query-param-cookies*.js',
'tests/ui/e2e/patient_onboarding/generated_tests/*-st.spec.js'
],
default: ['test/specs/**/*.js'] // 10 specs
};To add a new suite, add one entry to the suites object in wdio.conf.js — no other file needs changing.
wdio-browserstack-randomtest-commonjs/
├── package.json
├── wdio.conf.js # Single source of truth: suite defs + run mode 1 (even split)
├── wdio.preferred.conf.js # Single-process config for run mode 2 (D1 priority, both devices)
├── run-wdio.js # CLI interceptor: routes --preferred to run-tests.js
├── run-tests.js # Priority queue orchestrator: assigns specs to devices
├── .env.example
└── test/
├── specs/ # Default suite (test1.js … test10.js)
└── suites/
├── suite-a/ # Suite A (suite-a-test1.js … suite-a-test20.js)
├── suite-b/ # Suite B (suite-b-test1.js … suite-b-test13.js) — 13 specs, D1 preference demo
└── suite-c/ # Suite C (suite-c-test1.js … suite-c-test10.js)
run-wdio.js parses --suite=<name> and --preferred from process.argv.
The suite name is forwarded as the SUITE environment variable. For Mode 1
it spawns wdio directly; for Mode 2 it always delegates to node run-tests.js
so the Device 1 priority assignment is applied — wdio is never spawned directly
in preferred mode, as that would bypass the orchestrator.
This approach avoids WDIO 9 intercepting --suite as its own native flag.
Both modes spawn a single wdio process — all sessions appear under one build.
Mode 2 passes the two spec lists to wdio.preferred.conf.js via
DEVICE1_SPECS and DEVICE2_SPECS JSON environment variables, and injects
BROWSERSTACK_BUILD_NAME so both capability blocks share the same build name.
One process = one build on the BrowserStack dashboard, no race condition.
run-tests.js walks the shuffled spec list and assigns specs round-by-round:
the first DEVICE1_LIMIT specs in each round go to Device 1, the next
DEVICE2_LIMIT go to Device 2. Any remainder at the end of the last round
always goes to Device 1 — so Device 1 is always preferred.
Both modes apply a Fisher-Yates shuffle to the fully resolved glob file list before any assignment. Re-run the same command and the device split changes every time.
If --suite is omitted or the name doesn't match a key in wdio.conf.js,
both modes fall back to default (test/specs/**/*.js).
- The sample specs hit BrowserStack's public demo app (https://bstackdemo.com/) — no app of your own required. Replace the spec files with your real tests.
- To scale beyond 5 parallel sessions per device, bump
wdio:maxInstancesinwdio.conf.jsandDEVICE1_LIMIT/DEVICE2_LIMITinrun-tests.js. - To add a third device, add another capability block to
wdio.preferred.conf.js, extend the assignment logic inrun-tests.js, and add the capability towdio.conf.jsfor Mode 1. - The
globpackage resolves wildcard suite patterns and is listed underdependenciesinpackage.json— installed automatically vianpm install.
The following changes were made to fix npm run test:preferred and ensure
all sessions group under one BrowserStack build:
Removed the external suite definitions file. All suite patterns now live
directly in wdio.conf.js under exports.config.suites, eliminating a
separate file and making wdio.conf.js the single source of truth.
- Moved the
suitesobject inline (previously insuites.js). - Exposed it via
exports.config.suitessorun-tests.jscan read it withrequire('./wdio.conf.js').config.suites— no duplication. - Added Suite A (20 specs), expanded Suite B to 13 specs (Device 1 preference demo), kept Suite C at 10 specs.
- Remains the primary config for Mode 1 (even 5/5 split).
- Fixed the
--preferredrouting: now always delegates tonode run-tests.jsvia theSUITEenvironment variable. - Previously it could spawn
wdiodirectly for preferred mode, bypassing the orchestrator and breaking the priority queue.
- Reads suite definitions from
wdio.conf.js(single source of truth). - Generates one
SHARED_BUILD_NAMEtimestamp at startup. - Single wdio process: passes both spec lists to
wdio.preferred.conf.jsviaDEVICE1_SPECS/DEVICE2_SPECSJSON env vars — one process means one BrowserStack build, no race condition. - Priority queue assignment: Device 1 slots fill first in every round; any remainder always goes to Device 1 (never Device 2).
- Retains Fisher-Yates shuffle for randomized spec order each run.
- Removed all static array slicing, shuffling, and glob resolution.
- Now a barebones single-process config: reads
DEVICE1_SPECSandDEVICE2_SPECSfrom env, assigns them to per-capabilityspecsarrays. - Both capabilities share
BROWSERSTACK_BUILD_NAME→ one build on the BrowserStack Automate dashboard. maxInstances: 5per device (up from 1); concurrency is controlled by the capability-levelwdio:maxInstances.
- Added
suite-a-test11.jsthroughsuite-a-test20.js(20 specs total).
- Added
suite-b-test11.jsthroughsuite-b-test13.js(13 specs total). - 13 specs with 5+5 device limits produces D1=8, D2=5 — the clearest
demonstration of Device 1 preference. Run with:
npm run test:preferred -- --suite=B