ng-qcauto
Version:
Effortless, stable test IDs for Angular apps. Automatically injects data-qcauto attributes for QA and test automation teams without cluttering templates.
317 lines (234 loc) β’ 9.98 kB
Markdown
# π ng-qcauto
**Effortless, stable test IDs for Angular apps, controlled by testers β not code.**
[](https://www.npmjs.com/package/ng-qcauto)
[](https://opensource.org/licenses/MIT)
## π 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`.
### β¨ Key Features
- π **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 (optional)
- π§βπ€βπ§ **Tester-friendly** β configuration lives in `localStorage`, manageable via modal
- π **Version-based migration** β automatic cleanup when upgrading package versions
- π¦ **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
## π How IDs Are Generated
IDs follow this pattern: **`qc_{route}_{tag}_{identifier}`**
**Examples:**
- `/dashboard` route β `qc_dashboard_button_abc123`
- `/users/profile` route β `qc_users_profile_input_xyz789`
- Root `/` route β `qc_home_form_loginForm`
**Identifier Logic:**
1. If element has `data-qc-key` β used directly (`qc_dashboard_li_42`)
2. Else if element has `id` β reused (`qc_dashboard_form_loginForm`)
3. Else β deterministic hash (`qc_dashboard_button_1k9d2`)
IDs remain **stable across reloads** as long as **route** and **structure** don't change.
## π Angular Version Support
| Angular Version | Supported | Setup Type |
|-----------------|-----------|------------|
| **v15+** | β
Yes | Standalone bootstrap (`bootstrapApplication`) |
| **v14 and below** | β
Yes | Module bootstrap (`bootstrapModule(AppModule)`) |
## π¦ Installation
```bash
npm install ng-qcauto
```
## π Usage
### πΉ Angular v14 and Below (Modules)
For module-bootstrapped apps:
```ts
// 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:
```ts
// 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**.
### 1οΈβ£ Open Configuration Modal
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:
```js
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();
```
### 2οΈβ£ Example Template
```html
<!-- 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>
```
### 3οΈβ£ After Render
```html
<!-- 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>
```
### 4οΈβ£ Copy QC IDs (NEW!)
When **Click-to-Copy** is enabled:
1. Elements with QC IDs show a **pointer cursor** π
2. **Right-click** any element to copy its QC ID
3. A toast notification appears: `β qc_dashboard_button_1k9d2`
4. Paste anywhere: `Ctrl+V`
## π 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-clickToCopy` β Boolean string (`'true'` or `'false'`) for right-click copy mode
### β¨οΈ Keyboard Shortcut
- **Ctrl+Q** (Windows/Linux) or **Cmd+Q** (Mac) β Opens configuration modal
- Press again to close modal
### π Version-Based Configuration
`ng-qcauto` automatically manages configuration updates when you upgrade the package:
- **Automatic cleanup**: When the package version changes, all old `qcAuto-*` localStorage items are cleared
- **Fresh defaults**: After cleanup, new default configuration is applied
- **No manual migration**: Users don't need to manually clear old settings
- **Version tracking**: Current version is stored in `localStorage` as `qcAuto-version`
This ensures a clean state after updates and prevents conflicts from old configuration formats.
### π Reset Config
To manually reset configuration to defaults:
```js
// Clear all qcAuto settings (will trigger fresh defaults on reload)
for (let i = localStorage.length - 1; i >= 0; i--) {
const key = localStorage.key(i);
if (key && key.startsWith('qcAuto-')) {
localStorage.removeItem(key);
}
}
location.reload();
```
Or reset to specific values:
```js
localStorage.setItem('qcAuto-tags', JSON.stringify(['button', 'input', 'a']));
localStorage.setItem('qcAuto-classes', JSON.stringify([]));
localStorage.setItem('qcAuto-ids', JSON.stringify([]));
localStorage.setItem('qcAuto-clickToCopy', 'false');
location.reload();
```
## π§ͺ Testing Examples
### Cypress
```js
// 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:
```js
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();
```
### Playwright
```ts
// Direct selector
await page.locator('[data-qcauto="qc_dashboard_li_42"]').click();
// Route-based pattern
await page.locator('[data-qcauto^="qc_checkout"]').count();
```
### Selenium
```java
// Java
WebElement element = driver.findElement(
By.cssSelector("[data-qcauto='qc_dashboard_button_submit']"));
element.click();
```
## π‘ Test-Only Mode
To disable in production, guard init with environment flags:
```ts
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**: `MutationObserver` handles **only new nodes**
- **Optimized**:
- Skips already tagged nodes
- Filters by config before hashing
- Uses `data-qc-key` for list stability
Overhead is negligible compared to Angular rendering.
## π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## π Changelog
See [CHANGELOG.md](https://github.com/KareemMostafa77/qc-auto-package/blob/master/projects/ng-qcauto/CHANGELOG.md) for version history.
## π Links
- **GitHub Repository**: [qc-auto-package](https://github.com/KareemMostafa77/qc-auto-package)
- **npm Package**: [ng-qcauto](https://www.npmjs.com/package/ng-qcauto)
- **Demo**: [GitHub Pages](https://kareemmostafa77.github.io/qc-auto-package/)
- **Issues**: [Report a bug](https://github.com/KareemMostafa77/qc-auto-package/issues)
## π License
MIT Β© 2025 β Kareem Mostafa
**Made with β€οΈ for QA Engineers and Test Automation Teams**