@rashidee/co2 1.3.7 → 1.3.9

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.
Files changed (77) hide show
  1. package/dist/.co2-dat/app.db +0 -0
  2. package/dist/.co2-dat/app.db-shm +0 -0
  3. package/dist/.co2-dat/app.db-wal +0 -0
  4. package/dist/index.js +204 -75
  5. package/package.json +41 -41
  6. package/plugin/skills/conductor-feature-develop/SKILL.md +1384 -1383
  7. package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +225 -224
  8. package/plugin/skills/mockgen-shadcn/SKILL.md +1073 -1067
  9. package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +5 -3
  10. package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +631 -498
  11. package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +2 -2
  12. package/plugin/skills/mockgen-tailwind/SKILL.md +913 -904
  13. package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +722 -720
  14. package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +631 -498
  15. package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -190
  16. package/static/assets/{abnfDiagram-VRR7QNED-CsyqZblo.js → abnfDiagram-VRR7QNED-C0CrqPXX.js} +1 -1
  17. package/static/assets/{arc-it3yvCvj.js → arc-BFSRYIcI.js} +1 -1
  18. package/static/assets/{architectureDiagram-ZJ3FMSHR-sjNv2MOQ.js → architectureDiagram-ZJ3FMSHR-DuwTK2W9.js} +1 -1
  19. package/static/assets/{blockDiagram-677ZJIJ3-DW9ZjtwO.js → blockDiagram-677ZJIJ3-Bn9tAB0n.js} +1 -1
  20. package/static/assets/{c4Diagram-LMCZKHZV-DP3gJkhN.js → c4Diagram-LMCZKHZV-CDiCEjY7.js} +1 -1
  21. package/static/assets/channel-CZGeGYqv.js +1 -0
  22. package/static/assets/{chunk-2Q5K7J3B-ByxTr7KB.js → chunk-2Q5K7J3B-BMwS7NgW.js} +1 -1
  23. package/static/assets/{chunk-32BRIVSS-vNETwpX4.js → chunk-32BRIVSS-8l0XBCjq.js} +1 -1
  24. package/static/assets/{chunk-5VM5RSS4-DbdsRtpE.js → chunk-5VM5RSS4-C6Cv-67E.js} +1 -1
  25. package/static/assets/{chunk-EX3LRPZG-DPkDGikN.js → chunk-EX3LRPZG-DjWs4lB9.js} +1 -1
  26. package/static/assets/{chunk-JWPE2WC7-Ccpx-Rog.js → chunk-JWPE2WC7-DuK_Mu9c.js} +1 -1
  27. package/static/assets/{chunk-MOJQB5TN-CnRPtHb3.js → chunk-MOJQB5TN-K-bFFmpr.js} +1 -1
  28. package/static/assets/{chunk-RYQCIY6F-C--_KRGR.js → chunk-RYQCIY6F-B-b4TyDb.js} +1 -1
  29. package/static/assets/{chunk-V7JOEXUC-BJQ5fwNa.js → chunk-V7JOEXUC-BZRKjyVr.js} +1 -1
  30. package/static/assets/{chunk-VR4S4FIN-BPcVRYqJ.js → chunk-VR4S4FIN-BcvbM_n3.js} +1 -1
  31. package/static/assets/{chunk-XXDRQBXY-BoC3DCKQ.js → chunk-XXDRQBXY-Z2nhcpRg.js} +1 -1
  32. package/static/assets/classDiagram-OUVF2IWQ-DV5beYlE.js +1 -0
  33. package/static/assets/classDiagram-v2-EOCWNBFH-DV5beYlE.js +1 -0
  34. package/static/assets/{cose-bilkent-JH36ORCC-DlKrOw_E.js → cose-bilkent-JH36ORCC-CPBw8Z2X.js} +1 -1
  35. package/static/assets/{cynefin-VYW2F7L2-CZQhaPM1.js → cynefin-VYW2F7L2-DeJ1X-1F.js} +1 -1
  36. package/static/assets/{cynefinDiagram-TSTJHNR4-cZP74_0I.js → cynefinDiagram-TSTJHNR4-BPs3jIZR.js} +1 -1
  37. package/static/assets/{dagre-VKFMJZFB-tB2cBd_e.js → dagre-VKFMJZFB-Bn5Xp63O.js} +1 -1
  38. package/static/assets/{diagram-FQU43EPY-QwoADT9c.js → diagram-FQU43EPY-DWIlVMUR.js} +1 -1
  39. package/static/assets/{diagram-G47NLZAW-Bjz2rwUz.js → diagram-G47NLZAW-BWxpunVI.js} +1 -1
  40. package/static/assets/{diagram-NH7WQ7WH-DS9j6K7F.js → diagram-NH7WQ7WH-CpoAYUis.js} +1 -1
  41. package/static/assets/{diagram-OA4YK3LP-CUPwlGEi.js → diagram-OA4YK3LP-Dv1XfQGK.js} +1 -1
  42. package/static/assets/{diagram-WEI45ONY-iejOZOdV.js → diagram-WEI45ONY-DSk6wZtO.js} +1 -1
  43. package/static/assets/{ebnfDiagram-CCIWWBDH-Bg3puNXE.js → ebnfDiagram-CCIWWBDH-DIDhI2dD.js} +1 -1
  44. package/static/assets/{erDiagram-Q63AITRT-DTxdGEtK.js → erDiagram-Q63AITRT-CGO6ep6G.js} +1 -1
  45. package/static/assets/{flowDiagram-23GEKE2U-CRl-AJlj.js → flowDiagram-23GEKE2U-D8nzLjfG.js} +1 -1
  46. package/static/assets/{ganttDiagram-NO4QXBWP-wCOJ8cfC.js → ganttDiagram-NO4QXBWP-Usz8f26d.js} +1 -1
  47. package/static/assets/{gitGraphDiagram-IHSO6WYX-CnVtKot-.js → gitGraphDiagram-IHSO6WYX-WEJUXa7P.js} +1 -1
  48. package/static/assets/{index-xLqMMC0F.css → index-CM4GfOLV.css} +1 -1
  49. package/static/assets/{index-DAL1vVaa.js → index-j3pCmOym.js} +194 -194
  50. package/static/assets/{infoDiagram-FWYZ7A6U-DmvEl4fi.js → infoDiagram-FWYZ7A6U-BRPwqRvz.js} +1 -1
  51. package/static/assets/{ishikawaDiagram-FXEZZL3T-CjKerAXn.js → ishikawaDiagram-FXEZZL3T-CrntEiqz.js} +1 -1
  52. package/static/assets/{journeyDiagram-5HDEW3XC-Bmc4ARKl.js → journeyDiagram-5HDEW3XC-BmhRsz4F.js} +1 -1
  53. package/static/assets/{kanban-definition-HUTT4EX6-BZ9I3imN.js → kanban-definition-HUTT4EX6-CWxUXdc3.js} +1 -1
  54. package/static/assets/{linear-CrHKLpp_.js → linear-BGjzqzT6.js} +1 -1
  55. package/static/assets/{mindmap-definition-LN4V7U3C-SUk59XXF.js → mindmap-definition-LN4V7U3C-CoVNmO4b.js} +1 -1
  56. package/static/assets/{pegDiagram-2B236MQR-CknFYdh_.js → pegDiagram-2B236MQR-hBHOzL7x.js} +1 -1
  57. package/static/assets/{pieDiagram-ENE6RG2P-B3VlLXXT.js → pieDiagram-ENE6RG2P-Cpv7VZPG.js} +1 -1
  58. package/static/assets/{quadrantDiagram-ABIIQ3AL-BUkZjvW_.js → quadrantDiagram-ABIIQ3AL-BIQAB21D.js} +1 -1
  59. package/static/assets/{railroadDiagram-RFXS5EU6-LklmPimD.js → railroadDiagram-RFXS5EU6-DX6hST3S.js} +1 -1
  60. package/static/assets/{requirementDiagram-TGXJPOKE-DnRFaZbz.js → requirementDiagram-TGXJPOKE-Gas2cE_l.js} +1 -1
  61. package/static/assets/{sankeyDiagram-HTMAVEWB-D242DUlQ.js → sankeyDiagram-HTMAVEWB-BpwoyJDe.js} +1 -1
  62. package/static/assets/{sequenceDiagram-DBY2YBRQ-DmiiStqo.js → sequenceDiagram-DBY2YBRQ-CzBxna5k.js} +1 -1
  63. package/static/assets/{sizeCapture-X5ZJPWSS-cbMvR047.js → sizeCapture-X5ZJPWSS-Cn-5NsLr.js} +1 -1
  64. package/static/assets/{stateDiagram-2N3HPSRC-YIIylI9N.js → stateDiagram-2N3HPSRC-BiW03iCW.js} +1 -1
  65. package/static/assets/stateDiagram-v2-6OUMAXLB-CmQ_Kjkj.js +1 -0
  66. package/static/assets/{swimlanes-5IMT3BWC-Ba309Ysu.js → swimlanes-5IMT3BWC-3jPuufWt.js} +2 -2
  67. package/static/assets/swimlanesDiagram-G3AALYLV-OaMW2HRF.js +8 -0
  68. package/static/assets/{timeline-definition-FHXFAJF6-BdpW3kGY.js → timeline-definition-FHXFAJF6-CzThWt1O.js} +1 -1
  69. package/static/assets/{vennDiagram-L72KCM5P-BiSgQ9Lf.js → vennDiagram-L72KCM5P-D1Axj0CM.js} +1 -1
  70. package/static/assets/{wardleyDiagram-EHGQE667-Clr8YWGW.js → wardleyDiagram-EHGQE667-mKj7UT6Q.js} +1 -1
  71. package/static/assets/{xychartDiagram-FW5EYKEG-LsC066MO.js → xychartDiagram-FW5EYKEG-CSAkvAer.js} +1 -1
  72. package/static/index.html +2 -2
  73. package/static/assets/channel-C_J_aLZ7.js +0 -1
  74. package/static/assets/classDiagram-OUVF2IWQ-yUfBrCjx.js +0 -1
  75. package/static/assets/classDiagram-v2-EOCWNBFH-yUfBrCjx.js +0 -1
  76. package/static/assets/stateDiagram-v2-6OUMAXLB-m0MSGh0v.js +0 -1
  77. package/static/assets/swimlanesDiagram-G3AALYLV-DY_J_ih_.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:3000/hub_middleware
