QaWolf Tutorial: Browser Test Automation Without the Complexity
Browser automation has a reputation for being painful. You write tests, the UI changes, the tests break, and you spend more time fixing tests than building features. QaWolf was designed to change that calculus — making browser automation accessible without sacrificing power.
This tutorial walks through QaWolf from first install to running tests in CI.
What Is QaWolf?
QaWolf is a browser test automation tool that generates Playwright-based tests from user interactions. You record actions in the browser, QaWolf writes the test code, and you get a working test file you can edit and extend.
The key differentiator: QaWolf generates tests that are more resilient than those produced by naive recorders. It uses multiple element selectors and prioritizes stable attributes over brittle ones like CSS classes or XPath.
Installation
QaWolf runs as an npm package. Install it globally or as a dev dependency:
npm install -g qawolf
# or
npm install --save-dev qawolfQaWolf requires Node.js 14+ and Chromium (installed automatically via Playwright).
Recording Your First Test
Launch the QaWolf recorder:
qawolf create https://app.example.comThis opens a browser with a toolbar overlay. Every action you take — clicks, typing, form submissions — gets recorded. When you're done, close the browser and QaWolf writes a test file.
The generated test looks like this:
const { launch } = require("qawolf");
let browser;
let page;
beforeAll(async () => {
browser = await launch({ url: "https://app.example.com" });
page = browser.page;
});
afterAll(() => browser.close());
test("create account", async () => {
await page.click('[data-testid="signup-button"]');
await page.type('[data-testid="email-input"]', "user@example.com");
await page.type('[data-testid="password-input"]', "password123");
await page.click('[data-testid="submit-button"]');
await page.waitForSelector('[data-testid="dashboard"]');
});Notice QaWolf prefers data-testid attributes when available. If those don't exist, it falls back to text content, ARIA roles, and other stable selectors.
Adding Assertions
The recorder captures interactions but not assertions — you add those manually. QaWolf tests are plain JavaScript/TypeScript, so you use standard Playwright assertions:
test("create account", async () => {
await page.click('[data-testid="signup-button"]');
await page.type('[data-testid="email-input"]', "user@example.com");
await page.type('[data-testid="password-input"]', "password123");
await page.click('[data-testid="submit-button"]');
// Wait for redirect to dashboard
await page.waitForSelector('[data-testid="dashboard"]');
// Assert welcome message
const welcomeText = await page.textContent('[data-testid="welcome-heading"]');
expect(welcomeText).toContain("Welcome");
// Assert user email shown
const userEmail = await page.textContent('[data-testid="user-email"]');
expect(userEmail).toBe("user@example.com");
});Running Tests
Run all tests:
qawolf testRun a specific test file:
qawolf test tests/create_account.test.jsRun in headed mode (see the browser):
qawolf test --headedHandling Authentication
Testing authenticated flows is a common challenge. QaWolf handles this by letting you save browser state after login:
// Setup: log in once and save state
const { launch } = require("qawolf");
const { chromium } = require("playwright");
async function saveAuthState() {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("https://app.example.com/login");
await page.fill('[name="email"]', "test@example.com");
await page.fill('[name="password"]', "testpassword");
await page.click('[type="submit"]');
await page.waitForNavigation();
await context.storageState({ path: "auth-state.json" });
await browser.close();
}
saveAuthState();Then load the auth state in your tests:
beforeAll(async () => {
browser = await launch({
url: "https://app.example.com/dashboard",
storageState: "auth-state.json",
});
page = browser.page;
});This avoids logging in before every test, keeping your suite fast.
Selector Strategy
QaWolf's selector strategy is one of its strongest features. When recording, it captures multiple ways to find each element. When running tests, if the primary selector fails, it tries alternatives.
The priority order:
data-testidordata-qaattributes- ARIA roles and labels
- Text content
- CSS selectors
- XPath (last resort)
To improve test stability, add data-testid attributes to your important UI elements. QaWolf will use them automatically.
CI Integration
QaWolf works in any CI environment that supports Node.js. Here's a GitHub Actions example:
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18"
- run: npm ci
- run: npx playwright install chromium
- run: qawolf test
env:
BASE_URL: ${{ secrets.STAGING_URL }}For parallel execution across multiple environments:
strategy:
matrix:
browser: [chromium, firefox, webkit]
steps:
- run: qawolf test --browser ${{ matrix.browser }}Dealing with Dynamic Content
Modern apps load content asynchronously. QaWolf provides utilities for waiting:
// Wait for element to appear
await page.waitForSelector('[data-testid="results-list"]');
// Wait for network to settle
await page.waitForLoadState("networkidle");
// Wait for specific text
await page.waitForFunction(
() => document.body.textContent.includes("Loading complete")
);For polling behavior, use a loop:
async function waitForStatus(page, status, timeout = 30000) {
const start = Date.now();
while (Date.now() - start < timeout) {
const text = await page.textContent('[data-testid="status"]');
if (text === status) return;
await page.waitForTimeout(500);
}
throw new Error(`Timeout waiting for status: ${status}`);
}Comparing QaWolf to Other Tools
vs Cypress: Cypress runs tests inside the browser, giving it deep access to app internals. QaWolf runs externally via Playwright, which more closely mirrors real user behavior. QaWolf is simpler to set up; Cypress has a richer ecosystem.
vs Playwright directly: QaWolf sits on top of Playwright, adding a recorder and resilient selector logic. If you're comfortable writing Playwright tests from scratch, you may not need QaWolf. If you want to generate test scaffolding faster, QaWolf helps.
vs Selenium: QaWolf is significantly faster, more modern, and easier to maintain. The selector strategy is more robust than typical Selenium setups.
Scaling Your Test Suite
As your test suite grows, organize tests by feature area:
tests/
auth/
login.test.js
logout.test.js
password-reset.test.js
checkout/
add-to-cart.test.js
payment.test.js
profile/
edit-profile.test.jsRun subsets with glob patterns:
qawolf test tests/auth/**
qawolf test tests/checkout/**QaWolf and HelpMeTest
QaWolf handles creating tests; HelpMeTest adds continuous monitoring. Run your QaWolf tests on every deployment, and set up HelpMeTest health checks to verify critical user flows are working 24/7 — even between deploys.
The combination covers two different risk surfaces: code changes (QaWolf in CI) and infrastructure/third-party failures (HelpMeTest monitoring).
Common Mistakes
Relying on CSS classes: UI libraries frequently change class names. If your tests use .btn-primary or .MuiButton-root, expect breakage. Use data-testid instead.
No wait strategy: Clicking a button and immediately asserting the result often fails on slow connections or servers. Always wait for the expected state before asserting.
Testing too much in one test: Long tests are hard to debug. A test that covers signup → product search → checkout → payment is a nightmare when the payment step fails after 3 minutes of setup. Split into focused tests.
Ignoring flakiness: A test that passes 90% of the time is not a passing test. Investigate and fix the root cause rather than re-running.
Summary
QaWolf reduces the barrier to browser automation by generating test scaffolding from recordings and using resilient selector strategies. It's built on Playwright, so you get the full power of modern browser automation without the friction of writing everything from scratch.
For teams that want solid browser coverage without a dedicated QA engineer writing complex test infrastructure, QaWolf is worth evaluating.