@postxl/generators 1.16.0 → 1.17.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.
@@ -0,0 +1,65 @@
1
+ # Custom Commands for Claude Code
2
+
3
+ This directory contains custom command documentation that Claude Code can reference when assisting with development tasks.
4
+
5
+ ## Available Commands
6
+
7
+ ### 1. Run E2E Tests (`run-e2e-tests.md`)
8
+ Comprehensive instructions for running end-to-end tests using Playwright.
9
+
10
+ **Trigger phrases:**
11
+ - "run e2e tests"
12
+ - "run end-to-end tests"
13
+ - "execute e2e test suite"
14
+
15
+ ### 2. Fix CI (`fix-ci.md`)
16
+ Guidance for troubleshooting and fixing CI/CD pipeline issues.
17
+
18
+ **Trigger phrases:**
19
+ - "fix ci"
20
+ - "help with continuous integration"
21
+
22
+ ### 3. Browser Observe (`browser-observe.md`)
23
+ Instructions for browser-based testing and observation workflows.
24
+
25
+ **Trigger phrases:**
26
+ - "observe browser"
27
+ - "start browser observation"
28
+
29
+ ### 4. Prepare E2E Tests (`prepare-e2e-tests.md`)
30
+ Analyzes code changes, determines needed E2E test coverage, writes test specs, and runs them to verify they pass.
31
+
32
+ **Trigger phrases:**
33
+ - "prepare e2e tests"
34
+ - "write e2e tests for my changes"
35
+ - "add e2e test coverage"
36
+
37
+ ## How It Works
38
+
39
+ When you ask Claude to perform one of these tasks, it will:
40
+ 1. Automatically detect the request based on your natural language
41
+ 2. Reference the appropriate command file
42
+ 3. Follow the detailed instructions in that file
43
+ 4. Execute the necessary steps to complete the task
44
+
45
+ ## Adding New Commands
46
+
47
+ To add a new custom command:
48
+
49
+ 1. Create a new markdown file in this directory with a descriptive name (e.g., `deploy-staging.md`)
50
+ 2. Write clear, detailed instructions including:
51
+ - Overview of the task
52
+ - Prerequisites
53
+ - Step-by-step instructions
54
+ - Common issues and troubleshooting
55
+ - Examples
56
+ 3. Update this README to list the new command
57
+ 5. Commit all files to version control
58
+
59
+ ## Best Practices
60
+
61
+ - **Be specific**: Include exact commands, file paths, and configuration details
62
+ - **Include context**: Explain why certain steps are necessary
63
+ - **Add troubleshooting**: Document common errors and solutions
64
+ - **Use examples**: Show concrete examples of usage
65
+ - **Keep updated**: Review and update commands as the project evolves
@@ -4,6 +4,8 @@
4
4
  "Read CLAUDE.md for project-specific guidance before making changes",
5
5
  "This is a PostXL-generated project - see the main PostXL monorepo CLAUDE.md for full documentation",
6
6
  "Always run type checks after code changes: pnpm run test:types",
7
- "Use @postxl/ui-components for all UI elements - never create custom generic UI primitives"
7
+ "Use @postxl/ui-components for all UI elements - never create custom generic UI primitives",
8
+ "When asked to 'run e2e tests', 'run end-to-end tests', or 'execute e2e': ALWAYS read the detailed instructions from .claude/commands/run-e2e-tests.md first, then run the automated script at scripts/e2e.sh from the project root",
9
+ "When significant frontend or backend changes are made (new pages, routes, forms, tables, or API endpoints), proactively suggest running /prepare-e2e-tests to ensure E2E test coverage"
8
10
  ]
9
11
  }
