svelte-qa-ids
v1.0.2
Published
Automatically inject stable data-qa-id attributes into Svelte components for testing and automation
Maintainers
Readme
svelte-qa-ids
Automatically inject stable
data-qa-idattributes into Svelte components for testing and automation
Features
- Automated Injection: Scans Svelte files and programmatically inserts
data-qa-idattributes - Stable IDs: Generates consistent, human-readable IDs based on component structure
- Idempotent: Removes old IDs before adding new ones for clean updates
- Svelte 5 Compatible: Uses
svelte.preprocessfor proper Svelte 5 syntax parsing - Smart Exclusion: Skips icon libraries (e.g.,
lucide-svelte) and handles parsing errors gracefully - CLI & API: Use as a command-line tool or programmatically in your build process
Installation
npm install -D svelte-qa-ids
# or
bun add -D svelte-qa-idsCLI Usage
# Process all Svelte files in a directory
svelte-qa-ids src/
# Dry run to see what would change
svelte-qa-ids src/ --dry-run
# Process a single file
svelte-qa-ids src/routes/+page.svelte
# Verbose output
svelte-qa-ids src/ --verbose
# Custom component prefix length (2-10 characters)
svelte-qa-ids src/ --component 4
svelte-qa-ids src/ -c 2Programmatic Usage
Effect-based API (Primary)
The library is built with Effect-ts for type-safe error handling and composability:
import { inject } from 'svelte-qa-ids';
import { Effect } from 'effect';
// Process a directory
const program = inject('src/', {
verbose: true,
prefixLength: 4,
});
// Run the Effect
const results = await Effect.runPromise(program);
// Handle errors with Effect
const safeProgram = inject('src/', {
prefixLength: 4,
}).pipe(
Effect.catchAll((error) =>
Effect.succeed([
{
file: 'src/',
success: false,
error: error.message,
},
])
)
);
const results = await Effect.runPromise(safeProgram);Promise-based API (Convenience)
For simpler use cases, a Promise-based wrapper is provided:
import { injectPromise } from 'svelte-qa-ids';
// Process a directory
const results = await injectPromise('src/', {
verbose: true,
});
// Process a single file
await injectPromise('src/routes/+page.svelte');
// With custom options
await injectPromise('src/', {
dryRun: true,
prefixLength: 4,
abbreviations: {
'custom-button': 'cb',
},
skipComponents: ['MyIcon', 'AnotherIcon'],
});data-qa-id Generation Rules
Component Prefix
The component prefix is generated from the file path and can be customized with the prefixLength option (default: 3, range: 2-10).
For pages (src/routes/...):
- Uses first
prefixLengthletters of the route directory - Example with default (prefixLength=3):
src/routes/analytics/+page.svelte→ana - Example with prefixLength=2:
src/routes/analytics/+page.svelte→an - Example with prefixLength=4:
src/routes/analytics/+page.svelte→anal - Root page (
src/routes/+page.svelte) →rt
For components (src/lib/...):
- Uses first
prefixLengthletters of each word in filename - Example with default (prefixLength=3):
upgrade-banner.svelte→upg-ban - Example with prefixLength=2:
upgrade-banner.svelte→up-ba - Example with prefixLength=4:
question-counter.svelte→quest-count
Tag Abbreviations
Common tags are abbreviated for conciseness:
| Tag | Abbreviation |
|-----|--------------|
| div | d |
| button | btn |
| input | in |
| label | lbl |
| span | sp |
| section | sec |
| form | f |
Examples
<!-- Before: src/routes/splash/+page.svelte -->
<div class="container">
<h1>Welcome</h1>
<button>Continue</button>
</div>
<!-- After (default prefixLength=3) -->
<div class="container" data-qa-id="spl-d">
<h1 data-qa-id="spl-d-h1">Welcome</h1>
<button data-qa-id="spl-d-btn">Continue</button>
</div>
<!-- After (prefixLength=2) -->
<div class="container" data-qa-id="sp-d">
<h1 data-qa-id="sp-d-h1">Welcome</h1>
<button data-qa-id="sp-d-btn">Continue</button>
</div>
<!-- After (prefixLength=4) -->
<div class="container" data-qa-id="spla-d">
<h1 data-qa-id="spla-d-h1">Welcome</h1>
<button data-qa-id="spla-d-btn">Continue</button>
</div>Component example (src/lib/question-counter.svelte):
<!-- Before -->
<div class="counter">
<button id="increment">+</button>
<span>0</span>
</div>
<!-- After (default prefixLength=3) -->
<div class="counter" data-qa-id="que-d">
<button id="increment" data-qa-id="que-d-btn">+</button>
<span data-qa-id="que-d-sp">0</span>
</div>
<!-- After (prefixLength=4) -->
<div class="counter" data-qa-id="quest-d">
<button id="increment" data-qa-id="quest-d-btn">+</button>
<span data-qa-id="quest-d-sp">0</span>
</div>Integration with Build Process
SvelteKit
Add to your package.json:
{
"scripts": {
"prebuild": "svelte-qa-ids src/"
}
}Vite
// vite.config.ts
import { inject } from 'svelte-qa-ids';
export default {
plugins: [
{
name: 'inject-qa-ids',
async buildStart() {
await inject('src/');
}
}
]
};Configuration Examples
Custom Prefix Length
Control how many characters from the component name are used for the ID prefix:
import { inject } from 'svelte-qa-ids';
// Use 2-character prefixes (more concise)
await inject('src/', { prefixLength: 2 });
// question-counter.svelte → qu-btn
// Use 4-character prefixes (more descriptive)
await inject('src/', { prefixLength: 4 });
// question-counter.svelte → quest-btn
// Default is 3 characters
await inject('src/');
// question-counter.svelte → que-btnCLI Prefix Length
# Short prefix (2 chars)
svelte-qa-ids src/ --component 2
# Long prefix (5 chars)
svelte-qa-ids src/ --component 5Custom Abbreviations
Override default tag abbreviations:
await inject('src/', {
abbreviations: {
'custom-button': 'cb',
'data-table': 'dt',
'user-card': 'uc',
},
});Skip Specific Components
Exclude certain components from processing:
await inject('src/', {
skipComponents: ['Icon', 'MyIcon', 'ThirdPartyIcon'],
});API Reference
inject(path, options?)
Injects data-qa-id attributes into Svelte files. This is the primary Effect-based API.
Parameters:
path(string) - Path to a file or directoryoptions(Options, optional) - Configuration options
Returns: Effect<Result[], Error>
Use Effect.runPromise() to execute:
import { inject } from 'svelte-qa-ids';
import { Effect } from 'effect';
const results = await Effect.runPromise(inject('src/'));injectPromise(path, options?)
Promise-based convenience wrapper for inject().
Parameters:
path(string) - Path to a file or directoryoptions(Options, optional) - Configuration options
Returns: Promise<Result[]>
Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| abbreviations | Record<string, string> | built-in | Custom tag abbreviations |
| prefixLength | number | 3 | Characters from component name for prefix (2-10) |
| prefixGenerator | (filePath: string, prefixLength?: number) => string | built-in | Custom prefix function |
| skipComponents | Set<string> \| string[] | lucide-svelte | Components to skip |
| include | string[] | undefined | Include files matching glob patterns |
| exclude | string[] | [] | Exclude files matching glob patterns |
| dryRun | boolean | false | Don't modify files |
| verbose | boolean | false | Detailed logging |
| preserveOnError | boolean | true | Keep original content on parse errors |
Result:
interface Result {
file: string;
success: boolean;
error?: string;
injectedCount?: number;
skipped?: boolean;
}Known Limitations
Svelte 5 {#let} Blocks
Files containing {#let} blocks nested within {#each} loops may not be parseable by the standalone Svelte parser. These files are automatically skipped with a warning, preserving their original content.
This is a limitation of the Svelte compiler in standalone mode and affects files like:
{#each items as item}
{#let value = compute(item)}
<!-- content -->
{/let}
{/each}License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
