qa-deck-backend 1.0.0

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,760 @@
1
+ /**
2
+ * QA Deck — CI/CD Config Generator
3
+ *
4
+ * Generates production-ready CI/CD pipeline configs:
5
+ * - GitHub Actions (.github/workflows/qa-tests.yml)
6
+ * - Jenkins (Jenkinsfile)
7
+ *
8
+ * Supports all 4 frameworks × multiple options:
9
+ * - Browser: chromium / firefox / webkit (Playwright) | chrome / firefox (Selenium)
10
+ * - Parallelism: matrix / sharding
11
+ * - Reporting: JUnit XML, HTML, Allure
12
+ * - Environments: staging / production, PR triggers
13
+ * - Notifications: Slack, email
14
+ */
15
+
16
+ // ─── Main entry ───────────────────────────────────────────────────────────────
17
+
18
+ function generateCICD(options = {}) {
19
+ const {
20
+ framework = "selenium-python",
21
+ projectName = "qa-deck",
22
+ pageType = "page",
23
+ baseUrl = "https://staging.example.com",
24
+ browsers = ["chromium"],
25
+ parallel = false,
26
+ reporters = ["html", "junit"],
27
+ slackWebhook = false,
28
+ emailNotify = false,
29
+ branches = ["main", "develop"],
30
+ prTrigger = true,
31
+ testCaseCount = 10,
32
+ useAllure = false,
33
+ nodeVersion = "20",
34
+ pythonVersion = "3.11",
35
+ javaVersion = "17",
36
+ } = options;
37
+
38
+ const ctx = {
39
+ framework,
40
+ projectName: slugify(projectName),
41
+ pageType,
42
+ baseUrl,
43
+ browsers,
44
+ parallel,
45
+ reporters,
46
+ slackWebhook,
47
+ emailNotify,
48
+ branches,
49
+ prTrigger,
50
+ testCaseCount,
51
+ useAllure,
52
+ nodeVersion,
53
+ pythonVersion,
54
+ javaVersion,
55
+ isPlaywright: framework.startsWith("playwright"),
56
+ isPython: framework.includes("python"),
57
+ isJava: framework.includes("java"),
58
+ isTypeScript: framework.includes("typescript"),
59
+ ext: framework.includes("java") ? "java" : framework.includes("typescript") ? "ts" : "py",
60
+ runner: getRunner(framework),
61
+ installCmd: getInstallCmd(framework),
62
+ testCmd: getTestCmd(framework, parallel, reporters, useAllure),
63
+ cacheKey: getCacheKey(framework),
64
+ };
65
+
66
+ return {
67
+ githubActions: {
68
+ filename: ".github/workflows/qa-tests.yml",
69
+ content: generateGitHubActions(ctx),
70
+ },
71
+ jenkins: {
72
+ filename: "Jenkinsfile",
73
+ content: generateJenkinsfile(ctx),
74
+ },
75
+ dockerCompose: {
76
+ filename: "docker-compose.ci.yml",
77
+ content: generateDockerCompose(ctx),
78
+ },
79
+ makefileTargets: {
80
+ filename: "Makefile",
81
+ content: generateMakefile(ctx),
82
+ },
83
+ };
84
+ }
85
+
86
+ // ─── GitHub Actions ───────────────────────────────────────────────────────────
87
+
88
+ function generateGitHubActions(ctx) {
89
+ const { framework, projectName, baseUrl, browsers, parallel, branches, prTrigger,
90
+ slackWebhook, emailNotify, useAllure, nodeVersion, pythonVersion, javaVersion,
91
+ isPlaywright, isPython, isJava, isTypeScript, testCmd, reporters } = ctx;
92
+
93
+ const branchList = branches.map(b => ` - ${b}`).join("\n");
94
+ const browserMatrix = parallel && browsers.length > 1
95
+ ? ` strategy:
96
+ fail-fast: false
97
+ matrix:
98
+ browser: [${browsers.map(b => `"${b}"`).join(", ")}]`
99
+ : "";
100
+
101
+ const browserEnv = parallel && browsers.length > 1
102
+ ? ` BROWSER: \${{ matrix.browser }}`
103
+ : ` BROWSER: ${browsers[0]}`;
104
+
105
+ const setupSteps = isJava ? `
106
+ - name: Set up JDK ${javaVersion}
107
+ uses: actions/setup-java@v4
108
+ with:
109
+ java-version: '${javaVersion}'
110
+ distribution: 'temurin'
111
+ cache: maven
112
+
113
+ - name: Cache Maven packages
114
+ uses: actions/cache@v4
115
+ with:
116
+ path: ~/.m2
117
+ key: \${{ runner.os }}-m2-\${{ hashFiles('**/pom.xml') }}
118
+ restore-keys: \${{ runner.os }}-m2` :
119
+ isTypeScript ? `
120
+ - name: Set up Node.js ${nodeVersion}
121
+ uses: actions/setup-node@v4
122
+ with:
123
+ node-version: '${nodeVersion}'
124
+ cache: 'npm'
125
+
126
+ - name: Install dependencies
127
+ run: npm ci
128
+
129
+ - name: Install Playwright browsers
130
+ run: npx playwright install --with-deps ${browsers.join(" ")}` :
131
+ /* Python */ `
132
+ - name: Set up Python ${pythonVersion}
133
+ uses: actions/setup-python@v5
134
+ with:
135
+ python-version: '${pythonVersion}'
136
+
137
+ - name: Cache pip packages
138
+ uses: actions/cache@v4
139
+ with:
140
+ path: ~/.cache/pip
141
+ key: \${{ runner.os }}-pip-\${{ hashFiles('**/requirements.txt') }}
142
+ restore-keys: \${{ runner.os }}-pip-
143
+
144
+ - name: Install dependencies
145
+ run: pip install -r requirements.txt${isPlaywright ? "\n\n - name: Install Playwright browsers\n run: playwright install --with-deps " + browsers.join(" ") : ""}`;
146
+
147
+ const testStep = isJava ? `
148
+ - name: Run tests
149
+ env:
150
+ BASE_URL: \${{ env.BASE_URL }}
151
+ ${browserEnv}
152
+ run: mvn test -Dsurefire.failIfNoSpecifiedTests=false
153
+
154
+ - name: Publish Test Results
155
+ uses: EnricoMi/publish-unit-test-result-action@v2
156
+ if: always()
157
+ with:
158
+ files: target/surefire-reports/*.xml` :
159
+ isTypeScript ? `
160
+ - name: Run Playwright tests
161
+ env:
162
+ BASE_URL: \${{ env.BASE_URL }}
163
+ ${browserEnv}
164
+ run: ${testCmd}
165
+
166
+ - name: Upload Playwright report
167
+ uses: actions/upload-artifact@v4
168
+ if: always()
169
+ with:
170
+ name: playwright-report-\${{ matrix.browser || '${browsers[0]}' }}
171
+ path: playwright-report/
172
+ retention-days: 30` :
173
+ /* Python */ `
174
+ - name: Run tests
175
+ env:
176
+ BASE_URL: \${{ env.BASE_URL }}
177
+ ${browserEnv}
178
+ run: ${testCmd}
179
+
180
+ - name: Upload test results
181
+ uses: actions/upload-artifact@v4
182
+ if: always()
183
+ with:
184
+ name: test-results-\${{ matrix.browser || '${browsers[0]}' }}
185
+ path: |
186
+ reports/
187
+ test-results.xml
188
+ retention-days: 30`;
189
+
190
+ const allureSteps = useAllure ? `
191
+ - name: Generate Allure report
192
+ uses: simple-elf/allure-report-action@master
193
+ if: always()
194
+ with:
195
+ allure_results: allure-results
196
+ allure_history: allure-history
197
+
198
+ - name: Deploy Allure report to GitHub Pages
199
+ uses: peaceiris/actions-gh-pages@v3
200
+ if: always()
201
+ with:
202
+ github_token: \${{ secrets.GITHUB_TOKEN }}
203
+ publish_branch: gh-pages
204
+ publish_dir: allure-history` : "";
205
+
206
+ const slackStep = slackWebhook ? `
207
+ - name: Notify Slack on failure
208
+ uses: 8398a7/action-slack@v3
209
+ if: failure()
210
+ with:
211
+ status: \${{ job.status }}
212
+ fields: repo,message,commit,author,action,eventName,ref,workflow
213
+ text: ':x: QA tests failed on \`\${{ github.ref_name }}\`'
214
+ env:
215
+ SLACK_WEBHOOK_URL: \${{ secrets.SLACK_WEBHOOK_URL }}` : "";
216
+
217
+ const emailStep = emailNotify ? `
218
+ - name: Send email on failure
219
+ uses: dawidd6/action-send-mail@v3
220
+ if: failure()
221
+ with:
222
+ server_address: smtp.gmail.com
223
+ server_port: 465
224
+ username: \${{ secrets.MAIL_USERNAME }}
225
+ password: \${{ secrets.MAIL_PASSWORD }}
226
+ subject: "QA Tests Failed — \${{ github.repository }} (\${{ github.ref_name }})"
227
+ body: "Test suite failed on commit \${{ github.sha }}. View run: \${{ github.server_url }}/\${{ github.repository }}/actions/runs/\${{ github.run_id }}"
228
+ to: \${{ secrets.NOTIFY_EMAIL }}
229
+ from: QA Deck` : "";
230
+
231
+ const prSection = prTrigger ? `
232
+ pull_request:
233
+ branches:
234
+ ${branchList}
235
+ types: [opened, synchronize, reopened]` : "";
236
+
237
+ return `# QA Deck — Generated GitHub Actions Workflow
238
+ # Framework: ${framework}
239
+ # Generated: ${new Date().toISOString()}
240
+ #
241
+ # Required secrets:
242
+ # BASE_URL_STAGING — staging environment URL${slackWebhook ? "\n# SLACK_WEBHOOK_URL — Slack incoming webhook" : ""}${emailNotify ? "\n# MAIL_USERNAME, MAIL_PASSWORD, NOTIFY_EMAIL — email alerts" : ""}
243
+
244
+ name: QA Tests — ${projectName}
245
+
246
+ on:
247
+ push:
248
+ branches:
249
+ ${branchList}
250
+ ${prSection}
251
+ workflow_dispatch:
252
+ inputs:
253
+ environment:
254
+ description: 'Target environment'
255
+ required: true
256
+ default: 'staging'
257
+ type: choice
258
+ options: [staging, production]
259
+ browser:
260
+ description: 'Browser to test'
261
+ required: false
262
+ default: '${browsers[0]}'
263
+ type: choice
264
+ options: [${isPlaywright ? "chromium, firefox, webkit" : "chrome, firefox"}]
265
+
266
+ env:
267
+ BASE_URL: \${{ secrets.BASE_URL_STAGING || '${baseUrl}' }}
268
+ CI: true
269
+
270
+ jobs:
271
+ qa-tests:
272
+ name: QA Tests${parallel && browsers.length > 1 ? " (${{ matrix.browser }})" : ""}
273
+ runs-on: ubuntu-latest
274
+ timeout-minutes: 30
275
+ ${browserMatrix}
276
+
277
+ steps:
278
+ - name: Checkout repository
279
+ uses: actions/checkout@v4
280
+
281
+ - name: Set up environment variables
282
+ run: |
283
+ echo "RUN_ID=\${{ github.run_id }}" >> \$GITHUB_ENV
284
+ echo "COMMIT_SHA=\${{ github.sha }}" >> \$GITHUB_ENV
285
+ echo "BRANCH_NAME=\${{ github.ref_name }}" >> \$GITHUB_ENV
286
+ ${setupSteps}
287
+ ${testStep}
288
+ ${allureSteps}
289
+ ${slackStep}
290
+ ${emailStep}
291
+
292
+ # ── Summary job — runs after all matrix jobs complete ──────────────────────
293
+ summary:
294
+ name: Test Summary
295
+ runs-on: ubuntu-latest
296
+ needs: qa-tests
297
+ if: always()
298
+ steps:
299
+ - name: Check result
300
+ run: |
301
+ if [ "\${{ needs.qa-tests.result }}" = "success" ]; then
302
+ echo "✅ All QA tests passed"
303
+ else
304
+ echo "❌ QA tests failed — result: \${{ needs.qa-tests.result }}"
305
+ exit 1
306
+ fi
307
+ `.trimStart();
308
+ }
309
+
310
+ // ─── Jenkinsfile ──────────────────────────────────────────────────────────────
311
+
312
+ function generateJenkinsfile(ctx) {
313
+ const { framework, projectName, baseUrl, browsers, parallel, branches, slackWebhook,
314
+ emailNotify, useAllure, nodeVersion, pythonVersion, javaVersion,
315
+ isPlaywright, isPython, isJava, isTypeScript, testCmd } = ctx;
316
+
317
+ const agentLabel = isJava ? "java-agent" : "python-agent";
318
+
319
+ const toolBlock = isJava ? `
320
+ jdk 'JDK-${javaVersion}'
321
+ maven 'Maven-3.9'` :
322
+ isTypeScript ? `
323
+ nodejs 'Node-${nodeVersion}'` :
324
+ `
325
+ // Python available via system or pyenv`;
326
+
327
+ const installStage = isJava ? `
328
+ stage('Install Dependencies') {
329
+ steps {
330
+ sh 'mvn dependency:resolve -q'
331
+ }
332
+ }` :
333
+ isTypeScript ? `
334
+ stage('Install Dependencies') {
335
+ steps {
336
+ sh 'npm ci'
337
+ sh 'npx playwright install --with-deps ${browsers.join(" ")}'
338
+ }
339
+ }` :
340
+ `
341
+ stage('Install Dependencies') {
342
+ steps {
343
+ sh '''
344
+ python${pythonVersion.split('.')[0]} -m venv .venv
345
+ . .venv/bin/activate
346
+ pip install --upgrade pip -q
347
+ pip install -r requirements.txt -q${isPlaywright ? "\n playwright install --with-deps " + browsers.join(" ") : ""}
348
+ '''
349
+ }
350
+ }`;
351
+
352
+ const testStage = isJava ? `
353
+ stage('Run Tests') {
354
+ steps {
355
+ script {
356
+ try {
357
+ sh """
358
+ mvn test \\
359
+ -DBASE_URL=\${BASE_URL} \\
360
+ -DBROWSER=\${BROWSER}
361
+ """
362
+ } catch (err) {
363
+ currentBuild.result = 'UNSTABLE'
364
+ throw err
365
+ }
366
+ }
367
+ }
368
+ post {
369
+ always {
370
+ junit 'target/surefire-reports/*.xml'
371
+ archiveArtifacts artifacts: 'target/surefire-reports/**', allowEmptyArchive: true
372
+ }
373
+ }
374
+ }` :
375
+ isTypeScript ? `
376
+ stage('Run Tests') {
377
+ steps {
378
+ script {
379
+ try {
380
+ sh """
381
+ ${testCmd} \\
382
+ --reporter=junit \\
383
+ --output-dir=test-results
384
+ """
385
+ } catch (err) {
386
+ currentBuild.result = 'UNSTABLE'
387
+ throw err
388
+ }
389
+ }
390
+ }
391
+ post {
392
+ always {
393
+ junit 'test-results/*.xml'
394
+ publishHTML([
395
+ allowMissing: true,
396
+ alwaysLinkToLastBuild: true,
397
+ keepAll: true,
398
+ reportDir: 'playwright-report',
399
+ reportFiles: 'index.html',
400
+ reportName: 'Playwright Report'
401
+ ])
402
+ }
403
+ }
404
+ }` :
405
+ `
406
+ stage('Run Tests') {
407
+ steps {
408
+ script {
409
+ try {
410
+ sh """
411
+ . .venv/bin/activate
412
+ ${testCmd}
413
+ """
414
+ } catch (err) {
415
+ currentBuild.result = 'UNSTABLE'
416
+ throw err
417
+ }
418
+ }
419
+ }
420
+ post {
421
+ always {
422
+ junit 'reports/junit.xml'
423
+ publishHTML([
424
+ allowMissing: true,
425
+ alwaysLinkToLastBuild: true,
426
+ keepAll: true,
427
+ reportDir: 'reports/html',
428
+ reportFiles: 'index.html',
429
+ reportName: 'Test Report'
430
+ ])
431
+ }
432
+ }
433
+ }`;
434
+
435
+ const allureStage = useAllure ? `
436
+ stage('Allure Report') {
437
+ steps {
438
+ allure([
439
+ includeProperties: false,
440
+ jdk: '',
441
+ properties: [],
442
+ reportBuildPolicy: 'ALWAYS',
443
+ results: [[path: 'allure-results']]
444
+ ])
445
+ }
446
+ }` : "";
447
+
448
+ const notifyBlock = slackWebhook ? `
449
+ failure {
450
+ slackSend(
451
+ channel: '#qa-alerts',
452
+ color: 'danger',
453
+ message: """❌ *QA Tests FAILED*
454
+ Pipeline: \${env.JOB_NAME} #\${env.BUILD_NUMBER}
455
+ Branch: \${env.BRANCH_NAME}
456
+ URL: \${env.BUILD_URL}"""
457
+ )
458
+ }
459
+ success {
460
+ slackSend(
461
+ channel: '#qa-alerts',
462
+ color: 'good',
463
+ message: "✅ QA Tests passed — \${env.JOB_NAME} #\${env.BUILD_NUMBER}"
464
+ )
465
+ }` :
466
+ emailNotify ? `
467
+ failure {
468
+ emailext(
469
+ subject: "QA Tests FAILED — \${env.JOB_NAME} #\${env.BUILD_NUMBER}",
470
+ body: """<h3>QA Test Failure</h3>
471
+ <p>Pipeline: <a href="\${env.BUILD_URL}">\${env.JOB_NAME} #\${env.BUILD_NUMBER}</a></p>
472
+ <p>Branch: \${env.BRANCH_NAME}</p>""",
473
+ mimeType: 'text/html',
474
+ recipientProviders: [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
475
+ )
476
+ }` : `
477
+ failure {
478
+ echo 'Tests failed — check console output for details'
479
+ }`;
480
+
481
+ const parallelBrowsers = parallel && browsers.length > 1 ? `
482
+ stage('Cross-Browser Tests') {
483
+ parallel {
484
+ ${browsers.map(b => `stage('${b}') {
485
+ steps {
486
+ script {
487
+ env.BROWSER = '${b}'
488
+ }
489
+ sh 'echo "Running on browser: ${b}"'
490
+ }
491
+ }`).join("\n ")}
492
+ }
493
+ }` : "";
494
+
495
+ return `// QA Deck — Generated Jenkinsfile
496
+ // Framework: ${framework}
497
+ // Generated: ${new Date().toISOString()}
498
+ //
499
+ // Required Jenkins plugins:
500
+ // - Pipeline
501
+ // - HTML Publisher
502
+ // - JUnit
503
+ ${useAllure ? "// - Allure Jenkins Plugin" : ""}
504
+ ${slackWebhook ? "// - Slack Notification" : ""}
505
+ ${isTypeScript || isJava ? `// - NodeJS Plugin (Node ${nodeVersion})` : ""}
506
+ //
507
+ // Required Jenkins credentials:
508
+ // - BASE_URL — environment variable with target URL
509
+ ${slackWebhook ? "// - SLACK_TOKEN — Slack bot token" : ""}
510
+
511
+ pipeline {
512
+ agent {
513
+ label '${agentLabel}'
514
+ }
515
+
516
+ tools {${toolBlock}
517
+ }
518
+
519
+ options {
520
+ timeout(time: 30, unit: 'MINUTES')
521
+ buildDiscarder(logRotator(numToKeepStr: '20'))
522
+ disableConcurrentBuilds()
523
+ timestamps()
524
+ }
525
+
526
+ triggers {
527
+ // Poll SCM every 15 minutes (or use webhooks)
528
+ pollSCM('H/15 * * * *')
529
+ }
530
+
531
+ parameters {
532
+ choice(
533
+ name: 'ENVIRONMENT',
534
+ choices: ['staging', 'production'],
535
+ description: 'Target environment'
536
+ )
537
+ choice(
538
+ name: 'BROWSER',
539
+ choices: [${isPlaywright ?
540
+ "'chromium', 'firefox', 'webkit'" :
541
+ "'chrome', 'firefox'"}],
542
+ description: 'Browser to run tests in'
543
+ )
544
+ booleanParam(
545
+ name: 'RUN_SMOKE_ONLY',
546
+ defaultValue: false,
547
+ description: 'Run only smoke tests (faster)'
548
+ )
549
+ }
550
+
551
+ environment {
552
+ BASE_URL = credentials('BASE_URL') ?: '${baseUrl}'
553
+ BROWSER = "\${params.BROWSER}"
554
+ CI = 'true'
555
+ REPORT_DIR = "reports/\${BUILD_NUMBER}"
556
+ }
557
+
558
+ stages {
559
+ stage('Checkout') {
560
+ steps {
561
+ checkout scm
562
+ sh 'git log --oneline -5'
563
+ }
564
+ }
565
+ ${installStage}
566
+ ${parallelBrowsers}
567
+ ${testStage}
568
+ ${allureStage}
569
+
570
+ stage('Archive Results') {
571
+ steps {
572
+ archiveArtifacts(
573
+ artifacts: 'reports/**,${isTypeScript ? "playwright-report/**" : ""}',
574
+ allowEmptyArchive: true,
575
+ fingerprint: true
576
+ )
577
+ }
578
+ }
579
+ }
580
+
581
+ post {
582
+ always {
583
+ cleanWs(cleanWhenNotBuilt: false, cleanWhenAborted: true, cleanWhenFailure: false)
584
+ }
585
+ ${notifyBlock}
586
+ }
587
+ }
588
+ `;
589
+ }
590
+
591
+ // ─── Docker Compose for local CI simulation ───────────────────────────────────
592
+
593
+ function generateDockerCompose(ctx) {
594
+ const { framework, baseUrl, browsers, isPlaywright, isJava, isTypeScript, pythonVersion, nodeVersion, javaVersion } = ctx;
595
+
596
+ const serviceImage = isJava
597
+ ? `maven:3.9-eclipse-temurin-${javaVersion}`
598
+ : isTypeScript
599
+ ? `mcr.microsoft.com/playwright:v1.40.0-jammy`
600
+ : isPlaywright
601
+ ? `mcr.microsoft.com/playwright/python:v1.40.0-jammy`
602
+ : `python:${pythonVersion}-slim`;
603
+
604
+ const installCmd = isJava
605
+ ? "mvn dependency:resolve -q"
606
+ : isTypeScript
607
+ ? "npm ci && npx playwright install"
608
+ : `pip install -r requirements.txt -q${isPlaywright ? " && playwright install" : ""}`;
609
+
610
+ const testCmd = isJava
611
+ ? "mvn test"
612
+ : isTypeScript
613
+ ? "npx playwright test --reporter=html"
614
+ : isPlaywright
615
+ ? "pytest tests/ -v --html=reports/report.html --self-contained-html"
616
+ : "pytest tests/ -v --html=reports/report.html --self-contained-html";
617
+
618
+ return `# QA Deck — Docker Compose for local CI simulation
619
+ # Usage: docker-compose -f docker-compose.ci.yml up --abort-on-container-exit
620
+
621
+ version: '3.8'
622
+
623
+ services:
624
+ qa-tests:
625
+ image: ${serviceImage}
626
+ working_dir: /app
627
+ volumes:
628
+ - .:/app
629
+ - /app/${isJava ? ".m2" : isTypeScript ? "node_modules" : ".venv"}
630
+ environment:
631
+ - BASE_URL=${baseUrl}
632
+ - BROWSER=${browsers[0]}
633
+ - CI=true
634
+ - PYTHONDONTWRITEBYTECODE=1
635
+ command: >
636
+ sh -c "${installCmd} &&
637
+ ${testCmd}"
638
+ ${!isJava && !isTypeScript && !isPlaywright ? `# For Selenium, you need a browser service:
639
+ depends_on:
640
+ - selenium-hub` : ""}
641
+
642
+ ${!isJava && !isTypeScript && !isPlaywright ? ` selenium-hub:
643
+ image: selenium/standalone-chrome:latest
644
+ ports:
645
+ - "4444:4444"
646
+ environment:
647
+ - SE_NODE_MAX_SESSIONS=3
648
+ shm_size: '2g'` : ""}
649
+
650
+ volumes:
651
+ ${isJava ? "maven-cache" : isTypeScript ? "node-cache" : "pip-cache"}:
652
+ `;
653
+ }
654
+
655
+ // ─── Makefile ─────────────────────────────────────────────────────────────────
656
+
657
+ function generateMakefile(ctx) {
658
+ const { framework, isJava, isTypeScript, isPlaywright, isPython, browsers, useAllure } = ctx;
659
+
660
+ const installTarget = isJava
661
+ ? `install:\n\tmvn dependency:resolve -q`
662
+ : isTypeScript
663
+ ? `install:\n\tnpm ci\n\tnpx playwright install --with-deps`
664
+ : `install:\n\tpip install -r requirements.txt${isPlaywright ? "\n\tplaywright install --with-deps" : ""}`;
665
+
666
+ const testTarget = isJava
667
+ ? `test:\n\tmvn test\n\ntest-smoke:\n\tmvn test -Dgroups=smoke\n\ntest-regression:\n\tmvn test -Dgroups=regression`
668
+ : isTypeScript
669
+ ? `test:\n\tnpx playwright test\n\ntest-smoke:\n\tnpx playwright test --grep @smoke\n\ntest-headed:\n\tnpx playwright test --headed\n\ntest-debug:\n\tnpx playwright test --debug`
670
+ : `test:\n\tpytest tests/ -v\n\ntest-smoke:\n\tpytest tests/ -v -m smoke\n\ntest-parallel:\n\tpytest tests/ -v -n auto\n\ntest-html:\n\tpytest tests/ -v --html=reports/report.html --self-contained-html`;
671
+
672
+ const browserTargets = browsers.map(b =>
673
+ `test-${b}:\n\tBROWSER=${b} ${isTypeScript ? `npx playwright test --project=${b}` : isJava ? `mvn test -DBROWSER=${b}` : `pytest tests/ -v`}`
674
+ ).join("\n\n");
675
+
676
+ const allureTargets = useAllure
677
+ ? `\nallure-open:\n\tallure serve allure-results\n\nallure-report:\n\tallure generate allure-results --clean -o allure-report`
678
+ : "";
679
+
680
+ return `.PHONY: install test clean help ${browsers.map(b => `test-${b}`).join(" ")}
681
+
682
+ # QA Deck — Makefile
683
+ # Generated: ${new Date().toISOString()}
684
+
685
+ ${installTarget}
686
+
687
+ ${testTarget}
688
+
689
+ ${browserTargets}
690
+
691
+ ${allureTargets}
692
+
693
+ clean:
694
+ ${isJava ? "\tmvn clean" : isTypeScript ? "\trm -rf test-results/ playwright-report/" : "\trm -rf reports/ .pytest_cache/ __pycache__/"}
695
+
696
+ ci: install test
697
+
698
+ docker-test:
699
+ \tdocker-compose -f docker-compose.ci.yml up --abort-on-container-exit --exit-code-from qa-tests
700
+
701
+ help:
702
+ \t@echo "Available targets:"
703
+ \t@grep -E '^[a-zA-Z_-]+:' Makefile | awk -F: '{print " " $$1}'
704
+ `;
705
+ }
706
+
707
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
708
+
709
+ function getRunner(framework) {
710
+ const map = {
711
+ "selenium-python": "pytest", "selenium-java": "TestNG",
712
+ "playwright-python": "pytest-playwright", "playwright-typescript": "Playwright Test",
713
+ };
714
+ return map[framework] || "pytest";
715
+ }
716
+
717
+ function getInstallCmd(framework) {
718
+ if (framework.includes("java")) return "mvn dependency:resolve";
719
+ if (framework.includes("typescript")) return "npm ci && npx playwright install";
720
+ if (framework.includes("playwright")) return "pip install -r requirements.txt && playwright install";
721
+ return "pip install -r requirements.txt";
722
+ }
723
+
724
+ function getTestCmd(framework, parallel, reporters, useAllure) {
725
+ const junitFlag = reporters.includes("junit");
726
+ const htmlFlag = reporters.includes("html");
727
+ const allureFlag = useAllure;
728
+
729
+ if (framework === "playwright-typescript") {
730
+ const flags = ["--reporter=list"];
731
+ if (junitFlag) flags.push("--reporter=junit");
732
+ if (allureFlag) flags.push("'allure-playwright'");
733
+ return `npx playwright test ${flags.join(" ")}`;
734
+ }
735
+ if (framework === "selenium-java" || framework === "playwright-java") {
736
+ return `mvn test${junitFlag ? " -Dsurefire.reportFormat=xml" : ""}`;
737
+ }
738
+ // Python (pytest)
739
+ const args = ["pytest tests/ -v"];
740
+ if (parallel) args.push("-n auto");
741
+ if (junitFlag) args.push("--junit-xml=reports/junit.xml");
742
+ if (htmlFlag) args.push("--html=reports/html/report.html --self-contained-html");
743
+ if (allureFlag) args.push("--alluredir=allure-results");
744
+ return args.join(" ");
745
+ }
746
+
747
+ function getCacheKey(framework) {
748
+ if (framework.includes("java")) return "~/.m2/repository";
749
+ if (framework.includes("typescript")) return "~/.npm";
750
+ return "~/.cache/pip";
751
+ }
752
+
753
+ function slugify(str) {
754
+ return (str || "project")
755
+ .toLowerCase()
756
+ .replace(/[^a-z0-9]+/g, "-")
757
+ .replace(/^-|-$/g, "");
758
+ }
759
+
760
+ module.exports = { generateCICD };