Package Exports
- ng-qcauto
- ng-qcauto/package.json
Readme
π ng-qcauto v2.0
Effortless, stable test IDs for Angular apps, controlled by testers β not code.
π Overview
ng-qcauto is an Angular utility library that automatically injects stable data-qcauto attributes into DOM elements.
It empowers QA and test automation teams by providing deterministic, human-friendly selectors without requiring developers to clutter templates with data-testid.
βββββββββββββββββββββββββββββββ
β qc_dashboard_button_xyz [π] β β Overlay appears on hover
βββββββββββββββββββββββββββββββ
ββββββββββββ
β Submit β β Your button (with dashed outline)
ββββββββββββFeatures:
- π― Hover to Reveal: IDs only show when you hover over elements
- π Copy Button: Click to copy the ID to clipboard
- β Toast Notification: Confirms successful copy
- ποΈ Toggle On/Off: Control visibility from the config modal
- π¨ Non-intrusive: Overlay doesn't disrupt your UI layout
- π Visual Outline: Dashed green border helps identify tracked elementsIDs for Angular apps, controlled by testers β not code.**
-### 3. After Render
<!-- On /dashboard route -->
<button data-qcauto="qc_dashboard_button_1k9d2">Save</button>
<button class="btn-primary" data-qcauto="qc_dashboard_button_btn-primary">Submit</button>
<form id="loginForm" data-qcauto="qc_dashboard_form_loginForm"> ... </form>
<!-- With data-qc-key -->
<li data-qc-key="42" data-qcauto="qc_dashboard_li_42">John Doe</li>Note: IDs now include the route path (e.g., qc_dashboard_*, qc_users_profile_*)
π How IDs Are Generated (v2.0)
IDs follow this pattern: qc_{route}_{tag}_{identifier}
- Route Path: Current URL path (e.g.,
/users/profileβusers_profile) - Tag: Element's tag name (e.g.,
button,input) - Identifier:
- If element has
data-qc-keyβ used directly (qc_home_li_42) - Else if element has
idβ reused (qc_dashboard_form_loginForm) - Else β deterministic hash (
qc_users_button_1k9d2)
- If element has
IDs remain stable across reloads as long as route and structure don't change.
ποΈ Visual ID Indicators (NEW!)
When enabled, you'll see green badges above tracked elements showing their data-qcauto value:
βββββββββββββββββββββββββββββββ
β qc_dashboard_button_xyz [π] β β Visual indicator with copy button
βββββββββββββββββββββββββββββββ
ββββββββββββ
β Submit β β Your button
ββββββββββββFeatures:
- π Copy Button: Click to copy the ID to clipboard
- β Toast Notification: Confirms successful copy
- οΏ½οΈ Toggle On/Off: Control visibility from the config modal
- π¨ Non-intrusive: Styled to not interfere with your UI
β¨οΈ Keyboard Shortcuts
Ctrl+Q+C(orCmd+Q+Con Mac): Open configuration modaliewng-qcautois an Angular utility library that automatically injects stabledata-qcautoattributes into DOM elements.
It empowers QA and test automation teams by providing deterministic, human-friendly selectors without requiring developers to clutter templates with data-testid.
β¨ Key Features
- π Automatic injection β works globally, no directives or template edits.
- π― Configurable β track elements by tag, class, or ID.
- π Stable IDs β deterministic hashes, with support for
data-qc-keyin lists. - π£οΈ Route-based IDs β IDs now include current route path for better stability.
- β¨οΈ Visual Configuration Modal β Press
Ctrl+Q+Cto open config modal (no DevTools needed!). - ποΈ Visual ID Indicators β See IDs directly in the UI with copy functionality.
- π§βπ€βπ§ Tester-friendly β configuration lives in
localStorage, editable via modal. - π¦ Test-only mode β enable in dev/staging, disable in prod.
- β‘ Lightweight β observer-based, minimal performance impact.
- π Angular v14 and below + v15+ support β works in both module-based and standalone bootstraps.
π Angular Version Support
| Angular Version | Supported | Setup Type |
|---|---|---|
| v15+ | β Yes | Standalone bootstrap (bootstrapApplication) |
| v14 and below | β Yes | Module bootstrap (bootstrapModule(AppModule)) |
π¦ Installation
npm install ng-qcautoπ Usage
πΉ Angular v14 and Below (Modules)
For module-bootstrapped apps:
// main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { initQcAutoGlobal } from 'ng-qcauto';
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(() => initQcAutoGlobal()) // init after Angular bootstraps
.catch(err => console.error(err));πΉ Angular v15+ (Standalone)
For standalone-bootstrapped apps:
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { initQcAutoGlobal } from 'ng-qcauto';
bootstrapApplication(AppComponent).then(() => {
initQcAutoGlobal(); // init after bootstrap
});π§βπ» Tester Workflow
ng-qcauto reads its configuration from localStorage.
π― Method 1: Visual Config Modal (NEW in v2.0!)
- Press
Ctrl+Q+Canywhere in the app - The configuration modal will appear
- Enter your targets:
- Tags:
button, input, a - Classes:
btn-primary, form-control - IDs:
saveBtn, loginForm
- Tags:
- Toggle "Show Visual ID Indicators" to see IDs in the UI
- Click "Save & Reload"
π οΈ Method 2: Add Targets in DevTools (Classic)
localStorage.setItem('qcAuto-tags', JSON.stringify(['button','input','a']));
localStorage.setItem('qcAuto-classes', JSON.stringify(['btn-primary']));
localStorage.setItem('qcAuto-ids', JSON.stringify(['saveBtn']));
localStorage.setItem('qcAuto-showVisualIds', 'true'); // NEW: Show visual indicators
location.reload();2. Example Template
<button>Save</button>
<button class="btn-primary">Submit</button>
<form id="loginForm"> ... </form>
<ul>
<li *ngFor="let user of users" [attr.data-qc-key]="user.id">
{{ user.name }}
</li>
</ul>3. After Render
<button data-qcauto="qc_button_1k9d2">Save</button>
<button class="btn-primary" data-qcauto="qc_button_btn-primary">Submit</button>
<form id="loginForm" data-qcauto="qc_form_loginForm"> ... </form>
<li data-qc-key="42" data-qcauto="qc_li_42">John Doe</li>π How IDs Are Generated
- If element has
data-qc-keyβ used directly (qc_li_42). - Else if element has
idβ reused (qc_form_loginForm). - Else β deterministic hash (
qc_button_1k9d2).
IDs remain stable across reloads as long as structure doesnβt change.
βοΈ Configuration Reference
LocalStorage Keys
qcAuto-tagsβ Array of tag names (e.g.['button','input'])qcAuto-classesβ Array of class names (e.g.['btn-primary'])qcAuto-idsβ Array of element IDs (e.g.['loginForm'])qcAuto-showVisualIdsβ Boolean string ('true'or'false') for visual indicators (NEW!)
Reset Config
localStorage.setItem('qcAuto-tags', JSON.stringify([]));
localStorage.setItem('qcAuto-classes', JSON.stringify([]));
localStorage.setItem('qcAuto-ids', JSON.stringify([]));
localStorage.setItem('qcAuto-showVisualIds', 'false');
location.reload();π§ͺ Testing Examples
Cypress
// Using full ID with route
cy.get('[data-qcauto="qc_dashboard_form_loginForm"]').should('be.visible');
// Using partial match for route independence
cy.get('[data-qcauto$="form_loginForm"]').click();
// Find all buttons on a specific route
cy.get('[data-qcauto^="qc_dashboard_button"]').should('have.length', 3);Custom command:
Cypress.Commands.add('qc', (selector, matchType = 'exact') => {
if (matchType === 'exact') {
return cy.get(`[data-qcauto="${selector}"]`);
} else if (matchType === 'ends') {
return cy.get(`[data-qcauto$="${selector}"]`);
} else if (matchType === 'contains') {
return cy.get(`[data-qcauto*="${selector}"]`);
}
});
// Usage
cy.qc('qc_dashboard_form_loginForm').submit();
cy.qc('form_loginForm', 'ends').submit(); // Route-independentPlaywright
// Full ID
await page.locator('[data-qcauto="qc_users_li_42"]').click();
// Partial match (route-independent)
await page.locator('[data-qcauto$="li_42"]').click();
// Find by route prefix
const dashboardButtons = await page.locator('[data-qcauto^="qc_dashboard_"]').all();Selenium
// Java example
WebElement loginForm = driver.findElement(
By.cssSelector("[data-qcauto='qc_home_form_loginForm']")
);
// Route-independent
WebElement loginForm = driver.findElement(
By.cssSelector("[data-qcauto$='form_loginForm']")
);π‘ Test-Only Mode
To disable in production, guard init with environment flags:
import { environment } from './environments/environment';
import { initQcAutoGlobal } from 'ng-qcauto';
bootstrapApplication(AppComponent).then(() => {
if (!environment.production) {
initQcAutoGlobal();
}
});β‘ Performance Notes
- Startup: one-time DOM scan (few ms even for large apps).
- Runtime:
MutationObserverhandles only new nodes. - Optimized:
- Skips already tagged nodes.
- Filters by config before hashing.
- Uses
data-qc-keyfor list stability.
Overhead is negligible compared to Angular rendering.
οΏ½ Migration from v1.x to v2.0
Breaking Changes
ID Format Change: IDs now include route path prefix.
Before (v1.x):
<button data-qcauto="qc_button_1k9d2">Submit</button>After (v2.0):
<button data-qcauto="qc_dashboard_button_1k9d2">Submit</button>Migration Strategy
Option 1: Update Test Selectors (Recommended)
Update your test selectors to use the new format:
// Old
cy.get('[data-qcauto="qc_button_xyz"]')
// New
cy.get('[data-qcauto="qc_dashboard_button_xyz"]')Option 2: Use Partial Matching (Quick Fix)
Use suffix matching to ignore route prefix:
// Works with both v1.x and v2.0
cy.get('[data-qcauto$="button_xyz"]')New Features to Adopt
- Visual Config Modal: Use
Ctrl+Q+Cinstead of DevTools - Visual ID Indicators: Enable to easily discover IDs
- Route-based Stability: IDs are now more stable per route
π Troubleshooting
Modal doesn't open with Ctrl+Q+C
- Make sure the page has loaded completely
- Check browser console for errors
- Try
Cmd+Q+Con Mac
Visual indicators not showing
- Open config modal (
Ctrl+Q+C) - Check "Show Visual ID Indicators"
- Click "Save & Reload"
IDs changing unexpectedly
- IDs now include route path
- Ensure you're on the same route when testing
- Use
data-qc-keyfor dynamic list items
Elements not getting IDs
- Check your configuration (tags, classes, ids)
- Open config modal to verify settings
- Check browser console for initialization logs
οΏ½π License
MIT Β© 2025 β Kareem Mostafa