119
- const MOCKUP_BASE_URL = process.env.TEST_MOCKUP_BASE_URL || 'http://localhost:3000/<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 port 3000) and MOCKUP_BASE_URL must include
137
- * the app slug prefix.
138
- */
139
- export async function captureMockupBaseline(
140
- page: Page,
141
- mockupRoute: string,
142
- module: string,
143
- screenName: string
144
- ): Promise<void> {
145
- await page.goto(`${MOCKUP_BASE_URL}${mockupRoute}`);
146
- await page.waitForLoadState('networkidle');
147
-
148
- const screenshotPath = path.join(VISUAL_BASELINES_DIR, module, `${screenName}.png`);
149
- await page.screenshot({ path: screenshotPath, fullPage: true });
150
- }
151
-
152
- /**
153
- * Compare the Spring application screen against a mockup baseline.
154
- * Uses Playwright's toHaveScreenshot with relaxed thresholds to allow
155
- * content differences while catching aesthetic deviations (colors,
156
- * alignment, padding, margins, layout structure).
157
- */
158
- export async function compareWithMockup(
159
- page: Page,
160
- appRoute: string,
161
- snapshotName: string
162
- ): Promise<void> {
163
- await page.goto(appRoute);
164
- await page.waitForLoadState('networkidle');
165
-
166
- await expect(page).toHaveScreenshot(snapshotName, {
167
- maxDiffPixelRatio: 0.15, // Up to 15% pixel difference (content varies)
168
- threshold: 0.3, // Per-pixel color threshold (0=exact, 1=any)
169
- animations: 'disabled',
170
- fullPage: true,
171
- });
172
- }
173
- ```
174
-
175
- ## Visual Test File Pattern
176
-
177
- ```typescript
178
- // e2e/tests/<module-slug>.visual.spec.ts
179
- import { test, expect } from '@playwright/test';
180
- import { loginAs } from '../helpers/auth';
181
-
182
- test.describe('<Module Name> - Visual Consistency', () => {
183
-
184
- test('<screen-name> layout matches mockup', async ({ page }) => {
185
- await loginAs(page, '<role>');
186
- await page.goto('<app-route>');
187
- await page.waitForLoadState('networkidle');
188
-
189
- // Compares against snapshot — on first run, generates baseline
190
- // Threshold allows content differences but catches styling issues:
191
- // - Colors (backgrounds, borders, text, buttons)
192
- // - Spacing (padding, margins, gaps)
193
- // - Layout (grid/flex arrangement, proportions)
194
- // - Typography (font sizes, weights — relative, not exact)
195
- await expect(page).toHaveScreenshot('<module>-<screen>.png', {
196
- maxDiffPixelRatio: 0.15,
197
- threshold: 0.3,
198
- animations: 'disabled',
199
- });
200
- });
201
- });
202
- ```
203
-
204
- ## Running Tests
205
-
206
- ```bash
207
- # Run specific module functional tests
208
- cd <source-code-path>/e2e && npx playwright test tests/<module-slug>.spec.ts
209
-
210
- # Run specific module visual tests
211
- cd <source-code-path>/e2e && npx playwright test tests/<module-slug>.visual.spec.ts
212
-
213
- # Run all tests in order
214
- cd <source-code-path>/e2e && npx playwright test
215
-
216
- # Update visual snapshots (after reviewing and approving changes)
217
- cd <source-code-path>/e2e && npx playwright test tests/<module-slug>.visual.spec.ts --update-snapshots
218
-
219
- # Run with headed browser (for debugging)
220
- cd <source-code-path>/e2e && npx playwright test tests/<module-slug>.spec.ts --headed
221
-
222
- # View HTML report
223
- cd <source-code-path>/e2e && npx playwright show-report
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
+ ```