Instruction file imported from dreamquality/agentic-pw (
.github/instructions/helpers.instructions.md). Copyright stays with the author.
Helpers
Critical
- Helpers are plain functions. No Playwright fixture lifecycle — no
use(), nobase.extend. If you need setup →use(data)→ teardown, it's a fixture, not a helper (see thefixturesskill). - App-specific logic lives in
helpers/{area}/(e.g.helpers/app/). Generic utilities live inhelpers/util/. - ALWAYS add JSDoc with
@paramand@returnson every exported helper. - ALWAYS specify explicit return types (
Promise<void>,string, etc.). No implicitany. - NEVER hardcode URLs, credentials, or tokens. Read env-driven values from
process.env.*(see theconfigskill). Useenums/{area}/*for endpoint paths and storage-state paths. - ALWAYS validate API responses inside helpers with the mandatory pattern
expect(SchemaName.parse(body)).toBeTruthy();— identical to theapi-testingCritical rule. - Function naming: camelCase verbs (
createAppStorageState,setUserAccessToken,formatDate,parseCurrency). - Do not promote a helper to a helper fixture unless the same setup/teardown is copy-pasted across 3+ spec files and needs guaranteed lifecycle (see the
api-testingskill, Phase 8 rule of thumb). - Mutating
process.envfrom a helper is a narrow exception, not a general pattern. It is acceptable only for auth-bootstrap helpers that publish a token (e.g. writingprocess.env.ACCESS_TOKENinside a login helper). Do not copy the env-mutation pattern into other helpers — return values through the function signature instead.
File Locations
{area}is a placeholder. Before creating or referencing any path below, runls helpers/to discover the real subdirectory names in this repo (e.g.,front-office,back-office) and use those instead.
| Type | Directory | Purpose | Scaffold example |
|---|---|---|---|
| App helpers | helpers/{area}/ |
App-specific helper functions (auth bootstrap, storage state, seeding) | helpers/app/createStorageState.ts |
| Utility helpers | helpers/util/ |
Generic utility functions reusable across apps/projects | helpers/util/util.ts |
Instructions
Phase 1: Classify what you're adding
Use this table. The correct criterion is "does this need the Playwright fixture lifecycle?" — not "is this used in setup or in tests?". A plain utility like formatDate is a helper even though it's called from inside tests.
| Symptom | Home |
|---|---|
| Pure function — no setup/teardown lifecycle needed | Helper in helpers/{area}/ or helpers/util/ |
Needs page / request context via DI, or owns setup → use(data) → teardown around each test |
Fixture (see the fixtures skill; for lifecycle API helpers see api-testing Phase 8) |
| Encapsulates locators and user interactions on a specific page | Page object (see the page-objects skill) |
| One-off API call inside a single test | Call apiRequest directly in the test — neither helper nor fixture |
| Reusable happy-path data generation | Factory under test-data/factories/{area}/ (see the data-strategy skill) |
If the need fits none of these rows, stop and ask. Do not invent a new location.
Phase 2: Pick the home — app-specific or utility
helpers/{area}/— logic that only makes sense for the app under test: authentication flows, storage state creation, data seeding, app-specific request composition.helpers/util/— logic that's reusable across apps or projects: date formatting, string manipulation, retry logic, parsing utilities.
Prefer extending an existing file (createStorageState.ts, util.ts) over creating a new one when the function belongs to the same domain. Create a new file only when the domain is genuinely new.
Phase 3: Define the function signature, types, and JSDoc
Every exported helper must declare:
- An explicit return type (
Promise<void>,string,UserResponse, etc.). No implicitany. - Named parameters with explicit types.
- A JSDoc block with a description,
@paramfor every argument,@returns, and — when useful — an@exampleblock.
Pattern:
/**
* Creates and saves the browser storage state after successful login.
* @returns {Promise<void>} Resolves when storage state is saved.
*/
export async function createAppStorageState(): Promise<void> {
// implementation
}
Phase 4: Read env-driven values from process.env.*
Helpers never hardcode URLs, credentials, or tokens. Read from process.env.* and use enums for endpoint paths / storage-state paths:
import { ApiEndpoints, StorageStatePaths } from '../../enums/app/app';
// CORRECT
baseUrl: process.env.API_URL,
url: ApiEndpoints.LOGIN,
body: { email: process.env.APP_EMAIL, password: process.env.APP_PASSWORD },
await context.storageState({ path: StorageStatePaths.APP });
// FORBIDDEN
baseUrl: 'https://api.example.com',
url: '/api/users/login',
See the config skill for sources of truth and the enums skill for paths.
Phase 5: If the helper makes API calls, validate with Zod
The mandatory API response-validation pattern from api-testing applies equally inside helpers:
const { status, body } = await apiRequest<UserResponse>({
method: 'POST',
url: ApiEndpoints.LOGIN,
baseUrl: process.env.API_URL,
body: { email: process.env.APP_EMAIL, password: process.env.APP_PASSWORD },
});
expect(status).toBe(200);
expect(UserResponseSchema.parse(body)).toBeTruthy();
Rules:
- The generic
<UserResponse>gives compile-time safety onbody. expect(SchemaName.parse(body)).toBeTruthy();is the exact assertion — notschema.parse(body)alone.- If the response can legitimately be
null(e.g., a 204 DELETE), assertexpect(body).toBeNull()instead.
Phase 6: Consume the helper
Call the helper from the right context — helpers have no lifecycle of their own, so where you call them matters:
- Auth bootstrap (
tests/{area}/auth.setup.ts) — app-login helpers run once before the main test suite to produce storage state and/or tokens. The scaffold ships a demo implementation inhelpers/app/createStorageState.ts; adapt or replace it to match your app's auth flow. - Inside a test /
beforeEach/afterEach— utility helpers freely, API helpers when they do self-contained work. - Inside a fixture — a fixture can call a helper as part of its setup/teardown. The fixture owns the lifecycle; the helper stays stateless.
Auth-bootstrap helpers that publish a token via process.env.* (e.g. the demo setUserAccessToken in the scaffold) are the one place env mutation is acceptable. Every other helper must return its results through the function signature.
Examples
Example 1: Add a utility helper
User says: "Add a helper that parses a currency string like $1,234.56 into a number so tests can assert on cart totals."
Actions:
- Phase 1 — Pure function, no lifecycle → helper.
- Phase 2 — Generic, reusable across apps →
helpers/util/util.ts(extend the existing file). - Phase 3 — Signature:
export function parseCurrency(value: string): number, with JSDoc +@param+@returns. - Phase 6 — Consume inside tests:
expect(parseCurrency(await cart.totalText())).toBe(1234.56);.
Example 2: Add an app-specific seeding helper
User says: "Wrap the factory + API create call so setup scripts can seed a product."
Actions:
- Phase 1 — Plain function (takes
apiRequestas a parameter; does not own lifecycle) → helper. If it needed guaranteed per-test teardown, it would be a fixture instead. - Phase 2 — App-specific →
helpers/{area}/(e.g.helpers/app/seedProduct.ts). - Phase 3 — Signature:
export async function seedProduct(apiRequest: ApiRequestFn, overrides?: Partial<Product>): Promise<Product>. - Phase 4 — Read base URL / token from
process.env.*, endpoint fromApiEndpoints.PRODUCTS. - Phase 5 — Validate the response:
expect(status).toBe(201);+expect(ProductSchema.parse(body)).toBeTruthy();. - Phase 6 — Call from
tests/{area}/auth.setup.tsor inside a fixture's setup block.
Example 3: Counterexample — this is a fixture, not a helper
User says: "I want to write a helper that creates a test user before each test and deletes it after — same API I wrote for seedProduct."
Actions:
- Phase 1 — "Creates before, deletes after" = Playwright lifecycle → fixture, not a helper.
- Stop. Route to the
fixturesskill + theapi-testingskill (Phase 8) and promote only if the setup/teardown is reused across 3+ spec files. - If it's used in only 1–2 files, keep it inline in
beforeEach/afterEachusingapiRequestdirectly.
Troubleshooting
My helper returns undefined for process.env.* values.
Cause: Missing env variable in the active env/.env.${ENVIRONMENT} file.
Fix: Confirm the key exists there; update env/.env.example if you added a new variable. See the config skill.
I want the helper to set up a resource and tear it down around each test.
Fix: That's a fixture, not a helper. Route to the fixtures skill; if the setup/teardown is API-driven see the api-testing skill (Phase 8).
process.env.ACCESS_TOKEN (or whichever token env var your auth helper writes) is undefined in API tests.
Cause: The auth-setup test didn't run, or your login helper failed silently before writing the env var.
Fix: Confirm Playwright's project dependencies are wired so tests/{area}/auth.setup.ts runs before the main suite. Re-run the setup and watch for schema-parse failures inside the auth helper (a schema mismatch means the login response shape changed).
My API helper calls schema.parse(body) without wrapping in expect(...).toBeTruthy().
Cause: Old pattern.
Fix: Replace with expect(SchemaName.parse(body)).toBeTruthy(); — identical to the api-testing Critical rule.
I want to mutate process.env inside a new helper for convenience.
Fix: Don't. The setUserAccessToken env mutation is a sanctioned auth-bootstrap exception. For any other case, pass values through return types or factory overrides — env mutation hides state and breaks parallel isolation.
I'm about to promote a one-off helper to a helper fixture because "it feels reusable".
Fix: Promote only when the same setup/teardown is copy-pasted across 3+ spec files with a lifecycle need. Otherwise keep it as a helper or an inline apiRequest call (see the api-testing skill, Phase 8 rule of thumb).
TypeScript complains that my helper has an implicit any return type.
Fix: Add an explicit return type (Promise<void>, Promise<UserResponse>, string, etc.) — Critical rule.
See Also
fixturesskill — Playwright fixtures withuse()lifecycle (setup / yield / teardown); the sibling category to helpers.api-testingskill — mandatoryexpect(Schema.parse(body)).toBeTruthy();pattern and the Phase 8 rule of thumb for promoting to a helper fixture.configskill — env variable conventions (process.env.*), whereAPP_URL/API_URL/APP_EMAIL/APP_PASSWORDlive.enumsskill —ApiEndpoints.*for endpoint paths andStorageStatePaths.*for storage-state file paths.data-strategyskill — Faker + Zod factories used inside seeding helpers; three-tier rule for static invalid data.debuggingskill —process.env.ACCESS_TOKENundefined, auth-bootstrap failures, and other helper-driven test failures.