@rashidee/co2 1.3.6 → 1.3.8
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/dist/.co2-dat/app.db +0 -0
- package/dist/.co2-dat/app.db-shm +0 -0
- package/dist/.co2-dat/app.db-wal +0 -0
- package/dist/index.js +167 -68
- package/package.json +41 -41
- package/plugin/skills/conductor-feature-develop/SKILL.md +1384 -1383
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +225 -224
- package/plugin/skills/mockgen-shadcn/SKILL.md +1073 -1067
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +5 -3
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +631 -498
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +2 -2
- package/plugin/skills/mockgen-tailwind/SKILL.md +913 -904
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +722 -720
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +631 -498
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -190
- package/static/assets/{abnfDiagram-VRR7QNED-CzKpxD9q.js → abnfDiagram-VRR7QNED-DP7zbvBZ.js} +1 -1
- package/static/assets/{arc-0jj0p8Qe.js → arc-Ckh0cqx_.js} +1 -1
- package/static/assets/{architectureDiagram-ZJ3FMSHR-DXUL6S5t.js → architectureDiagram-ZJ3FMSHR-EpVuCwzS.js} +1 -1
- package/static/assets/{blockDiagram-677ZJIJ3-BBjsQESd.js → blockDiagram-677ZJIJ3-DsgBkX_g.js} +1 -1
- package/static/assets/{c4Diagram-LMCZKHZV-CTsLC95S.js → c4Diagram-LMCZKHZV-CR04rrP-.js} +1 -1
- package/static/assets/channel-DfxyTZ0M.js +1 -0
- package/static/assets/{chunk-2Q5K7J3B-BpisBKYx.js → chunk-2Q5K7J3B-DeQC-sio.js} +1 -1
- package/static/assets/{chunk-32BRIVSS-DAfsr4ph.js → chunk-32BRIVSS-Qbam-XOV.js} +1 -1
- package/static/assets/{chunk-5VM5RSS4-Z3FtY-B6.js → chunk-5VM5RSS4-CH-IX0uh.js} +1 -1
- package/static/assets/{chunk-EX3LRPZG-FI9smQaN.js → chunk-EX3LRPZG-6gUCPZ6x.js} +1 -1
- package/static/assets/{chunk-JWPE2WC7-sOygQj_B.js → chunk-JWPE2WC7-DSNEXnbo.js} +1 -1
- package/static/assets/{chunk-MOJQB5TN-DCSpai7w.js → chunk-MOJQB5TN-BVmlLOXk.js} +1 -1
- package/static/assets/{chunk-RYQCIY6F-CchqtfnI.js → chunk-RYQCIY6F-2pIZN6PI.js} +1 -1
- package/static/assets/{chunk-V7JOEXUC-DaOzsfx7.js → chunk-V7JOEXUC-Dj8a39S7.js} +1 -1
- package/static/assets/{chunk-VR4S4FIN-DmxuoVTy.js → chunk-VR4S4FIN-D2kLoVlo.js} +1 -1
- package/static/assets/{chunk-XXDRQBXY-ChERxfri.js → chunk-XXDRQBXY-x3SQWIAg.js} +1 -1
- package/static/assets/classDiagram-OUVF2IWQ-DPLLyb_P.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DPLLyb_P.js +1 -0
- package/static/assets/{cose-bilkent-JH36ORCC-CO7_eF6b.js → cose-bilkent-JH36ORCC-ltgQwU4R.js} +1 -1
- package/static/assets/{cynefin-VYW2F7L2-CzQkWtdY.js → cynefin-VYW2F7L2-BwJnOy5O.js} +1 -1
- package/static/assets/{cynefinDiagram-TSTJHNR4-BN2o8rFa.js → cynefinDiagram-TSTJHNR4-Bi47Fvnv.js} +1 -1
- package/static/assets/{dagre-VKFMJZFB-GLjBE-Xs.js → dagre-VKFMJZFB-5_l7QdUh.js} +1 -1
- package/static/assets/{diagram-FQU43EPY-Cyv7saju.js → diagram-FQU43EPY-HKDyXOzo.js} +1 -1
- package/static/assets/{diagram-G47NLZAW-B9PmZJ3U.js → diagram-G47NLZAW-xHHInyx1.js} +1 -1
- package/static/assets/{diagram-NH7WQ7WH-ByKD8yJU.js → diagram-NH7WQ7WH-nftmRdjw.js} +1 -1
- package/static/assets/{diagram-OA4YK3LP-BvbW7fIS.js → diagram-OA4YK3LP-CX8gIXu7.js} +1 -1
- package/static/assets/{diagram-WEI45ONY-BgXZ9SJ8.js → diagram-WEI45ONY-BaT3ur6a.js} +1 -1
- package/static/assets/{ebnfDiagram-CCIWWBDH-CXEEJ26B.js → ebnfDiagram-CCIWWBDH-CM9hwJSh.js} +1 -1
- package/static/assets/{erDiagram-Q63AITRT-CcHs_S16.js → erDiagram-Q63AITRT-C9cK36a5.js} +1 -1
- package/static/assets/{flowDiagram-23GEKE2U-Cwg3I3GK.js → flowDiagram-23GEKE2U-BQw_JFaW.js} +1 -1
- package/static/assets/{ganttDiagram-NO4QXBWP-ky6w5Lvy.js → ganttDiagram-NO4QXBWP-Cgg9ZbM9.js} +1 -1
- package/static/assets/{gitGraphDiagram-IHSO6WYX-DN0eRQ7r.js → gitGraphDiagram-IHSO6WYX-SciNAnN3.js} +1 -1
- package/static/assets/{index-Cz5Fcq3G.css → index-CM4GfOLV.css} +1 -1
- package/static/assets/{index-CCC4-Ibh.js → index-HUEMwsDE.js} +167 -167
- package/static/assets/{infoDiagram-FWYZ7A6U-COojw6OC.js → infoDiagram-FWYZ7A6U-BPCKxKmJ.js} +1 -1
- package/static/assets/{ishikawaDiagram-FXEZZL3T-BR5w5ZqM.js → ishikawaDiagram-FXEZZL3T-C0eQKvpo.js} +1 -1
- package/static/assets/{journeyDiagram-5HDEW3XC-B5WtTEBM.js → journeyDiagram-5HDEW3XC-DgrJ90BU.js} +1 -1
- package/static/assets/{kanban-definition-HUTT4EX6-DnQ_zg4E.js → kanban-definition-HUTT4EX6-ISgFrxBl.js} +1 -1
- package/static/assets/{linear-C5kGzenz.js → linear-CaPqviiq.js} +1 -1
- package/static/assets/{mindmap-definition-LN4V7U3C-BMksxBso.js → mindmap-definition-LN4V7U3C-DTpJ3Okf.js} +1 -1
- package/static/assets/{pegDiagram-2B236MQR-BKrF97iU.js → pegDiagram-2B236MQR-D0EbYB8X.js} +1 -1
- package/static/assets/{pieDiagram-ENE6RG2P-DZAQkZwC.js → pieDiagram-ENE6RG2P-lAwviOjS.js} +1 -1
- package/static/assets/{quadrantDiagram-ABIIQ3AL-Dx0-5odM.js → quadrantDiagram-ABIIQ3AL-Dn8yVCWJ.js} +1 -1
- package/static/assets/{railroadDiagram-RFXS5EU6-BbY-CxL_.js → railroadDiagram-RFXS5EU6-BVLcNBsS.js} +1 -1
- package/static/assets/{requirementDiagram-TGXJPOKE-ItSYgvcN.js → requirementDiagram-TGXJPOKE-Bmjsh1uy.js} +1 -1
- package/static/assets/{sankeyDiagram-HTMAVEWB-CjIuRRYE.js → sankeyDiagram-HTMAVEWB-tdNqcgyd.js} +1 -1
- package/static/assets/{sequenceDiagram-DBY2YBRQ-B277PT2z.js → sequenceDiagram-DBY2YBRQ-ChtQkNW9.js} +1 -1
- package/static/assets/{sizeCapture-X5ZJPWSS-BYc46yWE.js → sizeCapture-X5ZJPWSS-BAnRJGV8.js} +1 -1
- package/static/assets/{stateDiagram-2N3HPSRC-Cq41gYoq.js → stateDiagram-2N3HPSRC-CI7wmYAe.js} +1 -1
- package/static/assets/stateDiagram-v2-6OUMAXLB-BJrR07NX.js +1 -0
- package/static/assets/{swimlanes-5IMT3BWC-DMxA2jQL.js → swimlanes-5IMT3BWC-k6NGsb3p.js} +2 -2
- package/static/assets/swimlanesDiagram-G3AALYLV-B0-5v6_-.js +8 -0
- package/static/assets/{timeline-definition-FHXFAJF6-CB8iORNI.js → timeline-definition-FHXFAJF6-CweEa8iU.js} +1 -1
- package/static/assets/{vennDiagram-L72KCM5P-CHwAX4cP.js → vennDiagram-L72KCM5P-DDPbV_sp.js} +1 -1
- package/static/assets/{wardleyDiagram-EHGQE667-DL19kD5s.js → wardleyDiagram-EHGQE667-IP4J8kqe.js} +1 -1
- package/static/assets/{xychartDiagram-FW5EYKEG-b271oXxd.js → xychartDiagram-FW5EYKEG-CMfWLoM8.js} +1 -1
- package/static/index.html +2 -2
- package/static/assets/channel-1MKDXK8w.js +0 -1
- package/static/assets/classDiagram-OUVF2IWQ-bADaUwoJ.js +0 -1
- package/static/assets/classDiagram-v2-EOCWNBFH-bADaUwoJ.js +0 -1
- package/static/assets/stateDiagram-v2-6OUMAXLB-B9V3_HpU.js +0 -1
- package/static/assets/swimlanesDiagram-G3AALYLV-qx8IX4cU.js +0 -8
|
@@ -1,224 +1,225 @@
|
|
|
1
|
-
# Playwright E2E Test Setup Reference
|
|
2
|
-
|
|
3
|
-
## Project Initialization
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
mkdir -p <source-code-path>/e2e
|
|
7
|
-
cd <source-code-path>/e2e
|
|
8
|
-
npm init -y
|
|
9
|
-
npm install -D @playwright/test
|
|
10
|
-
npx playwright install chromium
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## playwright.config.ts
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import { defineConfig } from '@playwright/test';
|
|
17
|
-
|
|
18
|
-
export default defineConfig({
|
|
19
|
-
testDir: './tests',
|
|
20
|
-
fullyParallel: false, // Sequential — module order matters
|
|
21
|
-
forbidOnly: true,
|
|
22
|
-
retries: 1,
|
|
23
|
-
workers: 1, // Single worker — tests share state
|
|
24
|
-
reporter: [['html'], ['list']],
|
|
25
|
-
use: {
|
|
26
|
-
baseURL: 'http://localhost:8080',
|
|
27
|
-
trace: 'on-first-retry',
|
|
28
|
-
screenshot: 'only-on-failure',
|
|
29
|
-
},
|
|
30
|
-
projects: [
|
|
31
|
-
{
|
|
32
|
-
name: 'chromium',
|
|
33
|
-
use: { browserName: 'chromium' },
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
});
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Helper: Login Utility
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
// e2e/helpers/auth.ts
|
|
43
|
-
import { Page } from '@playwright/test';
|
|
44
|
-
|
|
45
|
-
export async function login(page: Page, username: string, password: string) {
|
|
46
|
-
await page.goto('/');
|
|
47
|
-
// Keycloak login page
|
|
48
|
-
await page.fill('#username', username);
|
|
49
|
-
await page.fill('#password', password);
|
|
50
|
-
await page.click('#kc-login');
|
|
51
|
-
await page.waitForURL('**/dashboard**');
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Helper: Data Seeding via CLI
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
// e2e/helpers/seed.ts
|
|
59
|
-
import { execSync } from 'child_process';
|
|
60
|
-
|
|
61
|
-
export function seedMongo(script: string) {
|
|
62
|
-
execSync(`mongosh "mongodb://localhost:27017/urp-hub-middleware" --eval '${script}'`, {
|
|
63
|
-
timeout: 30000,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function publishRabbitMQ(exchange: string, routingKey: string, payload: object) {
|
|
68
|
-
const json = JSON.stringify(payload).replace(/'/g, "'\\''");
|
|
69
|
-
execSync(
|
|
70
|
-
`rabbitmqadmin --host=localhost --port=15672 --username=guest --password=guest ` +
|
|
71
|
-
`publish exchange=${exchange} routing_key=${routingKey} payload='${json}'`,
|
|
72
|
-
{ timeout: 30000 }
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function keycloakAdmin(command: string) {
|
|
77
|
-
execSync(`hub_single_sign_on/kcadm.bat ${command}`, { timeout: 30000 });
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Test File Pattern
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
// e2e/tests/<module-slug>.spec.ts
|
|
85
|
-
import { test, expect } from '@playwright/test';
|
|
86
|
-
import { login } from '../helpers/auth';
|
|
87
|
-
import { seedMongo } from '../helpers/seed';
|
|
88
|
-
|
|
89
|
-
test.describe('<Module Name>', () => {
|
|
90
|
-
|
|
91
|
-
test.beforeAll(async () => {
|
|
92
|
-
// Run seeding script from TEST_SPEC.md Section 4
|
|
93
|
-
seedMongo(`
|
|
94
|
-
db.<collection>.insertMany([...]);
|
|
95
|
-
`);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// NO afterAll cleanup — test data persists for downstream modules
|
|
99
|
-
|
|
100
|
-
test('<SCENARIO-ID>: <description>', async ({ page }) => {
|
|
101
|
-
await login(page, 'hub_admin_test', 'Test@1234');
|
|
102
|
-
// Steps from TEST_SPEC.md scenarios
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Helper: Visual Comparison Utilities
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
// e2e/helpers/visual.ts
|
|
111
|
-
import { Page, expect } from '@playwright/test';
|
|
112
|
-
import { execSync } from 'child_process';
|
|
113
|
-
import path from 'path';
|
|
114
|
-
|
|
115
|
-
// The shared Mockup Hub serves ALL applications' mockups from <root>/mockup.
|
|
116
|
-
// Screens live under /{app_slug}/{role}/{page}. The hub port is configurable
|
|
117
|
-
// (PORT env / mockup.config.json), so read the base URL from config.
|
|
118
|
-
// TEST_MOCKUP_BASE_URL example: http://localhost:
|
|
119
|
-
const MOCKUP_BASE_URL = process.env.TEST_MOCKUP_BASE_URL || 'http://localhost:
|
|
120
|
-
const VISUAL_BASELINES_DIR = path.resolve(__dirname, '..', 'visual-baselines');
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Start the shared Mockup Hub in the background.
|
|
124
|
-
* hubDir is <root>/mockup — the hub is zero-dependency (no npm install needed).
|
|
125
|
-
* Call this in beforeAll of visual tests if baselines need capturing.
|
|
126
|
-
*/
|
|
127
|
-
export function startMockupServer(hubDir: string): void {
|
|
128
|
-
// Start in background — the hub stays alive for the test suite
|
|
129
|
-
execSync(`cd "${hubDir}" && npm start &`, { timeout: 10000 });
|
|
130
|
-
// Give the hub time to start
|
|
131
|
-
execSync('sleep 2');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Capture a baseline screenshot from the Mockup Hub.
|
|
136
|
-
* The hub must be running (default port
|
|
137
|
-
*
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
await page.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
await page.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
import {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
await page
|
|
187
|
-
await page.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
// -
|
|
193
|
-
// -
|
|
194
|
-
// -
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
1
|
+
# Playwright E2E Test Setup Reference
|
|
2
|
+
|
|
3
|
+
## Project Initialization
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
mkdir -p <source-code-path>/e2e
|
|
7
|
+
cd <source-code-path>/e2e
|
|
8
|
+
npm init -y
|
|
9
|
+
npm install -D @playwright/test
|
|
10
|
+
npx playwright install chromium
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## playwright.config.ts
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { defineConfig } from '@playwright/test';
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
testDir: './tests',
|
|
20
|
+
fullyParallel: false, // Sequential — module order matters
|
|
21
|
+
forbidOnly: true,
|
|
22
|
+
retries: 1,
|
|
23
|
+
workers: 1, // Single worker — tests share state
|
|
24
|
+
reporter: [['html'], ['list']],
|
|
25
|
+
use: {
|
|
26
|
+
baseURL: 'http://localhost:8080',
|
|
27
|
+
trace: 'on-first-retry',
|
|
28
|
+
screenshot: 'only-on-failure',
|
|
29
|
+
},
|
|
30
|
+
projects: [
|
|
31
|
+
{
|
|
32
|
+
name: 'chromium',
|
|
33
|
+
use: { browserName: 'chromium' },
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Helper: Login Utility
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// e2e/helpers/auth.ts
|
|
43
|
+
import { Page } from '@playwright/test';
|
|
44
|
+
|
|
45
|
+
export async function login(page: Page, username: string, password: string) {
|
|
46
|
+
await page.goto('/');
|
|
47
|
+
// Keycloak login page
|
|
48
|
+
await page.fill('#username', username);
|
|
49
|
+
await page.fill('#password', password);
|
|
50
|
+
await page.click('#kc-login');
|
|
51
|
+
await page.waitForURL('**/dashboard**');
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Helper: Data Seeding via CLI
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// e2e/helpers/seed.ts
|
|
59
|
+
import { execSync } from 'child_process';
|
|
60
|
+
|
|
61
|
+
export function seedMongo(script: string) {
|
|
62
|
+
execSync(`mongosh "mongodb://localhost:27017/urp-hub-middleware" --eval '${script}'`, {
|
|
63
|
+
timeout: 30000,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function publishRabbitMQ(exchange: string, routingKey: string, payload: object) {
|
|
68
|
+
const json = JSON.stringify(payload).replace(/'/g, "'\\''");
|
|
69
|
+
execSync(
|
|
70
|
+
`rabbitmqadmin --host=localhost --port=15672 --username=guest --password=guest ` +
|
|
71
|
+
`publish exchange=${exchange} routing_key=${routingKey} payload='${json}'`,
|
|
72
|
+
{ timeout: 30000 }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function keycloakAdmin(command: string) {
|
|
77
|
+
execSync(`hub_single_sign_on/kcadm.bat ${command}`, { timeout: 30000 });
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Test File Pattern
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// e2e/tests/<module-slug>.spec.ts
|
|
85
|
+
import { test, expect } from '@playwright/test';
|
|
86
|
+
import { login } from '../helpers/auth';
|
|
87
|
+
import { seedMongo } from '../helpers/seed';
|
|
88
|
+
|
|
89
|
+
test.describe('<Module Name>', () => {
|
|
90
|
+
|
|
91
|
+
test.beforeAll(async () => {
|
|
92
|
+
// Run seeding script from TEST_SPEC.md Section 4
|
|
93
|
+
seedMongo(`
|
|
94
|
+
db.<collection>.insertMany([...]);
|
|
95
|
+
`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// NO afterAll cleanup — test data persists for downstream modules
|
|
99
|
+
|
|
100
|
+
test('<SCENARIO-ID>: <description>', async ({ page }) => {
|
|
101
|
+
await login(page, 'hub_admin_test', 'Test@1234');
|
|
102
|
+
// Steps from TEST_SPEC.md scenarios
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Helper: Visual Comparison Utilities
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// e2e/helpers/visual.ts
|
|
111
|
+
import { Page, expect } from '@playwright/test';
|
|
112
|
+
import { execSync } from 'child_process';
|
|
113
|
+
import path from 'path';
|
|
114
|
+
|
|
115
|
+
// The shared Mockup Hub serves ALL applications' mockups from <root>/mockup.
|
|
116
|
+
// Screens live under /{app_slug}/{role}/{page}. The hub port is configurable
|
|
117
|
+
// (PORT env / mockup.config.json), so read the base URL from config.
|
|
118
|
+
// TEST_MOCKUP_BASE_URL example: http://localhost:4000/hub_middleware
|
|
119
|
+
const MOCKUP_BASE_URL = process.env.TEST_MOCKUP_BASE_URL || 'http://localhost:4000/<app_slug>';
|
|
120
|
+
const VISUAL_BASELINES_DIR = path.resolve(__dirname, '..', 'visual-baselines');
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Start the shared Mockup Hub in the background.
|
|
124
|
+
* hubDir is <root>/mockup — the hub is zero-dependency (no npm install needed).
|
|
125
|
+
* Call this in beforeAll of visual tests if baselines need capturing.
|
|
126
|
+
*/
|
|
127
|
+
export function startMockupServer(hubDir: string): void {
|
|
128
|
+
// Start in background — the hub stays alive for the test suite
|
|
129
|
+
execSync(`cd "${hubDir}" && npm start &`, { timeout: 10000 });
|
|
130
|
+
// Give the hub time to start
|
|
131
|
+
execSync('sleep 2');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Capture a baseline screenshot from the Mockup Hub.
|
|
136
|
+
* The hub must be running (default: first unused port from 4000 — pin it with the PORT
|
|
137
|
+
* env when capturing) and MOCKUP_BASE_URL must include
|
|
138
|
+
* the app slug prefix.
|
|
139
|
+
*/
|
|
140
|
+
export async function captureMockupBaseline(
|
|
141
|
+
page: Page,
|
|
142
|
+
mockupRoute: string,
|
|
143
|
+
module: string,
|
|
144
|
+
screenName: string
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
await page.goto(`${MOCKUP_BASE_URL}${mockupRoute}`);
|
|
147
|
+
await page.waitForLoadState('networkidle');
|
|
148
|
+
|
|
149
|
+
const screenshotPath = path.join(VISUAL_BASELINES_DIR, module, `${screenName}.png`);
|
|
150
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Compare the Spring application screen against a mockup baseline.
|
|
155
|
+
* Uses Playwright's toHaveScreenshot with relaxed thresholds to allow
|
|
156
|
+
* content differences while catching aesthetic deviations (colors,
|
|
157
|
+
* alignment, padding, margins, layout structure).
|
|
158
|
+
*/
|
|
159
|
+
export async function compareWithMockup(
|
|
160
|
+
page: Page,
|
|
161
|
+
appRoute: string,
|
|
162
|
+
snapshotName: string
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
await page.goto(appRoute);
|
|
165
|
+
await page.waitForLoadState('networkidle');
|
|
166
|
+
|
|
167
|
+
await expect(page).toHaveScreenshot(snapshotName, {
|
|
168
|
+
maxDiffPixelRatio: 0.15, // Up to 15% pixel difference (content varies)
|
|
169
|
+
threshold: 0.3, // Per-pixel color threshold (0=exact, 1=any)
|
|
170
|
+
animations: 'disabled',
|
|
171
|
+
fullPage: true,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Visual Test File Pattern
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// e2e/tests/<module-slug>.visual.spec.ts
|
|
180
|
+
import { test, expect } from '@playwright/test';
|
|
181
|
+
import { loginAs } from '../helpers/auth';
|
|
182
|
+
|
|
183
|
+
test.describe('<Module Name> - Visual Consistency', () => {
|
|
184
|
+
|
|
185
|
+
test('<screen-name> layout matches mockup', async ({ page }) => {
|
|
186
|
+
await loginAs(page, '<role>');
|
|
187
|
+
await page.goto('<app-route>');
|
|
188
|
+
await page.waitForLoadState('networkidle');
|
|
189
|
+
|
|
190
|
+
// Compares against snapshot — on first run, generates baseline
|
|
191
|
+
// Threshold allows content differences but catches styling issues:
|
|
192
|
+
// - Colors (backgrounds, borders, text, buttons)
|
|
193
|
+
// - Spacing (padding, margins, gaps)
|
|
194
|
+
// - Layout (grid/flex arrangement, proportions)
|
|
195
|
+
// - Typography (font sizes, weights — relative, not exact)
|
|
196
|
+
await expect(page).toHaveScreenshot('<module>-<screen>.png', {
|
|
197
|
+
maxDiffPixelRatio: 0.15,
|
|
198
|
+
threshold: 0.3,
|
|
199
|
+
animations: 'disabled',
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Running Tests
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Run specific module functional tests
|
|
209
|
+
cd <source-code-path>/e2e && npx playwright test tests/<module-slug>.spec.ts
|
|
210
|
+
|
|
211
|
+
# Run specific module visual tests
|
|
212
|
+
cd <source-code-path>/e2e && npx playwright test tests/<module-slug>.visual.spec.ts
|
|
213
|
+
|
|
214
|
+
# Run all tests in order
|
|
215
|
+
cd <source-code-path>/e2e && npx playwright test
|
|
216
|
+
|
|
217
|
+
# Update visual snapshots (after reviewing and approving changes)
|
|
218
|
+
cd <source-code-path>/e2e && npx playwright test tests/<module-slug>.visual.spec.ts --update-snapshots
|
|
219
|
+
|
|
220
|
+
# Run with headed browser (for debugging)
|
|
221
|
+
cd <source-code-path>/e2e && npx playwright test tests/<module-slug>.spec.ts --headed
|
|
222
|
+
|
|
223
|
+
# View HTML report
|
|
224
|
+
cd <source-code-path>/e2e && npx playwright show-report
|
|
225
|
+
```
|