Effortless, stable test IDs for Angular apps, controlled by testers β not code.
IDs follow this pattern: qc_{route}_{tag}_{identifier}
Examples:
/dashboardroute βqc_dashboard_button_abc123/users/profileroute βqc_users_profile_input_xyz789- Root
/route βqc_home_form_loginForm
Identifier Logic:
- If element has
data-qc-keyβ used directly (qc_dashboard_li_42) - Else if element has
idβ reused (qc_dashboard_form_loginForm) - Else β deterministic hash (
qc_dashboard_button_1k9d2)
IDs remain stable across reloads as long as route and structure don't change. 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.
- π Automatic injection β works globally, no directives or template edits.
- π― Configurable β track elements by tag, class, or ID.
- π Route-based stable IDs β IDs include route path for better organization.
- β¨οΈ Ctrl+Q Modal β Easy configuration interface without DevTools.
- π±οΈ Right-click to Copy β Quickly copy QC IDs during testing.
- π§βπ€βπ§ Tester-friendly β configuration lives in
localStorage, manageable 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 | Supported | Setup Type |
|---|---|---|
| v15+ | β Yes | Standalone bootstrap (bootstrapApplication) |
| v14 and below | β Yes | Module bootstrap (bootstrapModule(AppModule)) |
npm install ng-qcautoFor 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));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
});ng-qcauto reads its configuration from localStorage.
Press Ctrl+Q (or Cmd+Q on Mac) anywhere in the app to open the configuration modal:
βββββββββββββββββββββββββββββββββββββββ
β QC Auto Configuration β β
βββββββββββββββββββββββββββββββββββββββ€
β Tags: button, input, a β
β Classes: btn-primary β
β IDs: saveBtn β
β β Enable Click-to-Copy QC IDs β
β β
β [Save & Reload] [Cancel] β
βββββββββββββββββββββββββββββββββββββββ
OR use DevTools Console:
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-clickToCopy', 'true');
location.reload();<!-- On /dashboard route -->
<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><!-- 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>
<li data-qc-key="42" data-qcauto="qc_dashboard_li_42">John Doe</li>When Click-to-Copy is enabled:
- Elements with QC IDs show a pointer cursor π
- Right-click any element to copy its QC ID
- A toast notification appears:
β qc_dashboard_button_1k9d2 - Paste anywhere:
Ctrl+V
- 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.
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-clickToCopyβ Boolean string ('true'or'false') for right-click copy mode
- Ctrl+Q (Windows/Linux) or Cmd+Q (Mac) β Opens configuration modal
- Press again to close modal
localStorage.setItem('qcAuto-tags', JSON.stringify([]));
localStorage.setItem('qcAuto-classes', JSON.stringify([]));
localStorage.setItem('qcAuto-ids', JSON.stringify([]));
localStorage.setItem('qcAuto-clickToCopy', 'false');
location.reload();// Full ID
cy.get('[data-qcauto="qc_dashboard_form_loginForm"]').should('be.visible');
// Pattern matching (all buttons on dashboard)
cy.get('[data-qcauto^="qc_dashboard_button"]').click();
// By route prefix
cy.get('[data-qcauto^="qc_users_profile"]').should('exist');Custom command:
Cypress.Commands.add('qc', selector =>
cy.get(`[data-qcauto="${selector}"]`)
);
// Usage
cy.qc('qc_dashboard_form_loginForm').submit();
cy.qc('qc_users_profile_button_save').click();// Direct selector
await page.locator('[data-qcauto="qc_dashboard_li_42"]').click();
// Route-based pattern
await page.locator('[data-qcauto^="qc_checkout"]').count();// Java
WebElement element = driver.findElement(
By.cssSelector("[data-qcauto='qc_dashboard_button_submit']"));
element.click();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();
}
});- 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.
MIT Β© 2025 β Kareem Mostafa