docs/extend/scout/global-setup-hook.md
Use the global setup and teardown hooks to run code once before any tests start and/or after all workers finish — even with multiple workers. They are most useful for parallel suites, where shared data/setup must exist before workers begin and suite-wide state may need to be reset afterwards. Both hooks are also supported by non-parallel test suites.
Use globalSetupHook(...) in global.setup.ts to run code once before any tests start.
Common uses:
esArchiverapiServiceskbnClientFor suite-wide state that's shared across spec files (data ingested in global.setup.ts, feature-flag overrides, global UI settings), use the optional global teardown hook instead.
:::::::::::{stepper}
::::::::::{step} Turn it on in your config
Set runGlobalSetup: true in your Playwright config:
import { createPlaywrightConfig } from '@kbn/scout';
export default createPlaywrightConfig({
testDir: './parallel_tests',
workers: 2,
runGlobalSetup: true,
});
::::::::::
::::::::::{step} Create global.setup.ts
Add global.setup.ts inside the testDir folder. Scout will discover and run it automatically.
test/scout/ui/
└── parallel_tests/
├── global.setup.ts
└── some_suite.spec.ts
::::::::::
::::::::::{step} Write setup code
Example: load an ES archive once:
import { globalSetupHook } from '@kbn/scout';
globalSetupHook('Load test data', async ({ esArchiver, log }) => {
log.info('[setup] loading ES archive (only if needed)...');
await esArchiver.loadIfNeeded('x-pack/platform/test/fixtures/es_archives/ml/farequote');
});
::::::{warning}
The global setup hook only has access to worker-scoped fixtures. It cannot use test-scoped fixtures like page, browserAuth, or pageObjects.
::::::
::::::::::
::::::::::{step} Run tests
Run tests as usual via Run Scout tests. The global setup hook runs first — check console logs to verify it ran successfully.
::::::::::::
When runGlobalSetup: true is set, Scout also wires up an optional globalTeardownHook that runs once after all workers finish, even when tests fail. Use it to reset shared cluster/Kibana state that would otherwise leak into other Scout configs running against the same Kibana/ES — for example, dropping legacy or hand-indexed data ingested by global.setup.ts, reverting feature-flag overrides, or unsetting global UI settings.
Add global.teardown.ts next to global.setup.ts in your testDir and call globalTeardownHook(...). No extra config flag — runGlobalSetup: true is already enough. If the file is absent, the project is silently skipped.
test/scout/ui/
└── parallel_tests/
├── global.setup.ts
├── global.teardown.ts
└── some_suite.spec.ts
import { globalTeardownHook } from '@kbn/scout';
globalTeardownHook(
'Reset shared Kibana state',
async ({ esClient, kbnClient, apiServices, log }) => {
log.info('[teardown] resetting shared state');
// Drop legacy or hand-indexed data the suite created.
await esClient.indices.delete({ index: 'apm-8.0.0-*', ignore_unavailable: true });
await esClient.indices
.deleteDataStream({ name: 'logs-my_dataset-*' })
.catch(() => undefined); // tolerate missing data stream on a clean cluster
// Revert any global UI settings / advanced settings the suite set.
await kbnClient.uiSettings.unset('discover:searchOnPageLoad');
await kbnClient.uiSettings.updateGlobal({ hideAnnouncements: false });
// Revert feature-flag overrides flipped via apiServices.core.settings(...).
await apiServices.core.settings({
'feature_flags.overrides': { 'discover.isEsqlDefault': false },
});
}
);
globalTeardownHook exposes a deliberately narrower fixture surface than globalSetupHook:
log, config, kbnUrlesClient, kbnClientapiServices (incl. apiServices.core.settings(...) for feature-flag rollbacks)::::::{warning}
esArchiver is intentionally not exposed in globalTeardownHook. Scout's esArchiver fixture only ever exposed loadIfNeeded — by design, there is no archive-driven unload. Removing archives that way is slow and unnecessary: leftover indexes in the cluster don't break Scout tests (setup is idempotent via loadIfNeeded), so there's no reason to spend time deleting them at the end of every run.
For state that does need resetting (server-wide feature-flag overrides, global UI settings, hand-indexed data that breaks redirect or navigation logic for other configs sharing the cluster), use targeted esClient.indices.delete, esClient.indices.deleteDataStream, esClient.deleteByQuery, kbnClient.uiSettings.unset, or apiServices.core.settings.
::::::
globalSetupHook (which is idempotent via esArchiver.loadIfNeeded).afterEach/afterAll. The global teardown is for state shared across the whole suite — typically state seeded by global.setup.ts itself, or anything that other Scout configs sharing the cluster would inherit.page, browserAuth, or pageObjects.teardown field, so the hook runs after the setup project AND every project depending on it has finished, including on failure. Use this to your advantage (always-clean cluster) but write the teardown defensively — pass ignore_unavailable: true on Elasticsearch deletes and swallow expected 404s on optional resources — so a partial setup doesn't break cleanup.