@keber/qa-framework 1.10.0 → 1.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -18
- package/README.md +1 -1
- package/docs/folder-structure-guide.md +6 -0
- package/integrations/ado-powershell/pipelines/azure-pipeline-qa.yml +92 -25
- package/integrations/playwright/README.md +124 -0
- package/package.json +3 -4
- package/qa-framework.config.json +1 -1
- package/scripts/init.js +53 -38
- package/scripts/upgrade.js +105 -10
- package/templates/automation-scaffold/.env.example +20 -1
- package/templates/automation-scaffold/fixtures/base.ts +108 -0
- package/templates/automation-scaffold/global-setup.ts +119 -29
- package/templates/automation-scaffold/playwright.config.ts +24 -0
- package/templates/defect-report.md +1 -1
- package/templates/integration-scaffold/.env.example +40 -0
- package/templates/integration-scaffold/README.md +77 -0
- package/templates/integration-scaffold/global-setup.ts +99 -0
- package/templates/integration-scaffold/package.json +15 -0
- package/templates/integration-scaffold/playwright.config.ts +78 -0
- package/templates/integration-scaffold/tests/example.spec.ts +43 -0
- package/templates/memory/ci-pipeline-findings.md +36 -0
- package/templates/memory/data-volatility-strategies.md +70 -0
- package/templates/memory/e2e-stabilization-patterns.md +54 -0
- package/templates/qa-framework.instructions.md +9 -1
- package/templates/qa-readme.md +21 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------
|
|
2
|
+
# Integration test environment variables
|
|
3
|
+
# -----------------------------------------------------------------------
|
|
4
|
+
# Copy this file to .env and fill in real values.
|
|
5
|
+
# NEVER commit .env to source control.
|
|
6
|
+
# -----------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
# -----------------------------------------------------------------------
|
|
9
|
+
# Application under test
|
|
10
|
+
# -----------------------------------------------------------------------
|
|
11
|
+
QA_BASE_URL=https://your-app.qa.example.com
|
|
12
|
+
|
|
13
|
+
# -----------------------------------------------------------------------
|
|
14
|
+
# Auth - choose one strategy
|
|
15
|
+
# -----------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
# Strategy 1: Static bearer token (preferred for pure API services)
|
|
18
|
+
# QA_API_TOKEN=your-api-token-here
|
|
19
|
+
|
|
20
|
+
# Strategy 2: Browser login (for apps with cookie/session auth)
|
|
21
|
+
# QA_USER_EMAIL=qa-user@example.com
|
|
22
|
+
# QA_USER_PASSWORD=CHANGE_ME
|
|
23
|
+
|
|
24
|
+
# -----------------------------------------------------------------------
|
|
25
|
+
# Login form selectors (used only when QA_API_TOKEN is not set)
|
|
26
|
+
# -----------------------------------------------------------------------
|
|
27
|
+
# QA_LOGIN_PATH=/login
|
|
28
|
+
# QA_LOGIN_EMAIL_SELECTOR=input[type="email"]
|
|
29
|
+
# QA_LOGIN_PASSWORD_SELECTOR=input[type="password"]
|
|
30
|
+
# QA_LOGIN_SUBMIT_SELECTOR=button[type="submit"]
|
|
31
|
+
# QA_LOGIN_SUCCESS_SELECTOR=.dashboard
|
|
32
|
+
|
|
33
|
+
# -----------------------------------------------------------------------
|
|
34
|
+
# Azure DevOps integration (optional)
|
|
35
|
+
# -----------------------------------------------------------------------
|
|
36
|
+
# ADO_ORG=your-org
|
|
37
|
+
# ADO_PROJECT=YourProject
|
|
38
|
+
# ADO_PLAN_ID=00000
|
|
39
|
+
# ADO_PAT=your-pat-here
|
|
40
|
+
# SYSTEM_ACCESSTOKEN= # Set automatically in Azure Pipelines
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# API Integration Tests
|
|
2
|
+
|
|
3
|
+
Playwright-based API test project for `qa/07-automation/integration/`.
|
|
4
|
+
|
|
5
|
+
Tests run without a browser. All tests use Playwright's `APIRequestContext`
|
|
6
|
+
to call HTTP endpoints directly. Auth state is established once by
|
|
7
|
+
`global-setup.ts` and reused across the test run.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Node.js 18+
|
|
12
|
+
- `@playwright/test` (installed via `npm install` in this directory)
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd qa/07-automation/integration
|
|
18
|
+
npm install
|
|
19
|
+
cp .env.example .env
|
|
20
|
+
# Edit .env and fill in QA_BASE_URL and auth credentials
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Add the following to your root `.gitignore`:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
qa/07-automation/integration/.env
|
|
27
|
+
qa/07-automation/integration/.auth/
|
|
28
|
+
qa/07-automation/integration/test-results/
|
|
29
|
+
qa/07-automation/integration/playwright-report/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Environment variables
|
|
33
|
+
|
|
34
|
+
| Variable | Required | Description |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `QA_BASE_URL` | Yes | Base URL of the API under test |
|
|
37
|
+
| `QA_API_TOKEN` | One of | Static bearer token for token-based APIs |
|
|
38
|
+
| `QA_USER_EMAIL` | One of | Email/username for browser-login APIs |
|
|
39
|
+
| `QA_USER_PASSWORD` | One of | Password for browser-login APIs |
|
|
40
|
+
|
|
41
|
+
See `.env.example` for all supported variables.
|
|
42
|
+
|
|
43
|
+
## Running tests
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# All tests
|
|
47
|
+
npm test
|
|
48
|
+
|
|
49
|
+
# CI mode (retries enabled)
|
|
50
|
+
npm run test:ci
|
|
51
|
+
|
|
52
|
+
# Open HTML report
|
|
53
|
+
npm run report
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Writing tests
|
|
57
|
+
|
|
58
|
+
Import from `@playwright/test` and use the `request` fixture:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { test, expect } from '@playwright/test';
|
|
62
|
+
|
|
63
|
+
test('GET /users returns 200', async ({ request }) => {
|
|
64
|
+
const response = await request.get('/users');
|
|
65
|
+
expect(response.status()).toBe(200);
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For token-based APIs that require an `Authorization` header, pass it per request:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const response = await request.get('/api/protected', {
|
|
73
|
+
headers: { Authorization: `Bearer ${process.env.QA_API_TOKEN}` },
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
See `tests/example.spec.ts` for a starter test to replace.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* global-setup.ts - Integration (API-only) project
|
|
3
|
+
*
|
|
4
|
+
* Runs once before all test files to establish auth state.
|
|
5
|
+
*
|
|
6
|
+
* Two strategies, tried in order:
|
|
7
|
+
* 1. Token-based: QA_API_TOKEN is set -> write a minimal storageState with the
|
|
8
|
+
* token in localStorage so the `request` fixture picks it up automatically.
|
|
9
|
+
* 2. Browser login: QA_USER_EMAIL + QA_USER_PASSWORD are set -> launch Chromium,
|
|
10
|
+
* perform the login flow, and save the resulting storageState to
|
|
11
|
+
* .auth/api-session.json.
|
|
12
|
+
*
|
|
13
|
+
* Playwright's `storageState` mechanism handles cookies and localStorage for
|
|
14
|
+
* APIRequestContext automatically when `use.storageState` is configured.
|
|
15
|
+
* For token-only APIs that require an Authorization header, set the header
|
|
16
|
+
* directly in each test or extend the `request` fixture in fixtures/base.ts.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from 'fs';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import { chromium } from '@playwright/test';
|
|
22
|
+
import * as dotenv from 'dotenv';
|
|
23
|
+
|
|
24
|
+
dotenv.config();
|
|
25
|
+
|
|
26
|
+
const AUTH_FILE = path.join(__dirname, '.auth', 'api-session.json');
|
|
27
|
+
|
|
28
|
+
export default async function globalSetup(): Promise<void> {
|
|
29
|
+
fs.mkdirSync(path.dirname(AUTH_FILE), { recursive: true });
|
|
30
|
+
|
|
31
|
+
if (process.env.QA_API_TOKEN) {
|
|
32
|
+
await setupWithToken(process.env.QA_API_TOKEN, process.env.QA_BASE_URL!);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await setupWithBrowserLogin();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Write a minimal storageState that carries the bearer token in localStorage.
|
|
41
|
+
* Replace the localStorage key/value format if your app uses a different scheme
|
|
42
|
+
* (e.g. a cookie, or a different localStorage key name).
|
|
43
|
+
*/
|
|
44
|
+
async function setupWithToken(token: string, baseURL: string): Promise<void> {
|
|
45
|
+
const origin = baseURL.replace(/\/$/, '');
|
|
46
|
+
|
|
47
|
+
const state = {
|
|
48
|
+
cookies: [],
|
|
49
|
+
origins: [
|
|
50
|
+
{
|
|
51
|
+
origin,
|
|
52
|
+
localStorage: [
|
|
53
|
+
{ name: 'qa_api_token', value: token },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify(state, null, 2), 'utf-8');
|
|
60
|
+
console.log('[integration-setup] Token-based auth written to api-session.json');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Perform a browser login and save the resulting cookies + localStorage to disk.
|
|
65
|
+
*
|
|
66
|
+
* Configure login form selectors via env vars (see .env.example) or update the
|
|
67
|
+
* defaults below to match your application's login page.
|
|
68
|
+
*/
|
|
69
|
+
async function setupWithBrowserLogin(): Promise<void> {
|
|
70
|
+
const baseURL = process.env.QA_BASE_URL!.replace(/\/$/, '');
|
|
71
|
+
const email = process.env.QA_USER_EMAIL!;
|
|
72
|
+
const password = process.env.QA_USER_PASSWORD!;
|
|
73
|
+
const loginPath = process.env.QA_LOGIN_PATH ?? '/login';
|
|
74
|
+
const emailSelector = process.env.QA_LOGIN_EMAIL_SELECTOR ?? 'input[type="email"]';
|
|
75
|
+
const passwordSelector = process.env.QA_LOGIN_PASSWORD_SELECTOR ?? 'input[type="password"]';
|
|
76
|
+
const submitSelector = process.env.QA_LOGIN_SUBMIT_SELECTOR ?? 'button[type="submit"]';
|
|
77
|
+
const successSelector = process.env.QA_LOGIN_SUCCESS_SELECTOR ?? '.dashboard, .main-content, [data-testid="app-shell"]';
|
|
78
|
+
|
|
79
|
+
console.log('[integration-setup] Performing browser login...');
|
|
80
|
+
|
|
81
|
+
const browser = await chromium.launch();
|
|
82
|
+
const context = await browser.newContext();
|
|
83
|
+
const page = await context.newPage();
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await page.goto(`${baseURL}${loginPath}`, { waitUntil: 'load', timeout: 60_000 });
|
|
87
|
+
|
|
88
|
+
await page.locator(emailSelector).waitFor({ state: 'visible', timeout: 30_000 });
|
|
89
|
+
await page.locator(emailSelector).fill(email);
|
|
90
|
+
await page.locator(passwordSelector).fill(password);
|
|
91
|
+
await page.locator(submitSelector).click();
|
|
92
|
+
await page.waitForSelector(successSelector, { timeout: 60_000 });
|
|
93
|
+
|
|
94
|
+
await context.storageState({ path: AUTH_FILE });
|
|
95
|
+
console.log(`[integration-setup] Auth saved: ${AUTH_FILE}`);
|
|
96
|
+
} finally {
|
|
97
|
+
await browser.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qa-integration-tests",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "API integration tests - no browser required",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "playwright test",
|
|
8
|
+
"test:ci": "CI=true playwright test",
|
|
9
|
+
"report": "playwright show-report"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@playwright/test": "^1.44.0",
|
|
13
|
+
"dotenv": "^16.4.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* playwright.config.ts - API integration test project (no browser)
|
|
3
|
+
*
|
|
4
|
+
* All tests use APIRequestContext directly. No browser projects are defined.
|
|
5
|
+
* Auth is handled by global-setup.ts, which writes .auth/api-session.json.
|
|
6
|
+
*
|
|
7
|
+
* Required env vars:
|
|
8
|
+
* QA_BASE_URL - Base URL of the API under test
|
|
9
|
+
*
|
|
10
|
+
* Auth (at least one of the following):
|
|
11
|
+
* QA_API_TOKEN - Bearer token (skips browser login in global-setup)
|
|
12
|
+
* QA_USER_EMAIL + QA_USER_PASSWORD - Credentials for browser-based login
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { defineConfig } from '@playwright/test';
|
|
16
|
+
import * as dotenv from 'dotenv';
|
|
17
|
+
|
|
18
|
+
dotenv.config();
|
|
19
|
+
|
|
20
|
+
const required = ['QA_BASE_URL'];
|
|
21
|
+
for (const key of required) {
|
|
22
|
+
if (!process.env[key]) {
|
|
23
|
+
throw new Error(`[integration] Missing required env var: ${key}. Check your .env file.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const hasToken = !!process.env.QA_API_TOKEN;
|
|
28
|
+
const hasCredentials = !!(process.env.QA_USER_EMAIL && process.env.QA_USER_PASSWORD);
|
|
29
|
+
|
|
30
|
+
if (!hasToken && !hasCredentials) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
'[integration] No auth configured. Set QA_API_TOKEN, or both QA_USER_EMAIL and QA_USER_PASSWORD.',
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Optional: Azure DevOps reporter configuration
|
|
37
|
+
// To enable: set env vars ADO_ORG, ADO_PROJECT, ADO_PAT, ADO_PLAN_ID and CI=true,
|
|
38
|
+
// then uncomment the reporter entry in the `reporter` array below.
|
|
39
|
+
// Install: npm install @alex_neo/playwright-azure-reporter --save-dev
|
|
40
|
+
// const adoReporterConfig = {
|
|
41
|
+
// orgUrl: `https://dev.azure.com/${process.env.ADO_ORG}`,
|
|
42
|
+
// token: process.env.SYSTEM_ACCESSTOKEN ?? process.env.ADO_PAT,
|
|
43
|
+
// planId: Number(process.env.ADO_PLAN_ID),
|
|
44
|
+
// projectName: process.env.ADO_PROJECT!,
|
|
45
|
+
// testRunTitle: `[Auto API] ${new Date().toISOString().slice(0, 10)}`,
|
|
46
|
+
// publishTestResultsMode: 'testRun' as const,
|
|
47
|
+
// uploadAttachments: false,
|
|
48
|
+
// isDisabled: !process.env.CI,
|
|
49
|
+
// autoMarkTestCasesAsAutomated: {
|
|
50
|
+
// enabled: true,
|
|
51
|
+
// updateAutomatedTestName: true,
|
|
52
|
+
// updateAutomatedTestStorage: true,
|
|
53
|
+
// },
|
|
54
|
+
// };
|
|
55
|
+
|
|
56
|
+
export default defineConfig({
|
|
57
|
+
testDir: './tests',
|
|
58
|
+
fullyParallel: false,
|
|
59
|
+
workers: 1,
|
|
60
|
+
timeout: 30_000,
|
|
61
|
+
retries: process.env.CI ? 1 : 0,
|
|
62
|
+
|
|
63
|
+
globalSetup: './global-setup.ts',
|
|
64
|
+
|
|
65
|
+
reporter: [
|
|
66
|
+
['list'],
|
|
67
|
+
['html', { open: 'never', outputFolder: 'playwright-report' }],
|
|
68
|
+
// Uncomment for ADO:
|
|
69
|
+
// ['@alex_neo/playwright-azure-reporter', adoReporterConfig],
|
|
70
|
+
],
|
|
71
|
+
|
|
72
|
+
use: {
|
|
73
|
+
baseURL: process.env.QA_BASE_URL,
|
|
74
|
+
storageState: '.auth/api-session.json',
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
outputDir: 'test-results/',
|
|
78
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* example.spec.ts - API integration test example
|
|
3
|
+
*
|
|
4
|
+
* This file shows the basic pattern for API tests using Playwright's
|
|
5
|
+
* APIRequestContext. Replace this with your real tests.
|
|
6
|
+
*
|
|
7
|
+
* The `request` fixture is pre-configured with:
|
|
8
|
+
* - baseURL from QA_BASE_URL
|
|
9
|
+
* - storageState from .auth/api-session.json (set by global-setup.ts)
|
|
10
|
+
*
|
|
11
|
+
* For token-based APIs that require an Authorization header, add it
|
|
12
|
+
* explicitly per request or extend the fixture in a fixtures/base.ts file.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { test, expect } from '@playwright/test';
|
|
16
|
+
|
|
17
|
+
// Replace with the actual health/status endpoint of your API.
|
|
18
|
+
const HEALTH_ENDPOINT = '/health';
|
|
19
|
+
|
|
20
|
+
test.describe('API smoke tests', () => {
|
|
21
|
+
|
|
22
|
+
test('GET health endpoint returns 200', async ({ request }) => {
|
|
23
|
+
const response = await request.get(HEALTH_ENDPOINT);
|
|
24
|
+
|
|
25
|
+
expect(response.status()).toBe(200);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Example of a test with an Authorization header when QA_API_TOKEN is set.
|
|
29
|
+
// Uncomment and adapt when your API uses Bearer token auth.
|
|
30
|
+
//
|
|
31
|
+
// test('GET protected resource returns 200 with bearer token', async ({ request }) => {
|
|
32
|
+
// const response = await request.get('/api/me', {
|
|
33
|
+
// headers: {
|
|
34
|
+
// Authorization: `Bearer ${process.env.QA_API_TOKEN}`,
|
|
35
|
+
// },
|
|
36
|
+
// });
|
|
37
|
+
//
|
|
38
|
+
// expect(response.status()).toBe(200);
|
|
39
|
+
// const body = await response.json();
|
|
40
|
+
// expect(body).toHaveProperty('id');
|
|
41
|
+
// });
|
|
42
|
+
|
|
43
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# CI Pipeline Findings
|
|
2
|
+
|
|
3
|
+
> Document CI pipeline issues, root causes, and applied fixes as they are discovered.
|
|
4
|
+
> This file is loaded by the agent when diagnosing CI failures or reviewing pipeline changes.
|
|
5
|
+
|
|
6
|
+
## Applied Fixes
|
|
7
|
+
|
|
8
|
+
Document each fix that was applied to the pipeline or test setup. Use a table or prose - whatever fits the change.
|
|
9
|
+
|
|
10
|
+
| Problem | Root cause | Fix applied |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| _Add pipeline issue here_ | _Root cause_ | _What was changed and where_ |
|
|
13
|
+
|
|
14
|
+
> Examples: auth setup defaults, missing reporter packages, artifact name collisions,
|
|
15
|
+
> browser binary not installed in a CI stage, grep flag escaping issues.
|
|
16
|
+
|
|
17
|
+
## Known Limitations
|
|
18
|
+
|
|
19
|
+
Document constraints that are accepted but not fixed (timeout budgets, worker counts, retry behavior).
|
|
20
|
+
|
|
21
|
+
> Examples:
|
|
22
|
+
> - Full suite exceeds the CI timeout with 1 worker. Workaround: run only P0/P1 tags by default.
|
|
23
|
+
> - `retries: 2` counts each attempt separately, so reported execution count exceeds test count.
|
|
24
|
+
> - Attachment upload warnings on `PublishTestResults` when multiple failures share filenames.
|
|
25
|
+
|
|
26
|
+
## Failure Triage Categories
|
|
27
|
+
|
|
28
|
+
Use these categories when classifying CI failures. Add project-specific categories as needed.
|
|
29
|
+
|
|
30
|
+
| Category | Description | Action |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| TIMEOUT | Test exceeded its time budget | Adjust `timeout` in the spec or in `playwright.config.ts` |
|
|
33
|
+
| SESSION | Auth session expired mid-run | Implement session-refresh fixture or reduce suite duration |
|
|
34
|
+
| FLAKY | Intermittent failure, passes on retry | Acceptable if retries cover it; investigate if failure rate > 20% |
|
|
35
|
+
| APP-DEFECT | Application does not meet the spec | Open defect file; mark test with `test.fail()` |
|
|
36
|
+
| TEST-CODE | Selector or assertion is wrong in CI | Fix the spec file |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Data Volatility Strategies
|
|
2
|
+
|
|
3
|
+
> The QA environment is typically shared between automated tests and manual testers.
|
|
4
|
+
> Data (record counts, available filter values, document states) changes constantly,
|
|
5
|
+
> which invalidates hardcoded values in specs. Document the chosen strategy here.
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
List the types of data that are volatile in this project's QA environment.
|
|
10
|
+
|
|
11
|
+
| Data type | Example of fragile assertion | Tests affected |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Filter option values | `hasText: 'specific-value'` | _Add count here_ |
|
|
14
|
+
| Exact row counts | `toBe(33)` | _Add count here_ |
|
|
15
|
+
| Record names used as search inputs | `fill('specific-record-name')` | _Add count here_ |
|
|
16
|
+
| Process or transaction IDs | `TEST_ID = '608'` | _Add count here_ |
|
|
17
|
+
|
|
18
|
+
## Strategy 1: Shared Constants with Dynamic Discovery
|
|
19
|
+
|
|
20
|
+
Maintain a `fixtures/test-data-constants.ts` file with well-named constants. Populate them
|
|
21
|
+
from a discovery step (global-setup or a `beforeAll` fixture) that reads the actual environment.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// fixtures/test-data-constants.ts
|
|
25
|
+
export const QA_ITEM_CURRENT = 'current-item-name'; // most recent available
|
|
26
|
+
export const QA_ITEM_OLDEST = 'oldest-item-name'; // oldest available
|
|
27
|
+
export const QA_ITEM_INVALID = 'nonexistent-item'; // guaranteed absent
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tradeoff:** eliminates hardcoded filter values centrally, but discovery adds startup time
|
|
31
|
+
and blocks the suite if it fails.
|
|
32
|
+
|
|
33
|
+
## Strategy 2: Resilient Assertions (Patterns and Ranges)
|
|
34
|
+
|
|
35
|
+
Replace exact-value assertions with pattern or range checks.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Instead of:
|
|
39
|
+
expect(count).toBe(33);
|
|
40
|
+
await expect(counter).toHaveText('1-5 of 5');
|
|
41
|
+
|
|
42
|
+
// Use:
|
|
43
|
+
expect(count).toBeGreaterThan(0);
|
|
44
|
+
await expect(counter).toHaveText(/\d+-\d+ of \d+/);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Tradeoff:** no extra infrastructure, each test is self-contained, but precision is lower.
|
|
48
|
+
Useful for count assertions; less useful for filter-value selection.
|
|
49
|
+
|
|
50
|
+
## Strategy 3: Snapshot Delta Validation
|
|
51
|
+
|
|
52
|
+
Capture the initial state before applying a filter, then assert that the filtered state
|
|
53
|
+
differs from the initial state in the expected direction (fewer rows, etc.).
|
|
54
|
+
|
|
55
|
+
**Tradeoff:** maximum resilience, but tests become longer and harder to trace. Use only for
|
|
56
|
+
critical filter tests where behavior (not data) must be validated.
|
|
57
|
+
|
|
58
|
+
## Chosen Approach
|
|
59
|
+
|
|
60
|
+
> Document which strategy (or combination) was chosen for this project, and which test
|
|
61
|
+
> categories each strategy applies to.
|
|
62
|
+
|
|
63
|
+
| Test category | Strategy | Status |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Filter option values | Strategy 1 or 2 | _Pending / Implemented_ |
|
|
66
|
+
| Row count assertions | Strategy 2 | _Pending / Implemented_ |
|
|
67
|
+
| Search input values | Strategy 2 or 3 | _Pending / Implemented_ |
|
|
68
|
+
|
|
69
|
+
> Tests that are blocked on a data strategy can be marked `test.skip()` with a
|
|
70
|
+
> `PENDING-DATA-STRATEGY` annotation until the strategy is implemented.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# E2E Stabilization Patterns
|
|
2
|
+
|
|
3
|
+
> A running log of flakiness patterns found in this project and the techniques used to fix them.
|
|
4
|
+
> Load this file when investigating intermittent test failures or refactoring fragile specs.
|
|
5
|
+
|
|
6
|
+
## Crash-Recovery Pattern
|
|
7
|
+
|
|
8
|
+
When the application server crashes intermittently (e.g. unhandled error page with a Reload link),
|
|
9
|
+
a bare `waitFor` will timeout. Use `Promise.race` to detect the error state and recover:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const outcome = await Promise.race([
|
|
13
|
+
targetElement.waitFor({ state: 'visible', timeout: 60_000 }).then(() => 'ready' as const),
|
|
14
|
+
page.getByText('An unhandled error has occurred.').waitFor({ state: 'visible', timeout: 60_000 }).then(() => 'crash' as const),
|
|
15
|
+
]).catch(() => 'timeout' as const);
|
|
16
|
+
|
|
17
|
+
if (outcome === 'crash') {
|
|
18
|
+
await page.getByRole('link', { name: 'Reload' }).click();
|
|
19
|
+
await targetElement.waitFor({ state: 'visible', timeout: 60_000 });
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> Document which spec files use this pattern and under what conditions it fires.
|
|
24
|
+
|
|
25
|
+
## Strict-Mode Selector Conflicts
|
|
26
|
+
|
|
27
|
+
`getByText()` and similar matchers can match multiple elements, triggering strict-mode violations.
|
|
28
|
+
Scope selectors to the most specific container available.
|
|
29
|
+
|
|
30
|
+
> Document conflicts found in this project here.
|
|
31
|
+
> Example: a pagination counter regex that also matches a card element.
|
|
32
|
+
> Fix: use a scoped `locator('.specific-class')` instead of a broad text match.
|
|
33
|
+
|
|
34
|
+
## Known Flaky Failure Types
|
|
35
|
+
|
|
36
|
+
Document categories of instability specific to this project. A row per pattern is enough.
|
|
37
|
+
|
|
38
|
+
| ID | Root cause | Fix | Affected tests |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| _Add ID_ | _Describe the underlying cause_ | _Technique used to fix_ | _Spec files or TC IDs_ |
|
|
41
|
+
|
|
42
|
+
> Examples: framework hydration delays, input binding issues requiring `pressSequentially`,
|
|
43
|
+
> elements that are in the DOM but not yet rendered, session expiry during long runs.
|
|
44
|
+
|
|
45
|
+
## Reference Timeouts
|
|
46
|
+
|
|
47
|
+
Fill in the timeout values used in this project once they are stabilized.
|
|
48
|
+
|
|
49
|
+
| Setting | Value | Location |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| Default test timeout | - | `playwright.config.ts` |
|
|
52
|
+
| `actionTimeout` | - | `playwright.config.ts` |
|
|
53
|
+
| `navigationTimeout` | - | `playwright.config.ts` |
|
|
54
|
+
| Long-running spec timeout | - | Per-spec `describe.configure` |
|
|
@@ -24,7 +24,15 @@ This project uses `@keber/qa-framework` v{{VERSION}} for spec-driven automated t
|
|
|
24
24
|
1. Generate the output in `UTF-8` encoding without BOM (`UTF-8`, no signature).
|
|
25
25
|
2. Ensure the raw output is `UTF-8` encoded with no `EF BB BF` bytes at the beginning.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
## Azure DevOps integration
|
|
28
|
+
|
|
29
|
+
Before any ADO operation, check if `.github/skills/ado-qa/` exists or if
|
|
30
|
+
`integrations.ado.enabled` is `true` in `qa/qa-framework.config.json`. If either
|
|
31
|
+
condition is met, load `.github/skills/qa-ado-integration/SKILL.md` and use it for
|
|
32
|
+
all ADO interactions (work items, test plans, test cases, bugs, etc.).
|
|
33
|
+
|
|
34
|
+
> To enable Azure DevOps integration run: `npm install github:keber/ado-qa`
|
|
35
|
+
|
|
28
36
|
## QA Pipeline
|
|
29
37
|
|
|
30
38
|
Stages must run in order. Never start a stage unless its prerequisite is met.
|
package/templates/qa-readme.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Framework: `@keber/qa-framework` v{VERSION}
|
|
4
4
|
> Language: {LANGUAGE} | Base URL: `{QA_BASE_URL}`
|
|
5
|
+
> ADO Project: `{ADO_ORG} / {ADO_PROJECT}` _(optional — remove line if not using Azure DevOps)_
|
|
5
6
|
|
|
6
7
|
---
|
|
7
8
|
|
|
@@ -47,12 +48,32 @@ npx playwright test --last-failed
|
|
|
47
48
|
|
|
48
49
|
---
|
|
49
50
|
|
|
51
|
+
## Last Run
|
|
52
|
+
|
|
53
|
+
| Suite | Date | Pass | Fail | Skip | CI Link |
|
|
54
|
+
|---|---|---|---|---|---|
|
|
55
|
+
| E2E | — | — | — | — | — |
|
|
56
|
+
| API | — | — | — | — | — |
|
|
57
|
+
|
|
58
|
+
> Update this table after each significant run. The CI Link can be an ADO TestRun URL, a
|
|
59
|
+
> GitHub Actions run URL, or a path to a local report file.
|
|
60
|
+
|
|
50
61
|
## Last Execution
|
|
51
62
|
|
|
52
63
|
| Date | Suite | Pass | Skip | Fail | Duration | Report |
|
|
53
64
|
|---|---|---|---|---|---|---|
|
|
54
65
|
| — | — | — | — | — | — | — |
|
|
55
66
|
|
|
67
|
+
## Flaky Tests
|
|
68
|
+
|
|
69
|
+
> Mark known flaky tests with this inline annotation directly in the table or spec file.
|
|
70
|
+
> Format: `[flaky] <test-id-or-name> - <root cause> - last seen <YYYY-MM-DD>`
|
|
71
|
+
>
|
|
72
|
+
> Example:
|
|
73
|
+
> `[flaky] TC-MOD-012 - timing issue on slow CI agents - last seen 2026-01-15`
|
|
74
|
+
>
|
|
75
|
+
> Remove the annotation once the test has been stable for 2+ consecutive CI runs.
|
|
76
|
+
|
|
56
77
|
---
|
|
57
78
|
|
|
58
79
|
## Sprint History
|