@toolstackhq/create-qa-patterns 1.0.0 → 1.0.1
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/README.md +17 -10
- package/index.js +197 -1
- package/package.json +4 -3
- package/templates/playwright-template/.env.example +17 -0
- package/templates/playwright-template/.github/workflows/playwright-tests.yml +126 -0
- package/templates/playwright-template/README.md +234 -0
- package/templates/playwright-template/allurerc.mjs +10 -0
- package/templates/playwright-template/components/flash-message.ts +16 -0
- package/templates/playwright-template/config/environments.ts +41 -0
- package/templates/playwright-template/config/runtime-config.ts +53 -0
- package/templates/playwright-template/config/secret-manager.ts +28 -0
- package/templates/playwright-template/config/test-env.ts +9 -0
- package/templates/playwright-template/data/README.md +9 -0
- package/templates/playwright-template/data/factories/data-factory.ts +33 -0
- package/templates/playwright-template/data/generators/id-generator.ts +17 -0
- package/templates/playwright-template/data/generators/seeded-faker.ts +13 -0
- package/templates/playwright-template/docker/Dockerfile +21 -0
- package/templates/playwright-template/eslint.config.mjs +66 -0
- package/templates/playwright-template/fixtures/test-fixtures.ts +43 -0
- package/templates/playwright-template/lint/architecture-plugin.cjs +118 -0
- package/templates/playwright-template/package-lock.json +4724 -0
- package/templates/playwright-template/package.json +34 -0
- package/templates/playwright-template/pages/base-page.ts +24 -0
- package/templates/playwright-template/pages/login-page.ts +22 -0
- package/templates/playwright-template/pages/people-page.ts +39 -0
- package/templates/playwright-template/playwright.config.ts +46 -0
- package/templates/playwright-template/reporters/structured-reporter.ts +61 -0
- package/templates/playwright-template/scripts/generate-allure-report.mjs +57 -0
- package/templates/playwright-template/scripts/run-tests.sh +6 -0
- package/templates/playwright-template/tests/api-people.spec.ts +28 -0
- package/templates/playwright-template/tests/ui-journey.spec.ts +30 -0
- package/templates/playwright-template/tsconfig.json +31 -0
- package/templates/playwright-template/utils/logger.ts +55 -0
- package/templates/playwright-template/utils/test-step.ts +22 -0
package/README.md
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
# @toolstackhq/create-qa-patterns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for generating QA framework templates from `qa-patterns`.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @toolstackhq/create-qa-patterns
|
|
9
|
+
```
|
|
8
10
|
|
|
9
11
|
## Usage
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
|
-
npm install -g @toolstackhq/create-qa-patterns
|
|
13
14
|
create-qa-patterns
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Generate into a new directory:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
create-qa-patterns my-project
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Generate the Playwright template explicitly:
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
```bash
|
|
26
|
+
create-qa-patterns playwright-template my-project
|
|
27
|
+
```
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
## Supported templates
|
|
21
30
|
|
|
22
|
-
-
|
|
23
|
-
- scaffold reference demo applications
|
|
24
|
-
- generate standardized config, lint, CI, and reporting assets
|
|
31
|
+
- `playwright-template`
|
package/index.js
CHANGED
|
@@ -1,3 +1,199 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TEMPLATE = "playwright-template";
|
|
7
|
+
const DEFAULT_GITIGNORE = `node_modules/
|
|
8
|
+
|
|
9
|
+
.env
|
|
10
|
+
.env.*
|
|
11
|
+
!.env.example
|
|
12
|
+
|
|
13
|
+
reports/
|
|
14
|
+
allure-results/
|
|
15
|
+
allure-report/
|
|
16
|
+
test-results/
|
|
17
|
+
playwright-report/
|
|
18
|
+
`;
|
|
19
|
+
const TEMPLATE_ALIASES = new Map([
|
|
20
|
+
["playwright", DEFAULT_TEMPLATE],
|
|
21
|
+
["pw", DEFAULT_TEMPLATE],
|
|
22
|
+
[DEFAULT_TEMPLATE, DEFAULT_TEMPLATE]
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function printHelp() {
|
|
26
|
+
process.stdout.write(`create-qa-patterns
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
create-qa-patterns
|
|
30
|
+
create-qa-patterns <target-directory>
|
|
31
|
+
create-qa-patterns <template> [target-directory]
|
|
32
|
+
|
|
33
|
+
Supported templates:
|
|
34
|
+
playwright-template
|
|
35
|
+
playwright
|
|
36
|
+
pw
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveTemplate(value) {
|
|
41
|
+
return TEMPLATE_ALIASES.get(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveScaffoldArgs(args) {
|
|
45
|
+
if (args.length === 0) {
|
|
46
|
+
return {
|
|
47
|
+
templateName: DEFAULT_TEMPLATE,
|
|
48
|
+
targetDirectory: process.cwd(),
|
|
49
|
+
generatedInCurrentDirectory: true
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (args.length === 1) {
|
|
54
|
+
const templateName = resolveTemplate(args[0]);
|
|
55
|
+
if (templateName) {
|
|
56
|
+
return {
|
|
57
|
+
templateName,
|
|
58
|
+
targetDirectory: process.cwd(),
|
|
59
|
+
generatedInCurrentDirectory: true
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
templateName: DEFAULT_TEMPLATE,
|
|
65
|
+
targetDirectory: path.resolve(process.cwd(), args[0]),
|
|
66
|
+
generatedInCurrentDirectory: false
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (args.length === 2) {
|
|
71
|
+
const templateName = resolveTemplate(args[0]);
|
|
72
|
+
if (!templateName) {
|
|
73
|
+
throw new Error(`Unsupported template "${args[0]}". Use "playwright-template".`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
templateName,
|
|
78
|
+
targetDirectory: path.resolve(process.cwd(), args[1]),
|
|
79
|
+
generatedInCurrentDirectory: false
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
throw new Error("Too many arguments. Run `create-qa-patterns --help` for usage.");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ensureScaffoldTarget(targetDirectory) {
|
|
87
|
+
if (!fs.existsSync(targetDirectory)) {
|
|
88
|
+
fs.mkdirSync(targetDirectory, { recursive: true });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const entries = fs
|
|
93
|
+
.readdirSync(targetDirectory)
|
|
94
|
+
.filter((entry) => ![".git", ".DS_Store"].includes(entry));
|
|
95
|
+
|
|
96
|
+
if (entries.length > 0) {
|
|
97
|
+
throw new Error(`Target directory is not empty: ${targetDirectory}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toPackageName(targetDirectory) {
|
|
102
|
+
const baseName = path.basename(targetDirectory).toLowerCase();
|
|
103
|
+
const normalized = baseName
|
|
104
|
+
.replace(/[^a-z0-9-_]+/g, "-")
|
|
105
|
+
.replace(/^-+|-+$/g, "")
|
|
106
|
+
.replace(/-{2,}/g, "-");
|
|
107
|
+
|
|
108
|
+
return normalized || "playwright-template";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function updateJsonFile(filePath, update) {
|
|
112
|
+
const current = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
113
|
+
const next = update(current);
|
|
114
|
+
fs.writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function customizeProject(targetDirectory) {
|
|
118
|
+
const packageName = toPackageName(targetDirectory);
|
|
119
|
+
const packageJsonPath = path.join(targetDirectory, "package.json");
|
|
120
|
+
const packageLockPath = path.join(targetDirectory, "package-lock.json");
|
|
121
|
+
const gitignorePath = path.join(targetDirectory, ".gitignore");
|
|
122
|
+
|
|
123
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
124
|
+
updateJsonFile(packageJsonPath, (pkg) => ({
|
|
125
|
+
...pkg,
|
|
126
|
+
name: packageName
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (fs.existsSync(packageLockPath)) {
|
|
131
|
+
updateJsonFile(packageLockPath, (lock) => ({
|
|
132
|
+
...lock,
|
|
133
|
+
name: packageName,
|
|
134
|
+
packages: lock.packages
|
|
135
|
+
? {
|
|
136
|
+
...lock.packages,
|
|
137
|
+
"": {
|
|
138
|
+
...lock.packages[""],
|
|
139
|
+
name: packageName
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
: lock.packages
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
147
|
+
fs.writeFileSync(gitignorePath, DEFAULT_GITIGNORE, "utf8");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function scaffoldProject(templateName, targetDirectory) {
|
|
152
|
+
const templateDirectory = path.resolve(__dirname, "templates", templateName);
|
|
153
|
+
|
|
154
|
+
if (!fs.existsSync(templateDirectory)) {
|
|
155
|
+
throw new Error(`Template files are missing for "${templateName}".`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
ensureScaffoldTarget(targetDirectory);
|
|
159
|
+
fs.cpSync(templateDirectory, targetDirectory, { recursive: true });
|
|
160
|
+
customizeProject(targetDirectory);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function printNextSteps(targetDirectory, generatedInCurrentDirectory) {
|
|
164
|
+
process.stdout.write(`Generated ${DEFAULT_TEMPLATE} in ${targetDirectory}
|
|
165
|
+
|
|
166
|
+
Next steps:
|
|
167
|
+
`);
|
|
168
|
+
|
|
169
|
+
if (!generatedInCurrentDirectory) {
|
|
170
|
+
process.stdout.write(` cd ${path.relative(process.cwd(), targetDirectory) || "."}
|
|
171
|
+
`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
process.stdout.write(` npm install
|
|
175
|
+
npx playwright install
|
|
176
|
+
npm test
|
|
177
|
+
`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function main() {
|
|
181
|
+
const args = process.argv.slice(2);
|
|
182
|
+
|
|
183
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
184
|
+
printHelp();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const { templateName, targetDirectory, generatedInCurrentDirectory } = resolveScaffoldArgs(args);
|
|
189
|
+
scaffoldProject(templateName, targetDirectory);
|
|
190
|
+
printNextSteps(targetDirectory, generatedInCurrentDirectory);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
main();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
197
|
+
process.stderr.write(`${message}\n`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toolstackhq/create-qa-patterns",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "CLI for generating QA framework templates.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"index.js",
|
|
19
|
+
"templates",
|
|
19
20
|
"README.md",
|
|
20
21
|
"LICENSE"
|
|
21
22
|
],
|
|
@@ -24,6 +25,6 @@
|
|
|
24
25
|
},
|
|
25
26
|
"scripts": {
|
|
26
27
|
"start": "node ./index.js",
|
|
27
|
-
"test": "node ./index.js"
|
|
28
|
+
"test": "node ./index.js --help"
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
TEST_ENV=dev
|
|
2
|
+
TEST_RUN_ID=local
|
|
3
|
+
|
|
4
|
+
DEV_UI_BASE_URL=http://127.0.0.1:3000
|
|
5
|
+
DEV_API_BASE_URL=http://127.0.0.1:3001
|
|
6
|
+
DEV_APP_USERNAME=tester
|
|
7
|
+
DEV_APP_PASSWORD=Password123!
|
|
8
|
+
|
|
9
|
+
STAGING_UI_BASE_URL=https://staging-ui.example.internal
|
|
10
|
+
STAGING_API_BASE_URL=https://staging-api.example.internal
|
|
11
|
+
STAGING_APP_USERNAME=staging-user
|
|
12
|
+
STAGING_APP_PASSWORD=replace-me
|
|
13
|
+
|
|
14
|
+
PROD_UI_BASE_URL=https://ui.example.internal
|
|
15
|
+
PROD_API_BASE_URL=https://api.example.internal
|
|
16
|
+
PROD_APP_USERNAME=prod-user
|
|
17
|
+
PROD_APP_PASSWORD=replace-me
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
name: Playwright Template Validation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
paths:
|
|
7
|
+
- "templates/playwright-template/**"
|
|
8
|
+
- "test-apps/**"
|
|
9
|
+
- "package.json"
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
playwright:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: "20"
|
|
21
|
+
|
|
22
|
+
- name: Install workspace dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Install Playwright browsers
|
|
26
|
+
working-directory: templates/playwright-template
|
|
27
|
+
run: npx playwright install --with-deps chromium
|
|
28
|
+
|
|
29
|
+
- name: Start API demo server
|
|
30
|
+
run: npm run dev --workspace @toolstackhq/api-demo-server &
|
|
31
|
+
|
|
32
|
+
- name: Start UI demo app
|
|
33
|
+
run: npm run dev --workspace @toolstackhq/ui-demo-app &
|
|
34
|
+
|
|
35
|
+
- name: Wait for demo services
|
|
36
|
+
run: |
|
|
37
|
+
for target in http://127.0.0.1:3000/health http://127.0.0.1:3001/health; do
|
|
38
|
+
until curl --silent --fail "$target" >/dev/null; do
|
|
39
|
+
sleep 1
|
|
40
|
+
done
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
- name: Run Playwright validation
|
|
44
|
+
working-directory: templates/playwright-template
|
|
45
|
+
env:
|
|
46
|
+
TEST_ENV: dev
|
|
47
|
+
TEST_RUN_ID: ci
|
|
48
|
+
run: bash ./scripts/run-tests.sh
|
|
49
|
+
|
|
50
|
+
- name: Generate Allure report
|
|
51
|
+
if: always()
|
|
52
|
+
working-directory: templates/playwright-template
|
|
53
|
+
run: npm run report:allure
|
|
54
|
+
|
|
55
|
+
- name: Upload Playwright artifacts
|
|
56
|
+
if: always()
|
|
57
|
+
uses: actions/upload-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: playwright-template-artifacts
|
|
60
|
+
path: |
|
|
61
|
+
templates/playwright-template/reports
|
|
62
|
+
templates/playwright-template/allure-results
|
|
63
|
+
templates/playwright-template/test-results
|
|
64
|
+
|
|
65
|
+
playwright-docker:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
|
|
71
|
+
- uses: actions/setup-node@v4
|
|
72
|
+
with:
|
|
73
|
+
node-version: "20"
|
|
74
|
+
|
|
75
|
+
- name: Install workspace dependencies
|
|
76
|
+
run: npm ci
|
|
77
|
+
|
|
78
|
+
- name: Start API demo server
|
|
79
|
+
run: npm run dev --workspace @toolstackhq/api-demo-server &
|
|
80
|
+
|
|
81
|
+
- name: Start UI demo app
|
|
82
|
+
run: npm run dev --workspace @toolstackhq/ui-demo-app &
|
|
83
|
+
|
|
84
|
+
- name: Wait for demo services
|
|
85
|
+
run: |
|
|
86
|
+
for target in http://127.0.0.1:3000/health http://127.0.0.1:3001/health; do
|
|
87
|
+
until curl --silent --fail "$target" >/dev/null; do
|
|
88
|
+
sleep 1
|
|
89
|
+
done
|
|
90
|
+
done
|
|
91
|
+
|
|
92
|
+
- name: Prepare Docker artifacts directories
|
|
93
|
+
run: |
|
|
94
|
+
rm -rf templates/playwright-template/reports templates/playwright-template/test-results templates/playwright-template/allure-results
|
|
95
|
+
mkdir -p templates/playwright-template/reports templates/playwright-template/test-results templates/playwright-template/allure-results
|
|
96
|
+
|
|
97
|
+
- name: Build Playwright template Docker image
|
|
98
|
+
run: docker build -t qa-patterns-playwright-template:ci -f templates/playwright-template/docker/Dockerfile templates/playwright-template
|
|
99
|
+
|
|
100
|
+
- name: Run Playwright template inside Docker
|
|
101
|
+
run: |
|
|
102
|
+
docker run --rm \
|
|
103
|
+
--add-host=host.docker.internal:host-gateway \
|
|
104
|
+
-e TEST_ENV=dev \
|
|
105
|
+
-e TEST_RUN_ID=ci-docker \
|
|
106
|
+
-e DEV_UI_BASE_URL=http://host.docker.internal:3000 \
|
|
107
|
+
-e DEV_API_BASE_URL=http://host.docker.internal:3001 \
|
|
108
|
+
-v "${GITHUB_WORKSPACE}/templates/playwright-template/reports:/workspace/reports" \
|
|
109
|
+
-v "${GITHUB_WORKSPACE}/templates/playwright-template/allure-results:/workspace/allure-results" \
|
|
110
|
+
-v "${GITHUB_WORKSPACE}/templates/playwright-template/test-results:/workspace/test-results" \
|
|
111
|
+
qa-patterns-playwright-template:ci
|
|
112
|
+
|
|
113
|
+
- name: Generate Allure report
|
|
114
|
+
if: always()
|
|
115
|
+
working-directory: templates/playwright-template
|
|
116
|
+
run: npm run report:allure
|
|
117
|
+
|
|
118
|
+
- name: Upload Docker Playwright artifacts
|
|
119
|
+
if: always()
|
|
120
|
+
uses: actions/upload-artifact@v4
|
|
121
|
+
with:
|
|
122
|
+
name: playwright-template-docker-artifacts
|
|
123
|
+
path: |
|
|
124
|
+
templates/playwright-template/reports
|
|
125
|
+
templates/playwright-template/allure-results
|
|
126
|
+
templates/playwright-template/test-results
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Playwright Template
|
|
2
|
+
|
|
3
|
+
This is a Playwright + TypeScript automation framework template for UI and API tests.
|
|
4
|
+
|
|
5
|
+
## Table of contents
|
|
6
|
+
|
|
7
|
+
- [Feature set](#feature-set)
|
|
8
|
+
- [How it works](#how-it-works)
|
|
9
|
+
- [Project structure](#project-structure)
|
|
10
|
+
- [Quick start](#quick-start)
|
|
11
|
+
- [Environment and secrets](#environment-and-secrets)
|
|
12
|
+
- [Main commands](#main-commands)
|
|
13
|
+
- [Reports and artifacts](#reports-and-artifacts)
|
|
14
|
+
- [Add a new test](#add-a-new-test)
|
|
15
|
+
- [Extend the framework](#extend-the-framework)
|
|
16
|
+
- [CI and Docker](#ci-and-docker)
|
|
17
|
+
|
|
18
|
+
## Feature set
|
|
19
|
+
|
|
20
|
+
- Playwright + TypeScript setup
|
|
21
|
+
- page object pattern with selectors kept out of tests
|
|
22
|
+
- shared fixtures for config, logging, data, and page objects
|
|
23
|
+
- generic data factory pattern with `DataFactory`
|
|
24
|
+
- multi-environment runtime config with `dev`, `staging`, and `prod`
|
|
25
|
+
- env-based secret resolution with a replaceable `SecretProvider`
|
|
26
|
+
- Playwright HTML report by default
|
|
27
|
+
- optional Allure single-file report
|
|
28
|
+
- traces, screenshots, videos, and structured logs for debugging
|
|
29
|
+
- ESLint rules that protect framework conventions
|
|
30
|
+
- GitHub Actions workflow and Docker support
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
- tests import shared fixtures from `fixtures/test-fixtures.ts`
|
|
35
|
+
- page objects in `pages/` own locators and user actions
|
|
36
|
+
- runtime config is loaded from `config/runtime-config.ts`
|
|
37
|
+
- application URLs and credentials are resolved from `TEST_ENV`
|
|
38
|
+
- reports and artifacts are written under `reports/`, `allure-results/`, and `test-results/`
|
|
39
|
+
|
|
40
|
+
## Project structure
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
playwright-template
|
|
44
|
+
├── tests
|
|
45
|
+
├── pages
|
|
46
|
+
├── components
|
|
47
|
+
├── fixtures
|
|
48
|
+
├── data
|
|
49
|
+
├── config
|
|
50
|
+
├── reporters
|
|
51
|
+
├── utils
|
|
52
|
+
├── lint
|
|
53
|
+
├── scripts
|
|
54
|
+
├── docker
|
|
55
|
+
├── playwright.config.ts
|
|
56
|
+
└── package.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
1. Install dependencies.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
2. Start the target apps you want the tests to hit.
|
|
68
|
+
|
|
69
|
+
For the local demo apps from the root repo:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm run dev:ui
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm run dev:api
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
3. Run tests.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm test
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Default local values:
|
|
86
|
+
|
|
87
|
+
- UI base URL: `http://127.0.0.1:3000`
|
|
88
|
+
- API base URL: `http://127.0.0.1:3001`
|
|
89
|
+
- username: `tester`
|
|
90
|
+
- password: `Password123!`
|
|
91
|
+
|
|
92
|
+
## Environment and secrets
|
|
93
|
+
|
|
94
|
+
The template supports:
|
|
95
|
+
|
|
96
|
+
- `TEST_ENV=dev`
|
|
97
|
+
- `TEST_ENV=staging`
|
|
98
|
+
- `TEST_ENV=prod`
|
|
99
|
+
|
|
100
|
+
Runtime values are resolved in this order:
|
|
101
|
+
|
|
102
|
+
1. environment-specific variables such as `DEV_UI_BASE_URL`
|
|
103
|
+
2. generic variables such as `UI_BASE_URL`
|
|
104
|
+
3. built-in defaults from `config/environments.ts`
|
|
105
|
+
|
|
106
|
+
The same pattern is used for credentials:
|
|
107
|
+
|
|
108
|
+
1. `DEV_APP_USERNAME` or `DEV_APP_PASSWORD`
|
|
109
|
+
2. `APP_USERNAME` or `APP_PASSWORD`
|
|
110
|
+
3. built-in defaults for the selected environment
|
|
111
|
+
|
|
112
|
+
For local overrides, copy:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
.env.example
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
to:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
.env
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The template loads:
|
|
125
|
+
|
|
126
|
+
- `.env`
|
|
127
|
+
- `.env.<TEST_ENV>`
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
TEST_ENV=staging \
|
|
133
|
+
STAGING_UI_BASE_URL=https://staging-ui.example.internal \
|
|
134
|
+
STAGING_API_BASE_URL=https://staging-api.example.internal \
|
|
135
|
+
STAGING_APP_USERNAME=my-user \
|
|
136
|
+
STAGING_APP_PASSWORD=my-password \
|
|
137
|
+
npx playwright test
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If your team uses a real secret system later, replace the implementation behind `config/secret-manager.ts`.
|
|
141
|
+
|
|
142
|
+
## Main commands
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm test
|
|
146
|
+
npm run test:smoke
|
|
147
|
+
npm run test:regression
|
|
148
|
+
npm run test:critical
|
|
149
|
+
npm run lint
|
|
150
|
+
npm run typecheck
|
|
151
|
+
npm run report:playwright
|
|
152
|
+
npm run report:allure
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Reports and artifacts
|
|
156
|
+
|
|
157
|
+
Default Playwright HTML report:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm run report:playwright
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Optional Allure report:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npm run report:allure
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Outputs:
|
|
170
|
+
|
|
171
|
+
- Playwright HTML: `reports/html`
|
|
172
|
+
- Allure single file: `reports/allure/index.html`
|
|
173
|
+
- structured event log: `reports/logs/playwright-events.jsonl`
|
|
174
|
+
- raw Allure results: `allure-results`
|
|
175
|
+
- traces, screenshots, videos: `test-results`
|
|
176
|
+
|
|
177
|
+
If you only want Playwright reporting, remove the `allure-playwright` reporter entry in `playwright.config.ts`.
|
|
178
|
+
|
|
179
|
+
## Add a new test
|
|
180
|
+
|
|
181
|
+
Create tests under `tests/` and import the shared fixtures:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { expect, test } from "../fixtures/test-fixtures";
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Keep the pattern simple:
|
|
188
|
+
|
|
189
|
+
- create data with `dataFactory`
|
|
190
|
+
- interact through page objects
|
|
191
|
+
- assert in the test
|
|
192
|
+
|
|
193
|
+
Example shape:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
test("do something @smoke", async ({ dataFactory, loginPage }) => {
|
|
197
|
+
const person = dataFactory.person();
|
|
198
|
+
// use page objects here
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Extend the framework
|
|
203
|
+
|
|
204
|
+
Common extension points:
|
|
205
|
+
|
|
206
|
+
- add page objects under `pages/`
|
|
207
|
+
- add reusable UI pieces under `components/`
|
|
208
|
+
- extend fixtures in `fixtures/test-fixtures.ts`
|
|
209
|
+
- add more generic builders under `data/factories/`
|
|
210
|
+
- add stronger custom lint rules in `lint/architecture-plugin.cjs`
|
|
211
|
+
- add custom reporters under `reporters/`
|
|
212
|
+
|
|
213
|
+
Recommended rules:
|
|
214
|
+
|
|
215
|
+
- keep selectors in page objects
|
|
216
|
+
- keep assertions in test files
|
|
217
|
+
- prefer semantic selectors such as `getByRole`, `getByLabel`, and `data-testid`
|
|
218
|
+
- keep the data layer generic until the project really needs domain-specific factories
|
|
219
|
+
|
|
220
|
+
## CI and Docker
|
|
221
|
+
|
|
222
|
+
The CI entrypoint is:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
scripts/run-tests.sh
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Docker support is included in:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
docker/Dockerfile
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The included GitHub Actions workflow installs dependencies, runs tests, and uploads artifacts. The Docker path is also validated in CI so the container setup does not drift from the normal runner path.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Locator, Page } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
export class FlashMessage {
|
|
4
|
+
private readonly message: Locator;
|
|
5
|
+
|
|
6
|
+
constructor(page: Page) {
|
|
7
|
+
this.message = page.getByTestId("flash-message");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async getText(): Promise<string | null> {
|
|
11
|
+
if (!(await this.message.count())) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return this.message.textContent();
|
|
15
|
+
}
|
|
16
|
+
}
|