Data Strategy
ASecurityTest data strategy for the Playwright scaffold — Faker + Zod factories for dynamic happy-path data, static TS files (`.ts` with `as const` exports — never `.json`) for domain-specific curated invalid sets, and the universal type-mismatch arrays in test-data/static/util/invalid-values.ts. Use when creating or editing a data factory, adding a new invalid-values dataset, deciding between factory / static / inline / enum for a new test value, writing data-driven tests, or updating existing static...
- 181 stars
- 0 votes
- 0 copies
- 0 views
- Added September 22, 2026
Works with
Security analysis
100/100Pro scans all 3 files and shows the line behind each finding
npx -y skills add idavidov13/agentic-playwright --skill data-strategy --agent claude-codeAre you the author of Data Strategy?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/idavidov13-data-strategy)---
name: data-strategy
description: Test data strategy for the Playwright scaffold — Faker + Zod factories for dynamic happy-path data, static TS files (`.ts` with `as const` exports — never `.json`) for domain-specific curated invalid sets, and the universal type-mismatch arrays in test-data/static/util/invalid-values.ts. Use when creating or editing a data factory, adding a new invalid-values dataset, deciding between factory / static / inline / enum for a new test value, writing data-driven tests, or updating existing static data. For the API negative-testing patterns that consume this data see the api-testing skill (Phase 6, three-tier rule); for safe edits to existing static values see the refactor-values skill; for Zod 4 validator rules see the type-safety skill.
author: Ivan Davidov
---
# Data Strategy
This framework uses a **bifurcated data strategy**: static data for deterministic curated cases and dynamic factories for test isolation.
## Critical
- **Static data files are TypeScript only.** Every file under `test-data/static/**` is a `.ts` file that exports `as const` literal values. **NEVER** use `.json`.
- **Static data files may only export literal values.** No runtime imports (type-only imports are fine), no function definitions, no computed values, no Faker calls. Dynamic data belongs in factories, not in static files.
- **NEVER** hardcode test content strings (names, emails, todo text, product names, descriptions, etc.) in a spec file. Generate with a Faker factory.
- **NEVER** redefine universal type-mismatch arrays (`[123, true, null, undefined]`, etc.) inline. Import them from `test-data/static/util/invalid-values.ts`.
- **ALWAYS** validate factory output with `Schema.parse(...)` and return the Zod-inferred type.
- **NEVER** generate app-defined strings with Faker (error messages, button labels, page headers). Those live in `enums/` so they stay in sync with the application under test.
- **NEVER** store fixed expected values that are used in a single assertion in a static data file. Keep them inline in the test.
- **ALWAYS** follow the `refactor-values` skill before editing any existing static-data file or enum value — these edits cascade through assertions and data-driven loops.
- **NEVER** introduce magic numbers (timeouts, retry counts, limits) inline. Prefer web-first assertions; when a numeric value is unavoidable, route it through `playwright.config.ts` or an enum in `enums/`.
## File Locations
> **`{area}` is a placeholder.** Before creating or referencing any path below, run `ls test-data/static/` and `ls test-data/factories/` to discover the real subdirectory names in this repo (e.g., `front-office`, `back-office`) and use those instead.
| Type | Directory | Purpose |
| ------------------------ | ----------------------------- | ------------------------------------------------------------------------------------ |
| Universal invalid arrays | `test-data/static/util/` | Type-mismatch tuples reused by every negative test (`.ts`, `as const`) |
| Domain-specific static | `test-data/static/{area}/` | Curated invalid/boundary sets tied to the app's validation rules (`.ts`, `as const`) |
| Dynamic factories | `test-data/factories/{area}/` | Faker + Zod factories for unique, valid data per test run |
## Instructions
### Phase 1: Classify the value you need
Use this decision table before touching any file. Each row points to the canonical home.
| Value kind | Home |
| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| Happy-path dynamic data (emails, names, IDs, todo text, product names) | Factory in `test-data/factories/{area}/*.factory.ts` |
| Universal type-mismatch values for any `string`/`number`/etc. field | Import from `test-data/static/util/invalid-values.ts` (already exists) |
| Domain-specific curated invalid sets (invalid emails, weak passwords, forbidden categories, invalid locales) | `.ts` with `as const` exports under `test-data/static/{area}/` |
| Field-specific boundary / range values (e.g., out-of-range for a `1..5` number) | **Inline** in the spec file when used in exactly one place |
| App-defined strings (error messages, button labels, page titles) | `enums/{area}/*` (see the `enums` skill) — **not** Faker, **not** static |
| Fixed expected values used in a single assertion | **Inline** in the test — not a JSON file |
| Timeouts, retries, workers, test-suite tuning | `playwright.config.ts` (see the `config` skill) — not `test-data/` |
If the value fits none of the rows, stop and ask. Do not invent a new location.
### Phase 2: Create or extend a factory
Follow this when Phase 1 pointed at a factory. Factories use **Faker + Zod** for unique, valid data per test run — this prevents collisions in parallel execution.
```typescript
import { faker } from '@faker-js/faker';
import {
UserResponse,
UserResponseSchema,
} from '../../../fixtures/api/schemas/app/userSchema';
/**
* Generates a valid user object with randomized data.
* @param {Partial<UserResponse>} overrides - Optional overrides for specific fields.
* @returns {UserResponse} A valid user object matching the schema.
*/
export const generateUser = (
overrides?: Partial<UserResponse>
): UserResponse => {
const defaults: UserResponse = {
id: faker.string.uuid(),
email: faker.internet.email(),
token: faker.string.alphanumeric(64),
};
return UserResponseSchema.parse({ ...defaults, ...overrides });
};
```
> **Note:** Zod 4 uses top-level validators (`z.uuid()`, `z.email()`, etc.) in schemas, but the `.parse()` API and `z.infer<>` type inference work identically to v3. Factories require no code changes beyond updating their imported schemas.
Key requirements:
1. **Import Faker:** `import { faker } from '@faker-js/faker';`
2. **Import the Zod schema:** use the corresponding schema from `fixtures/api/schemas/{area}/`.
3. **Accept overrides:** `overrides?: Partial<SchemaType>` for customisation.
4. **Validate with schema:** always call `Schema.parse(...)` on the merged output.
5. **Export typed return:** return type must match the Zod-inferred type.
6. **JSDoc:** factories are action-like functions — include `@param` / `@returns`.
### Phase 3: Add static data
Follow this when Phase 1 pointed at static data. Pick the right tier first.
#### Tier 1 — Universal type-mismatch arrays (already centralised)
Do **not** create a new file. The universal arrays already live at `test-data/static/util/invalid-values.ts`:
```
INVALID_STRING_VALUES → [123, true, null, undefined]
INVALID_NUMBER_VALUES → ['string', '123', true, null, undefined]
INVALID_BOOLEAN_VALUES → ['yes', 1, 0, null, undefined]
INVALID_UUID_VALUES → ['not-a-uuid', '', 123, null, undefined]
INVALID_ENUM_VALUES → ['invalidValue', '', 123, null, undefined]
INVALID_ARRAY_VALUES → ['string', 123, null, undefined, {}]
INVALID_OBJECT_VALUES → ['string', 123, null, undefined, []]
```
Import and iterate in spec files; never redefine. See the `api-testing` skill (Phase 6) for the full consumer pattern.
#### Tier 2 — Domain-specific static data (`test-data/static/{area}/`)
Create a new `.ts` file here when you have a **curated** set of invalid or boundary values that are specific to the application's validation rules (invalid email formats, weak-password policy violations, forbidden enum values, locale strings, etc.).
Two canonical shapes — pick whichever fits the call site:
**Shape A — per-field invalid-value arrays** (the shape used by the scaffold's existing `test-data/static/app/invalidCredentials.ts`):
```typescript
export const INVALID_EMAILS = [
'',
'plaintext',
'missing-at-sign.com',
'missing@domain',
'@missing-local.com',
'double@@at.com',
] as const;
export const INVALID_PASSWORDS = ['', '123', 'short', 'NoNumber!'] as const;
```
Use Shape A when each invalid value targets **one** field at a time (classic per-field negative loop).
**Shape B — test-case objects with descriptions** for parametrised tests that combine multiple fields:
```typescript
export const INVALID_LOGIN_ATTEMPTS = [
{
description: 'valid email with wrong password',
email: 'test.user@example.com',
password: 'WrongPassword123!',
},
{
description: 'unknown email with any password',
email: 'nobody@example.com',
password: 'AnyPassword123!',
},
] as const;
```
Use Shape B when each row represents a **scenario** with multiple fields that must be tested together.
Always `.ts` with `as const`. Never `.json`. `.ts` gives type safety, `undefined`/`NaN` support, narrow literal autocomplete, JSDoc comments explaining rationale, and compiler-assisted refactors — all lost with JSON.
#### Tier 3 — Field-specific boundary values
These stay **inline** in the spec file. Do not create a static file for a single out-of-range set.
```typescript
const outOfRangeRatings = [-1, 0, 0.99, 5.01, 6, 1000];
```
Promote to Tier 2 only if the same boundary set is reused across 2+ fields or spec files.
### Phase 4: Consume the data in tests
**Factory (happy path or setup):**
```typescript
import {
generateUser,
generateLoginCredentials,
} from '../../../test-data/factories/app/user.factory';
const user = generateUser();
const creds = generateLoginCredentials();
const adminUser = generateUser({ email: 'admin@company.com' });
const customCreds = generateLoginCredentials({
password: 'SpecificPassword123!',
});
```
**Universal invalid arrays (Tier 1):**
```typescript
import { INVALID_STRING_VALUES } from '../../../test-data/static/util/invalid-values';
for (const invalidValue of INVALID_STRING_VALUES) {
test(
`should return 400 when name is ${JSON.stringify(invalidValue)}`,
{ tag: '@api' },
async ({ apiRequest }) => {
// ...
}
);
}
```
**Domain-specific static data (Tier 2)** and **field-specific boundary (Tier 3)** — full snippets in `references/examples.md`. Same `for...of` pattern as Tier 1 above; Tier 2 imports a named `as const` export from `test-data/static/{area}/`, Tier 3 declares the array inline.
### Phase 5: Editing existing static data
When you need to update a value in `test-data/static/`, the change can break existing test assertions and expectations that rely on the old value. **Always search for all consumers before editing.**
**Read the `refactor-values` skill** (`.claude/skills/refactor-values/SKILL.md`) before modifying any existing static-data file or enum value. It owns the impact-analysis + cascading-update workflow.
## No Magic Numbers
Do not hardcode timeouts, retry counts, or other tuning numbers inline. The preferred path is web-first assertions — Playwright waits for the condition automatically:
```typescript
// FORBIDDEN -- hard wait masks real timing issues
await page.waitForTimeout(5000);
// CORRECT -- web-first assertion, no magic number needed
await expect(appPage.successMessage).toBeVisible();
```
When a numeric tuning value is genuinely unavoidable:
- **Test-suite tuning** (default action timeout, expect timeout, retries, workers) → `playwright.config.ts` (see the `config` skill).
- **Domain-level limits** (max retries for a custom helper, page-size constants) → an enum in `enums/{area}/*` (see the `enums` skill).
- **Per-assertion override** (one assertion that legitimately needs more time) → pass inline on the assertion: `await expect(locator).toBeVisible({ timeout: 10000 })`. Justify why in a short comment.
## See Also
- **`api-testing`** skill — Phase 6 three-tier consumer pattern, per-field `for...of` loops, negative-validation coverage matrix.
- **`refactor-values`** skill — impact analysis and cascading update workflow for enum values and static data changes.
- **`type-safety`** skill — Zod 4 validator reference and `z.strictObject()` patterns used in factory validation.
- **`enums`** skill — canonical home for app-defined strings (error messages, labels, route paths).
- **`config`** skill — where timeouts / retries / workers belong (`playwright.config.ts`), not `test-data/`.
- **`debugging`** skill — when a factory output fails `Schema.parse(...)` or a data-driven loop fails one row out of many, classify and investigate before mutating the factory.
- **`references/examples.md`** — three worked walkthroughs (new factory, domain-specific invalid set, full negative-API coverage combining factory + universal + boundary) plus Tier 2 + Tier 3 consumption snippets.
- **`references/troubleshooting.md`** — common data-strategy pitfalls (parallel collisions, factory schema parse failure, inline invalid arrays, Faker-generated app strings, single-string JSON, edits to existing static data, magic-number timeouts).
Files in this skill
- SKILL.md
- references/examples.md
- references/troubleshooting.md
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.