QA Playbook | arbory-www
A comprehensive guide for quality assurance on the Arbory Digital AEM Edge Delivery Services site. Covers manual testing, automated checks, AI-assisted QA workflows, and PR review processes.
TABLE OF CONTENTS
- Environment Setup
- Block Inventory & Test Matrix
- Manual QA Checklist
- Automated Testing
- Linting & Code Quality
- Browser Testing with Playwright
- Performance & GitHub Checks
- PR Review Process
- AI-Assisted QA (Using .skills in VS Code)
- Design System Verification
- Cross-Origin & External Dependencies
- Troubleshooting
1. ENVIRONMENT SETUP
Prerequisites
- Node.js v20+ (check with: node --version)
- AEM CLI installed globally (npm install -g @adobe/aem-cli)
- Git with access to the arbory-digital-inc/arbory-www repo
<br>
First-Time Setup
Run in terminal:
git clone <repo-url>cd arbory-wwwnpm install
<br>
Start Local Dev Server
Run in terminal:
aem up
This starts the local proxy at http://localhost:3000. All manual and browser testing runs against this server.
<br>
VS Code Extensions (Recommended)
- ESLint - real-time JS lint feedback
- Stylelint - real-time CSS lint feedback
- Copilot / Cline / Continue / Windsurf - AI coding assistant with access to .skills/
2. BLOCK INVENTORY & TEST MATRIX
The site has 34 blocks currently. Every block should be tested when global styles or core scripts change. When a PR modifies a specific block, test that block and any blocks it interacts with.
<br>
What to Test Per Block
For each block, verify:
- Renders correctly on desktop (900px and above)
- Renders correctly on tablet (600–899px)
- Renders correctly on mobile (below 600px)
- All variants render as expected
- Light mode appearance
- Dark mode appearance (if in overlay-dark section)
- Interactive behavior works (clicks, hovers, animations, expand/collapse)
- Images load and are optimized (picture element with srcset)
- Links point to correct destinations
- Icons render via SVG inlining (not broken images)
- No console errors in browser DevTools
- Accessibility basics - keyboard navigable, focus visible, screen reader friendly
3. MANUAL QA CHECKLIST
Use this checklist for every QA pass - whether reviewing a PR or doing a full site sweep.
<br>
Page-Level Checks
- Page loads without errors (check DevTools Console)
- First image has
fetchPriority="high"and noloading="lazy" - Sections alternate backgrounds correctly (odd = shaded, even = background)
--card-bgcontrasts with parent section background- Overlay sections (
overlay-dark / overlay-light) force correct color-scheme - Metadata renders in the head (meta tags for title, description, og:image)
- Fonts load correctly (heading + body families)
- Scroll behavior is smooth and no layout shifts occur
<br>
Responsive Breakpoints
Test at these widths:
- Mobile: 375px (iPhone SE / small phone)
- Tablet portrait: 768px
- Tablet landscape: 1024px
- Desktop: 1440px
- Wide desktop: 1920px
<br>
Dark Mode / Light Mode
The site uses light-dark() CSS functions with these tokens:
--color-background: light-dark(#F7F8FA, midnight)--color-shaded: light-dark(#E8EAF0, charcoal)
Toggle your OS or browser dark mode preference and verify:
- All text remains readable
- Card backgrounds contrast with section backgrounds
- Icons inherit
currentColorcorrectly - No hard-coded colors that break in the opposite mode
4. AUTOMATED TESTING
Test Runner
The project uses Web Test Runner (@web/test-runner) with Chai assertions and Sinon for mocking.
Commands:
npm test- Run all testsnpm run test:watch- Watch mode (re-runs on changes)npm run test:file -- ./test/scripts/scripts.test.js- Run a specific test file
<br>
Existing Tests
test/scripts/scripts.test.js
Covers: Core loadPage() - verifies first image gets fetchPriority: high and loading removed
test/scripts/dapreview.test.js
Covers: DA preview mode - verifies ?dapreview=true loads tools/da/da.js
<br>
Writing New Tests
Tests use @esm-bundle/chai for assertions. Example:
import { expect } from '@esm-bundle/chai';
describe('myFunction', () => {
it('should do something', () => {
expect(result).to.equal(expected);
});
});
<br>
When to add tests:
- New utility functions in scripts/utils/
- Data processing / transformation logic
- API integration helpers
- Complex conditional logic
<br>
Don't write unit tests for:
- Block decoration (DOM transforms) — validate these in the browser
- CSS styling — validate visually
- Simple getters/setters
5. LINTING & CODE QUALITY
Commands
npm run lint- Run all linting (JS + CSS)npm run lint:js- JS onlynpm run lint:css- CSS only
<br>
Rules Overview
JavaScript (ESLint with @adobe/eslint-config-helix):
- ES6+ features required
- .js extension required in imports
- no-await-in-loop disabled
- no-param-reassign allows property mutation
- No license headers required
<br>
CSS (Stylelint with stylelint-config-standard):
- Block CSS must be scoped: selectors start with main .{block-name}
- Use CSS custom properties (not hard-coded colors/fonts)
- Mobile-first responsive: use @media (width >= 600px) range syntax
- Low specificity: 2–3 selector levels max
- No !important
<br>
Lint Must Pass Before Merge
Non-negotiable. Every PR must have clean lint output. Run npm run lint locally before pushing.
6. BROWSER TESTING WITH PLAYWRIGHT
For visual validation, responsive checks, and interactive behavior testing. These are throwaway tests - used to validate, capture screenshots, then discard.
<br>
Setup (One-Time)
npm install --save-dev playwright
npx playwright install chromium
<br>
Example: Test a Block at Multiple Viewports
// test-my-block.js (DO NOT COMMIT)
import {
chromium
} from 'playwright';
import {
mkdir
} from 'fs/promises';
async function test() {
await mkdir('./test/tmp/screenshots', {
recursive: true
});
const browser = await chromium.launch({
headless: false
});
const page = await browser.newPage();
const viewports = [
{
name: 'mobile',
width: 375,
height: 812
},
{
name: 'tablet',
width: 768,
height: 1024
},
{
name: 'desktop',
width: 1440,
height: 900
},
];
for (const vp of viewports) {
await page.setViewportSize({
width: vp.width,
height: vp.height
});
await page.goto('http://localhost:3000/path/to/test-page');
await page.waitForSelector('.my-block');
await page.screenshot({
path: './test/tmp/screenshots/my-block-' + vp.name + '.png',
fullPage: true,
});
}
await browser.close();
}
test().catch(console.error);
<br>
Run it:
node test-my-block.js
<br>
Tips
- Always wait for block decoration: await page.waitForSelector('.my-block')
- Test interactions: clicks, hovers, form fills, accordion expand/collapse
- Screenshot specific elements: await page.locator('.hero').screenshot(...)
- Include screenshots in PRs to help reviewers
- Keep throwaway scripts in test/tmp/ (gitignored)
7. PERFORMANCE & GITHUB CHECKS
Automatic CI Checks
When you push a branch and create a PR:
1. Linting runs automatically
2. PSI (PageSpeed Insights) runs if you include a test link in the PR description
<br>
PR Description Must Include a Test Link
Add this to your PR description:
## Testing
Preview: https://branch-name--arbory-www--arbory-digital-inc.aem.page/path/to/test
<br>
Monitor Checks
gh pr checks --watch - Watch checks in real-time
gh pr checks - Check once
<br>
Common Performance Issues
Too much eager JS
Fix: Move non-LCP code to loadLazy() or loadDelayed()
CSS blocking render
Fix: Move non-critical styles to lazy-styles.css
Large images
Fix: Use optimized formats, lazy load below-fold images
Third-party scripts in eager phase
Fix: Defer to loadDelayed()