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.tsimport{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.tsimport{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><buttonclass="btn-primary">Submit</button><formid="loginForm"> ... </form><ul><li*ngFor="let user of users" [attr.data-qc-key]="user.id">{{user.name }} </li></ul><!-- On /dashboard route --><buttondata-qcauto="qc_dashboard_button_1k9d2">Save</button><buttonclass="btn-primary" data-qcauto="qc_dashboard_button_btn-primary">Submit</button><formid="loginForm" data-qcauto="qc_dashboard_form_loginForm"> ... </form><lidata-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 IDcy.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 prefixcy.get('[data-qcauto^="qc_users_profile"]').should('exist');Custom command:
Cypress.Commands.add('qc',selector=>cy.get(`[data-qcauto="${selector}"]`));// Usagecy.qc('qc_dashboard_form_loginForm').submit();cy.qc('qc_users_profile_button_save').click();// Direct selectorawaitpage.locator('[data-qcauto="qc_dashboard_li_42"]').click();// Route-based patternawaitpage.locator('[data-qcauto^="qc_checkout"]').count();// JavaWebElementelement = 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