@@ -0,0 +1,22 @@
1
+ {
2
+ "prompts": [
3
+ {
4
+ "name": "Run E2E Tests",
5
+ "description": "Run end-to-end tests using Playwright",
6
+ "path": "../.claude/commands/run-e2e-tests.md",
7
+ "tags": ["testing", "e2e", "playwright"]
8
+ },
9
+ {
10
+ "name": "Fix CI",
11
+ "description": "Help fix CI/CD pipeline issues",
12
+ "path": "../.claude/commands/fix-ci.md",
13
+ "tags": ["ci", "devops"]
14
+ },
15
+ {
16
+ "name": "Browser Observe",
17
+ "description": "Browser testing and observation workflows",
18
+ "path": "../.claude/commands/browser-observe.md",
19
+ "tags": ["testing", "browser"]
20
+ }
21
+ ]
22
+ }
@@ -70,7 +70,6 @@ function generateE2EYaml(context) {
70
70
  env:
71
71
  VITE_PUBLIC_API_URL: http://localhost:3001
72
72
  VITE_PUBLIC_BASE_URL: http://localhost:3000
73
- VITE_PUBLIC_MAPBOX_ACCESS_TOKEN: \${{ secrets.MAPBOX_ACCESS_TOKEN }}
74
73
 
75
74
  - name: Check if Backend and Frontend are running and Print Logs
76
75
  run: |
@@ -164,6 +163,14 @@ function generateE2EYaml(context) {
164
163
  ENABLE_BACKEND_SHUTDOWN: true
165
164
  PLAYWRIGHT_WORKERS: 2
166
165
 
166
+ - name: Dump runtime logs on failure
167
+ if: \${{ failure() }}
168
+ run: |
169
+ echo "Backend runtime log tail:"
170
+ tail -n 500 backend/backend.log || true
171
+ echo "Frontend runtime log tail:"
172
+ tail -n 500 frontend/frontend.log || true
173
+
167
174
  - name: Upload Playwright Report
168
175
  uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #v4.6.2
169
176
  if: \${{ !cancelled() }}
@@ -172,6 +179,16 @@ function generateE2EYaml(context) {
172
179
  path: e2e/test-results
173
180
  retention-days: 5
174
181
 
182
+ - name: Upload Runtime Logs
183
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #v4.6.2
184
+ if: \${{ failure() && !cancelled() }}
185
+ with:
186
+ name: runtime-logs
187
+ path: |
188
+ backend/backend.log
189
+ frontend/frontend.log
190
+ retention-days: 5
191
+
175
192
  - name: Upload Backend Coverage
176
193
  uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #v4.6.2
177
194
  if: \${{ !cancelled() }}
@@ -1 +1 @@
1
- {"version":3,"file":"e2e-yml.generator.js","sourceRoot":"","sources":["../../../src/devops/generators/e2e-yml.generator.ts"],"names":[],"mappings":";;AAEA,0CAoMC;AApMD,SAAgB,eAAe,CAAC,OAA4B;IAC1D,MAAM,EACJ,MAAM,EAAE,EAAE,QAAQ,EAAE,GACrB,GAAG,OAAO,CAAA;IACX,0FAA0F;IAC1F,MAAM,gBAAgB,GAAG,gDAAgD,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IAE9F,OAAO,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;QAqBT,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;UAoB9C,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;cAgB3C,QAAQ,CAAC,CAAC,CAAC,yBAAyB,gBAAgB,GAAG,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmIzE,CAAA;AACD,CAAC;AAED,MAAM,sBAAsB,GAAG,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAkB,EAAE,EAAE,CAAC;;;;;6BAKxD,QAAQ;iCACJ,QAAQ;2BACd,QAAQ;;;;;;;;;CASlC,CAAA;AAED,MAAM,aAAa,GAAG,CAAC,gBAAwB,EAAE,EAAE,CAAC;;;;;oCAKhB,gBAAgB;CACnD,CAAA"}
1
+ {"version":3,"file":"e2e-yml.generator.js","sourceRoot":"","sources":["../../../src/devops/generators/e2e-yml.generator.ts"],"names":[],"mappings":";;AAEA,0CAqNC;AArND,SAAgB,eAAe,CAAC,OAA4B;IAC1D,MAAM,EACJ,MAAM,EAAE,EAAE,QAAQ,EAAE,GACrB,GAAG,OAAO,CAAA;IACX,0FAA0F;IAC1F,MAAM,gBAAgB,GAAG,gDAAgD,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IAE9F,OAAO,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;QAqBT,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;UAoB9C,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;cAgB3C,QAAQ,CAAC,CAAC,CAAC,yBAAyB,gBAAgB,GAAG,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoJzE,CAAA;AACD,CAAC;AAED,MAAM,sBAAsB,GAAG,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAkB,EAAE,EAAE,CAAC;;;;;6BAKxD,QAAQ;iCACJ,QAAQ;2BACd,QAAQ;;;;;;;;;CASlC,CAAA;AAED,MAAM,aAAa,GAAG,CAAC,gBAAwB,EAAE,EAAE,CAAC;;;;;oCAKhB,gBAAgB;CACnD,CAAA"}
@@ -28,8 +28,7 @@ ENV TURBO_TEAM=postxl
28
28
  # Safely use Turborepo secret according to https://docs.docker.com/build/building/secrets/
29
29
  RUN --mount=type=secret,id=TURBO_TOKEN \
30
30
  VITE_PUBLIC_API_URL=PLACEHOLDER_VITE_PUBLIC_API_URL VITE_PUBLIC_BASE_URL=PLACEHOLDER_VITE_PUBLIC_BASE_URL \
31
- VITE_PUBLIC_MAPBOX_ACCESS_TOKEN=PLACEHOLDER_VITE_PUBLIC_MAPBOX_ACCESS_TOKEN VITE_PUBLIC_ENABLE_ANALYTICS=PLACEHOLDER_VITE_PUBLIC_ENABLE_ANALYTICS \
32
- VITE_PUBLIC_ANALYTICS_SCRIPT_URL=PLACEHOLDER_VITE_PUBLIC_ANALYTICS_SCRIPT_URL VITE_PUBLIC_KEYCLOAK_URL=PLACEHOLDER_VITE_PUBLIC_KEYCLOAK_URL \
31
+ VITE_PUBLIC_KEYCLOAK_URL=PLACEHOLDER_VITE_PUBLIC_KEYCLOAK_URL \
33
32
  VITE_PUBLIC_KEYCLOAK_CLIENT_ID=PLACEHOLDER_VITE_PUBLIC_KEYCLOAK_CLIENT_ID VITE_PUBLIC_KEYCLOAK_REALM=PLACEHOLDER_VITE_PUBLIC_KEYCLOAK_REALM \
34
33
  VITE_PUBLIC_CHECK_LOGIN_IFRAME=PLACEHOLDER_VITE_PUBLIC_CHECK_LOGIN_IFRAME \
35
34
  pnpm ${isWorkspace ? `turbo:ci build --filter=@postxl/${slug} --filter=@postxl/${slug}-frontend --filter=@postxl/${slug}-api` : `turbo run build --filter=@postxl/${slug}-frontend --filter=@postxl/${slug}-api`}
@@ -1 +1 @@
1
- {"version":3,"file":"frontend-dockerfile.generator.js","sourceRoot":"","sources":["../../../src/devops/generators/frontend-dockerfile.generator.ts"],"names":[],"mappings":";;AAEA,gEAKC;AALD,SAAgB,0BAA0B,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAuB;IAC/F,MAAM,WAAW,GAAG,WAAW,KAAK,WAAW,CAAA;IAC/C,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IAExD,OAAO,kBAAkB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;AAC7D,CAAC;AAED,MAAM,kBAAkB,GAAG,CAAC,EAC1B,SAAS,EACT,IAAI,EACJ,WAAW,GAKZ,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAA;;oCAEK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;SAwBzD,WAAW,CAAC,CAAC,CAAC,mCAAmC,IAAI,qBAAqB,IAAI,8BAA8B,IAAI,MAAM,CAAC,CAAC,CAAC,oCAAoC,IAAI,8BAA8B,IAAI,MAAM;;;;;;;;;;;;;6BAarL,SAAS;;;6BAGT,SAAS;;;aAGzB,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;aAUxC,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;CAUpD,CAAA"}
1
+ {"version":3,"file":"frontend-dockerfile.generator.js","sourceRoot":"","sources":["../../../src/devops/generators/frontend-dockerfile.generator.ts"],"names":[],"mappings":";;AAEA,gEAKC;AALD,SAAgB,0BAA0B,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAuB;IAC/F,MAAM,WAAW,GAAG,WAAW,KAAK,WAAW,CAAA;IAC/C,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IAExD,OAAO,kBAAkB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;AAC7D,CAAC;AAED,MAAM,kBAAkB,GAAG,CAAC,EAC1B,SAAS,EACT,IAAI,EACJ,WAAW,GAKZ,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAA;;oCAEK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;SAuBzD,WAAW,CAAC,CAAC,CAAC,mCAAmC,IAAI,qBAAqB,IAAI,8BAA8B,IAAI,MAAM,CAAC,CAAC,CAAC,oCAAoC,IAAI,8BAA8B,IAAI,MAAM;;;;;;;;;;;;;6BAarL,SAAS;;;6BAGT,SAAS;;;aAGzB,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;aAUxC,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;CAUpD,CAAA"}
@@ -38,8 +38,7 @@ function generateJenkinsfile({ devOps: { database, keycloak, useS3 }, schema: {
38
38
  -e KEYCLOAK_LOGOUT_REDIRECT_URL=\${KEYCLOAK_LOGOUT_REDIRECT_URL} \\`
39
39
  : '';
40
40
  const keycloakRunEnvFrontend = keycloak
41
- ? `
42
- -e VITE_PUBLIC_KEYCLOAK_URL=\${KEYCLOAK_URL} \\
41
+ ? ` -e VITE_PUBLIC_KEYCLOAK_URL=\${KEYCLOAK_URL} \\
43
42
  -e VITE_PUBLIC_KEYCLOAK_CLIENT_ID=\${KEYCLOAK_CLIENT_ID} \\
44
43
  -e VITE_PUBLIC_KEYCLOAK_REALM=\${KEYCLOAK_REALM} \\`
45
44
  : '';
@@ -82,7 +81,6 @@ function generateJenkinsfile({ devOps: { database, keycloak, useS3 }, schema: {
82
81
  return /*Jenkinsfile*/ `
83
82
  def secrets = [${dbSecrets}${keycloakSecrets}
84
83
  [envVar: 'TURBO_TOKEN', secretRef: 'op://PostXL service/Turborepo Cache Token/password'],
85
- [envVar: 'MAPBOX_ACCESS_TOKEN', secretRef: 'op://PostXL service/Mapbox Access Token/password'],
86
84
  ]
87
85
 
88
86
  pipeline {
@@ -154,9 +152,7 @@ pipeline {
154
152
  \${REGISTRY}/\${PROJECT_NAME}_backend:latest"
155
153
  sh "\${SSH_COMMAND} docker run -d --name \${PROJECT_NAME}_frontend \\
156
154
  -e VITE_PUBLIC_API_URL=\${API_URL} \\
157
- -e VITE_PUBLIC_BASE_URL=\${FRONTEND_URL} \\
158
- -e VITE_PUBLIC_MAPBOX_ACCESS_TOKEN=\${MAPBOX_ACCESS_TOKEN} \\
159
- -e VITE_PUBLIC_ENABLE_ANALYTICS=false \\${keycloakRunEnvFrontend}
155
+ -e VITE_PUBLIC_BASE_URL=\${FRONTEND_URL} \\${keycloakRunEnvFrontend ? `\n${keycloakRunEnvFrontend}` : ''}
160
156
  -e VITE_PUBLIC_CHECK_LOGIN_IFRAME=true \\
161
157
  -p 0.0.0.0:\${PORT_MAPPING_FRONTEND} \\
162
158
  \${REGISTRY}/\${PROJECT_NAME}_frontend:latest"
@@ -1 +1 @@
1
- {"version":3,"file":"jenkinsfile.generator.js","sourceRoot":"","sources":["../../../src/devops/generators/jenkinsfile.generator.ts"],"names":[],"mappings":";;AAEA,kDAmLC;AAnLD,SAAgB,mBAAmB,CAAC,EAClC,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EACrC,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,GACT;IACpB,iFAAiF;IACjF,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACtC,MAAM,SAAS,GAAG,QAAQ;QACxB,CAAC,CAAC;mFAC6E,IAAI;mFACJ,IAAI;KAClF;QACD,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,eAAe,GAAG,QAAQ;QAC9B,CAAC,CAAC;oGAC8F,IAAI;KACnG;QACD,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,WAAW,GAAG,QAAQ;QAC1B,CAAC,CAAC;;gCAE0B,IAAI;4BACR,IAAI;;;SAGvB;QACL,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,KAAK,GAAG,QAAQ;QACpB,CAAC,CAAC;wIACkI;QACpI,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,cAAc,GAAG,QAAQ;QAC7B,CAAC,CAAC;;;;;;0GAMoG;QACtG,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,sBAAsB,GAAG,QAAQ;QACrC,CAAC,CAAC;;;sFAGgF;QAClF,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,KAAK,GAAG,KAAK;QACjB,CAAC,CAAC;;;;SAIG;QACL,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,QAAQ,GAAG,KAAK;QACpB,CAAC,CAAC;;;sEAGgE;QAClE,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,KAAK,GAAG;;;;;;;;;;;SAWP,CAAA;IAEP,MAAM,QAAQ,GAAG;;;;;;;;;;sGAUmF,CAAA;IAEpG,OAAO,eAAe,CAAC;iBACR,SAAS,GAAG,eAAe;;;;;;;;;;;;;;0BAclB,IAAI;;;;;;6BAMD,IAAI;;;kCAGC,IAAI;;UAE5B,WAAW,GAAG,KAAK,GAAG,KAAK;;;;;;;sBAOf,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG;;;;;;;;sBAQvD,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;uEA2BN,KAAK,GAAG,cAAc,GAAG,QAAQ;wCAChE,QAAQ;;;;;;;;4EAQ4B,sBAAsB;;;;;;;;;;CAUjG,CAAA;AACD,CAAC"}
1
+ {"version":3,"file":"jenkinsfile.generator.js","sourceRoot":"","sources":["../../../src/devops/generators/jenkinsfile.generator.ts"],"names":[],"mappings":";;AAEA,kDA+KC;AA/KD,SAAgB,mBAAmB,CAAC,EAClC,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EACrC,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,GACT;IACpB,iFAAiF;IACjF,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACtC,MAAM,SAAS,GAAG,QAAQ;QACxB,CAAC,CAAC;mFAC6E,IAAI;mFACJ,IAAI;KAClF;QACD,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,eAAe,GAAG,QAAQ;QAC9B,CAAC,CAAC;oGAC8F,IAAI;KACnG;QACD,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,WAAW,GAAG,QAAQ;QAC1B,CAAC,CAAC;;gCAE0B,IAAI;4BACR,IAAI;;;SAGvB;QACL,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,KAAK,GAAG,QAAQ;QACpB,CAAC,CAAC;wIACkI;QACpI,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,cAAc,GAAG,QAAQ;QAC7B,CAAC,CAAC;;;;;;0GAMoG;QACtG,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,sBAAsB,GAAG,QAAQ;QACrC,CAAC,CAAC;;sFAEgF;QAClF,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,KAAK,GAAG,KAAK;QACjB,CAAC,CAAC;;;;SAIG;QACL,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,QAAQ,GAAG,KAAK;QACpB,CAAC,CAAC;;;sEAGgE;QAClE,CAAC,CAAC,EAAE,CAAA;IAEN,MAAM,KAAK,GAAG;;;;;;;;;;;SAWP,CAAA;IAEP,MAAM,QAAQ,GAAG;;;;;;;;;;sGAUmF,CAAA;IAEpG,OAAO,eAAe,CAAC;iBACR,SAAS,GAAG,eAAe;;;;;;;;;;;;;0BAalB,IAAI;;;;;;6BAMD,IAAI;;;kCAGC,IAAI;;UAE5B,WAAW,GAAG,KAAK,GAAG,KAAK;;;;;;;sBAOf,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG;;;;;;;;sBAQvD,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;uEA2BN,KAAK,GAAG,cAAc,GAAG,QAAQ;wCAChE,QAAQ;;;;;;+EAM+B,sBAAsB,CAAC,CAAC,CAAC,KAAK,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;CAUzI,CAAA;AACD,CAAC"}
@@ -1,15 +1,12 @@
1
1
  #!/bin/sh
2
2
 
3
- if [ -z "$VITE_PUBLIC_API_URL" ] || [ -z "$VITE_PUBLIC_BASE_URL" ] || [ -z "$VITE_PUBLIC_MAPBOX_ACCESS_TOKEN" ] || [ -z "$VITE_PUBLIC_ENABLE_ANALYTICS" ] || [ -z "$VITE_PUBLIC_KEYCLOAK_URL" ] || [ -z "$VITE_PUBLIC_KEYCLOAK_CLIENT_ID" ] || [ -z "$VITE_PUBLIC_KEYCLOAK_REALM" ] || [ -z "$VITE_PUBLIC_CHECK_LOGIN_IFRAME" ]; then
4
- echo "Error: VITE_PUBLIC_API_URL, VITE_PUBLIC_BASE_URL, VITE_PUBLIC_MAPBOX_ACCESS_TOKEN, VITE_PUBLIC_ENABLE_ANALYTICS, VITE_PUBLIC_KEYCLOAK_URL, VITE_PUBLIC_KEYCLOAK_CLIENT_ID, VITE_PUBLIC_KEYCLOAK_REALM, VITE_PUBLIC_CHECK_LOGIN_IFRAME is not defined" >&2
3
+ if [ -z "$VITE_PUBLIC_API_URL" ] || [ -z "$VITE_PUBLIC_BASE_URL" ] || [ -z "$VITE_PUBLIC_KEYCLOAK_URL" ] || [ -z "$VITE_PUBLIC_KEYCLOAK_CLIENT_ID" ] || [ -z "$VITE_PUBLIC_KEYCLOAK_REALM" ] || [ -z "$VITE_PUBLIC_CHECK_LOGIN_IFRAME" ]; then
4
+ echo "Error: VITE_PUBLIC_API_URL, VITE_PUBLIC_BASE_URL, VITE_PUBLIC_KEYCLOAK_URL, VITE_PUBLIC_KEYCLOAK_CLIENT_ID, VITE_PUBLIC_KEYCLOAK_REALM, VITE_PUBLIC_CHECK_LOGIN_IFRAME is not defined" >&2
5
5
  exit 1
6
6
  fi
7
7
 
8
8
  find /app/frontend/dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PLACEHOLDER_VITE_PUBLIC_API_URL#$VITE_PUBLIC_API_URL#g"
9
9
  find /app/frontend/dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PLACEHOLDER_VITE_PUBLIC_BASE_URL#$VITE_PUBLIC_BASE_URL#g"
10
- find /app/frontend/dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PLACEHOLDER_VITE_PUBLIC_MAPBOX_ACCESS_TOKEN#$VITE_PUBLIC_MAPBOX_ACCESS_TOKEN#g"
11
- find /app/frontend/dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PLACEHOLDER_VITE_PUBLIC_ENABLE_ANALYTICS#$VITE_PUBLIC_ENABLE_ANALYTICS#g"
12
- find /app/frontend/dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PLACEHOLDER_VITE_PUBLIC_ANALYTICS_SCRIPT_URL#$VITE_PUBLIC_ANALYTICS_SCRIPT_URL#g"
13
10
  find /app/frontend/dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PLACEHOLDER_VITE_PUBLIC_KEYCLOAK_URL#$VITE_PUBLIC_KEYCLOAK_URL#g"
14
11
  find /app/frontend/dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PLACEHOLDER_VITE_PUBLIC_KEYCLOAK_CLIENT_ID#$VITE_PUBLIC_KEYCLOAK_CLIENT_ID#g"
15
12
  find /app/frontend/dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PLACEHOLDER_VITE_PUBLIC_KEYCLOAK_REALM#$VITE_PUBLIC_KEYCLOAK_REALM#g"
@@ -54,7 +54,23 @@ exports.generator = {
54
54
  },
55
55
  generate: async (context) => {
56
56
  const vfs = new Generator.VirtualFileSystem();
57
- const templateContext = { schema: context.schema };
57
+ const { projectType, slug } = context.schema;
58
+ const isWorkspace = projectType === 'workspace';
59
+ // Template context with project-type-aware values
60
+ const templateContext = {
61
+ schema: context.schema,
62
+ // For e2e.sh
63
+ monorepoRootExpr: isWorkspace ? '"$(cd "$PROJECT_DIR/../.." && pwd)"' : '"$PROJECT_DIR"',
64
+ dockerWorkDir: isWorkspace ? `/pxl/projects/${slug}/e2e` : '/pxl/e2e',
65
+ e2eArtifactsPath: isWorkspace ? `/projects/${slug}/e2e` : '/e2e',
66
+ nodeShimsCleanup: isWorkspace
67
+ ? 'rm -f /pxl/node_modules/.bin/node /pxl/projects/*/node_modules/.bin/node 2>/dev/null'
68
+ : 'rm -f /pxl/node_modules/.bin/node 2>/dev/null',
69
+ // For run-e2e-tests.md
70
+ cdProjectDir: isWorkspace ? `cd projects/${slug}\n` : '',
71
+ backendPath: isWorkspace ? `projects/${slug}/backend` : 'backend',
72
+ frontendPath: isWorkspace ? `projects/${slug}/frontend` : 'frontend',
73
+ };
58
74
  // Load e2e folder, excluding files that need template substitution
59
75
  await vfs.loadFolder({
60
76
  diskPath: path.resolve(__dirname, './template/e2e'),
@@ -70,6 +86,33 @@ exports.generator = {
70
86
  throw new Error(`Failed to generate package.json: ${packageJsonContent.unwrapErr().message}`);
71
87
  }
72
88
  vfs.write(`/e2e/${PACKAGE_JSON_FILENAME}`, packageJsonContent.unwrap());
89
+ // Generate e2e.sh with project-type-aware paths
90
+ const e2eShContent = await Generator.generateFromTemplate({
91
+ file: path.resolve(__dirname, './template/scripts/e2e.sh'),
92
+ context: templateContext,
93
+ });
94
+ if (e2eShContent.isErr()) {
95
+ throw new Error(`Failed to generate e2e.sh: ${e2eShContent.unwrapErr().message}`);
96
+ }
97
+ vfs.write('/scripts/e2e.sh', e2eShContent.unwrap());
98
+ // Generate run-e2e-tests.md with project-type-aware paths
99
+ const runE2eTestsMdContent = await Generator.generateFromTemplate({
100
+ file: path.resolve(__dirname, './template/.claude/commands/run-e2e-tests.md'),
101
+ context: templateContext,
102
+ });
103
+ if (runE2eTestsMdContent.isErr()) {
104
+ throw new Error(`Failed to generate run-e2e-tests.md: ${runE2eTestsMdContent.unwrapErr().message}`);
105
+ }
106
+ vfs.write('/.claude/commands/run-e2e-tests.md', runE2eTestsMdContent.unwrap());
107
+ // Generate prepare-e2e-tests.md with project-type-aware paths
108
+ const prepareE2eTestsMdContent = await Generator.generateFromTemplate({
109
+ file: path.resolve(__dirname, './template/.claude/commands/prepare-e2e-tests.md'),
110
+ context: templateContext,
111
+ });
112
+ if (prepareE2eTestsMdContent.isErr()) {
113
+ throw new Error(`Failed to generate prepare-e2e-tests.md: ${prepareE2eTestsMdContent.unwrapErr().message}`);
114
+ }
115
+ vfs.write('/.claude/commands/prepare-e2e-tests.md', prepareE2eTestsMdContent.unwrap());
73
116
  // write dynamic files
74
117
  vfs.write('/e2e/support/model-test-ids.ts', (0, model_test_id_generator_1.generateModelTestIds)(context.e2e));
75
118
  vfs.write('/scripts/docker.sh', (0, docker_sh_generator_1.generateDockerSh)(context));
@@ -1 +1 @@
1
- {"version":3,"file":"e2e.generator.js","sourceRoot":"","sources":["../../src/e2e/e2e.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAiC;AAEjC,6DAA8C;AAI9C,0EAAmE;AACnE,kFAA2E;AAE3E,MAAM,qBAAqB,GAAG,cAAc,CAAA;AAsB/B,QAAA,WAAW,GAAG,SAAS,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAA;AAErD,QAAA,SAAS,GAAiC;IACrD,EAAE,EAAE,mBAAW;IACf,QAAQ,EAAE,CAAC,mBAAW,CAAC;IAEvB,QAAQ,EAAE,CAAiD,GAAY,EAAiB,EAAE;QACxF,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QACvF,OAAO;YACL,GAAG,GAAG;YACN,GAAG,EAAE;gBACH,OAAO,EAAE,EAAE;aACZ;SACF,CAAA;IACH,CAAC;IAED,QAAQ,EAAE,KAAK,EAAiC,OAAgB,EAAoB,EAAE;QACpF,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,iBAAiB,EAAE,CAAA;QAC7C,MAAM,eAAe,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;QAElD,mEAAmE;QACnE,MAAM,GAAG,CAAC,UAAU,CAAC;YACnB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gBAAgB,CAAC;YACnD,UAAU,EAAE,MAAM;YAClB,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,qBAAqB,CAAC;SAChE,CAAC,CAAA;QAEF,wCAAwC;QACxC,MAAM,kBAAkB,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC;YAC9D,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gBAAgB,EAAE,qBAAqB,CAAC;YACtE,OAAO,EAAE,eAAe;SACzB,CAAC,CAAA;QACF,IAAI,kBAAkB,CAAC,KAAK,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,kBAAkB,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/F,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,QAAQ,qBAAqB,EAAE,EAAE,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAA;QAEvE,sBAAsB;QACtB,GAAG,CAAC,KAAK,CAAC,gCAAgC,EAAE,IAAA,8CAAoB,EAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;QAC9E,GAAG,CAAC,KAAK,CAAC,oBAAoB,EAAE,IAAA,sCAAgB,EAAC,OAAO,CAAC,CAAC,CAAA;QAE1D,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;QAEnD,OAAO,OAAO,CAAA;IAChB,CAAC;CACF,CAAA"}
1
+ {"version":3,"file":"e2e.generator.js","sourceRoot":"","sources":["../../src/e2e/e2e.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAiC;AAEjC,6DAA8C;AAI9C,0EAAmE;AACnE,kFAA2E;AAE3E,MAAM,qBAAqB,GAAG,cAAc,CAAA;AAsB/B,QAAA,WAAW,GAAG,SAAS,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAA;AAErD,QAAA,SAAS,GAAiC;IACrD,EAAE,EAAE,mBAAW;IACf,QAAQ,EAAE,CAAC,mBAAW,CAAC;IAEvB,QAAQ,EAAE,CAAiD,GAAY,EAAiB,EAAE;QACxF,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QACvF,OAAO;YACL,GAAG,GAAG;YACN,GAAG,EAAE;gBACH,OAAO,EAAE,EAAE;aACZ;SACF,CAAA;IACH,CAAC;IAED,QAAQ,EAAE,KAAK,EAAiC,OAAgB,EAAoB,EAAE;QACpF,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,iBAAiB,EAAE,CAAA;QAC7C,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAA;QAC5C,MAAM,WAAW,GAAG,WAAW,KAAK,WAAW,CAAA;QAE/C,kDAAkD;QAClD,MAAM,eAAe,GAAG;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,aAAa;YACb,gBAAgB,EAAE,WAAW,CAAC,CAAC,CAAC,qCAAqC,CAAC,CAAC,CAAC,gBAAgB;YACxF,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,iBAAiB,IAAI,MAAM,CAAC,CAAC,CAAC,UAAU;YACrE,gBAAgB,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM;YAChE,gBAAgB,EAAE,WAAW;gBAC3B,CAAC,CAAC,sFAAsF;gBACxF,CAAC,CAAC,+CAA+C;YACnD,uBAAuB;YACvB,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE;YACxD,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,YAAY,IAAI,UAAU,CAAC,CAAC,CAAC,SAAS;YACjE,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,YAAY,IAAI,WAAW,CAAC,CAAC,CAAC,UAAU;SACrE,CAAA;QAED,mEAAmE;QACnE,MAAM,GAAG,CAAC,UAAU,CAAC;YACnB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gBAAgB,CAAC;YACnD,UAAU,EAAE,MAAM;YAClB,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,qBAAqB,CAAC;SAChE,CAAC,CAAA;QAEF,wCAAwC;QACxC,MAAM,kBAAkB,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC;YAC9D,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gBAAgB,EAAE,qBAAqB,CAAC;YACtE,OAAO,EAAE,eAAe;SACzB,CAAC,CAAA;QACF,IAAI,kBAAkB,CAAC,KAAK,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,kBAAkB,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/F,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,QAAQ,qBAAqB,EAAE,EAAE,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAA;QAEvE,gDAAgD;QAChD,MAAM,YAAY,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC;YACxD,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,2BAA2B,CAAC;YAC1D,OAAO,EAAE,eAAe;SACzB,CAAC,CAAA;QACF,IAAI,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,8BAA8B,YAAY,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QACnF,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,iBAAiB,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAA;QAEnD,0DAA0D;QAC1D,MAAM,oBAAoB,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC;YAChE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,8CAA8C,CAAC;YAC7E,OAAO,EAAE,eAAe;SACzB,CAAC,CAAA;QACF,IAAI,oBAAoB,CAAC,KAAK,EAAE,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,wCAAwC,oBAAoB,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QACrG,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,oCAAoC,EAAE,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAA;QAE9E,8DAA8D;QAC9D,MAAM,wBAAwB,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC;YACpE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,kDAAkD,CAAC;YACjF,OAAO,EAAE,eAAe;SACzB,CAAC,CAAA;QACF,IAAI,wBAAwB,CAAC,KAAK,EAAE,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,4CAA4C,wBAAwB,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QAC7G,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,wCAAwC,EAAE,wBAAwB,CAAC,MAAM,EAAE,CAAC,CAAA;QAEtF,sBAAsB;QACtB,GAAG,CAAC,KAAK,CAAC,gCAAgC,EAAE,IAAA,8CAAoB,EAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;QAC9E,GAAG,CAAC,KAAK,CAAC,oBAAoB,EAAE,IAAA,sCAAgB,EAAC,OAAO,CAAC,CAAC,CAAA;QAE1D,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;QAEnD,OAAO,OAAO,CAAA;IAChB,CAAC;CACF,CAAA"}
@@ -0,0 +1,251 @@
1
+ # Prepare E2E Tests
2
+
3
+ You are tasked with analyzing code changes and preparing end-to-end (E2E) tests for the <% schema.slug %> project.
4
+
5
+ ## Arguments
6
+
7
+ - `$ARGUMENTS` - Optional: specific features or areas to focus testing on. If not provided, analyze the git diff automatically.
8
+
9
+ ## Overview
10
+
11
+ This command analyzes your recent code changes, determines what E2E test coverage is needed, writes the test specs following existing patterns, and runs them to verify they pass.
12
+
13
+ ## Step 1: Analyze Changes
14
+
15
+ ### 1.1 Get Changed Files
16
+
17
+ Determine what has changed by examining the git diff:
18
+
19
+ ```bash
20
+ # If on a feature branch, diff against main
21
+ git diff main...HEAD --name-only
22
+
23
+ # Also check for any uncommitted changes
24
+ git diff --name-only
25
+ git diff --staged --name-only
26
+ ```
27
+
28
+ If `$ARGUMENTS` was provided, focus analysis on the specified features or areas instead of the full diff.
29
+
30
+ ### 1.2 Categorize Changes
31
+
32
+ Read each changed file and categorize it:
33
+
34
+ - **Frontend pages/routes**: Files in `<% frontendPath %>/src/routes/` — need navigation tests, content verification, and visual snapshots
35
+ - **Frontend components**: Files in `<% frontendPath %>/src/components/` or `<% frontendPath %>/src/pages/` — need interaction tests
36
+ - **Backend endpoints/routers**: Files in `<% backendPath %>/libs/router-trpc/` or `<% backendPath %>/libs/actions/` — need frontend integration tests verifying data display
37
+ - **Backend views**: Files in `<% backendPath %>/libs/view/` — need data display verification tests
38
+ - **Shared types**: Files in `<% backendPath %>/libs/types/` — may affect existing tests
39
+ - **Schema changes**: `postxl-schema.json` — affect models and may need comprehensive new tests
40
+ - **Forms**: Components containing form elements (TanStack Form, inputs, selects) — need form interaction tests
41
+ - **Tables/DataGrids**: Components using DataGrid — need data display, sorting, and filtering tests
42
+
43
+ ### 1.3 Read Changed Files
44
+
45
+ For each changed file, read its content to understand:
46
+
47
+ - What routes/URLs are added or modified
48
+ - What data is displayed (model names, field names)
49
+ - What user interactions are possible (clicks, form fills, navigation)
50
+ - What test IDs are used (`data-test-id` attributes)
51
+
52
+ Also read these reference files for context:
53
+
54
+ ```
55
+ e2e/support/model-test-ids.ts # Available test IDs
56
+ e2e/fixtures/test-fixtures.ts # Available test fixtures
57
+ e2e/support/wait-for-page-loaded.ts # Page loading utility
58
+ e2e/support/page-stability.ts # API stability utility
59
+ ```
60
+
61
+ ### 1.4 Check Existing Test Coverage
62
+
63
+ Read all existing spec files in `e2e/specs/` to understand what is already covered. Do not duplicate existing tests.
64
+
65
+ ## Step 2: Determine Test Coverage
66
+
67
+ Based on the analysis, determine which tests to write:
68
+
69
+ ### For New Pages/Routes
70
+
71
+ - **Navigation test**: Verify the page is accessible at its URL
72
+ - **Content verification test**: Verify key content elements are visible
73
+ - **Visual regression snapshot**: Capture a baseline screenshot with `toHaveScreenshot()`
74
+
75
+ ### For New Forms
76
+
77
+ - **Form display test**: Verify form fields are rendered correctly
78
+ - **Form interaction test**: Fill fields, submit, and verify success feedback
79
+ - **Form validation test**: Submit with invalid data and verify error messages
80
+
81
+ ### For New Tables/Data Views
82
+
83
+ - **Data display test**: Verify table renders with expected columns and data
84
+ - **Sorting test**: Click column headers and verify sort order (if sortable)
85
+ - **Empty state test**: Verify empty state message when no data exists
86
+
87
+ ### For API/Backend Changes
88
+
89
+ - **Data integration test**: Verify the frontend correctly displays API data
90
+ - **Error state test**: Verify error handling when API returns errors (if applicable)
91
+
92
+ ### For UI Component Changes
93
+
94
+ - **Visual regression snapshot**: Capture screenshots of affected pages
95
+ - **Interaction test**: Verify component interactions work correctly
96
+
97
+ ## Step 3: Write Tests
98
+
99
+ ### 3.1 Test File Structure
100
+
101
+ Create new test files in `e2e/specs/` following the naming convention `<feature>.spec.ts`.
102
+
103
+ **CRITICAL**: Always use the project's custom test fixtures, NOT bare Playwright imports:
104
+
105
+ ```typescript
106
+ import { expect, test } from '../fixtures/test-fixtures'
107
+ ```
108
+
109
+ ### 3.2 Test Patterns to Follow
110
+
111
+ **Reference the existing examples:**
112
+
113
+ - `e2e/specs/example.spec.ts` — basic navigation, visual snapshots, content verification
114
+ - `e2e/specs/example-backend-isolation.spec.ts` — backend isolation pattern
115
+
116
+ **Navigation and Snapshot Test Pattern:**
117
+
118
+ ```typescript
119
+ import { expect, test } from '../fixtures/test-fixtures'
120
+
121
+ test.describe('<Feature Name>', () => {
122
+ test('<Page name> page is accessible', async ({ page }) => {
123
+ await page.goto('/<route>')
124
+ await expect(page).toHaveURL(/.*\/<route>$/)
125
+ })
126
+
127
+ test('<Page name> page visual snapshot', async ({ page }) => {
128
+ await page.goto('/<route>')
129
+ await expect(page).toHaveScreenshot()
130
+ })
131
+ })
132
+ ```
133
+
134
+ **Content Verification Pattern:**
135
+
136
+ ```typescript
137
+ test('<Page> displays expected content', async ({ page }) => {
138
+ await page.goto('/<route>')
139
+ await expect(page.getByText('Expected Heading')).toBeVisible()
140
+ await expect(page.getByTestId('some-test-id')).toBeVisible()
141
+ })
142
+ ```
143
+
144
+ **Form Interaction Pattern:**
145
+
146
+ ```typescript
147
+ test.describe('<Form Name> Form', () => {
148
+ test('can fill and submit form', async ({ page }) => {
149
+ await page.goto('/<form-route>')
150
+
151
+ // Fill form fields
152
+ await page.getByLabel('Field Name').fill('Test Value')
153
+ await page.getByRole('combobox').click()
154
+ await page.getByRole('option', { name: 'Option' }).click()
155
+
156
+ // Submit
157
+ await page.getByRole('button', { name: 'Submit' }).click()
158
+
159
+ // Verify success
160
+ await expect(page.getByText('Success')).toBeVisible()
161
+ })
162
+ })
163
+ ```
164
+
165
+ **Table/DataGrid Verification Pattern:**
166
+
167
+ ```typescript
168
+ test.describe('<Model> Table', () => {
169
+ test('displays data in table', async ({ page }) => {
170
+ await page.goto('/<admin-route>')
171
+
172
+ // Verify table headers
173
+ await expect(page.getByText('Column Header')).toBeVisible()
174
+
175
+ // Verify data is loaded
176
+ await expect(page.locator('table tbody tr').first()).toBeVisible()
177
+ })
178
+ })
179
+ ```
180
+
181
+ ### 3.3 Available Utilities
182
+
183
+ Use these utilities from the `support/` and `fixtures/` directories:
184
+
185
+ - `waitForPageLoaded(page)` from `support/wait-for-page-loaded` — wait for DOM + images
186
+ - `waitForAppIdle(page)` from `support/page-stability` — wait for API requests to complete
187
+ - `resetData()` from `support/reset-data` — reset backend data (stateful mode only)
188
+ - `setData(data)` from `support/set-data` — set specific test data (stateful mode only)
189
+ - `maskToast(page)` from `support/mask-toast` — hide toast notifications for screenshots
190
+ - `clickCheckboxByName(page, name)` from `support/checkbox-click` — click checkboxes reliably
191
+ - `MODEL_TEST_IDS` from `support/model-test-ids` — generated test IDs for models
192
+ - `DataMocker` from `@mock-data/dataMocker.class` — create mock data
193
+
194
+ ### 3.4 Best Practices
195
+
196
+ 1. **Always use custom test fixtures**: `import { expect, test } from '../fixtures/test-fixtures'`
197
+ 2. **Use descriptive test names**: Explain what the test verifies, not how
198
+ 3. **Use `data-test-id` attributes**: Prefer `getByTestId()` for stable selectors
199
+ 4. **Use role-based selectors**: `getByRole('button', { name: '...' })` for accessibility
200
+ 5. **Avoid `waitForTimeout()`**: Use condition-based waits instead
201
+ 6. **Group related tests**: Use `test.describe()` blocks
202
+ 7. **Mask toasts for screenshots**: Use `maskToast(page)` before `toHaveScreenshot()` if toasts interfere
203
+ 8. **Do not modify existing test files** unless the changes specifically affect those tests
204
+
205
+ ## Step 4: Run and Verify
206
+
207
+ ### 4.1 Run the Tests
208
+
209
+ Execute the E2E tests using the automated script:
210
+
211
+ ```bash
212
+ <% cdProjectDir %>./scripts/e2e.sh
213
+ ```
214
+
215
+ ### 4.2 Handle First-Run Snapshot Baselines
216
+
217
+ New tests with `toHaveScreenshot()` will fail on the first run because no baseline snapshot exists yet. This is expected. Re-run the tests to create the baseline and confirm the snapshot is correct.
218
+
219
+ ### 4.3 Investigate Failures
220
+
221
+ If tests fail for reasons other than missing baselines, follow the investigation workflow from the `run-e2e-tests` command:
222
+
223
+ 1. **Parse test output** to identify which tests failed
224
+ 2. **Read the last run status**: `cat e2e/test-results/.last-run.json`
225
+ 3. **Read monocart report**: `cat e2e/monocart-report/index.json`
226
+ 4. **Analyze failure type**: snapshot mismatch, element not found, timeout, assertion failure
227
+ 5. **Read screenshots** from `e2e/test-results/` for visual context
228
+ 6. **Fix and re-run** until all tests pass
229
+
230
+ ### 4.4 Common Fixes
231
+
232
+ - **Element not found**: Add `waitForPageLoaded()` or check the selector matches the DOM
233
+ - **Timeout**: Add proper waits for API responses using `waitForAppIdle()`
234
+ - **Toast interfering with screenshot**: Add `maskToast(page)` before the screenshot assertion
235
+ - **Animation causing flaky snapshots**: The fixture auto-disables them, but custom CSS animations may need attention
236
+
237
+ ## Step 5: Report Summary
238
+
239
+ After completing the process, provide:
240
+
241
+ - **Changes analyzed**: List of files categorized by type
242
+ - **Tests created**: List of new spec files with test names
243
+ - **Test results**: Pass/fail status
244
+ - **Coverage gaps**: Any areas that could not be automatically tested (e.g., require auth, complex multi-step workflows)
245
+
246
+ ## Important Notes
247
+
248
+ - **Tests run in Docker** for consistent snapshots — never update snapshots outside Docker
249
+ - **Use stateless mode** as default for E2E tests
250
+ - **Follow existing patterns**: Match the coding style and structure of `example.spec.ts`
251
+ - **CRITICAL**: Always run tests in a NEW terminal separate from backend and frontend