Use this file to discover all available pages before exploring further.
Hooks let you run setup and teardown logic at defined points in the test lifecycle without repeating that code inside every it() block. KT Testing Suite Core supports four hook types that cover both per-test and per-suite lifecycles. All four are registered by calling them inside a describe() callback, and all four throw Error: <hook>() must be called inside a describe() if invoked at module scope.
Per-test hooks run around every it() in the suite where they are registered. They are the right tool for resetting a counter, restoring a variable, or any other state that must be fresh for each test.
// src/tests/beforeAfterEach.test.tsimport { describe, it, expect, beforeEach, afterEach } from 'kt-testing-suite-core';describe('beforeEach and afterEach hooks', () => { let counter = 0; beforeEach(() => { counter += 1; // Increment counter before each test }); afterEach(() => { counter = 0; // Reset counter after each test }); it('should run beforeEach before the test', () => { expect(counter).toBe(1); // Counter should be incremented by beforeEach }); it('should reset counter after each test using afterEach', () => { expect(counter).toBe(1); // Counter should be reset and incremented again });});
The second test sees counter === 1 because afterEach reset it to 0 after the first test, and then beforeEach incremented it to 1 again before the second test ran.
Suite-level hooks run exactly once per suite — beforeAll fires before the first test starts, and afterAll fires after the last test finishes. They are ideal for expensive setup that would be wasteful to repeat, such as initialising a shared data structure.
// src/tests/beforeAfterAll.test.tsimport { describe, it, expect, beforeAll, afterAll, beforeEach } from 'kt-testing-suite-core';describe('beforeAll and afterAll hooks', () => { let suiteSetup = false; let testCounter = 0; beforeAll(() => { suiteSetup = true; }); afterAll(() => { suiteSetup = false; }); beforeEach(() => { testCounter++; }); it('should run beforeAll once before all tests', () => { expect(suiteSetup).toBe(true); expect(testCounter).toBe(1); }); it('should not run beforeAll again for second test', () => { expect(suiteSetup).toBe(true); // Still true from beforeAll expect(testCounter).toBe(2); // beforeEach ran again });});
suiteSetup stays true for both tests because beforeAll ran only once. testCounter, by contrast, increments for every test because it is driven by beforeEach.
beforeAll and afterAll are the canonical place to manage shared resources. Because the resource is created once, tests can safely observe mutations from previous tests within the same suite.
Because tests within a suite share the same resource instance, the order in which it() blocks run matters when one test mutates the resource. Avoid depending on mutation order across suites.
When beforeEach and afterEach are registered in both a parent and a child describe, the runner builds a chain by walking up the parent references on the suite. The execution direction differs between setup and teardown hooks:
Hook
Execution order
beforeEach
Outer (parent) → inner (child)
afterEach
Inner (child) → outer (parent)
beforeAll
Runs independently per suite, no inheritance
afterAll
Runs independently per suite, no inheritance
The following example from the test suite shows this clearly. The outer beforeEach adds 1, then the nested beforeEach adds 10, so the test inside the nested suite sees counter === 11:
beforeAll and afterAll do not chain across parent/child suites. Each suite’s beforeAll runs independently before that suite’s own tests start, regardless of what the parent suite registered.
The following example shows that variables set by a parent beforeAll are still accessible inside a nested suite’s tests (because they are in the same closure scope), and each suite’s own beforeAll/afterAll pair runs independently:
describe('beforeAll and afterAll hooks', () => { let suiteSetup = false; let testCounter = 0; beforeAll(() => { suiteSetup = true; }); afterAll(() => { suiteSetup = false; }); beforeEach(() => { testCounter++; }); describe('nested suite', () => { let nestedSetup = false; beforeAll(() => { nestedSetup = true; }); afterAll(() => { nestedSetup = false; }); it('should inherit parent beforeAll and run nested beforeAll', () => { expect(suiteSetup).toBe(true); // From parent closure expect(nestedSetup).toBe(true); // From nested beforeAll }); });});
If a hook function throws, the runner catches the error, prints a warning to the ExtendScript console via $.writeln, and continues execution. Tests are not skipped because of a failing hook — the runner reports them normally.
⚠️ Error in beforeAll: Setup failed
This behaviour is demonstrated by the error-handling suite:
describe('beforeAll and afterAll error handling', () => { let setupRan = false; beforeAll(() => { setupRan = true; throw new Error('Setup failed'); // Warning is printed; tests still run }); it('should run test even if beforeAll throws', () => { expect(setupRan).toBe(true); // beforeAll executed despite the error expect(true).toBe(true); // Test runs normally });});
If a shared resource fails to initialise inside beforeAll, guard your test bodies with a null-check rather than relying on the test to throw — this produces a clearer failure message than an unexpected TypeError.